// ── Chat UI controller ──────────────────────────────────────────────────────── const $ = (sel) => document.querySelector(sel); export class Chat { constructor() { this._container = $('#messages'); this._emptyState = $('#empty-state'); } clear() { this._container.innerHTML = ''; this._emptyState?.classList.remove('hidden'); } renderHistory(messages) { this._container.innerHTML = ''; this._emptyState?.classList.add('hidden'); messages.forEach((m) => this._appendBubble(m.role, m.content)); this._scrollBottom(); } async send(sessionId, message, contextIds = []) { // Immediately render user bubble this._appendBubble('user', message); this._emptyState?.classList.add('hidden'); // Typing indicator const typingId = this._appendTyping(); try { const res = await fetch(`/api/chat/sessions/${sessionId}/ask`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message, context_ids: contextIds }), }); this._removeTyping(typingId); let data; try { data = await res.json(); } catch { this._appendBubble('assistant', 'Server error — could not parse response.', true); this._scrollBottom(); return; } if (!res.ok) { this._appendBubble('assistant', `Error: ${data.error || 'Unknown error'}`, true); } else { this._appendBubble('assistant', data.reply); } } catch (err) { this._removeTyping(typingId); this._appendBubble('assistant', `Network error: ${err.message}`, true); } this._scrollBottom(); } _appendBubble(role, content, isError = false) { const wrap = document.createElement('div'); wrap.className = `flex ${role === 'user' ? 'justify-end' : 'justify-start'} gap-3`; if (role === 'assistant') { wrap.innerHTML = `