Showing
1 changed file
with
550 additions
and
129 deletions
| @@ -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 | + |
| 1782 | + // 使用新的定时器管理系统 | ||
| 1783 | + updateTime(); // 立即更新一次 | ||
| 1784 | + checkStatus(); // 立即检查一次 | ||
| 1785 | + checkReportLockStatus(); // 立即检查一次 | ||
| 1786 | + | ||
| 1787 | + // 启动所有定时器 | ||
| 1788 | + startAllTimers(); | ||
| 1789 | + | ||
| 1790 | + // 立即更新一次所有Engine的进度,恢复刷新前的状态 | ||
| 1791 | + updateAllEngineProgress(); | ||
| 1792 | + | ||
| 1793 | + // 监听页面可见性变化 | ||
| 1794 | + document.addEventListener('visibilitychange', handleVisibilityChange); | ||
| 1795 | + | ||
| 1796 | + // 监听页面卸载事件 | ||
| 1797 | + window.addEventListener('beforeunload', cleanupOnUnload); | ||
| 1798 | + window.addEventListener('unload', cleanupOnUnload); | ||
| 1799 | + | ||
| 1612 | // 初始化密码切换功能(事件委托,只需调用一次) | 1800 | // 初始化密码切换功能(事件委托,只需调用一次) |
| 1613 | attachConfigPasswordToggles(); | 1801 | attachConfigPasswordToggles(); |
| 1614 | - | ||
| 1615 | - // 初始化Report Engine锁定状态检查 | ||
| 1616 | - checkReportLockStatus(); | ||
| 1617 | - reportLockCheckInterval = setInterval(checkReportLockStatus, 10000); // 每10秒检查一次 | ||
| 1618 | - | ||
| 1619 | - // 优化控制台刷新频率:从 1 秒改为 2 秒,减少不必要的 API 调用 | ||
| 1620 | - setInterval(() => { | ||
| 1621 | - if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') { | ||
| 1622 | - refreshConsoleOutput(); | ||
| 1623 | - } | ||
| 1624 | - }, 2000); | ||
| 1625 | 1802 | ||
| 1626 | - // 优化论坛对话刷新频率:从 2 秒改为 3 秒 | ||
| 1627 | - setInterval(() => { | ||
| 1628 | - if (currentApp === 'forum' || appStatus.forum === 'running') { | ||
| 1629 | - refreshForumMessages(); | ||
| 1630 | - } | ||
| 1631 | - }, 3000); | ||
| 1632 | - | ||
| 1633 | // 初始化论坛相关功能 | 1803 | // 初始化论坛相关功能 |
| 1634 | initializeForum(); | 1804 | initializeForum(); |
| 1635 | - | ||
| 1636 | - // 延迟预加载iframe以确保应用启动完成 | 1805 | + |
| 1806 | + // 延迟预加载iframe以确保应用启动完成,并且只在页面可见时加载 | ||
| 1637 | setTimeout(() => { | 1807 | setTimeout(() => { |
| 1638 | - preloadIframes(); | ||
| 1639 | - }, 3000); | 1808 | + if (isPageVisible) { |
| 1809 | + preloadIframes(); | ||
| 1810 | + } | ||
| 1811 | + }, 5000); // 延迟时间从3秒增加到5秒,减少初始加载压力 | ||
| 1812 | + | ||
| 1813 | + // 连接探测定时器(保持运行) | ||
| 1814 | + startConnectionProbe(); | ||
| 1640 | }); | 1815 | }); |
| 1641 | 1816 | ||
| 1642 | // Socket.IO连接 | 1817 | // Socket.IO连接 |
| @@ -2228,45 +2403,48 @@ | @@ -2228,45 +2403,48 @@ | ||
| 2228 | if (reportPreview) { | 2403 | if (reportPreview) { |
| 2229 | reportPreview.innerHTML = '<div class="report-loading">等待新的搜索结果生成报告...</div>'; | 2404 | reportPreview.innerHTML = '<div class="report-loading">等待新的搜索结果生成报告...</div>'; |
| 2230 | } | 2405 | } |
| 2231 | - | 2406 | + |
| 2232 | // 清除任务进度显示 | 2407 | // 清除任务进度显示 |
| 2233 | const taskProgressArea = document.getElementById('taskProgressArea'); | 2408 | const taskProgressArea = document.getElementById('taskProgressArea'); |
| 2234 | if (taskProgressArea) { | 2409 | if (taskProgressArea) { |
| 2235 | taskProgressArea.innerHTML = ''; | 2410 | taskProgressArea.innerHTML = ''; |
| 2236 | } | 2411 | } |
| 2237 | - | 2412 | + |
| 2238 | // 重置自动生成相关标志 | 2413 | // 重置自动生成相关标志 |
| 2239 | autoGenerateTriggered = false; | 2414 | autoGenerateTriggered = false; |
| 2240 | reportTaskId = null; | 2415 | reportTaskId = null; |
| 2241 | - | 2416 | + |
| 2242 | // 停止可能正在进行的轮询 | 2417 | // 停止可能正在进行的轮询 |
| 2243 | if (reportPollingInterval) { | 2418 | if (reportPollingInterval) { |
| 2244 | clearInterval(reportPollingInterval); | 2419 | clearInterval(reportPollingInterval); |
| 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 | - | ||
| 2261 | - // 构建搜索URL | ||
| 2262 | - const searchUrl = `http://${window.location.hostname}:${ports[app]}?query=${encodeURIComponent(query)}&auto_search=true`; | ||
| 2263 | - console.log(`向 ${app} 发送搜索请求: ${searchUrl}`); | ||
| 2264 | - | ||
| 2265 | - // 直接更新主iframe的src来传递搜索参数 | ||
| 2266 | - preloadedIframes[app].src = searchUrl; | 2430 | + |
| 2431 | + // 懒加载iframe(如果还没有加载) | ||
| 2432 | + let iframe = preloadedIframes[app]; | ||
| 2433 | + if (!iframe) { | ||
| 2434 | + iframe = lazyLoadIframe(app); | ||
| 2435 | + } | ||
| 2436 | + | ||
| 2437 | + if (iframe) { | ||
| 2438 | + // 构建搜索URL | ||
| 2439 | + const searchUrl = `http://${window.location.hostname}:${ports[app]}?query=${encodeURIComponent(query)}&auto_search=true`; | ||
| 2440 | + console.log(`向 ${app} 发送搜索请求: ${searchUrl}`); | ||
| 2441 | + | ||
| 2442 | + // 直接更新iframe的src来传递搜索参数 | ||
| 2443 | + iframe.src = searchUrl; | ||
| 2444 | + } | ||
| 2267 | } | 2445 | } |
| 2268 | }); | 2446 | }); |
| 2269 | - | 2447 | + |
| 2270 | if (totalRunning === 0) { | 2448 | if (totalRunning === 0) { |
| 2271 | button.disabled = false; | 2449 | button.disabled = false; |
| 2272 | button.innerHTML = '搜索'; | 2450 | button.innerHTML = '搜索'; |
| @@ -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'); |
| @@ -2304,49 +2488,56 @@ | @@ -2304,49 +2488,56 @@ | ||
| 2304 | if (app === 'forum') { | 2488 | if (app === 'forum') { |
| 2305 | // 切换到论坛模式 | 2489 | // 切换到论坛模式 |
| 2306 | document.getElementById('embeddedHeader').textContent = 'Forum Engine - 多智能体交流'; | 2490 | document.getElementById('embeddedHeader').textContent = 'Forum Engine - 多智能体交流'; |
| 2307 | - | 2491 | + |
| 2308 | // 显示论坛容器,隐藏其他内容 | 2492 | // 显示论坛容器,隐藏其他内容 |
| 2309 | document.getElementById('forumContainer').classList.add('active'); | 2493 | document.getElementById('forumContainer').classList.add('active'); |
| 2310 | document.getElementById('reportContainer').classList.remove('active'); | 2494 | document.getElementById('reportContainer').classList.remove('active'); |
| 2311 | - | 2495 | + |
| 2312 | // 追加提示并加载forum日志 | 2496 | // 追加提示并加载forum日志 |
| 2313 | appendConsoleTextLine('forum', '[系统] 切换到论坛模式'); | 2497 | appendConsoleTextLine('forum', '[系统] 切换到论坛模式'); |
| 2314 | loadForumLog(); | 2498 | loadForumLog(); |
| 2315 | - | 2499 | + |
| 2316 | } else if (app === 'report') { | 2500 | } else if (app === 'report') { |
| 2317 | // 切换到报告模式 | 2501 | // 切换到报告模式 |
| 2318 | document.getElementById('embeddedHeader').textContent = 'Report Agent - 最终报告生成'; | 2502 | document.getElementById('embeddedHeader').textContent = 'Report Agent - 最终报告生成'; |
| 2319 | - | 2503 | + |
| 2320 | // 显示报告容器,隐藏其他内容 | 2504 | // 显示报告容器,隐藏其他内容 |
| 2321 | document.getElementById('reportContainer').classList.add('active'); | 2505 | document.getElementById('reportContainer').classList.add('active'); |
| 2322 | document.getElementById('forumContainer').classList.remove('active'); | 2506 | document.getElementById('forumContainer').classList.remove('active'); |
| 2323 | - | 2507 | + |
| 2324 | // 追加提示并加载report日志 | 2508 | // 追加提示并加载report日志 |
| 2325 | appendConsoleTextLine('report', '[系统] 切换到报告生成模式'); | 2509 | appendConsoleTextLine('report', '[系统] 切换到报告生成模式'); |
| 2326 | loadReportLog(); | 2510 | loadReportLog(); |
| 2327 | - | 2511 | + |
| 2328 | // 只在报告界面未初始化时才重新加载 | 2512 | // 只在报告界面未初始化时才重新加载 |
| 2329 | const reportContent = document.getElementById('reportContent'); | 2513 | const reportContent = document.getElementById('reportContent'); |
| 2330 | if (!reportContent || reportContent.children.length === 0) { | 2514 | if (!reportContent || reportContent.children.length === 0) { |
| 2331 | loadReportInterface(); | 2515 | loadReportInterface(); |
| 2332 | } | 2516 | } |
| 2333 | - | 2517 | + |
| 2334 | // 切换到report页面时检查是否可以自动生成报告 | 2518 | // 切换到report页面时检查是否可以自动生成报告 |
| 2335 | setTimeout(() => { | 2519 | setTimeout(() => { |
| 2336 | checkReportLockStatus(); | 2520 | checkReportLockStatus(); |
| 2337 | }, 500); | 2521 | }, 500); |
| 2338 | - | 2522 | + |
| 2339 | } else { | 2523 | } else { |
| 2340 | // 切换到普通Engine模式 | 2524 | // 切换到普通Engine模式 |
| 2341 | document.getElementById('embeddedHeader').textContent = agentTitles[app] || appNames[app]; | 2525 | document.getElementById('embeddedHeader').textContent = agentTitles[app] || appNames[app]; |
| 2342 | - | 2526 | + |
| 2343 | // 隐藏论坛和报告容器 | 2527 | // 隐藏论坛和报告容器 |
| 2344 | document.getElementById('forumContainer').classList.remove('active'); | 2528 | document.getElementById('forumContainer').classList.remove('active'); |
| 2345 | document.getElementById('reportContainer').classList.remove('active'); | 2529 | document.getElementById('reportContainer').classList.remove('active'); |
| 2346 | - | 2530 | + |
| 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,123 +2733,194 @@ | @@ -2542,123 +2733,194 @@ | ||
| 2542 | // 预加载的iframe存储 | 2733 | // 预加载的iframe存储 |
| 2543 | let preloadedIframes = {}; | 2734 | let preloadedIframes = {}; |
| 2544 | let iframesInitialized = false; | 2735 | let iframesInitialized = false; |
| 2545 | - | ||
| 2546 | - // 预加载所有iframe(只执行一次) | ||
| 2547 | - function preloadIframes() { | ||
| 2548 | - if (iframesInitialized) return; | ||
| 2549 | - | 2736 | + let currentVisibleIframe = null; // 跟踪当前可见的iframe |
| 2737 | + | ||
| 2738 | + // 懒加载iframe - 只在真正需要时才创建 | ||
| 2739 | + function lazyLoadIframe(app) { | ||
| 2740 | + // 如果iframe已存在,直接返回 | ||
| 2741 | + if (preloadedIframes[app]) { | ||
| 2742 | + return preloadedIframes[app]; | ||
| 2743 | + } | ||
| 2744 | + | ||
| 2550 | const ports = { insight: 8501, media: 8502, query: 8503 }; | 2745 | const ports = { insight: 8501, media: 8502, query: 8503 }; |
| 2746 | + if (!ports[app]) { | ||
| 2747 | + console.warn(`未知的应用: ${app}`); | ||
| 2748 | + return null; | ||
| 2749 | + } | ||
| 2750 | + | ||
| 2551 | const content = document.getElementById('embeddedContent'); | 2751 | const content = document.getElementById('embeddedContent'); |
| 2552 | - | ||
| 2553 | - for (const [app, port] of Object.entries(ports)) { | ||
| 2554 | - const iframe = document.createElement('iframe'); | ||
| 2555 | - iframe.src = `http://${window.location.hostname}:${port}`; | ||
| 2556 | - iframe.style.width = '100%'; | ||
| 2557 | - iframe.style.height = '100%'; | ||
| 2558 | - iframe.style.border = 'none'; | ||
| 2559 | - iframe.style.position = 'absolute'; | ||
| 2560 | - iframe.style.top = '0'; | ||
| 2561 | - iframe.style.left = '0'; | ||
| 2562 | - iframe.style.display = 'none'; | ||
| 2563 | - iframe.id = `iframe-${app}`; | ||
| 2564 | - | ||
| 2565 | - // 直接添加到content区域 | ||
| 2566 | - content.appendChild(iframe); | ||
| 2567 | - preloadedIframes[app] = iframe; | ||
| 2568 | - | ||
| 2569 | - console.log(`预加载 ${app} 应用完成`); | 2752 | + const iframe = document.createElement('iframe'); |
| 2753 | + iframe.src = `http://${window.location.hostname}:${ports[app]}`; | ||
| 2754 | + iframe.style.width = '100%'; | ||
| 2755 | + iframe.style.height = '100%'; | ||
| 2756 | + iframe.style.border = 'none'; | ||
| 2757 | + iframe.style.position = 'absolute'; | ||
| 2758 | + iframe.style.top = '0'; | ||
| 2759 | + iframe.style.left = '0'; | ||
| 2760 | + iframe.style.display = 'none'; | ||
| 2761 | + iframe.id = `iframe-${app}`; | ||
| 2762 | + | ||
| 2763 | + // 添加加载完成事件 | ||
| 2764 | + iframe.addEventListener('load', () => { | ||
| 2765 | + console.log(`${app} iframe 加载完成`); | ||
| 2766 | + }); | ||
| 2767 | + | ||
| 2768 | + content.appendChild(iframe); | ||
| 2769 | + preloadedIframes[app] = iframe; | ||
| 2770 | + | ||
| 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 | + } | ||
| 2570 | } | 2792 | } |
| 2571 | - | 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 | + }); | ||
| 2818 | + } | ||
| 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 | // 更新嵌入页面 |
| 2577 | function updateEmbeddedPage(app) { | 2828 | function updateEmbeddedPage(app) { |
| 2578 | const header = document.getElementById('embeddedHeader'); | 2829 | const header = document.getElementById('embeddedHeader'); |
| 2579 | const content = document.getElementById('embeddedContent'); | 2830 | const content = document.getElementById('embeddedContent'); |
| 2580 | - | 2831 | + |
| 2581 | // 如果是Forum Engine,直接显示论坛界面 | 2832 | // 如果是Forum Engine,直接显示论坛界面 |
| 2582 | if (app === 'forum') { | 2833 | if (app === 'forum') { |
| 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 => { | ||
| 2588 | - iframe.style.display = 'none'; | ||
| 2589 | - }); | ||
| 2590 | - } | ||
| 2591 | - | 2837 | + Object.values(preloadedIframes).forEach(iframe => { |
| 2838 | + iframe.style.display = 'none'; | ||
| 2839 | + }); | ||
| 2840 | + | ||
| 2592 | // 移除占位符 | 2841 | // 移除占位符 |
| 2593 | const placeholder = content.querySelector('.status-placeholder'); | 2842 | const placeholder = content.querySelector('.status-placeholder'); |
| 2594 | if (placeholder) { | 2843 | if (placeholder) { |
| 2595 | placeholder.remove(); | 2844 | placeholder.remove(); |
| 2596 | } | 2845 | } |
| 2597 | - | 2846 | + |
| 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 | + |
| 2604 | // 如果是Report Engine,显示报告界面 | 2858 | // 如果是Report Engine,显示报告界面 |
| 2605 | if (app === 'report') { | 2859 | if (app === 'report') { |
| 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 => { | ||
| 2611 | - iframe.style.display = 'none'; | ||
| 2612 | - }); | ||
| 2613 | - } | ||
| 2614 | - | 2863 | + Object.values(preloadedIframes).forEach(iframe => { |
| 2864 | + iframe.style.display = 'none'; | ||
| 2865 | + }); | ||
| 2866 | + | ||
| 2615 | // 移除占位符 | 2867 | // 移除占位符 |
| 2616 | const placeholder = content.querySelector('.status-placeholder'); | 2868 | const placeholder = content.querySelector('.status-placeholder'); |
| 2617 | if (placeholder) { | 2869 | if (placeholder) { |
| 2618 | placeholder.remove(); | 2870 | placeholder.remove(); |
| 2619 | } | 2871 | } |
| 2620 | - | 2872 | + |
| 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 | + |
| 2627 | // 隐藏论坛和报告容器 | 2884 | // 隐藏论坛和报告容器 |
| 2628 | document.getElementById('forumContainer').classList.remove('active'); | 2885 | document.getElementById('forumContainer').classList.remove('active'); |
| 2629 | document.getElementById('reportContainer').classList.remove('active'); | 2886 | document.getElementById('reportContainer').classList.remove('active'); |
| 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 | // 移除占位符 |
| 2646 | const placeholder = content.querySelector('.status-placeholder'); | 2906 | const placeholder = content.querySelector('.status-placeholder'); |
| 2647 | if (placeholder) { | 2907 | if (placeholder) { |
| 2648 | placeholder.remove(); | 2908 | placeholder.remove(); |
| 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 => { |
| 2659 | iframe.style.display = 'none'; | 2921 | iframe.style.display = 'none'; |
| 2660 | }); | 2922 | }); |
| 2661 | - | 2923 | + |
| 2662 | // 显示状态信息 | 2924 | // 显示状态信息 |
| 2663 | let placeholder = content.querySelector('.status-placeholder'); | 2925 | let placeholder = content.querySelector('.status-placeholder'); |
| 2664 | if (!placeholder) { | 2926 | if (!placeholder) { |
| @@ -2667,11 +2929,13 @@ | @@ -2667,11 +2929,13 @@ | ||
| 2667 | 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%;'; | 2929 | 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%;'; |
| 2668 | content.appendChild(placeholder); | 2930 | content.appendChild(placeholder); |
| 2669 | } | 2931 | } |
| 2670 | - | 2932 | + |
| 2671 | placeholder.innerHTML = ` | 2933 | placeholder.innerHTML = ` |
| 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 | ||
| @@ -2840,7 +3104,164 @@ | @@ -2840,7 +3104,164 @@ | ||
| 2840 | 3104 | ||
| 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; |
-
Please register or login to post a comment