@@ -4,15 +4,15 @@ import {
44 createMemo ,
55 createSignal ,
66 For ,
7- Index ,
87 Match ,
98 onMount ,
109 Show ,
1110 Switch ,
1211 onCleanup ,
12+ Index ,
1313 type JSX ,
1414} from "solid-js"
15- import { createStore , unwrap } from "solid-js/store"
15+ import { createStore } from "solid-js/store"
1616import stripAnsi from "strip-ansi"
1717import { Dynamic } from "solid-js/web"
1818import {
@@ -481,15 +481,6 @@ function partDefaultOpen(part: PartType, shell = false, edit = false) {
481481 return toolDefaultOpen ( part . tool , shell , edit )
482482}
483483
484- function bindMessage < T extends MessageType > ( input : T ) {
485- const data = useData ( )
486- const base = structuredClone ( unwrap ( input ) ) as T
487- return createMemo ( ( ) => {
488- const next = data . store . message ?. [ base . sessionID ] ?. find ( ( item ) => item . id === base . id )
489- return ( next as T | undefined ) ?? base
490- } )
491- }
492-
493484export function AssistantParts ( props : {
494485 messages : AssistantMessage [ ]
495486 showAssistantCopyPartID ?: string | null
@@ -530,55 +521,62 @@ export function AssistantParts(props: {
530521
531522 return (
532523 < Index each = { grouped ( ) } >
533- { ( entry ) => {
534- const kind = createMemo ( ( ) => entry ( ) . type )
535- const parts = createMemo (
536- ( ) => {
537- const value = entry ( )
538- if ( value . type !== "context" ) return emptyTools
539- return value . refs
540- . map ( ( ref ) => part ( ) . get ( ref . messageID ) ?. get ( ref . partID ) )
541- . filter ( ( part ) : part is ToolPart => ! ! part && isContextGroupTool ( part ) )
542- } ,
543- emptyTools ,
544- { equals : same } ,
545- )
546- const busy = createMemo ( ( ) => props . working && last ( ) === entry ( ) . key )
547- const message = createMemo ( ( ) => {
548- const value = entry ( )
549- if ( value . type !== "part" ) return
550- return msgs ( ) . get ( value . ref . messageID )
551- } )
552- const item = createMemo ( ( ) => {
553- const value = entry ( )
554- if ( value . type !== "part" ) return
555- return part ( ) . get ( value . ref . messageID ) ?. get ( value . ref . partID )
556- } )
557- const ready = createMemo ( ( ) => {
558- if ( kind ( ) !== "part" ) return
559- const msg = message ( )
560- const value = item ( )
561- if ( ! msg || ! value ) return
562- return { msg, value }
563- } )
524+ { ( entryAccessor ) => {
525+ const entryType = createMemo ( ( ) => entryAccessor ( ) . type )
564526
565527 return (
566- < >
567- < Show when = { kind ( ) === "context" && parts ( ) . length > 0 } >
568- < ContextToolGroup parts = { parts ( ) } busy = { busy ( ) } />
569- </ Show >
570- < Show when = { ready ( ) } >
571- { ( ready ) => (
572- < Part
573- part = { ready ( ) . value }
574- message = { ready ( ) . msg }
575- showAssistantCopyPartID = { props . showAssistantCopyPartID }
576- turnDurationMs = { props . turnDurationMs }
577- defaultOpen = { partDefaultOpen ( ready ( ) . value , props . shellToolDefaultOpen , props . editToolDefaultOpen ) }
578- />
579- ) }
580- </ Show >
581- </ >
528+ < Switch >
529+ < Match when = { entryType ( ) === "context" } >
530+ { ( ( ) => {
531+ const parts = createMemo (
532+ ( ) => {
533+ const entry = entryAccessor ( )
534+ if ( entry . type !== "context" ) return emptyTools
535+ return entry . refs
536+ . map ( ( ref ) => part ( ) . get ( ref . messageID ) ?. get ( ref . partID ) )
537+ . filter ( ( part ) : part is ToolPart => ! ! part && isContextGroupTool ( part ) )
538+ } ,
539+ emptyTools ,
540+ { equals : same } ,
541+ )
542+ const busy = createMemo ( ( ) => props . working && last ( ) === entryAccessor ( ) . key )
543+
544+ return (
545+ < Show when = { parts ( ) . length > 0 } >
546+ < ContextToolGroup parts = { parts ( ) } busy = { busy ( ) } />
547+ </ Show >
548+ )
549+ } ) ( ) }
550+ </ Match >
551+ < Match when = { entryType ( ) === "part" } >
552+ { ( ( ) => {
553+ const message = createMemo ( ( ) => {
554+ const entry = entryAccessor ( )
555+ if ( entry . type !== "part" ) return
556+ return msgs ( ) . get ( entry . ref . messageID )
557+ } )
558+ const item = createMemo ( ( ) => {
559+ const entry = entryAccessor ( )
560+ if ( entry . type !== "part" ) return
561+ return part ( ) . get ( entry . ref . messageID ) ?. get ( entry . ref . partID )
562+ } )
563+
564+ return (
565+ < Show when = { message ( ) } >
566+ < Show when = { item ( ) } >
567+ < Part
568+ part = { item ( ) ! }
569+ message = { message ( ) ! }
570+ showAssistantCopyPartID = { props . showAssistantCopyPartID }
571+ turnDurationMs = { props . turnDurationMs }
572+ defaultOpen = { partDefaultOpen ( item ( ) ! , props . shellToolDefaultOpen , props . editToolDefaultOpen ) }
573+ />
574+ </ Show >
575+ </ Show >
576+ )
577+ } ) ( ) }
578+ </ Match >
579+ </ Switch >
582580 )
583581 } }
584582 </ Index >
@@ -690,22 +688,25 @@ export function registerPartComponent(type: string, component: PartComponent) {
690688}
691689
692690export function Message ( props : MessageProps ) {
693- if ( props . message . role === "user" ) {
694- return < UserMessageDisplay message = { props . message as UserMessage } parts = { props . parts } actions = { props . actions } />
695- }
696-
697- if ( props . message . role === "assistant" ) {
698- return (
699- < AssistantMessageDisplay
700- message = { props . message as AssistantMessage }
701- parts = { props . parts }
702- showAssistantCopyPartID = { props . showAssistantCopyPartID }
703- showReasoningSummaries = { props . showReasoningSummaries }
704- />
705- )
706- }
707-
708- return undefined
691+ return (
692+ < Switch >
693+ < Match when = { props . message . role === "user" && props . message } >
694+ { ( userMessage ) => (
695+ < UserMessageDisplay message = { userMessage ( ) as UserMessage } parts = { props . parts } actions = { props . actions } />
696+ ) }
697+ </ Match >
698+ < Match when = { props . message . role === "assistant" && props . message } >
699+ { ( assistantMessage ) => (
700+ < AssistantMessageDisplay
701+ message = { assistantMessage ( ) as AssistantMessage }
702+ parts = { props . parts }
703+ showAssistantCopyPartID = { props . showAssistantCopyPartID }
704+ showReasoningSummaries = { props . showReasoningSummaries }
705+ />
706+ ) }
707+ </ Match >
708+ </ Switch >
709+ )
709710}
710711
711712export function AssistantMessageDisplay ( props : {
@@ -732,42 +733,52 @@ export function AssistantMessageDisplay(props: {
732733
733734 return (
734735 < Index each = { grouped ( ) } >
735- { ( entry ) => {
736- const kind = createMemo ( ( ) => entry ( ) . type )
737- const parts = createMemo (
738- ( ) => {
739- const value = entry ( )
740- if ( value . type !== "context" ) return emptyTools
741- return value . refs
742- . map ( ( ref ) => part ( ) . get ( ref . partID ) )
743- . filter ( ( part ) : part is ToolPart => ! ! part && isContextGroupTool ( part ) )
744- } ,
745- emptyTools ,
746- { equals : same } ,
747- )
748- const item = createMemo ( ( ) => {
749- const value = entry ( )
750- if ( value . type !== "part" ) return
751- return part ( ) . get ( value . ref . partID )
752- } )
753- const ready = createMemo ( ( ) => {
754- if ( kind ( ) !== "part" ) return
755- const value = item ( )
756- if ( ! value ) return
757- return value
758- } )
736+ { ( entryAccessor ) => {
737+ const entryType = createMemo ( ( ) => entryAccessor ( ) . type )
759738
760739 return (
761- < >
762- < Show when = { kind ( ) === "context" && parts ( ) . length > 0 } >
763- < ContextToolGroup parts = { parts ( ) } />
764- </ Show >
765- < Show when = { ready ( ) } >
766- { ( ready ) => (
767- < Part part = { ready ( ) } message = { props . message } showAssistantCopyPartID = { props . showAssistantCopyPartID } />
768- ) }
769- </ Show >
770- </ >
740+ < Switch >
741+ < Match when = { entryType ( ) === "context" } >
742+ { ( ( ) => {
743+ const parts = createMemo (
744+ ( ) => {
745+ const entry = entryAccessor ( )
746+ if ( entry . type !== "context" ) return emptyTools
747+ return entry . refs
748+ . map ( ( ref ) => part ( ) . get ( ref . partID ) )
749+ . filter ( ( part ) : part is ToolPart => ! ! part && isContextGroupTool ( part ) )
750+ } ,
751+ emptyTools ,
752+ { equals : same } ,
753+ )
754+
755+ return (
756+ < Show when = { parts ( ) . length > 0 } >
757+ < ContextToolGroup parts = { parts ( ) } />
758+ </ Show >
759+ )
760+ } ) ( ) }
761+ </ Match >
762+ < Match when = { entryType ( ) === "part" } >
763+ { ( ( ) => {
764+ const item = createMemo ( ( ) => {
765+ const entry = entryAccessor ( )
766+ if ( entry . type !== "part" ) return
767+ return part ( ) . get ( entry . ref . partID )
768+ } )
769+
770+ return (
771+ < Show when = { item ( ) } >
772+ < Part
773+ part = { item ( ) ! }
774+ message = { props . message }
775+ showAssistantCopyPartID = { props . showAssistantCopyPartID }
776+ />
777+ </ Show >
778+ )
779+ } ) ( ) }
780+ </ Match >
781+ </ Switch >
771782 )
772783 } }
773784 </ Index >
@@ -834,9 +845,11 @@ function ContextToolGroup(props: { parts: ToolPart[]; busy?: boolean }) {
834845 < Collapsible . Content >
835846 < div data-component = "context-tool-group-list" >
836847 < Index each = { props . parts } >
837- { ( part ) => {
838- const trigger = createMemo ( ( ) => contextToolTrigger ( part ( ) , i18n ) )
839- const running = createMemo ( ( ) => part ( ) . state . status === "pending" || part ( ) . state . status === "running" )
848+ { ( partAccessor ) => {
849+ const trigger = createMemo ( ( ) => contextToolTrigger ( partAccessor ( ) , i18n ) )
850+ const running = createMemo (
851+ ( ) => partAccessor ( ) . state . status === "pending" || partAccessor ( ) . state . status === "running" ,
852+ )
840853 return (
841854 < div data-slot = "context-tool-group-item" >
842855 < div data-component = "tool-trigger" >
@@ -874,7 +887,6 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
874887 const data = useData ( )
875888 const dialog = useDialog ( )
876889 const i18n = useI18n ( )
877- const message = bindMessage ( props . message )
878890 const [ state , setState ] = createStore ( {
879891 copied : false ,
880892 busy : undefined as "fork" | "revert" | undefined ,
@@ -897,22 +909,22 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
897909 const agents = createMemo ( ( ) => ( props . parts ?. filter ( ( p ) => p . type === "agent" ) as AgentPart [ ] ) ?? [ ] )
898910
899911 const model = createMemo ( ( ) => {
900- const providerID = message ( ) . model ?. providerID
901- const modelID = message ( ) . model ?. modelID
912+ const providerID = props . message . model ?. providerID
913+ const modelID = props . message . model ?. modelID
902914 if ( ! providerID || ! modelID ) return ""
903915 const match = data . store . provider ?. all ?. find ( ( p ) => p . id === providerID )
904916 return match ?. models ?. [ modelID ] ?. name ?? modelID
905917 } )
906918 const timefmt = createMemo ( ( ) => new Intl . DateTimeFormat ( i18n . locale ( ) , { timeStyle : "short" } ) )
907919
908920 const stamp = createMemo ( ( ) => {
909- const created = message ( ) . time ?. created
921+ const created = props . message . time ?. created
910922 if ( typeof created !== "number" ) return ""
911923 return timefmt ( ) . format ( created )
912924 } )
913925
914926 const metaHead = createMemo ( ( ) => {
915- const agent = message ( ) . agent
927+ const agent = props . message . agent
916928 const items = [ agent ? agent [ 0 ] ?. toUpperCase ( ) + agent . slice ( 1 ) : "" , model ( ) ]
917929 return items . filter ( ( x ) => ! ! x ) . join ( "\u00A0\u00B7\u00A0" )
918930 } )
@@ -938,8 +950,8 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
938950 void Promise . resolve ( )
939951 . then ( ( ) =>
940952 act ( {
941- sessionID : message ( ) . sessionID ,
942- messageID : message ( ) . id ,
953+ sessionID : props . message . sessionID ,
954+ messageID : props . message . id ,
943955 } ) ,
944956 )
945957 . finally ( ( ) => {
@@ -1298,27 +1310,27 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
12981310 const i18n = useI18n ( )
12991311 const numfmt = createMemo ( ( ) => new Intl . NumberFormat ( i18n . locale ( ) ) )
13001312 const part = ( ) => props . part as TextPart
1301- const message = bindMessage ( props . message )
13021313 const interrupted = createMemo (
1303- ( ) => message ( ) . role === "assistant" && ( message ( ) as AssistantMessage ) . error ?. name === "MessageAbortedError" ,
1314+ ( ) =>
1315+ props . message . role === "assistant" && ( props . message as AssistantMessage ) . error ?. name === "MessageAbortedError" ,
13041316 )
13051317
13061318 const model = createMemo ( ( ) => {
1307- const current = message ( )
1308- if ( current . role !== "assistant" ) return ""
1309- const match = data . store . provider ?. all ?. find ( ( p ) => p . id === current . providerID )
1310- return match ?. models ?. [ current . modelID ] ?. name ?? current . modelID
1319+ if ( props . message . role !== "assistant" ) return ""
1320+ const message = props . message as AssistantMessage
1321+ const match = data . store . provider ?. all ?. find ( ( p ) => p . id === message . providerID )
1322+ return match ?. models ?. [ message . modelID ] ?. name ?? message . modelID
13111323 } )
13121324
13131325 const duration = createMemo ( ( ) => {
1314- const current = message ( )
1315- if ( current . role !== "assistant" ) return ""
1316- const completed = current . time . completed
1326+ if ( props . message . role !== "assistant" ) return ""
1327+ const message = props . message as AssistantMessage
1328+ const completed = message . time . completed
13171329 const ms =
13181330 typeof props . turnDurationMs === "number"
13191331 ? props . turnDurationMs
13201332 : typeof completed === "number"
1321- ? completed - current . time . created
1333+ ? completed - message . time . created
13221334 : - 1
13231335 if ( ! ( ms >= 0 ) ) return ""
13241336 const total = Math . round ( ms / 1000 )
@@ -1332,9 +1344,8 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
13321344 } )
13331345
13341346 const meta = createMemo ( ( ) => {
1335- const current = message ( )
1336- if ( current . role !== "assistant" ) return ""
1337- const agent = current . agent
1347+ if ( props . message . role !== "assistant" ) return ""
1348+ const agent = ( props . message as AssistantMessage ) . agent
13381349 const items = [
13391350 agent ? agent [ 0 ] ?. toUpperCase ( ) + agent . slice ( 1 ) : "" ,
13401351 model ( ) ,
@@ -1347,13 +1358,13 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
13471358 const displayText = ( ) => ( part ( ) . text ?? "" ) . trim ( )
13481359 const throttledText = createThrottledValue ( displayText )
13491360 const isLastTextPart = createMemo ( ( ) => {
1350- const last = ( data . store . part ?. [ message ( ) . id ] ?? [ ] )
1361+ const last = ( data . store . part ?. [ props . message . id ] ?? [ ] )
13511362 . filter ( ( item ) : item is TextPart => item ?. type === "text" && ! ! item . text ?. trim ( ) )
13521363 . at ( - 1 )
13531364 return last ?. id === part ( ) . id
13541365 } )
13551366 const showCopy = createMemo ( ( ) => {
1356- if ( message ( ) . role !== "assistant" ) return isLastTextPart ( )
1367+ if ( props . message . role !== "assistant" ) return isLastTextPart ( )
13571368 if ( props . showAssistantCopyPartID === null ) return false
13581369 if ( typeof props . showAssistantCopyPartID === "string" ) return props . showAssistantCopyPartID === part ( ) . id
13591370 return isLastTextPart ( )
0 commit comments