@@ -14,6 +14,13 @@ interface ToolCallBlockProps {
1414 chatStatus ?: ChatSpanStatus ;
1515}
1616
17+ interface WebSearchResult {
18+ type ?: string ;
19+ title ?: string ;
20+ url ?: string ;
21+ page_age ?: string ;
22+ }
23+
1724export const ToolCallBlock : FC < ToolCallBlockProps > = memo ( ( { toolCall, toolResponse, chatStatus } ) => {
1825 const { t } = useTranslation ( ) ;
1926 const [ isParamsCopied , setIsParamsCopied ] = useState < boolean > ( false ) ;
@@ -65,6 +72,22 @@ export const ToolCallBlock: FC<ToolCallBlockProps> = memo(({ toolCall, toolRespo
6572 return null ;
6673 } ;
6774
75+ // 检查是否为web_search工具的结果数组
76+ const getWebSearchResults = ( ) : WebSearchResult [ ] | null => {
77+ if ( toolCall . n !== 'web_search' || ! toolResponse ) {
78+ return null ;
79+ }
80+ try {
81+ const parsed = JSON . parse ( toolResponse . r ) ;
82+ if ( Array . isArray ( parsed ) && parsed . length > 0 && parsed [ 0 ] . type === 'web_search_result' ) {
83+ return parsed as WebSearchResult [ ] ;
84+ }
85+ } catch {
86+ return null ;
87+ }
88+ return null ;
89+ } ;
90+
6891 const copyToClipboard = ( text : string , isParams : boolean ) => ( e : React . MouseEvent ) => {
6992 if ( ! navigator . clipboard || ! navigator . clipboard . writeText ) {
7093 return ;
@@ -83,6 +106,7 @@ export const ToolCallBlock: FC<ToolCallBlockProps> = memo(({ toolCall, toolRespo
83106 } ;
84107
85108 const code = getCodeIfAvailable ( ) ;
109+ const webSearchResults = getWebSearchResults ( ) ;
86110
87111 const toggleOpen = ( ) => {
88112 setIsOpen ( ! isOpen ) ;
@@ -92,28 +116,28 @@ export const ToolCallBlock: FC<ToolCallBlockProps> = memo(({ toolCall, toolRespo
92116 return (
93117 < div className = "codeblock relative font-sans text-[16px]" >
94118 { /* Tool header - 统一的标题栏 */ }
95- < div
96- className = "flex items-center gap-2 py-[6px] px-3 bg-[#3d3d3d] cursor-pointer hover:bg-[#454545] transition-all duration-200 ease-in-out"
97- style = { {
119+ < div
120+ className = "flex items-center gap-2 py-[6px] px-3 bg-gray-200 dark:bg-gray-700 cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 transition-all duration-200 ease-in-out"
121+ style = { {
98122 width : isOpen ? '100%' : collapsedWidth ? `${ collapsedWidth } px` : 'fit-content' ,
99123 maxWidth : '100%' ,
100124 justifyContent : isOpen ? 'space-between' : 'flex-start' ,
101- borderTopLeftRadius : 12 ,
125+ borderTopLeftRadius : 12 ,
102126 borderTopRightRadius : 12 ,
103127 borderBottomLeftRadius : isOpen ? 0 : 12 ,
104128 borderBottomRightRadius : isOpen ? 0 : 12 ,
105129 } }
106130 onClick = { toggleOpen }
107131 >
108132 < div className = "flex items-center gap-2" >
109- < span className = "text-blue-400" > 🔧</ span >
110- < span className = "text-sm text-white" > { toolCall . n } </ span >
133+ < span > 🔧</ span >
134+ < span className = "text-sm text-gray-800 dark:text- white" > { toolCall . n } </ span >
111135 </ div >
112- < div
136+ < div
113137 className = "flex items-center transition-transform duration-300 ease-in-out"
114138 style = { { transform : isOpen ? 'rotate(90deg)' : 'rotate(0deg)' } }
115139 >
116- < IconChevronRight size = { 18 } stroke = "#9ca3af " />
140+ < IconChevronRight size = { 18 } className = "stroke-gray-500 " />
117141 </ div >
118142 </ div >
119143
@@ -148,13 +172,13 @@ export const ToolCallBlock: FC<ToolCallBlockProps> = memo(({ toolCall, toolRespo
148172 < Tooltip >
149173 < TooltipTrigger asChild >
150174 < button
151- className = "flex items-center rounded bg-none p-1 text-xs text-white hover:bg-white/10"
175+ className = "flex items-center rounded bg-none p-1 text-xs hover:bg-white/10"
152176 onClick = { copyToClipboard ( code , true ) }
153177 >
154178 { isParamsCopied ? (
155- < IconCheck stroke = { ' white' } size = { 16 } />
179+ < IconCheck stroke = " white" size = { 16 } />
156180 ) : (
157- < IconClipboard stroke = { ' white' } size = { 16 } />
181+ < IconClipboard stroke = " white" size = { 16 } />
158182 ) }
159183 </ button >
160184 </ TooltipTrigger >
@@ -169,28 +193,28 @@ export const ToolCallBlock: FC<ToolCallBlockProps> = memo(({ toolCall, toolRespo
169193 // 普通的参数显示
170194 < div className = "relative group" >
171195 < div
172- className = "whitespace-pre-wrap break-words font-mono text-sm p-4 bg-[#282c34] text-[#abb2bf] "
196+ className = "whitespace-pre-wrap break-words font-mono text-sm p-4 bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 "
173197 style = { {
174198 borderBottomRightRadius : toolResponse ? 0 : 12 ,
175199 borderBottomLeftRadius : toolResponse ? 0 : 12 ,
176200 } }
177201 >
178202 { toolCall . p }
179203 </ div >
180-
204+
181205 { /* 参数区域的复制按钮 */ }
182206 < div className = "absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity" >
183207 < TooltipProvider >
184208 < Tooltip >
185209 < TooltipTrigger asChild >
186210 < button
187- className = "flex items-center rounded bg-none p-1 text-xs text-white hover:bg-white/10"
211+ className = "flex items-center rounded bg-none p-1 text-xs hover:bg-black/10 dark: hover:bg-white/10"
188212 onClick = { copyToClipboard ( toolCall . p , true ) }
189213 >
190214 { isParamsCopied ? (
191- < IconCheck stroke = { 'white' } size = { 16 } />
215+ < IconCheck className = "stroke-gray-600 dark:stroke-gray-300" size = { 16 } />
192216 ) : (
193- < IconClipboard stroke = { 'white' } size = { 16 } />
217+ < IconClipboard className = "stroke-gray-600 dark:stroke-gray-300" size = { 16 } />
194218 ) }
195219 </ button >
196220 </ TooltipTrigger >
@@ -206,19 +230,19 @@ export const ToolCallBlock: FC<ToolCallBlockProps> = memo(({ toolCall, toolRespo
206230
207231 { /* Tool response - 统一的响应区域 */ }
208232 { toolResponse && (
209- < div
233+ < div
210234 className = "overflow-hidden transition-all duration-300 ease-in-out"
211235 style = { {
212236 maxHeight : isOpen ? '2000px' : '0' ,
213237 opacity : isOpen ? 1 : 0 ,
214238 } }
215239 >
216240 { /* Separator line */ }
217- < div className = "bg-[#3d3d3d] h-[1px]" />
241+ < div className = "bg-gray-300 dark:bg-gray-600 h-[1px]" />
218242
219243 { /* Response content */ }
220- < div
221- className = " relative group whitespace-pre-wrap break-words text-sm p-4 bg-[#282c34] text-[#abb2bf]"
244+ < div
245+ className = { ` relative group text-sm bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 ${ webSearchResults ? 'p-2' : 'p-4' } ` }
222246 style = { {
223247 borderBottomRightRadius : 12 ,
224248 borderBottomLeftRadius : 12 ,
@@ -230,13 +254,13 @@ export const ToolCallBlock: FC<ToolCallBlockProps> = memo(({ toolCall, toolRespo
230254 < Tooltip >
231255 < TooltipTrigger asChild >
232256 < button
233- className = "flex items-center rounded bg-none p-1 text-xs text-white hover:bg-white/10"
257+ className = "flex items-center rounded bg-none p-1 text-xs hover:bg-black/10 dark: hover:bg-white/10"
234258 onClick = { copyToClipboard ( toolResponse . r , false ) }
235259 >
236260 { isResponseCopied ? (
237- < IconCheck stroke = { 'white' } size = { 16 } />
261+ < IconCheck className = "stroke-gray-600 dark:stroke-gray-300" size = { 16 } />
238262 ) : (
239- < IconClipboard stroke = { 'white' } size = { 16 } />
263+ < IconClipboard className = "stroke-gray-600 dark:stroke-gray-300" size = { 16 } />
240264 ) }
241265 </ button >
242266 </ TooltipTrigger >
@@ -246,7 +270,42 @@ export const ToolCallBlock: FC<ToolCallBlockProps> = memo(({ toolCall, toolRespo
246270 </ Tooltip >
247271 </ TooltipProvider >
248272 </ div >
249- { toolResponse . r }
273+ { webSearchResults ? (
274+ < table className = "w-full border-collapse text-left m-0" >
275+ < thead >
276+ < tr className = "border-b border-gray-300 dark:border-gray-600" >
277+ < th className = "py-1 pr-3 font-medium" > { t ( 'Title' ) } </ th >
278+ < th className = "py-1 px-3 font-medium whitespace-nowrap" > { t ( 'Age' ) } </ th >
279+ </ tr >
280+ </ thead >
281+ < tbody >
282+ { webSearchResults . map ( ( result , index ) => (
283+ < tr key = { index } className = "border-b border-gray-300 dark:border-gray-600 last:border-b-0 hover:bg-gray-200 dark:hover:bg-gray-700" >
284+ < td className = "py-1 pr-3" title = { result . url } >
285+ { result . url ? (
286+ < a
287+ href = { result . url }
288+ target = "_blank"
289+ rel = "noopener noreferrer"
290+ className = "text-blue-600 dark:text-blue-400 hover:underline"
291+ onClick = { ( e ) => e . stopPropagation ( ) }
292+ >
293+ { result . title || result . url }
294+ </ a >
295+ ) : ( result . title || '-' ) }
296+ </ td >
297+ < td className = "py-1 px-3 whitespace-nowrap" >
298+ { result . page_age || '-' }
299+ </ td >
300+ </ tr >
301+ ) ) }
302+ </ tbody >
303+ </ table >
304+ ) : (
305+ < div className = "whitespace-pre-wrap break-words" >
306+ { toolResponse . r }
307+ </ div >
308+ ) }
250309 </ div >
251310 </ div >
252311 ) }
@@ -256,9 +315,9 @@ export const ToolCallBlock: FC<ToolCallBlockProps> = memo(({ toolCall, toolRespo
256315 className = "absolute -z-10 inline-flex items-center gap-2 py-[6px] px-3"
257316 style = { { visibility : 'hidden' , pointerEvents : 'none' , whiteSpace : 'nowrap' } }
258317 >
259- < span className = "text-blue-400" > 🔧</ span >
260- < span className = "text-sm text-white " > { toolCall . n } </ span >
261- < IconChevronRight size = { 18 } stroke = "#9ca3af " />
318+ < span > 🔧</ span >
319+ < span className = "text-sm" > { toolCall . n } </ span >
320+ < IconChevronRight size = { 18 } className = "stroke-gray-500 " />
262321 </ div >
263322 </ div >
264323 ) ;
0 commit comments