From 0508cd77fd97a383c64685ce4bf7d779166beea1 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 4 May 2026 01:57:38 -0500 Subject: [PATCH] fix(chat): raise history cap and add 30s frontend timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The demo chat was silently dropping conversation context past 40 turns and leaving the thinking bubble spinning forever when the soul backend hung — visitors saw a frozen UI with no way to know what went wrong. Changes: - Stored history cap raised from 40 → 200 messages so longer conversations actually persist across page refreshes. - History sent to backend per turn raised from 20 → 50 messages. - 30s AbortController timeout on the /api/demo fetch — surfaces a distinct "Took too long to respond" error instead of hanging. - Restore script (restore-chat-js-with-preview.py) is now correctly idempotent in both directions: detects when modal HTML is inlined but chat JS got extracted to an asset, and re-injects fresh source so extract-js picks up changes on the next build. --- scripts/restore-chat-js-with-preview.py | 86 +++-- src/styles.el | 400 +++++++++++++++++++++++- 2 files changed, 463 insertions(+), 23 deletions(-) diff --git a/scripts/restore-chat-js-with-preview.py b/scripts/restore-chat-js-with-preview.py index 9709d70..af0f26c 100644 --- a/scripts/restore-chat-js-with-preview.py +++ b/scripts/restore-chat-js-with-preview.py @@ -352,7 +352,7 @@ CHAT_HTML_AND_JS = r""" 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); + if (session.messages.length > 200) session.messages = session.messages.slice(-200); saveSession(session); } return el; @@ -388,26 +388,36 @@ CHAT_HTML_AND_JS = r""" } if (turnstileVerified && !session._cfSent) { session._cfSent = true; } try { - var hist = (session.messages || []).slice(-20).filter(function(m){ return m.role !== 'thinking'; }).map(function(m){ + var hist = (session.messages || []).slice(-50).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 - }) - }); + // 30s frontend timeout — surfaces a real error if the soul hangs + // instead of leaving the thinking bubble spinning forever. + var ctrl = new AbortController(); + var timeoutId = setTimeout(function() { ctrl.abort(); }, 30000); + var r; + try { + r = await fetch('/api/demo', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + signal: ctrl.signal, + 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 + }) + }); + } finally { + clearTimeout(timeoutId); + } var d = await r.json(); if (thinking) thinking.remove(); _um(session, d.sn, d.se); @@ -426,7 +436,10 @@ CHAT_HTML_AND_JS = r""" 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.'); + var msg = (e && e.name === 'AbortError') + ? 'Took too long to respond — try again.' + : 'Stepped out for a moment. Try again.'; + addMsg('ai', msg); } if (msgCount < MAX && btn) btn.disabled = false; if (input) input.focus(); @@ -451,12 +464,41 @@ OLD_LINE = '' def main(): src = STYLES_EL.read_text(encoding="utf-8") - if MARKER in src: - print("styles.el already contains the share preview modal - skipping") + # If the anchor `