@@ -19,8 +19,8 @@ function rowKindClass(m: StoredMessage) {
1919 exception: isException (m ),
2020 }
2121}
22- import { fmtTs , fmtDateSep , toggleToolCall } from ' ../messageRender'
23- import { inlineArgs , resultPreview } from ' ../toolCallFormat'
22+ import { fmtTs , fmtDateSep } from ' ../messageRender'
23+ import { inlineArgs , resultPreviewFromMessage } from ' ../toolCallFormat'
2424import { resolveLabels , tpl } from ' ../labels'
2525import ContentParts from ' ./_ContentParts.vue'
2626import ThinkDrawer from ' ./ThinkDrawer.vue'
@@ -46,6 +46,51 @@ const props = withDefaults(defineProps<{
4646
4747const L = computed (() => resolveLabels (props .labels ))
4848
49+ const toolResultMap = computed (() => {
50+ const map = new Map <string , StoredMessage >()
51+ for (const msg of props .messages ) {
52+ if (msg .message .role === MessageRole .Tool && msg .message .tool_call_id ) {
53+ map .set (msg .message .tool_call_id , msg )
54+ }
55+ }
56+ return map
57+ })
58+
59+ const toolResultPreviewMap = computed (() => {
60+ const map = new Map <string , string >()
61+ for (const [toolCallId, msg] of toolResultMap .value ) {
62+ const preview = resultPreviewFromMessage (msg )
63+ if (preview ) map .set (toolCallId , preview )
64+ }
65+ return map
66+ })
67+
68+ const expandedToolCalls = ref <Set <string >>(new Set ())
69+
70+ function messageKey(msg : StoredMessage , idx : number ): string {
71+ if (msg .id != null ) return ` id:${msg .id } `
72+ const message = msg .message
73+ const role = String (message .role )
74+ const createdAt = msg .createdAt ?? ' na'
75+ const toolCallId = ' tool_call_id' in message ? (message .tool_call_id ?? ' ' ) : ' '
76+ const toolCallIds = ' tool_calls' in message && message .tool_calls
77+ ? message .tool_calls .map ((tc : ToolCall ) => tc .id ).join (' ,' )
78+ : ' '
79+ const thinkId = msg .thinkId ?? ' '
80+ return [role , createdAt , msg .kind , toolCallId , toolCallIds , thinkId , idx ].join (' :' )
81+ }
82+
83+ function isToolCallExpanded(id : string ): boolean {
84+ return expandedToolCalls .value .has (id )
85+ }
86+
87+ function toggleToolCall(id : string ): void {
88+ const next = new Set (expandedToolCalls .value )
89+ if (next .has (id )) next .delete (id )
90+ else next .add (id )
91+ expandedToolCalls .value = next
92+ }
93+
4994function sameDay(a ? : number , b ? : number ) {
5095 if (! a || ! b ) return false
5196 return new Date (a * 1000 ).toDateString () === new Date (b * 1000 ).toDateString ()
@@ -69,7 +114,7 @@ function openThink(thinkId: string) {
69114}
70115
71116function findToolResult(toolCallId : string ): StoredMessage | undefined {
72- return props . messages . find ( m => m . message . role === MessageRole . Tool && m . message . tool_call_id === toolCallId )
117+ return toolResultMap . value . get ( toolCallId )
73118}
74119
75120/** True when the row should be skipped (tool results are rendered nested inside their AI parent). */
@@ -82,7 +127,7 @@ function isEmbeddedTool(msg: StoredMessage): boolean {
82127 <div class =" chatui-messages" >
83128 <div v-if =" messages.length === 0 && !isStreaming" class =" chatui-empty" >{{ L.noHistory }}</div >
84129
85- <template v-for =" (msg , idx ) in messages " :key =" idx " >
130+ <template v-for =" (msg , idx ) in messages " :key =" messageKey ( msg , idx ) " >
86131 <div v-if =" showDateSep(idx)" class =" chatui-date-sep" >
87132 <span >{{ fmtDateSep(msg.createdAt, L.dateToday, L.dateYesterday) }}</span >
88133 </div >
@@ -133,14 +178,20 @@ function isEmbeddedTool(msg: StoredMessage): boolean {
133178 </div >
134179 </div >
135180 <div v-for =" tc in (msg.message.tool_calls as ToolCall[])" :key =" tc.id" class =" tool-call-item" >
136- <div class =" tool-call-header" @click =" toggleToolCall($event.currentTarget as HTMLElement)" >
181+ <button
182+ type =" button"
183+ class =" tool-call-header"
184+ :class =" { expanded: isToolCallExpanded(tc.id) }"
185+ :aria-expanded =" isToolCallExpanded(tc.id)"
186+ @click =" toggleToolCall(tc.id)"
187+ >
137188 <span class =" tool-call-name" >{{ tc.name }}</span >
138189 <span v-if =" inlineArgs(tc)" class =" tool-call-inline-args" >{{ inlineArgs(tc) }}</span >
139- <span v-if =" resultPreview(messages, tc.id)" class =" tool-call-result-preview" >↳ {{ resultPreview(messages, tc.id) }}</span >
140- </div >
141- <div class =" tool-call-detail" >
190+ <span v-if =" toolResultPreviewMap.get( tc.id)" class =" tool-call-result-preview" >↳ {{ toolResultPreviewMap.get( tc.id) }}</span >
191+ </button >
192+ <div class =" tool-call-detail" :class = " { show: isToolCallExpanded(tc.id) } " >
142193 <div class =" tool-call-args" >{{ JSON.stringify(tc.args, null, 2) }}</div >
143- <template v-if =" findToolResult (tc .id ) as StoredMessage | undefined " >
194+ <template v-if =" findToolResult (tc .id )" >
144195 <div class =" tool-call-result" >
145196 <div class =" tool-call-result-top" >
146197 <div class =" tool-call-result-label" >{{ L.toolResult }}</div >
@@ -370,12 +421,18 @@ function isEmbeddedTool(msg: StoredMessage): boolean {
370421 overflow : hidden ;
371422}
372423.tool-call-header {
424+ width : 100% ;
425+ border : 0 ;
426+ background : transparent ;
427+ color : inherit ;
373428 padding : 6px 10px ;
374429 cursor : pointer ;
375430 display : flex ;
376431 align-items : center ;
377432 gap : 8px ;
378433 font-weight : 500 ;
434+ font : inherit ;
435+ text-align : left ;
379436 user-select : none ;
380437}
381438.tool-call-header ::after {
0 commit comments