IMPROVEMENT (infrastructure): javascript code is now based on jQuery
This commit is contained in:
parent
42206afc88
commit
00fa5ae75a
@ -6,6 +6,7 @@
|
||||
<title>FMF GPT</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/main.css" rel="stylesheet">
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container py-4 chat-wrap">
|
||||
|
||||
126
main.js
126
main.js
@ -1,50 +1,18 @@
|
||||
// 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 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 () {
|
||||
// Element refs
|
||||
const $chat = $('#chat');
|
||||
const $form = $('#chatForm');
|
||||
const $msg = $('#messageInput');
|
||||
const $sys = $('#systemInput');
|
||||
const $reset = $('#resetBtn');
|
||||
const $theme = $('#themeToggle');
|
||||
|
||||
// Helpers
|
||||
function escapeHtmlWithNewlines(s) {
|
||||
const t = document.createElement('div');
|
||||
t.innerText = s;
|
||||
return t.innerHTML.replace(/\n/g, '<br>');
|
||||
// jQuery-safe escape via .text(), then convert \n -> <br>
|
||||
return $('<div>').text(s).html().replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
function nowTime() {
|
||||
@ -54,49 +22,91 @@ function nowTime() {
|
||||
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
|
||||
formEl?.addEventListener('submit', async (e) => {
|
||||
$form.on('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
const message = msgInput.value.trim();
|
||||
const system = sysInput ? sysInput.value.trim() : '';
|
||||
|
||||
const message = ($msg.val() || '').toString().trim();
|
||||
const system = $sys.length ? ($sys.val() || '').toString().trim() : '';
|
||||
if (!message) return;
|
||||
|
||||
appendUserMessage(message);
|
||||
msgInput.value = '';
|
||||
msgInput.focus();
|
||||
$msg.val('').focus();
|
||||
|
||||
try {
|
||||
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>`);
|
||||
}
|
||||
} catch (err) {
|
||||
appendAssistantHtml(`<p class="text-danger">Network error</p>`);
|
||||
}
|
||||
});
|
||||
|
||||
// Reset chat
|
||||
resetBtn?.addEventListener('click', async () => {
|
||||
$reset.on('click', async function () {
|
||||
try {
|
||||
const res = await post('reset', {});
|
||||
if (res.ok) {
|
||||
chatEl.innerHTML = `<div class="text-secondary">Start the conversation below…</div>`;
|
||||
$chat.html('<div class="text-secondary">Start the conversation below…</div>');
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
});
|
||||
|
||||
// Theme toggle
|
||||
themeBtn?.addEventListener('click', async () => {
|
||||
$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);
|
||||
window.__APP__.theme = next;
|
||||
if (window.__APP__) window.__APP__.theme = next;
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
});
|
||||
|
||||
// With "enter" key press, send message
|
||||
msgInput.addEventListener('keydown', e => {
|
||||
// With "enter" key press, send message (Shift+Enter => newline)
|
||||
$msg.on('keydown', function (e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault(); // stop newline
|
||||
formEl.dispatchEvent(new Event('submit', {cancelable: true}));
|
||||
e.preventDefault();
|
||||
$form.trigger('submit');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user