Skip to content

Commit 85d3525

Browse files
committed
Refactor plan approval state management
Moves plan approval request and waiting state management from PlanChat and PlanPanelRight into PlanPage, passing them as props. This centralizes WebSocket handling and plan state, simplifies component logic, and ensures consistent plan approval UI updates across components.
1 parent 40da5e9 commit 85d3525

File tree

4 files changed

+119
-133
lines changed

4 files changed

+119
-133
lines changed

src/frontend/src/components/content/PlanChat.tsx

Lines changed: 18 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ import PlanChatBody from "./PlanChatBody";
3434
interface SimplifiedPlanChatProps extends PlanChatProps {
3535
onPlanReceived?: (planData: MPlanData) => void;
3636
initialTask?: string;
37+
planApprovalRequest: MPlanData | null;
38+
waitingForPlan: boolean;
39+
messagesContainerRef: React.RefObject<HTMLDivElement>;
3740
}
3841

3942
const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
@@ -45,67 +48,25 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
4548
onPlanApproval,
4649
onPlanReceived,
4750
initialTask,
51+
planApprovalRequest,
52+
waitingForPlan,
53+
messagesContainerRef
54+
4855
}) => {
4956
const navigate = useNavigate();
50-
const messagesContainerRef = useRef<HTMLDivElement>(null);
57+
5158
const { showToast, dismissToast } = useInlineToaster();
5259
// States
53-
const [planApprovalRequest, setPlanApprovalRequest] = useState<MPlanData | null>(null);
60+
5461
const [processingApproval, setProcessingApproval] = useState(false);
55-
const [waitingForPlan, setWaitingForPlan] = useState(true);
56-
const [userFeedback, setUserFeedback] = useState('');
57-
const [showFeedbackInput, setShowFeedbackInput] = useState(false);
62+
63+
const [showApprovalButtons, setShowApprovalButtons] = useState(true);
64+
5865

5966

60-
// Auto-scroll helper
61-
const scrollToBottom = useCallback(() => {
62-
setTimeout(() => {
63-
if (messagesContainerRef.current) {
64-
messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
65-
}
66-
}, 100);
67-
}, []);
6867

6968
// Listen for m_plan streaming
70-
useEffect(() => {
71-
const unsubscribe = webSocketService.on(WebsocketMessageType.PLAN_APPROVAL_REQUEST, (approvalRequest: any) => {
72-
console.log('📋 Plan received:', approvalRequest);
73-
74-
let mPlanData: MPlanData | null = null;
75-
76-
// Handle the different message structures
77-
if (approvalRequest.parsedData) {
78-
// Direct parsedData property
79-
mPlanData = approvalRequest.parsedData;
80-
} else if (approvalRequest.data && typeof approvalRequest.data === 'object') {
81-
// Data property with nested object
82-
if (approvalRequest.data.parsedData) {
83-
mPlanData = approvalRequest.data.parsedData;
84-
} else {
85-
// Try to parse the data object directly
86-
mPlanData = approvalRequest.data;
87-
}
88-
} else if (approvalRequest.rawData) {
89-
// Parse the raw data string
90-
mPlanData = PlanDataService.parsePlanApprovalRequest(approvalRequest.rawData);
91-
} else {
92-
// Try to parse the entire object
93-
mPlanData = PlanDataService.parsePlanApprovalRequest(approvalRequest);
94-
}
95-
96-
if (mPlanData) {
97-
console.log('✅ Parsed plan data:', mPlanData);
98-
setPlanApprovalRequest(mPlanData);
99-
setWaitingForPlan(false);
100-
onPlanReceived?.(mPlanData);
101-
scrollToBottom();
102-
} else {
103-
console.error('❌ Failed to parse plan data', approvalRequest);
104-
}
105-
});
106-
107-
return () => unsubscribe();
108-
}, [onPlanReceived, scrollToBottom]);
69+
10970

11071
// Handle plan approval
11172
const handleApprovePlan = useCallback(async () => {
@@ -118,12 +79,12 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
11879
m_plan_id: planApprovalRequest.id,
11980
plan_id: planData?.plan?.id,
12081
approved: true,
121-
feedback: userFeedback || 'Plan approved by user'
82+
feedback: 'Plan approved by user'
12283
});
12384

12485
dismissToast(id);
125-
setShowFeedbackInput(false);
12686
onPlanApproval?.(true);
87+
setShowApprovalButtons(false);
12788

12889
} catch (error) {
12990
dismissToast(id);
@@ -132,7 +93,7 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
13293
} finally {
13394
setProcessingApproval(false);
13495
}
135-
}, [planApprovalRequest, planData, userFeedback, onPlanApproval]);
96+
}, [planApprovalRequest, planData, onPlanApproval]);
13697

13798
// Handle plan rejection
13899
const handleRejectPlan = useCallback(async () => {
@@ -145,7 +106,7 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
145106
m_plan_id: planApprovalRequest.id,
146107
plan_id: planData?.plan?.id,
147108
approved: false,
148-
feedback: userFeedback || 'Plan rejected by user'
109+
feedback: 'Plan rejected by user'
149110
});
150111

151112
dismissToast(id);
@@ -192,7 +153,7 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
192153
{renderThinkingState(waitingForPlan)}
193154

194155
{/* Plan response with all information */}
195-
{renderPlanResponse(planApprovalRequest, handleApprovePlan, handleRejectPlan, processingApproval)}
156+
{renderPlanResponse(planApprovalRequest, handleApprovePlan, handleRejectPlan, processingApproval, showApprovalButtons)}
196157
</div>
197158

198159
{/* Chat Input - only show if no plan is waiting for approval */}

src/frontend/src/components/content/PlanPanelRight.tsx

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ interface PlanPanelRightProps {
2828
planApproved: boolean;
2929
onPlanApproval?: (approved: boolean) => void;
3030
wsConnected?: boolean;
31+
planApprovalRequest: MPlanData | null;
32+
3133
}
3234

3335
interface GroupedMessage {
@@ -56,9 +58,9 @@ const PlanPanelRight: React.FC<PlanPanelRightProps> = ({
5658
streamingMessages = [],
5759
planApproved,
5860
wsConnected = false,
61+
planApprovalRequest
5962
}) => {
6063
const [groupedStreamingMessages, setGroupedStreamingMessages] = useState<GroupedMessage[]>([]);
61-
const [planApprovalRequest, setPlanApprovalRequest] = useState<MPlanData | null>(null);
6264
const [hasStreamingStarted, setHasStreamingStarted] = useState(false);
6365

6466
// Helper function to get clean agent display name
@@ -107,24 +109,7 @@ const PlanPanelRight: React.FC<PlanPanelRightProps> = ({
107109
}
108110
}, [streamingMessages, hasStreamingStarted]);
109111

110-
// Add WebSocket listener for plan approval requests - but only store, don't display until streaming starts
111-
useEffect(() => {
112-
const unsubscribePlanApproval = webSocketService.onPlanApprovalRequest((approvalRequest) => {
113-
if (approvalRequest.parsedData) {
114-
const parsedData = PlanDataService.parsePlanApprovalRequest(approvalRequest);
115-
if (parsedData) {
116-
console.log('📥 Right panel received plan approval request:', parsedData);
117-
setPlanApprovalRequest(parsedData);
118-
// Reset states when new plan comes in
119-
setHasStreamingStarted(false);
120-
}
121-
}
122-
});
123112

124-
return () => {
125-
unsubscribePlanApproval();
126-
};
127-
}, []);
128113

129114
// Group streaming messages by agent
130115
const groupStreamingMessages = useCallback((messages: StreamingPlanUpdate[]): GroupedMessage[] => {
@@ -170,7 +155,7 @@ const PlanPanelRight: React.FC<PlanPanelRightProps> = ({
170155
}, [streamingMessages, groupStreamingMessages]);
171156

172157
// ✅ NEW: Get comprehensive agent status combining planned and streaming agents
173-
const getAgentStatus = useCallback((): AgentStatus[] => {
158+
const getAgentStatus = useCallback((planApprovalRequest: MPlanData | null): AgentStatus[] => {
174159
const agentStatusMap = new Map<string, AgentStatus>();
175160

176161
// Add planned agents from the plan approval request
@@ -249,21 +234,7 @@ const PlanPanelRight: React.FC<PlanPanelRightProps> = ({
249234

250235
// ✅ ENHANCED: Show waiting message only when we don't have plan approval request
251236
if (!planApprovalRequest) {
252-
return (
253-
<div style={{
254-
width: '280px',
255-
height: '100vh',
256-
display: 'flex',
257-
alignItems: 'center',
258-
justifyContent: 'center',
259-
color: 'var(--colorNeutralForeground3)',
260-
fontSize: '14px',
261-
padding: '20px',
262-
textAlign: 'center'
263-
}}>
264-
Waiting for plan creation...
265-
</div>
266-
);
237+
return null;
267238
}
268239

269240
// Render Plan Section - show once we have plan approval request
@@ -354,7 +325,7 @@ const PlanPanelRight: React.FC<PlanPanelRightProps> = ({
354325

355326
// ✅ ENHANCED: Render Agents Section - show planned agents immediately, update with streaming status
356327
const renderAgentsSection = () => {
357-
const agents = getAgentStatus();
328+
const agents = getAgentStatus(planApprovalRequest);
358329

359330
return (
360331
<div style={{

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

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { BotRegular, CheckmarkRegular, DismissRegular } from "@fluentui/react-ic
44
import ReactMarkdown from "react-markdown";
55
import remarkGfm from "remark-gfm";
66
// Render the complete plan with all information
7-
const renderPlanResponse = (planApprovalRequest: MPlanData | null, handleApprovePlan: () => void, handleRejectPlan: () => void, processingApproval: boolean,) => {
7+
const renderPlanResponse = (planApprovalRequest: MPlanData | null, handleApprovePlan: () => void, handleRejectPlan: () => void, processingApproval: boolean, showApprovalButtons: boolean) => {
88
if (!planApprovalRequest) return null;
99

1010
return (
@@ -55,7 +55,7 @@ const renderPlanResponse = (planApprovalRequest: MPlanData | null, handleApprove
5555
}}>
5656
<span>Plan ID: {planApprovalRequest.id}</span>
5757
<Tag size="extra-small" appearance="outline">
58-
{planApprovalRequest.status?.replace(/^.*'([^']*)'.*$/, '$1') || planApprovalRequest.status || 'PENDING_APPROVAL'}
58+
{planApprovalRequest.status?.replace(/^.*'([^']*)'.*$/, '$1') || planApprovalRequest.status || 'Pending'}
5959
</Tag>
6060
</div>
6161
</div>
@@ -203,46 +203,49 @@ const renderPlanResponse = (planApprovalRequest: MPlanData | null, handleApprove
203203

204204

205205
{/* Action Buttons - Separate section */}
206-
<div style={{
207-
display: 'flex',
208-
gap: '12px',
209-
alignItems: 'center',
210-
padding: '20px',
211-
backgroundColor: 'var(--colorNeutralBackground2)',
212-
borderRadius: '12px',
213-
border: '1px solid var(--colorNeutralStroke2)',
214-
marginTop: '20px'
215-
}}>
206+
{showApprovalButtons && <>
216207
<div style={{
217-
flex: 1,
218-
fontSize: '14px',
219-
color: 'var(--colorNeutralForeground2)'
208+
display: 'flex',
209+
gap: '12px',
210+
alignItems: 'center',
211+
padding: '20px',
212+
backgroundColor: 'var(--colorNeutralBackground2)',
213+
borderRadius: '12px',
214+
border: '1px solid var(--colorNeutralStroke2)',
215+
marginTop: '20px'
220216
}}>
221-
<span>Ready for approval</span>
217+
<div style={{
218+
flex: 1,
219+
fontSize: '14px',
220+
color: 'var(--colorNeutralForeground2)'
221+
}}>
222+
<span>Ready for approval</span>
222223

223-
</div>
224+
</div>
224225

225-
<Button
226-
appearance="primary"
227-
icon={processingApproval ? <Spinner size="extra-tiny" /> : <CheckmarkRegular />}
228-
onClick={handleApprovePlan}
229-
disabled={processingApproval}
230-
size="medium"
231-
style={{ minWidth: '140px' }}
232-
>
233-
{processingApproval ? 'Processing...' : 'Approve'}
234-
</Button>
235-
<Button
236-
appearance="outline"
237-
icon={<DismissRegular />}
238-
onClick={handleRejectPlan}
239-
disabled={processingApproval}
240-
size="medium"
241-
style={{ minWidth: '100px' }}
242-
>
243-
Cancel
244-
</Button>
245-
</div>
226+
<Button
227+
appearance="primary"
228+
icon={processingApproval ? <Spinner size="extra-tiny" /> : <CheckmarkRegular />}
229+
onClick={handleApprovePlan}
230+
disabled={processingApproval}
231+
size="medium"
232+
style={{ minWidth: '140px' }}
233+
>
234+
{processingApproval ? 'Processing...' : 'Approve'}
235+
</Button>
236+
<Button
237+
appearance="outline"
238+
icon={<DismissRegular />}
239+
onClick={handleRejectPlan}
240+
disabled={processingApproval}
241+
size="medium"
242+
style={{ minWidth: '100px' }}
243+
>
244+
Cancel
245+
</Button>
246+
247+
</div>
248+
</>}
246249
</div>
247250
</div>
248251
);

0 commit comments

Comments
 (0)