Fix duplicate messages after entering a running chain

When the chat is opened while a chain is already streaming, the recovery
poll (_streamingPollInterval) is started. Its forced pollHistory(true)
renders the DB messages while the live SSE bubbles render them too — the
live bubbles never advance lastKnownMessageId, so the poll re-fetches the
same assistant message and renders a second bubble.

Make live SSE and the recovery poll mutually exclusive:
- Stop a lingering recovery poll at the start of a real live stream so the
  SSE renders alone.
- Re-ensure the regular (isStreaming-guarded) history polling idempotently
  in the finally block, so it is not left off after entering a running chain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MelekiandClaude Opus 4.8 committed 2026-06-02 08:32:51 +02:00
1 parent f2fd493e5f
commit cf4238859e
1 file changed
+14
+14
View File
@@ -279,6 +279,15 @@ async function _runStream(fetchPromise) {
stopBtn.hidden = false;
isStreaming = true;
_abortController = new AbortController();
// Eine Live-SSE-Antwort rendert ALLEIN. Einen evtl. noch laufenden Recovery-Poll
// (_streamingPollInterval – gestartet beim Betreten des Chats WAEHREND eine Chain
// lief) hier stoppen. Sonst rendert dessen pollHistory(true) dieselben DB-Nachrichten
// ein zweites Mal NEBEN den Live-Bubbles (die lastKnownMessageId nicht vorruecken)
// => Doppel-Nachrichten nach Eintritt in eine laufende Chain.
if (_streamingPollInterval) {
clearInterval(_streamingPollInterval);
_streamingPollInterval = null;
}
_currentToolBox = null;
_chatAbandoned = false;
_stopClickCount = 0;
@@ -345,6 +354,11 @@ async function _runStream(fetchPromise) {
stopBtn.title = 'Antwort abbrechen';
isStreaming = false;
_abortController = null;
// Regulaeres History-Polling idempotent wieder sicherstellen: Falls beim Eintritt
// in eine laufende Chain auf den Recovery-Poll umgeschaltet (und History-Polling
// gestoppt) wurde, ist es nach dem Live-Stream sonst aus. startHistoryPolling()
// hat selbst einen Guard (if (historyPollInterval) return) => kein Doppel-Intervall.
startHistoryPolling();
// Steering-Hinweis zurücksetzen (Eingabe ist wieder normales Senden)
if (_steerInputEl) {
_steerInputEl.placeholder = _prevPlaceholder;