初始化模版工程
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
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<string>()
|
||||
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 (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col rounded-xl border border-gray-200 overflow-hidden bg-white w-full max-w-[480px] mb-4',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{/* 标题栏 */}
|
||||
<div className="flex items-center gap-2 px-3 py-2 bg-gray-50 border-b border-gray-100">
|
||||
<Globe className="w-4 h-4 text-muted-foreground shrink-0" />
|
||||
<div className="flex flex-col min-w-0 flex-1">
|
||||
<span className="text-sm font-medium leading-tight">
|
||||
{name || '正在浏览'}
|
||||
</span>
|
||||
{url && (
|
||||
<span className="text-xs text-muted-foreground truncate">
|
||||
{url}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 内容区 */}
|
||||
<div
|
||||
className={cn(
|
||||
'relative bg-white',
|
||||
screenshotUrl || loading ? 'pt-[56.25%]' : 'py-8',
|
||||
)}
|
||||
>
|
||||
{loading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-6 h-6 border-2 border-muted border-t-primary rounded-full animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{screenshotUrl && !loading && (
|
||||
<ImagePreview src={screenshotUrl} alt="browser screenshot">
|
||||
<img
|
||||
className="absolute inset-0 w-full h-full object-contain cursor-zoom-in"
|
||||
src={screenshotUrl}
|
||||
alt="browser screenshot"
|
||||
/>
|
||||
</ImagePreview>
|
||||
)}
|
||||
|
||||
{!screenshotUrl && !loading && (
|
||||
<div className="flex flex-col items-center justify-center gap-2 text-muted-foreground">
|
||||
<Monitor className="w-8 h-8 opacity-40" />
|
||||
<span className="text-xs">正在执行浏览器任务…</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const BrowserUseAction = React.memo(InnerBrowserUseAction)
|
||||
export default BrowserUseAction
|
||||
Reference in New Issue
Block a user