'use client'; import React, { useEffect, useMemo, useState } from 'react'; import { AlertCircle, Bot, Braces, ChevronDown, Loader2, MessageSquare, RefreshCw, TerminalSquare } from 'lucide-react'; import { getChatLogs } from '@/lib/api'; import { pickAppText } from '@/lib/i18n/core'; import { useAppI18n } from '@/lib/i18n/provider'; import type { ChatLogEvent, ChatLogSession } from '@/types'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { containedJsonTextClass } from '@/lib/text-wrapping'; function eventLabel(event: ChatLogEvent): string { return event.event_type || event.role || 'event'; } function eventIcon(event: ChatLogEvent) { if (event.event_type === 'llm_request_snapshotted') return Braces; if (event.role === 'assistant') return Bot; if (event.role === 'tool') return TerminalSquare; return MessageSquare; } function formatPayload(value: unknown): string { if (value === null || value === undefined || value === '') return ''; if (typeof value === 'string') return value; try { return JSON.stringify(value, null, 2); } catch { return String(value); } } function eventBody(event: ChatLogEvent): string { const content = event.content?.trim(); if (content) return content; return formatPayload(event.event_payload); } function timestampLabel(value?: string | null): string { if (!value) return ''; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleString(); } export default function LogsPage() { const { locale } = useAppI18n(); const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [expandedRuns, setExpandedRuns] = useState>(() => new Set()); const runs = useMemo( () => sessions.flatMap((session) => session.runs.map((run) => ({ ...run, sessionTitle: session.title || session.session_id, })) ), [sessions] ); const loadLogs = async () => { setLoading(true); setError(null); try { const data = await getChatLogs(80); setSessions(data.sessions || []); const firstRun = data.sessions?.[0]?.runs?.[0]?.run_id; if (firstRun) { setExpandedRuns((current) => (current.size ? current : new Set([firstRun]))); } } catch (err: any) { setError(err.message || pickAppText(locale, '读取日志失败', 'Failed to load logs')); } finally { setLoading(false); } }; useEffect(() => { loadLogs(); }, []); const toggleRun = (runId: string) => { setExpandedRuns((current) => { const next = new Set(current); if (next.has(runId)) next.delete(runId); else next.add(runId); return next; }); }; return (

{pickAppText(locale, '运行日志', 'Runtime Logs')}

{pickAppText( locale, '按每一次用户输入分组,查看 LLM 请求、回复、工具结果和隐藏运行快照。', 'Grouped by each user input, with LLM requests, responses, tool results, and hidden runtime snapshots.' )}

{error ? (

{pickAppText(locale, '无法读取日志', 'Unable to load logs')}

{error}

) : null} {loading && !runs.length ? (
) : null} {!loading && !runs.length && !error ? ( {pickAppText(locale, '还没有可展示的运行日志。', 'No runtime logs are available yet.')} ) : null}
{runs.map((run) => { const expanded = expandedRuns.has(run.run_id); return ( {expanded ? ( {run.events.map((event, index) => { const Icon = eventIcon(event); const body = eventBody(event); return (
{eventLabel(event)} {event.context_visible ? 'visible' : 'hidden'} {event.tool_name ? {event.tool_name} : null}
{timestampLabel(event.timestamp)}
                          {body || formatPayload(event)}
                        
); })}
) : null}
); })}
); }