import { useState, useRef, useEffect } from 'react'; import './ChatArea.css'; const SendIcon = () => ( ); const UploadIcon = () => ( ); const PinIcon = () => ( ); const ChevronIcon = ({ open }) => ( ); const BotIcon = () => ( ); function VerdictBadge({ verdict }) { const map = { supported: ['badge-success', '✓ Supported'], partially_supported: ['badge-warning', '⚬ Partial'], refused: ['badge-error', '✕ Refused'], loading: ['badge-muted', '…'], }; const [cls, label] = map[verdict] || ['badge-muted', verdict]; return {label}; } function ConfidenceBar({ value }) { const pct = Math.round((value || 0) * 100); return (
Confidence
75 ? 'linear-gradient(90deg,#10b981,#34d399)' : pct > 45 ? 'linear-gradient(90deg,#f59e0b,#fbbf24)' : 'linear-gradient(90deg,#ef4444,#f87171)' }} />
{pct}%
); } function CitationList({ citations }) { if (!citations?.length) return null; return (
{citations.map((c, i) => ( [{c.evidence_id}] p.{c.pages} ))}
); } function MessageBubble({ turn, onTurnClick, onPin }) { const [showEvidence, setShowEvidence] = useState(false); const isLoading = turn.verdict === 'loading'; const handlePin = (e) => { e.stopPropagation(); const text = window.getSelection()?.toString() || turn.answer?.slice(0, 120) + '…'; onPin(text, turn.question); }; return (
onTurnClick(turn)}> {/* User */}
{turn.question}
{turn.rewritten_question && (
↻ Context-expanded: "{turn.rewritten_question.slice(0, 60)}…"
)}
{/* AI */}
{isLoading ? (
Analysing evidence…
) : ( <>
{turn.verdict !== 'refused' && turn.confidence != null && ( )} {turn.timestamp}
{(turn.answer || '').split('\n').map((line, i) => (

{highlightCitations(line)}

))}
{turn.citations?.length > 0 && ( <> {showEvidence && (
{turn.citations.map((c, i) => (
[{c.evidence_id}] {c.text_preview} p.{c.pages}
))}
)} )} {turn.citation_coverage != null && turn.verdict !== 'refused' && (
Citation coverage
{Math.round(turn.citation_coverage * 100)}%
)} )}
); } function highlightCitations(text) { const parts = text.split(/(\[E\d+\])/g); return parts.map((p, i) => /\[E\d+\]/.test(p) ? {p} : p ); } function EmptyState({ onUpload, uploadLoading }) { const fileRef = useRef(null); return (

Upload a research paper to begin

Upload any academic PDF and ask questions. Answers are grounded in evidence with inline citations and hallucination detection.

{ const f = e.target.files?.[0]; if (f) onUpload(f); e.target.value = ''; }} />

Supports multi-document indexing • Conversation memory • Citation tracking

); } export default function ChatArea({ sessionName, turns, indexStatus, activeDocs, isLoading, error, onAsk, onTurnClick, onPin, onClearSession, onUpload, uploadLoading, }) { const [question, setQuestion] = useState(''); const [useMemory, setUseMemory] = useState(true); const [enableNli, setEnableNli] = useState(false); const fileRef = useRef(null); const bottomRef = useRef(null); const inputRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [turns.length, isLoading]); const submit = () => { const q = question.trim(); if (!q || isLoading) return; setQuestion(''); onAsk(q, { useMemory, enableNli }); }; const handleKey = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); } }; const hasIndex = indexStatus.chunk_count > 0; return (
{/* Top Bar */}

{sessionName}

{activeDocs.length > 0 ? activeDocs.map((d, i) => ( {d.filename.replace('.pdf', '')} )) : No papers loaded} {hasIndex && {indexStatus.chunk_count} chunks}
{turns.length > 0 && ( )}
{/* Error Banner */} {error && (
⚠ {error}
)} {/* Messages */}
{!hasIndex && turns.length === 0 ? ( ) : ( <> {turns.map((turn) => ( ))} {isLoading && turns[turns.length - 1]?.verdict !== 'loading' && (
)}
)}
{/* Input Bar */}
{ const f = e.target.files?.[0]; if (f) onUpload(f); e.target.value = ''; }} />