Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
88 lines
3.7 KiB
TypeScript
88 lines
3.7 KiB
TypeScript
import { Braces } from "lucide-react";
|
|
import { useState } from "react";
|
|
import { useCapabilityTools } from "~/api/mcp.ts";
|
|
import { DetailSection, DetailSheet } from "~/components/console/detail-sheet.tsx";
|
|
import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
|
|
import { JsonView } from "~/components/console/json-view.tsx";
|
|
import { KvList } from "~/components/console/kv-list.tsx";
|
|
import { Button } from "~/components/ui/button.tsx";
|
|
import { formatDateTime } from "~/lib/utils.ts";
|
|
import type { ToolCallView } from "./use-agent-stream.ts";
|
|
|
|
// 会话页工具卡片的"查看 schema 与本次输入输出"抽屉。
|
|
// 本次输入输出取事件流里的摘要:服务端已脱敏(凭证键一律遮盖),超长部分截断;schema 取能力清单(GET /capabilities/tools)。
|
|
|
|
/** 摘要尽量按 JSON 美化;截断后的摘要不是合法 JSON,原样显示 */
|
|
export function parseSummary(text: string | null): { value: unknown; truncated: boolean } {
|
|
if (text === null) return { value: null, truncated: false };
|
|
const truncated = /…\(\d+ more\)$/.test(text);
|
|
if (truncated) return { value: text, truncated };
|
|
try {
|
|
return { value: JSON.parse(text), truncated };
|
|
} catch {
|
|
return { value: text, truncated };
|
|
}
|
|
}
|
|
|
|
export function ToolCallIo({ call }: { call: ToolCallView }) {
|
|
const [open, setOpen] = useState(false);
|
|
return (
|
|
<>
|
|
<Button size="sm" variant="ghost" className="justify-self-start" onClick={() => setOpen(true)}>
|
|
<Braces />
|
|
查看 schema 与输入输出
|
|
</Button>
|
|
{open ? <ToolCallIoSheet call={call} onClose={() => setOpen(false)} /> : null}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function ToolCallIoSheet({ call, onClose }: { call: ToolCallView; onClose: () => void }) {
|
|
const tools = useCapabilityTools();
|
|
const tool = tools.data?.items.find((t) => t.id === call.capabilityId) ?? null;
|
|
const input = parseSummary(call.inputSummary);
|
|
const output = parseSummary(call.outputSummary);
|
|
const failed = call.status === "failed" || call.status === "denied";
|
|
|
|
return (
|
|
<DetailSheet
|
|
open
|
|
onOpenChange={(o) => !o && onClose()}
|
|
title={tool?.title ?? call.capabilityId}
|
|
description={call.capabilityId}
|
|
width="lg"
|
|
>
|
|
<KvList
|
|
items={[
|
|
{ label: "调用 ID", value: call.toolCallId, mono: true, wide: true },
|
|
{ label: "开始", value: formatDateTime(call.startedAt) },
|
|
{ label: "结束", value: call.endedAt ? formatDateTime(call.endedAt) : "—" },
|
|
]}
|
|
/>
|
|
<DetailSection title="本次输入(已脱敏)">
|
|
<JsonView value={input.value} maxHeight="18rem" />
|
|
</DetailSection>
|
|
<DetailSection title={failed ? "本次错误" : "本次输出(已脱敏)"}>
|
|
<JsonView value={output.value ?? call.reason} maxHeight="18rem" tone={failed ? "destructive" : "neutral"} />
|
|
</DetailSection>
|
|
{input.truncated || output.truncated ? (
|
|
<p className="text-caption text-muted-foreground">只保存前 500 个字符,其余已截断。</p>
|
|
) : null}
|
|
<DetailSection title="输入 schema">
|
|
{tools.isPending ? (
|
|
<LoadingState rows={2} />
|
|
) : tools.error ? (
|
|
<ErrorState title="读取能力清单失败" error={tools.error} onRetry={() => tools.refetch()} plain />
|
|
) : tool ? (
|
|
<JsonView value={tool.inputJsonSchema} maxHeight="20rem" />
|
|
) : (
|
|
<p className="text-caption text-muted-foreground">这个能力未注册,没有 schema。</p>
|
|
)}
|
|
</DetailSection>
|
|
{tool ? (
|
|
<JsonView value={tool.outputJsonSchema} maxHeight="20rem" defaultOpen={false} label="输出 schema" />
|
|
) : null}
|
|
</DetailSheet>
|
|
);
|
|
}
|