@@ -15,6 +15,7 @@ import {
1515 selectPlanData ,
1616 selectContinueWithWebsocketFlow ,
1717 selectPlanApproved ,
18+ selectShowProcessingPlanSpinner ,
1819 approvalRequestReceived ,
1920 planCompletedFinal ,
2021 planFailedFinal ,
@@ -44,11 +45,11 @@ import {
4445 ProcessedPlanData ,
4546} from '@/models' ;
4647import { APIService } from '@/api/apiService' ;
48+ import { ToastIntent } from '@/components/toast/InlineToaster' ;
49+ import { formatElapsedTime } from '@/utils' ;
4750
4851const apiService = new APIService ( ) ;
4952
50- import { ToastIntent } from '@/components/toast/InlineToaster' ;
51-
5253interface UsePlanWebSocketProps {
5354 planId : string | undefined ;
5455 scrollToBottom : ( ) => void ;
@@ -99,8 +100,20 @@ export function usePlanWebSocket({
99100 const dispatch = useAppDispatch ( ) ;
100101 const planData = useAppSelector ( selectPlanData ) ;
101102 const planApproved = useAppSelector ( selectPlanApproved ) ;
103+ const showProcessingPlanSpinner = useAppSelector ( selectShowProcessingPlanSpinner ) ;
102104 const continueWithWebsocketFlow = useAppSelector ( selectContinueWithWebsocketFlow ) ;
103105 const streamingMessageBuffer = useAppSelector ( selectStreamingMessageBuffer ) ;
106+ const processingStartedAtRef = React . useRef < number | null > ( null ) ;
107+
108+ useEffect ( ( ) => {
109+ if ( showProcessingPlanSpinner ) {
110+ if ( processingStartedAtRef . current === null ) {
111+ processingStartedAtRef . current = Date . now ( ) ;
112+ }
113+ } else {
114+ processingStartedAtRef . current = null ;
115+ }
116+ } , [ showProcessingPlanSpinner ] ) ;
104117
105118 // ── PLAN_APPROVAL_REQUEST ─────────────────────────────────────
106119 useEffect ( ( ) => {
@@ -161,6 +174,7 @@ export function usePlanWebSocket({
161174 dispatch ( addAgentMessage ( agentMessageData ) ) ;
162175 dispatch ( setShowBufferingText ( false ) ) ;
163176 dispatch ( setShowProcessingPlanSpinner ( false ) ) ;
177+ processingStartedAtRef . current = null ;
164178 dispatch ( setSubmittingChatDisableInput ( false ) ) ;
165179 scrollToBottom ( ) ;
166180 persistAgentMessage ( agentMessageData , planData , dispatch ) ;
@@ -181,6 +195,12 @@ export function usePlanWebSocket({
181195 WebsocketMessageType . FINAL_RESULT_MESSAGE ,
182196 ( finalMessage : any ) => {
183197 if ( ! finalMessage ) return ;
198+ const completionElapsedSeconds = processingStartedAtRef . current
199+ ? Math . max ( Math . round ( ( Date . now ( ) - processingStartedAtRef . current ) / 1000 ) , 0 )
200+ : null ;
201+ const completionTimeLine = completionElapsedSeconds !== null
202+ ? `\n\n**Total completion time: ${ formatElapsedTime ( completionElapsedSeconds ) } **`
203+ : '' ;
184204 const messageStatus = finalMessage ?. data ?. status ;
185205
186206 if ( messageStatus === PlanStatus . COMPLETED ) {
@@ -190,14 +210,15 @@ export function usePlanWebSocket({
190210 timestamp : Date . now ( ) ,
191211 steps : [ ] ,
192212 next_steps : [ ] ,
193- content : finalMessage . data ?. content || '' ,
213+ content : ( finalMessage . data ?. content || '' ) + completionTimeLine ,
194214 raw_data : finalMessage ,
195215 } ;
196216 dispatch ( setShowBufferingText ( true ) ) ;
197217 dispatch ( addAgentMessage ( agentMessageData ) ) ;
198218 dispatch ( setSelectedTeam ( planData ?. team || null ) ) ;
199219 /* P0: single compound action replaces setShowProcessingPlanSpinner(false) + markPlanCompleted() */
200220 dispatch ( planCompletedFinal ( ) ) ;
221+ processingStartedAtRef . current = null ;
201222 scrollToFinalResult ( ) ;
202223 webSocketService . disconnect ( ) ;
203224 persistAgentMessage ( agentMessageData , planData , dispatch , true , streamingMessageBuffer ) ;
@@ -256,6 +277,7 @@ export function usePlanWebSocket({
256277 } ;
257278 dispatch ( addAgentMessage ( errorAgent ) ) ;
258279 dispatch ( planFailedFinal ( ) ) ;
280+ processingStartedAtRef . current = null ;
259281 dispatch ( setShowBufferingText ( false ) ) ;
260282 dispatch ( setSubmittingChatDisableInput ( true ) ) ;
261283 scrollToBottom ( ) ;
0 commit comments