103 lines
3.1 KiB
JavaScript
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}));
|
|
}
|
|
});
|