learnix-chatbot / static /script.js
shashidharak99's picture
Update static/script.js
12d403f verified
/* ─── 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 = `<span>${icon}</span><span>${msg}</span>`;
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 = `
<div class="msg-avatar ${isUser ? 'user-av' : 'bot-av'}">${isUser ? 'U' : 'AI'}</div>
<div class="msg-meta">
<div class="msg-bubble">${content}</div>
<span class="msg-time">${getTime()}</span>
</div>
`;
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 = `
<div class="msg-avatar bot-av">AI</div>
<div class="typing-bubble">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
`;
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 = `
<td contenteditable="true">${rowData.question}</td>
<td contenteditable="true">${rowData.answer}</td>
<td><button class="save-btn" onclick="saveEdit(this)">Save</button></td>
`;
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 = `<svg class="load-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg> 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 = `
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
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);
});
});
});