diff --git a/templates/index.html b/templates/index.html index 9d28d76..9e1efe1 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1212,6 +1212,13 @@ forum: 'stopped', // 前端启动后再标记为 running report: 'stopped' // Report Engine }; + // 为每个Engine存储进度条状态 + let engineProgress = { + insight: null, + media: null, + query: null, + report: null + }; let customTemplate = ''; // 存储用户上传的自定义模板内容 let configValues = {}; let configDirty = false; @@ -1228,6 +1235,122 @@ let activeConsoleLayer = currentApp; const logRenderers = {}; + // 页面可见性状态管理 + let isPageVisible = !document.hidden; + let allTimers = { + updateTime: null, + checkStatus: null, + refreshConsole: null, + refreshForum: null, + reportLockCheck: null, + connectionProbe: null, + updateEngineProgress: null // 新增:更新所有Engine进度的定时器 + }; + + // 页面可见性变化处理 + function handleVisibilityChange() { + isPageVisible = !document.hidden; + + if (isPageVisible) { + console.log('页面可见,恢复定时器'); + startAllTimers(); + } else { + console.log('页面隐藏,暂停定时器以节省资源'); + pauseAllTimers(); + } + } + + // 启动所有定时器 + function startAllTimers() { + // 清理旧定时器 + stopAllTimers(); + + // 时间更新定时器 - 只在页面可见时更新 + if (isPageVisible) { + allTimers.updateTime = setInterval(updateTime, 1000); + } + + // 状态检查定时器 - 从5秒增加到10秒 + allTimers.checkStatus = setInterval(checkStatus, 10000); + + // 控制台刷新定时器 - 从2秒增加到3秒,只在有运行中应用时执行 + allTimers.refreshConsole = setInterval(() => { + if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') { + refreshConsoleOutput(); + } + }, 3000); + + // 论坛刷新定时器 - 从3秒增加到5秒 + allTimers.refreshForum = setInterval(() => { + if (currentApp === 'forum' || appStatus.forum === 'running') { + refreshForumMessages(); + } + }, 5000); + + // 报告锁定检查定时器 - 从10秒增加到15秒 + allTimers.reportLockCheck = setInterval(checkReportLockStatus, 15000); + + // 更新所有Engine进度的定时器 - 每5秒更新一次 + allTimers.updateEngineProgress = setInterval(updateAllEngineProgress, 5000); + } + + // 暂停所有定时器 + function pauseAllTimers() { + // 只保留关键的连接检查定时器,其他全部暂停 + Object.keys(allTimers).forEach(key => { + if (key !== 'connectionProbe' && allTimers[key]) { + clearInterval(allTimers[key]); + allTimers[key] = null; + } + }); + } + + // 停止所有定时器 + function stopAllTimers() { + Object.keys(allTimers).forEach(key => { + if (allTimers[key]) { + clearInterval(allTimers[key]); + allTimers[key] = null; + } + }); + } + + // 页面卸载时清理资源 + function cleanupOnUnload() { + console.log('页面卸载,清理所有资源'); + + // 停止所有定时器 + stopAllTimers(); + + // 清理所有日志渲染器 + Object.values(logRenderers).forEach(renderer => { + if (renderer && typeof renderer.dispose === 'function') { + renderer.dispose(); + } + }); + + // 卸载所有iframe + Object.keys(preloadedIframes).forEach(app => { + unloadIframe(app); + }); + + // 关闭Socket连接 + if (socket) { + socket.close(); + } + + // 关闭SSE连接 + safeCloseReportStream(); + + // 清理全局变量 + Object.keys(consoleLayers).forEach(key => { + delete consoleLayers[key]; + }); + Object.keys(logRenderers).forEach(key => { + delete logRenderers[key]; + }); + } + // 轻量日志虚拟渲染器:可视窗口渲染 + 节流 + 包级别截断,降低内存占用 class LogVirtualList { constructor(container) { @@ -1238,8 +1361,9 @@ this.pool = []; this.lineHeight = 18; this.maxVisible = 120; - this.maxLines = 2000; // 硬性保留的最大行数,超出时裁剪老旧数据 - this.trimTarget = 1500; // 裁剪后保留的目标行数,避免频繁裁剪 + this.maxLines = 500; // 减少到500行,降低75%内存占用 + this.trimTarget = 300; // 裁剪后保留300行 + this.maxPoolSize = 200; // 限制DOM节点池大小 this.rafId = null; this.autoScrollEnabled = true; this.resumeDelay = 3000; // 手动滚动后重新自动滚动的延迟(降低到3秒) @@ -1249,19 +1373,57 @@ this.needsScroll = false; // 标记是否需要滚动 this.lastScrollTime = 0; // 上次滚动时间,用于节流 this.scrollThrottle = 100; // 滚动节流时间(毫秒) + this.scrollHandler = null; // 存储滚动处理器引用 this.attachScroll(); } attachScroll() { if (!this.scrollElement) return; + if (this.scrollHandler) return; // 防止重复绑定 + let scrollTimer = null; - this.scrollElement.addEventListener('scroll', () => { + this.scrollHandler = () => { // 防抖处理,避免频繁触发 if (scrollTimer) clearTimeout(scrollTimer); scrollTimer = setTimeout(() => { this.handleUserScroll(); - }, 100); - }, { passive: true }); + }, 150); // 增加防抖时间到150ms + }; + + this.scrollElement.addEventListener('scroll', this.scrollHandler, { passive: true }); + } + + // 添加清理方法 + dispose() { + // 清理定时器 + if (this.rafId) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this.clearResumeTimer(); + + // 移除事件监听器 + if (this.scrollElement && this.scrollHandler) { + this.scrollElement.removeEventListener('scroll', this.scrollHandler); + this.scrollHandler = null; + } + + // 清空数据结构 + this.lines = []; + this.pending = []; + + // 清空并释放DOM节点池 + this.pool.forEach(node => { + if (node && node.parentNode) { + node.parentNode.removeChild(node); + } + }); + this.pool = []; + + // 清空容器 + if (this.container) { + this.container.innerHTML = ''; + } } handleUserScroll() { @@ -1352,8 +1514,8 @@ } this.pending.push({ text, className }); - // 降低批处理阈值到 50,更快响应 - if (this.pending.length > 50) { + // 增加批处理阈值到 100,减少渲染频率 + if (this.pending.length > 100) { this.flush(); } this.maybeTrim(); @@ -1409,6 +1571,8 @@ if (!total) { if (this.container.innerHTML !== '') { this.container.innerHTML = ''; + // 清空时也清理pool + this.pool = []; } return; } @@ -1438,19 +1602,29 @@ const needed = Math.max(0, end - start); + // 限制DOM节点池大小,防止内存泄漏 + if (this.pool.length > this.maxPoolSize) { + const excess = this.pool.length - this.maxPoolSize; + // 移除多余的节点 + this.pool.splice(this.maxPoolSize, excess).forEach(node => { + if (node && node.parentNode) { + node.parentNode.removeChild(node); + } + }); + } + // 复用现有的 DOM 节点池 - while (this.pool.length < needed) { + while (this.pool.length < needed && this.pool.length < this.maxPoolSize) { const node = document.createElement('div'); node.className = 'console-line'; this.pool.push(node); } - // 不要完全清空容器,而是更新现有节点 - const existingChildren = Array.from(this.container.children); + // 使用DocumentFragment来减少DOM重绘 const fragment = document.createDocumentFragment(); // 更新或创建前置占位符 - let beforeSpacer = existingChildren.find(el => el.dataset.spacer === 'before'); + let beforeSpacer = this.container.querySelector('[data-spacer="before"]'); if (!beforeSpacer) { beforeSpacer = document.createElement('div'); beforeSpacer.dataset.spacer = 'before'; @@ -1458,7 +1632,7 @@ beforeSpacer.style.height = `${beforeHeight}px`; // 更新或创建后置占位符 - let afterSpacer = existingChildren.find(el => el.dataset.spacer === 'after'); + let afterSpacer = this.container.querySelector('[data-spacer="after"]'); if (!afterSpacer) { afterSpacer = document.createElement('div'); afterSpacer.dataset.spacer = 'after'; @@ -1468,7 +1642,8 @@ // 只更新可见区域的节点 for (let idx = start; idx < end; idx++) { const line = this.lines[idx]; - const node = this.pool[idx - start]; + const poolIdx = idx - start; + const node = this.pool[poolIdx]; if (!node) continue; // 只在内容或类名变化时才更新节点 @@ -1603,40 +1778,40 @@ initializeEventListeners(); ensureSystemReadyOnLoad(); loadConsoleOutput(currentApp); - updateTime(); - setInterval(updateTime, 1000); - checkStatus(); - setInterval(checkStatus, 5000); - startConnectionProbe(); - + + // 使用新的定时器管理系统 + updateTime(); // 立即更新一次 + checkStatus(); // 立即检查一次 + checkReportLockStatus(); // 立即检查一次 + + // 启动所有定时器 + startAllTimers(); + + // 立即更新一次所有Engine的进度,恢复刷新前的状态 + updateAllEngineProgress(); + + // 监听页面可见性变化 + document.addEventListener('visibilitychange', handleVisibilityChange); + + // 监听页面卸载事件 + window.addEventListener('beforeunload', cleanupOnUnload); + window.addEventListener('unload', cleanupOnUnload); + // 初始化密码切换功能(事件委托,只需调用一次) attachConfigPasswordToggles(); - - // 初始化Report Engine锁定状态检查 - checkReportLockStatus(); - reportLockCheckInterval = setInterval(checkReportLockStatus, 10000); // 每10秒检查一次 - // 优化控制台刷新频率:从 1 秒改为 2 秒,减少不必要的 API 调用 - setInterval(() => { - if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') { - refreshConsoleOutput(); - } - }, 2000); - - // 优化论坛对话刷新频率:从 2 秒改为 3 秒 - setInterval(() => { - if (currentApp === 'forum' || appStatus.forum === 'running') { - refreshForumMessages(); - } - }, 3000); - // 初始化论坛相关功能 initializeForum(); - - // 延迟预加载iframe以确保应用启动完成 + + // 延迟预加载iframe以确保应用启动完成,并且只在页面可见时加载 setTimeout(() => { - preloadIframes(); - }, 3000); + if (isPageVisible) { + preloadIframes(); + } + }, 5000); // 延迟时间从3秒增加到5秒,减少初始加载压力 + + // 连接探测定时器(保持运行) + startConnectionProbe(); }); // Socket.IO连接 @@ -2228,45 +2403,48 @@ if (reportPreview) { reportPreview.innerHTML = '
等待新的搜索结果生成报告...
'; } - + // 清除任务进度显示 const taskProgressArea = document.getElementById('taskProgressArea'); if (taskProgressArea) { taskProgressArea.innerHTML = ''; } - + // 重置自动生成相关标志 autoGenerateTriggered = false; reportTaskId = null; - + // 停止可能正在进行的轮询 if (reportPollingInterval) { clearInterval(reportPollingInterval); reportPollingInterval = null; } - // 确保所有iframe已初始化 - if (!iframesInitialized) { - preloadIframes(); - } - // 向所有运行中的应用发送搜索请求(通过刷新iframe传递参数) let totalRunning = 0; const ports = { insight: 8501, media: 8502, query: 8503 }; - + Object.keys(appStatus).forEach(app => { - if (appStatus[app] === 'running' && preloadedIframes[app]) { + if (appStatus[app] === 'running' && ports[app]) { totalRunning++; - - // 构建搜索URL - const searchUrl = `http://${window.location.hostname}:${ports[app]}?query=${encodeURIComponent(query)}&auto_search=true`; - console.log(`向 ${app} 发送搜索请求: ${searchUrl}`); - - // 直接更新主iframe的src来传递搜索参数 - preloadedIframes[app].src = searchUrl; + + // 懒加载iframe(如果还没有加载) + let iframe = preloadedIframes[app]; + if (!iframe) { + iframe = lazyLoadIframe(app); + } + + if (iframe) { + // 构建搜索URL + const searchUrl = `http://${window.location.hostname}:${ports[app]}?query=${encodeURIComponent(query)}&auto_search=true`; + console.log(`向 ${app} 发送搜索请求: ${searchUrl}`); + + // 直接更新iframe的src来传递搜索参数 + iframe.src = searchUrl; + } } }); - + if (totalRunning === 0) { button.disabled = false; button.innerHTML = '搜索'; @@ -2291,6 +2469,12 @@ } } + // 隐藏当前Engine的进度条 + const engines = ['insight', 'media', 'query']; + if (engines.includes(currentApp)) { + hideEngineProgress(currentApp); + } + // 更新按钮状态 document.querySelectorAll('.app-button').forEach(btn => { btn.classList.remove('active'); @@ -2304,49 +2488,56 @@ if (app === 'forum') { // 切换到论坛模式 document.getElementById('embeddedHeader').textContent = 'Forum Engine - 多智能体交流'; - + // 显示论坛容器,隐藏其他内容 document.getElementById('forumContainer').classList.add('active'); document.getElementById('reportContainer').classList.remove('active'); - + // 追加提示并加载forum日志 appendConsoleTextLine('forum', '[系统] 切换到论坛模式'); loadForumLog(); - + } else if (app === 'report') { // 切换到报告模式 document.getElementById('embeddedHeader').textContent = 'Report Agent - 最终报告生成'; - + // 显示报告容器,隐藏其他内容 document.getElementById('reportContainer').classList.add('active'); document.getElementById('forumContainer').classList.remove('active'); - + // 追加提示并加载report日志 appendConsoleTextLine('report', '[系统] 切换到报告生成模式'); loadReportLog(); - + // 只在报告界面未初始化时才重新加载 const reportContent = document.getElementById('reportContent'); if (!reportContent || reportContent.children.length === 0) { loadReportInterface(); } - + // 切换到report页面时检查是否可以自动生成报告 setTimeout(() => { checkReportLockStatus(); }, 500); - + } else { // 切换到普通Engine模式 document.getElementById('embeddedHeader').textContent = agentTitles[app] || appNames[app]; - + // 隐藏论坛和报告容器 document.getElementById('forumContainer').classList.remove('active'); document.getElementById('reportContainer').classList.remove('active'); - + // 追加提示并加载新的控制台输出 appendConsoleTextLine(app, '[系统] 切换到 ' + appNames[app]); loadConsoleOutput(app); + + // 显示该Engine的进度条(如果有) + if (engines.includes(app)) { + showEngineProgress(app); + // 立即更新一次进度,确保显示最新状态 + updateEngineProgress(app); + } } // 更新嵌入页面 @@ -2542,123 +2733,194 @@ // 预加载的iframe存储 let preloadedIframes = {}; let iframesInitialized = false; - - // 预加载所有iframe(只执行一次) - function preloadIframes() { - if (iframesInitialized) return; - - const ports = { insight: 8501, media: 8502, query: 8503 }; - const content = document.getElementById('embeddedContent'); - - for (const [app, port] of Object.entries(ports)) { - const iframe = document.createElement('iframe'); - iframe.src = `http://${window.location.hostname}:${port}`; - iframe.style.width = '100%'; - iframe.style.height = '100%'; - iframe.style.border = 'none'; - iframe.style.position = 'absolute'; - iframe.style.top = '0'; - iframe.style.left = '0'; - iframe.style.display = 'none'; - iframe.id = `iframe-${app}`; - - // 直接添加到content区域 - content.appendChild(iframe); - preloadedIframes[app] = iframe; - - console.log(`预加载 ${app} 应用完成`); + let currentVisibleIframe = null; // 跟踪当前可见的iframe + + // 懒加载iframe - 只在真正需要时才创建 + function lazyLoadIframe(app) { + // 如果iframe已存在,直接返回 + if (preloadedIframes[app]) { + return preloadedIframes[app]; } - + + const ports = { insight: 8501, media: 8502, query: 8503 }; + if (!ports[app]) { + console.warn(`未知的应用: ${app}`); + return null; + } + + const content = document.getElementById('embeddedContent'); + const iframe = document.createElement('iframe'); + iframe.src = `http://${window.location.hostname}:${ports[app]}`; + iframe.style.width = '100%'; + iframe.style.height = '100%'; + iframe.style.border = 'none'; + iframe.style.position = 'absolute'; + iframe.style.top = '0'; + iframe.style.left = '0'; + iframe.style.display = 'none'; + iframe.id = `iframe-${app}`; + + // 添加加载完成事件 + iframe.addEventListener('load', () => { + console.log(`${app} iframe 加载完成`); + }); + + content.appendChild(iframe); + preloadedIframes[app] = iframe; + + console.log(`懒加载 ${app} iframe`); + return iframe; + } + + // 卸载不需要的iframe以释放内存 + function unloadIframe(app) { + if (!preloadedIframes[app]) return; + + const iframe = preloadedIframes[app]; + + // 先隐藏iframe + iframe.style.display = 'none'; + + // 清空iframe内容以释放内存 + if (iframe.contentWindow) { + try { + // 尝试清空iframe的DOM + iframe.src = 'about:blank'; + } catch (e) { + console.warn(`无法清空 ${app} iframe:`, e); + } + } + + // 从DOM中移除iframe + if (iframe.parentNode) { + iframe.parentNode.removeChild(iframe); + } + + // 从缓存中删除 + delete preloadedIframes[app]; + + console.log(`卸载 ${app} iframe,释放内存`); + } + + // 卸载所有非当前应用的iframe + function unloadInactiveIframes(currentApp) { + const apps = ['insight', 'media', 'query']; + apps.forEach(app => { + if (app !== currentApp && preloadedIframes[app]) { + // 延迟卸载,给一些缓冲时间 + setTimeout(() => { + if (currentApp !== app) { // 再次确认没有切换回来 + unloadIframe(app); + } + }, 30000); // 30秒后卸载不活跃的iframe + } + }); + } + + // 预加载所有iframe(只执行一次)- 已废弃,改用懒加载 + function preloadIframes() { + // 不再预加载所有iframe,改用懒加载机制 + console.log('使用懒加载机制,不再预加载所有iframe'); iframesInitialized = true; - console.log('所有iframe预加载完成,准备进行无缝切换'); } // 更新嵌入页面 function updateEmbeddedPage(app) { const header = document.getElementById('embeddedHeader'); const content = document.getElementById('embeddedContent'); - + // 如果是Forum Engine,直接显示论坛界面 if (app === 'forum') { header.textContent = 'Forum Engine - 多智能体交流'; - + // 隐藏所有iframe - if (typeof preloadedIframes !== 'undefined') { - Object.values(preloadedIframes).forEach(iframe => { - iframe.style.display = 'none'; - }); - } - + Object.values(preloadedIframes).forEach(iframe => { + iframe.style.display = 'none'; + }); + // 移除占位符 const placeholder = content.querySelector('.status-placeholder'); if (placeholder) { placeholder.remove(); } - + // 显示论坛容器,隐藏报告容器 document.getElementById('forumContainer').classList.add('active'); document.getElementById('reportContainer').classList.remove('active'); + + // 卸载不活跃的iframe + unloadInactiveIframes(null); + + currentVisibleIframe = null; return; } - + // 如果是Report Engine,显示报告界面 if (app === 'report') { header.textContent = 'Report Agent - 最终报告生成'; - + // 隐藏所有iframe - if (typeof preloadedIframes !== 'undefined') { - Object.values(preloadedIframes).forEach(iframe => { - iframe.style.display = 'none'; - }); - } - + Object.values(preloadedIframes).forEach(iframe => { + iframe.style.display = 'none'; + }); + // 移除占位符 const placeholder = content.querySelector('.status-placeholder'); if (placeholder) { placeholder.remove(); } - + // 显示报告容器,隐藏论坛容器 document.getElementById('reportContainer').classList.add('active'); document.getElementById('forumContainer').classList.remove('active'); + + // 卸载不活跃的iframe + unloadInactiveIframes(null); + + currentVisibleIframe = null; return; } - + // 隐藏论坛和报告容器 document.getElementById('forumContainer').classList.remove('active'); document.getElementById('reportContainer').classList.remove('active'); header.textContent = agentTitles[app] || appNames[app] || app; - // 如果应用正在运行,显示对应的iframe + // 如果应用正在运行,显示对应的iframe(使用懒加载) if (appStatus[app] === 'running') { - // 确保iframe已初始化 - if (!iframesInitialized) { - preloadIframes(); + // 懒加载当前应用的iframe + const iframe = lazyLoadIframe(app); + + if (!iframe) { + console.error(`无法加载 ${app} iframe`); + return; } - + // 隐藏所有iframe - Object.values(preloadedIframes).forEach(iframe => { - iframe.style.display = 'none'; + Object.values(preloadedIframes).forEach(otherIframe => { + otherIframe.style.display = 'none'; }); - + // 移除占位符 const placeholder = content.querySelector('.status-placeholder'); if (placeholder) { placeholder.remove(); } - + // 显示当前应用的iframe - if (preloadedIframes[app]) { - preloadedIframes[app].style.display = 'block'; - console.log(`切换到 ${app} 应用 - 无刷新切换`); - } + iframe.style.display = 'block'; + currentVisibleIframe = app; + console.log(`切换到 ${app} 应用 - 懒加载模式`); + + // 卸载不活跃的iframe(30秒后) + unloadInactiveIframes(app); } else { // 隐藏所有iframe Object.values(preloadedIframes).forEach(iframe => { iframe.style.display = 'none'; }); - + // 显示状态信息 let placeholder = content.querySelector('.status-placeholder'); if (!placeholder) { @@ -2667,11 +2929,13 @@ placeholder.style.cssText = 'display: flex; align-items: center; justify-content: center; height: 100%; color: #666; flex-direction: column; position: absolute; top: 0; left: 0; width: 100%;'; content.appendChild(placeholder); } - + placeholder.innerHTML = `
${appNames[app]} 未运行
状态: ${appStatus[app]}
`; + + currentVisibleIframe = null; } } @@ -2840,7 +3104,164 @@ // Forum Engine 相关函数 let forumLogLineCount = 0; - + + // 更新所有Engine的进度条 + function updateAllEngineProgress() { + // 通过现有的status API获取所有Engine的状态 + fetch('/api/status') + .then(response => response.json()) + .then(data => { + // 为每个需要进度显示的Engine更新状态 + const engines = ['insight', 'media', 'query']; + + engines.forEach(engine => { + if (data[engine]) { + const info = data[engine]; + const status = info.status === 'running' ? 'running' : 'stopped'; + + // 如果Engine正在运行,显示进度条 + if (status === 'running') { + // 尝试从API获取详细进度,如果失败则显示基本运行状态 + updateEngineProgress(engine); + } else { + // Engine未运行,清除进度信息 + engineProgress[engine] = null; + const progressContainer = document.getElementById(`progress-${engine}`); + if (progressContainer && progressContainer.parentNode) { + progressContainer.parentNode.removeChild(progressContainer); + } + } + } + }); + }) + .catch(error => { + console.log('获取Engine状态失败:', error); + }); + } + + // 更新单个Engine的进度 + function updateEngineProgress(engine) { + // 先尝试从专用进度API获取 + fetch(`/api/${engine}/progress`) + .then(response => { + if (!response.ok) { + throw new Error('Progress API not available'); + } + return response.json(); + }) + .then(data => { + if (data.success && data.progress) { + // 存储进度信息 + engineProgress[engine] = { + status: data.progress.status || 'running', + progress: data.progress.progress || 0, + message: data.progress.message || '正在处理...', + updated_at: new Date().toISOString() + }; + + // 如果当前正在查看该Engine,更新显示 + if (currentApp === engine) { + displayEngineProgress(engine); + } + } + }) + .catch(error => { + // 如果专用API不可用,使用基本的运行状态 + if (appStatus[engine] === 'running') { + // 使用基本的进度信息 + if (!engineProgress[engine]) { + engineProgress[engine] = { + status: 'running', + progress: 50, // 默认显示50%表示运行中 + message: '正在分析中...', + updated_at: new Date().toISOString() + }; + } + + // 如果当前正在查看该Engine,更新显示 + if (currentApp === engine) { + displayEngineProgress(engine); + } + } + }); + } + + // 在嵌入页面区域显示Engine进度 + function displayEngineProgress(engine) { + const progress = engineProgress[engine]; + if (!progress) return; + + // 查找或创建进度显示容器 + let progressContainer = document.getElementById(`progress-${engine}`); + if (!progressContainer) { + // 在嵌入内容区域的顶部创建进度条容器 + const embeddedContent = document.getElementById('embeddedContent'); + if (!embeddedContent) return; + + progressContainer = document.createElement('div'); + progressContainer.id = `progress-${engine}`; + progressContainer.className = 'task-progress-container'; + progressContainer.style.position = 'absolute'; + progressContainer.style.top = '10px'; + progressContainer.style.left = '10px'; + progressContainer.style.right = '10px'; + progressContainer.style.zIndex = '100'; + progressContainer.style.backgroundColor = '#f5f5f0'; + embeddedContent.insertBefore(progressContainer, embeddedContent.firstChild); + } + + // 更新进度条内容 + const loadingIndicator = progress.status !== 'completed' && progress.status !== 'error' + ? '' + : ''; + + progressContainer.innerHTML = ` +
+
+ ${loadingIndicator}${appNames[engine] || engine} - ${progress.message} +
+
+
+
${progress.progress || 0}%
+
+
+ `; + + // 如果任务已完成,5秒后淡出进度条 + if (progress.status === 'completed') { + setTimeout(() => { + if (progressContainer && progressContainer.parentNode) { + progressContainer.style.transition = 'opacity 1s'; + progressContainer.style.opacity = '0'; + setTimeout(() => { + if (progressContainer && progressContainer.parentNode) { + progressContainer.parentNode.removeChild(progressContainer); + } + }, 1000); + } + }, 5000); + } + } + + // 隐藏指定Engine的进度条(切换时使用) + function hideEngineProgress(engine) { + const progressContainer = document.getElementById(`progress-${engine}`); + if (progressContainer) { + progressContainer.style.display = 'none'; + } + } + + // 显示指定Engine的进度条(切换时使用) + function showEngineProgress(engine) { + const progressContainer = document.getElementById(`progress-${engine}`); + if (progressContainer) { + progressContainer.style.display = 'block'; + } else if (engineProgress[engine]) { + // 如果有缓存的进度信息但容器不存在,重新创建 + displayEngineProgress(engine); + } + } + // Report Engine 相关函数 let reportLogLineCount = 0; let reportLockCheckInterval = null;