"use client" import { useCallback, useEffect, useMemo, useState } from "react" import { RefreshCwIcon, SearchIcon } 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 { Input } from "@/components/ui/input" import { AgentRunLogDetailDialog } from "./_components/detail" import { fetchAgentRunLogs, fetchAIAgentsAll, type AIAgent, type AgentRunLog, 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 [detailOpen, setDetailOpen] = useState(false) const [activeLogId, setActiveLogId] = useState(null) const [result, setResult] = useState>({ results: [], page: { page: 1, limit: 20, total: 0 }, }) const [aiAgents, setAiAgents] = useState([]) 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() } 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")} />
{!loading && result.results.length === 0 ? (
暂无 Agent 运行日志
) : (
时间
用户问题
规划动作
Skill / Tool
最终状态
耗时
操作
{result.results.map((item) => (
{formatDateTime(item.createdAt)}
{item.errorMessage ? (
{item.errorMessage}
) : null}
{item.plannedAction || "-"}
{item.plannedSkillCode || item.graphToolCode || item.plannedToolCode ? (
{item.plannedSkillCode || item.graphToolCode || item.plannedToolCode}
{item.plannedSkillName ? (
{item.plannedSkillName}
) : item.handoffReason ? (
转人工原因:{item.handoffReason}
) : item.recommendedAction ? (
分流建议:{item.recommendedAction} {item.riskLevel ? ` / ${item.riskLevel} risk` : ""} {item.ticketDraftReady ? " / 草稿已就绪" : ""}
) : null}
) : ( - )}
{item.finalAction || "-"}
{item.hitlStatusName ? `${item.hitlStatusName} / ${item.finalStatus || "-"}` : item.finalStatus || "-"}
{item.latencyMs} ms
))}
)}
{ setLimit(nextLimit) setPage(1) }} />
{ setDetailOpen(open) if (!open) { setActiveLogId(null) } }} /> ) } function UserMessagePreview({ value }: { value?: string }) { const preview = useMemo(() => summarizeUserMessage(value), [value]) return (
{preview}
) } function summarizeUserMessage(value?: string) { const normalized = value?.trim() if (!normalized) { return "-" } const text = extractTextFromHTML(normalized).replace(/\s+/g, " ").trim() if (text) { return text } if (containsHTML(normalized)) { if (/]/i.test(normalized)) { return "[图片]" } return "[富文本消息]" } return normalized } function containsHTML(value: string) { return /<[^>]+>/.test(value) } function extractTextFromHTML(value: string) { if (typeof window === "undefined") { return value } const doc = new DOMParser().parseFromString(value, "text/html") return doc.body.textContent || "" }