/* ─── Theme System ────────────────────────────────────────────────────────── */ (function () { const saved = localStorage.getItem('learnix-theme') || 'dark'; document.documentElement.setAttribute('data-theme', saved); })(); let page = 1; /* ─── Theme Management ────────────────────────────────────────────────────── */ function initTheme() { const saved = localStorage.getItem('learnix-theme') || 'dark'; applyTheme(saved); } function applyTheme(theme) { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('learnix-theme', theme); updateSettingsUI(theme); } function updateSettingsUI(theme) { // Update base toggle document.querySelectorAll('.theme-base-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.base === (isDarkVariant(theme) ? 'dark' : 'light')); }); // Update swatches document.querySelectorAll('.swatch').forEach(s => { s.classList.toggle('active', s.dataset.theme === theme); }); } function isDarkVariant(theme) { return ['dark', 'emerald', 'rose', 'amber'].includes(theme); } /* ─── Settings Popup ──────────────────────────────────────────────────────── */ function openSettings() { document.getElementById('settingsOverlay').classList.add('open'); document.getElementById('settingsPopup').classList.add('open'); } function closeSettings() { document.getElementById('settingsOverlay').classList.remove('open'); document.getElementById('settingsPopup').classList.remove('open'); } /* ─── Toast Notification ──────────────────────────────────────────────────── */ function showToast(msg, icon = '✓') { let toast = document.getElementById('toast'); toast.innerHTML = `${icon}${msg}`; toast.classList.remove('hide'); toast.classList.add('show'); clearTimeout(toast._timeout); toast._timeout = setTimeout(() => { toast.classList.remove('show'); toast.classList.add('hide'); }, 2800); } /* ─── Chat ────────────────────────────────────────────────────────────────── */ function getTime() { return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } function appendMsg(content, role) { const chatBox = document.getElementById('chatBox'); // Remove empty state const empty = chatBox.querySelector('.empty-chat'); if (empty) empty.remove(); const isUser = role === 'user'; const row = document.createElement('div'); row.className = `msg-row ${isUser ? 'user-row user' : 'bot'}`; row.innerHTML = `
${isUser ? 'U' : 'AI'}
${content}
${getTime()}
`; chatBox.appendChild(row); chatBox.scrollTo({ top: chatBox.scrollHeight, behavior: 'smooth' }); return row; } function showTyping() { const chatBox = document.getElementById('chatBox'); const row = document.createElement('div'); row.className = 'typing-row'; row.id = 'typingIndicator'; row.innerHTML = `
AI
`; chatBox.appendChild(row); chatBox.scrollTo({ top: chatBox.scrollHeight, behavior: 'smooth' }); } function removeTyping() { const el = document.getElementById('typingIndicator'); if (el) el.remove(); } async function sendMessage() { const input = document.getElementById('messageInput'); const msg = input.value.trim(); if (!msg) return; input.value = ''; input.disabled = true; const sendBtn = document.getElementById('sendBtn'); sendBtn.classList.add('sending'); setTimeout(() => sendBtn.classList.remove('sending'), 500); appendMsg(msg, 'user'); showTyping(); try { const res = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: msg }) }); const data = await res.json(); removeTyping(); appendMsg(data.answer, 'bot'); } catch (err) { removeTyping(); appendMsg('⚠ Could not reach the server.', 'bot'); } finally { input.disabled = false; input.focus(); } } /* ─── Records ─────────────────────────────────────────────────────────────── */ function makeRow(rowData, i, prepend = false) { const tr = document.createElement('tr'); tr.style.animationDelay = prepend ? '0s' : `${i * 0.04}s`; if (prepend) tr.classList.add('row-new'); tr.innerHTML = ` ${rowData.question} ${rowData.answer} `; return tr; } async function loadRecords(reset = false) { if (reset) { page = 1; document.querySelector('#recordsTable tbody').innerHTML = ''; const vm = document.getElementById('viewMore'); vm.style.display = ''; } const btn = document.getElementById('viewMore'); btn.classList.add('loading'); btn.querySelector('span').innerHTML = ` Loading...`; const res = await fetch(`/records?page=${page}`); const data = await res.json(); const tbody = document.querySelector('#recordsTable tbody'); data.records.forEach((row, i) => { tbody.appendChild(makeRow(row, i)); }); btn.classList.remove('loading'); btn.querySelector('span').innerHTML = ` View More `; if (data.records.length < 30) { document.getElementById('viewMore').style.display = 'none'; } } async function refreshRecords() { const refreshBtn = document.getElementById('refreshBtn'); refreshBtn.classList.add('spinning'); await loadRecords(true); setTimeout(() => refreshBtn.classList.remove('spinning'), 600); showToast('Knowledge base refreshed', '↺'); } async function saveEdit(btn) { const row = btn.parentNode.parentNode; const q = row.children[0].innerText; const a = row.children[1].innerText; btn.textContent = '...'; btn.disabled = true; await fetch('/update', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ old_question: q, question: q, answer: a }) }); btn.textContent = 'Save'; btn.disabled = false; showToast('Record updated', '✓'); } async function addRecord() { const qEl = document.getElementById('newQ'); const aEl = document.getElementById('newA'); const q = qEl.value.trim(); const a = aEl.value.trim(); if (!q || !a) { showToast('Fill in both fields', '⚠'); return; } await fetch('/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question: q, answer: a }) }); // Prepend new row to top of table without reloading page const tbody = document.querySelector('#recordsTable tbody'); const tr = makeRow({ question: q, answer: a }, 0, true); tbody.insertBefore(tr, tbody.firstChild); qEl.value = ''; aEl.value = ''; showToast('Record added', '✓'); } function viewMore() { page++; loadRecords(); } /* ─── Init ────────────────────────────────────────────────────────────────── */ document.addEventListener('DOMContentLoaded', () => { initTheme(); loadRecords(); // Enter key to send document.getElementById('messageInput').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // Close settings on overlay click document.getElementById('settingsOverlay').addEventListener('click', closeSettings); // Theme base buttons document.querySelectorAll('.theme-base-btn').forEach(btn => { btn.addEventListener('click', () => { const base = btn.dataset.base; const current = localStorage.getItem('learnix-theme') || 'dark'; // Switch base while keeping color if applicable if (base === 'light') applyTheme('light'); else applyTheme('dark'); }); }); // Swatch clicks document.querySelectorAll('.swatch').forEach(swatch => { swatch.addEventListener('click', () => { applyTheme(swatch.dataset.theme); }); }); });