From bf1f8339b86fe66d83581021e8f5cef4ff69efd0 Mon Sep 17 00:00:00 2001 From: b3nw Date: Sun, 28 Jun 2026 17:33:11 +0000 Subject: [PATCH] fix(ui): use absolute keep_count for edit and regenerate truncate Refs nesquena/hermes-webui#5096 Co-authored-by: b3nw --- static/ui.js | 30 +++++++------ ...sue_edit_regenerate_absolute_keep_count.py | 42 +++++++++++++++++++ 2 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 tests/test_issue_edit_regenerate_absolute_keep_count.py diff --git a/static/ui.js b/static/ui.js index 2d8cc95f..3a7400ae 100644 --- a/static/ui.js +++ b/static/ui.js @@ -14086,16 +14086,19 @@ function autoResizeTextarea(ta) { async function submitEdit(msgIdx, newText) { if(!S.session || S.busy) return; - // Truncate session at msgIdx (keep messages before the edited one) - // then re-send the edited text + const initialSid = S.session.session_id; + const absoluteKeepCount = _oldestIdx + msgIdx; + if(typeof _ensureAllMessagesLoaded==='function'){ + await _ensureAllMessagesLoaded(); + } + if(!S.session || S.session.session_id !== initialSid) return; try { await api('/api/session/truncate', {method:'POST', body:JSON.stringify({ - session_id: S.session.session_id, - keep_count: msgIdx // keep messages[0..msgIdx-1], discard from msgIdx onward + session_id: initialSid, + keep_count: absoluteKeepCount })}); - S.messages = S.messages.slice(0, msgIdx); + S.messages = S.messages.slice(0, absoluteKeepCount); renderMessages(); - // Now send the edited message as a new chat $('msg').value = newText; await send(); } catch(e) { setStatus(t('edit_failed') + e.message); } @@ -14103,24 +14106,27 @@ async function submitEdit(msgIdx, newText) { async function regenerateResponse(btn) { if(!S.session || S.busy) return; - // Find the last user message and re-run it - // Remove the last assistant message first (truncate to before it) const row = btn.closest('[data-msg-idx]'); if(!row) return; const assistantIdx = parseInt(row.dataset.msgIdx, 10); - // Find the last user message text (one before this assistant message) + const absoluteKeepCount = _oldestIdx + assistantIdx; + const initialSid = S.session.session_id; let lastUserText = ''; for(let i = assistantIdx - 1; i >= 0; i--) { const m = S.messages[i]; if(m && m.role === 'user') { lastUserText = msgContent(m); break; } } if(!lastUserText) return; + if(typeof _ensureAllMessagesLoaded==='function'){ + await _ensureAllMessagesLoaded(); + } + if(!S.session || S.session.session_id !== initialSid) return; try { await api('/api/session/truncate', {method:'POST', body:JSON.stringify({ - session_id: S.session.session_id, - keep_count: assistantIdx // remove the assistant message + session_id: initialSid, + keep_count: absoluteKeepCount })}); - S.messages = S.messages.slice(0, assistantIdx); + S.messages = S.messages.slice(0, absoluteKeepCount); renderMessages(); $('msg').value = lastUserText; await send(); diff --git a/tests/test_issue_edit_regenerate_absolute_keep_count.py b/tests/test_issue_edit_regenerate_absolute_keep_count.py new file mode 100644 index 00000000..b871c9f9 --- /dev/null +++ b/tests/test_issue_edit_regenerate_absolute_keep_count.py @@ -0,0 +1,42 @@ +"""Regression: edit/regenerate use absolute keep_count (#2184 pattern).""" + +import re +from pathlib import Path + +REPO = Path(__file__).resolve().parents[1] +UI_JS = (REPO / "static" / "ui.js").read_text(encoding="utf-8") + + +def _function_body(src: str, name: str) -> str: + needle_async = f"async function {name}" + start = src.index(needle_async) + brace = src.index("{", start) + depth = 0 + for i in range(brace, len(src)): + if src[i] == "{": + depth += 1 + elif src[i] == "}": + depth -= 1 + if depth == 0: + return src[start : i + 1] + raise AssertionError(f"function {name!r} body not found") + + +def test_submit_edit_uses_absolute_keep_count(): + body = _function_body(UI_JS, "submitEdit") + assert re.search(r"absoluteKeepCount\s*=\s*_oldestIdx\s*\+\s*msgIdx", body) + assert "keep_count: absoluteKeepCount" in body + + +def test_regenerate_uses_absolute_keep_count(): + body = _function_body(UI_JS, "regenerateResponse") + assert re.search(r"absoluteKeepCount\s*=\s*_oldestIdx\s*\+\s*assistantIdx", body) + assert "keep_count: absoluteKeepCount" in body + + +def test_submit_edit_captures_absolute_before_await(): + body = _function_body(UI_JS, "submitEdit") + cap = re.search(r"absoluteKeepCount\s*=\s*_oldestIdx\s*\+\s*msgIdx", body) + assert cap + first_await = re.search(r"\bawait\b", body) + assert first_await and cap.start() < first_await.start() \ No newline at end of file