113 lines
3.3 KiB
JavaScript
113 lines
3.3 KiB
JavaScript
// 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 -> <br>
|
|
return $('<div>').text(s).html().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}`;
|
|
}
|
|
|
|
function appendUserMessage(text) {
|
|
const html = `
|
|
<div class="msg user">
|
|
<div class="content">${escapeHtmlWithNewlines(text)}</div>
|
|
<div class="time">${nowTime()}</div>
|
|
</div>`;
|
|
$chat.append(html);
|
|
$chat.prop('scrollTop', $chat[0].scrollHeight);
|
|
}
|
|
|
|
function appendAssistantHtml(html) {
|
|
const clean = (window.DOMPurify ? DOMPurify.sanitize(html) : html);
|
|
const block = `
|
|
<div class="msg assistant">
|
|
<div class="content">${clean}</div>
|
|
<div class="time">${nowTime()}</div>
|
|
</div>`;
|
|
$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(`<p class="text-danger">Error: ${res.error || 'Unknown'}</p>`);
|
|
}
|
|
} catch (err) {
|
|
appendAssistantHtml(`<p class="text-danger">Network error</p>`);
|
|
}
|
|
});
|
|
|
|
// Reset chat
|
|
$reset.on('click', async function () {
|
|
try {
|
|
const res = await post('reset', {});
|
|
if (res.ok) {
|
|
$chat.html('<div class="text-secondary">Start the conversation below…</div>');
|
|
}
|
|
} 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');
|
|
}
|
|
});
|
|
});
|