@@ -311,6 +311,66 @@ export const useChat = () => {
311311 toolCalls . push ( ...completion . choices [ 0 ] . message . tool_calls )
312312 }
313313 } else {
314+ // High-throughput scheduler: batch UI updates on rAF (requestAnimationFrame)
315+ let rafScheduled = false
316+ let rafHandle : number | undefined
317+ let pendingDeltaCount = 0
318+ const scheduleFlush = ( ) => {
319+ if ( rafScheduled ) return
320+ rafScheduled = true
321+ const doSchedule = ( cb : ( ) => void ) => {
322+ if ( typeof requestAnimationFrame !== 'undefined' ) {
323+ rafHandle = requestAnimationFrame ( ( ) => cb ( ) )
324+ } else {
325+ // Fallback for non-browser test environments
326+ const t = setTimeout ( ( ) => cb ( ) , 0 ) as unknown as number
327+ rafHandle = t
328+ }
329+ }
330+ doSchedule ( ( ) => {
331+ const currentContent = newAssistantThreadContent (
332+ activeThread . id ,
333+ accumulatedText ,
334+ {
335+ tool_calls : toolCalls . map ( ( e ) => ( {
336+ ...e ,
337+ state : 'pending' ,
338+ } ) ) ,
339+ }
340+ )
341+ updateStreamingContent ( currentContent )
342+ if ( pendingDeltaCount > 0 ) {
343+ updateTokenSpeed ( currentContent , pendingDeltaCount )
344+ }
345+ pendingDeltaCount = 0
346+ rafScheduled = false
347+ } )
348+ }
349+ const flushIfPending = ( ) => {
350+ if ( ! rafScheduled ) return
351+ if ( typeof cancelAnimationFrame !== 'undefined' && rafHandle !== undefined ) {
352+ cancelAnimationFrame ( rafHandle )
353+ } else if ( rafHandle !== undefined ) {
354+ clearTimeout ( rafHandle )
355+ }
356+ // Do an immediate flush
357+ const currentContent = newAssistantThreadContent (
358+ activeThread . id ,
359+ accumulatedText ,
360+ {
361+ tool_calls : toolCalls . map ( ( e ) => ( {
362+ ...e ,
363+ state : 'pending' ,
364+ } ) ) ,
365+ }
366+ )
367+ updateStreamingContent ( currentContent )
368+ if ( pendingDeltaCount > 0 ) {
369+ updateTokenSpeed ( currentContent , pendingDeltaCount )
370+ }
371+ pendingDeltaCount = 0
372+ rafScheduled = false
373+ }
314374 for await ( const part of completion ) {
315375 // Error message
316376 if ( ! part . choices ) {
@@ -323,39 +383,19 @@ export const useChat = () => {
323383 const delta = part . choices [ 0 ] ?. delta ?. content || ''
324384
325385 if ( part . choices [ 0 ] ?. delta ?. tool_calls ) {
326- const calls = extractToolCall ( part , currentCall , toolCalls )
327- const currentContent = newAssistantThreadContent (
328- activeThread . id ,
329- accumulatedText ,
330- {
331- tool_calls : calls . map ( ( e ) => ( {
332- ...e ,
333- state : 'pending' ,
334- } ) ) ,
335- }
336- )
337- updateStreamingContent ( currentContent )
338- await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) )
386+ extractToolCall ( part , currentCall , toolCalls )
387+ // Schedule a flush to reflect tool update
388+ scheduleFlush ( )
339389 }
340390 if ( delta ) {
341391 accumulatedText += delta
342- // Create a new object each time to avoid reference issues
343- // Use a timeout to prevent React from batching updates too quickly
344- const currentContent = newAssistantThreadContent (
345- activeThread . id ,
346- accumulatedText ,
347- {
348- tool_calls : toolCalls . map ( ( e ) => ( {
349- ...e ,
350- state : 'pending' ,
351- } ) ) ,
352- }
353- )
354- updateStreamingContent ( currentContent )
355- updateTokenSpeed ( currentContent )
356- await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) )
392+ pendingDeltaCount += 1
393+ // Batch UI update on next animation frame
394+ scheduleFlush ( )
357395 }
358396 }
397+ // Ensure any pending buffered content is rendered at the end
398+ flushIfPending ( )
359399 }
360400 } catch ( error ) {
361401 const errorMessage =
0 commit comments