"use client" import { useCallback, useEffect, useMemo, useState } from "react" import { RefreshCwIcon, SearchIcon } from "lucide-react" import { toast } from "sonner" import { DashboardPage, DashboardTableShell, DashboardToolbar, } from "@/components/dashboard-page" 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 { 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 [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 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] ) 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 : t("agentRunLog.loadFailed")) } finally { setLoading(false) } }, [aiAgentId, finalAction, finalStatus, hitlStatus, keyword, limit, page, plannedAction, t]) useEffect(() => { void loadData() }, [loadData]) 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]) 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 ( <> void loadData()} disabled={loading} className="w-full xl:w-auto" > {t("agentRunLog.refresh")} } >
setKeywordInput(event.target.value)} onKeyDown={handleFilterKeyDown} placeholder={t("agentRunLog.filterUserMessage")} className="pl-9" />
setPlannedActionInput(value || "all")} />
setFinalActionInput(value || "all")} />
setFinalStatusInput(value || "all")} />
setHitlStatusInput(value || "all")} />
setAiAgentIdInput(value || "all")} />
{ setLimit(nextLimit) setPage(1) }} /> } > {!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.plannedSkillCode || item.graphToolCode || item.plannedToolCode ? (
{item.plannedSkillCode || item.graphToolCode || item.plannedToolCode}
{item.plannedSkillName ? (
{item.plannedSkillName}
) : 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
))}
)}
{ 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 || "" }