2025-09-17 18:12:28 +00:00

103 lines
3.1 KiB
JavaScript

// Minimal client that sends CSRF, renders assistant HTML, and toggles theme.
const qs = (s, r = document) => r.querySelector(s);
const chatEl = qs('#chat');
const formEl = qs('#chatForm');
const msgInput = qs('#messageInput');
const sysInput = qs('#systemInput');
const resetBtn = qs('#resetBtn');
const themeBtn = qs('#themeToggle');
function post(action, data = {}) {
const body = new URLSearchParams({ action, csrf: (window.__APP__ && window.__APP__.csrf) || '', ...data });
return fetch(location.pathname, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
body
}).then(r => r.json());
}
// Simple helpers to append messages to the chat
function appendUserMessage(text) {
const wrap = document.createElement('div');
wrap.className = 'msg user';
wrap.innerHTML = `
<div class="content">${escapeHtmlWithNewlines(text)}</div>
<div class="time">${nowTime()}</div>
`;
chatEl.appendChild(wrap);
chatEl.scrollTop = chatEl.scrollHeight;
}
function appendAssistantHtml(html) {
const clean = (window.DOMPurify ? DOMPurify.sanitize(html) : html);
const wrap = document.createElement('div');
wrap.className = 'msg assistant';
wrap.innerHTML = `
<div class="content">${clean}</div>
<div class="time">${nowTime()}</div>
`;
chatEl.appendChild(wrap);
chatEl.scrollTop = chatEl.scrollHeight;
}
function escapeHtmlWithNewlines(s) {
const t = document.createElement('div');
t.innerText = s;
return t.innerHTML.replace(/\n/g, '<br>');
}
function nowTime() {
const d = new Date();
const hh = String(d.getHours()).padStart(2,'0');
const mm = String(d.getMinutes()).padStart(2,'0');
return `${hh}:${mm}`;
}
// Submit chat
formEl?.addEventListener('submit', async (e) => {
e.preventDefault();
const message = msgInput.value.trim();
const system = sysInput ? sysInput.value.trim() : '';
if (!message) return;
appendUserMessage(message);
msgInput.value = '';
msgInput.focus();
const res = await post('chat', { message, system });
if (res.ok) {
// Prefer server-rendered sanitized HTML for assistant
appendAssistantHtml(res.reply_html || escapeHtmlWithNewlines(res.reply || ''));
} else {
appendAssistantHtml(`<p class="text-danger">Error: ${res.error || 'Unknown'}</p>`);
}
});
// Reset chat
resetBtn?.addEventListener('click', async () => {
const res = await post('reset', {});
if (res.ok) {
chatEl.innerHTML = `<div class="text-secondary">Start the conversation below…</div>`;
}
});
// Theme toggle
themeBtn?.addEventListener('click', async () => {
const current = (window.__APP__ && window.__APP__.theme) || 'dark';
const next = current === 'dark' ? 'light' : 'dark';
const res = await post('set_theme', { theme: next });
if (res.ok) {
document.documentElement.setAttribute('data-theme', next);
window.__APP__.theme = next;
}
});
// With "enter" key press, send message
msgInput.addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); // stop newline
formEl.dispatchEvent(new Event('submit', {cancelable: true}));
}
});