"use client" import { useEffect, useState } from "react" import { AlertTriangleIcon, BotMessageSquareIcon, Clock3Icon, WorkflowIcon, WrenchIcon } from "lucide-react" import { toast } from "sonner" import { DashboardListPage } from "@/components/dashboard/list" import { JsonTreeViewer } from "@/components/json-tree-viewer" import { OptionCombobox } from "@/components/option-combobox" import { ProjectDialog } from "@/components/project-dialog" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" import { fetchAgentRun, fetchAgentRunMetrics, fetchAgentRuns, fetchAIWorkflowRun, saveAgentRunQualityFeedback, type AgentRun, type AgentRunMetrics, type AgentStep, type AgentToolCall, type AIWorkflowRun } from "@/lib/api/admin" import { formatDateTime } from "@/lib/utils" import { useI18n } from "@/i18n/provider" import { WorkflowRunAuditGraph } from "../ai-workflow-runs/_components/workflow-run-audit-graph" function statusVariant(status: string) { if (status === "failed") return "destructive" as const if (status === "interrupted") return "outline" as const if (status === "completed") return "default" as const return "secondary" as const } export default function DashboardAgentRunsPage() { const t = useI18n() const [open, setOpen] = useState(false) const [loading, setLoading] = useState(false) const [run, setRun] = useState(null) const [workflowAuditOpen, setWorkflowAuditOpen] = useState(false) const [workflowAuditLoading, setWorkflowAuditLoading] = useState(false) const [workflowRun, setWorkflowRun] = useState(null) const [metrics, setMetrics] = useState(null) useEffect(() => { void fetchAgentRunMetrics().then(setMetrics).catch(() => setMetrics(null)) }, []) async function openDetail(id: number) { setOpen(true) setLoading(true) try { setRun(await fetchAgentRun(id)) } catch (error) { toast.error(error instanceof Error ? error.message : t("agentRun.loadDetailFailed")) setOpen(false) } finally { setLoading(false) } } async function openWorkflowAudit(id: number) { if (id <= 0) return setWorkflowAuditOpen(true) setWorkflowAuditLoading(true) try { setWorkflowRun(await fetchAIWorkflowRun(id)) } catch (error) { toast.error(error instanceof Error ? error.message : "加载 Workflow 节点审计失败") setWorkflowAuditOpen(false) } finally { setWorkflowAuditLoading(false) } } return ( <> {metrics ?
: null} filters={[ { name: "conversationId", label: t("agentRun.conversation"), defaultValue: "", valueType: "number", className: "w-full sm:w-40" }, { name: "aiAgentId", label: t("agentRun.agent"), defaultValue: "", valueType: "number", className: "w-full sm:w-40" }, { name: "status", label: t("agentRun.status"), defaultValue: "", className: "w-full sm:w-40" }, ]} fetchList={fetchAgentRuns} getItemId={(item) => item.id} getRowClassName={() => "cursor-pointer"} onRowClick={(item) => void openDetail(item.id)} columns={[ { key: "startedAt", label: t("agentRun.startedAt"), className: "w-42 text-xs text-muted-foreground", render: (item) => formatDateTime(item.startedAt || item.createdAt) }, { key: "agent", label: t("agentRun.agent"), className: "w-28", render: (item) => `#${item.aiAgentId || "-"}` }, { key: "conversation", label: t("agentRun.conversation"), className: "w-28", render: (item) => `#${item.conversationId || "-"}` }, { key: "status", label: t("agentRun.status"), className: "w-30", render: (item) => {item.status || "-"} }, { key: "duration", label: t("agentRun.duration"), className: "w-24 text-right", render: (item) => `${item.durationMs || 0} ms` }, { key: "tokens", label: t("agentRun.tokens"), className: "w-28 text-right", render: (item) => `${item.promptTokens || 0}/${item.completionTokens || 0}` }, { key: "error", label: t("agentRun.error"), className: "w-72 max-w-72", render: (item) => item.errorMessage ? {item.errorMessage} : "-" }, ]} labels={{ refresh: t("agentRun.refresh"), query: t("agentRun.query"), loading: t("agentRun.loading"), empty: t("agentRun.empty"), loadFailed: t("agentRun.loadFailed") }} /> void openDetail(id)} onOpenChange={(next) => { setOpen(next); if (!next) setRun(null) }} t={t} /> { setWorkflowAuditOpen(next); if (!next) setWorkflowRun(null) }} /> ) } function Metric({ label, value, detail }: { label: string; value: string; detail: string }) { return
{label}
{value}
{detail}
} function AgentRunDetailDialog({ open, loading, run, onOpenChange, onOpenWorkflowAudit, onQualityFeedbackSaved, t }: { open: boolean; loading: boolean; run: AgentRun | null; onOpenChange: (open: boolean) => void; onOpenWorkflowAudit: (workflowRunId: number) => void; onQualityFeedbackSaved: (agentRunId: number) => void; t: (key: string) => string }) { return {t("agentRun.detailTitle")}} description={run ? `Run #${run.id}` : t("agentRun.detailDescription")} footer={}> {loading ?
{t("agentRun.loadingDetail")}
: run ?
{run.workflowRunId > 0 ?
关联 Workflow 审计
Workflow Run #{run.workflowRunId} 的节点输入、输出和状态
: null} {run.errorMessage ?
{run.errorMessage}
: null}

{t("agentRun.steps")}

{(run.steps ?? []).map((step) => )}{!run.steps?.length ?

{t("agentRun.emptySteps")}

: null}

{t("agentRun.toolCalls")}

{(run.toolCalls ?? []).map((call) => )}{!run.toolCalls?.length ?

{t("agentRun.emptyToolCalls")}

: null}
:
{t("agentRun.notFound")}
}
} function QualityFeedbackPanel({ run, onSaved }: { run: AgentRun; onSaved: (agentRunId: number) => void }) { const [resolutionStatus, setResolutionStatus] = useState<"unknown" | "resolved" | "unresolved">("unknown") const [evidenceStatus, setEvidenceStatus] = useState<"unknown" | "supported" | "unsupported">("unknown") const [comment, setComment] = useState("") const [saving, setSaving] = useState(false) useEffect(() => { setResolutionStatus(run.qualityFeedback?.resolutionStatus ?? "unknown") setEvidenceStatus(run.qualityFeedback?.evidenceStatus ?? "unknown") setComment(run.qualityFeedback?.comment ?? "") }, [run.id, run.qualityFeedback]) async function save() { setSaving(true) try { await saveAgentRunQualityFeedback({ agentRunId: run.id, resolutionStatus, evidenceStatus, comment }) toast.success("质检结果已保存") onSaved(run.id) } catch (error) { toast.error(error instanceof Error ? error.message : "保存质检结果失败") } finally { setSaving(false) } } return

运行质检

解决率和无依据率仅统计已质检记录。

setResolutionStatus(value === "resolved" || value === "unresolved" ? value : "unknown")} /> setEvidenceStatus(value === "supported" || value === "unsupported" ? value : "unknown")} />