// Minimal client that sends CSRF, renders assistant HTML, and toggles theme. (jQuery version) $(function () { // Element refs const $chat = $('#chat'); const $form = $('#chatForm'); const $msg = $('#messageInput'); const $sys = $('#systemInput'); const $reset = $('#resetBtn'); const $theme = $('#themeToggle'); // Helpers function escapeHtmlWithNewlines(s) { // jQuery-safe escape via .text(), then convert \n ->
return $('
').text(s).html().replace(/\n/g, '
'); } 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}`; } function appendUserMessage(text) { const html = `
${escapeHtmlWithNewlines(text)}
${nowTime()}
`; $chat.append(html); $chat.prop('scrollTop', $chat[0].scrollHeight); } function appendAssistantHtml(html) { const clean = (window.DOMPurify ? DOMPurify.sanitize(html) : html); const block = `
${clean}
${nowTime()}
`; $chat.append(block); $chat.prop('scrollTop', $chat[0].scrollHeight); } // AJAX POST helper (x-www-form-urlencoded) function post(action, data = {}) { const csrf = (window.__APP__ && window.__APP__.csrf) || ''; const payload = new URLSearchParams({ action, csrf, ...data }).toString(); return $.ajax({ url: location.pathname, method: 'POST', data: payload, contentType: 'application/x-www-form-urlencoded; charset=UTF-8', dataType: 'json' }); } // Submit chat $form.on('submit', async function (e) { e.preventDefault(); const message = ($msg.val() || '').toString().trim(); const system = $sys.length ? ($sys.val() || '').toString().trim() : ''; if (!message) return; appendUserMessage(message); $msg.val('').focus(); try { const res = await post('chat', { message, system }); if (res.ok) { appendAssistantHtml(res.reply_html || escapeHtmlWithNewlines(res.reply || '')); } else { appendAssistantHtml(`

Error: ${res.error || 'Unknown'}

`); } } catch (err) { appendAssistantHtml(`

Network error

`); } }); // Reset chat $reset.on('click', async function () { try { const res = await post('reset', {}); if (res.ok) { $chat.html('
Start the conversation below…
'); } } catch (_) { /* ignore */ } }); // Theme toggle $theme.on('click', async function () { const current = (window.__APP__ && window.__APP__.theme) || 'dark'; const next = current === 'dark' ? 'light' : 'dark'; try { const res = await post('set_theme', { theme: next }); if (res.ok) { document.documentElement.setAttribute('data-theme', next); if (window.__APP__) window.__APP__.theme = next; } } catch (_) { /* ignore */ } }); // With "enter" key press, send message (Shift+Enter => newline) $msg.on('keydown', function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); $form.trigger('submit'); } }); });