import { useState, useEffect, useMemo } from 'react'; import type { Session, DbMessage } from '../types'; import { t } from '../i18n'; const ROLE_BADGE: Record = { user: 'badge-blue', assistant: 'badge-gray', tool_use: 'badge-yellow', tool_result: 'badge-green', }; function roleBadgeClass(role: string): string { return ROLE_BADGE[role] ?? 'badge-gray'; } function formatTime(ts: string): string { try { return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); } catch { return ts; } } function formatDate(ts: string): string { try { return new Date(ts).toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' }); } catch { return ts; } } interface MessageBubbleProps { msg: DbMessage; } function MessageBubble({ msg }: MessageBubbleProps) { const isToolUse = msg.role === 'tool_use'; const isToolResult = msg.role === 'tool_result'; const useCodeBlock = isToolUse || isToolResult; const bodyText = isToolUse ? (msg.tool_input ?? msg.content ?? '') : (msg.content ?? ''); return (
{formatTime(msg.created_at)} {msg.role} {isToolUse && msg.tool_name && ( {msg.tool_name} )}
{bodyText && ( useCodeBlock ? (
            {typeof bodyText === 'object' ? JSON.stringify(bodyText, null, 2) : String(bodyText)}
          
) : (

{String(bodyText)}

) )}
); } interface SessionWithCount extends Session { message_count?: number; } export default function HistoryPage() { const [sessions, setSessions] = useState([]); const [selectedId, setSelectedId] = useState(null); const [messages, setMessages] = useState([]); const [search, setSearch] = useState(''); const [loadingSessions, setLoadingSessions] = useState(true); const [loadingMessages, setLoadingMessages] = useState(false); useEffect(() => { fetch('/api/sessions') .then((r) => r.json()) .then((data: SessionWithCount[]) => { if (Array.isArray(data)) { const sorted = [...data].sort( (a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() ); setSessions(sorted); } }) .catch(() => {}) .finally(() => setLoadingSessions(false)); }, []); useEffect(() => { if (!selectedId) return; setLoadingMessages(true); fetch(`/api/history?session_id=${encodeURIComponent(selectedId)}`) .then((r) => r.json()) .then((data: DbMessage[]) => { if (Array.isArray(data)) setMessages(data); else setMessages([]); }) .catch(() => setMessages([])) .finally(() => setLoadingMessages(false)); }, [selectedId]); const filteredMessages = useMemo(() => { if (!search.trim()) return messages; const q = search.toLowerCase(); return messages.filter( (m) => m.content?.toLowerCase().includes(q) || m.tool_name?.toLowerCase().includes(q) || m.tool_input?.toLowerCase().includes(q) ); }, [messages, search]); const selectedSession = sessions.find((s) => s.id === selectedId); return (
{/* Header */}

{t('history.title')}

{t('history.subtitle')}

{/* Session list — left panel */}
{loadingSessions ? (
Loading...
) : sessions.length === 0 ? (
{t('history.noSessions')}
) : (
{sessions.map((session) => ( ))}
)}
{/* Message timeline — right panel */}
{!selectedId ? (
{t('history.selectSession')}
) : ( <> {/* Search bar */}

{selectedSession?.title || 'Untitled'}

setSearch(e.target.value)} className="input-base w-56" />
{/* Messages */}
{loadingMessages ? (
Loading...
) : filteredMessages.length === 0 ? (
{search ? 'No messages match your search.' : 'No messages in this session.'}
) : ( filteredMessages.map((msg) => (
)) )}
)}
); }