import { useState, useRef, useEffect } from 'react' import type { ChatMessage } from '../types' import { SourceBadge } from './SourceBadge' interface ChatPanelProps { onSend: (message: string, temperature?: number, maxTokens?: number) => Promise loading: boolean } function formatNum(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M` if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K` return n.toString() } export function ChatPanel({ onSend, loading }: ChatPanelProps) { const [messages, setMessages] = useState([]) const [input, setInput] = useState('') const [temperature, setTemperature] = useState(0.0) const [showSettings, setShowSettings] = useState(false) const scrollRef = useRef(null) useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight } }, [messages, loading]) const handleSend = async () => { const msg = input.trim() if (!msg || loading) return const userMsg: ChatMessage = { id: `u-${Date.now()}`, role: 'user', text: msg, timestamp: Date.now(), } setMessages(prev => [...prev, userMsg]) setInput('') try { const resp = await onSend(msg, temperature) const botMsg: ChatMessage = { id: `b-${Date.now()}`, role: 'palimpseste', text: resp.response, confidence: resp.confidence, source: resp.source, explanation: resp.explanation, chain: resp.chain, elapsed_ms: resp.elapsed_ms, timestamp: Date.now(), } setMessages(prev => [...prev, botMsg]) } catch (err) { setMessages(prev => [...prev, { id: `e-${Date.now()}`, role: 'palimpseste', text: '⚠️ Connection error. Is the API server running on :3332?', source: 'fallback', confidence: 0, timestamp: Date.now(), }]) } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSend() } } const clearChat = () => setMessages([]) return (
{/* Header */}

Cortex Chat

{messages.length} message{messages.length !== 1 ? 's' : ''}
{messages.length > 0 && ( )}
{/* Settings drawer */} {showSettings && (
setTemperature(parseFloat(e.target.value))} className="flex-1 accent-pal-accent" />
)} {/* Messages */}
{messages.length === 0 && (
🧠

PALIMPSESTE

A self-referential hypervectorial cortex.
No weights. No gradient. No GPU.

{['hello', 'who are you?', 'what is python?', 'what is the capital of france'].map(q => ( ))}
)} {messages.map(msg => (

{msg.text}

{msg.role === 'palimpseste' && msg.source && (
{msg.elapsed_ms !== undefined && ( {msg.elapsed_ms < 1 ? '<1' : msg.elapsed_ms.toFixed(0)}ms )}
{msg.explanation && msg.source !== 'fallback' && (

💡 {msg.explanation.split('\n').map((line, i) => ( {i > 0 &&
}{line}
))}

)} {msg.chain && msg.chain.success && ( )}
)}
))} {loading && (
)}
{/* Input */}