马一丁

Fixed the Front-End Progress Bar Display Logic

@@ -1212,6 +1212,13 @@ @@ -1212,6 +1212,13 @@
1212 forum: 'stopped', // 前端启动后再标记为 running 1212 forum: 'stopped', // 前端启动后再标记为 running
1213 report: 'stopped' // Report Engine 1213 report: 'stopped' // Report Engine
1214 }; 1214 };
  1215 + // 为每个Engine存储进度条状态
  1216 + let engineProgress = {
  1217 + insight: null,
  1218 + media: null,
  1219 + query: null,
  1220 + report: null
  1221 + };
1215 let customTemplate = ''; // 存储用户上传的自定义模板内容 1222 let customTemplate = ''; // 存储用户上传的自定义模板内容
1216 let configValues = {}; 1223 let configValues = {};
1217 let configDirty = false; 1224 let configDirty = false;
@@ -1228,6 +1235,122 @@ @@ -1228,6 +1235,122 @@
1228 let activeConsoleLayer = currentApp; 1235 let activeConsoleLayer = currentApp;
1229 const logRenderers = {}; 1236 const logRenderers = {};
1230 1237
  1238 + // 页面可见性状态管理
  1239 + let isPageVisible = !document.hidden;
  1240 + let allTimers = {
  1241 + updateTime: null,
  1242 + checkStatus: null,
  1243 + refreshConsole: null,
  1244 + refreshForum: null,
  1245 + reportLockCheck: null,
  1246 + connectionProbe: null,
  1247 + updateEngineProgress: null // 新增:更新所有Engine进度的定时器
  1248 + };
  1249 +
  1250 + // 页面可见性变化处理
  1251 + function handleVisibilityChange() {
  1252 + isPageVisible = !document.hidden;
  1253 +
  1254 + if (isPageVisible) {
  1255 + console.log('页面可见,恢复定时器');
  1256 + startAllTimers();
  1257 + } else {
  1258 + console.log('页面隐藏,暂停定时器以节省资源');
  1259 + pauseAllTimers();
  1260 + }
  1261 + }
  1262 +
  1263 + // 启动所有定时器
  1264 + function startAllTimers() {
  1265 + // 清理旧定时器
  1266 + stopAllTimers();
  1267 +
  1268 + // 时间更新定时器 - 只在页面可见时更新
  1269 + if (isPageVisible) {
  1270 + allTimers.updateTime = setInterval(updateTime, 1000);
  1271 + }
  1272 +
  1273 + // 状态检查定时器 - 从5秒增加到10秒
  1274 + allTimers.checkStatus = setInterval(checkStatus, 10000);
  1275 +
  1276 + // 控制台刷新定时器 - 从2秒增加到3秒,只在有运行中应用时执行
  1277 + allTimers.refreshConsole = setInterval(() => {
  1278 + if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') {
  1279 + refreshConsoleOutput();
  1280 + }
  1281 + }, 3000);
  1282 +
  1283 + // 论坛刷新定时器 - 从3秒增加到5秒
  1284 + allTimers.refreshForum = setInterval(() => {
  1285 + if (currentApp === 'forum' || appStatus.forum === 'running') {
  1286 + refreshForumMessages();
  1287 + }
  1288 + }, 5000);
  1289 +
  1290 + // 报告锁定检查定时器 - 从10秒增加到15秒
  1291 + allTimers.reportLockCheck = setInterval(checkReportLockStatus, 15000);
  1292 +
  1293 + // 更新所有Engine进度的定时器 - 每5秒更新一次
  1294 + allTimers.updateEngineProgress = setInterval(updateAllEngineProgress, 5000);
  1295 + }
  1296 +
  1297 + // 暂停所有定时器
  1298 + function pauseAllTimers() {
  1299 + // 只保留关键的连接检查定时器,其他全部暂停
  1300 + Object.keys(allTimers).forEach(key => {
  1301 + if (key !== 'connectionProbe' && allTimers[key]) {
  1302 + clearInterval(allTimers[key]);
  1303 + allTimers[key] = null;
  1304 + }
  1305 + });
  1306 + }
  1307 +
  1308 + // 停止所有定时器
  1309 + function stopAllTimers() {
  1310 + Object.keys(allTimers).forEach(key => {
  1311 + if (allTimers[key]) {
  1312 + clearInterval(allTimers[key]);
  1313 + allTimers[key] = null;
  1314 + }
  1315 + });
  1316 + }
  1317 +
  1318 + // 页面卸载时清理资源
  1319 + function cleanupOnUnload() {
  1320 + console.log('页面卸载,清理所有资源');
  1321 +
  1322 + // 停止所有定时器
  1323 + stopAllTimers();
  1324 +
  1325 + // 清理所有日志渲染器
  1326 + Object.values(logRenderers).forEach(renderer => {
  1327 + if (renderer && typeof renderer.dispose === 'function') {
  1328 + renderer.dispose();
  1329 + }
  1330 + });
  1331 +
  1332 + // 卸载所有iframe
  1333 + Object.keys(preloadedIframes).forEach(app => {
  1334 + unloadIframe(app);
  1335 + });
  1336 +
  1337 + // 关闭Socket连接
  1338 + if (socket) {
  1339 + socket.close();
  1340 + }
  1341 +
  1342 + // 关闭SSE连接
  1343 + safeCloseReportStream();
  1344 +
  1345 + // 清理全局变量
  1346 + Object.keys(consoleLayers).forEach(key => {
  1347 + delete consoleLayers[key];
  1348 + });
  1349 + Object.keys(logRenderers).forEach(key => {
  1350 + delete logRenderers[key];
  1351 + });
  1352 + }
  1353 +
1231 // 轻量日志虚拟渲染器:可视窗口渲染 + 节流 + 包级别截断,降低内存占用 1354 // 轻量日志虚拟渲染器:可视窗口渲染 + 节流 + 包级别截断,降低内存占用
1232 class LogVirtualList { 1355 class LogVirtualList {
1233 constructor(container) { 1356 constructor(container) {
@@ -1238,8 +1361,9 @@ @@ -1238,8 +1361,9 @@
1238 this.pool = []; 1361 this.pool = [];
1239 this.lineHeight = 18; 1362 this.lineHeight = 18;
1240 this.maxVisible = 120; 1363 this.maxVisible = 120;
1241 - this.maxLines = 2000; // 硬性保留的最大行数,超出时裁剪老旧数据  
1242 - this.trimTarget = 1500; // 裁剪后保留的目标行数,避免频繁裁剪 1364 + this.maxLines = 500; // 减少到500行,降低75%内存占用
  1365 + this.trimTarget = 300; // 裁剪后保留300行
  1366 + this.maxPoolSize = 200; // 限制DOM节点池大小
1243 this.rafId = null; 1367 this.rafId = null;
1244 this.autoScrollEnabled = true; 1368 this.autoScrollEnabled = true;
1245 this.resumeDelay = 3000; // 手动滚动后重新自动滚动的延迟(降低到3秒) 1369 this.resumeDelay = 3000; // 手动滚动后重新自动滚动的延迟(降低到3秒)
@@ -1249,19 +1373,57 @@ @@ -1249,19 +1373,57 @@
1249 this.needsScroll = false; // 标记是否需要滚动 1373 this.needsScroll = false; // 标记是否需要滚动
1250 this.lastScrollTime = 0; // 上次滚动时间,用于节流 1374 this.lastScrollTime = 0; // 上次滚动时间,用于节流
1251 this.scrollThrottle = 100; // 滚动节流时间(毫秒) 1375 this.scrollThrottle = 100; // 滚动节流时间(毫秒)
  1376 + this.scrollHandler = null; // 存储滚动处理器引用
1252 this.attachScroll(); 1377 this.attachScroll();
1253 } 1378 }
1254 1379
1255 attachScroll() { 1380 attachScroll() {
1256 if (!this.scrollElement) return; 1381 if (!this.scrollElement) return;
  1382 + if (this.scrollHandler) return; // 防止重复绑定
  1383 +
1257 let scrollTimer = null; 1384 let scrollTimer = null;
1258 - this.scrollElement.addEventListener('scroll', () => { 1385 + this.scrollHandler = () => {
1259 // 防抖处理,避免频繁触发 1386 // 防抖处理,避免频繁触发
1260 if (scrollTimer) clearTimeout(scrollTimer); 1387 if (scrollTimer) clearTimeout(scrollTimer);
1261 scrollTimer = setTimeout(() => { 1388 scrollTimer = setTimeout(() => {
1262 this.handleUserScroll(); 1389 this.handleUserScroll();
1263 - }, 100);  
1264 - }, { passive: true }); 1390 + }, 150); // 增加防抖时间到150ms
  1391 + };
  1392 +
  1393 + this.scrollElement.addEventListener('scroll', this.scrollHandler, { passive: true });
  1394 + }
  1395 +
  1396 + // 添加清理方法
  1397 + dispose() {
  1398 + // 清理定时器
  1399 + if (this.rafId) {
  1400 + cancelAnimationFrame(this.rafId);
  1401 + this.rafId = null;
  1402 + }
  1403 + this.clearResumeTimer();
  1404 +
  1405 + // 移除事件监听器
  1406 + if (this.scrollElement && this.scrollHandler) {
  1407 + this.scrollElement.removeEventListener('scroll', this.scrollHandler);
  1408 + this.scrollHandler = null;
  1409 + }
  1410 +
  1411 + // 清空数据结构
  1412 + this.lines = [];
  1413 + this.pending = [];
  1414 +
  1415 + // 清空并释放DOM节点池
  1416 + this.pool.forEach(node => {
  1417 + if (node && node.parentNode) {
  1418 + node.parentNode.removeChild(node);
  1419 + }
  1420 + });
  1421 + this.pool = [];
  1422 +
  1423 + // 清空容器
  1424 + if (this.container) {
  1425 + this.container.innerHTML = '';
  1426 + }
1265 } 1427 }
1266 1428
1267 handleUserScroll() { 1429 handleUserScroll() {
@@ -1352,8 +1514,8 @@ @@ -1352,8 +1514,8 @@
1352 } 1514 }
1353 1515
1354 this.pending.push({ text, className }); 1516 this.pending.push({ text, className });
1355 - // 降低批处理阈值到 50,更快响应  
1356 - if (this.pending.length > 50) { 1517 + // 增加批处理阈值到 100,减少渲染频率
  1518 + if (this.pending.length > 100) {
1357 this.flush(); 1519 this.flush();
1358 } 1520 }
1359 this.maybeTrim(); 1521 this.maybeTrim();
@@ -1409,6 +1571,8 @@ @@ -1409,6 +1571,8 @@
1409 if (!total) { 1571 if (!total) {
1410 if (this.container.innerHTML !== '') { 1572 if (this.container.innerHTML !== '') {
1411 this.container.innerHTML = ''; 1573 this.container.innerHTML = '';
  1574 + // 清空时也清理pool
  1575 + this.pool = [];
1412 } 1576 }
1413 return; 1577 return;
1414 } 1578 }
@@ -1438,19 +1602,29 @@ @@ -1438,19 +1602,29 @@
1438 1602
1439 const needed = Math.max(0, end - start); 1603 const needed = Math.max(0, end - start);
1440 1604
  1605 + // 限制DOM节点池大小,防止内存泄漏
  1606 + if (this.pool.length > this.maxPoolSize) {
  1607 + const excess = this.pool.length - this.maxPoolSize;
  1608 + // 移除多余的节点
  1609 + this.pool.splice(this.maxPoolSize, excess).forEach(node => {
  1610 + if (node && node.parentNode) {
  1611 + node.parentNode.removeChild(node);
  1612 + }
  1613 + });
  1614 + }
  1615 +
1441 // 复用现有的 DOM 节点池 1616 // 复用现有的 DOM 节点池
1442 - while (this.pool.length < needed) { 1617 + while (this.pool.length < needed && this.pool.length < this.maxPoolSize) {
1443 const node = document.createElement('div'); 1618 const node = document.createElement('div');
1444 node.className = 'console-line'; 1619 node.className = 'console-line';
1445 this.pool.push(node); 1620 this.pool.push(node);
1446 } 1621 }
1447 1622
1448 - // 不要完全清空容器,而是更新现有节点  
1449 - const existingChildren = Array.from(this.container.children); 1623 + // 使用DocumentFragment来减少DOM重绘
1450 const fragment = document.createDocumentFragment(); 1624 const fragment = document.createDocumentFragment();
1451 1625
1452 // 更新或创建前置占位符 1626 // 更新或创建前置占位符
1453 - let beforeSpacer = existingChildren.find(el => el.dataset.spacer === 'before'); 1627 + let beforeSpacer = this.container.querySelector('[data-spacer="before"]');
1454 if (!beforeSpacer) { 1628 if (!beforeSpacer) {
1455 beforeSpacer = document.createElement('div'); 1629 beforeSpacer = document.createElement('div');
1456 beforeSpacer.dataset.spacer = 'before'; 1630 beforeSpacer.dataset.spacer = 'before';
@@ -1458,7 +1632,7 @@ @@ -1458,7 +1632,7 @@
1458 beforeSpacer.style.height = `${beforeHeight}px`; 1632 beforeSpacer.style.height = `${beforeHeight}px`;
1459 1633
1460 // 更新或创建后置占位符 1634 // 更新或创建后置占位符
1461 - let afterSpacer = existingChildren.find(el => el.dataset.spacer === 'after'); 1635 + let afterSpacer = this.container.querySelector('[data-spacer="after"]');
1462 if (!afterSpacer) { 1636 if (!afterSpacer) {
1463 afterSpacer = document.createElement('div'); 1637 afterSpacer = document.createElement('div');
1464 afterSpacer.dataset.spacer = 'after'; 1638 afterSpacer.dataset.spacer = 'after';
@@ -1468,7 +1642,8 @@ @@ -1468,7 +1642,8 @@
1468 // 只更新可见区域的节点 1642 // 只更新可见区域的节点
1469 for (let idx = start; idx < end; idx++) { 1643 for (let idx = start; idx < end; idx++) {
1470 const line = this.lines[idx]; 1644 const line = this.lines[idx];
1471 - const node = this.pool[idx - start]; 1645 + const poolIdx = idx - start;
  1646 + const node = this.pool[poolIdx];
1472 if (!node) continue; 1647 if (!node) continue;
1473 1648
1474 // 只在内容或类名变化时才更新节点 1649 // 只在内容或类名变化时才更新节点
@@ -1603,40 +1778,40 @@ @@ -1603,40 +1778,40 @@
1603 initializeEventListeners(); 1778 initializeEventListeners();
1604 ensureSystemReadyOnLoad(); 1779 ensureSystemReadyOnLoad();
1605 loadConsoleOutput(currentApp); 1780 loadConsoleOutput(currentApp);
1606 - updateTime();  
1607 - setInterval(updateTime, 1000);  
1608 - checkStatus();  
1609 - setInterval(checkStatus, 5000);  
1610 - startConnectionProbe();  
1611 1781
1612 - // 初始化密码切换功能(事件委托,只需调用一次)  
1613 - attachConfigPasswordToggles(); 1782 + // 使用新的定时器管理系统
  1783 + updateTime(); // 立即更新一次
  1784 + checkStatus(); // 立即检查一次
  1785 + checkReportLockStatus(); // 立即检查一次
1614 1786
1615 - // 初始化Report Engine锁定状态检查  
1616 - checkReportLockStatus();  
1617 - reportLockCheckInterval = setInterval(checkReportLockStatus, 10000); // 每10秒检查一次 1787 + // 启动所有定时器
  1788 + startAllTimers();
1618 1789
1619 - // 优化控制台刷新频率:从 1 秒改为 2 秒,减少不必要的 API 调用  
1620 - setInterval(() => {  
1621 - if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') {  
1622 - refreshConsoleOutput();  
1623 - }  
1624 - }, 2000); 1790 + // 立即更新一次所有Engine的进度,恢复刷新前的状态
  1791 + updateAllEngineProgress();
1625 1792
1626 - // 优化论坛对话刷新频率:从 2 秒改为 3 秒  
1627 - setInterval(() => {  
1628 - if (currentApp === 'forum' || appStatus.forum === 'running') {  
1629 - refreshForumMessages();  
1630 - }  
1631 - }, 3000); 1793 + // 监听页面可见性变化
  1794 + document.addEventListener('visibilitychange', handleVisibilityChange);
  1795 +
  1796 + // 监听页面卸载事件
  1797 + window.addEventListener('beforeunload', cleanupOnUnload);
  1798 + window.addEventListener('unload', cleanupOnUnload);
  1799 +
  1800 + // 初始化密码切换功能(事件委托,只需调用一次)
  1801 + attachConfigPasswordToggles();
1632 1802
1633 // 初始化论坛相关功能 1803 // 初始化论坛相关功能
1634 initializeForum(); 1804 initializeForum();
1635 1805
1636 - // 延迟预加载iframe以确保应用启动完成 1806 + // 延迟预加载iframe以确保应用启动完成,并且只在页面可见时加载
1637 setTimeout(() => { 1807 setTimeout(() => {
  1808 + if (isPageVisible) {
1638 preloadIframes(); 1809 preloadIframes();
1639 - }, 3000); 1810 + }
  1811 + }, 5000); // 延迟时间从3秒增加到5秒,减少初始加载压力
  1812 +
  1813 + // 连接探测定时器(保持运行)
  1814 + startConnectionProbe();
1640 }); 1815 });
1641 1816
1642 // Socket.IO连接 1817 // Socket.IO连接
@@ -2245,25 +2420,28 @@ @@ -2245,25 +2420,28 @@
2245 reportPollingInterval = null; 2420 reportPollingInterval = null;
2246 } 2421 }
2247 2422
2248 - // 确保所有iframe已初始化  
2249 - if (!iframesInitialized) {  
2250 - preloadIframes();  
2251 - }  
2252 -  
2253 // 向所有运行中的应用发送搜索请求(通过刷新iframe传递参数) 2423 // 向所有运行中的应用发送搜索请求(通过刷新iframe传递参数)
2254 let totalRunning = 0; 2424 let totalRunning = 0;
2255 const ports = { insight: 8501, media: 8502, query: 8503 }; 2425 const ports = { insight: 8501, media: 8502, query: 8503 };
2256 2426
2257 Object.keys(appStatus).forEach(app => { 2427 Object.keys(appStatus).forEach(app => {
2258 - if (appStatus[app] === 'running' && preloadedIframes[app]) { 2428 + if (appStatus[app] === 'running' && ports[app]) {
2259 totalRunning++; 2429 totalRunning++;
2260 2430
  2431 + // 懒加载iframe(如果还没有加载)
  2432 + let iframe = preloadedIframes[app];
  2433 + if (!iframe) {
  2434 + iframe = lazyLoadIframe(app);
  2435 + }
  2436 +
  2437 + if (iframe) {
2261 // 构建搜索URL 2438 // 构建搜索URL
2262 const searchUrl = `http://${window.location.hostname}:${ports[app]}?query=${encodeURIComponent(query)}&auto_search=true`; 2439 const searchUrl = `http://${window.location.hostname}:${ports[app]}?query=${encodeURIComponent(query)}&auto_search=true`;
2263 console.log(`向 ${app} 发送搜索请求: ${searchUrl}`); 2440 console.log(`向 ${app} 发送搜索请求: ${searchUrl}`);
2264 2441
2265 - // 直接更新主iframe的src来传递搜索参数  
2266 - preloadedIframes[app].src = searchUrl; 2442 + // 直接更新iframe的src来传递搜索参数
  2443 + iframe.src = searchUrl;
  2444 + }
2267 } 2445 }
2268 }); 2446 });
2269 2447
@@ -2291,6 +2469,12 @@ @@ -2291,6 +2469,12 @@
2291 } 2469 }
2292 } 2470 }
2293 2471
  2472 + // 隐藏当前Engine的进度条
  2473 + const engines = ['insight', 'media', 'query'];
  2474 + if (engines.includes(currentApp)) {
  2475 + hideEngineProgress(currentApp);
  2476 + }
  2477 +
2294 // 更新按钮状态 2478 // 更新按钮状态
2295 document.querySelectorAll('.app-button').forEach(btn => { 2479 document.querySelectorAll('.app-button').forEach(btn => {
2296 btn.classList.remove('active'); 2480 btn.classList.remove('active');
@@ -2347,6 +2531,13 @@ @@ -2347,6 +2531,13 @@
2347 // 追加提示并加载新的控制台输出 2531 // 追加提示并加载新的控制台输出
2348 appendConsoleTextLine(app, '[系统] 切换到 ' + appNames[app]); 2532 appendConsoleTextLine(app, '[系统] 切换到 ' + appNames[app]);
2349 loadConsoleOutput(app); 2533 loadConsoleOutput(app);
  2534 +
  2535 + // 显示该Engine的进度条(如果有)
  2536 + if (engines.includes(app)) {
  2537 + showEngineProgress(app);
  2538 + // 立即更新一次进度,确保显示最新状态
  2539 + updateEngineProgress(app);
  2540 + }
2350 } 2541 }
2351 2542
2352 // 更新嵌入页面 2543 // 更新嵌入页面
@@ -2542,17 +2733,24 @@ @@ -2542,17 +2733,24 @@
2542 // 预加载的iframe存储 2733 // 预加载的iframe存储
2543 let preloadedIframes = {}; 2734 let preloadedIframes = {};
2544 let iframesInitialized = false; 2735 let iframesInitialized = false;
  2736 + let currentVisibleIframe = null; // 跟踪当前可见的iframe
2545 2737
2546 - // 预加载所有iframe(只执行一次)  
2547 - function preloadIframes() {  
2548 - if (iframesInitialized) return; 2738 + // 懒加载iframe - 只在真正需要时才创建
  2739 + function lazyLoadIframe(app) {
  2740 + // 如果iframe已存在,直接返回
  2741 + if (preloadedIframes[app]) {
  2742 + return preloadedIframes[app];
  2743 + }
2549 2744
2550 const ports = { insight: 8501, media: 8502, query: 8503 }; 2745 const ports = { insight: 8501, media: 8502, query: 8503 };
2551 - const content = document.getElementById('embeddedContent'); 2746 + if (!ports[app]) {
  2747 + console.warn(`未知的应用: ${app}`);
  2748 + return null;
  2749 + }
2552 2750
2553 - for (const [app, port] of Object.entries(ports)) { 2751 + const content = document.getElementById('embeddedContent');
2554 const iframe = document.createElement('iframe'); 2752 const iframe = document.createElement('iframe');
2555 - iframe.src = `http://${window.location.hostname}:${port}`; 2753 + iframe.src = `http://${window.location.hostname}:${ports[app]}`;
2556 iframe.style.width = '100%'; 2754 iframe.style.width = '100%';
2557 iframe.style.height = '100%'; 2755 iframe.style.height = '100%';
2558 iframe.style.border = 'none'; 2756 iframe.style.border = 'none';
@@ -2562,15 +2760,68 @@ @@ -2562,15 +2760,68 @@
2562 iframe.style.display = 'none'; 2760 iframe.style.display = 'none';
2563 iframe.id = `iframe-${app}`; 2761 iframe.id = `iframe-${app}`;
2564 2762
2565 - // 直接添加到content区域 2763 + // 添加加载完成事件
  2764 + iframe.addEventListener('load', () => {
  2765 + console.log(`${app} iframe 加载完成`);
  2766 + });
  2767 +
2566 content.appendChild(iframe); 2768 content.appendChild(iframe);
2567 preloadedIframes[app] = iframe; 2769 preloadedIframes[app] = iframe;
2568 2770
2569 - console.log(`预加载 ${app} 应用完成`); 2771 + console.log(`懒加载 ${app} iframe`);
  2772 + return iframe;
  2773 + }
  2774 +
  2775 + // 卸载不需要的iframe以释放内存
  2776 + function unloadIframe(app) {
  2777 + if (!preloadedIframes[app]) return;
  2778 +
  2779 + const iframe = preloadedIframes[app];
  2780 +
  2781 + // 先隐藏iframe
  2782 + iframe.style.display = 'none';
  2783 +
  2784 + // 清空iframe内容以释放内存
  2785 + if (iframe.contentWindow) {
  2786 + try {
  2787 + // 尝试清空iframe的DOM
  2788 + iframe.src = 'about:blank';
  2789 + } catch (e) {
  2790 + console.warn(`无法清空 ${app} iframe:`, e);
  2791 + }
  2792 + }
  2793 +
  2794 + // 从DOM中移除iframe
  2795 + if (iframe.parentNode) {
  2796 + iframe.parentNode.removeChild(iframe);
  2797 + }
  2798 +
  2799 + // 从缓存中删除
  2800 + delete preloadedIframes[app];
  2801 +
  2802 + console.log(`卸载 ${app} iframe,释放内存`);
  2803 + }
  2804 +
  2805 + // 卸载所有非当前应用的iframe
  2806 + function unloadInactiveIframes(currentApp) {
  2807 + const apps = ['insight', 'media', 'query'];
  2808 + apps.forEach(app => {
  2809 + if (app !== currentApp && preloadedIframes[app]) {
  2810 + // 延迟卸载,给一些缓冲时间
  2811 + setTimeout(() => {
  2812 + if (currentApp !== app) { // 再次确认没有切换回来
  2813 + unloadIframe(app);
  2814 + }
  2815 + }, 30000); // 30秒后卸载不活跃的iframe
  2816 + }
  2817 + });
2570 } 2818 }
2571 2819
  2820 + // 预加载所有iframe(只执行一次)- 已废弃,改用懒加载
  2821 + function preloadIframes() {
  2822 + // 不再预加载所有iframe,改用懒加载机制
  2823 + console.log('使用懒加载机制,不再预加载所有iframe');
2572 iframesInitialized = true; 2824 iframesInitialized = true;
2573 - console.log('所有iframe预加载完成,准备进行无缝切换');  
2574 } 2825 }
2575 2826
2576 // 更新嵌入页面 2827 // 更新嵌入页面
@@ -2583,11 +2834,9 @@ @@ -2583,11 +2834,9 @@
2583 header.textContent = 'Forum Engine - 多智能体交流'; 2834 header.textContent = 'Forum Engine - 多智能体交流';
2584 2835
2585 // 隐藏所有iframe 2836 // 隐藏所有iframe
2586 - if (typeof preloadedIframes !== 'undefined') {  
2587 Object.values(preloadedIframes).forEach(iframe => { 2837 Object.values(preloadedIframes).forEach(iframe => {
2588 iframe.style.display = 'none'; 2838 iframe.style.display = 'none';
2589 }); 2839 });
2590 - }  
2591 2840
2592 // 移除占位符 2841 // 移除占位符
2593 const placeholder = content.querySelector('.status-placeholder'); 2842 const placeholder = content.querySelector('.status-placeholder');
@@ -2598,6 +2847,11 @@ @@ -2598,6 +2847,11 @@
2598 // 显示论坛容器,隐藏报告容器 2847 // 显示论坛容器,隐藏报告容器
2599 document.getElementById('forumContainer').classList.add('active'); 2848 document.getElementById('forumContainer').classList.add('active');
2600 document.getElementById('reportContainer').classList.remove('active'); 2849 document.getElementById('reportContainer').classList.remove('active');
  2850 +
  2851 + // 卸载不活跃的iframe
  2852 + unloadInactiveIframes(null);
  2853 +
  2854 + currentVisibleIframe = null;
2601 return; 2855 return;
2602 } 2856 }
2603 2857
@@ -2606,11 +2860,9 @@ @@ -2606,11 +2860,9 @@
2606 header.textContent = 'Report Agent - 最终报告生成'; 2860 header.textContent = 'Report Agent - 最终报告生成';
2607 2861
2608 // 隐藏所有iframe 2862 // 隐藏所有iframe
2609 - if (typeof preloadedIframes !== 'undefined') {  
2610 Object.values(preloadedIframes).forEach(iframe => { 2863 Object.values(preloadedIframes).forEach(iframe => {
2611 iframe.style.display = 'none'; 2864 iframe.style.display = 'none';
2612 }); 2865 });
2613 - }  
2614 2866
2615 // 移除占位符 2867 // 移除占位符
2616 const placeholder = content.querySelector('.status-placeholder'); 2868 const placeholder = content.querySelector('.status-placeholder');
@@ -2621,6 +2873,11 @@ @@ -2621,6 +2873,11 @@
2621 // 显示报告容器,隐藏论坛容器 2873 // 显示报告容器,隐藏论坛容器
2622 document.getElementById('reportContainer').classList.add('active'); 2874 document.getElementById('reportContainer').classList.add('active');
2623 document.getElementById('forumContainer').classList.remove('active'); 2875 document.getElementById('forumContainer').classList.remove('active');
  2876 +
  2877 + // 卸载不活跃的iframe
  2878 + unloadInactiveIframes(null);
  2879 +
  2880 + currentVisibleIframe = null;
2624 return; 2881 return;
2625 } 2882 }
2626 2883
@@ -2630,16 +2887,19 @@ @@ -2630,16 +2887,19 @@
2630 2887
2631 header.textContent = agentTitles[app] || appNames[app] || app; 2888 header.textContent = agentTitles[app] || appNames[app] || app;
2632 2889
2633 - // 如果应用正在运行,显示对应的iframe 2890 + // 如果应用正在运行,显示对应的iframe(使用懒加载)
2634 if (appStatus[app] === 'running') { 2891 if (appStatus[app] === 'running') {
2635 - // 确保iframe已初始化  
2636 - if (!iframesInitialized) {  
2637 - preloadIframes(); 2892 + // 懒加载当前应用的iframe
  2893 + const iframe = lazyLoadIframe(app);
  2894 +
  2895 + if (!iframe) {
  2896 + console.error(`无法加载 ${app} iframe`);
  2897 + return;
2638 } 2898 }
2639 2899
2640 // 隐藏所有iframe 2900 // 隐藏所有iframe
2641 - Object.values(preloadedIframes).forEach(iframe => {  
2642 - iframe.style.display = 'none'; 2901 + Object.values(preloadedIframes).forEach(otherIframe => {
  2902 + otherIframe.style.display = 'none';
2643 }); 2903 });
2644 2904
2645 // 移除占位符 2905 // 移除占位符
@@ -2649,10 +2909,12 @@ @@ -2649,10 +2909,12 @@
2649 } 2909 }
2650 2910
2651 // 显示当前应用的iframe 2911 // 显示当前应用的iframe
2652 - if (preloadedIframes[app]) {  
2653 - preloadedIframes[app].style.display = 'block';  
2654 - console.log(`切换到 ${app} 应用 - 无刷新切换`);  
2655 - } 2912 + iframe.style.display = 'block';
  2913 + currentVisibleIframe = app;
  2914 + console.log(`切换到 ${app} 应用 - 懒加载模式`);
  2915 +
  2916 + // 卸载不活跃的iframe(30秒后)
  2917 + unloadInactiveIframes(app);
2656 } else { 2918 } else {
2657 // 隐藏所有iframe 2919 // 隐藏所有iframe
2658 Object.values(preloadedIframes).forEach(iframe => { 2920 Object.values(preloadedIframes).forEach(iframe => {
@@ -2672,6 +2934,8 @@ @@ -2672,6 +2934,8 @@
2672 <div style="margin-bottom: 10px;">${appNames[app]} 未运行</div> 2934 <div style="margin-bottom: 10px;">${appNames[app]} 未运行</div>
2673 <div style="font-size: 12px;">状态: ${appStatus[app]}</div> 2935 <div style="font-size: 12px;">状态: ${appStatus[app]}</div>
2674 `; 2936 `;
  2937 +
  2938 + currentVisibleIframe = null;
2675 } 2939 }
2676 } 2940 }
2677 2941
@@ -2841,6 +3105,163 @@ @@ -2841,6 +3105,163 @@
2841 // Forum Engine 相关函数 3105 // Forum Engine 相关函数
2842 let forumLogLineCount = 0; 3106 let forumLogLineCount = 0;
2843 3107
  3108 + // 更新所有Engine的进度条
  3109 + function updateAllEngineProgress() {
  3110 + // 通过现有的status API获取所有Engine的状态
  3111 + fetch('/api/status')
  3112 + .then(response => response.json())
  3113 + .then(data => {
  3114 + // 为每个需要进度显示的Engine更新状态
  3115 + const engines = ['insight', 'media', 'query'];
  3116 +
  3117 + engines.forEach(engine => {
  3118 + if (data[engine]) {
  3119 + const info = data[engine];
  3120 + const status = info.status === 'running' ? 'running' : 'stopped';
  3121 +
  3122 + // 如果Engine正在运行,显示进度条
  3123 + if (status === 'running') {
  3124 + // 尝试从API获取详细进度,如果失败则显示基本运行状态
  3125 + updateEngineProgress(engine);
  3126 + } else {
  3127 + // Engine未运行,清除进度信息
  3128 + engineProgress[engine] = null;
  3129 + const progressContainer = document.getElementById(`progress-${engine}`);
  3130 + if (progressContainer && progressContainer.parentNode) {
  3131 + progressContainer.parentNode.removeChild(progressContainer);
  3132 + }
  3133 + }
  3134 + }
  3135 + });
  3136 + })
  3137 + .catch(error => {
  3138 + console.log('获取Engine状态失败:', error);
  3139 + });
  3140 + }
  3141 +
  3142 + // 更新单个Engine的进度
  3143 + function updateEngineProgress(engine) {
  3144 + // 先尝试从专用进度API获取
  3145 + fetch(`/api/${engine}/progress`)
  3146 + .then(response => {
  3147 + if (!response.ok) {
  3148 + throw new Error('Progress API not available');
  3149 + }
  3150 + return response.json();
  3151 + })
  3152 + .then(data => {
  3153 + if (data.success && data.progress) {
  3154 + // 存储进度信息
  3155 + engineProgress[engine] = {
  3156 + status: data.progress.status || 'running',
  3157 + progress: data.progress.progress || 0,
  3158 + message: data.progress.message || '正在处理...',
  3159 + updated_at: new Date().toISOString()
  3160 + };
  3161 +
  3162 + // 如果当前正在查看该Engine,更新显示
  3163 + if (currentApp === engine) {
  3164 + displayEngineProgress(engine);
  3165 + }
  3166 + }
  3167 + })
  3168 + .catch(error => {
  3169 + // 如果专用API不可用,使用基本的运行状态
  3170 + if (appStatus[engine] === 'running') {
  3171 + // 使用基本的进度信息
  3172 + if (!engineProgress[engine]) {
  3173 + engineProgress[engine] = {
  3174 + status: 'running',
  3175 + progress: 50, // 默认显示50%表示运行中
  3176 + message: '正在分析中...',
  3177 + updated_at: new Date().toISOString()
  3178 + };
  3179 + }
  3180 +
  3181 + // 如果当前正在查看该Engine,更新显示
  3182 + if (currentApp === engine) {
  3183 + displayEngineProgress(engine);
  3184 + }
  3185 + }
  3186 + });
  3187 + }
  3188 +
  3189 + // 在嵌入页面区域显示Engine进度
  3190 + function displayEngineProgress(engine) {
  3191 + const progress = engineProgress[engine];
  3192 + if (!progress) return;
  3193 +
  3194 + // 查找或创建进度显示容器
  3195 + let progressContainer = document.getElementById(`progress-${engine}`);
  3196 + if (!progressContainer) {
  3197 + // 在嵌入内容区域的顶部创建进度条容器
  3198 + const embeddedContent = document.getElementById('embeddedContent');
  3199 + if (!embeddedContent) return;
  3200 +
  3201 + progressContainer = document.createElement('div');
  3202 + progressContainer.id = `progress-${engine}`;
  3203 + progressContainer.className = 'task-progress-container';
  3204 + progressContainer.style.position = 'absolute';
  3205 + progressContainer.style.top = '10px';
  3206 + progressContainer.style.left = '10px';
  3207 + progressContainer.style.right = '10px';
  3208 + progressContainer.style.zIndex = '100';
  3209 + progressContainer.style.backgroundColor = '#f5f5f0';
  3210 + embeddedContent.insertBefore(progressContainer, embeddedContent.firstChild);
  3211 + }
  3212 +
  3213 + // 更新进度条内容
  3214 + const loadingIndicator = progress.status !== 'completed' && progress.status !== 'error'
  3215 + ? '<span class="report-loading-spinner"></span>'
  3216 + : '';
  3217 +
  3218 + progressContainer.innerHTML = `
  3219 + <div class="task-progress-header">
  3220 + <div class="task-progress-title">
  3221 + ${loadingIndicator}${appNames[engine] || engine} - ${progress.message}
  3222 + </div>
  3223 + <div class="task-progress-bar">
  3224 + <div class="task-progress-fill" style="width: ${Math.min(Math.max(progress.progress || 0, 0), 100)}%"></div>
  3225 + <div class="task-progress-text">${progress.progress || 0}%</div>
  3226 + </div>
  3227 + </div>
  3228 + `;
  3229 +
  3230 + // 如果任务已完成,5秒后淡出进度条
  3231 + if (progress.status === 'completed') {
  3232 + setTimeout(() => {
  3233 + if (progressContainer && progressContainer.parentNode) {
  3234 + progressContainer.style.transition = 'opacity 1s';
  3235 + progressContainer.style.opacity = '0';
  3236 + setTimeout(() => {
  3237 + if (progressContainer && progressContainer.parentNode) {
  3238 + progressContainer.parentNode.removeChild(progressContainer);
  3239 + }
  3240 + }, 1000);
  3241 + }
  3242 + }, 5000);
  3243 + }
  3244 + }
  3245 +
  3246 + // 隐藏指定Engine的进度条(切换时使用)
  3247 + function hideEngineProgress(engine) {
  3248 + const progressContainer = document.getElementById(`progress-${engine}`);
  3249 + if (progressContainer) {
  3250 + progressContainer.style.display = 'none';
  3251 + }
  3252 + }
  3253 +
  3254 + // 显示指定Engine的进度条(切换时使用)
  3255 + function showEngineProgress(engine) {
  3256 + const progressContainer = document.getElementById(`progress-${engine}`);
  3257 + if (progressContainer) {
  3258 + progressContainer.style.display = 'block';
  3259 + } else if (engineProgress[engine]) {
  3260 + // 如果有缓存的进度信息但容器不存在,重新创建
  3261 + displayEngineProgress(engine);
  3262 + }
  3263 + }
  3264 +
2844 // Report Engine 相关函数 3265 // Report Engine 相关函数
2845 let reportLogLineCount = 0; 3266 let reportLogLineCount = 0;
2846 let reportLockCheckInterval = null; 3267 let reportLockCheckInterval = null;