"use client" import { useEffect, useMemo, useState } from "react" import { SearchIcon } from "lucide-react" import { toast } from "sonner" import { DashboardListPage } from "@/components/dashboard/list" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { AgentRunLogDetailDialog } from "./_components/detail" import { fetchAgentRunLogs, fetchAIAgentsAll, type AIAgent, type AgentRunLog, } from "@/lib/api/admin" import { useI18n } from "@/i18n/provider" import { formatDateTime } from "@/lib/utils" type TFunction = (key: string, values?: Record) => string function getActionOptions(t: TFunction) { return [ { value: "all", label: t("agentRunLog.allActions") }, { value: "rag", label: "RAG" }, { value: "skill", label: "Skill" }, { value: "tool", label: "Tool" }, { value: "graph", label: "Graph" }, { value: "handoff", label: t("agentRunLog.handoff") }, { value: "reply", label: t("agentRunLog.reply") }, { value: "fallback", label: t("agentRunLog.fallback") }, ] } function getFinalStatusOptions(t: TFunction) { return [ { value: "all", label: t("agentRunLog.allStatus") }, { value: "completed", label: "completed" }, { value: "interrupted", label: "interrupted" }, { value: "expired", label: "expired" }, { value: "error", label: "error" }, { value: "fallback", label: "fallback" }, ] } function getHitlStatusOptions(t: TFunction) { return [ { value: "all", label: t("agentRunLog.allHitl") }, { value: "pending", label: t("agentRunLog.hitlPending") }, { value: "confirmed", label: t("agentRunLog.hitlConfirmed") }, { value: "cancelled", label: t("agentRunLog.hitlCancelled") }, { value: "expired", label: t("agentRunLog.hitlExpired") }, { value: "triggered", label: t("agentRunLog.hitlTriggered") }, ] } function getHitlStatusLabel(status: string | undefined, t: TFunction) { switch (status) { case "pending": return t("agentRunLog.hitlPending") case "confirmed": return t("agentRunLog.hitlConfirmed") case "cancelled": return t("agentRunLog.hitlCancelled") case "expired": return t("agentRunLog.hitlExpired") case "triggered": return t("agentRunLog.hitlTriggered") default: return "" } } 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 } } export default function DashboardAgentRunLogsPage() { const t = useI18n() const [detailOpen, setDetailOpen] = useState(false) const [activeLogId, setActiveLogId] = useState(null) const [aiAgents, setAiAgents] = useState([]) const actionOptions = useMemo(() => getActionOptions(t), [t]) const finalStatusOptions = useMemo(() => getFinalStatusOptions(t), [t]) const hitlStatusOptions = useMemo(() => getHitlStatusOptions(t), [t]) const aiAgentOptions = useMemo( () => [ { value: "all", label: t("agentRunLog.allAgents") }, ...aiAgents.map((item) => ({ value: String(item.id), label: item.name, })), ], [aiAgents, t] ) useEffect(() => { async function loadAIAgents() { try { const data = await fetchAIAgentsAll() setAiAgents(data) } catch (error) { toast.error(error instanceof Error ? error.message : t("agentRunLog.loadAgentsFailed")) } } void loadAIAgents() }, [t]) return ( <> filters={[ { name: "userMessage", label: t("agentRunLog.filterUserMessage"), placeholder: t("agentRunLog.filterUserMessage"), defaultValue: "", trim: true, className: "min-w-0", inputClassName: "pl-9", icon: , }, { name: "plannedAction", label: t("agentRunLog.plannedAction"), type: "select", defaultValue: "all", allValue: "all", options: actionOptions, placeholder: t("agentRunLog.plannedAction"), searchPlaceholder: t("agentRunLog.searchAction"), emptyText: t("agentRunLog.emptyAction"), className: "min-w-0", }, { name: "finalAction", label: t("agentRunLog.finalAction"), type: "select", defaultValue: "all", allValue: "all", options: actionOptions, placeholder: t("agentRunLog.finalAction"), searchPlaceholder: t("agentRunLog.searchAction"), emptyText: t("agentRunLog.emptyAction"), className: "min-w-0", }, { name: "finalStatus", label: t("agentRunLog.finalStatus"), type: "select", defaultValue: "all", allValue: "all", options: finalStatusOptions, placeholder: t("agentRunLog.finalStatus"), searchPlaceholder: t("agentRunLog.searchStatus"), emptyText: t("agentRunLog.emptyStatus"), className: "min-w-0", }, { name: "hitlStatus", label: t("agentRunLog.hitlStatus"), type: "select", defaultValue: "all", allValue: "all", options: hitlStatusOptions, placeholder: t("agentRunLog.hitlStatus"), searchPlaceholder: t("agentRunLog.searchHitl"), emptyText: t("agentRunLog.emptyStatus"), className: "min-w-0", }, { name: "aiAgentId", label: t("agentRunLog.selectAgent"), type: "select", defaultValue: "all", allValue: "all", options: aiAgentOptions, placeholder: t("agentRunLog.selectAgent"), searchPlaceholder: t("agentRunLog.searchAgent"), emptyText: t("agentRunLog.emptyAgent"), className: "min-w-0", }, ]} fetchList={fetchAgentRunLogs} renderContent={({ result, loading }) => !loading && result.results.length === 0 ? (
{t("agentRunLog.emptyRows")}
) : (
{t("agentRunLog.time")}
{t("agentRunLog.userMessage")}
{t("agentRunLog.plannedAction")}
{t("agentRunLog.skillTool")}
{t("agentRunLog.finalStatus")}
{t("agentRunLog.duration")}
{t("agentRunLog.actions")}
{result.results.map((item) => (
{formatDateTime(item.createdAt)}
{item.errorMessage ? (
{item.errorMessage}
) : null}
{item.plannedAction || "-"}
{item.plannedSkillId || item.graphToolCode || item.plannedToolCode ? (
{item.plannedSkillName || (item.plannedSkillId ? `Skill #${item.plannedSkillId}` : "") || item.graphToolCode || item.plannedToolCode}
{item.plannedSkillId ? (
Skill #{item.plannedSkillId}
) : item.handoffReason ? (
{t("agentRunLog.handoffReason", { reason: item.handoffReason })}
) : item.recommendedAction ? (
{t("agentRunLog.routingRecommendation", { action: item.recommendedAction })} {item.riskLevel ? ` / ${item.riskLevel} risk` : ""} {item.ticketDraftReady ? ` / ${t("agentRunLog.draftReady")}` : ""}
) : null}
) : ( - )}
{item.finalAction || "-"}
{getHitlStatusLabel(item.hitlStatus, t) ? `${getHitlStatusLabel(item.hitlStatus, t)} / ${item.finalStatus || "-"}` : item.finalStatus || "-"}
{item.latencyMs} ms
))}
) } labels={{ refresh: t("agentRunLog.refresh"), query: t("agentRunLog.query"), loading: t("agentRunLog.loadingRows"), empty: t("agentRunLog.emptyRows"), loadFailed: t("agentRunLog.loadFailed"), }} /> { setDetailOpen(open) if (!open) { setActiveLogId(null) } }} /> ) } function UserMessagePreview({ value, t }: { value?: string; t: TFunction }) { const preview = useMemo(() => summarizeUserMessage(value, t), [value, t]) return (
{preview}
) } function summarizeUserMessage(value: string | undefined, t: TFunction) { 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 t("agentRunLog.imageMessage") } return t("agentRunLog.richMessage") } 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 || "" }