From 1087ddca4431a00bcf192dcfd01843f6a50e23a8 Mon Sep 17 00:00:00 2001 From: mlogclub Date: Fri, 10 Apr 2026 16:52:45 +0800 Subject: [PATCH] feat: add debug dialog and related functionality for skill debugging --- internal/ai/runtime/debug_run.go | 4 + internal/pkg/dto/response/skill_response.go | 4 + .../_components/debug-dialog.tsx | 414 ++++++++++++++++++ web/app/(console)/skill-definition/page.tsx | 35 +- web/lib/api/admin.ts | 35 ++ 5 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 web/app/(console)/skill-definition/_components/debug-dialog.tsx diff --git a/internal/ai/runtime/debug_run.go b/internal/ai/runtime/debug_run.go index 1987aa1..2699814 100644 --- a/internal/ai/runtime/debug_run.go +++ b/internal/ai/runtime/debug_run.go @@ -76,6 +76,10 @@ func buildSkillDebugRunResponse(req request.SkillDebugRunRequest, summary *Summa resp.SkillAllowedToolCodes = append([]string(nil), summary.SkillAllowedToolCodes...) resp.ToolCodes = append([]string(nil), summary.ToolCodes...) resp.InvokedToolCodes = append([]string(nil), summary.InvokedToolCodes...) + resp.ToolSearchTrace = extractToolSearchTrace(summary) + resp.GraphToolTrace = extractGraphToolTrace(summary) + resp.GraphToolCode = firstGraphToolCode(summary) + resp.InterruptType = firstInterruptType(summary) resp.CheckPointID = summary.CheckPointID resp.Interrupted = summary.Interrupted resp.TraceData = summary.TraceData diff --git a/internal/pkg/dto/response/skill_response.go b/internal/pkg/dto/response/skill_response.go index 52d93b4..2bac734 100644 --- a/internal/pkg/dto/response/skill_response.go +++ b/internal/pkg/dto/response/skill_response.go @@ -29,6 +29,10 @@ type SkillDebugRunResponse struct { SkillAllowedToolCodes []string `json:"skillAllowedToolCodes"` ToolCodes []string `json:"toolCodes"` InvokedToolCodes []string `json:"invokedToolCodes"` + ToolSearchTrace string `json:"toolSearchTrace"` + GraphToolTrace string `json:"graphToolTrace"` + GraphToolCode string `json:"graphToolCode"` + InterruptType string `json:"interruptType"` CheckPointID string `json:"checkPointId"` Interrupted bool `json:"interrupted"` TraceData string `json:"traceData"` diff --git a/web/app/(console)/skill-definition/_components/debug-dialog.tsx b/web/app/(console)/skill-definition/_components/debug-dialog.tsx new file mode 100644 index 0000000..0325cf0 --- /dev/null +++ b/web/app/(console)/skill-definition/_components/debug-dialog.tsx @@ -0,0 +1,414 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { useEffect, useMemo, useState } from "react" +import { Resolver, useForm } from "react-hook-form" +import { z } from "zod/v4" +import { LoaderCircleIcon, PlayIcon } from "lucide-react" +import { toast } from "sonner" + +import { ProjectDialog } from "@/components/project-dialog" +import { OptionCombobox } from "@/components/option-combobox" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { + Field, + FieldContent, + FieldError, + FieldLabel, +} from "@/components/ui/field" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { + debugRunSkillDefinition, + fetchAIAgentsAll, + type AIAgent, + type SkillDebugRunPayload, + type SkillDebugRunResult, +} from "@/lib/api/admin" + +type DebugDialogProps = { + open: boolean + skillCode: string + skillName: string + onOpenChange: (open: boolean) => void +} + +const debugFormSchema = z.object({ + aiAgentId: z.string().trim().min(1, "请选择 AI Agent"), + conversationId: z.string().trim(), + userMessage: z.string().trim().min(1, "请输入用户消息"), +}) + +type DebugForm = z.infer + +const debugFormResolver = zodResolver(debugFormSchema as never) as Resolver< + z.input, + undefined, + z.output +> + +const emptyForm: DebugForm = { + aiAgentId: "", + conversationId: "", + userMessage: "", +} + +function ResultBlock({ + title, + value, + emptyText = "暂无数据", +}: { + title: string + value?: string + emptyText?: string +}) { + return ( + + + {title} + + + {value ? ( +
+            {value}
+          
+ ) : ( +
{emptyText}
+ )} +
+
+ ) +} + +export function DebugDialog({ + open, + skillCode, + skillName, + onOpenChange, +}: DebugDialogProps) { + if (!open) { + return null + } + + return ( + + ) +} + +function DebugDialogBody({ + open, + skillCode, + skillName, + onOpenChange, +}: DebugDialogProps) { + const formId = `skill-debug-form-${skillCode}` + const [running, setRunning] = useState(false) + const [aiAgents, setAiAgents] = useState([]) + const [result, setResult] = useState(null) + const form = useForm< + z.input, + undefined, + z.output + >({ + resolver: debugFormResolver, + defaultValues: emptyForm, + }) + + const { + handleSubmit, + reset, + register, + setValue, + watch, + formState: { errors }, + } = form + + const selectedAgentId = watch("aiAgentId") + + useEffect(() => { + async function loadAIAgents() { + try { + const data = await fetchAIAgentsAll({ status: 1 }) + setAiAgents(data) + } catch (error) { + console.error("Failed to load AI agents:", error) + } + } + + void loadAIAgents() + }, []) + + useEffect(() => { + if (!open) { + return + } + reset(emptyForm) + setResult(null) + }, [open, reset]) + + useEffect(() => { + if (!open || aiAgents.length === 0 || selectedAgentId) { + return + } + setValue("aiAgentId", String(aiAgents[0].id), { shouldValidate: true }) + }, [aiAgents, open, selectedAgentId, setValue]) + + const aiAgentOptions = useMemo( + () => + aiAgents.map((item) => ({ + value: String(item.id), + label: item.name, + })), + [aiAgents], + ) + + const selectedAgent = useMemo( + () => aiAgents.find((item) => String(item.id) === selectedAgentId) ?? null, + [aiAgents, selectedAgentId], + ) + + async function onSubmit(values: DebugForm) { + const payload: SkillDebugRunPayload = { + aiAgentId: Number(values.aiAgentId), + skillCode, + userMessage: values.userMessage.trim(), + } + const conversationId = Number(values.conversationId) + if (conversationId > 0) { + payload.conversationId = conversationId + } + + setRunning(true) + try { + const data = await debugRunSkillDefinition(payload) + setResult(data) + } catch (error) { + toast.error(error instanceof Error ? error.message : "Skill 调试失败") + setResult(null) + } finally { + setRunning(false) + } + } + + return ( + + + + + } + > +
+ + + 调试输入 + + +
+
+ + AI Agent + + + setValue("aiAgentId", value, { shouldValidate: true }) + } + /> + + + + + + Conversation ID + + + + + + +
+
+ + Skill + + + + + + 命中 Agent + + + + +
+ + 用户消息 + +