diff --git a/web/static/css/style.css b/web/static/css/style.css index 3de68c95..70230c4b 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -2419,6 +2419,9 @@ header { padding-top: 12px; border-top: 1px solid var(--border-color); width: 100%; + min-width: 0; + max-width: 100%; + box-sizing: border-box; } .mcp-call-label { @@ -2473,10 +2476,15 @@ header { padding-top: 12px; border-top: 1px solid var(--border-color); width: 100%; + min-width: 0; + max-width: 100%; + box-sizing: border-box; } .process-details-content { width: 100%; + min-width: 0; + max-width: 100%; } .process-details-content .progress-timeline { @@ -2488,6 +2496,7 @@ header { .process-details-content .progress-timeline.expanded { max-height: 2000px; + overflow-x: hidden; overflow-y: auto; opacity: 1; margin-top: 12px; @@ -3913,6 +3922,7 @@ header { .progress-timeline.expanded { max-height: 2000px; + overflow-x: hidden; overflow-y: auto; } @@ -3920,7 +3930,8 @@ header { .progress-container.is-streaming .progress-timeline.expanded, .process-details-container.is-streaming .process-details-content .progress-timeline.expanded { max-height: none; - overflow: visible; + overflow-x: hidden; + overflow-y: visible; } .timeline-item { @@ -3931,6 +3942,10 @@ header { background: var(--bg-secondary); border-radius: 4px; transition: all 0.2s; + min-width: 0; + max-width: 100%; + overflow: hidden; + box-sizing: border-box; } .timeline-item:hover { @@ -4088,6 +4103,12 @@ header { font-size: 0.875rem; color: var(--text-secondary); line-height: 1.6; + min-width: 0; + max-width: 100%; + overflow-wrap: break-word; + word-break: break-word; + overflow-x: auto; + box-sizing: border-box; } /* 流式增量阶段纯文本展示(避免半段 Markdown 反复解析) */ @@ -4096,6 +4117,37 @@ header { word-break: break-word; } +/* 过程详情 Markdown:避免长 URL/代码/表格撑破紫/蓝时间线条目 */ +.timeline-item-content p, +.timeline-item-content li, +.timeline-item-content td, +.timeline-item-content th { + overflow-wrap: break-word; + word-break: break-word; +} + +.timeline-item-content pre { + max-width: 100%; + overflow-x: auto; + white-space: pre-wrap; + word-break: break-word; + box-sizing: border-box; +} + +.timeline-item-content code { + overflow-wrap: break-word; + word-break: break-word; +} + +.timeline-item-content .table-wrapper { + max-width: 100%; + overflow-x: auto; +} + +.timeline-item-content table { + max-width: 100%; +} + /* 长过程详情:跳过视口外时间线条目的布局/绘制,减轻大段工具输出时的主线程压力 */ .progress-timeline .timeline-item { content-visibility: auto; @@ -14649,6 +14701,7 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible { } .webshell-ai-process-block .process-details-content .progress-timeline.expanded { max-height: 2000px; + overflow-x: hidden; overflow-y: auto; } diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 1d2cae5b..ecabf5fd 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -172,6 +172,59 @@ function einoMainStreamPlanningTitle(responseData) { return prefix + '📝 ' + plan; } +/** + * 主通道 response 结束时:将流式占位条目固化为 planning(与后端 flushResponsePlan 落库类型一致), + * 避免 integrateProgressToMCPSection 快照前删除占位导致「助手输出」仅刷新后才出现。 + */ +function finalizeMainResponseStreamItem(streamState, finalMessage, responseData) { + if (!streamState || !streamState.itemId) return false; + const item = document.getElementById(streamState.itemId); + if (!item || !item.parentNode) return false; + + const fullText = (finalMessage != null && String(finalMessage).trim() !== '') + ? String(finalMessage) + : (streamState.buffer || ''); + if (!String(fullText).trim()) { + item.parentNode.removeChild(item); + return false; + } + + const meta = Object.assign({}, streamState.streamMeta || {}, responseData || {}); + + item.classList.remove('timeline-item-thinking'); + item.classList.add('timeline-item-planning'); + item.dataset.timelineType = 'planning'; + delete item.dataset.responseStreamPlaceholder; + if (meta.orchestration != null && String(meta.orchestration).trim() !== '') { + item.dataset.orchestration = String(meta.orchestration).trim(); + } + if (meta.einoAgent != null && String(meta.einoAgent).trim() !== '') { + item.dataset.einoAgent = String(meta.einoAgent).trim(); + } + + const titleEl = item.querySelector('.timeline-item-title'); + if (titleEl && typeof einoMainStreamPlanningTitle === 'function') { + titleEl.textContent = einoMainStreamPlanningTitle(meta); + } + + let contentEl = item.querySelector('.timeline-item-content'); + if (!contentEl) { + contentEl = document.createElement('div'); + contentEl.className = 'timeline-item-content'; + item.appendChild(contentEl); + } + flushStreamPlainTextUpdate(contentEl); + const body = typeof formatTimelineStreamBody === 'function' + ? formatTimelineStreamBody(fullText, meta) + : fullText; + if (typeof formatMarkdown === 'function') { + setTimelineItemContentStreamRich(contentEl, formatMarkdown(body, timelineMarkdownOpts)); + } else { + setTimelineItemContentStreamPlain(contentEl, body); + } + return true; +} + function translateProgressMessage(message, data) { if (!message || typeof message !== 'string') return message; if (typeof window.t !== 'function') return message; @@ -224,6 +277,7 @@ if (typeof window !== 'undefined') { window.translateProgressMessage = translateProgressMessage; window.translatePlanExecuteAgentName = translatePlanExecuteAgentName; window.einoMainStreamPlanningTitle = einoMainStreamPlanningTitle; + window.finalizeMainResponseStreamItem = finalizeMainResponseStreamItem; window.formatTimelineStreamBody = formatTimelineStreamBody; } @@ -2401,14 +2455,18 @@ function handleStreamEvent(event, progressElement, progressId, updateAssistantBubbleContent(assistantIdFinal, event.message, true); } - // 移除 response_start/response_delta 阶段创建的「规划中」占位条目。 - // 该条目属于 UI-only 的流式展示,不应被拷贝到最终的过程详情里; - // 否则会出现“不刷新页面仍显示规划中,刷新后消失”的不一致。 + // 将 response_start/response_delta 占位固化为 planning,与后端落库一致后再快照过程详情 if (streamState && streamState.itemId) { - const planningItem = document.getElementById(streamState.itemId); - if (planningItem && planningItem.parentNode) { - planningItem.parentNode.removeChild(planningItem); - } + finalizeMainResponseStreamItem(streamState, event.message, responseData); + } else if (event.message && String(event.message).trim()) { + addTimelineItem(timeline, 'planning', { + title: typeof einoMainStreamPlanningTitle === 'function' + ? einoMainStreamPlanningTitle(responseData) + : ('📝 ' + (typeof window.t === 'function' ? window.t('chat.planning') : '规划中')), + message: event.message, + data: responseData, + expanded: false + }); } // 最终回复时隐藏进度卡片(多代理模式下,迭代过程已完整展示)