feat: show client tools in debug preview
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
Sparkles,
|
||||
Video,
|
||||
Waves,
|
||||
Wrench,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
|
||||
@@ -49,6 +50,7 @@ import { usePhotoCaptureTool } from "@/hooks/use-photo-capture-tool";
|
||||
import {
|
||||
useVoicePreview,
|
||||
type ChatMessage,
|
||||
type ClientToolDefinition,
|
||||
type VoicePreview,
|
||||
type VoicePreviewStatus,
|
||||
} from "@/hooks/use-voice-preview";
|
||||
@@ -216,6 +218,7 @@ export function DebugDrawer({
|
||||
/>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<ClientToolsPopover tools={preview.clientTools} />
|
||||
<CallPreviewLink assistantId={assistantId} />
|
||||
{dynamicVariablesEnabled && (
|
||||
<DynamicVariableValuesPopover
|
||||
@@ -284,6 +287,131 @@ export function DebugDrawer({
|
||||
);
|
||||
}
|
||||
|
||||
function ClientToolsPopover({ tools }: { tools: ClientToolDefinition[] }) {
|
||||
const [copiedToolName, setCopiedToolName] = useState<string | null>(null);
|
||||
|
||||
const copyToolName = useCallback(async (toolName: string) => {
|
||||
await navigator.clipboard.writeText(toolName);
|
||||
setCopiedToolName(toolName);
|
||||
window.setTimeout(
|
||||
() =>
|
||||
setCopiedToolName((current) =>
|
||||
current === toolName ? null : current,
|
||||
),
|
||||
1600,
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="查看调试端支持的 Client Tools"
|
||||
title="调试端支持的 Client Tools"
|
||||
className="relative flex h-8 w-8 items-center justify-center rounded-full border border-hairline bg-canvas-soft text-muted-foreground transition-colors hover:bg-surface-strong hover:text-foreground"
|
||||
>
|
||||
<Wrench size={15} />
|
||||
<span className="absolute -right-1 -top-1 flex h-4 min-w-4 items-center justify-center rounded-full border border-card bg-surface-strong px-1 text-[9px] tabular-nums text-foreground">
|
||||
{tools.length}
|
||||
</span>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
side="bottom"
|
||||
className="w-80 space-y-3 rounded-2xl p-4"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
|
||||
<Wrench size={15} />
|
||||
Client Tools
|
||||
</div>
|
||||
<p className="text-xs leading-5 text-muted-foreground">
|
||||
当前调试客户端已注册、能够响应的工具。
|
||||
</p>
|
||||
</div>
|
||||
{tools.length === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-hairline-strong bg-canvas-soft px-4 py-4 text-center text-xs text-muted-foreground">
|
||||
当前没有可用的 Client Tool
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-72 space-y-2 overflow-y-auto pr-1">
|
||||
{tools.map((tool) => (
|
||||
<div
|
||||
key={tool.name}
|
||||
className="rounded-xl border border-hairline bg-canvas-soft p-3"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="text-xs font-medium text-foreground">
|
||||
{tool.label}
|
||||
</div>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium"
|
||||
>
|
||||
可用
|
||||
</Badge>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void copyToolName(tool.name)}
|
||||
className="mt-1 flex w-full items-center justify-between gap-2 rounded-lg px-1 py-1 text-left text-foreground transition-colors hover:bg-surface-strong"
|
||||
aria-label={`复制工具名 ${tool.name}`}
|
||||
title="点击复制工具名"
|
||||
>
|
||||
<code className="min-w-0 break-all font-mono text-[11px]">
|
||||
{tool.name}
|
||||
</code>
|
||||
{copiedToolName === tool.name ? (
|
||||
<Check size={13} className="shrink-0 text-success" />
|
||||
) : (
|
||||
<Copy size={13} className="shrink-0 text-muted-soft" />
|
||||
)}
|
||||
</button>
|
||||
<p className="mt-1 text-[11px] leading-5 text-muted-foreground">
|
||||
{tool.description}
|
||||
</p>
|
||||
<div className="mt-2 border-t border-hairline pt-2">
|
||||
<div className="mb-1.5 text-[10px] font-medium uppercase tracking-[0.08em] text-muted-soft">
|
||||
参数
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{tool.parameters.map((parameter) => (
|
||||
<div key={parameter.name} className="text-[11px] leading-4">
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<code className="font-mono font-medium text-foreground">
|
||||
{parameter.name}
|
||||
</code>
|
||||
<span className="rounded-full bg-surface-strong px-1.5 py-0.5 font-mono text-[9px] text-muted-foreground">
|
||||
{parameter.type}
|
||||
</span>
|
||||
<span
|
||||
className={
|
||||
parameter.required
|
||||
? "text-destructive"
|
||||
: "text-muted-soft"
|
||||
}
|
||||
>
|
||||
{parameter.required ? "必填" : "可选"}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-0.5 text-muted-foreground">
|
||||
{parameter.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function DynamicVariableValuesPopover({
|
||||
entries,
|
||||
values,
|
||||
@@ -629,7 +757,7 @@ function DebugVoicePanel({
|
||||
dynamicVariables: Record<string, string | number | boolean>;
|
||||
dynamicVariablesError: string;
|
||||
}) {
|
||||
const photoCapture = usePhotoCaptureTool(preview);
|
||||
const photoCapture = usePhotoCaptureTool(preview, vision);
|
||||
const {
|
||||
status,
|
||||
error,
|
||||
|
||||
Reference in New Issue
Block a user