import { useEffect, useRef, useState } from "react"; import { askQuestion, sendFeedback } from "./api"; // A single turn. Assistant turns carry the source passage so it can be shown // under the answer. `pending` marks the in-flight assistant bubble. function makeMessage(role, text, extra = {}) { return { id: crypto.randomUUID(), role, text, ...extra }; } export default function Chat({ doc, onReset }) { const [messages, setMessages] = useState([]); const [question, setQuestion] = useState(""); const [busy, setBusy] = useState(false); const endRef = useRef(null); useEffect(() => { endRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); async function send(e) { e.preventDefault(); const q = question.trim(); if (!q || busy) return; setMessages((m) => [...m, makeMessage("user", q)]); setQuestion(""); setBusy(true); try { const res = await askQuestion(doc.document_id, q); setMessages((m) => [ ...m, makeMessage("assistant", res.answer, { source: res.source_passage, interactionId: res.interaction_id, feedback: null, generated: res.generated, }), ]); } catch (err) { setMessages((m) => [...m, makeMessage("error", err.message)]); } finally { setBusy(false); } } async function rate(messageId, interactionId, value) { // Optimistically reflect the choice; revert if the request fails. setMessages((m) => m.map((msg) => (msg.id === messageId ? { ...msg, feedback: value } : msg)), ); try { await sendFeedback(interactionId, value); } catch { setMessages((m) => m.map((msg) => (msg.id === messageId ? { ...msg, feedback: null } : msg)), ); } } return (
{doc.filename || "Pasted text"}
{doc.num_chunks} chunks indexed
Ask a question about this document to get started.
)} {messages.map((m) => { if (m.role === "user") { return ({m.source}