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

{messages.length === 0 && (

Ask a question about this document to get started.

)} {messages.map((m) => { if (m.role === "user") { return (
{m.text}
); } if (m.role === "error") { return (
⚠ {m.text}
); } return (
{m.text}
{m.generated && ( ✨ AI answer · grounded in the source below )} {m.source && (
Source passage

{m.source}

)} {m.interactionId && (
Helpful? rate(m.id, m.interactionId, "up")} > 👍 rate(m.id, m.interactionId, "down")} > 👎
)}
); })} {busy && (
)}
setQuestion(e.target.value)} placeholder="Ask a question…" className="flex-1 rounded-lg border border-slate-300 bg-slate-50 px-3 py-2 text-sm outline-none transition focus:border-slate-400 focus:bg-white" />
); } function Dot({ delay = "0ms" }) { return ( ); } function FeedbackButton({ active, dimmed, activeClass, label, onClick, children }) { return ( ); }