diff --git a/files b/files
new file mode 100644
index 0000000..a9b203a
--- /dev/null
+++ b/files
@@ -0,0 +1 @@
+main.js
diff --git a/index.html b/index.html
index ecdcab9..c2f7412 100644
--- a/index.html
+++ b/index.html
@@ -6,6 +6,7 @@
diff --git a/main.js b/main.js
index fecebff..45a73ae 100644
--- a/main.js
+++ b/main.js
@@ -1,102 +1,112 @@
-// Minimal client that sends CSRF, renders assistant HTML, and toggles theme.
+// Minimal client that sends CSRF, renders assistant HTML, and toggles theme. (jQuery version)
-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 () {
+ // Element refs
+ const $chat = $('#chat');
+ const $form = $('#chatForm');
+ const $msg = $('#messageInput');
+ const $sys = $('#systemInput');
+ const $reset = $('#resetBtn');
+ const $theme = $('#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 = `
-
${escapeHtmlWithNewlines(text)}
-
${nowTime()}
- `;
- 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 = `
-
${clean}
-
${nowTime()}
- `;
- chatEl.appendChild(wrap);
- chatEl.scrollTop = chatEl.scrollHeight;
-}
-
-function escapeHtmlWithNewlines(s) {
- const t = document.createElement('div');
- t.innerText = s;
- return t.innerHTML.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}`;
-}
-
-// 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(`
Error: ${res.error || 'Unknown'}
`);
+ // Helpers
+ function escapeHtmlWithNewlines(s) {
+ // jQuery-safe escape via .text(), then convert \n ->
+ return $('
').text(s).html().replace(/\n/g, '
');
}
-});
-// Reset chat
-resetBtn?.addEventListener('click', async () => {
- const res = await post('reset', {});
- if (res.ok) {
- chatEl.innerHTML = `
Start the conversation below…
`;
+ 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}`;
}
-});
-// 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;
+ function appendUserMessage(text) {
+ const html = `
+
+
${escapeHtmlWithNewlines(text)}
+
${nowTime()}
+
`;
+ $chat.append(html);
+ $chat.prop('scrollTop', $chat[0].scrollHeight);
}
-});
-// 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}));
+ 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');
+ }
+ });
});
diff --git a/ticket b/ticket
new file mode 100644
index 0000000..2bb0138
--- /dev/null
+++ b/ticket
@@ -0,0 +1 @@
+give me a drop-in replacement that uses jQuery