@@ -33,8 +33,8 @@ import {
33
33
WRITE_FILE_TOOL_NAME ,
34
34
type WRITE_FILE_TOOL_PARAMETERS
35
35
} from '@onlook/ai' ;
36
+ import { Tool , ToolContent , ToolHeader , ToolInput , ToolOutput } from '@onlook/ui/ai-elements' ;
36
37
import { Icons } from '@onlook/ui/icons' ;
37
- import { cn } from '@onlook/ui/utils' ;
38
38
import type { ToolUIPart } from 'ai' ;
39
39
import { type z } from 'zod' ;
40
40
@@ -62,10 +62,6 @@ const TOOL_ICONS: Record<string, any> = {
62
62
[ GLOB_TOOL_NAME ] : Icons . MagnifyingGlass ,
63
63
} as const ;
64
64
65
- function truncateString ( str : string , maxLength : number = 30 ) {
66
- return str . length > maxLength ? str . substring ( 0 , maxLength ) + '...' : str ;
67
- }
68
-
69
65
export function ToolCallSimple ( {
70
66
toolPart,
71
67
className,
@@ -77,149 +73,155 @@ export function ToolCallSimple({
77
73
} ) {
78
74
const toolName = toolPart . type . split ( '-' ) [ 1 ] ?? '' ;
79
75
const Icon = TOOL_ICONS [ toolName ] ?? Icons . QuestionMarkCircled ;
76
+ const title = getLabel ( toolName , toolPart ) ;
77
+ return (
78
+ < Tool className = { className } >
79
+ < ToolHeader loading = { loading } title = { title } type = { toolPart . type } state = { toolPart . state } icon = { < Icon className = "w-4 h-4 flex-shrink-0" /> } />
80
+ < ToolContent >
81
+ < ToolInput input = { toolPart . input } />
82
+ < ToolOutput errorText = { toolPart . errorText } output = { toolPart . output } />
83
+ </ ToolContent >
84
+ </ Tool >
85
+ )
86
+ }
80
87
81
- const getLabel = ( ) => {
82
- try {
83
- switch ( toolName ) {
84
- case TERMINAL_COMMAND_TOOL_NAME :
85
- return 'Terminal' ;
86
- case LIST_BRANCHES_TOOL_NAME :
87
- return 'Listing branches' ;
88
- case SEARCH_REPLACE_EDIT_FILE_TOOL_NAME :
89
- const params = toolPart . input as z . infer < typeof SEARCH_REPLACE_EDIT_FILE_TOOL_PARAMETERS > ;
90
- if ( params ?. file_path ) {
91
- return 'Editing ' + ( params . file_path . split ( '/' ) . pop ( ) || '' ) ;
92
- } else {
93
- return 'Editing file' ;
94
- }
95
- case SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_NAME :
96
- const params1 = toolPart . input as z . infer < typeof SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_PARAMETERS > ;
97
- if ( params1 ?. edits ) {
98
- return 'Editing ' + ( params1 . file_path . split ( '/' ) . pop ( ) || '' ) ;
99
- } else {
100
- return 'Editing files' ;
101
- }
102
- case FUZZY_EDIT_FILE_TOOL_NAME :
103
- const params2 = toolPart . input as z . infer < typeof FUZZY_EDIT_FILE_TOOL_PARAMETERS > ;
104
- if ( params2 ?. file_path ) {
105
- return 'Editing ' + ( params2 . file_path . split ( '/' ) . pop ( ) || '' ) ;
106
- } else {
107
- return 'Editing file' ;
108
- }
109
- case WRITE_FILE_TOOL_NAME :
110
- const params3 = toolPart . input as z . infer < typeof WRITE_FILE_TOOL_PARAMETERS > ;
111
- if ( params3 ?. file_path ) {
112
- return 'Writing file ' + ( params3 . file_path . split ( '/' ) . pop ( ) || '' ) ;
113
- } else {
114
- return 'Writing file' ;
115
- }
116
- case LIST_FILES_TOOL_NAME :
117
- const params4 = toolPart . input as z . infer < typeof LIST_FILES_TOOL_PARAMETERS > ;
118
- if ( params4 ?. path ) {
119
- return 'Reading directory ' + ( params4 . path . split ( '/' ) . pop ( ) || '' ) ;
120
- } else {
121
- return 'Reading directory' ;
122
- }
123
- case READ_FILE_TOOL_NAME :
124
- const params5 = toolPart . input as z . infer < typeof READ_FILE_TOOL_PARAMETERS > ;
125
- if ( params5 ?. file_path ) {
126
- return 'Reading file ' + ( params5 . file_path . split ( '/' ) . pop ( ) || '' ) ;
127
- } else {
128
- return 'Reading files' ;
129
- }
130
- case SCRAPE_URL_TOOL_NAME :
131
- const params6 = toolPart . input as z . infer < typeof SCRAPE_URL_TOOL_PARAMETERS > ;
132
- if ( params6 ?. url ) {
133
- return 'Visiting ' + ( new URL ( params6 . url ) . hostname || 'URL' ) ;
134
- } else {
135
- return 'Visiting URL' ;
136
- }
137
- case WEB_SEARCH_TOOL_NAME :
138
- if ( toolPart . input && typeof toolPart . input === 'object' && 'query' in toolPart . input ) {
139
- const params10 = toolPart . input as z . infer < typeof WEB_SEARCH_TOOL_PARAMETERS > ;
140
- const query = params10 . query ;
141
- return "Searching \"" + truncateString ( query ) + "\"" ;
142
- } else {
143
- return 'Searching web' ;
144
- }
145
- case SANDBOX_TOOL_NAME :
146
- if ( toolPart . input && typeof toolPart . input === 'object' && 'command' in toolPart . input ) {
147
- return 'Sandbox: ' + toolPart . input ?. command ;
148
- } else {
149
- return 'Sandbox' ;
150
- }
151
- case GREP_TOOL_NAME :
152
- if ( toolPart . input && typeof toolPart . input === 'object' && 'pattern' in toolPart . input ) {
153
- const params11 = toolPart . input as z . infer < typeof GREP_TOOL_PARAMETERS > ;
154
- const pattern = params11 . pattern ;
155
- return 'Searching for ' + truncateString ( pattern ) ;
156
- } else {
157
- return 'Searching' ;
158
- }
159
- case BASH_EDIT_TOOL_NAME :
160
- const params7 = toolPart . input as z . infer < typeof BASH_EDIT_TOOL_PARAMETERS > ;
161
- if ( params7 ?. command ) {
162
- return 'Running command ' + ( params7 . command . split ( '/' ) . pop ( ) || '' ) ;
163
- } else {
164
- return 'Running command' ;
165
- }
166
- case BASH_READ_TOOL_NAME :
167
- const params8 = toolPart . input as z . infer < typeof BASH_READ_TOOL_PARAMETERS > ;
168
- if ( params8 ?. command ) {
169
- return 'Reading file ' + ( params8 . command . split ( '/' ) . pop ( ) || '' ) ;
170
- } else {
171
- return 'Reading file' ;
172
- }
173
- case TODO_WRITE_TOOL_NAME :
174
- const params9 = toolPart . input as z . infer < typeof TODO_WRITE_TOOL_PARAMETERS > ;
175
- if ( params9 ?. todos ) {
176
- return 'Writing todos ' + ( params9 ?. todos . map ( ( todo : { content : string ; status : string ; priority : string ; } ) => todo . content ) . join ( ', ' ) || '' ) ;
177
- } else {
178
- return 'Writing todos' ;
179
- }
180
- case GLOB_TOOL_NAME :
181
- const params12 = toolPart . input as z . infer < typeof GLOB_TOOL_PARAMETERS > ;
182
- if ( params12 ?. pattern ) {
183
- return 'Searching for ' + truncateString ( params12 . pattern ) ;
184
- } else {
185
- return 'Searching' ;
186
- }
187
- case EXIT_PLAN_MODE_TOOL_NAME :
188
- return 'Exiting plan mode' ;
189
- case READ_STYLE_GUIDE_TOOL_NAME :
190
- return 'Reading style guide' ;
191
- case ONLOOK_INSTRUCTIONS_TOOL_NAME :
192
- return 'Reading Onlook instructions' ;
193
- case TYPECHECK_TOOL_NAME :
194
- return 'Checking types' ;
195
- default :
196
- return toolName ?. replace ( / [ - _ ] / g, ' ' ) . replace ( / \b \w / g, c => c . toUpperCase ( ) ) ;
88
+ function truncateString ( str : string , maxLength : number = 30 ) {
89
+ return str . length > maxLength ? str . substring ( 0 , maxLength ) + '...' : str ;
90
+ }
91
+
92
+ const getLabel = ( toolName : string , toolPart : ToolUIPart ) => {
93
+ try {
94
+ switch ( toolName ) {
95
+ case TERMINAL_COMMAND_TOOL_NAME :
96
+ return 'Terminal' ;
97
+ case LIST_BRANCHES_TOOL_NAME :
98
+ return 'Listing branches' ;
99
+ case SEARCH_REPLACE_EDIT_FILE_TOOL_NAME : {
100
+ const params = toolPart . input as z . infer < typeof SEARCH_REPLACE_EDIT_FILE_TOOL_PARAMETERS > ;
101
+ if ( params ?. file_path ) {
102
+ return 'Editing ' + ( params . file_path . split ( '/' ) . pop ( ) || '' ) ;
103
+ } else {
104
+ return 'Editing file' ;
105
+ }
106
+ }
107
+ case SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_NAME : {
108
+ const params = toolPart . input as z . infer < typeof SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_PARAMETERS > ;
109
+ if ( params ?. edits ) {
110
+ return 'Editing ' + ( params . file_path . split ( '/' ) . pop ( ) || '' ) ;
111
+ } else {
112
+ return 'Editing files' ;
113
+ }
114
+ }
115
+ case FUZZY_EDIT_FILE_TOOL_NAME : {
116
+ const params = toolPart . input as z . infer < typeof FUZZY_EDIT_FILE_TOOL_PARAMETERS > ;
117
+ if ( params ?. file_path ) {
118
+ return 'Editing ' + ( params . file_path . split ( '/' ) . pop ( ) || '' ) ;
119
+ } else {
120
+ return 'Editing file' ;
121
+ }
197
122
}
198
- } catch ( error ) {
199
- console . error ( 'Error getting label' , error ) ;
200
- return toolName ?. replace ( / [ - _ ] / g, ' ' ) . replace ( / \b \w / g, c => c . toUpperCase ( ) ) ;
123
+ case WRITE_FILE_TOOL_NAME : {
124
+ const params = toolPart . input as z . infer < typeof WRITE_FILE_TOOL_PARAMETERS > ;
125
+ if ( params ?. file_path ) {
126
+ return 'Writing file ' + ( params . file_path . split ( '/' ) . pop ( ) || '' ) ;
127
+ } else {
128
+ return 'Writing file' ;
129
+ }
130
+ }
131
+ case LIST_FILES_TOOL_NAME : {
132
+ const params = toolPart . input as z . infer < typeof LIST_FILES_TOOL_PARAMETERS > ;
133
+ if ( params ?. path ) {
134
+ return 'Reading directory ' + ( params . path . split ( '/' ) . pop ( ) || '' ) ;
135
+ } else {
136
+ return 'Reading directory' ;
137
+ }
138
+ }
139
+ case READ_FILE_TOOL_NAME : {
140
+ const params = toolPart . input as z . infer < typeof READ_FILE_TOOL_PARAMETERS > ;
141
+ if ( params ?. file_path ) {
142
+ return 'Reading file ' + ( params . file_path . split ( '/' ) . pop ( ) || '' ) ;
143
+ } else {
144
+ return 'Reading files' ;
145
+ }
146
+ }
147
+ case SCRAPE_URL_TOOL_NAME : {
148
+ const params = toolPart . input as z . infer < typeof SCRAPE_URL_TOOL_PARAMETERS > ;
149
+ if ( params ?. url ) {
150
+ return 'Visiting ' + ( new URL ( params . url ) . hostname || 'URL' ) ;
151
+ } else {
152
+ return 'Visiting URL' ;
153
+ }
154
+ }
155
+ case WEB_SEARCH_TOOL_NAME : {
156
+ if ( toolPart . input && typeof toolPart . input === 'object' && 'query' in toolPart . input ) {
157
+ const params = toolPart . input as z . infer < typeof WEB_SEARCH_TOOL_PARAMETERS > ;
158
+ const query = params . query ;
159
+ return "Searching \"" + truncateString ( query ) + "\"" ;
160
+ } else {
161
+ return 'Searching web' ;
162
+ }
163
+ }
164
+ case SANDBOX_TOOL_NAME : {
165
+ if ( toolPart . input && typeof toolPart . input === 'object' && 'command' in toolPart . input ) {
166
+ return 'Sandbox: ' + toolPart . input ?. command ;
167
+ } else {
168
+ return 'Sandbox' ;
169
+ }
170
+ }
171
+ case GREP_TOOL_NAME : {
172
+ if ( toolPart . input && typeof toolPart . input === 'object' && 'pattern' in toolPart . input ) {
173
+ const params = toolPart . input as z . infer < typeof GREP_TOOL_PARAMETERS > ;
174
+ const pattern = params . pattern ;
175
+ return 'Searching for ' + truncateString ( pattern ) ;
176
+ } else {
177
+ return 'Searching' ;
178
+ }
179
+ }
180
+ case BASH_EDIT_TOOL_NAME : {
181
+ const params = toolPart . input as z . infer < typeof BASH_EDIT_TOOL_PARAMETERS > ;
182
+ if ( params ?. command ) {
183
+ return 'Running command ' + ( params . command . split ( '/' ) . pop ( ) || '' ) ;
184
+ } else {
185
+ return 'Running command' ;
186
+ }
187
+ }
188
+ case BASH_READ_TOOL_NAME : {
189
+ const params = toolPart . input as z . infer < typeof BASH_READ_TOOL_PARAMETERS > ;
190
+ if ( params ?. command ) {
191
+ return 'Reading file ' + ( params . command . split ( '/' ) . pop ( ) || '' ) ;
192
+ } else {
193
+ return 'Reading file' ;
194
+ }
195
+ }
196
+ case TODO_WRITE_TOOL_NAME : {
197
+ const params = toolPart . input as z . infer < typeof TODO_WRITE_TOOL_PARAMETERS > ;
198
+ if ( params ?. todos ) {
199
+ return 'Writing todos ' + ( params . todos . map ( ( todo : { content : string ; status : string ; priority : string ; } ) => todo . content ) . join ( ', ' ) || '' ) ;
200
+ } else {
201
+ return 'Writing todos' ;
202
+ }
203
+ }
204
+ case GLOB_TOOL_NAME : {
205
+ const params = toolPart . input as z . infer < typeof GLOB_TOOL_PARAMETERS > ;
206
+ if ( params ?. pattern ) {
207
+ return 'Searching for ' + truncateString ( params . pattern ) ;
208
+ } else {
209
+ return 'Searching' ;
210
+ }
211
+ }
212
+ case EXIT_PLAN_MODE_TOOL_NAME :
213
+ return 'Exiting plan mode' ;
214
+ case READ_STYLE_GUIDE_TOOL_NAME :
215
+ return 'Reading style guide' ;
216
+ case ONLOOK_INSTRUCTIONS_TOOL_NAME :
217
+ return 'Reading Onlook instructions' ;
218
+ case TYPECHECK_TOOL_NAME :
219
+ return 'Checking types' ;
220
+ default :
221
+ return toolName ?. replace ( / [ - _ ] / g, ' ' ) . replace ( / \b \w / g, c => c . toUpperCase ( ) ) ;
201
222
}
223
+ } catch ( error ) {
224
+ console . error ( 'Error getting label' , error ) ;
225
+ return toolName ?. replace ( / [ - _ ] / g, ' ' ) . replace ( / \b \w / g, c => c . toUpperCase ( ) ) ;
202
226
}
203
- return (
204
- < div className = "flex flex-col gap-2" >
205
- < div className = { cn ( 'flex items-center gap-2 ml-2 text-foreground-tertiary/80' , className ) } >
206
- < Icon className = "w-4 h-4 flex-shrink-0" />
207
- < span
208
- className = { cn (
209
- 'text-regularPlus' ,
210
- loading &&
211
- 'bg-gradient-to-l from-white/20 via-white/90 to-white/20 bg-[length:200%_100%] bg-clip-text text-transparent animate-shimmer filter drop-shadow-[0_0_10px_rgba(255,255,255,0.4)]'
212
- ) }
213
- >
214
- { getLabel ( ) }
215
- </ span >
216
- </ div >
217
- { ( toolPart . state === 'output-error' ) && (
218
- < div className = "flex items-start gap-2 ml-2 text-red-500 text-small max-h-32 overflow-y-auto border-l" >
219
- < Icons . ExclamationTriangle className = "w-4 h-4 flex-shrink-0" />
220
- < span className = "text-regularPlus" > { toolPart . errorText || 'Error calling tool' } </ span >
221
- </ div >
222
- ) }
223
- </ div >
224
- ) ;
225
- }
227
+ }
0 commit comments