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 @@ FMF GPT +
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