// Right pane — assistant conversation. Per-day persistent thread, streaming // responses, proposal cards with "Apply to draft" / "Dismiss". // Chat only; media capture happens elsewhere (Telegram, Shortcuts). function Assistant({ slug, date, inPostCount, totalCount, onApply }) { const [chat, setChat] = React.useState([]); const [composer, setComposer] = React.useState(""); const [streaming, setStreaming] = React.useState(false); const [streamBuf, setStreamBuf] = React.useState(""); const endRef = React.useRef(); // Load prior turns when the day changes. React.useEffect(() => { if (!slug || !date) return; let cancelled = false; api.chatHistory(slug, date) .then((h) => { if (!cancelled) { setChat(h || []); setStreamBuf(""); } }) .catch(() => { if (!cancelled) setChat([]); }); return () => { cancelled = true; }; }, [slug, date]); React.useEffect(() => { endRef.current && endRef.current.scrollIntoView({ block: "end" }); }, [chat.length, streamBuf]); const send = async (text) => { const msg = (text || "").trim(); if (!msg || streaming) return; setComposer(""); setChat((h) => [...h, { role: "user", content: msg, id: "u-" + Date.now() }]); setStreaming(true); setStreamBuf(""); try { await api.chatSend(slug, date, msg, { delta: (d) => setStreamBuf((b) => b + (d.text || "")), suggest: () => {}, done: (final) => { setChat((h) => [...h, { id: final.id || ("a-" + Date.now()), role: "assistant", content: final.content, suggestion: final.suggestion, suggestion_scope: final.suggestion_scope, created_at: final.created_at, }]); setStreamBuf(""); setStreaming(false); }, error: (e) => { setChat((h) => [...h, { role: "assistant", content: "error: " + (e.message || "chat failed"), id: "err-" + Date.now() }]); setStreamBuf(""); setStreaming(false); }, }); } catch { setStreaming(false); setStreamBuf(""); } }; const quickPrompts = [ "draft the day", "shorter", "more concrete", "what did I skip from today?", ]; const clearChat = async () => { if (streaming) return; if (chat.length === 0) return; if (!confirm("Clear the conversation for this day?")) return; try { await api.chatClear(slug, date); setChat([]); setStreamBuf(""); } catch (e) { // noop — surfacing via inline msg would be overkill for this } }; return ( ); } function BotMsg({ m, inPostCount, totalCount, onApply, onDismiss }) { const wc = m.suggestion ? m.suggestion.trim().split(/\s+/).filter(Boolean).length : 0; return (