From 67123444116e3563421c3abb9579a5578182b885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Thu, 18 Jun 2026 12:46:46 +0800 Subject: [PATCH] Add files via upload --- web/static/i18n/en-US.json | 3 +- web/static/i18n/zh-CN.json | 3 +- web/static/js/chat.js | 81 ++++++++++++++++++++++++++------------ web/static/js/monitor.js | 40 +------------------ web/static/js/webshell.js | 22 ----------- 5 files changed, 62 insertions(+), 87 deletions(-) diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index bd9752d8..925b3b03 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -2353,7 +2353,8 @@ "copyContent": "Copy content", "correctInfo": "Correct info", "errorInfo": "Error info", - "copyError": "Copy error" + "copyError": "Copy error", + "contentTruncated": "… (display truncated; use read_file on the path in persisted-output for the full file)" }, "attackChainModal": { "title": "Attack chain", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 6d54ea6c..c369a8da 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -2341,7 +2341,8 @@ "copyContent": "复制内容", "correctInfo": "正确信息", "errorInfo": "错误信息", - "copyError": "复制错误" + "copyError": "复制错误", + "contentTruncated": "…(展示已截断;完整内容见 persisted-output 中的文件路径,用 read_file 读取)" }, "attackChainModal": { "title": "攻击链可视化", diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 2b3600cc..65a44994 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -2639,6 +2639,57 @@ async function batchUpdateButtonToolNames(buttonsContainer, executionIds) { } // 显示MCP调用详情 +const MCP_DETAIL_MAX_CHARS = 120000; + +function extractMCPResultText(result) { + if (!result) return ''; + const content = result.content; + if (typeof content === 'string') return content; + if (Array.isArray(content)) { + return content + .map(item => (item && typeof item === 'object' && typeof item.text === 'string') ? item.text : '') + .filter(Boolean) + .join('\n\n'); + } + if (content && typeof content === 'object' && typeof content.text === 'string') { + return content.text; + } + return ''; +} + +function truncateMCPDetailText(text, maxChars) { + if (text == null) return ''; + const s = String(text); + if (s.length <= maxChars) return s; + const hint = typeof window.t === 'function' + ? window.t('mcpDetailModal.contentTruncated') + : '…(展示已截断;完整内容见 persisted-output 中的文件路径,用 read_file 读取)'; + return s.slice(0, maxChars) + '\n\n' + hint; +} + +/** 响应结果区 JSON 展示(过大时截断 content 内 text,避免 stringify 卡死页面) */ +function formatMCPResultJsonForDisplay(result, maxChars) { + if (!result) return '{}'; + const payload = { + content: result.content, + isError: !!result.isError + }; + let json = JSON.stringify(payload, null, 2); + if (json.length <= maxChars) { + return json; + } + const text = extractMCPResultText(result); + const truncatedPayload = { + content: [{ type: 'text', text: truncateMCPDetailText(text, Math.min(maxChars - 800, MCP_DETAIL_MAX_CHARS)) }], + isError: !!result.isError + }; + json = JSON.stringify(truncatedPayload, null, 2); + if (json.length > maxChars) { + return json.slice(0, maxChars) + '\n…'; + } + return json; +} + async function showMCPDetail(executionId) { try { openAppModal('mcp-detail-modal', { focus: false }); @@ -2700,42 +2751,22 @@ async function showMCPDetail(executionId) { } if (exec.result) { - const responseData = { - content: exec.result.content, - isError: exec.result.isError - }; - responseElement.textContent = JSON.stringify(responseData, null, 2); + const agentVisibleText = truncateMCPDetailText(extractMCPResultText(exec.result), MCP_DETAIL_MAX_CHARS); + const emptyText = typeof window.t === 'function' ? window.t('mcpDetailModal.execSuccessNoContent') : '执行成功,未返回可展示的文本内容。'; if (exec.result.isError) { - // 错误场景:响应结果标红 + 错误信息区块 responseElement.className = 'code-block error'; + responseElement.textContent = formatMCPResultJsonForDisplay(exec.result, MCP_DETAIL_MAX_CHARS); if (exec.error && errorSection && errorElement) { errorSection.style.display = 'block'; errorElement.textContent = exec.error; } } else { - // 成功场景:响应结果保持普通样式,正确信息单独拎出来 responseElement.className = 'code-block'; + responseElement.textContent = formatMCPResultJsonForDisplay(exec.result, MCP_DETAIL_MAX_CHARS); if (successSection && successElement) { successSection.style.display = 'block'; - let successText = ''; - const content = exec.result.content; - if (typeof content === 'string') { - successText = content; - } else if (Array.isArray(content)) { - const texts = content - .map(item => (item && typeof item === 'object' && typeof item.text === 'string') ? item.text : '') - .filter(Boolean); - if (texts.length > 0) { - successText = texts.join('\n\n'); - } - } else if (content && typeof content === 'object' && typeof content.text === 'string') { - successText = content.text; - } - if (!successText) { - successText = typeof window.t === 'function' ? window.t('mcpDetailModal.execSuccessNoContent') : '执行成功,未返回可展示的文本内容。'; - } - successElement.textContent = successText; + successElement.textContent = agentVisibleText || emptyText; } } } else { diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index ab4c296b..d0305aa4 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -2059,45 +2059,9 @@ function handleStreamEvent(event, progressElement, progressId, } break; - case 'tool_result_delta': { - const deltaInfo = event.data || {}; - const toolCallId = deltaInfo.toolCallId || null; - if (!toolCallId) break; - - const key = toolResultStreamKey(progressId, toolCallId); - let state = toolResultStreamStateByKey.get(key); - const deltaText = event.message || ''; - if (!deltaText) break; - - if (!state) { - const mapping = getToolCallMapping(progressId, toolCallId); - let callItemId = mapping && mapping.itemId ? mapping.itemId : null; - if (callItemId) { - const callItem = document.getElementById(callItemId); - if (callItem) { - ensureToolCallResultSlot(callItem); - const section = callItem.querySelector('.tool-result-section'); - if (section) { - section.classList.remove('pending'); - section.className = 'tool-result-section success'; - } - } - } - state = { itemId: callItemId, buffer: '', onCallItem: !!callItemId }; - toolResultStreamStateByKey.set(key, state); - } - - state.buffer += deltaText; - const item = state.itemId ? document.getElementById(state.itemId) : null; - if (item) { - const pre = item.querySelector('pre.tool-result'); - if (pre) { - pre.classList.remove('tool-result-pending'); - scheduleStreamPlainTextUpdate(pre, state.buffer); - } - } + case 'tool_result_delta': + // 工具执行过程不流式展示,仅等 tool_result 展示最终结果。 break; - } case 'tool_result': const resultInfo = event.data || {}; diff --git a/web/static/js/webshell.js b/web/static/js/webshell.js index f94fcd6a..49590044 100644 --- a/web/static/js/webshell.js +++ b/web/static/js/webshell.js @@ -3391,28 +3391,6 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) { } if (!streamingTarget) assistantDiv.textContent = '…'; - // ─── Tool result delta (streaming output) ─── - } else if (_et === 'tool_result_delta' && _ed.toolCallId) { - var trdKey = _ed.toolCallId; - var trdDelta = _em || ''; - if (trdDelta) { - var trdState = wsToolResultStreams.get(trdKey); - if (!trdState) { - var callEl = wsToolCallItems.get(trdKey); - trdState = { el: callEl || null, buf: '', onCall: !!callEl }; - wsToolResultStreams.set(trdKey, trdState); - } - trdState.buf += trdDelta; - if (trdState.el) { - var trdPre = trdState.el.querySelector('pre.tool-result'); - if (trdPre) { - trdPre.classList.remove('tool-result-pending'); - trdPre.textContent = trdState.buf; - } - } - } - if (!streamingTarget) assistantDiv.textContent = '…'; - // ─── Tool result (final) ─── } else if (_et === 'tool_result' && _ed) { var success = _ed.success !== false;