Skip to content

Commit 128bb0b

Browse files
committed
Refactor plan page state and improve streaming parsing
Refactored PlanPage state management to use explicit types and removed unused team config logic. Improved agent message streaming parsing and added a utility to simplify human clarification lines in PlanDataService. Enhanced WebSocketService logging for agent message streaming events.
1 parent 48a6926 commit 128bb0b

File tree

4 files changed

+84
-91
lines changed

4 files changed

+84
-91
lines changed

src/frontend/src/components/content/streaming/StreamingBufferMessage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import remarkGfm from "remark-gfm";
88
import rehypePrism from "rehype-prism";
99

1010
const renderBufferMessage = (streamingMessageBuffer: string) => {
11-
const [isExpanded, setIsExpanded] = useState(false);
12-
11+
const [isExpanded, setIsExpanded] = useState<boolean>(false);
12+
// console.log(`streamingMessageBuffer: ${streamingMessageBuffer}`);
1313
if (!streamingMessageBuffer || streamingMessageBuffer.trim() === "") return null;
1414

1515
const start = Math.max(0, streamingMessageBuffer.length - 500);

src/frontend/src/pages/PlanPage.tsx

Lines changed: 28 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -38,37 +38,30 @@ const PlanPage: React.FC = () => {
3838
const navigate = useNavigate();
3939
const { showToast, dismissToast } = useInlineToaster();
4040
const messagesContainerRef = useRef<HTMLDivElement>(null);
41-
const [input, setInput] = useState("");
41+
const [input, setInput] = useState<string>("");
4242
const [planData, setPlanData] = useState<ProcessedPlanData | any>(null);
43-
const [allPlans, setAllPlans] = useState<ProcessedPlanData[]>([]);
4443
const [loading, setLoading] = useState<boolean>(true);
4544
const [submittingChatDisableInput, setSubmittingChatDisableInput] = useState<boolean>(true);
46-
const [error, setError] = useState<Error | null>(null);
45+
const [errorLoading, setErrorLoading] = useState<boolean>(false);
4746
const [clarificationMessage, setClarificationMessage] = useState<ParsedUserClarification | null>(null);
48-
const [processingApproval, setProcessingApproval] = useState(false);
47+
const [processingApproval, setProcessingApproval] = useState<boolean>(false);
4948
const [planApprovalRequest, setPlanApprovalRequest] = useState<MPlanData | null>(null);
50-
const [reloadLeftList, setReloadLeftList] = useState(true);
51-
const [waitingForPlan, setWaitingForPlan] = useState(true);
49+
const [reloadLeftList, setReloadLeftList] = useState<boolean>(true);
50+
const [waitingForPlan, setWaitingForPlan] = useState<boolean>(true);
5251
const [showProcessingPlanSpinner, setShowProcessingPlanSpinner] = useState<boolean>(false);
5352
const [showApprovalButtons, setShowApprovalButtons] = useState<boolean>(true);
5453
// WebSocket connection state
55-
const [wsConnected, setWsConnected] = useState(false);
54+
const [wsConnected, setWsConnected] = useState<boolean>(false);
5655
const [streamingMessages, setStreamingMessages] = useState<StreamingPlanUpdate[]>([]);
5756
const [streamingMessageBuffer, setStreamingMessageBuffer] = useState<string>("");
5857

59-
6058
const [agentMessages, setAgentMessages] = useState<AgentMessageData[]>([]);
61-
// Team config state
62-
const [teamConfig, setTeamConfig] = useState<TeamConfig | null>(null);
63-
const [loadingTeamConfig, setLoadingTeamConfig] = useState(true);
6459

6560
// Plan approval state - track when plan is approved
66-
const [planApproved, setPlanApproved] = useState(false);
61+
const [planApproved, setPlanApproved] = useState<boolean>(false);
6762

6863
const [loadingMessage, setLoadingMessage] = useState<string>(loadingMessages[0]);
6964

70-
// Use ref to store the function to avoid stale closure issues
71-
const loadPlanDataRef = useRef<() => Promise<ProcessedPlanData[]>>();
7265
// Auto-scroll helper
7366
const scrollToBottom = useCallback(() => {
7467
setTimeout(() => {
@@ -114,7 +107,6 @@ const PlanPage: React.FC = () => {
114107
setPlanApprovalRequest(mPlanData);
115108
setWaitingForPlan(false);
116109
setShowProcessingPlanSpinner(false);
117-
// onPlanReceived?.(mPlanData);
118110
scrollToBottom();
119111
} else {
120112
console.error('❌ Failed to parse plan data', approvalRequest);
@@ -129,8 +121,9 @@ const PlanPage: React.FC = () => {
129121
const unsubscribe = webSocketService.on(WebsocketMessageType.AGENT_MESSAGE_STREAMING, (streamingMessage: any) => {
130122
// console.log('📋 Streaming Message', streamingMessage);
131123
// if is final true clear buffer and add final message to agent messages
132-
setStreamingMessageBuffer(prev => prev + streamingMessage.data.content);
133-
scrollToBottom();
124+
const line = PlanDataService.simplifyHumanClarification(streamingMessage.data.content);
125+
setStreamingMessageBuffer(prev => prev + line);
126+
//scrollToBottom();
134127

135128
});
136129

@@ -166,7 +159,7 @@ const PlanPage: React.FC = () => {
166159
useEffect(() => {
167160
const unsubscribe = webSocketService.on(WebsocketMessageType.AGENT_TOOL_MESSAGE, (toolMessage: any) => {
168161
console.log('📋 Tool Message', toolMessage);
169-
scrollToBottom();
162+
// scrollToBottom();
170163

171164
});
172165

@@ -184,7 +177,7 @@ const PlanPage: React.FC = () => {
184177
timestamp: Date.now(),
185178
steps: [], // intentionally always empty
186179
next_steps: [], // intentionally always empty
187-
content: finalMessage.data.content || '',
180+
content: "🎉🎉 " + (finalMessage.data.content || ''),
188181
raw_data: finalMessage.data || '',
189182
} as AgentMessageData;
190183
console.log('✅ Parsed final result message:', agentMessageData);
@@ -198,7 +191,6 @@ const PlanPage: React.FC = () => {
198191
return () => unsubscribe();
199192
}, [scrollToBottom]);
200193

201-
202194
//WebsocketMessageType.AGENT_MESSAGE
203195
useEffect(() => {
204196
const unsubscribe = webSocketService.on(WebsocketMessageType.AGENT_MESSAGE, (agentMessage: any) => {
@@ -288,80 +280,35 @@ const PlanPage: React.FC = () => {
288280
}
289281
}, [planId, loading]);
290282

291-
useEffect(() => {
292-
293-
const loadTeamConfig = async () => {
294-
try {
295-
setLoadingTeamConfig(true);
296-
const teams = await TeamService.getUserTeams();
297-
// Get the first team as default config, or you can implement logic to get current team
298-
const config = teams.length > 0 ? teams[0] : null;
299-
setTeamConfig(config);
300-
} catch (error) {
301-
console.error('Failed to load team config:', error);
302-
// Don't show error for team config loading - it's optional
303-
} finally {
304-
setLoadingTeamConfig(false);
305-
}
306-
};
307-
308-
loadTeamConfig();
309-
}, []);
310-
311-
// Helper function to convert PlanWithSteps to ProcessedPlanData
312-
const convertToProcessedPlanData = (planWithSteps: PlanWithSteps): ProcessedPlanData => {
313-
return PlanDataService.processPlanData(planWithSteps, []);
314-
};
315-
316283
// Create loadPlanData function with useCallback to memoize it
317284
const loadPlanData = useCallback(
318-
async (useCache = true): Promise<ProcessedPlanData[]> => {
319-
if (!planId) return [];
285+
async (useCache = true): Promise<ProcessedPlanData | null> => {
286+
if (!planId) return null;
320287

321288
setLoading(true);
322-
setError(null);
323289

324290
try {
325291
let actualPlanId = planId;
326292
let planResult: ProcessedPlanData | null = null;
327293

328-
if (actualPlanId && !planResult) {
329-
console.log("Fetching plan with ID:", actualPlanId);
330-
planResult = await PlanDataService.fetchPlanData(actualPlanId, useCache);
331-
console.log("Plan data loaded successfully");
332-
}
333294

334-
const allPlansWithSteps = await apiService.getPlans();
335-
const allPlansData = allPlansWithSteps.map(convertToProcessedPlanData);
336-
setAllPlans(allPlansData);
295+
console.log("Fetching plan with ID:", actualPlanId);
296+
planResult = await PlanDataService.fetchPlanData(actualPlanId, useCache);
297+
337298

338-
if (planResult?.plan?.id && planResult.plan.id !== actualPlanId) {
339-
console.log('Plan ID mismatch detected, redirecting...', {
340-
requested: actualPlanId,
341-
actual: planResult.plan.id
342-
});
343-
navigate(`/plan/${planResult.plan.id}`, { replace: true });
344-
}
345299

346300
setPlanData(planResult);
347-
return allPlansData;
301+
return planResult;
348302
} catch (err) {
349303
console.log("Failed to load plan data:", err);
350-
setError(
351-
err instanceof Error ? err : new Error("Failed to load plan data")
352-
);
353-
return [];
304+
return null;
354305
} finally {
355306
setLoading(false);
356307
}
357308
},
358309
[planId, navigate]
359310
);
360311

361-
// Update the ref whenever loadPlanData changes
362-
useEffect(() => {
363-
loadPlanDataRef.current = loadPlanData;
364-
}, [loadPlanData]);
365312

366313
// Handle plan approval
367314
const handleApprovePlan = useCallback(async () => {
@@ -418,6 +365,7 @@ const PlanPage: React.FC = () => {
418365
}
419366
}, [planApprovalRequest, planData, navigate, setProcessingApproval]);
420367
// Chat submission handler - updated for v3 backend compatibility
368+
421369
const handleOnchatSubmit = useCallback(
422370
async (chatInput: string) => {
423371
if (!chatInput.trim()) {
@@ -482,14 +430,17 @@ const PlanPage: React.FC = () => {
482430
}, [navigate]);
483431

484432

485-
486433
const resetReload = useCallback(() => {
487434
setReloadLeftList(false);
488435
}, []);
489436

490437
useEffect(() => {
491438
const initializePlanLoading = async () => {
492-
if (!planId) return;
439+
if (!planId) {
440+
441+
setErrorLoading(true);
442+
return;
443+
}
493444

494445
try {
495446
await loadPlanData(true);
@@ -501,7 +452,7 @@ const PlanPage: React.FC = () => {
501452
initializePlanLoading();
502453
}, [planId, loadPlanData]);
503454

504-
if (error) {
455+
if (errorLoading) {
505456
return (
506457
<CoralShellColumn>
507458
<CoralShellRow>
@@ -512,7 +463,7 @@ const PlanPage: React.FC = () => {
512463
color: 'var(--colorNeutralForeground2)'
513464
}}>
514465
<Text size={500}>
515-
{error.message || "An error occurred while loading the plan"}
466+
{"An error occurred while loading the plan"}
516467
</Text>
517468
</div>
518469
</Content>
@@ -532,7 +483,7 @@ const PlanPage: React.FC = () => {
532483
onTeamSelect={() => { }}
533484
onTeamUpload={async () => { }}
534485
isHomePage={false}
535-
selectedTeam={teamConfig}
486+
selectedTeam={null}
536487
/>
537488

538489
<Content>

src/frontend/src/services/PlanDataService.tsx

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -489,15 +489,21 @@ export class PlanDataService {
489489
* - { type: 'agent_message_streaming', data: "AgentMessageStreaming(agent_name='X', content='partial', is_final=False)" }
490490
* - "AgentMessageStreaming(agent_name='X', content='partial', is_final=False)"
491491
*/
492+
// Replace the body of parseAgentMessageStreaming with this improved version
492493
static parseAgentMessageStreaming(rawData: any): {
493494
agent: string;
494495
content: string;
495496
is_final: boolean;
496497
raw_data: any;
497498
} | null {
498499
try {
499-
// Unwrap wrapper
500-
if (rawData && typeof rawData === 'object' && rawData.type === 'agent_message_streaming' && typeof rawData.data === 'string') {
500+
// Unwrap wrapper object
501+
if (
502+
rawData &&
503+
typeof rawData === 'object' &&
504+
rawData.type === WebsocketMessageType.AGENT_MESSAGE_STREAMING &&
505+
typeof rawData.data === 'string'
506+
) {
501507
return this.parseAgentMessageStreaming(rawData.data);
502508
}
503509

@@ -511,13 +517,15 @@ export class PlanDataService {
511517
source.match(/agent_name="([^"]+)"/)?.[1] ||
512518
'UnknownAgent';
513519

514-
const contentMatch = source.match(/content='((?:\\'|[^'])*)'/);
515-
let content = contentMatch ? contentMatch[1] : '';
520+
// Support content='...' OR content="..." with escapes
521+
const contentMatch = source.match(/content=(?:"((?:\\.|[^"])*)"|'((?:\\.|[^'])*)')/);
522+
let content = contentMatch ? (contentMatch[1] ?? contentMatch[2] ?? '') : '';
516523
content = content
517524
.replace(/\\n/g, '\n')
518525
.replace(/\\'/g, "'")
519526
.replace(/\\"/g, '"')
520-
.replace(/\\\\/g, '\\');
527+
.replace(/\\\\/g, '\\')
528+
.trim();
521529

522530
let is_final = false;
523531
const finalMatch = source.match(/is_final=(True|False)/i);
@@ -531,8 +539,46 @@ export class PlanDataService {
531539
return null;
532540
}
533541
}
534-
// ...inside export class PlanDataService { (place near other parsers, e.g. after parseAgentMessageStreaming)
542+
// Place inside export class PlanDataService (near other parsers)
535543

544+
/**
545+
* Simplify a human clarification streaming line.
546+
* Input example:
547+
* Human clarification: UserClarificationResponse(request_id='uuid', answer='no more steps', plan_id='', m_plan_id='')
548+
* Output (markdown/plain):
549+
* Human clarification: no more steps
550+
* If pattern not found, returns the original string.
551+
*/
552+
static simplifyHumanClarification(line: string): string {
553+
if (
554+
typeof line !== 'string' ||
555+
!line.includes('Human clarification:') ||
556+
!line.includes('UserClarificationResponse(')
557+
) {
558+
return line;
559+
}
560+
561+
// Capture the inside of UserClarificationResponse(...)
562+
const outerMatch = line.match(/Human clarification:\s*UserClarificationResponse\((.*?)\)/s);
563+
if (!outerMatch) return line;
564+
565+
const inner = outerMatch[1];
566+
567+
// Find answer= '...' | "..."
568+
const answerMatch = inner.match(/answer=(?:"((?:\\.|[^"])*)"|'((?:\\.|[^'])*)')/);
569+
if (!answerMatch) return line;
570+
571+
let answer = answerMatch[1] ?? answerMatch[2] ?? '';
572+
// Unescape common sequences
573+
answer = answer
574+
.replace(/\\n/g, '\n')
575+
.replace(/\\'/g, "'")
576+
.replace(/\\"/g, '"')
577+
.replace(/\\\\/g, '\\')
578+
.trim();
579+
580+
return `Human clarification: ${answer}`;
581+
}
536582
/**
537583
* Parse a user clarification request message (possibly deeply nested).
538584
* Accepts objects like:

src/frontend/src/services/WebSocketService.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,6 @@ class WebSocketService {
181181

182182
private handleMessage(message: StreamMessage): void {
183183

184-
//console.log('WebSocket message received:', message);
185-
const hasClarification = /\bclarifications?\b/i.test(message.data || '');
186-
187-
if (hasClarification) {
188-
console.log("Message contains 'clarification':", message.data);
189-
}
190184
switch (message.type) {
191185
case WebsocketMessageType.PLAN_APPROVAL_REQUEST: {
192186
console.log("Message Plan Approval Request':", message);
@@ -217,8 +211,10 @@ class WebSocketService {
217211
}
218212

219213
case WebsocketMessageType.AGENT_MESSAGE_STREAMING: {
214+
console.log("Message streamming agent buffer:", message);
220215
if (message.data) {
221216
const streamedMessage = PlanDataService.parseAgentMessageStreaming(message);
217+
console.log('WebSocket AGENT_MESSAGE_STREAMING message received:', streamedMessage);
222218
this.emit(WebsocketMessageType.AGENT_MESSAGE_STREAMING, streamedMessage);
223219
}
224220
break;

0 commit comments

Comments
 (0)