// ────────────────────────────────── ChatDrawer.jsx import { useEffect, useRef, useState } from "react"; import { Sheet, SheetTrigger, SheetContent } from "./ui/sheet.js"; import { Button } from "./ui/button.js"; import { Input } from "./ui/input.js"; import { MessageCircle } from "lucide-react"; /* --------------------------------------------------------------- Streams from /api/chat/free and executes UI-tool callbacks ----------------------------------------------------------------*/ export default function ChatDrawer({ pageContext = "Home", snapshot = null, }) { /* state */ const [open, setOpen] = useState(false); const [prompt, setPrompt] = useState(""); const [messages, setMessages] = useState([]); // { role, content } const listRef = useRef(null); /* auto-scroll */ useEffect(() => { listRef.current && (listRef.current.scrollTop = listRef.current.scrollHeight); }, [messages]); /* helper: stream-friendly append */ const pushAssistant = (chunk) => setMessages((prev) => { const last = prev.at(-1); if (last?.role === "assistant") { const updated = [...prev]; updated[updated.length - 1] = { ...last, content: last.content + chunk }; return updated; } return [...prev, { role: "assistant", content: chunk }]; }); /* send prompt */ async function sendPrompt() { const text = prompt.trim(); if (!text) return; setMessages((m) => [...m, { role: "user", content: text }]); setPrompt(""); const body = JSON.stringify({ prompt: text, pageContext, chatHistory: messages, snapshot }); try { const token = localStorage.getItem("token") || ""; const headers = { "Content-Type": "application/json", Accept : "text/event-stream", ...(token ? { Authorization: `Bearer ${token}` } : {}) }; const resp = await fetch("/api/chat/free", { method: "POST", headers, body }); if (!resp.ok || !resp.body) throw new Error(`HTTP ${resp.status}`); const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buf = ""; /* ─────────────── STREAM LOOP ─────────────── */ while (true) { const { value, done } = await reader.read(); if (done) break; if (!value) continue; buf += decoder.decode(value, { stream: true }); let nl; while ((nl = buf.indexOf("\n")) !== -1) { const line = buf.slice(0, nl).trim(); // one full line buf = buf.slice(nl + 1); // keep remainder /* 2️⃣ normal assistant text */ if (line) pushAssistant(line + "\n"); } } /* ───────── END STREAM LOOP ───────── */ if (buf.trim()) pushAssistant(buf); } catch (err) { console.error("[ChatDrawer] stream error", err); pushAssistant("Sorry — something went wrong. Please try again later."); } } /* Enter submits */ const handleKeyDown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendPrompt(); } }; /* UI */ return ( {/* floating button */} {/* drawer */}
{pageContext}
{/* transcript */}
{messages.map((m, i) => (
{m.content}
))}
{/* prompt box */}
{ e.preventDefault(); sendPrompt(); }} className="flex gap-2" > setPrompt(e.target.value)} onKeyDown={handleKeyDown} placeholder="Ask me anything…" className="flex-1" />
); }