"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { fetchSystemLogs, type QuerySystemLogsResult } from "@/features/agent/services/systemLogApi"; import { toast } from "@/hooks/useToast"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Copy } from "lucide-react"; function tryFormatJSON(raw?: string | null): string { if (!raw) return ""; try { const parsed = JSON.parse(raw); return JSON.stringify(parsed, null, 2); } catch { return raw; } } function levelColor(level: string): string { if (level === "error") return "text-red-600"; if (level === "warn") return "text-amber-600"; return "text-emerald-600"; } export default function LogsPage({ embedded = false }: { embedded?: boolean }) { const [from, setFrom] = useState(() => { const d = new Date(); d.setDate(d.getDate() - 6); return d.toISOString().slice(0, 10); }); const [to, setTo] = useState(() => new Date().toISOString().slice(0, 10)); const [level, setLevel] = useState(""); const [category, setCategory] = useState(""); const [source, setSource] = useState(""); const [event, setEvent] = useState(""); const [keyword, setKeyword] = useState(""); const [conversationId, setConversationId] = useState(""); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [page, setPage] = useState(1); const pageSize = 50; const [selected, setSelected] = useState<(QuerySystemLogsResult["items"][number]) | null>(null); const selectedMeta = useMemo(() => tryFormatJSON(selected?.meta_json), [selected]); const load = useCallback(async () => { setLoading(true); try { const conv = conversationId.trim() ? Number(conversationId) : undefined; const res = await fetchSystemLogs({ from, to, level: level || undefined, category: category || undefined, source: source || undefined, event: event || undefined, keyword: keyword || undefined, conversationId: conv, page, pageSize, }); setData(res); } catch (e) { toast.error((e as Error).message || "加载日志失败"); setData(null); } finally { setLoading(false); } }, [from, to, level, category, source, event, keyword, conversationId, page]); useEffect(() => { void load(); }, [load]); const totalPages = useMemo(() => { if (!data) return 1; return Math.max(1, Math.ceil(data.total / data.page_size)); }, [data]); return (

日志中心

按分类查看 AI / RAG / 系统 / 前端日志,用于排障定位。

setFrom(e.target.value)} className="rounded-md border px-2 py-1 text-sm" /> setTo(e.target.value)} className="rounded-md border px-2 py-1 text-sm" /> setEvent(e.target.value)} className="rounded-md border px-2 py-1 text-sm min-w-[180px]" /> setConversationId(e.target.value)} className="rounded-md border px-2 py-1 text-sm w-24" /> setKeyword(e.target.value)} className="rounded-md border px-2 py-1 text-sm min-w-[220px]" />
共 {data?.total ?? 0} 条,当前第 {data?.page ?? page}/{totalPages} 页
{(data?.items ?? []).map((item) => ( setSelected(item)} > ))} {(data?.items ?? []).length === 0 && !loading && ( )}
时间 级别 分类 事件 会话 来源 消息
{new Date(item.timestamp).toLocaleString()} {item.level} {item.category} {item.event} {item.conversation_id ?? "-"} {item.source} {item.message}
暂无日志
{ if (!open) setSelected(null); }} > 日志详情 {selected ? ( {selected.level} ) : null} {selected ? (
时间
{new Date(selected.timestamp).toLocaleString()}
source / event
{selected.source} / {selected.event}
category
{selected.category}
trace_id
{selected.trace_id || "-"}
conversation_id
{selected.conversation_id ?? "-"}
user_id / visitor_id
{selected.user_id ?? "-"} / {selected.visitor_id ?? "-"}
message
{selected.message}
meta_json
                  {selectedMeta || "(无 meta_json)"}
                
) : null}
); }