"use client"; import { MonitorSmartphone, Plus, Server, Trash2, Wrench } from "lucide-react"; import { useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { createToolSimulation, getToolSimulationValidationMessage, type FixedInputTurn, type ToolSimulation, type ToolSimulationResponse, } from "@/data/test-suites"; import { cn } from "@/lib/utils"; export type EditorToolOption = { id: string; functionName: string; label: string; type: "http" | "mcp" | "client"; parameters: { name: string; description: string }[]; }; const TOOL_TYPE_LABEL: Record = { http: "服务端 · HTTP", mcp: "服务端 · MCP", client: "客户端交互", }; export function ToolSimulationEditor({ value, tools, turns, onChange, }: { value: ToolSimulation[]; tools: EditorToolOption[]; turns: FixedInputTurn[]; onChange: (value: ToolSimulation[]) => void; }) { const [addOpen, setAddOpen] = useState(false); const configuredIds = useMemo( () => new Set(value.map((simulation) => simulation.toolId)), [value], ); const availableTools = tools.filter((tool) => !configuredIds.has(tool.id)); const turnIds = useMemo(() => new Set(turns.map((turn) => turn.id)), [turns]); function addTool(tool: EditorToolOption) { onChange([ ...value, createToolSimulation({ toolId: tool.id, functionName: tool.functionName, toolType: tool.type, }), ]); setAddOpen(false); } function updateSimulation(index: number, simulation: ToolSimulation) { onChange( value.map((item, itemIndex) => itemIndex === index ? simulation : item, ), ); } return (
{value.length === 0 ? (

暂未配置工具模拟

运行中调用未配置的外部工具将立即停止当前用例。

) : (
{value.map((simulation, index) => { const tool = tools.find((candidate) => candidate.id === simulation.toolId) ?? tools.find( (candidate) => candidate.functionName === simulation.functionName, ) ?? null; return ( updateSimulation(index, next)} onRemove={() => onChange(value.filter((_, itemIndex) => itemIndex !== index)) } /> ); })}
)}
选择工具
{availableTools.length === 0 ? (

所有可用工具均已配置

) : ( availableTools.map((tool) => ( )) )}
); } function ToolSimulationCard({ simulation, tool, validationMessage, onChange, onRemove, }: { simulation: ToolSimulation; tool: EditorToolOption | null; validationMessage: string | null; onChange: (simulation: ToolSimulation) => void; onRemove: () => void; }) { const toolType = tool?.type; const response = simulation.mode === "fixed" ? simulation.response : simulation.steps[0] ? responseFromLegacyStep(simulation.steps[0]) : defaultResponse(toolType, simulation.functionName); function updateFixedResponse(nextResponse: ToolSimulationResponse) { onChange({ toolId: simulation.toolId, functionName: simulation.functionName, mode: "fixed", response: nextResponse, }); } return (
{toolType ? : }
{tool?.label || simulation.functionName} {toolType ? TOOL_TYPE_LABEL[toolType] : "工具已不可用"}
{simulation.functionName}
{simulation.mode === "sequence" ? (

这是旧版顺序配置。转换后将复用第一条响应作为固定返回。

) : null}

每次调用都返回此结果;调用次数仍由预期行为单独判断。

{validationMessage ? (

{validationMessage}

) : null}
); } function SimulationResponseFields({ value, toolType, ariaPrefix, onChange, }: { value: ToolSimulationResponse; toolType: EditorToolOption["type"] | undefined; ariaPrefix: string; onChange: (value: ToolSimulationResponse) => void; }) { return (
{ const next = Number(event.target.value); onChange({ ...value, delayMs: Number.isFinite(next) ? Math.min(60000, Math.max(0, Math.round(next))) : 0, }); }} aria-label={`${ariaPrefix}延迟`} className="h-8 border-hairline-strong bg-background text-xs" /> ms