@@ -15,11 +15,13 @@ import type { BashTool } from "opencode/tool/bash"
1515import type { EditTool } from "opencode/tool/edit"
1616import type { WriteTool } from "opencode/tool/write"
1717import { DiffChanges } from "./diff-changes"
18+ import { TodoWriteTool } from "opencode/tool/todo"
1819
1920export function AssistantMessage ( props : { message : AssistantMessage ; parts : Part [ ] } ) {
21+ const filteredParts = createMemo ( ( ) => props . parts . filter ( ( x ) => x . type !== "tool" || x . tool !== "todoread" ) )
2022 return (
2123 < div class = "w-full flex flex-col items-start gap-4" >
22- < For each = { props . parts } >
24+ < For each = { filteredParts ( ) } >
2325 { ( part ) => {
2426 const component = createMemo ( ( ) => PART_MAPPING [ part . type as keyof typeof PART_MAPPING ] )
2527 return (
@@ -88,8 +90,11 @@ function ToolPart(props: { part: ToolPart; message: AssistantMessage }) {
8890
8991type TriggerTitle = {
9092 title : string
93+ titleClass ?: string
9194 subtitle ?: string
95+ subtitleClass ?: string
9296 args ?: string [ ]
97+ argsClass ?: string
9398 action ?: JSX . Element
9499}
95100
@@ -99,34 +104,58 @@ const isTriggerTitle = (val: any): val is TriggerTitle => {
99104
100105function BasicTool ( props : { icon : IconProps [ "name" ] ; trigger : TriggerTitle | JSX . Element ; children ?: JSX . Element } ) {
101106 const resolved = children ( ( ) => props . children )
102-
103107 return (
104108 < Collapsible >
105109 < Collapsible . Trigger >
106110 < div class = "w-full flex items-center self-stretch gap-5 justify-between" >
107111 < div class = "w-full flex items-center self-stretch gap-5" >
108- < Icon name = { props . icon } size = "small" />
109- < Switch >
110- < Match when = { isTriggerTitle ( props . trigger ) } >
111- < div class = "w-full flex items-center gap-2 justify-between" >
112- < div class = "flex items-center gap-2" >
113- < span class = "text-12-medium text-text-base capitalize" >
114- { ( props . trigger as TriggerTitle ) . title }
115- </ span >
116- < Show when = { ( props . trigger as TriggerTitle ) . subtitle } >
117- < span class = "text-12-medium text-text-weak" > { ( props . trigger as TriggerTitle ) . subtitle } </ span >
118- </ Show >
119- < Show when = { ( props . trigger as TriggerTitle ) . args ?. length } >
120- < For each = { ( props . trigger as TriggerTitle ) . args } >
121- { ( arg ) => < span class = "text-12-regular text-text-weaker" > { arg } </ span > }
122- </ For >
123- </ Show >
124- </ div >
125- < Show when = { ( props . trigger as TriggerTitle ) . action } > { ( props . trigger as TriggerTitle ) . action } </ Show >
126- </ div >
127- </ Match >
128- < Match when = { true } > { props . trigger as JSX . Element } </ Match >
129- </ Switch >
112+ < Icon name = { props . icon } size = "small" class = "shrink-0" />
113+ < div class = "grow min-w-0" >
114+ < Switch >
115+ < Match when = { isTriggerTitle ( props . trigger ) && props . trigger } >
116+ { ( trigger ) => (
117+ < div class = "w-full flex items-center gap-2 justify-between" >
118+ < div class = "flex items-center gap-2 whitespace-nowrap truncate" >
119+ < span
120+ classList = { {
121+ "text-12-medium text-text-base" : true ,
122+ [ trigger ( ) . titleClass ?? "" ] : ! ! trigger ( ) . titleClass ,
123+ } }
124+ >
125+ { trigger ( ) . title }
126+ </ span >
127+ < Show when = { trigger ( ) . subtitle } >
128+ < span
129+ classList = { {
130+ "text-12-medium text-text-weak" : true ,
131+ [ trigger ( ) . subtitleClass ?? "" ] : ! ! trigger ( ) . subtitleClass ,
132+ } }
133+ >
134+ { trigger ( ) . subtitle }
135+ </ span >
136+ </ Show >
137+ < Show when = { trigger ( ) . args ?. length } >
138+ < For each = { trigger ( ) . args } >
139+ { ( arg ) => (
140+ < span
141+ classList = { {
142+ "text-12-regular text-text-weak" : true ,
143+ [ trigger ( ) . argsClass ?? "" ] : ! ! trigger ( ) . argsClass ,
144+ } }
145+ >
146+ { arg }
147+ </ span >
148+ ) }
149+ </ For >
150+ </ Show >
151+ </ div >
152+ < Show when = { trigger ( ) . action } > { trigger ( ) . action } </ Show >
153+ </ div >
154+ ) }
155+ </ Match >
156+ < Match when = { true } > { props . trigger as JSX . Element } </ Match >
157+ </ Switch >
158+ </ div >
130159 </ div >
131160 < Show when = { resolved ( ) } >
132161 < Collapsible . Arrow />
@@ -178,7 +207,7 @@ ToolRegistry.register<typeof ReadTool>({
178207 return (
179208 < BasicTool
180209 icon = "glasses"
181- trigger = { { title : props . tool , subtitle : props . input . filePath ? getFilename ( props . input . filePath ) : "" } }
210+ trigger = { { title : "Read" , subtitle : props . input . filePath ? getFilename ( props . input . filePath ) : "" } }
182211 />
183212 )
184213 } ,
@@ -188,7 +217,7 @@ ToolRegistry.register<typeof ListTool>({
188217 name : "list" ,
189218 render ( props ) {
190219 return (
191- < BasicTool icon = "bullet-list" trigger = { { title : props . tool , subtitle : getDirectory ( props . input . path || "/" ) } } >
220+ < BasicTool icon = "bullet-list" trigger = { { title : "List" , subtitle : getDirectory ( props . input . path || "/" ) } } >
192221 < Show when = { false && props . output } >
193222 < div class = "whitespace-pre" > { props . output } </ div >
194223 </ Show >
@@ -204,7 +233,7 @@ ToolRegistry.register<typeof GlobTool>({
204233 < BasicTool
205234 icon = "magnifying-glass-menu"
206235 trigger = { {
207- title : props . tool ,
236+ title : "Glob" ,
208237 subtitle : getDirectory ( props . input . path || "/" ) ,
209238 args : props . input . pattern ? [ "pattern=" + props . input . pattern ] : [ ] ,
210239 } }
@@ -227,7 +256,7 @@ ToolRegistry.register<typeof GrepTool>({
227256 < BasicTool
228257 icon = "magnifying-glass-menu"
229258 trigger = { {
230- title : props . tool ,
259+ title : "Grep" ,
231260 subtitle : getDirectory ( props . input . path || "/" ) ,
232261 args,
233262 } }
@@ -247,7 +276,7 @@ ToolRegistry.register<typeof WebFetchTool>({
247276 < BasicTool
248277 icon = "window-cursor"
249278 trigger = { {
250- title : props . tool ,
279+ title : "Webfetch" ,
251280 subtitle : props . input . url || "" ,
252281 args : props . input . format ? [ "format=" + props . input . format ] : [ ] ,
253282 action : (
@@ -273,6 +302,7 @@ ToolRegistry.register<typeof TaskTool>({
273302 icon = "task"
274303 trigger = { {
275304 title : `${ props . input . subagent_type || props . tool } Agent` ,
305+ titleClass : "capitalize" ,
276306 subtitle : props . input . description ,
277307 } }
278308 >
@@ -311,7 +341,7 @@ ToolRegistry.register<typeof EditTool>({
311341 icon = "code-lines"
312342 trigger = {
313343 < div class = "flex items-center justify-between w-full" >
314- < div class = "flex items-center gap-5 " >
344+ < div class = "flex items-center gap-2 " >
315345 < div class = "text-12-medium text-text-base capitalize" > Edit</ div >
316346 < div class = "flex" >
317347 < Show when = { props . input . filePath ?. includes ( "/" ) } >
@@ -340,7 +370,7 @@ ToolRegistry.register<typeof WriteTool>({
340370 icon = "code-lines"
341371 trigger = {
342372 < div class = "flex items-center justify-between w-full" >
343- < div class = "flex items-center gap-5 " >
373+ < div class = "flex items-center gap-2 " >
344374 < div class = "text-12-medium text-text-base capitalize" > Write</ div >
345375 < div class = "flex" >
346376 < Show when = { props . input . filePath ?. includes ( "/" ) } >
@@ -360,3 +390,22 @@ ToolRegistry.register<typeof WriteTool>({
360390 )
361391 } ,
362392} )
393+
394+ ToolRegistry . register < typeof TodoWriteTool > ( {
395+ name : "todowrite" ,
396+ render ( props ) {
397+ return (
398+ < BasicTool
399+ icon = "checklist"
400+ trigger = { {
401+ title : "To-dos" ,
402+ subtitle : `${ props . input . todos ?. filter ( ( t ) => t . status === "completed" ) . length } /${ props . input . todos ?. length } ` ,
403+ } }
404+ >
405+ < Show when = { false && props . output } >
406+ < div class = "whitespace-pre" > { props . output } </ div >
407+ </ Show >
408+ </ BasicTool >
409+ )
410+ } ,
411+ } )
0 commit comments