This commit is contained in:
seb
2026-10-06 03:15:58 +02:00
parent 12db70e1ae
commit 9a5d90a99f

View File

@@ -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 <GoogleIcon sx={sx} />;
}
return (
<Box
component="img"
src="https://www.google.com/favicon.ico"
alt="Google"
onError={this.handleError}
sx={{
width: 18,
height: 18,
borderRadius: '2px',
objectFit: 'contain',
verticalAlign: 'middle',
display: 'inline-block',
...sx,
}}
/>
);
}
}
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 <WebIcon sx={sx} />;
}
return (
<Box
component="img"
src={currentSrc}
alt=""
onError={this.handleError}
sx={{
width: 18,
height: 18,
borderRadius: '2px',
objectFit: 'contain',
verticalAlign: 'middle',
display: 'inline-block',
...sx,
}}
/>
);
}
}
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 (
<ClickTooltip
@@ -358,7 +492,9 @@ function WorkIconView({ tool }) {
},
}}
>
{ToolNameIcon ? (
{isUrlGet ? (
<UrlFaviconIcon url={targetUrl} sx={{ fontSize: 18, color: 'inherit' }} />
) : ToolNameIcon ? (
<ToolNameIcon sx={{ fontSize: 18, color: 'inherit' }} />
) : (
<>