import { useState, useRef, useEffect } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { sendChatMessage } from '../services/api'; export default function ChatWindow({ hasDocuments }) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const messagesEndRef = useRef(null); const inputRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(scrollToBottom, [messages]); const handleSend = async () => { const question = input.trim(); if (!question || isStreaming) return; const userMessage = { role: 'user', content: question }; setMessages((prev) => [...prev, userMessage]); setInput(''); setIsStreaming(true); // Add empty assistant message that we'll fill via streaming const assistantMessage = { role: 'assistant', content: '', sources: [] }; setMessages((prev) => [...prev, assistantMessage]); const chatHistory = messages.map((m) => ({ role: m.role, content: m.content, })); await sendChatMessage( question, chatHistory, // onChunk (chunk) => { setMessages((prev) => { const updated = [...prev]; const last = { ...updated[updated.length - 1] }; last.content += chunk; updated[updated.length - 1] = last; return updated; }); }, // onSources (sources) => { setMessages((prev) => { const updated = [...prev]; const last = { ...updated[updated.length - 1] }; last.sources = sources; updated[updated.length - 1] = last; return updated; }); }, // onDone () => setIsStreaming(false), // onError (error) => { setMessages((prev) => { const updated = [...prev]; const last = updated[updated.length - 1]; last.content = `❌ Error: ${error}`; last.isError = true; return [...updated]; }); setIsStreaming(false); } ); }; const handleKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }; return (
{messages.length === 0 ? (

RAG Assistant

Upload a PDF document and ask questions about its content.

Your answers will be grounded in the uploaded documents.

) : ( messages.map((msg, i) => (
{msg.role === 'user' ? ( ) : ( )}
{msg.role === 'user' ? 'You' : 'Assistant'}
{msg.content} {isStreaming && i === messages.length - 1 && msg.role === 'assistant' && ( )}
{msg.sources && msg.sources.length > 0 && (
Sources: {msg.sources.map((src, j) => ( 📄 {src.filename} ({(src.relevance * 100).toFixed(0)}%) ))}
)}
)) )}
{!hasDocuments && (
⚠️ Upload a document first to get answers based on your content
)}