Web-Search: Streaming-Fix (pauseStreamBubble statt resumeStreamBubble), leere Suchergebnisse nicht speichern, Sources auf Shared-Pages anzeigen

This commit is contained in:
Meleki committed 2026-03-07 10:46:45 +01:00
1 parent d81dab635a
commit 6c38ccfe12
7 files changed
+103 -74

No files matched your search

+4 -2
View File
@@ -118,13 +118,15 @@ public static class SharedEndpoint
// Alle Messages der Chain bis zu dieser Nachricht laden
var contextMessages = await db.GetChainMessagesUpTo(message.ChainId, shared.MessageId);
// Nur text-Messages zurückgeben (keine tool_use/tool_result)
// Text- und Web-Search-Messages zurückgeben (keine tool_use/tool_result)
var allowedTypes = new HashSet<string> { "text", "web_search_result" };
var textMessages = contextMessages
.Where(m => m.Type == "text" && m.DeletedOn == null)
.Where(m => allowedTypes.Contains(m.Type) && m.DeletedOn == null)
.Select(m => new
{
m.Id,
m.Role,
m.Type,
m.Content,
m.CreatedAt,
m.Iteration
+26 -17
View File
@@ -179,12 +179,15 @@ public class AnthropicService
}
}
}
await _db.SaveMessage(new MessageRecord
if (sources.Count > 0)
{
ChainId = chainId, Iteration = iterations,
Role = "assistant", Type = "web_search_result",
Content = JsonSerializer.Serialize(sources)
});
await _db.SaveMessage(new MessageRecord
{
ChainId = chainId, Iteration = iterations,
Role = "assistant", Type = "web_search_result",
Content = JsonSerializer.Serialize(sources)
});
}
}
}
@@ -683,14 +686,17 @@ public class AnthropicService
}
}
await _db.SaveMessage(new MessageRecord
if (sources.Count > 0)
{
ChainId = chainId,
Iteration = baseIterations + iterations,
Role = "assistant",
Type = "web_search_result",
Content = JsonSerializer.Serialize(sources)
});
await _db.SaveMessage(new MessageRecord
{
ChainId = chainId,
Iteration = baseIterations + iterations,
Role = "assistant",
Type = "web_search_result",
Content = JsonSerializer.Serialize(sources)
});
}
}
else if (blockType == "server_tool_use")
{
@@ -889,12 +895,15 @@ public class AnthropicService
}
}
}
await _db.SaveMessage(new MessageRecord
if (sources.Count > 0)
{
ChainId = ctx.ChainId, Iteration = iterations,
Role = "assistant", Type = "web_search_result",
Content = JsonSerializer.Serialize(sources)
});
await _db.SaveMessage(new MessageRecord
{
ChainId = ctx.ChainId, Iteration = iterations,
Role = "assistant", Type = "web_search_result",
Content = JsonSerializer.Serialize(sources)
});
}
}
else if (blockType == "server_tool_use")
{
+17 -1
View File
@@ -125,7 +125,7 @@
width: .55em;
height: 1.15em;
background: var(--text);
animation: blink .2s step-end infinite;
animation: blink .5s step-end infinite;
vertical-align: text-bottom;
margin-left: .15em;
border-radius: 1px;
@@ -1023,6 +1023,22 @@
padding: var(--page-padding);
}
.shared-content .tool-box[data-tool-name="web_search"] {
border: none;
max-width: unset;
font-size: unset;
.tool-box-header {
font-size: var(--font-size);
opacity: var(--semi-transparent);
cursor: unset !important;
}
.tool-result {
display: none;
}
}
/* Review Bubbles */
.review-bubble {
+9 -17
View File
@@ -139,7 +139,6 @@ async function sendMessage() {
let fullText = '';
let currentToolBox = null;
let _afterWebSearch = false; // true wenn gerade eine Web-Search beendet wurde
_chatAbandoned = false;
@@ -167,14 +166,10 @@ async function sendMessage() {
switch (eventType) {
case 'text_start':
if (_afterWebSearch) {
// Nach Web-Search: letzte Bubble wiederverwenden statt neue
_afterWebSearch = false;
resumeStreamBubble();
} else {
endStreamBubble();
startStreamBubble();
}
// Wenn bereits eine Bubble offen ist (z.B. nach Web-Search-Pause),
// einfach weiter reinschreiben – appendToStream kümmert sich darum.
// Nur wenn keine Bubble existiert, wird eine neue erstellt.
if (!_streamDiv) startStreamBubble();
break;
case 'text_delta':
@@ -183,11 +178,12 @@ async function sendMessage() {
break;
case 'text_end':
endStreamBubble();
// Nur Cursor entfernen, Bubble offen lassen
// (nach Web-Search kommt evtl. noch ein text_start in dieselbe Bubble)
pauseStreamBubble();
break;
case 'tool_start':
_afterWebSearch = false;
endStreamBubble();
if (isToolVisible(data.name)) {
currentToolBox = addToolBoxLoading(data.name);
@@ -204,7 +200,8 @@ async function sendMessage() {
break;
case 'web_search_start':
// Bubble NICHT beenden – Text wird nach Search fortgesetzt
// Bubble pausieren, nicht beenden – Text wird nach Search fortgesetzt
pauseStreamBubble();
currentToolBox = addToolBoxLoading('web_search');
break;
@@ -213,11 +210,9 @@ async function sendMessage() {
finishToolBox(currentToolBox, { name: 'web_search', result: 'Suche abgeschlossen', isError: false, sources: data.sources });
}
currentToolBox = null;
_afterWebSearch = true;
break;
case 'review_start':
_afterWebSearch = false;
endStreamBubble();
addReviewBubble('start', data);
break;
@@ -239,7 +234,6 @@ async function sendMessage() {
break;
case 'finalizer_start':
_afterWebSearch = false;
endStreamBubble();
addReviewBubble('start', data, 'finalizer');
break;
@@ -257,13 +251,11 @@ 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);
+11 -16
View File
@@ -156,19 +156,12 @@ function _mergeConsecutiveAssistantTexts(msgs) {
const result = [];
for (const msg of msgs) {
if (msg.type === 'text' && msg.role === 'assistant') {
// Finde den letzten assistant-text in result (überspringe web_search_result dazwischen)
var prev = null;
for (var i = result.length - 1; i >= 0; i--) {
if (result[i].type === 'web_search_result') continue; // Web-Search-Ergebnisse überspringen
if (result[i].type === 'text' && result[i].role === 'assistant') prev = result[i];
break;
}
const prev = result.length > 0 ? result[result.length - 1] : null;
const isReview = msg.content && (msg.content.startsWith('[Review:') || msg.content.startsWith('[Finalizer:'));
const prevIsReview = prev && prev.content && (prev.content.startsWith('[Review:') || prev.content.startsWith('[Finalizer:'));
if (prev && prev.iteration === msg.iteration && !isReview && !prevIsReview) {
// Gleiche Iteration: nahtlos zusammenfügen (Web-Search-Fragmente etc.)
prev.content = (prev.content || '') + '\n\n' + (msg.content || '');
// IDs/Timestamps: letztes behalten (für Mark-Button etc.)
if (prev && prev.type === 'text' && prev.role === 'assistant'
&& prev.iteration === msg.iteration && !isReview && !prevIsReview) {
prev.content = (prev.content || '') + ' ' + (msg.content || '');
if (msg.id) prev.id = msg.id;
if (msg.createdAt) prev.createdAt = msg.createdAt;
if (msg.isMarked) prev.isMarked = true;
@@ -255,10 +248,11 @@ function _renderMessagesIntoFragment(fragment, msgs, imageMessageIds) {
break;
case 'web_search_result': {
if (!isToolVisible('web_search')) continue;
div.className = 'tool-box';
div.dataset.toolName = 'web_search';
var wsSources = [];
try { wsSources = JSON.parse(msg.content || '[]'); } catch(e) {}
if (wsSources.length === 0) continue; // Leere Suchergebnisse überspringen
div.className = 'tool-box';
div.dataset.toolName = 'web_search';
renderToolBoxContent(div, 'web_search', null, null, false, wsSources);
fragment.appendChild(div);
break;
@@ -323,7 +317,7 @@ function renderMessages(msgs, addToConversation) {
if (isToolVisible('web_search')) {
var wsSrc = [];
try { wsSrc = JSON.parse(msg.content || '[]'); } catch(e) {}
addToolBoxStatic('web_search', null, null, false, wsSrc);
if (wsSrc.length > 0) addToolBoxStatic('web_search', null, null, false, wsSrc);
}
break;
}
@@ -426,10 +420,11 @@ async function loadLatestArchive() {
break;
case 'web_search_result': {
if (!isToolVisible('web_search')) continue;
div.className = 'tool-box archived';
div.dataset.toolName = 'web_search';
var archSrc = [];
try { archSrc = JSON.parse(msg.content || '[]'); } catch(e) {}
if (archSrc.length === 0) continue; // Leere Suchergebnisse überspringen
div.className = 'tool-box archived';
div.dataset.toolName = 'web_search';
renderToolBoxContent(div, 'web_search', null, null, false, archSrc);
break;
}
+28 -3
View File
@@ -26,6 +26,20 @@ registerPage('shared', {
for (const msg of messages) {
const c = msg.content || '';
// Web-Search-Results als Tool-Box mit Sources rendern
if (msg.type === 'web_search_result') {
var wsSources = [];
try { wsSources = JSON.parse(c || '[]'); } catch(e) {}
if (wsSources.length === 0) continue;
const div = document.createElement('div');
div.className = 'tool-box';
div.dataset.toolName = 'web_search';
renderToolBoxContent(div, 'web_search', null, null, false, wsSources);
container.appendChild(div);
continue;
}
const isReview = c.startsWith('[Review:') || c.startsWith('[Review-Feedback') || c.startsWith('[Finalizer:');
const div = document.createElement('div');
if (isReview) {
@@ -63,14 +77,25 @@ function _mergeConsecutiveTexts(msgs) {
const result = [];
for (const msg of msgs) {
const c = msg.content || '';
// web_search_result und Review/Finalizer-Messages nicht mergen
if (msg.type === 'web_search_result') {
result.push(Object.assign({}, msg));
continue;
}
const isReview = c.startsWith('[Review:') || c.startsWith('[Review-Feedback') || c.startsWith('[Finalizer:');
if (msg.role === 'assistant' && !isReview) {
const prev = result[result.length - 1];
if (msg.role === 'assistant' && !isReview && msg.type !== 'web_search_result') {
// Rückwärts suchen – web_search_result überspringen
let prev = null;
for (let i = result.length - 1; i >= 0; i--) {
if (result[i].type === 'web_search_result') continue;
prev = result[i];
break;
}
if (prev && prev.role === 'assistant'
&& !(prev.content || '').startsWith('[Review:')
&& !(prev.content || '').startsWith('[Finalizer:')
&& prev.iteration === msg.iteration) {
prev.content = (prev.content || '') + ' ' + c;
prev.content = (prev.content || '') + '\n\n' + c;
if (msg.createdAt) prev.createdAt = msg.createdAt;
continue;
}
+8 -18
View File
@@ -749,16 +749,12 @@ 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();
}
@@ -779,6 +775,14 @@ function appendToStream(text) {
scrollDown();
}
function pauseStreamBubble() {
// Cursor entfernen, aber Bubble + Text behalten (z.B. bei Web-Search-Pause)
if (_streamDiv) {
const cursor = _streamDiv.querySelector('.cursor');
if (cursor) cursor.remove();
}
}
function endStreamBubble() {
if (_streamDiv) {
const cursor = _streamDiv.querySelector('.cursor');
@@ -790,24 +794,10 @@ 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'];