// ── DOM Helpers ── const $ = (sel) => document.querySelector(sel); const chatEl = () => $('#chat'); const metaEl = () => $('#meta'); function scrollDown() { const chat = chatEl(); chat.scrollTop = chat.scrollHeight; } function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } function truncate(s, max) { return s && s.length > max ? s.substring(0, max) + '...' : (s || ''); } // ── Message Bubbles ── function addUserBubble(text, streamed = false) { const div = document.createElement('div'); div.className = 'msg user'; if (streamed) div.classList.add('streamed'); div.textContent = text; chatEl().appendChild(div); scrollDown(); } function addAssistantBubble(text) { const div = document.createElement('div'); div.className = 'msg assistant'; div.textContent = text; chatEl().appendChild(div); scrollDown(); return div; } function addErrorBubble(text) { const div = document.createElement('div'); div.className = 'msg system-info'; div.style.color = '#ff6b6b'; div.textContent = text; chatEl().appendChild(div); scrollDown(); } // ── Streaming Bubble ── let _streamDiv = null; let _streamText = ''; function startStreamBubble() { _streamDiv = document.createElement('div'); _streamDiv.className = 'msg assistant streamed'; _streamDiv.innerHTML = ''; _streamText = ''; chatEl().appendChild(_streamDiv); scrollDown(); } function appendToStream(text) { if (!_streamDiv) startStreamBubble(); _streamText += text; _streamDiv.textContent = _streamText; const cursor = document.createElement('span'); cursor.className = 'cursor'; _streamDiv.appendChild(cursor); scrollDown(); } function endStreamBubble() { if (_streamDiv) { const cursor = _streamDiv.querySelector('.cursor'); if (cursor) cursor.remove(); if (!_streamText.trim()) _streamDiv.remove(); } _streamDiv = null; _streamText = ''; } // ── Tool Boxes ── function addToolBoxLoading(name) { const div = document.createElement('div'); div.className = 'tool-box streamed'; div.innerHTML = '' + esc(name) + ' ⏳'; chatEl().appendChild(div); scrollDown(); return div; } function finishToolBox(div, data) { if (!div) return; div.innerHTML = '' + esc(data.name) + '' + '' + esc(truncate(data.input, 120)) + '' + '