From ee1bc7e209f98a78fe14d57837c7e4b1329a108d Mon Sep 17 00:00:00 2001 From: Mahima Shanware Date: Fri, 27 Mar 2026 20:10:26 +0000 Subject: [PATCH] perf(cli): throttle btw streaming updates to prevent layout shakiness --- packages/cli/src/ui/hooks/useBtw.ts | 49 ++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/ui/hooks/useBtw.ts b/packages/cli/src/ui/hooks/useBtw.ts index 5035045416..23aeb41175 100644 --- a/packages/cli/src/ui/hooks/useBtw.ts +++ b/packages/cli/src/ui/hooks/useBtw.ts @@ -27,7 +27,7 @@ interface BtwState { type BtwAction = | { type: 'SUBMIT'; query: string } - | { type: 'APPEND_CONTENT'; content: string } + | { type: 'SET_RESPONSE'; content: string } | { type: 'ERROR'; error: string } | { type: 'FINISHED' } | { type: 'DISMISS' }; @@ -51,10 +51,10 @@ const btwReducer = (state: BtwState, action: BtwAction): BtwState => { isStreaming: true, error: null, }; - case 'APPEND_CONTENT': + case 'SET_RESPONSE': return { ...state, - response: state.response + action.content, + response: action.content, }; case 'ERROR': return { @@ -103,6 +103,16 @@ export const useBtw = ( dispatch({ type: 'SUBMIT', query: newQuery }); + let accumulatedResponse = ''; + let lastDispatchTime = 0; + let flushTimer: NodeJS.Timeout | null = null; + + const flushResponse = () => { + if (abortControllerRef.current !== abortController) return; + dispatch({ type: 'SET_RESPONSE', content: accumulatedResponse }); + lastDispatchTime = Date.now(); + }; + try { const stream = geminiClient.sendBtwStream( [{ text: newQuery }], @@ -114,10 +124,27 @@ export const useBtw = ( if (abortController.signal.aborted) break; switch (event.type) { - case GeminiEventType.Content: - dispatch({ type: 'APPEND_CONTENT', content: event.value ?? '' }); + case GeminiEventType.Content: { + accumulatedResponse += event.value ?? ''; + const now = Date.now(); + if (now - lastDispatchTime > 50) { + if (flushTimer) { + clearTimeout(flushTimer); + flushTimer = null; + } + flushResponse(); + } else if (!flushTimer) { + flushTimer = setTimeout(() => { + flushResponse(); + flushTimer = null; + }, 50); + } break; + } case GeminiEventType.Error: { + if (flushTimer) clearTimeout(flushTimer); + flushResponse(); + const value = event.value; let errorMessage = 'Unknown error'; if ( @@ -142,9 +169,9 @@ export const useBtw = ( break; } case GeminiEventType.Finished: - dispatch({ type: 'FINISHED' }); - break; case GeminiEventType.UserCancelled: + if (flushTimer) clearTimeout(flushTimer); + flushResponse(); dispatch({ type: 'FINISHED' }); break; default: @@ -152,15 +179,21 @@ export const useBtw = ( } } } catch (err) { + if (flushTimer) clearTimeout(flushTimer); + flushResponse(); + if (err instanceof Error && err.name === 'AbortError') { // Ignore aborts - } else { + } else if (abortControllerRef.current === abortController) { dispatch({ type: 'ERROR', error: err instanceof Error ? err.message : String(err), }); } } finally { + if (flushTimer) clearTimeout(flushTimer); + flushResponse(); + if (abortControllerRef.current === abortController) { dispatch({ type: 'FINISHED' }); }