import { useEffect, useRef, useState } from 'react'; import type { AskResponse, ChatHistoryItem } from '../types'; import { askQuestion, clearChatHistory, getChatHistory, getChatLoggerStatus } from '../api/client'; interface Message { role: 'user' | 'assistant'; text: string; data?: AskResponse; } interface Props { onResult: (result: AskResponse) => void; } const SESSION_STORAGE_KEY = 'dodge_chat_session_id'; function createSessionId(): string { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID(); } return `sess-${Date.now()}-${Math.random().toString(36).slice(2)}`; } function getOrCreateSessionId(): string { const existing = localStorage.getItem(SESSION_STORAGE_KEY); if (existing) return existing; const created = createSessionId(); localStorage.setItem(SESSION_STORAGE_KEY, created); return created; } function buildAskResponseFromHistory(item: ChatHistoryItem): AskResponse { return { answer: item.answer, explanation: item.explanation, query_used: item.query_used, nodes: [], edges: [], data: [], metadata: item.metadata || {}, }; } function DataTable({ rows }: { rows: Record[] }) { if (rows.length === 0) return null; // Get column names from first row, filter out large objects const columns = Object.keys(rows[0]).filter((key) => { const val = rows[0][key]; return typeof val !== 'object' || val === null; }); if (columns.length === 0) return null; return (
{columns.map((col) => ( ))} {rows.map((row, i) => ( {columns.map((col) => ( ))} ))}
{col}
{String(row[col] ?? '')}
); } export default function ChatPanel({ onResult }: Props) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [sessionId, setSessionId] = useState(''); const [chatStoreStatus, setChatStoreStatus] = useState('Checking chat storage...'); const onResultRef = useRef(onResult); useEffect(() => { onResultRef.current = onResult; }, [onResult]); useEffect(() => { const currentSessionId = getOrCreateSessionId(); setSessionId(currentSessionId); getChatLoggerStatus() .then((status) => { if (!status.enabled) { setChatStoreStatus('Chat history storage disabled'); return; } if (!status.connected) { setChatStoreStatus('Chat history storage unavailable'); return; } setChatStoreStatus('Chat history storage connected'); return getChatHistory(currentSessionId, 200).then((history) => { const restored: Message[] = []; history.items.forEach((item) => { if (item.question) { restored.push({ role: 'user', text: item.question }); } const assistantText = item.answer || (item.error ? `Error: ${item.error}` : 'No response'); const data = item.status === 'success' ? buildAskResponseFromHistory(item) : undefined; restored.push({ role: 'assistant', text: assistantText, data }); }); setMessages(restored); const lastSuccess = [...history.items].reverse().find((item) => item.status === 'success' && !!item.query_used); if (lastSuccess) { onResultRef.current(buildAskResponseFromHistory(lastSuccess)); } }); }) .catch(() => { setChatStoreStatus('Chat history storage unavailable'); }); }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const question = input.trim(); if (!question || loading) return; setInput(''); setMessages((prev) => [...prev, { role: 'user', text: question }]); setLoading(true); try { const result = await askQuestion(question, undefined, sessionId || getOrCreateSessionId()); setMessages((prev) => [ ...prev, { role: 'assistant', text: result.answer, data: result, }, ]); onResult(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Query failed'; setMessages((prev) => [ ...prev, { role: 'assistant', text: `Error: ${message}` }, ]); } finally { setLoading(false); } }; const handleQuit = async () => { const currentSessionId = sessionId || localStorage.getItem(SESSION_STORAGE_KEY) || ''; if (currentSessionId) { try { await clearChatHistory(currentSessionId); } catch { // Keep local reset behavior even if remote clear fails. } } const newSessionId = createSessionId(); localStorage.setItem(SESSION_STORAGE_KEY, newSessionId); setSessionId(newSessionId); setMessages([]); }; return (

Query Chat

{chatStoreStatus}

{/* Messages */}
{messages.length === 0 && (

Ask a question about your graph...

)} {messages.map((msg, i) => (

{msg.text}

{msg.data && (

{msg.data.explanation}

{msg.data.query_used}

{msg.data.data.length > 0 ? `${msg.data.data.length} results` : `${msg.data.nodes.length} nodes, ${msg.data.edges.length} edges`} {msg.data.metadata.execution_time_ms != null && ` | ${String(msg.data.metadata.execution_time_ms)}ms`}

{msg.data.data.length > 0 && ( )}
)}
))} {loading && (
Thinking...
)}
{/* Input */}
setInput(e.target.value)} placeholder="e.g. How many sales orders are there?" className="flex-1 bg-slate-900 text-slate-200 text-sm rounded-lg px-3 py-2 border border-slate-600 focus:outline-none focus:border-indigo-500 placeholder-slate-500" disabled={loading} />
); }