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 ? (
) : (
<>