fix: Streaming-Navigation-Bugs (doppelte Messages, client_disconnected), Tool-Result ohne Truncation

- _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
This commit is contained in:
Meleki committed 2026-03-01 00:32:05 +01:00
1 parent 6f800e2d50
commit 7c0e5d180a
5 files changed
+30 -7

No files matched your search

+4 -2
View File
@@ -75,10 +75,12 @@
.badge { .badge {
padding: var(--badge-padding); padding: var(--badge-padding);
background: var(--f1-bg); background: var(--f2-bg);
border: var(--border-width) solid var(--f1-border); border: var(--border-width) solid var(--f2-border);
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
font-size: var(--font-size);
border-radius: var(--border-radius);
} }
#newAgentCard, #newAgentCard,
+8 -1
View File
@@ -33,6 +33,7 @@ let isStreaming = false;
let pendingFiles = []; // [{ name, type, base64 }] let pendingFiles = []; // [{ name, type, base64 }]
let pendingRefs = []; // [{ path, from, to }] - Workspace-Dateireferenzen let pendingRefs = []; // [{ path, from, to }] - Workspace-Dateireferenzen
let _lastDoneChainData = null; // Chain-Daten vom letzten done-Event 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() { async function clearChat() {
const archive = await apiArchive(getCurrentSessionId()); const archive = await apiArchive(getCurrentSessionId());
@@ -102,6 +103,8 @@ async function sendMessage() {
let fullText = ''; let fullText = '';
let currentToolBox = null; let currentToolBox = null;
_chatAbandoned = false;
try { try {
const resp = await apiChat(messages, getCurrentSessionId()); const resp = await apiChat(messages, getCurrentSessionId());
const reader = resp.body.getReader(); const reader = resp.body.getReader();
@@ -111,7 +114,7 @@ async function sendMessage() {
while (true) { while (true) {
const { done, value } = await reader.read(); const { done, value } = await reader.read();
if (done) break; if (done || _chatAbandoned) break;
buffer += decoder.decode(value, { stream: true }); buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n'); const lines = buffer.split('\n');
@@ -184,6 +187,8 @@ async function sendMessage() {
} }
} catch (e) { } catch (e) {
// Bei Navigation nichts tun – der neue Page-Init übernimmt
if (_chatAbandoned) return;
endStreamBubble(); endStreamBubble();
// Prüfen ob Backend noch streamt (z.B. bei Netzwerkfehler) // Prüfen ob Backend noch streamt (z.B. bei Netzwerkfehler)
try { try {
@@ -197,6 +202,8 @@ async function sendMessage() {
} catch (_) {} } catch (_) {}
addErrorBubble('Netzwerk-Fehler: ' + e.message); addErrorBubble('Netzwerk-Fehler: ' + e.message);
} finally { } finally {
// Bei Navigation nichts aufräumen – der neue Page-Init übernimmt
if (_chatAbandoned) return;
if (!isStreaming || !_streamingPollInterval) { if (!isStreaming || !_streamingPollInterval) {
btn.disabled = false; btn.disabled = false;
isStreaming = false; isStreaming = false;
+7 -1
View File
@@ -143,7 +143,7 @@ async function loadLatestArchive() {
if (lastToolBox && !lastToolBox.querySelector('.tool-result')) { if (lastToolBox && !lastToolBox.querySelector('.tool-result')) {
const rd = document.createElement('div'); const rd = document.createElement('div');
rd.className = 'tool-result' + (msg.isError ? ' tool-error' : ''); rd.className = 'tool-result' + (msg.isError ? ' tool-error' : '');
rd.textContent = truncate(msg.content, 300); rd.textContent = truncate(msg.content || '', 300);
lastToolBox.appendChild(rd); lastToolBox.appendChild(rd);
} }
continue; // kein separates div continue; // kein separates div
@@ -183,6 +183,8 @@ async function loadLatestArchive() {
// ── Polling ── // ── Polling ──
let _pollLock = false;
function startHistoryPolling(intervalMs = 3000) { function startHistoryPolling(intervalMs = 3000) {
if (historyPollInterval) return; if (historyPollInterval) return;
historyPollInterval = setInterval(pollHistory, intervalMs); historyPollInterval = setInterval(pollHistory, intervalMs);
@@ -197,6 +199,8 @@ function stopHistoryPolling() {
async function pollHistory(force = false) { async function pollHistory(force = false) {
if (isStreaming && !force) return; if (isStreaming && !force) return;
if (_pollLock) return;
_pollLock = true;
try { try {
const newMessages = await apiHistoryPoll(getCurrentSessionId(), lastKnownMessageId); const newMessages = await apiHistoryPoll(getCurrentSessionId(), lastKnownMessageId);
@@ -206,5 +210,7 @@ async function pollHistory(force = false) {
} }
} catch (e) { } catch (e) {
console.error('History poll fehlgeschlagen:', e); console.error('History poll fehlgeschlagen:', e);
} finally {
_pollLock = false;
} }
} }
+8
View File
@@ -5,6 +5,8 @@ let _recoveryChainId = null;
function startStreamingPoll(sessionId, sendBtn, chainId) { function startStreamingPoll(sessionId, sendBtn, chainId) {
_recoveryChainId = chainId; _recoveryChainId = chainId;
// History-Polling stoppen – Streaming-Poll übernimmt
stopHistoryPolling();
if (_streamingPollInterval) clearInterval(_streamingPollInterval); if (_streamingPollInterval) clearInterval(_streamingPollInterval);
_streamingPollInterval = setInterval(async () => { _streamingPollInterval = setInterval(async () => {
try { try {
@@ -29,6 +31,9 @@ function startStreamingPoll(sessionId, sendBtn, chainId) {
} }
setMeta('Bereit'); setMeta('Bereit');
_recoveryChainId = null; _recoveryChainId = null;
// History-Polling wieder starten
startHistoryPolling();
} }
} catch (e) { } catch (e) {
console.error('Streaming-Poll fehlgeschlagen:', e); console.error('Streaming-Poll fehlgeschlagen:', e);
@@ -274,6 +279,9 @@ registerPage('chat', {
}, },
destroy() { destroy() {
// Stream nicht abbrechen (Backend soll weiterlaufen),
// aber Frontend-Processing stoppen
_chatAbandoned = true;
if (this._closeMenu) { if (this._closeMenu) {
document.removeEventListener('click', this._closeMenu); document.removeEventListener('click', this._closeMenu);
this._closeMenu = null; this._closeMenu = null;
+3 -3
View File
@@ -238,7 +238,7 @@ function renderToolBoxContent(div, name, inputJson, result, isError) {
const resultDiv = document.createElement('div'); const resultDiv = document.createElement('div');
resultDiv.className = 'tool-result' + (isError ? ' tool-error' : ''); resultDiv.className = 'tool-result' + (isError ? ' tool-error' : '');
resultDiv.textContent = result ? truncate(result, 300) : ''; resultDiv.textContent = result || '';
resultDiv.hidden = true; resultDiv.hidden = true;
div.appendChild(resultDiv); div.appendChild(resultDiv);
foldTarget = resultDiv; foldTarget = resultDiv;
@@ -340,12 +340,12 @@ function appendToolResult(result, isError) {
const existing = lastBox.querySelector('.tool-result'); const existing = lastBox.querySelector('.tool-result');
if (existing) { if (existing) {
existing.textContent = truncate(result, 300); existing.textContent = result || '';
if (isError) existing.classList.add('tool-error'); if (isError) existing.classList.add('tool-error');
} else { } else {
const resultDiv = document.createElement('div'); const resultDiv = document.createElement('div');
resultDiv.className = 'tool-result' + (isError ? ' tool-error' : ''); resultDiv.className = 'tool-result' + (isError ? ' tool-error' : '');
resultDiv.textContent = truncate(result, 300); resultDiv.textContent = result || '';
resultDiv.hidden = true; resultDiv.hidden = true;
lastBox.appendChild(resultDiv); lastBox.appendChild(resultDiv);
} }