fix(chat): Web-Search-Streaming – Text-Bubbles nach Search in gleicher Bubble fortsetzen

- chat.js: _afterWebSearch Flag trackt ob gerade eine Web-Search beendet wurde
  - text_start nach Web-Search: resumeStreamBubble() statt neue Bubble
  - web_search_start: Bubble NICHT beenden (bleibt finalisiert im DOM)
  - web_search_end: setzt _afterWebSearch=true
  - Flag wird bei tool_start, review_start, finalizer_start, error, done zurückgesetzt
- ui.js: Neue resumeStreamBubble() Funktion
  - _lastStreamDiv/_lastStreamText speichern letzte finalisierte Bubble
  - resumeStreamBubble() reaktiviert die letzte Bubble zum Weiter-Streamen
  - Verhindert separate Message-Blöcke bei mehrfachen Web-Searches
This commit is contained in:
Meleki committed 2026-03-07 09:48:14 +01:00
1 parent 4bec740765
commit 116f21105c
2 files changed
+34 -3

No files matched your search

+16 -3
View File
@@ -139,6 +139,7 @@ async function sendMessage() {
let fullText = '';
let currentToolBox = null;
let _afterWebSearch = false; // true wenn gerade eine Web-Search beendet wurde
_chatAbandoned = false;
@@ -166,8 +167,14 @@ async function sendMessage() {
switch (eventType) {
case 'text_start':
endStreamBubble();
startStreamBubble();
if (_afterWebSearch) {
// Nach Web-Search: letzte Bubble wiederverwenden statt neue
_afterWebSearch = false;
resumeStreamBubble();
} else {
endStreamBubble();
startStreamBubble();
}
break;
case 'text_delta':
@@ -180,6 +187,7 @@ async function sendMessage() {
break;
case 'tool_start':
_afterWebSearch = false;
endStreamBubble();
if (isToolVisible(data.name)) {
currentToolBox = addToolBoxLoading(data.name);
@@ -196,7 +204,7 @@ async function sendMessage() {
break;
case 'web_search_start':
endStreamBubble();
// Bubble NICHT beenden – Text wird nach Search fortgesetzt
currentToolBox = addToolBoxLoading('web_search');
break;
@@ -205,9 +213,11 @@ async function sendMessage() {
finishToolBox(currentToolBox, { name: 'web_search', result: 'Suche abgeschlossen', isError: false });
}
currentToolBox = null;
_afterWebSearch = true;
break;
case 'review_start':
_afterWebSearch = false;
endStreamBubble();
addReviewBubble('start', data);
break;
@@ -229,6 +239,7 @@ async function sendMessage() {
break;
case 'finalizer_start':
_afterWebSearch = false;
endStreamBubble();
addReviewBubble('start', data, 'finalizer');
break;
@@ -246,11 +257,13 @@ async function sendMessage() {
break;
case 'error':
_afterWebSearch = false;
endStreamBubble();
addErrorBubble(data.error, true);
break;
case 'done':
_afterWebSearch = false;
endStreamBubble();
if (data.stopReason === 'max_tokens') {
addWarningBubble('Antwort wurde abgeschnitten (max_tokens erreicht). Bitte frage nach dem Rest.', true);
+18
View File
@@ -749,12 +749,16 @@ function finalizeReviewBubble(type, data, agentType) {
let _streamDiv = null;
let _streamText = '';
let _lastStreamDiv = null;
let _lastStreamText = '';
function startStreamBubble() {
_streamDiv = document.createElement('div');
_streamDiv.className = 'msg assistant markdown streamed';
_streamDiv.innerHTML = '<p><span class="cursor"></span></p>';
_streamText = '';
_lastStreamDiv = null;
_lastStreamText = '';
chatEl().appendChild(_streamDiv);
scrollDown();
}
@@ -786,10 +790,24 @@ function endStreamBubble() {
wrapKreaGalleries(_streamDiv);
}
}
_lastStreamDiv = _streamDiv;
_lastStreamText = _streamText;
_streamDiv = null;
_streamText = '';
}
function resumeStreamBubble() {
// Letzte finalisierte Bubble wiederverwenden (z.B. nach Web-Search)
if (_lastStreamDiv && _lastStreamDiv.parentNode) {
_streamDiv = _lastStreamDiv;
_streamText = _lastStreamText;
} else {
startStreamBubble();
}
_lastStreamDiv = null;
_lastStreamText = '';
}
// ── Tool Call Visibility ──
const MODIFICATION_TOOLS = ['write_file', 'edit_file', 'delete_file', 'memory_write'];