From 9a5d90a99f9efb3bee22c2a115b3824747bef9d5 Mon Sep 17 00:00:00 2001 From: seb Date: Tue, 6 Oct 2026 03:15:58 +0200 Subject: [PATCH] u --- src/components/chat/ChatFlowList.jsx | 140 ++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/src/components/chat/ChatFlowList.jsx b/src/components/chat/ChatFlowList.jsx index c80cc67..f072876 100644 --- a/src/components/chat/ChatFlowList.jsx +++ b/src/components/chat/ChatFlowList.jsx @@ -29,6 +29,38 @@ import { SURFACE_RAISED, } from '../../colors'; +class GoogleFaviconIcon extends PureComponent { + state = { hasError: false }; + + handleError = () => { + this.setState({ hasError: true }); + }; + + render() { + const { sx } = this.props; + if (this.state.hasError) { + return ; + } + return ( + + ); + } +} + const TOOL_ICON_MAP = { find_files: SearchRoundedIcon, javascript_execute: ComputerRoundedIcon, @@ -41,7 +73,7 @@ const TOOL_ICON_MAP = { read_file: DescriptionIcon, reset_vfs: DeleteForeverIcon, rm: DeleteIcon, - web_search: GoogleIcon, + web_search: GoogleFaviconIcon, url_get: WebIcon, sub_agent: EscalatorWarningIcon, pdf_read: PictureAsPdfIcon, @@ -318,12 +350,114 @@ function BrainIconView({ reasoning }) { ); } +function extractUrlFromTool(tool) { + if (!tool) return null; + const candidates = [tool.parameters, tool.arguments, tool.content, tool.result]; + for (const item of candidates) { + if (!item) continue; + if (typeof item === 'object') { + const u = item.url || item.Url || item.uri || item.URI || item.link; + if (typeof u === 'string' && u.trim()) return u.trim(); + } + const str = typeof item === 'string' ? item : JSON.stringify(item); + const match = str.match(/https?:\/\/[^\s"'`<>{}|\\]+/); + if (match) { + return match[0].replace(/[.,;:)]+$/, ''); + } + } + return null; +} + +class UrlFaviconIcon extends PureComponent { + state = { + srcIndex: 0, + hasError: false, + }; + + sources = []; + + constructor(props) { + super(props); + this.sources = this.buildSources(props.url); + } + + componentDidUpdate(prevProps) { + if (this.props.url !== prevProps.url) { + this.sources = this.buildSources(this.props.url); + this.setState({ srcIndex: 0, hasError: false }); + } + } + + buildSources(urlStr) { + if (!urlStr || typeof urlStr !== 'string') return []; + try { + const parsed = new URL( + urlStr.startsWith('http://') || urlStr.startsWith('https://') + ? urlStr + : `https://${urlStr}` + ); + const hostname = parsed.hostname; + const origin = parsed.origin; + if (!hostname) return []; + + const list = []; + if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local')) { + list.push(`${origin}/favicon.ico`); + } else { + list.push(`https://icons.duckduckgo.com/ip3/${hostname}.ico`); + list.push(`${origin}/favicon.ico`); + } + return list; + } catch { + return []; + } + } + + handleError = () => { + if (this.state.srcIndex + 1 < this.sources.length) { + this.setState((prev) => ({ srcIndex: prev.srcIndex + 1 })); + } else { + this.setState({ hasError: true }); + } + }; + + render() { + const { sx } = this.props; + const { srcIndex, hasError } = this.state; + const currentSrc = this.sources[srcIndex]; + + if (!currentSrc || hasError) { + return ; + } + + return ( + + ); + } +} + function WorkIconView({ tool }) { if (!tool || !tool.name) return null; const isGreen = tool?.state === 'green'; + const isUrlGet = tool?.name === 'url_get'; const ToolNameIcon = tool?.name ? TOOL_ICON_MAP[tool.name] : null; const prefix = ToolNameIcon ? tool?.name : null; const tooltipText = formatToolTooltip(tool?.content, tool?.result, tool?.parameters || tool?.arguments, prefix); + const targetUrl = isUrlGet ? extractUrlFromTool(tool) : null; return ( - {ToolNameIcon ? ( + {isUrlGet ? ( + + ) : ToolNameIcon ? ( ) : ( <>