@@ -117,6 +117,7 @@ function createThrottledValue(getValue: () => string) {
117117 createEffect ( ( ) => {
118118 const next = getValue ( )
119119 const now = Date . now ( )
120+
120121 const remaining = TEXT_RENDER_THROTTLE_MS - ( now - last )
121122 if ( remaining <= 0 ) {
122123 if ( timeout ) {
@@ -250,6 +251,126 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
250251}
251252
252253const CONTEXT_GROUP_TOOLS = new Set ( [ "read" , "glob" , "grep" , "list" ] )
254+ const HIDDEN_TOOLS = new Set ( [ "todowrite" , "todoread" ] )
255+
256+ function list < T > ( value : T [ ] | undefined | null , fallback : T [ ] ) {
257+ if ( Array . isArray ( value ) ) return value
258+ return fallback
259+ }
260+
261+ function renderable ( part : PartType ) {
262+ if ( part . type === "tool" ) {
263+ if ( HIDDEN_TOOLS . has ( part . tool ) ) return false
264+ if ( part . tool === "question" ) return part . state . status !== "pending" && part . state . status !== "running"
265+ return true
266+ }
267+ if ( part . type === "text" ) return ! ! part . text ?. trim ( )
268+ if ( part . type === "reasoning" ) return ! ! part . text ?. trim ( )
269+ return ! ! PART_MAPPING [ part . type ]
270+ }
271+
272+ export function AssistantParts ( props : {
273+ messages : AssistantMessage [ ]
274+ showAssistantCopyPartID ?: string | null
275+ working ?: boolean
276+ } ) {
277+ const data = useData ( )
278+ const emptyParts : PartType [ ] = [ ]
279+
280+ const grouped = createMemo ( ( ) => {
281+ const keys : string [ ] = [ ]
282+ const items : Record <
283+ string ,
284+ { type : "part" ; part : PartType ; message : AssistantMessage } | { type : "context" ; parts : ToolPart [ ] }
285+ > = { }
286+ const push = (
287+ key : string ,
288+ item : { type : "part" ; part : PartType ; message : AssistantMessage } | { type : "context" ; parts : ToolPart [ ] } ,
289+ ) => {
290+ keys . push ( key )
291+ items [ key ] = item
292+ }
293+
294+ const parts = props . messages . flatMap ( ( message ) =>
295+ list ( data . store . part ?. [ message . id ] , emptyParts )
296+ . filter ( renderable )
297+ . map ( ( part ) => ( { message, part } ) ) ,
298+ )
299+
300+ let start = - 1
301+
302+ const flush = ( end : number ) => {
303+ if ( start < 0 ) return
304+ const first = parts [ start ]
305+ const last = parts [ end ]
306+ if ( ! first || ! last ) {
307+ start = - 1
308+ return
309+ }
310+ push ( `context:${ first . part . id } ` , {
311+ type : "context" ,
312+ parts : parts
313+ . slice ( start , end + 1 )
314+ . map ( ( x ) => x . part )
315+ . filter ( ( part ) : part is ToolPart => isContextGroupTool ( part ) ) ,
316+ } )
317+ start = - 1
318+ }
319+
320+ parts . forEach ( ( item , index ) => {
321+ if ( isContextGroupTool ( item . part ) ) {
322+ if ( start < 0 ) start = index
323+ return
324+ }
325+
326+ flush ( index - 1 )
327+ push ( `part:${ item . message . id } :${ item . part . id } ` , { type : "part" , part : item . part , message : item . message } )
328+ } )
329+
330+ flush ( parts . length - 1 )
331+
332+ return { keys, items }
333+ } )
334+
335+ const last = createMemo ( ( ) => grouped ( ) . keys . at ( - 1 ) )
336+
337+ return (
338+ < For each = { grouped ( ) . keys } >
339+ { ( key ) => {
340+ const item = createMemo ( ( ) => grouped ( ) . items [ key ] )
341+ const ctx = createMemo ( ( ) => {
342+ const value = item ( )
343+ if ( ! value ) return
344+ if ( value . type !== "context" ) return
345+ return value
346+ } )
347+ const part = createMemo ( ( ) => {
348+ const value = item ( )
349+ if ( ! value ) return
350+ if ( value . type !== "part" ) return
351+ return value
352+ } )
353+ const tail = createMemo ( ( ) => last ( ) === key )
354+ return (
355+ < >
356+ < Show when = { ctx ( ) } >
357+ { ( entry ) => < ContextToolGroup parts = { entry ( ) . parts } busy = { props . working && tail ( ) } /> }
358+ </ Show >
359+ < Show when = { part ( ) } >
360+ { ( entry ) => (
361+ < Part
362+ part = { entry ( ) . part }
363+ message = { entry ( ) . message }
364+ showAssistantCopyPartID = { props . showAssistantCopyPartID }
365+ />
366+ ) }
367+ </ Show >
368+ </ >
369+ )
370+ } }
371+ </ For >
372+ )
373+ }
253374
254375function isContextGroupTool ( part : PartType ) : part is ToolPart {
255376 return part . type === "tool" && CONTEXT_GROUP_TOOLS . has ( part . tool )
@@ -390,6 +511,8 @@ export function AssistantMessageDisplay(props: {
390511 }
391512
392513 parts . forEach ( ( part , index ) => {
514+ if ( ! renderable ( part ) ) return
515+
393516 if ( isContextGroupTool ( part ) ) {
394517 if ( start < 0 ) start = index
395518 return
@@ -408,31 +531,43 @@ export function AssistantMessageDisplay(props: {
408531 < For each = { grouped ( ) . keys } >
409532 { ( key ) => {
410533 const item = createMemo ( ( ) => grouped ( ) . items [ key ] )
534+ const ctx = createMemo ( ( ) => {
535+ const value = item ( )
536+ if ( ! value ) return
537+ if ( value . type !== "context" ) return
538+ return value
539+ } )
540+ const part = createMemo ( ( ) => {
541+ const value = item ( )
542+ if ( ! value ) return
543+ if ( value . type !== "part" ) return
544+ return value
545+ } )
411546 return (
412- < Show when = { item ( ) } >
413- { ( value ) => {
414- const entry = value ( )
415- if ( entry . type === "context" ) return < ContextToolGroup parts = { entry . parts } />
416- return (
547+ < >
548+ < Show when = { ctx ( ) } > { ( entry ) => < ContextToolGroup parts = { entry ( ) . parts } /> } </ Show >
549+ < Show when = { part ( ) } >
550+ { ( entry ) => (
417551 < Part
418- part = { entry . part }
552+ part = { entry ( ) . part }
419553 message = { props . message }
420554 showAssistantCopyPartID = { props . showAssistantCopyPartID }
421555 />
422- )
423- } }
424- </ Show >
556+ ) }
557+ </ Show >
558+ </ >
425559 )
426560 } }
427561 </ For >
428562 )
429563}
430564
431- function ContextToolGroup ( props : { parts : ToolPart [ ] } ) {
565+ function ContextToolGroup ( props : { parts : ToolPart [ ] ; busy ?: boolean } ) {
432566 const i18n = useI18n ( )
433567 const [ open , setOpen ] = createSignal ( false )
434- const pending = createMemo ( ( ) =>
435- props . parts . some ( ( part ) => part . state . status === "pending" || part . state . status === "running" ) ,
568+ const pending = createMemo (
569+ ( ) =>
570+ ! ! props . busy || props . parts . some ( ( part ) => part . state . status === "pending" || part . state . status === "running" ) ,
436571 )
437572 const summary = createMemo ( ( ) => contextToolSummary ( props . parts ) )
438573 const details = createMemo ( ( ) => summary ( ) . join ( ", " ) )
@@ -445,7 +580,7 @@ function ContextToolGroup(props: { parts: ToolPart[] }) {
445580 when = { pending ( ) }
446581 fallback = {
447582 < span data-slot = "context-tool-group-title" >
448- < span data-slot = "context-tool-group-label" > Gathered context </ span >
583+ < span data-slot = "context-tool-group-label" > { i18n . t ( "ui.sessionTurn.status.gatheredContext" ) } </ span >
449584 < Show when = { details ( ) . length } >
450585 < span data-slot = "context-tool-group-summary" > { details ( ) } </ span >
451586 </ Show >
0 commit comments