Skip to content

Commit b0b90de

Browse files
committed
Refactor clarification and result message handling
Updated the HumanClarification model and related API/service methods to use new fields (request_id, answer, plan_id, m_plan_id). Improved PlanPage to handle clarification and final result messages via websocket, updating UI state and agent message history accordingly. Added robust parsing functions for user clarification and final result messages in PlanDataService.
1 parent ea315c9 commit b0b90de

File tree

5 files changed

+180
-45
lines changed

5 files changed

+180
-45
lines changed

src/frontend/src/api/apiService.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -290,14 +290,16 @@ export class APIService {
290290
* @returns Promise with response object
291291
*/
292292
async submitClarification(
293-
planId: string,
294-
sessionId: string,
295-
clarification: string
293+
request_id: string = "",
294+
answer: string = "",
295+
plan_id: string = "",
296+
m_plan_id: string = ""
296297
): Promise<{ status: string; session_id: string }> {
297298
const clarificationData: HumanClarification = {
298-
plan_id: planId,
299-
session_id: sessionId,
300-
human_clarification: clarification
299+
request_id,
300+
answer,
301+
plan_id,
302+
m_plan_id
301303
};
302304

303305
const response = await apiClient.post(
@@ -306,7 +308,7 @@ export class APIService {
306308
);
307309

308310
// Invalidate cached data
309-
this._cache.invalidate(new RegExp(`^(plan|steps)_${planId}`));
311+
this._cache.invalidate(new RegExp(`^(plan|steps)_${plan_id}`));
310312
this._cache.invalidate(new RegExp(`^plans_`));
311313

312314
return response;

src/frontend/src/models/agentMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ export interface AgentMessageData {
2828
next_steps: []; // intentionally always empty
2929
raw_content: string;
3030
raw_data: string;
31-
}
31+
}

src/frontend/src/models/messages.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,10 @@ export interface HumanFeedback {
6565
* Message containing human clarification on a plan
6666
*/
6767
export interface HumanClarification {
68-
/** Plan identifier */
68+
request_id: string;
69+
answer: string;
6970
plan_id: string;
70-
/** Session identifier */
71-
session_id: string;
72-
/** Clarification from human */
73-
human_clarification: string;
71+
m_plan_id: string;
7472
}
7573

7674
/**

src/frontend/src/pages/PlanPage.tsx

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef, useState, useMemo } from "react"
22
import { useParams, useNavigate } from "react-router-dom";
33
import { Spinner, Text } from "@fluentui/react-components";
44
import { PlanDataService } from "../services/PlanDataService";
5-
import { ProcessedPlanData, PlanWithSteps, WebsocketMessageType, MPlanData, AgentMessageData } from "../models";
5+
import { ProcessedPlanData, PlanWithSteps, WebsocketMessageType, MPlanData, AgentMessageData, AgentMessageType, ParsedUserClarification } from "../models";
66
import PlanChat from "../components/content/PlanChat";
77
import PlanPanelRight from "../components/content/PlanPanelRight";
88
import PlanPanelLeft from "../components/content/PlanPanelLeft";
@@ -42,8 +42,9 @@ const PlanPage: React.FC = () => {
4242
const [planData, setPlanData] = useState<ProcessedPlanData | any>(null);
4343
const [allPlans, setAllPlans] = useState<ProcessedPlanData[]>([]);
4444
const [loading, setLoading] = useState<boolean>(true);
45-
const [submittingChatDisableInput, setSubmittingChatDisableInput] = useState<boolean>(false);
45+
const [submittingChatDisableInput, setSubmittingChatDisableInput] = useState<boolean>(true);
4646
const [error, setError] = useState<Error | null>(null);
47+
const [clarificationMessage, setClarificationMessage] = useState<ParsedUserClarification | null>(null);
4748

4849
const [planApprovalRequest, setPlanApprovalRequest] = useState<MPlanData | null>(null);
4950
const [reloadLeftList, setReloadLeftList] = useState(true);
@@ -52,8 +53,7 @@ const PlanPage: React.FC = () => {
5253
const [wsConnected, setWsConnected] = useState(false);
5354
const [streamingMessages, setStreamingMessages] = useState<StreamingPlanUpdate[]>([]);
5455
const [streamingMessageBuffer, setStreamingMessageBuffer] = useState<string>("");
55-
// RAI Error state
56-
const [raiError, setRAIError] = useState<RAIErrorData | null>(null);
56+
5757

5858
const [agentMessages, setAgentMessages] = useState<AgentMessageData[]>([]);
5959
// Team config state
@@ -137,6 +137,19 @@ const PlanPage: React.FC = () => {
137137
useEffect(() => {
138138
const unsubscribe = webSocketService.on(WebsocketMessageType.USER_CLARIFICATION_REQUEST, (clarificationMessage: any) => {
139139
console.log('📋 Clarification Message', clarificationMessage);
140+
const agentMessageData = {
141+
agent: 'ProxyAgent',
142+
agent_type: AgentMessageType.AI_AGENT,
143+
timestamp: clarificationMessage.timestamp || Date.now(),
144+
steps: [], // intentionally always empty
145+
next_steps: [], // intentionally always empty
146+
raw_content: clarificationMessage.data.question || '',
147+
raw_data: clarificationMessage.data || '',
148+
} as AgentMessageData;
149+
console.log('✅ Parsed clarification message:', agentMessageData);
150+
setClarificationMessage(clarificationMessage.data as ParsedUserClarification | null);
151+
setAgentMessages(prev => [...prev, agentMessageData]);
152+
setSubmittingChatDisableInput(false);
140153
scrollToBottom();
141154

142155
});
@@ -154,6 +167,31 @@ const PlanPage: React.FC = () => {
154167
return () => unsubscribe();
155168
}, [scrollToBottom]);
156169

170+
171+
//WebsocketMessageType.FINAL_RESULT_MESSAGE
172+
useEffect(() => {
173+
const unsubscribe = webSocketService.on(WebsocketMessageType.FINAL_RESULT_MESSAGE, (finalMessage: any) => {
174+
console.log('📋 Final Result Message', finalMessage);
175+
const agentMessageData = {
176+
agent: 'ProxyAgent',
177+
agent_type: AgentMessageType.AI_AGENT,
178+
timestamp: Date.now(),
179+
steps: [], // intentionally always empty
180+
next_steps: [], // intentionally always empty
181+
raw_content: finalMessage.content || '',
182+
raw_data: finalMessage || '',
183+
} as AgentMessageData;
184+
console.log('✅ Parsed final result message:', agentMessageData);
185+
setAgentMessages(prev => [...prev, agentMessageData]);
186+
setSubmittingChatDisableInput(true);
187+
scrollToBottom();
188+
189+
});
190+
191+
return () => unsubscribe();
192+
}, [scrollToBottom]);
193+
194+
157195
//WebsocketMessageType.AGENT_MESSAGE
158196
useEffect(() => {
159197
const unsubscribe = webSocketService.on(WebsocketMessageType.AGENT_MESSAGE, (agentMessage: any) => {
@@ -332,17 +370,32 @@ const PlanPage: React.FC = () => {
332370

333371
try {
334372
// Use legacy method for non-v3 backends
335-
await PlanDataService.submitClarification(
336-
planData.plan.id,
337-
planData.plan.session_id,
338-
chatInput
339-
);
340-
373+
const response = await PlanDataService.submitClarification({
374+
request_id: clarificationMessage?.request_id || "",
375+
answer: chatInput,
376+
plan_id: planData?.plan.id,
377+
m_plan_id: planApprovalRequest?.id || ""
378+
});
341379

380+
console.log("Clarification submitted successfully:", response);
342381
setInput("");
343382
dismissToast(id);
344383
showToast("Clarification submitted successfully", "success");
345-
await loadPlanData(false);
384+
setClarificationMessage(null);
385+
const agentMessageData = {
386+
agent: 'You',
387+
agent_type: AgentMessageType.HUMAN_AGENT,
388+
timestamp: Date.now(),
389+
steps: [], // intentionally always empty
390+
next_steps: [], // intentionally always empty
391+
raw_content: chatInput || '',
392+
raw_data: chatInput || '',
393+
} as AgentMessageData;
394+
395+
setAgentMessages(prev => [...prev, agentMessageData]);
396+
setSubmittingChatDisableInput(true);
397+
scrollToBottom();
398+
346399
} catch (error: any) {
347400
dismissToast(id);
348401

src/frontend/src/services/PlanDataService.tsx

Lines changed: 103 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class PlanDataService {
4848
messages: PlanMessage[]
4949
): ProcessedPlanData {
5050
// Extract unique agents from steps
51-
console.log("Processing plan data for plan ID:", plan);
51+
5252
const uniqueAgents = new Set<AgentType>();
5353
if (plan.steps && plan.steps.length > 0) {
5454
plan.steps.forEach((step) => {
@@ -141,13 +141,19 @@ export class PlanDataService {
141141
* @param clarification Clarification text
142142
* @returns Promise with API response
143143
*/
144-
static async submitClarification(
145-
planId: string,
146-
sessionId: string,
147-
clarification: string
148-
) {
144+
static async submitClarification({
145+
request_id,
146+
answer,
147+
plan_id,
148+
m_plan_id
149+
}: {
150+
request_id: string;
151+
answer: string;
152+
plan_id: string;
153+
m_plan_id: string;
154+
}) {
149155
try {
150-
return apiService.submitClarification(planId, sessionId, clarification);
156+
return apiService.submitClarification(request_id, answer, plan_id, m_plan_id);
151157
} catch (error) {
152158
console.log("Failed to submit clarification:", error);
153159
throw error;
@@ -536,22 +542,27 @@ export class PlanDataService {
536542
* }
537543
* Returns ParsedUserClarification or null if not parsable.
538544
*/
545+
// ...existing code...
546+
/**
547+
* Parse a user clarification request message (possibly deeply nested).
548+
* Enhanced to support:
549+
* - question in single OR double quotes
550+
* - request_id in single OR double quotes
551+
* - escaped newline / quote sequences
552+
*/
539553
static parseUserClarificationRequest(rawData: any): ParsedUserClarification | null {
540554
try {
541-
// Depth-first search to locate the innermost string starting with UserClarificationRequest(
542555
const extractString = (val: any, depth = 0): string | null => {
543-
if (depth > 10) return null; // safety
556+
if (depth > 15) return null;
544557
if (typeof val === 'string') {
545-
if (val.startsWith('UserClarificationRequest(')) return val;
546-
return null;
558+
return val.startsWith('UserClarificationRequest(') ? val : null;
547559
}
548560
if (val && typeof val === 'object') {
549-
// Common pattern: { type: 'user_clarification_request', data: ... }
561+
// Prefer .data traversal
550562
if (val.data !== undefined) {
551563
const inner = extractString(val.data, depth + 1);
552564
if (inner) return inner;
553565
}
554-
// Also scan other enumerable props just in case
555566
for (const k of Object.keys(val)) {
556567
if (k === 'data') continue;
557568
const inner = extractString(val[k], depth + 1);
@@ -564,21 +575,23 @@ export class PlanDataService {
564575
const source = extractString(rawData);
565576
if (!source) return null;
566577

567-
// Extract question and request_id
568-
// question='...'
569-
const questionMatch = source.match(/question='((?:\\'|[^'])*)'/);
570-
const requestIdMatch = source.match(/request_id='([a-fA-F0-9-]+)'/);
571-
572-
if (!questionMatch || !requestIdMatch) return null;
578+
// question=( "...") OR ('...')
579+
const questionRegex = /question=(?:"((?:\\.|[^"])*)"|'((?:\\.|[^'])*)')/;
580+
const qMatch = source.match(questionRegex);
581+
if (!qMatch) return null;
573582

574-
let question = questionMatch[1]
583+
let question = (qMatch[1] ?? qMatch[2] ?? '')
575584
.replace(/\\n/g, '\n')
576585
.replace(/\\'/g, "'")
577586
.replace(/\\"/g, '"')
578587
.replace(/\\\\/g, '\\')
579588
.trim();
580589

581-
const request_id = requestIdMatch[1];
590+
// request_id='uuid' or "uuid"
591+
const requestIdRegex = /request_id=(?:"([a-fA-F0-9-]+)"|'([a-fA-F0-9-]+)')/;
592+
const rMatch = source.match(requestIdRegex);
593+
if (!rMatch) return null;
594+
const request_id = rMatch[1] ?? rMatch[2];
582595

583596
return {
584597
type: WebsocketMessageType.USER_CLARIFICATION_REQUEST,
@@ -590,6 +603,75 @@ export class PlanDataService {
590603
return null;
591604
}
592605
}
606+
// ...inside export class PlanDataService (place near other parsers) ...
607+
608+
/**
609+
* Parse a final result message (possibly nested).
610+
* Accepts structures like:
611+
* {
612+
* type: 'final_result_message',
613+
* data: { type: 'final_result_message', data: { content: '...', status: 'completed', timestamp: 12345.6 } }
614+
* }
615+
* Returns null if not parsable.
616+
*/
617+
static parseFinalResultMessage(rawData: any): {
618+
type: WebsocketMessageType;
619+
content: string;
620+
status: string;
621+
timestamp: number | null;
622+
raw_data: any;
623+
} | null {
624+
try {
625+
const extractPayload = (val: any, depth = 0): any => {
626+
if (depth > 10) return null;
627+
if (!val || typeof val !== 'object') return null;
628+
// If it has content & status, assume it's the payload
629+
if (('content' in val) && ('status' in val)) return val;
630+
if ('data' in val) {
631+
const inner = extractPayload(val.data, depth + 1);
632+
if (inner) return inner;
633+
}
634+
// Scan other keys as fallback
635+
for (const k of Object.keys(val)) {
636+
if (k === 'data') continue;
637+
const inner = extractPayload(val[k], depth + 1);
638+
if (inner) return inner;
639+
}
640+
return null;
641+
};
642+
643+
const payload = extractPayload(rawData);
644+
if (!payload) return null;
645+
646+
let content = typeof payload.content === 'string' ? payload.content : '';
647+
content = content
648+
.replace(/\\n/g, '\n')
649+
.replace(/\\'/g, "'")
650+
.replace(/\\"/g, '"')
651+
.replace(/\\\\/g, '\\')
652+
.trim();
653+
654+
const statusRaw = (payload.status || 'completed').toString().trim();
655+
const status = statusRaw.toLowerCase();
656+
657+
let timestamp: number | null = null;
658+
if (payload.timestamp != null) {
659+
const num = Number(payload.timestamp);
660+
if (!Number.isNaN(num)) timestamp = num;
661+
}
662+
663+
return {
664+
type: WebsocketMessageType.FINAL_RESULT_MESSAGE,
665+
content,
666+
status,
667+
timestamp,
668+
raw_data: rawData
669+
};
670+
} catch (e) {
671+
console.error('parseFinalResultMessage failed:', e);
672+
return null;
673+
}
674+
}
593675

594676

595677
}

0 commit comments

Comments
 (0)