"use client" import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react" import { BotMessageSquareIcon, RefreshCwIcon, SearchIcon, WorkflowIcon, } from "lucide-react" import { toast } from "sonner" import { ListPagination } from "@/components/list-pagination" import { OptionCombobox } from "@/components/option-combobox" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Drawer, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer" import { Input } from "@/components/ui/input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { fetchAgentRunLog, fetchAgentRunLogs, fetchAIAgentsAll, type AgentRunLog, type AIAgent, type PageResult, } from "@/lib/api/admin" import { formatDateTime } from "@/lib/utils" const actionOptions = [ { value: "all", label: "全部动作" }, { value: "rag", label: "RAG" }, { value: "skill", label: "Skill" }, { value: "tool", label: "Tool" }, { value: "graph", label: "Graph" }, { value: "handoff", label: "转人工" }, { value: "reply", label: "回复" }, { value: "fallback", label: "兜底" }, ] const finalStatusOptions = [ { value: "all", label: "全部状态" }, { value: "completed", label: "completed" }, { value: "interrupted", label: "interrupted" }, { value: "expired", label: "expired" }, { value: "error", label: "error" }, { value: "fallback", label: "fallback" }, ] const hitlStatusOptions = [ { value: "all", label: "全部 HITL" }, { value: "pending", label: "等待确认" }, { value: "confirmed", label: "已确认" }, { value: "cancelled", label: "已取消" }, { value: "expired", label: "已过期" }, { value: "triggered", label: "已触发" }, ] function actionBadgeVariant(action: string) { switch (action) { case "handoff": return "destructive" as const case "skill": return "default" as const case "tool": return "default" as const case "graph": return "default" as const case "rag": return "secondary" as const case "fallback": return "outline" as const default: return "secondary" as const } } function hitlBadgeVariant(status: string) { switch (status) { case "pending": return "secondary" as const case "confirmed": return "default" as const case "cancelled": return "outline" as const case "expired": return "destructive" as const default: return "secondary" as const } } export default function DashboardAgentRunLogsPage() { const [keywordInput, setKeywordInput] = useState("") const [plannedActionInput, setPlannedActionInput] = useState("all") const [finalActionInput, setFinalActionInput] = useState("all") const [finalStatusInput, setFinalStatusInput] = useState("all") const [hitlStatusInput, setHitlStatusInput] = useState("all") const [aiAgentIdInput, setAiAgentIdInput] = useState("all") const [keyword, setKeyword] = useState("") const [plannedAction, setPlannedAction] = useState("all") const [finalAction, setFinalAction] = useState("all") const [finalStatus, setFinalStatus] = useState("all") const [hitlStatus, setHitlStatus] = useState("all") const [aiAgentId, setAiAgentId] = useState("all") const [page, setPage] = useState(1) const [limit, setLimit] = useState(20) const [loading, setLoading] = useState(true) const [detailLoading, setDetailLoading] = useState(false) const [detailOpen, setDetailOpen] = useState(false) const [activeLog, setActiveLog] = useState(null) const [result, setResult] = useState>({ results: [], page: { page: 1, limit: 20, total: 0 }, }) const [aiAgents, setAiAgents] = useState([]) const activeTraceData = useMemo( () => safeParseJSON(activeLog?.traceData ?? ""), [activeLog?.traceData] ) const activeToolSearchTrace = useMemo( () => safeParseJSON(activeLog?.toolSearchTrace ?? ""), [activeLog?.toolSearchTrace] ) const activeGraphToolTrace = useMemo( () => safeParseJSON(activeLog?.graphToolTrace ?? ""), [activeLog?.graphToolTrace] ) const aiAgentOptions = useMemo( () => [ { value: "all", label: "全部 Agent" }, ...aiAgents.map((item) => ({ value: String(item.id), label: item.name, })), ], [aiAgents] ) const loadData = useCallback(async () => { setLoading(true) try { const data = await fetchAgentRunLogs({ userMessage: keyword.trim() || undefined, plannedAction: plannedAction === "all" ? undefined : plannedAction, finalAction: finalAction === "all" ? undefined : finalAction, finalStatus: finalStatus === "all" ? undefined : finalStatus, hitlStatus: hitlStatus === "all" ? undefined : hitlStatus, aiAgentId: aiAgentId === "all" ? undefined : aiAgentId, page, limit, }) setResult(data) } catch (error) { toast.error(error instanceof Error ? error.message : "加载 Agent 运行日志失败") } finally { setLoading(false) } }, [aiAgentId, finalAction, finalStatus, hitlStatus, keyword, limit, page, plannedAction]) useEffect(() => { void loadData() }, [loadData]) useEffect(() => { async function loadAIAgents() { try { const data = await fetchAIAgentsAll() setAiAgents(data) } catch (error) { toast.error(error instanceof Error ? error.message : "加载 AI Agent 列表失败") } } void loadAIAgents() }, []) function applyFilters() { setKeyword(keywordInput) setPlannedAction(plannedActionInput) setFinalAction(finalActionInput) setFinalStatus(finalStatusInput) setHitlStatus(hitlStatusInput) setAiAgentId(aiAgentIdInput) setPage(1) } function handleFilterKeyDown(event: React.KeyboardEvent) { if (event.key !== "Enter") { return } event.preventDefault() applyFilters() } async function openDetail(id: number) { setDetailLoading(true) setDetailOpen(true) try { const data = await fetchAgentRunLog(id) setActiveLog(data) } catch (error) { toast.error(error instanceof Error ? error.message : "加载日志详情失败") setDetailOpen(false) } finally { setDetailLoading(false) } } return ( <>
setKeywordInput(event.target.value)} onKeyDown={handleFilterKeyDown} placeholder="按用户问题筛选" className="pl-9" />
setPlannedActionInput(value || "all")} />
setFinalActionInput(value || "all")} />
setFinalStatusInput(value || "all")} />
setHitlStatusInput(value || "all")} />
setAiAgentIdInput(value || "all")} />
时间 用户问题 规划动作 Skill / Tool 最终状态 耗时 操作 {!loading && result.results.length === 0 ? ( 暂无 Agent 运行日志 ) : null} {result.results.map((item) => ( {formatDateTime(item.createdAt)}
{item.userMessage || "-"}
{item.errorMessage ? (
{item.errorMessage}
) : null}
{item.plannedAction || "-"} {item.plannedSkillCode || item.plannedToolCode ? (
{item.plannedSkillCode || item.graphToolCode || item.plannedToolCode} {item.plannedSkillName ? (
{item.plannedSkillName}
) : null} {item.handoffReason ? (
转人工原因:{item.handoffReason}
) : null}
) : ( "-" )}
{item.finalAction || "-"} {item.hitlStatusName ? (
{item.hitlStatusName}
) : null}
{item.finalStatus || "-"}
{item.latencyMs} ms
))}
{ setLimit(nextLimit) setPage(1) }} />
{ setDetailOpen(open) if (!open) { setActiveLog(null) } }} > Agent 运行详情 查看 planner 选择、最终动作、回复内容与错误信息。
{detailLoading ? (
加载中...
) : activeLog ? ( <>
} title="用户问题" value={activeLog.userMessage} /> } title="机器人回复" value={activeLog.replyText} /> ) : (
未找到详情数据
)}
) } function safeParseJSON(value: string) { if (!value.trim()) { return null } try { return JSON.parse(value) } catch { return null } } function MetricCard({ label, value }: { label: string; value: string }) { return (
{label}
{value}
) } function InfoBlock({ title, lines }: { title: string; lines: string[] }) { return (
{title}
{lines.map((line) => (
{line}
))}
) } function TextBlock({ title, value, icon, tone = "default", }: { title: string value?: string icon?: ReactNode tone?: "default" | "danger" }) { return (
{icon} {title}
{value?.trim() || "-"}
) }