diff --git a/docs/global-prompt-administration.md b/docs/global-prompt-administration.md index 44548ca3..5f1293d0 100644 --- a/docs/global-prompt-administration.md +++ b/docs/global-prompt-administration.md @@ -3,14 +3,12 @@ `CLINICAL_ASSISTANT_CONVERSATION_CHARS` is the sole conversation budget source. Missing/empty uses 120000; nonempty values must validate as an integer from 1000 through 1000000. Invalid configuration returns 503 before query rewrite, -retrieval, handoff generation or chat provider calls. Counting is exactly JavaScript string length (UTF-16 code units), including all history plus draft, not model tokens. The old `clinical_assistant.conversation_chars` database value is ignored and its generic config PUT is rejected. Status retains `conversationChars`/`conversationUnit` and adds `conversationEnv`, `conversationSource`, `conversationMeasure`. Admin config exposes the same metadata as top-level `conversationBudget: {limit,unit,measure,env,source}`. -Full-history storage, 8 MiB saves and explicit successful-only handoffs are unchanged. ## Catalogue and runtime use diff --git a/public/js/assistant/citations.js b/public/js/assistant/citations.js index 5bacb0f6..6bc14b65 100644 --- a/public/js/assistant/citations.js +++ b/public/js/assistant/citations.js @@ -13,7 +13,8 @@ export function safeImageUrl(src) { var base = (typeof window !== 'undefined' && window.location && window.location.href) || 'https://synthetic.invalid/'; var url = new URL(s, base); if (url.protocol === 'http:' || url.protocol === 'https:') return url.origin === new URL(base).origin ? s : ''; - return s; // relative paths resolve to our own origin by construction + if (url.protocol === 'file:' || url.protocol === 'javascript:' || url.protocol === 'data:') return ''; + return url.origin === new URL(base).origin ? s : ''; // relative paths resolve to our own origin only } catch (e) { return ''; } @@ -398,8 +399,8 @@ export function renderLatexText(text, katex, hold) { if (dollars || brackets) return render(raw, dollars || brackets, display); if (inline !== undefined) return render(raw, inline, false); if (parens !== undefined) return render(raw, parens, false); - if (ce !== undefined) return render(raw, ce, false); - if (pu !== undefined) return render(raw, pu, false); + if (ce !== undefined) return render(raw, '\\ce{' + ce + '}', false); + if (pu !== undefined) return render(raw, '\\pu{' + pu + '}', false); return raw; }); } diff --git a/public/js/clinicalAssistant.js b/public/js/clinicalAssistant.js index 776f0be4..573968bc 100644 --- a/public/js/clinicalAssistant.js +++ b/public/js/clinicalAssistant.js @@ -939,7 +939,7 @@ import { if (typeof showToast === 'function') showToast('No question precedes this answer to regenerate.', 'error'); return; } - messages = messages.slice(0, index); + messages = messages.slice(0, index - 1); // drop the answer AND its question; the question is replayed as the new message if (row && row.parentNode) row.remove(); var input = document.getElementById('assistant-input'); if (input) input.value = question.content; diff --git a/test/assistant-math-mhchem.test.js b/test/assistant-math-mhchem.test.js index 43a3b922..3897b19b 100644 --- a/test/assistant-math-mhchem.test.js +++ b/test/assistant-math-mhchem.test.js @@ -23,9 +23,9 @@ test('mhchem inline delimiters render as KaTeX without eating ordinary backslash } }; const html = renderAssistantMarkdown('Base deficit \\ce{HCO3-} and \\pu{37 C}; a plain \\ce without braces stays text.', [], { katex }); - assert.deepEqual(expressions, [{ expr: 'HCO3-', display: false }, { expr: '37 C', display: false }]); - assert.match(html, /HCO3-<\/span>/); - assert.match(html, /37 C<\/span>/); + assert.deepEqual(expressions, [{ expr: '\\ce{HCO3-}', display: false }, { expr: '\\pu{37 C}', display: false }]); + assert.match(html, /\\ce{HCO3-}<\/span>/); + assert.match(html, /\\pu{37 C}<\/span>/); assert.match(html, /without braces stays text/); }); diff --git a/test/assistant-message-actions.test.js b/test/assistant-message-actions.test.js index 953e9179..3f0d566f 100644 --- a/test/assistant-message-actions.test.js +++ b/test/assistant-message-actions.test.js @@ -77,11 +77,12 @@ test('regenerate replays the preceding question for the latest answer and never secondRow.querySelector('[data-assistant-msg-regenerate]').click(); assert.equal(app.document.getElementById('assistant-input').value, 'Second question', 'input refilled with the question'); await new Promise(r => setImmediate(r)); await new Promise(r => setImmediate(r)); - assert.equal(c.messages.length, 4, 'old answer replaced by the regenerated one'); - assert.equal(c.messages[3].content, 'Regenerated answer.'); + assert.equal(c.messages.length, 3, 'the replayed question is sent as the new message, not duplicated in history'); + assert.equal(c.messages[2].content, 'Regenerated answer.'); assert.equal(c.messages[0].content, 'First question', 'earlier turns untouched'); assert.equal(c.messages[1].content, 'First answer.'); - assert.match(rows(app)[3].textContent, /Regenerated answer/); + const all = rows(app); + assert.match(all[all.length - 1].textContent, /Regenerated answer/); }); test('regenerate on a non-latest answer refuses honestly without touching the chat', t => {