diff --git a/web/static/css/style.css b/web/static/css/style.css index 88aace8e..cd894d58 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -5316,8 +5316,8 @@ html[data-theme="dark"] .login-card .login-submit:disabled { } #mcp-detail-modal { - background: rgba(3, 7, 18, 0.66); - backdrop-filter: blur(8px); + background: rgba(3, 7, 18, 0.58); + backdrop-filter: blur(4px); } #mcp-detail-modal .mcp-detail-content { @@ -5325,20 +5325,19 @@ html[data-theme="dark"] .login-card .login-submit:disabled { max-width: 980px; max-height: min(86vh, 880px); margin: 6vh auto; - border-radius: 16px; - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.98)); - border: 1px solid rgba(148, 163, 184, 0.26); - box-shadow: 0 24px 70px rgba(15, 23, 42, 0.22); + border-radius: 12px; + background: #ffffff; + border: 1px solid rgba(148, 163, 184, 0.28); + box-shadow: 0 18px 48px rgba(15, 23, 42, 0.18); } #mcp-detail-modal .modal-header { - min-height: 68px; - padding: 18px 24px; - background: rgba(255, 255, 255, 0.72); - border-bottom: 1px solid rgba(148, 163, 184, 0.22); + min-height: 64px; + padding: 16px 24px; + background: #ffffff; + border-bottom: 1px solid rgba(148, 163, 184, 0.18); box-shadow: none; - backdrop-filter: blur(14px); + backdrop-filter: none; } #mcp-detail-modal .modal-header h2 { @@ -5355,34 +5354,32 @@ html[data-theme="dark"] .login-card .login-submit:disabled { #mcp-detail-modal .modal-header h2::before { content: ''; - width: 6px; - height: 26px; + width: 4px; + height: 22px; border-radius: 999px; - background: linear-gradient(180deg, #6366f1 0%, #38bdf8 100%); - box-shadow: 0 0 18px rgba(99, 102, 241, 0.38); + background: #64748b; + box-shadow: none; flex: 0 0 auto; } #mcp-detail-modal .modal-body { padding: 24px 26px 26px; - background: linear-gradient(180deg, rgba(248, 250, 252, 0.62), rgba(241, 245, 249, 0.36)); + background: #f8fafc; } #mcp-detail-modal .detail-section { margin-bottom: 18px; padding: 20px 22px; - border-radius: 12px; - background: rgba(255, 255, 255, 0.8); - border: 1px solid rgba(148, 163, 184, 0.22); - box-shadow: 0 12px 34px rgba(15, 23, 42, 0.05); + border-radius: 10px; + background: #ffffff; + border: 1px solid rgba(148, 163, 184, 0.18); + box-shadow: none; } #mcp-detail-modal .detail-section-overview { padding: 14px 16px; - background: - radial-gradient(circle at top left, rgba(59, 130, 246, 0.13), transparent 42%), - linear-gradient(145deg, rgba(255, 255, 255, 0.92), rgba(239, 246, 255, 0.68)); - border-color: rgba(59, 130, 246, 0.22); + background: #f8fafc; + border-color: rgba(148, 163, 184, 0.22); } #mcp-detail-modal .detail-section-header { @@ -5399,8 +5396,29 @@ html[data-theme="dark"] .login-card .login-submit:disabled { } #mcp-detail-modal .detail-section-header .btn-ghost { - border-color: rgba(37, 99, 235, 0.16); - background: rgba(37, 99, 235, 0.06); + border-color: rgba(148, 163, 184, 0.24); + background: rgba(248, 250, 252, 0.72); + color: var(--text-secondary); + min-height: 32px; + padding: 5px 12px; + border-radius: 8px; + font-size: 0.8rem; + line-height: 1.2; + box-shadow: none; +} + +#mcp-detail-modal .detail-section-header .btn-ghost:hover { + border-color: rgba(100, 116, 139, 0.34); + background: #ffffff; + color: var(--text-primary); +} + +#mcp-detail-modal .detail-section-header .btn-ghost:disabled { + cursor: default; + opacity: 1; + border-color: rgba(34, 197, 94, 0.24); + background: rgba(240, 253, 244, 0.72); + color: #15803d; } #mcp-detail-modal .detail-info-grid { @@ -5412,9 +5430,9 @@ html[data-theme="dark"] .login-card .login-submit:disabled { min-width: 0; min-height: 58px; padding: 10px 12px; - border-radius: 9px; - background: rgba(15, 23, 42, 0.035); - border-color: rgba(148, 163, 184, 0.2); + border-radius: 8px; + background: #ffffff; + border-color: rgba(148, 163, 184, 0.18); box-shadow: none; } @@ -5443,18 +5461,18 @@ html[data-theme="dark"] .login-card .login-submit:disabled { #mcp-detail-modal .status-chip { width: fit-content; - min-width: 88px; - padding: 5px 12px; + min-width: 76px; + padding: 4px 10px; font-size: 0.82rem; } #mcp-detail-modal .detail-code-card { padding: 0; overflow: hidden; - background: #0b1120; - border: 1px solid rgba(148, 163, 184, 0.18); - border-radius: 12px; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); + background: #f8fafc; + border: 1px solid rgba(148, 163, 184, 0.2); + border-radius: 8px; + box-shadow: none; } #mcp-detail-modal .detail-result-tabs { @@ -5462,17 +5480,17 @@ html[data-theme="dark"] .login-card .login-submit:disabled { align-items: center; gap: 4px; margin: -2px 0 12px; - padding: 4px; - border: 1px solid rgba(148, 163, 184, 0.22); - border-radius: 10px; - background: rgba(15, 23, 42, 0.045); + padding: 3px; + border: 1px solid rgba(148, 163, 184, 0.2); + border-radius: 8px; + background: #f8fafc; } #mcp-detail-modal .detail-result-tab { min-height: 30px; padding: 0 12px; border: 0; - border-radius: 7px; + border-radius: 6px; background: transparent; color: var(--text-secondary); font-size: 0.82rem; @@ -5484,7 +5502,7 @@ html[data-theme="dark"] .login-card .login-submit:disabled { #mcp-detail-modal .detail-result-tab.active { background: #ffffff; color: var(--text-primary); - box-shadow: 0 5px 14px rgba(15, 23, 42, 0.08); + box-shadow: none; } #mcp-detail-modal .detail-result-tab.disabled, @@ -5508,7 +5526,7 @@ html[data-theme="dark"] .login-card .login-submit:disabled { border: 0; border-radius: 0; background: transparent; - color: #dbeafe; + color: #0f172a; font-size: 0.84rem; line-height: 1.7; white-space: pre-wrap; @@ -5517,7 +5535,22 @@ html[data-theme="dark"] .login-card .login-submit:disabled { } #mcp-detail-modal .code-block.error { - color: #fecaca; + color: #991b1b; +} + +#mcp-detail-modal .code-block::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +#mcp-detail-modal .code-block::-webkit-scrollbar-thumb { + background: rgba(100, 116, 139, 0.36); + border: 2px solid rgba(248, 250, 252, 0.9); + border-radius: 999px; +} + +#mcp-detail-modal .code-block::-webkit-scrollbar-track { + background: rgba(226, 232, 240, 0.62); } #mcp-detail-modal .detail-success-wrapper { @@ -34189,18 +34222,18 @@ html[data-theme="dark"] #skill-view-body { } html[data-theme="dark"] #mcp-detail-modal { - background: rgba(2, 6, 23, 0.72); + background: rgba(2, 6, 23, 0.62); } html[data-theme="dark"] #mcp-detail-modal .mcp-detail-content { - background: linear-gradient(180deg, #111827 0%, #0f172a 100%) !important; - border-color: rgba(96, 165, 250, 0.16); - box-shadow: 0 28px 80px rgba(0, 0, 0, 0.46); + background: #111827 !important; + border-color: rgba(148, 163, 184, 0.16); + box-shadow: 0 18px 48px rgba(0, 0, 0, 0.34); } html[data-theme="dark"] #mcp-detail-modal .modal-header { - background: rgba(15, 23, 42, 0.78) !important; - border-bottom-color: rgba(148, 163, 184, 0.16) !important; + background: #111827 !important; + border-bottom-color: rgba(148, 163, 184, 0.14) !important; } html[data-theme="dark"] #mcp-detail-modal .modal-header h2 { @@ -34210,26 +34243,22 @@ html[data-theme="dark"] #mcp-detail-modal .modal-header h2 { } html[data-theme="dark"] #mcp-detail-modal .modal-body { - background: - radial-gradient(circle at top right, rgba(37, 99, 235, 0.12), transparent 42%), - linear-gradient(180deg, rgba(15, 23, 42, 0.95), rgba(2, 6, 23, 0.88)); + background: #0f172a; } html[data-theme="dark"] #mcp-detail-modal .detail-section { - background: rgba(15, 23, 42, 0.72); + background: #111827; border-color: rgba(148, 163, 184, 0.14); - box-shadow: 0 16px 44px rgba(0, 0, 0, 0.16); + box-shadow: none; } html[data-theme="dark"] #mcp-detail-modal .detail-section-overview { - background: - radial-gradient(circle at top left, rgba(59, 130, 246, 0.2), transparent 44%), - linear-gradient(145deg, rgba(15, 23, 42, 0.92), rgba(17, 24, 39, 0.74)); - border-color: rgba(59, 130, 246, 0.24); + background: #111827; + border-color: rgba(148, 163, 184, 0.16); } html[data-theme="dark"] #mcp-detail-modal .detail-info-grid .detail-item { - background: rgba(2, 6, 23, 0.42); + background: #0f172a; border-color: rgba(148, 163, 184, 0.14); } @@ -34237,14 +34266,32 @@ html[data-theme="dark"] #mcp-detail-modal .detail-item strong { color: #94a3b8; } +html[data-theme="dark"] #mcp-detail-modal .detail-section-header .btn-ghost { + background: #0f172a; + border-color: rgba(148, 163, 184, 0.18); + color: #94a3b8; +} + +html[data-theme="dark"] #mcp-detail-modal .detail-section-header .btn-ghost:hover { + background: #1f2937; + border-color: rgba(148, 163, 184, 0.26); + color: #e5e7eb; +} + +html[data-theme="dark"] #mcp-detail-modal .detail-section-header .btn-ghost:disabled { + background: rgba(20, 83, 45, 0.22); + border-color: rgba(74, 222, 128, 0.18); + color: #86efac; +} + html[data-theme="dark"] #mcp-detail-modal .detail-code-card { background: #020617; - border-color: rgba(148, 163, 184, 0.16); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035); + border-color: rgba(148, 163, 184, 0.14); + box-shadow: none; } html[data-theme="dark"] #mcp-detail-modal .detail-result-tabs { - background: rgba(2, 6, 23, 0.38); + background: #0f172a; border-color: rgba(148, 163, 184, 0.14); } @@ -34253,9 +34300,9 @@ html[data-theme="dark"] #mcp-detail-modal .detail-result-tab { } html[data-theme="dark"] #mcp-detail-modal .detail-result-tab.active { - background: rgba(59, 130, 246, 0.16); - color: #dbeafe; - box-shadow: 0 6px 16px rgba(0, 0, 0, 0.16); + background: #1f2937; + color: #e5e7eb; + box-shadow: none; } html[data-theme="dark"] #mcp-detail-modal .detail-code-card .code-block, @@ -34265,6 +34312,19 @@ html[data-theme="dark"] #mcp-detail-modal .code-block { color: #dbeafe; } +html[data-theme="dark"] #mcp-detail-modal .code-block.error { + color: #fecaca; +} + +html[data-theme="dark"] #mcp-detail-modal .code-block::-webkit-scrollbar-thumb { + background: rgba(148, 163, 184, 0.42); + border-color: rgba(2, 6, 23, 0.95); +} + +html[data-theme="dark"] #mcp-detail-modal .code-block::-webkit-scrollbar-track { + background: rgba(15, 23, 42, 0.72); +} + html[data-theme="dark"] #mcp-detail-modal .detail-success-wrapper { background: rgba(20, 83, 45, 0.16); border-color: rgba(34, 197, 94, 0.28); diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 516f9df2..bb9d8777 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -2466,6 +2466,19 @@ function isProcessDetailsUserExpanded(messageId) { return !!(container && container.dataset && container.dataset.userExpanded === '1'); } +function messageHasConversationContent(messageElement) { + if (!messageElement || !messageElement.classList || !messageElement.classList.contains('assistant')) return false; + if (messageElement.hasAttribute('data-system-ready-message')) return false; + if (messageElement.dataset && String(messageElement.dataset.backendMessageId || '').trim()) return true; + const raw = messageElement.dataset ? String(messageElement.dataset.originalContent || '').trim() : ''; + if (raw) return true; + const bubble = messageElement.querySelector('.message-bubble'); + if (!bubble) return false; + const clone = bubble.cloneNode(true); + clone.querySelectorAll('.message-copy-btn').forEach((btn) => btn.remove()); + return String(clone.textContent || '').trim().length > 0; +} + function syncProcessDetailButtonLabels(messageId, expanded) { const expandT = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情'; const collapseT = typeof window.t === 'function' ? window.t('tasks.collapseDetail') : '收起详情'; @@ -2522,7 +2535,8 @@ function renderProcessDetails(messageId, processDetails, options) { const isLazyRequest = (processDetails === null); const reasoningFromMessage = getMessageReasoningContent(messageElement); const backendId = messageElement.dataset ? String(messageElement.dataset.backendMessageId || '').trim() : ''; - if (isLazyRequest && !reasoningFromMessage && !backendId && getMcpExecutionCount(messageElement) <= 0) { + const hasConversationContent = !!renderOpts.force || messageHasConversationContent(messageElement); + if (isLazyRequest && !reasoningFromMessage && !backendId && getMcpExecutionCount(messageElement) <= 0 && !hasConversationContent) { pruneEmptyMcpCallSection(messageElement); return; } @@ -3084,11 +3098,12 @@ function pruneEmptyMcpCallSection(messageElement) { const chrome = getExistingMcpCallSectionChrome(messageElement); if (!chrome || !chrome.mcpSection) return; const hasDetails = !!chrome.mcpSection.querySelector('.process-details-container'); + const hasProcessDetailButton = !!(chrome.toolbar && chrome.toolbar.querySelector('.process-detail-btn')); const hasToolButtons = !!(chrome.toolList && chrome.toolList.querySelector('.mcp-detail-btn')); const hasPendingTools = getPendingMcpExecutionCount(messageElement) > 0 || getPendingToolExecutionSummaryCount(messageElement) > 0 || getMcpExecutionCount(messageElement) > 0; - if (!hasDetails && !hasToolButtons && !hasPendingTools) { + if (!hasDetails && !hasProcessDetailButton && !hasToolButtons && !hasPendingTools) { chrome.mcpSection.remove(); } } @@ -3419,6 +3434,34 @@ async function focusToolExecutionInProcessDetails(messageElement, summary, index }, 2200); } +async function findToolExecutionTimelineItem(messageElement, summary, index) { + const item = normalizeToolExecutionSummaryForButton(summary); + let detailsContainer = await ensureProcessDetailsForToolFocus(messageElement, item.processDetailId || ''); + let timeline = detailsContainer && detailsContainer.querySelector('.progress-timeline'); + if (!timeline) return null; + let target = null; + if (item.processDetailId) { + target = timeline.querySelector('[data-process-detail-id="' + cssEscapeValue(item.processDetailId) + '"]'); + } + if (!target && item.toolCallId) { + target = timeline.querySelector('[data-tool-call-id="' + cssEscapeValue(item.toolCallId) + '"]'); + } + if (!target) { + const toolItems = timeline.querySelectorAll('.timeline-item-tool_call'); + target = toolItems[index] || null; + } + if (!target && item.processDetailId && messageElement.dataset && messageElement.dataset.backendMessageId && typeof window.loadProcessDetailsPaginated === 'function') { + await window.loadProcessDetailsPaginated(messageElement.id, messageElement.dataset.backendMessageId, { + autoLoadAll: false, + anchorId: item.processDetailId + }); + detailsContainer = document.getElementById('process-details-' + messageElement.id); + timeline = detailsContainer && detailsContainer.querySelector('.progress-timeline'); + target = timeline ? timeline.querySelector('[data-process-detail-id="' + cssEscapeValue(item.processDetailId) + '"]') : null; + } + return target; +} + async function resolveToolExecutionSummaryForFocus(messageElement, executionId, index) { const wantedExecutionId = executionId == null ? '' : String(executionId).trim(); let summaries = getCachedToolExecutionSummaries(messageElement); @@ -3482,6 +3525,77 @@ window.setMcpExecutionSummaryCount = setMcpExecutionSummaryCount; window.setPendingMcpExecutionIds = setPendingMcpExecutionIds; window.setPendingToolExecutionSummaries = setPendingToolExecutionSummaries; +async function fetchProcessDetailDataForModal(detailId) { + const id = detailId != null ? String(detailId).trim() : ''; + if (!id || typeof apiFetch !== 'function') return null; + const res = await apiFetch('/api/process-details/' + encodeURIComponent(id)); + const j = await res.json().catch(() => ({})); + if (!res.ok) return null; + const detail = j && j.processDetail ? j.processDetail : null; + return detail && detail.data ? detail.data : null; +} + +function processToolResultToMCPResult(resultData, rawText) { + if (!resultData) return null; + const isError = !!(resultData.isError || resultData.success === false); + let text = rawText; + if (text == null || String(text) === '') { + const noResultText = typeof window.t === 'function' ? window.t('timeline.noResult') : '无结果'; + const result = resultData.result != null ? resultData.result : (resultData.error != null ? resultData.error : noResultText); + text = typeof result === 'string' ? result : JSON.stringify(result); + } + return { + content: [{ type: 'text', text: String(text || '') }], + isError: isError + }; +} + +async function showMCPDetailFromProcessToolItem(messageElement, summary, index) { + const target = await findToolExecutionTimelineItem(messageElement, summary, index); + if (!target) { + alert(typeof window.t === 'function' + ? window.t('chat.toolExecutionDetailPending') + : '工具执行详情尚未同步,请稍后重试。'); + return; + } + const state = typeof window.getToolCallDetailState === 'function' + ? window.getToolCallDetailState(target) + : {}; + let args = state.args; + let resultData = state.resultData; + let rawText = state.rawText; + if ((!args || Object.keys(args).length === 0) && state.processDetailId) { + const fullCall = await fetchProcessDetailDataForModal(state.processDetailId); + if (fullCall) { + args = typeof window.parseToolCallArgsFromData === 'function' + ? window.parseToolCallArgsFromData(fullCall) + : (fullCall.argumentsObj || {}); + } + } + const resultDetailId = state.resultDetailId || target.dataset.toolResultDetailId || ''; + if (!resultData && resultDetailId) { + resultData = await fetchProcessDetailDataForModal(resultDetailId); + } + const toolName = (summary && summary.toolName) || target.dataset.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具'); + const success = resultData ? !(resultData.isError || resultData.success === false) : target.dataset.toolSuccess !== '0'; + const status = resultData || target.classList.contains('tool-call-completed') || target.classList.contains('tool-call-failed') + ? (success ? 'completed' : 'failed') + : 'running'; + const exec = { + id: (resultData && resultData.executionId) || (summary && summary.executionId) || '', + toolName: toolName, + status: status, + startTime: target.dataset.createdAtIso || '', + arguments: args || {}, + result: processToolResultToMCPResult(resultData, rawText), + error: resultData && resultData.error ? String(resultData.error) : '' + }; + openAppModal('mcp-detail-modal', { focus: false }); + deferModalContent(function () { + renderMCPDetailModal(exec); + }); +} + async function openTaskToolExecutionDetail(messageElement, item, index) { let detailItem = item; if (!detailItem.executionId) { @@ -3496,9 +3610,7 @@ async function openTaskToolExecutionDetail(messageElement, item, index) { await showMCPDetail(detailItem.executionId); return; } - alert(typeof window.t === 'function' - ? window.t('chat.toolExecutionDetailPending') - : '工具执行详情尚未同步,请稍后重试。'); + await showMCPDetailFromProcessToolItem(messageElement, detailItem, index); } /** @@ -3779,6 +3891,97 @@ function copyActiveMCPResultDetail(triggerBtn = null) { copyDetailBlock(activeBlock ? activeBlock.id : 'detail-response', triggerBtn); } +function renderMCPDetailModal(exec) { + exec = exec || {}; + document.getElementById('detail-tool-name').textContent = exec.toolName || (typeof window.t === 'function' ? window.t('mcpDetailModal.unknown') : 'Unknown'); + document.getElementById('detail-execution-id').textContent = exec.id || 'N/A'; + const statusEl = document.getElementById('detail-status'); + const normalizedStatus = (exec.status || 'unknown').toLowerCase(); + statusEl.textContent = getStatusText(exec.status); + statusEl.className = `status-chip status-${normalizedStatus}`; + try { + statusEl.dataset.detailStatus = (exec.status || '') + ''; + } catch (e) { /* ignore */ } + const detailTimeLocale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US'; + const detailTimeEl = document.getElementById('detail-time'); + if (detailTimeEl) { + detailTimeEl.textContent = exec.startTime + ? new Date(exec.startTime).toLocaleString(detailTimeLocale) + : '—'; + try { + detailTimeEl.dataset.detailTimeIso = exec.startTime ? new Date(exec.startTime).toISOString() : ''; + } catch (e) { /* ignore */ } + } + + const requestData = { + tool: exec.toolName, + arguments: exec.arguments + }; + document.getElementById('detail-request').textContent = JSON.stringify(requestData, null, 2); + + const responseElement = document.getElementById('detail-response'); + const successElement = document.getElementById('detail-success'); + const errorSection = document.getElementById('detail-error-section'); + const errorElement = document.getElementById('detail-error'); + + responseElement.className = 'code-block'; + responseElement.textContent = ''; + if (successElement) { + successElement.className = 'code-block'; + successElement.textContent = ''; + } + if (errorSection && errorElement) { + errorSection.style.display = 'none'; + errorElement.textContent = ''; + } + setMCPResultDetailTabs('raw', false); + + if (exec.result) { + 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 (successElement) { + successElement.textContent = ''; + } + setMCPResultDetailTabs('raw', false); + 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 (successElement) { + successElement.textContent = agentVisibleText || emptyText; + } + setMCPResultDetailTabs('success', true); + } + } else { + if (normalizedStatus === 'running') { + responseElement.textContent = typeof window.t === 'function' ? window.t('mcpDetailModal.runningNoResponseYet') : '尚无返回,工具可能仍在执行。若长时间无响应,可在下方终止本次调用。'; + } else { + responseElement.textContent = typeof window.t === 'function' ? window.t('chat.noResponseData') : '暂无响应数据'; + } + setMCPResultDetailTabs('raw', false); + } + + const abortSection = document.getElementById('detail-abort-section'); + const abortBtn = document.getElementById('detail-abort-btn'); + if (abortSection && abortBtn) { + if (normalizedStatus === 'running' && exec.id) { + abortSection.style.display = 'block'; + abortBtn.dataset.execId = exec.id || ''; + abortBtn.textContent = typeof window.t === 'function' ? window.t('mcpDetailModal.abortBtn') : '终止工具'; + } else { + abortSection.style.display = 'none'; + delete abortBtn.dataset.execId; + } + } +} + async function showMCPDetail(executionId) { try { openAppModal('mcp-detail-modal', { focus: false }); @@ -3792,97 +3995,7 @@ async function showMCPDetail(executionId) { } deferModalContent(function () { - // 填充模态框内容 - document.getElementById('detail-tool-name').textContent = exec.toolName || (typeof window.t === 'function' ? window.t('mcpDetailModal.unknown') : 'Unknown'); - document.getElementById('detail-execution-id').textContent = exec.id || 'N/A'; - const statusEl = document.getElementById('detail-status'); - const normalizedStatus = (exec.status || 'unknown').toLowerCase(); - statusEl.textContent = getStatusText(exec.status); - statusEl.className = `status-chip status-${normalizedStatus}`; - try { - statusEl.dataset.detailStatus = (exec.status || '') + ''; - } catch (e) { /* ignore */ } - const detailTimeLocale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US'; - const detailTimeEl = document.getElementById('detail-time'); - if (detailTimeEl) { - detailTimeEl.textContent = exec.startTime - ? new Date(exec.startTime).toLocaleString(detailTimeLocale) - : '—'; - try { - detailTimeEl.dataset.detailTimeIso = exec.startTime ? new Date(exec.startTime).toISOString() : ''; - } catch (e) { /* ignore */ } - } - - // 请求参数 - const requestData = { - tool: exec.toolName, - arguments: exec.arguments - }; - document.getElementById('detail-request').textContent = JSON.stringify(requestData, null, 2); - - // 响应结果 + 正确信息 / 错误信息 - const responseElement = document.getElementById('detail-response'); - const successElement = document.getElementById('detail-success'); - const errorSection = document.getElementById('detail-error-section'); - const errorElement = document.getElementById('detail-error'); - - // 重置状态 - responseElement.className = 'code-block'; - responseElement.textContent = ''; - if (successElement) { - successElement.className = 'code-block'; - successElement.textContent = ''; - } - if (errorSection && errorElement) { - errorSection.style.display = 'none'; - errorElement.textContent = ''; - } - setMCPResultDetailTabs('raw', false); - - if (exec.result) { - 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 (successElement) { - successElement.textContent = ''; - } - setMCPResultDetailTabs('raw', false); - 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 (successElement) { - successElement.textContent = agentVisibleText || emptyText; - } - setMCPResultDetailTabs('success', true); - } - } else { - if (normalizedStatus === 'running') { - responseElement.textContent = typeof window.t === 'function' ? window.t('mcpDetailModal.runningNoResponseYet') : '尚无返回,工具可能仍在执行。若长时间无响应,可在下方终止本次调用。'; - } else { - responseElement.textContent = typeof window.t === 'function' ? window.t('chat.noResponseData') : '暂无响应数据'; - } - setMCPResultDetailTabs('raw', false); - } - - const abortSection = document.getElementById('detail-abort-section'); - const abortBtn = document.getElementById('detail-abort-btn'); - if (abortSection && abortBtn) { - if (normalizedStatus === 'running') { - abortSection.style.display = 'block'; - abortBtn.dataset.execId = exec.id || ''; - abortBtn.textContent = typeof window.t === 'function' ? window.t('mcpDetailModal.abortBtn') : '终止工具'; - } else { - abortSection.style.display = 'none'; - delete abortBtn.dataset.execId; - } - } + renderMCPDetailModal(exec); }); } catch (error) { closeMCPDetail(); diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 1b67d6b2..34f3eeca 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -1281,7 +1281,6 @@ function getAssistantId() { // 将进度详情集成到工具调用区域(流式阶段助手消息不挂 mcp 条,结束时在此创建,避免图二整行 MCP 芯片样式) function integrateProgressToMCPSection(progressId, assistantMessageId, mcpExecutionIds) { const progressElement = document.getElementById(progressId); - if (!progressElement) return; // 只 flush 终态标题/正文;完成后的详情回看统一走后端分页,不再复制实时 DOM 快照。 flushAllPendingStreamPlainUpdates(); @@ -2001,7 +2000,8 @@ function handleStreamEvent(event, progressElement, progressId, } const timeline = resolveStreamTimeline(progressId); - if (!timeline) return; + const canHandleWithoutTimeline = ['conversation', 'response', 'error', 'cancelled', 'done'].includes(String(event.type || '')); + if (!timeline && !canHandleWithoutTimeline) return; // 终态事件(error/cancelled)优先复用现有助手消息,避免重复追加相同报错 const upsertTerminalAssistantMessage = (message, preferredMessageId = null) => { @@ -2735,11 +2735,13 @@ function handleStreamEvent(event, progressElement, progressId, case 'cancelled': const taskCancelledText = typeof window.t === 'function' ? window.t('chat.taskCancelled') : '任务已取消'; - addTimelineItem(timeline, 'cancelled', { - title: '⛔ ' + taskCancelledText, - message: event.message, - data: event.data - }); + if (timeline) { + addTimelineItem(timeline, 'cancelled', { + title: '⛔ ' + taskCancelledText, + message: event.message, + data: event.data + }); + } const cancelTitle = document.querySelector(`#${progressId} .progress-title`); if (cancelTitle) { cancelTitle.textContent = '⛔ ' + taskCancelledText; @@ -2922,7 +2924,7 @@ function handleStreamEvent(event, progressElement, progressId, // 将 response_start/response_delta 占位固化为 planning,与后端落库一致后再快照过程详情 if (streamState && streamState.itemId) { finalizeMainResponseStreamItem(streamState, event.message, responseData); - } else if (bubbleText && String(bubbleText).trim() && !isEinoEmptyResponsePlaceholder(event.message)) { + } else if (timeline && bubbleText && String(bubbleText).trim() && !isEinoEmptyResponsePlaceholder(event.message)) { addTimelineItem(timeline, 'planning', { title: typeof einoMainStreamPlanningTitle === 'function' ? einoMainStreamPlanningTitle(responseData) @@ -2972,11 +2974,13 @@ function handleStreamEvent(event, progressElement, progressId, case 'error': // 显示错误 - addTimelineItem(timeline, 'error', { - title: '❌ ' + (typeof window.t === 'function' ? window.t('chat.error') : '错误'), - message: event.message, - data: event.data - }); + if (timeline) { + addTimelineItem(timeline, 'error', { + title: '❌ ' + (typeof window.t === 'function' ? window.t('chat.error') : '错误'), + message: event.message, + data: event.data + }); + } // 更新进度标题为错误状态 const errorTitle = document.querySelector(`#${progressId} .progress-title`); @@ -3799,6 +3803,11 @@ function buildToolResultSectionHtml(data, opts) { const toolCallDetailStateByItemId = new Map(); +function getToolCallDetailState(item) { + if (!item || !item.id) return {}; + return Object.assign({}, toolCallDetailStateByItemId.get(item.id) || {}); +} + function setToolCallDetailState(item, state) { if (!item || !item.id) return; toolCallDetailStateByItemId.set(item.id, Object.assign({}, state || {})); @@ -3807,6 +3816,8 @@ function setToolCallDetailState(item, state) { updateToolDetailToggleLabel(item); } +window.getToolCallDetailState = getToolCallDetailState; + function toolDetailToggleText(expanded) { if (typeof window.t === 'function') { return expanded @@ -6577,7 +6588,9 @@ function reconcileMonitorExecutionRows(tbody, rowEntries) { const nextRow = createMonitorExecutionRow(entry); if (!nextRow) return; if (row && row.parentNode === tbody) { + const replacingCursor = row === cursor; row.replaceWith(nextRow); + if (replacingCursor) cursor = nextRow; } row = nextRow; existingById.set(entry.id, row);