/* CheddahBot Frontend JS */ // ── Session Management ── const SESSION_KEY = 'cheddahbot_session'; function getSession() { try { return JSON.parse(localStorage.getItem(SESSION_KEY) || '{}'); } catch { return {}; } } function saveSession(data) { const s = getSession(); Object.assign(s, data); localStorage.setItem(SESSION_KEY, JSON.stringify(s)); } function getActiveAgent() { return getSession().agent_name || document.getElementById('input-agent-name')?.value || 'default'; } // ── Agent Switching ── function switchAgent(name) { // Update UI document.querySelectorAll('.agent-btn').forEach(b => { b.classList.toggle('active', b.dataset.agent === name); }); document.getElementById('input-agent-name').value = name; document.getElementById('input-conv-id').value = ''; saveSession({ agent_name: name, conv_id: null }); // Clear chat and load new sidebar document.getElementById('chat-messages').innerHTML = ''; refreshSidebar(); } function setActiveAgent(name) { document.querySelectorAll('.agent-btn').forEach(b => { b.classList.toggle('active', b.dataset.agent === name); }); const agentInput = document.getElementById('input-agent-name'); if (agentInput) agentInput.value = name; } // ── Sidebar ── function refreshSidebar() { const agent = getActiveAgent(); htmx.ajax('GET', '/chat/conversations?agent_name=' + agent, { target: '#sidebar-conversations', swap: 'innerHTML' }); } // ── Conversation Loading ── function loadConversation(convId) { const agent = getActiveAgent(); document.getElementById('input-conv-id').value = convId; saveSession({ conv_id: convId }); htmx.ajax('GET', '/chat/load/' + convId + '?agent_name=' + agent, { target: '#chat-messages', swap: 'innerHTML' }).then(() => { scrollChat(); renderAllMarkdown(); }); } // ── Chat Input ── function handleKeydown(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); document.getElementById('chat-form').requestSubmit(); } } function autoResize(el) { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 200) + 'px'; } function afterSend(event) { const input = document.getElementById('chat-input'); input.value = ''; input.style.height = 'auto'; // Clear file input and preview const fileInput = document.querySelector('input[type="file"]'); if (fileInput) fileInput.value = ''; const preview = document.getElementById('file-preview'); if (preview) { preview.style.display = 'none'; preview.innerHTML = ''; } scrollChat(); } function scrollChat() { const el = document.getElementById('chat-messages'); if (el) { requestAnimationFrame(() => { el.scrollTop = el.scrollHeight; }); } } // ── File Upload Preview ── function showFileNames(input) { const preview = document.getElementById('file-preview'); if (!input.files.length) { preview.style.display = 'none'; return; } let html = ''; for (const f of input.files) { html += '' + f.name + ''; } preview.innerHTML = html; preview.style.display = 'block'; } // Drag and drop document.addEventListener('DOMContentLoaded', () => { const chatMain = document.querySelector('.chat-main'); if (!chatMain) return; chatMain.addEventListener('dragover', e => { e.preventDefault(); chatMain.style.outline = '2px dashed var(--accent)'; }); chatMain.addEventListener('dragleave', () => { chatMain.style.outline = ''; }); chatMain.addEventListener('drop', e => { e.preventDefault(); chatMain.style.outline = ''; const fileInput = document.querySelector('input[type="file"]'); if (fileInput && e.dataTransfer.files.length) { fileInput.files = e.dataTransfer.files; showFileNames(fileInput); } }); }); // ── SSE Streaming ── // Handle SSE chunks for chat streaming let streamBuffer = ''; let activeSSE = null; document.addEventListener('htmx:sseBeforeMessage', function(e) { // This fires for each SSE event received by htmx }); // Watch for SSE trigger divs being added to the DOM const observer = new MutationObserver(mutations => { for (const m of mutations) { for (const node of m.addedNodes) { if (node.id === 'sse-trigger') { setupStream(node); } } } }); document.addEventListener('DOMContentLoaded', () => { const chatMessages = document.getElementById('chat-messages'); if (chatMessages) { observer.observe(chatMessages, { childList: true, subtree: true }); } }); function setupStream(triggerDiv) { const sseUrl = triggerDiv.getAttribute('sse-connect'); if (!sseUrl) return; // Remove the htmx SSE to manage manually triggerDiv.remove(); const responseDiv = document.getElementById('assistant-response'); if (!responseDiv) return; streamBuffer = ''; // Show typing indicator responseDiv.innerHTML = '