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>
|
<title>FMF GPT</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="/main.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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container py-4 chat-wrap">
|
<div class="container py-4 chat-wrap">
|
||||||
|
|||||||
140
main.js
140
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);
|
$(function () {
|
||||||
const chatEl = qs('#chat');
|
// Element refs
|
||||||
const formEl = qs('#chatForm');
|
const $chat = $('#chat');
|
||||||
const msgInput = qs('#messageInput');
|
const $form = $('#chatForm');
|
||||||
const sysInput = qs('#systemInput');
|
const $msg = $('#messageInput');
|
||||||
const resetBtn = qs('#resetBtn');
|
const $sys = $('#systemInput');
|
||||||
const themeBtn = qs('#themeToggle');
|
const $reset = $('#resetBtn');
|
||||||
|
const $theme = $('#themeToggle');
|
||||||
|
|
||||||
function post(action, data = {}) {
|
// Helpers
|
||||||
const body = new URLSearchParams({ action, csrf: (window.__APP__ && window.__APP__.csrf) || '', ...data });
|
function escapeHtmlWithNewlines(s) {
|
||||||
return fetch(location.pathname, {
|
// jQuery-safe escape via .text(), then convert \n -> <br>
|
||||||
method: 'POST',
|
return $('<div>').text(s).html().replace(/\n/g, '<br>');
|
||||||
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 nowTime() {
|
||||||
function appendUserMessage(text) {
|
const d = new Date();
|
||||||
const wrap = document.createElement('div');
|
const hh = String(d.getHours()).padStart(2, '0');
|
||||||
wrap.className = 'msg user';
|
const mm = String(d.getMinutes()).padStart(2, '0');
|
||||||
wrap.innerHTML = `
|
return `${hh}:${mm}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendUserMessage(text) {
|
||||||
|
const html = `
|
||||||
|
<div class="msg user">
|
||||||
<div class="content">${escapeHtmlWithNewlines(text)}</div>
|
<div class="content">${escapeHtmlWithNewlines(text)}</div>
|
||||||
<div class="time">${nowTime()}</div>
|
<div class="time">${nowTime()}</div>
|
||||||
`;
|
</div>`;
|
||||||
chatEl.appendChild(wrap);
|
$chat.append(html);
|
||||||
chatEl.scrollTop = chatEl.scrollHeight;
|
$chat.prop('scrollTop', $chat[0].scrollHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendAssistantHtml(html) {
|
function appendAssistantHtml(html) {
|
||||||
const clean = (window.DOMPurify ? DOMPurify.sanitize(html) : html);
|
const clean = (window.DOMPurify ? DOMPurify.sanitize(html) : html);
|
||||||
const wrap = document.createElement('div');
|
const block = `
|
||||||
wrap.className = 'msg assistant';
|
<div class="msg assistant">
|
||||||
wrap.innerHTML = `
|
|
||||||
<div class="content">${clean}</div>
|
<div class="content">${clean}</div>
|
||||||
<div class="time">${nowTime()}</div>
|
<div class="time">${nowTime()}</div>
|
||||||
`;
|
</div>`;
|
||||||
chatEl.appendChild(wrap);
|
$chat.append(block);
|
||||||
chatEl.scrollTop = chatEl.scrollHeight;
|
$chat.prop('scrollTop', $chat[0].scrollHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtmlWithNewlines(s) {
|
// AJAX POST helper (x-www-form-urlencoded)
|
||||||
const t = document.createElement('div');
|
function post(action, data = {}) {
|
||||||
t.innerText = s;
|
const csrf = (window.__APP__ && window.__APP__.csrf) || '';
|
||||||
return t.innerHTML.replace(/\n/g, '<br>');
|
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'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function nowTime() {
|
// Submit chat
|
||||||
const d = new Date();
|
$form.on('submit', async function (e) {
|
||||||
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();
|
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;
|
if (!message) return;
|
||||||
|
|
||||||
appendUserMessage(message);
|
appendUserMessage(message);
|
||||||
msgInput.value = '';
|
$msg.val('').focus();
|
||||||
msgInput.focus();
|
|
||||||
|
|
||||||
|
try {
|
||||||
const res = await post('chat', { message, system });
|
const res = await post('chat', { message, system });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
// Prefer server-rendered sanitized HTML for assistant
|
|
||||||
appendAssistantHtml(res.reply_html || escapeHtmlWithNewlines(res.reply || ''));
|
appendAssistantHtml(res.reply_html || escapeHtmlWithNewlines(res.reply || ''));
|
||||||
} else {
|
} else {
|
||||||
appendAssistantHtml(`<p class="text-danger">Error: ${res.error || 'Unknown'}</p>`);
|
appendAssistantHtml(`<p class="text-danger">Error: ${res.error || 'Unknown'}</p>`);
|
||||||
}
|
}
|
||||||
});
|
} catch (err) {
|
||||||
|
appendAssistantHtml(`<p class="text-danger">Network error</p>`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Reset chat
|
// Reset chat
|
||||||
resetBtn?.addEventListener('click', async () => {
|
$reset.on('click', async function () {
|
||||||
|
try {
|
||||||
const res = await post('reset', {});
|
const res = await post('reset', {});
|
||||||
if (res.ok) {
|
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
|
// Theme toggle
|
||||||
themeBtn?.addEventListener('click', async () => {
|
$theme.on('click', async function () {
|
||||||
const current = (window.__APP__ && window.__APP__.theme) || 'dark';
|
const current = (window.__APP__ && window.__APP__.theme) || 'dark';
|
||||||
const next = current === 'dark' ? 'light' : 'dark';
|
const next = current === 'dark' ? 'light' : 'dark';
|
||||||
|
try {
|
||||||
const res = await post('set_theme', { theme: next });
|
const res = await post('set_theme', { theme: next });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
document.documentElement.setAttribute('data-theme', next);
|
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
|
// With "enter" key press, send message (Shift+Enter => newline)
|
||||||
msgInput.addEventListener('keydown', e => {
|
$msg.on('keydown', function (e) {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
e.preventDefault(); // stop newline
|
e.preventDefault();
|
||||||
formEl.dispatchEvent(new Event('submit', {cancelable: true}));
|
$form.trigger('submit');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user