Web-Search: Streaming-Fix (pauseStreamBubble statt resumeStreamBubble), leere Suchergebnisse nicht speichern, Sources auf Shared-Pages anzeigen
This commit is contained in:
1 parent
d81dab635a
commit
6c38ccfe12
7 files changed
+103
-74
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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'];
|
||||
|
||||
Reference in new issue
Block a user