share: render markdown + preview-before-publish + soul-history probe
Share card now displays the AI bubble's marked-rendered HTML (after basic tag allowlist sanitization) instead of escaped plaintext. Markdown bold, lists, code, headers all show. Share click in chat now opens a preview modal. Publishing to the gallery only happens when the user explicitly clicks Publish in the modal - removes the click-and-immediately-public surprise.
This commit is contained in:
@@ -0,0 +1,457 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
restore-chat-js-with-preview.py - Re-inline the chat-widget JS into
|
||||
styles.el's page_close() so that extract-js.py picks up the freshly modified
|
||||
source on the next build (instead of carrying the obfuscated old asset
|
||||
forward forever).
|
||||
|
||||
What this writes:
|
||||
- The original chat-widget IIFE (from commit 640813e^), modified to
|
||||
capture bubble.innerHTML on Share-click and open a preview modal
|
||||
instead of POSTing to /api/share immediately.
|
||||
- Modal HTML (#neuron-share-preview-modal) inserted right before the
|
||||
chat-widget script.
|
||||
- The /api/share POST in publishSharePreview() sends both answer (legacy
|
||||
plaintext), answer_html (rendered, sanitized server-side), and
|
||||
answer_plaintext (og:desc).
|
||||
|
||||
Idempotent. Re-run is a no-op once the inline block is present.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
STYLES_EL = REPO_ROOT / "src" / "styles.el"
|
||||
|
||||
MARKER = "neuron-share-preview-modal"
|
||||
|
||||
# El strings use \" for embedded ". Newlines stay literal. Backticks fine.
|
||||
# We assemble the inline JS as a single literal: opening <script> ... </script>.
|
||||
# Every double-quote inside must become \\" in the El source. We write it as a
|
||||
# raw string and use string substitution at the end.
|
||||
|
||||
CHAT_HTML_AND_JS = r"""
|
||||
<!-- Share preview modal: shown after the user clicks Share on an AI bubble.
|
||||
Renders the share-card layout in an iframe (via srcdoc) so the visitor
|
||||
sees exactly what the public card will look like before publishing. -->
|
||||
<div id=\"neuron-share-preview-modal\" style=\"display:none;position:fixed;inset:0;z-index:200000;background:rgba(13,13,20,.55);align-items:center;justify-content:center;padding:1.5rem;font-family:'IBM Plex Sans',system-ui,sans-serif\">
|
||||
<div style=\"background:#fff;width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;border-radius:12px;box-shadow:0 24px 80px rgba(0,0,0,.35);overflow:hidden\">
|
||||
<div style=\"display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid rgba(0,0,0,.08)\">
|
||||
<div>
|
||||
<div style=\"font-size:.65rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#6B6B7E\">Preview</div>
|
||||
<div style=\"font-size:1rem;font-weight:500;color:#0D0D14;margin-top:.15rem\">This is what you are about to publish</div>
|
||||
</div>
|
||||
<button type=\"button\" id=\"neuron-share-preview-close\" aria-label=\"Close\" style=\"background:none;border:none;cursor:pointer;color:#6B6B7E;padding:.25rem;line-height:1;font-size:1.5rem\">×</button>
|
||||
</div>
|
||||
<iframe id=\"neuron-share-preview-frame\" style=\"flex:1;width:100%;min-height:420px;border:0;background:#FAFAF8\" sandbox=\"allow-same-origin\"></iframe>
|
||||
<div style=\"display:flex;align-items:center;justify-content:flex-end;gap:.6rem;padding:.85rem 1.25rem;border-top:1px solid rgba(0,0,0,.08);background:#FAFAF8\">
|
||||
<button type=\"button\" id=\"neuron-share-preview-cancel\" style=\"background:#fff;border:1px solid rgba(0,0,0,.18);color:#3A3A4A;cursor:pointer;padding:.55rem 1rem;font:inherit;font-size:.8rem;font-weight:500;border-radius:6px\">Cancel</button>
|
||||
<button type=\"button\" id=\"neuron-share-preview-publish\" style=\"background:#0052A0;border:1px solid #0052A0;color:#fff;cursor:pointer;padding:.55rem 1.1rem;font:inherit;font-size:.8rem;font-weight:600;letter-spacing:.04em;border-radius:6px\">Publish to gallery</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
if (typeof marked !== 'undefined') { marked.setOptions({ breaks: true, gfm: true }); }
|
||||
var TURNSTILE_SITE_KEY = '0x4AAAAAADHAZXyuRb3yD9mr';
|
||||
var turnstileToken = '';
|
||||
var turnstileWidgetId = null;
|
||||
var turnstileVerified = false;
|
||||
var isOpen = false;
|
||||
var MAX = 10;
|
||||
|
||||
// ── Share preview modal helpers ──────────────────────────────────────────
|
||||
// Captures the rendered (marked.js) HTML from the AI bubble and shows a
|
||||
// preview before publishing. The actual /api/share POST + gallery insert
|
||||
// only fires when the user clicks Publish in the modal.
|
||||
var SHARE_CARD_CSS = \"*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}body{font-family:'IBM Plex Sans',system-ui,sans-serif;background:#FAFAF8;color:#0D0D14;padding:1.25rem .75rem;min-height:100vh}.chat-frame{background:#fff;border:1px solid rgba(0,0,0,.09);box-shadow:0 4px 32px rgba(0,0,0,.07),0 1px 4px rgba(0,0,0,.04);padding:1.25rem;display:flex;flex-direction:column;gap:1rem;max-width:560px;margin:0 auto}.chat-row-user{display:flex;flex-direction:row-reverse}.chat-row-ai{display:flex;flex-direction:row;align-items:flex-end;gap:.625rem}.bubble-user{background:#0052A0;color:#fff;border-radius:18px 18px 4px 18px;padding:11px 15px;max-width:78%;font-size:.875rem;line-height:1.55;word-break:break-word}.bubble-ai{background:#FAFAF8;color:#0D0D14;border:1px solid rgba(0,0,0,.07);border-radius:18px 18px 18px 4px;padding:11px 15px;max-width:88%;font-size:.875rem;font-weight:300;line-height:1.65;word-break:break-word;box-shadow:0 2px 6px rgba(0,0,0,.05)}.bubble-ai p{margin:0}.bubble-ai p+p{margin-top:.6rem}.bubble-ai ul,.bubble-ai ol{margin:.5rem 0 .5rem 1.25rem;padding:0}.bubble-ai li+li{margin-top:.25rem}.bubble-ai strong{font-weight:600}.bubble-ai em{font-style:italic}.bubble-ai code{font-family:'IBM Plex Mono','Menlo',monospace;font-size:.8rem;background:rgba(0,0,0,.05);padding:1px 4px;border-radius:3px}.bubble-ai pre{background:rgba(0,0,0,.05);padding:.75rem;border-radius:6px;overflow-x:auto;font-size:.8rem;margin:.5rem 0}.bubble-ai pre code{background:none;padding:0}.bubble-ai blockquote{border-left:3px solid rgba(0,82,160,.3);margin:.5rem 0;padding:.25rem 0 .25rem .75rem;color:#3A3A4A}.bubble-ai h1,.bubble-ai h2,.bubble-ai h3,.bubble-ai h4{font-weight:600;margin:.5rem 0 .25rem}.bubble-ai h1{font-size:1.05rem}.bubble-ai h2{font-size:1rem}.bubble-ai h3{font-size:.95rem}.bubble-ai h4{font-size:.9rem}.bubble-ai a{color:#0052A0;text-decoration:underline}.ai-col{display:flex;flex-direction:column;gap:.25rem}.ai-label{font-size:.6rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#0052A0}.avatar{width:26px;height:26px;border-radius:50%;flex-shrink:0;background:#fff;border:1px solid rgba(0,82,160,.15);display:flex;align-items:center;justify-content:center;font-size:.7rem;color:#0052A0;font-weight:600}\";
|
||||
function _esc(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"'); }
|
||||
function _buildPreviewSrcdoc(question, answerHtml) {
|
||||
return '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>' + SHARE_CARD_CSS + '</style></head><body><div class=\"chat-frame\"><div class=\"chat-row-user\"><div class=\"bubble-user\">' + _esc(question || '(no prior question)') + '</div></div><div class=\"chat-row-ai\"><div class=\"avatar\">N</div><div class=\"ai-col\"><span class=\"ai-label\">Neuron</span><div class=\"bubble-ai\">' + (answerHtml || '') + '</div></div></div></div></body></html>';
|
||||
}
|
||||
var _sharePending = null;
|
||||
function openSharePreview(question, answerHtml, answerPlain, originBtn) {
|
||||
_sharePending = { question: question, answerHtml: answerHtml, answerPlain: answerPlain, btn: originBtn };
|
||||
var modal = document.getElementById('neuron-share-preview-modal');
|
||||
var frame = document.getElementById('neuron-share-preview-frame');
|
||||
if (!modal || !frame) return;
|
||||
frame.srcdoc = _buildPreviewSrcdoc(question, answerHtml);
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
function closeSharePreview() {
|
||||
var modal = document.getElementById('neuron-share-preview-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
var frame = document.getElementById('neuron-share-preview-frame');
|
||||
if (frame) frame.srcdoc = '';
|
||||
_sharePending = null;
|
||||
}
|
||||
async function publishSharePreview() {
|
||||
if (!_sharePending) return;
|
||||
var pending = _sharePending;
|
||||
var publishBtn = document.getElementById('neuron-share-preview-publish');
|
||||
if (publishBtn) { publishBtn.disabled = true; publishBtn.textContent = 'Publishing...'; }
|
||||
if (pending.btn) pending.btn.style.opacity = '0.4';
|
||||
try {
|
||||
var r = await fetch('/api/share', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
question: pending.question,
|
||||
answer: pending.answerPlain,
|
||||
answer_html: pending.answerHtml,
|
||||
answer_plaintext: pending.answerPlain
|
||||
})
|
||||
});
|
||||
var d = await r.json();
|
||||
if (d && d.id) {
|
||||
window.open('/share/' + d.id, '_blank');
|
||||
}
|
||||
} catch(e) {}
|
||||
if (pending.btn) pending.btn.style.opacity = '1';
|
||||
if (publishBtn) { publishBtn.disabled = false; publishBtn.textContent = 'Publish to gallery'; }
|
||||
closeSharePreview();
|
||||
}
|
||||
// Wire modal buttons once DOM is ready. The modal lives outside the chat
|
||||
// panel so it works whether the panel is open or closed.
|
||||
function _wireShareModal() {
|
||||
var pubBtn = document.getElementById('neuron-share-preview-publish');
|
||||
var cnlBtn = document.getElementById('neuron-share-preview-cancel');
|
||||
var clsBtn = document.getElementById('neuron-share-preview-close');
|
||||
var modal = document.getElementById('neuron-share-preview-modal');
|
||||
if (pubBtn) pubBtn.addEventListener('click', publishSharePreview);
|
||||
if (cnlBtn) cnlBtn.addEventListener('click', closeSharePreview);
|
||||
if (clsBtn) clsBtn.addEventListener('click', closeSharePreview);
|
||||
if (modal) modal.addEventListener('click', function(ev) { if (ev.target === modal) closeSharePreview(); });
|
||||
}
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', _wireShareModal);
|
||||
else _wireShareModal();
|
||||
|
||||
// Persistent session storage - survives page refreshes
|
||||
function loadSession() {
|
||||
try {
|
||||
var s = localStorage.getItem('neuron_demo_session');
|
||||
return s ? JSON.parse(s) : { messages: [], count: 0, context: '' };
|
||||
} catch(e) { return { messages: [], count: 0, context: '' }; }
|
||||
}
|
||||
function saveSession(session) {
|
||||
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
|
||||
}
|
||||
function clearSession() {
|
||||
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
|
||||
}
|
||||
|
||||
function _mg(s) { return s._m || { nodes: [], edges: [] }; }
|
||||
|
||||
function _um(s, nn, ne) {
|
||||
if (!nn || !nn.length) return;
|
||||
var g = _mg(s), nm = {}, ek = function(e) { return e.from+'->'+e.to; }, em = {};
|
||||
g.nodes.forEach(function(n) { nm[n.id] = n; });
|
||||
(nn || []).forEach(function(n) {
|
||||
if (nm[n.id]) { nm[n.id].w = Math.min(1.0, (nm[n.id].w || 0.5) + 0.08); }
|
||||
else { nm[n.id] = n; }
|
||||
});
|
||||
g.nodes = Object.values(nm);
|
||||
g.edges.forEach(function(e) { em[ek(e)] = e; });
|
||||
(ne || []).forEach(function(e) {
|
||||
var k = ek(e);
|
||||
if (em[k]) { em[k].weight = Math.min(1.0, (em[k].weight || 0.5) + 0.05); }
|
||||
else { em[k] = e; }
|
||||
});
|
||||
g.edges = Object.values(em);
|
||||
s._m = g; saveSession(s);
|
||||
}
|
||||
|
||||
function _ra(g, q) {
|
||||
if (!g || !g.nodes || !g.nodes.length) return [];
|
||||
var words = q.toLowerCase().split(/\s+/).filter(function(w) { return w.length > 3; });
|
||||
var sc = {};
|
||||
g.nodes.forEach(function(n) {
|
||||
var t = (n.content || '').toLowerCase();
|
||||
sc[n.id] = words.filter(function(w) { return t.indexOf(w) !== -1; }).length * 0.6 + (n.w || 0.5) * 0.4;
|
||||
});
|
||||
(g.edges || []).forEach(function(e) {
|
||||
if (sc[e.from] > 0.1) sc[e.to] = (sc[e.to] || 0) + sc[e.from] * (e.weight || 0.5) * 0.4;
|
||||
});
|
||||
return g.nodes.filter(function(n) { return sc[n.id] > 0.2; })
|
||||
.sort(function(a,b) { return sc[b.id]-sc[a.id]; }).slice(0,5)
|
||||
.map(function(n) { return { id: n.id, content: n.content, score: sc[n.id] }; });
|
||||
}
|
||||
|
||||
// ?reset=1 clears the session and reloads clean
|
||||
if (window.location.search.indexOf('reset=1') !== -1) {
|
||||
clearSession();
|
||||
var clean = window.location.pathname;
|
||||
window.history.replaceState({}, '', clean);
|
||||
}
|
||||
|
||||
var session = loadSession();
|
||||
// Ensure every user has a stable unique session ID.
|
||||
if (!session.uid) {
|
||||
session.uid = 'u' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
||||
saveSession(session);
|
||||
}
|
||||
var msgCount = session.count || 0;
|
||||
|
||||
function updateCountdown() {
|
||||
var el = document.getElementById('neuron-demo-countdown');
|
||||
if (!el) return;
|
||||
var remaining = MAX - msgCount;
|
||||
el.textContent = remaining + ' question' + (remaining === 1 ? '' : 's') + ' left';
|
||||
el.style.color = '#ffffff';
|
||||
el.style.fontWeight = '700';
|
||||
}
|
||||
|
||||
window.neuronDemoReset = function() {
|
||||
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
|
||||
session = { messages: [], count: 0, context: '' };
|
||||
msgCount = 0;
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
if (msgs) msgs.innerHTML = '';
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
if (input) { input.disabled = false; input.placeholder = 'Ask me anything...'; }
|
||||
var btn = document.getElementById('neuron-demo-send');
|
||||
if (btn) btn.disabled = false;
|
||||
addMsg('ai', 'Hey. What is on your mind?', true);
|
||||
};
|
||||
|
||||
window.neuronDemoToggle = function() {
|
||||
isOpen = !isOpen;
|
||||
var panel = document.getElementById('neuron-demo-panel');
|
||||
if (panel) panel.style.display = isOpen ? 'flex' : 'none';
|
||||
var btn = document.getElementById('neuron-demo-btn');
|
||||
if (btn) btn.style.display = isOpen ? 'none' : '';
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
if (isOpen && turnstileVerified && msgs && msgs.style.display !== 'none' && msgs.children.length === 0) {
|
||||
if (session.messages && session.messages.length > 0) {
|
||||
session.messages.forEach(function(m) { addMsg(m.role, m.text, true); });
|
||||
var remaining = MAX - msgCount;
|
||||
if (remaining <= 0) {
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
if (input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
|
||||
}
|
||||
} else if (!session.greeted) {
|
||||
addMsg('ai', 'Hey. What is on your mind?', true);
|
||||
session.greeted = true;
|
||||
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
|
||||
}
|
||||
}
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
if (isOpen && input && !input.disabled) input.focus();
|
||||
updateCountdown();
|
||||
if (isOpen && !turnstileWidgetId && typeof turnstile !== 'undefined') {
|
||||
var container = document.getElementById('neuron-demo-turnstile');
|
||||
if (container) {
|
||||
turnstileWidgetId = turnstile.render(container, {
|
||||
sitekey: TURNSTILE_SITE_KEY,
|
||||
size: 'compact',
|
||||
callback: function(token) {
|
||||
turnstileToken = token;
|
||||
turnstileVerified = true;
|
||||
if (typeof turnstile !== 'undefined' && turnstileWidgetId !== null) {
|
||||
try { turnstile.remove(turnstileWidgetId); } catch(e) {}
|
||||
turnstileWidgetId = null;
|
||||
}
|
||||
var gate = document.getElementById('neuron-demo-gate');
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
var inputRow = document.getElementById('neuron-demo-input-row');
|
||||
if (gate) gate.style.display = 'none';
|
||||
if (msgs) msgs.style.display = 'flex';
|
||||
if (inputRow) inputRow.style.display = 'flex';
|
||||
addMsg('ai', 'Hey. What is on your mind?', true);
|
||||
updateCountdown();
|
||||
var inp = document.getElementById('neuron-demo-text');
|
||||
if (inp) inp.focus();
|
||||
},
|
||||
'expired-callback': function() {
|
||||
turnstileToken = '';
|
||||
turnstileVerified = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function addMsg(role, text, skipSave) {
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
if (!msgs) return null;
|
||||
var el = document.createElement('div');
|
||||
el.className = 'demo-msg demo-msg-' + role;
|
||||
var avatar = document.createElement('div');
|
||||
avatar.className = 'demo-msg-avatar';
|
||||
if (role === 'ai') {
|
||||
var img = document.createElement('img');
|
||||
img.src = '/assets/brand/neuron-brain.png';
|
||||
img.alt = 'Neuron';
|
||||
avatar.appendChild(img);
|
||||
} else {
|
||||
var svgNS = 'http://www.w3.org/2000/svg';
|
||||
var svg = document.createElementNS(svgNS, 'svg');
|
||||
svg.setAttribute('width', '14'); svg.setAttribute('height', '14');
|
||||
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('fill', 'none');
|
||||
svg.setAttribute('stroke', 'currentColor'); svg.setAttribute('stroke-width', '2');
|
||||
var p1 = document.createElementNS(svgNS, 'path');
|
||||
p1.setAttribute('d', 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2');
|
||||
var c1 = document.createElementNS(svgNS, 'circle');
|
||||
c1.setAttribute('cx', '12'); c1.setAttribute('cy', '7'); c1.setAttribute('r', '4');
|
||||
svg.appendChild(p1); svg.appendChild(c1);
|
||||
avatar.appendChild(svg);
|
||||
}
|
||||
var bubble = document.createElement('div');
|
||||
bubble.className = 'demo-msg-bubble';
|
||||
if (role === 'ai' && typeof marked !== 'undefined') {
|
||||
try { bubble.innerHTML = marked.parse(text); } catch(e) { bubble.textContent = text; }
|
||||
} else {
|
||||
bubble.textContent = text;
|
||||
}
|
||||
if (role === 'ai') {
|
||||
var bodyWrap = document.createElement('div');
|
||||
bodyWrap.className = 'demo-msg-ai-body';
|
||||
bodyWrap.appendChild(bubble);
|
||||
if (!skipSave) {
|
||||
var shareBtn = document.createElement('button');
|
||||
shareBtn.className = 'demo-share-pill';
|
||||
shareBtn.title = 'Share this response';
|
||||
shareBtn.textContent = 'Share ↗';
|
||||
// Capture rendered HTML on click; preview before publish.
|
||||
shareBtn.onclick = function() {
|
||||
var prevUser = '';
|
||||
if (session.messages) {
|
||||
for (var i = session.messages.length - 1; i >= 0; i--) {
|
||||
if (session.messages[i].role === 'user') { prevUser = session.messages[i].text; break; }
|
||||
}
|
||||
}
|
||||
var answerHtml = bubble.innerHTML;
|
||||
var answerPlain = text;
|
||||
openSharePreview(prevUser, answerHtml, answerPlain, shareBtn);
|
||||
};
|
||||
bodyWrap.appendChild(shareBtn);
|
||||
}
|
||||
el.appendChild(avatar);
|
||||
el.appendChild(bodyWrap);
|
||||
} else {
|
||||
el.appendChild(avatar);
|
||||
el.appendChild(bubble);
|
||||
}
|
||||
msgs.appendChild(el);
|
||||
msgs.scrollTop = msgs.scrollHeight;
|
||||
if (!skipSave && role !== 'thinking') {
|
||||
session.messages = session.messages || [];
|
||||
session.messages.push({ role: role, text: text });
|
||||
if (session.messages.length > 40) session.messages = session.messages.slice(-40);
|
||||
saveSession(session);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
window.neuronDemoSend = async function() {
|
||||
if (msgCount >= MAX) return;
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
var btn = document.getElementById('neuron-demo-send');
|
||||
if (!input || btn.disabled) return;
|
||||
var msg = input.value.trim();
|
||||
if (!msg) return;
|
||||
input.value = '';
|
||||
btn.disabled = true;
|
||||
addMsg('user', msg);
|
||||
var thinking = document.createElement('div');
|
||||
thinking.className = 'demo-msg demo-msg-thinking';
|
||||
var thAvatar = document.createElement('div');
|
||||
thAvatar.className = 'demo-msg-avatar';
|
||||
var thImg = document.createElement('img');
|
||||
thImg.src = '/assets/brand/neuron-brain.png';
|
||||
thImg.alt = 'Neuron';
|
||||
thAvatar.appendChild(thImg);
|
||||
thinking.appendChild(thAvatar);
|
||||
var thDots = document.createElement('span');
|
||||
thDots.className = 'demo-msg-thinking-dots';
|
||||
thDots.innerHTML = '<span></span><span></span><span></span>';
|
||||
thinking.appendChild(thDots);
|
||||
var thMsgsEl = document.getElementById('neuron-demo-messages');
|
||||
if (thMsgsEl) {
|
||||
thMsgsEl.appendChild(thinking);
|
||||
thMsgsEl.scrollTop = thMsgsEl.scrollHeight;
|
||||
}
|
||||
if (turnstileVerified && !session._cfSent) { session._cfSent = true; }
|
||||
try {
|
||||
var hist = (session.messages || []).slice(-20).filter(function(m){ return m.role !== 'thinking'; }).map(function(m){
|
||||
return {role: m.role === 'ai' ? 'assistant' : 'user', content: m.text};
|
||||
});
|
||||
var activated_nodes = _ra(session._m, msg);
|
||||
var questionsRemaining = (MAX - msgCount) - 1;
|
||||
if (questionsRemaining < 0) questionsRemaining = 0;
|
||||
var r = await fetch('/api/demo', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
message: msg,
|
||||
history: hist,
|
||||
cf_token: turnstileVerified && !session._cfSent ? turnstileToken : '',
|
||||
uid: session.uid || '',
|
||||
activated_nodes: activated_nodes,
|
||||
engram_node_count: (session._m && session._m.nodes) ? session._m.nodes.length : 0,
|
||||
questions_remaining: questionsRemaining,
|
||||
is_last_question: questionsRemaining === 0
|
||||
})
|
||||
});
|
||||
var d = await r.json();
|
||||
if (thinking) thinking.remove();
|
||||
_um(session, d.sn, d.se);
|
||||
var reply = d.response || d.reply || d.message || '';
|
||||
var isError = !reply || reply === 'Stepped out for a moment. Try again.';
|
||||
if (!isError) {
|
||||
msgCount++;
|
||||
session.count = msgCount;
|
||||
saveSession(session);
|
||||
updateCountdown();
|
||||
if (msgCount >= MAX && input) {
|
||||
input.disabled = true;
|
||||
input.placeholder = 'Interaction limit reached';
|
||||
}
|
||||
}
|
||||
addMsg('ai', reply || 'Stepped out for a moment. Try again.');
|
||||
} catch(e) {
|
||||
if (thinking) thinking.remove();
|
||||
addMsg('ai', 'Stepped out for a moment. Try again.');
|
||||
}
|
||||
if (msgCount < MAX && btn) btn.disabled = false;
|
||||
if (input) input.focus();
|
||||
};
|
||||
|
||||
var inp = document.getElementById('neuron-demo-text');
|
||||
if (inp) {
|
||||
inp.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); window.neuronDemoSend(); }
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
|
||||
# Replace the line `<script src=\"/assets/js/fc247ef45b1d.js\" defer></script>`
|
||||
# with our new inline content + script. extract-js will then re-extract this
|
||||
# fresh inline block to a content-hashed asset on the next build.
|
||||
|
||||
OLD_LINE = '<script src=\\"/assets/js/fc247ef45b1d.js\\" defer></script>'
|
||||
|
||||
|
||||
def main():
|
||||
src = STYLES_EL.read_text(encoding="utf-8")
|
||||
if MARKER in src:
|
||||
print("styles.el already contains the share preview modal - skipping")
|
||||
return
|
||||
if OLD_LINE not in src:
|
||||
# Maybe extract-js already pulled out a different hash; fail loud.
|
||||
print("ERROR: anchor `<script src=...fc247ef45b1d.js...>` not found in styles.el")
|
||||
print(" Either it was renamed in a prior commit, or the file is already patched.")
|
||||
raise SystemExit(1)
|
||||
new_src = src.replace(OLD_LINE, CHAT_HTML_AND_JS.strip(), 1)
|
||||
STYLES_EL.write_text(new_src, encoding="utf-8")
|
||||
print("styles.el patched: chat-widget JS re-inlined with share preview modal")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user