11import type { RunEvent , ToolCall , ToolResult } from "@perstack/core"
2- import { useCallback , useMemo , useState } from "react"
2+ import { useCallback , useEffect , useMemo , useRef , useState } from "react"
33import { UI_CONSTANTS } from "../../constants.js"
44import type { DisplayStep , PerstackEvent , ToolExecution } from "../../types/index.js"
55
@@ -34,51 +34,51 @@ const extractQuery = (event: Extract<RunEvent, { type: "startRun" }>): string |
3434 if ( userMessage ?. type !== "userMessage" ) return undefined
3535 return userMessage . contents . find ( ( c ) => c . type === "textPart" ) ?. text
3636}
37- const buildSteps = ( events : PerstackEvent [ ] ) : DisplayStep [ ] => {
38- const stepMap = new Map < number , StepBuilder > ( )
39- const getOrCreateStep = ( stepNumber : number ) : StepBuilder => {
40- const existing = stepMap . get ( stepNumber )
41- if ( existing ) return existing
42- const builder : StepBuilder = { tools : new Map ( ) }
43- stepMap . set ( stepNumber , builder )
44- return builder
45- }
46- for ( const event of events ) {
47- if ( ! ( "stepNumber" in event ) ) continue
48- const stepNum = event . stepNumber
49- const builder = getOrCreateStep ( stepNum )
50- if ( event . type === "startRun" ) {
51- builder . query = extractQuery ( event )
52- } else if ( event . type === "completeRun" ) {
53- builder . completion = event . text
54- } else if ( isToolCallEvent ( event ) ) {
55- const { toolCall } = event
56- builder . tools . set ( toolCall . id , {
57- id : toolCall . id ,
58- toolName : toolCall . toolName ,
59- args : toolCall . args as Record < string , unknown > ,
60- } )
61- } else if ( isToolResultEvent ( event ) ) {
62- const { toolResult } = event
63- const existing = builder . tools . get ( toolResult . id )
64- if ( existing && Array . isArray ( toolResult . result ) ) {
65- existing . result = toolResult . result
66- existing . isSuccess = checkIsSuccess ( toolResult . result )
67- }
37+ const getOrCreateStep = ( stepMap : Map < number , StepBuilder > , stepNumber : number ) : StepBuilder => {
38+ const existing = stepMap . get ( stepNumber )
39+ if ( existing ) return existing
40+ const builder : StepBuilder = { tools : new Map ( ) }
41+ stepMap . set ( stepNumber , builder )
42+ return builder
43+ }
44+ const processEvent = ( stepMap : Map < number , StepBuilder > , event : PerstackEvent ) : void => {
45+ if ( ! ( "stepNumber" in event ) ) return
46+ const builder = getOrCreateStep ( stepMap , event . stepNumber )
47+ if ( event . type === "startRun" ) {
48+ builder . query = extractQuery ( event )
49+ } else if ( event . type === "completeRun" ) {
50+ builder . completion = event . text
51+ } else if ( isToolCallEvent ( event ) ) {
52+ const { toolCall } = event
53+ builder . tools . set ( toolCall . id , {
54+ id : toolCall . id ,
55+ toolName : toolCall . toolName ,
56+ args : toolCall . args as Record < string , unknown > ,
57+ } )
58+ } else if ( isToolResultEvent ( event ) ) {
59+ const { toolResult } = event
60+ const existing = builder . tools . get ( toolResult . id )
61+ if ( existing && Array . isArray ( toolResult . result ) ) {
62+ existing . result = toolResult . result
63+ existing . isSuccess = checkIsSuccess ( toolResult . result )
6864 }
6965 }
70- return Array . from ( stepMap . entries ( ) )
66+ }
67+ const buildStepsFromMap = ( stepMap : Map < number , StepBuilder > ) : DisplayStep [ ] =>
68+ Array . from ( stepMap . entries ( ) )
7169 . sort ( ( [ a ] , [ b ] ) => a - b )
7270 . map ( ( [ stepNumber , builder ] ) => ( {
7371 id : `step-${ stepNumber } ` ,
7472 stepNumber,
7573 query : builder . query ,
76- tools : Array . from ( builder . tools . values ( ) ) ,
74+ tools : Array . from ( builder . tools . values ( ) ) . map ( ( tool ) => ( { ... tool } ) ) ,
7775 completion : builder . completion ,
7876 } ) )
79- }
8077export const useStepStore = ( ) => {
8178 const [ events , setEvents ] = useState < PerstackEvent [ ] > ( [ ] )
79+ const [ steps , setSteps ] = useState < DisplayStep [ ] > ( [ ] )
80+ const stepMapRef = useRef < Map < number , StepBuilder > > ( new Map ( ) )
81+ const processedCountRef = useRef ( 0 )
8282 const addEvent = useCallback ( ( event : PerstackEvent ) => {
8383 setEvents ( ( prev ) => {
8484 const newEvents = [ ...prev , event ]
@@ -94,7 +94,18 @@ export const useStepStore = () => {
9494 : historicalEvents ,
9595 )
9696 } , [ ] )
97- const steps = useMemo ( ( ) => buildSteps ( events ) , [ events ] )
97+ useEffect ( ( ) => {
98+ if ( processedCountRef . current > events . length ) {
99+ stepMapRef . current = new Map ( )
100+ processedCountRef . current = 0
101+ }
102+ const newEvents = events . slice ( processedCountRef . current )
103+ for ( const event of newEvents ) {
104+ processEvent ( stepMapRef . current , event )
105+ }
106+ processedCountRef . current = events . length
107+ setSteps ( buildStepsFromMap ( stepMapRef . current ) )
108+ } , [ events ] )
98109 const completedSteps = useMemo ( ( ) => {
99110 if ( steps . length === 0 ) return [ ]
100111 const lastStep = steps . at ( - 1 )
0 commit comments