import React, { useEffect, useState } from 'react' import { Globe, Monitor } from 'lucide-react' import { cn } from '@/utils/cn' import { useNovaKit } from '../../context/useNovaKit' import { ImagePreview } from '@/components/ui/image-preview' export interface BrowserUseActionProps { /** 显示名称(action_name,如"正在浏览") */ name?: string /** 参数数组,第一个通常是 URL */ arguments?: string[] /** 工具原始输出(tool_output) */ toolOutput?: unknown className?: string } interface BrowserUseOutput { result?: { clean_screenshot_path?: string screenshot_path?: string url?: string title?: string } } /** * browser_use 工具调用专属渲染组件 * 对应原始 task-computer-use.tsx,去除了 Antd / UnoCSS / store 依赖 */ function InnerBrowserUseAction({ name, arguments: args, toolOutput, className, }: BrowserUseActionProps) { const { api } = useNovaKit() const [screenshotUrl, setScreenshotUrl] = useState() const [loading, setLoading] = useState(false) const url = args?.[0] || '' // 提取为字符串原始值作为 effect 依赖,只有路径真正变化才重新请求 const screenshotPath = (toolOutput as BrowserUseOutput)?.result?.clean_screenshot_path || (toolOutput as BrowserUseOutput)?.result?.screenshot_path // 加载截图,api 不作为依赖(Context 每次渲染都会返回新引用,加入会反复触发) useEffect(() => { if (!screenshotPath) return let cancelled = false queueMicrotask(() => { if (cancelled) return setLoading(true) }) api .getArtifactUrl?.({ path: screenshotPath, file_name: '', file_type: 'png' }) .then(res => { if (cancelled) return if (res?.data) setScreenshotUrl(res.data) }) .catch(() => {}) .finally(() => { if (cancelled) return setLoading(false) }) return () => { cancelled = true } // eslint-disable-next-line react-hooks/exhaustive-deps }, [screenshotPath]) return (
{/* 标题栏 */}
{name || '正在浏览'} {url && ( {url} )}
{/* 内容区 */}
{loading && (
)} {screenshotUrl && !loading && ( browser screenshot )} {!screenshotUrl && !loading && (
正在执行浏览器任务…
)}
) } export const BrowserUseAction = React.memo(InnerBrowserUseAction) export default BrowserUseAction