From 7c0e5d180a6e8ca9b810cf7eccd81e3b0e67ca5f Mon Sep 17 00:00:00 2001 From: Meleki Date: Sun, 1 Mar 2026 00:32:05 +0100 Subject: [PATCH] fix: Streaming-Navigation-Bugs (doppelte Messages, client_disconnected), Tool-Result ohne Truncation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _chatAbandoned-Flag statt AbortController: Backend-Stream läuft bei Navigation weiter, Frontend ignoriert Events - stopHistoryPolling() während Streaming-Poll aktiv, verhindert parallele Polls - _pollLock in pollHistory() gegen Race-Conditions bei gleichzeitigen Poll-Aufrufen - Tool-Result truncate(300) im Live-Chat entfernt, in History beibehalten - Dashboard Badge-Styling verfeinert --- wwwroot/css/dashboard.css | 6 ++++-- wwwroot/js/chat.js | 9 ++++++++- wwwroot/js/history.js | 8 +++++++- wwwroot/js/pages/chat-page.js | 8 ++++++++ wwwroot/js/ui.js | 6 +++--- 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/wwwroot/css/dashboard.css b/wwwroot/css/dashboard.css index 0636e28..2cff58b 100644 --- a/wwwroot/css/dashboard.css +++ b/wwwroot/css/dashboard.css @@ -75,10 +75,12 @@ .badge { padding: var(--badge-padding); - background: var(--f1-bg); - border: var(--border-width) solid var(--f1-border); + background: var(--f2-bg); + border: var(--border-width) solid var(--f2-border); white-space: nowrap; flex-shrink: 0; + font-size: var(--font-size); + border-radius: var(--border-radius); } #newAgentCard, diff --git a/wwwroot/js/chat.js b/wwwroot/js/chat.js index e889887..034f660 100644 --- a/wwwroot/js/chat.js +++ b/wwwroot/js/chat.js @@ -33,6 +33,7 @@ let isStreaming = false; let pendingFiles = []; // [{ name, type, base64 }] let pendingRefs = []; // [{ path, from, to }] - Workspace-Dateireferenzen let _lastDoneChainData = null; // Chain-Daten vom letzten done-Event +let _chatAbandoned = false; // true wenn User weg-navigiert während Stream läuft async function clearChat() { const archive = await apiArchive(getCurrentSessionId()); @@ -102,6 +103,8 @@ async function sendMessage() { let fullText = ''; let currentToolBox = null; + _chatAbandoned = false; + try { const resp = await apiChat(messages, getCurrentSessionId()); const reader = resp.body.getReader(); @@ -111,7 +114,7 @@ async function sendMessage() { while (true) { const { done, value } = await reader.read(); - if (done) break; + if (done || _chatAbandoned) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); @@ -184,6 +187,8 @@ async function sendMessage() { } } catch (e) { + // Bei Navigation nichts tun – der neue Page-Init übernimmt + if (_chatAbandoned) return; endStreamBubble(); // Prüfen ob Backend noch streamt (z.B. bei Netzwerkfehler) try { @@ -197,6 +202,8 @@ async function sendMessage() { } catch (_) {} addErrorBubble('Netzwerk-Fehler: ' + e.message); } finally { + // Bei Navigation nichts aufräumen – der neue Page-Init übernimmt + if (_chatAbandoned) return; if (!isStreaming || !_streamingPollInterval) { btn.disabled = false; isStreaming = false; diff --git a/wwwroot/js/history.js b/wwwroot/js/history.js index d1690c2..6f1297a 100644 --- a/wwwroot/js/history.js +++ b/wwwroot/js/history.js @@ -143,7 +143,7 @@ async function loadLatestArchive() { if (lastToolBox && !lastToolBox.querySelector('.tool-result')) { const rd = document.createElement('div'); rd.className = 'tool-result' + (msg.isError ? ' tool-error' : ''); - rd.textContent = truncate(msg.content, 300); + rd.textContent = truncate(msg.content || '', 300); lastToolBox.appendChild(rd); } continue; // kein separates div @@ -183,6 +183,8 @@ async function loadLatestArchive() { // ── Polling ── +let _pollLock = false; + function startHistoryPolling(intervalMs = 3000) { if (historyPollInterval) return; historyPollInterval = setInterval(pollHistory, intervalMs); @@ -197,6 +199,8 @@ function stopHistoryPolling() { async function pollHistory(force = false) { if (isStreaming && !force) return; + if (_pollLock) return; + _pollLock = true; try { const newMessages = await apiHistoryPoll(getCurrentSessionId(), lastKnownMessageId); @@ -206,5 +210,7 @@ async function pollHistory(force = false) { } } catch (e) { console.error('History poll fehlgeschlagen:', e); + } finally { + _pollLock = false; } } diff --git a/wwwroot/js/pages/chat-page.js b/wwwroot/js/pages/chat-page.js index fb2d06e..2ab2550 100644 --- a/wwwroot/js/pages/chat-page.js +++ b/wwwroot/js/pages/chat-page.js @@ -5,6 +5,8 @@ let _recoveryChainId = null; function startStreamingPoll(sessionId, sendBtn, chainId) { _recoveryChainId = chainId; + // History-Polling stoppen – Streaming-Poll übernimmt + stopHistoryPolling(); if (_streamingPollInterval) clearInterval(_streamingPollInterval); _streamingPollInterval = setInterval(async () => { try { @@ -29,6 +31,9 @@ function startStreamingPoll(sessionId, sendBtn, chainId) { } setMeta('Bereit'); _recoveryChainId = null; + + // History-Polling wieder starten + startHistoryPolling(); } } catch (e) { console.error('Streaming-Poll fehlgeschlagen:', e); @@ -274,6 +279,9 @@ registerPage('chat', { }, destroy() { + // Stream nicht abbrechen (Backend soll weiterlaufen), + // aber Frontend-Processing stoppen + _chatAbandoned = true; if (this._closeMenu) { document.removeEventListener('click', this._closeMenu); this._closeMenu = null; diff --git a/wwwroot/js/ui.js b/wwwroot/js/ui.js index 9c854d1..b7f38cc 100644 --- a/wwwroot/js/ui.js +++ b/wwwroot/js/ui.js @@ -238,7 +238,7 @@ function renderToolBoxContent(div, name, inputJson, result, isError) { const resultDiv = document.createElement('div'); resultDiv.className = 'tool-result' + (isError ? ' tool-error' : ''); - resultDiv.textContent = result ? truncate(result, 300) : ''; + resultDiv.textContent = result || ''; resultDiv.hidden = true; div.appendChild(resultDiv); foldTarget = resultDiv; @@ -340,12 +340,12 @@ function appendToolResult(result, isError) { const existing = lastBox.querySelector('.tool-result'); if (existing) { - existing.textContent = truncate(result, 300); + existing.textContent = result || ''; if (isError) existing.classList.add('tool-error'); } else { const resultDiv = document.createElement('div'); resultDiv.className = 'tool-result' + (isError ? ' tool-error' : ''); - resultDiv.textContent = truncate(result, 300); + resultDiv.textContent = result || ''; resultDiv.hidden = true; lastBox.appendChild(resultDiv); }