From f6f7b7b237f241f8293ff15bed6146ee6b758cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Tue, 19 May 2026 17:40:19 +0800 Subject: [PATCH] Add files via upload --- web/static/js/monitor.js | 40 ++++++++++++++++++++++++++++++++------- web/static/js/webshell.js | 39 ++++++++++++++++++++++++++------------ 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index b992e921..fbea849e 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -410,6 +410,34 @@ if (typeof window !== 'undefined') { window.normalizeStreamingDeltaJs = normalizeStreamingDeltaJs; } +/** + * SSE data.accumulated:服务端权威流式全文。有则直接用作 buffer,避免双端 normalize 叠字。 + * @param {object|null|undefined} data + * @returns {string|null} 有快照时返回全文;否则 null(回退 delta 归一化) + */ +function streamBufferFromAccumulated(data) { + if (!data || data.accumulated == null) { + return null; + } + return String(data.accumulated); +} + +/** + * @returns {string} 合并后的 buffer + */ +function mergeStreamBuffer(current, delta, data) { + const acc = streamBufferFromAccumulated(data); + if (acc !== null) { + return acc; + } + return normalizeStreamingDeltaJs(current, delta)[0]; +} + +if (typeof window !== 'undefined') { + window.streamBufferFromAccumulated = streamBufferFromAccumulated; + window.mergeStreamBuffer = mergeStreamBuffer; +} + /** 流式 delta:纯文本,避免每条全量 marked + DOMPurify */ function setTimelineItemContentStreamPlain(contentEl, text) { if (!contentEl) return; @@ -1411,8 +1439,7 @@ function handleStreamEvent(event, progressElement, progressId, const s = state.get(streamId); const delta = event.message || ''; - const merged = normalizeStreamingDeltaJs(s.buffer, delta); - s.buffer = merged[0]; + s.buffer = mergeStreamBuffer(s.buffer, delta, d); const item = document.getElementById(s.itemId); if (item) { @@ -1710,12 +1737,11 @@ function handleStreamEvent(event, progressElement, progressId, const streamId = d.streamId || null; if (!streamId) break; const delta = event.message || ''; - if (!delta) break; + if (!delta && streamBufferFromAccumulated(d) === null) break; const stateMap = einoAgentReplyStreamStateByProgressId.get(progressId); if (!stateMap || !stateMap.has(streamId)) break; const s = stateMap.get(streamId); - const merged = normalizeStreamingDeltaJs(s.buffer, delta); - s.buffer = merged[0]; + s.buffer = mergeStreamBuffer(s.buffer, delta, d); const item = document.getElementById(s.itemId); if (item) { let contentEl = item.querySelector('.timeline-item-content'); @@ -1901,8 +1927,8 @@ function handleStreamEvent(event, progressElement, progressId, } const deltaContent = event.message || ''; - const mergedResp = normalizeStreamingDeltaJs(state.buffer, deltaContent); - state.buffer = mergedResp[0]; + if (!deltaContent && streamBufferFromAccumulated(responseData) === null) break; + state.buffer = mergeStreamBuffer(state.buffer, deltaContent, responseData); // 更新时间线条目内容 if (state.itemId) { diff --git a/web/static/js/webshell.js b/web/static/js/webshell.js index 35bca967..1ea4fdc8 100644 --- a/web/static/js/webshell.js +++ b/web/static/js/webshell.js @@ -2924,11 +2924,16 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { messagesContainer.scrollTop = messagesContainer.scrollHeight; } else if (_et === 'response_delta') { var deltaText = (_em != null && _em !== '') ? String(_em) : ''; - if (deltaText) { - var normR = (typeof window.normalizeStreamingDeltaJs === 'function') - ? window.normalizeStreamingDeltaJs(streamingTarget, deltaText) - : [streamingTarget + deltaText, deltaText]; - streamingTarget = normR[0]; + var mergeBuf = (typeof window.mergeStreamBuffer === 'function') + ? window.mergeStreamBuffer + : function (cur, dlt) { + var normR = (typeof window.normalizeStreamingDeltaJs === 'function') + ? window.normalizeStreamingDeltaJs(cur, dlt) + : [cur + dlt, dlt]; + return normR[0]; + }; + if (deltaText || (_ed && _ed.accumulated != null)) { + streamingTarget = mergeBuf(streamingTarget, deltaText, _ed); webshellStreamingTypingId += 1; streamingTypingId = webshellStreamingTypingId; runWebshellAiStreamingTyping(assistantDiv, streamingTarget, streamingTypingId, messagesContainer); @@ -3001,12 +3006,17 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { wsThinkingStreams.set(_ed.streamId, { el: thinkSItem, body: thinkSPre, buf: '' }); } if (!streamingTarget) assistantDiv.textContent = '…'; - } else if ((_et === 'thinking_stream_delta' || _et === 'reasoning_chain_stream_delta') && _ed.streamId) { + } else if ((_et === 'thinking_stream_delta' || _et === 'reasoning_chain_stream_delta') && _ed && _ed.streamId) { var tsD = wsThinkingStreams.get(_ed.streamId); if (tsD) { - var normT = (typeof window.normalizeStreamingDeltaJs === 'function') - ? window.normalizeStreamingDeltaJs(tsD.buf, _em || '') : [tsD.buf + (_em || ''), _em || '']; - tsD.buf = normT[0]; + var mergeThink = (typeof window.mergeStreamBuffer === 'function') + ? window.mergeStreamBuffer + : function (cur, dlt) { + var normT = (typeof window.normalizeStreamingDeltaJs === 'function') + ? window.normalizeStreamingDeltaJs(cur, dlt) : [cur + dlt, dlt]; + return normT[0]; + }; + tsD.buf = mergeThink(tsD.buf, _em || '', _ed); if (typeof formatMarkdown === 'function') { tsD.body.innerHTML = formatMarkdown(tsD.buf); } else { @@ -3136,9 +3146,14 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { } else if (_et === 'eino_agent_reply_stream_delta' && _ed.streamId) { var stD = einoSubReplyStreams.get(_ed.streamId); if (stD) { - var normS = (typeof window.normalizeStreamingDeltaJs === 'function') - ? window.normalizeStreamingDeltaJs(stD.buf, _em || '') : [stD.buf + (_em || ''), _em || '']; - stD.buf = normS[0]; + var mergeSub = (typeof window.mergeStreamBuffer === 'function') + ? window.mergeStreamBuffer + : function (cur, dlt) { + var normS = (typeof window.normalizeStreamingDeltaJs === 'function') + ? window.normalizeStreamingDeltaJs(cur, dlt) : [cur + dlt, dlt]; + return normS[0]; + }; + stD.buf = mergeSub(stD.buf, _em || '', _ed); var preD = stD.el.querySelector('.webshell-eino-reply-stream-body'); if (!preD) { preD = document.createElement('pre');