// ───────────────────────── ChatDrawer.jsx import { useEffect, useRef, useState } from 'react'; import { Sheet, SheetTrigger, SheetContent } from './ui/sheet.js'; import { cn } from '../utils/cn.js'; import { Button } from './ui/button.js'; import { Input } from './ui/input.js'; import { MessageCircle } from 'lucide-react'; import RetirementChatBar from './RetirementChatBar.js'; /* ------------------------------------------------------------------ */ /* ChatDrawer – support-bot lives in this file (streamed from /api/chat/free) – retirement helper is just a passthrough to ------------------------------------------------------------------ */ export default function ChatDrawer({ /* ─ props from App.js ─ */ pageContext = 'Home', snapshot = null, open: controlledOpen = false, onOpenChange, pane: controlledPane = 'support', setPane: setControlledPane, retireProps = null, // { scenario, financialProfile, … } canShowRetireBot }) { /* ─────────────────────────── internal / fallback state ───────── */ const [openLocal, setOpenLocal] = useState(false); const [paneLocal, setPaneLocal] = useState('support'); /* prefer the controlled props when supplied */ const open = controlledOpen ?? openLocal; const setOpen = onOpenChange ?? setOpenLocal; const pane = controlledPane ?? paneLocal; const setPane = setControlledPane ?? setPaneLocal; /* ────────────────── free-tier support-bot state ─────────────── */ const [prompt, setPrompt] = useState(''); const [messages, setMessages] = useState([]); // { role, content } const listRef = useRef(null); /* auto-scroll on incoming messages */ useEffect(() => { listRef.current && (listRef.current.scrollTop = listRef.current.scrollHeight); }, [messages]); /* helper: merge chunks while streaming */ 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 }]; }); useEffect(() => { if (!canShowRetireBot && pane === 'retire') { setPane('support'); } }, [canShowRetireBot, pane, setPane]); /* ───────────────────────── send support-bot 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 = ''; while (true) { /* eslint-disable no-await-in-loop */ const { value, done } = await reader.read(); /* eslint-enable no-await-in-loop */ 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(); buf = buf.slice(nl + 1); if (line) pushAssistant(line + '\n'); } } if (buf.trim()) pushAssistant(buf); } catch (err) { console.error('[ChatDrawer] stream error', err); pushAssistant( 'Sorry — something went wrong. Please try again later.' ); } } const handleKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendPrompt(); } }; /* ---------- render ---------- */ return ( {/* floating action button */} {/* side drawer */} {/* header (tabs only if retirement bot is allowed) */}
{canShowRetireBot && ( )}
{/* body */} {pane === 'support' ? ( /* ── Support bot pane ── */ <>
{messages.map((m, i) => (
{m.content}
))}
{ e.preventDefault(); sendPrompt(); }} className="flex gap-2" > setPrompt(e.target.value)} placeholder="Ask me anything…" onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendPrompt(); } }} className="flex-1" />
) : retireProps ? ( /* ── Retirement helper pane ── */ ) : ( /* failsafe (retire tab opened before selecting a scenario) */
Select a scenario in  Retirement Planner
)}
); }