From 85d75d6f741d59bca403330a5c6ef2e5ef581e0c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=A9=AC=E4=B8=80=E4=B8=81?= <1769123563@qq.com>
Date: Tue, 18 Nov 2025 13:53:15 +0800
Subject: [PATCH] Fixed the Front-End Progress Bar Display Logic
---
templates/index.html | 683 ++++++++++++++++++++++++++++++++++---------
1 file changed, 552 insertions(+), 131 deletions(-)
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 = `
+
+ `;
+
+ // 如果任务已完成,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;