// Cassandra chat sidebar — ephemeral, client-state conversation. // No persistence: page refresh starts a new chat. (() => { const thread = document.getElementById('chat-thread'); const form = document.getElementById('chat-form'); const input = document.getElementById('chat-input'); const send = document.getElementById('chat-send'); if (!thread || !form || !input || !send) return; /** @type {{role: string, content: string}[]} */ const messages = []; function escapeHTML(s) { return s.replace(/&/g, '&') .replace(//g, '>'); } function append(role, html_or_text, opts) { const div = document.createElement('div'); div.className = 'chat-msg chat-msg--' + role; if (opts && opts.html) { div.innerHTML = html_or_text; } else { div.textContent = html_or_text; } thread.appendChild(div); thread.scrollTop = thread.scrollHeight; return div; } form.addEventListener('submit', async (e) => { e.preventDefault(); const text = input.value.trim(); if (!text || send.disabled) return; messages.push({role: 'user', content: text}); append('user', text); input.value = ''; send.disabled = true; const thinking = append('assistant pending', '…'); try { const r = await fetch('/api/chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({messages}), }); if (!r.ok) { const msg = await r.text(); thinking.className = 'chat-msg chat-msg--error'; thinking.textContent = 'HTTP ' + r.status + ': ' + msg.slice(0, 300); return; } const data = await r.json(); thinking.className = 'chat-msg chat-msg--assistant'; thinking.innerHTML = data.content_html || escapeHTML(data.content); messages.push({role: 'assistant', content: data.content}); } catch (err) { thinking.className = 'chat-msg chat-msg--error'; thinking.textContent = 'error: ' + err.message; } finally { send.disabled = false; input.focus(); } }); // Enter to send; Shift+Enter for newline. input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); form.requestSubmit(); } }); })();