马一丁

Repair the Logic of the Log Viewing System

@@ -326,6 +326,16 @@ @@ -326,6 +326,16 @@
326 min-height: 0; /* 允许内容缩小 */ 326 min-height: 0; /* 允许内容缩小 */
327 } 327 }
328 328
  329 + .console-layer {
  330 + display: none;
  331 + width: 100%;
  332 + min-height: 100%;
  333 + }
  334 +
  335 + .console-layer.active {
  336 + display: block;
  337 + }
  338 +
329 .console-line { 339 .console-line {
330 margin-bottom: 2px; 340 margin-bottom: 2px;
331 } 341 }
@@ -1153,9 +1163,7 @@ @@ -1153,9 +1163,7 @@
1153 </div> 1163 </div>
1154 1164
1155 <!-- 控制台输出 --> 1165 <!-- 控制台输出 -->
1156 - <div class="console-output" id="consoleOutput">  
1157 - <div class="console-line">[系统] 等待连接...</div>  
1158 - </div> 1166 + <div class="console-output" id="consoleOutput"></div>
1159 </div> 1167 </div>
1160 </div> 1168 </div>
1161 1169
@@ -1214,6 +1222,10 @@ @@ -1214,6 +1222,10 @@
1214 let socketConnected = false; 1222 let socketConnected = false;
1215 let reportStreamConnected = false; 1223 let reportStreamConnected = false;
1216 let backendReachable = false; 1224 let backendReachable = false;
  1225 + const consoleLayerApps = ['insight', 'media', 'query', 'forum', 'report'];
  1226 + const consoleLayers = {};
  1227 + const consoleLayerScrollPositions = {};
  1228 + let activeConsoleLayer = currentApp;
1217 1229
1218 const CONFIG_ENDPOINT = '/api/config'; 1230 const CONFIG_ENDPOINT = '/api/config';
1219 const SYSTEM_STATUS_ENDPOINT = '/api/system/status'; 1231 const SYSTEM_STATUS_ENDPOINT = '/api/system/status';
@@ -1318,9 +1330,11 @@ @@ -1318,9 +1330,11 @@
1318 1330
1319 // 初始化 1331 // 初始化
1320 document.addEventListener('DOMContentLoaded', function() { 1332 document.addEventListener('DOMContentLoaded', function() {
  1333 + initializeConsoleLayers();
1321 initializeSocket(); 1334 initializeSocket();
1322 initializeEventListeners(); 1335 initializeEventListeners();
1323 ensureSystemReadyOnLoad(); 1336 ensureSystemReadyOnLoad();
  1337 + loadConsoleOutput(currentApp);
1324 updateTime(); 1338 updateTime();
1325 setInterval(updateTime, 1000); 1339 setInterval(updateTime, 1000);
1326 checkStatus(); 1340 checkStatus();
@@ -1370,9 +1384,7 @@ @@ -1370,9 +1384,7 @@
1370 1384
1371 socket.on('console_output', function(data) { 1385 socket.on('console_output', function(data) {
1372 // 处理控制台输出 1386 // 处理控制台输出
1373 - if (data.app === currentApp) {  
1374 - addConsoleOutput(data.line);  
1375 - } 1387 + addConsoleOutput(data.line, data.app);
1376 1388
1377 // 如果是forum的输出,同时也处理为论坛消息 1389 // 如果是forum的输出,同时也处理为论坛消息
1378 if (data.app === 'forum') { 1390 if (data.app === 'forum') {
@@ -2014,6 +2026,7 @@ @@ -2014,6 +2026,7 @@
2014 document.querySelector(`[data-app="${app}"]`).classList.add('active'); 2026 document.querySelector(`[data-app="${app}"]`).classList.add('active');
2015 2027
2016 currentApp = app; 2028 currentApp = app;
  2029 + setActiveConsoleLayer(app);
2017 2030
2018 // 根据应用类型处理不同的显示逻辑 2031 // 根据应用类型处理不同的显示逻辑
2019 if (app === 'forum') { 2032 if (app === 'forum') {
@@ -2024,8 +2037,8 @@ @@ -2024,8 +2037,8 @@
2024 document.getElementById('forumContainer').classList.add('active'); 2037 document.getElementById('forumContainer').classList.add('active');
2025 document.getElementById('reportContainer').classList.remove('active'); 2038 document.getElementById('reportContainer').classList.remove('active');
2026 2039
2027 - // 清空控制台并加载forum日志  
2028 - document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到论坛模式</div>'; 2040 + // 追加提示并加载forum日志
  2041 + appendConsoleTextLine('forum', '[系统] 切换到论坛模式');
2029 loadForumLog(); 2042 loadForumLog();
2030 2043
2031 } else if (app === 'report') { 2044 } else if (app === 'report') {
@@ -2036,8 +2049,8 @@ @@ -2036,8 +2049,8 @@
2036 document.getElementById('reportContainer').classList.add('active'); 2049 document.getElementById('reportContainer').classList.add('active');
2037 document.getElementById('forumContainer').classList.remove('active'); 2050 document.getElementById('forumContainer').classList.remove('active');
2038 2051
2039 - // 清空控制台并加载report日志  
2040 - document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到报告生成模式</div>'; 2052 + // 追加提示并加载report日志
  2053 + appendConsoleTextLine('report', '[系统] 切换到报告生成模式');
2041 loadReportLog(); 2054 loadReportLog();
2042 2055
2043 // 只在报告界面未初始化时才重新加载 2056 // 只在报告界面未初始化时才重新加载
@@ -2059,11 +2072,8 @@ @@ -2059,11 +2072,8 @@
2059 document.getElementById('forumContainer').classList.remove('active'); 2072 document.getElementById('forumContainer').classList.remove('active');
2060 document.getElementById('reportContainer').classList.remove('active'); 2073 document.getElementById('reportContainer').classList.remove('active');
2061 2074
2062 - // 清空并加载新的控制台输出  
2063 - document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到 ' + appNames[app] + '</div>';  
2064 -  
2065 - // 重置行计数  
2066 - lastLineCount[app] = 0; 2075 + // 追加提示并加载新的控制台输出
  2076 + appendConsoleTextLine(app, '[系统] 切换到 ' + appNames[app]);
2067 loadConsoleOutput(app); 2077 loadConsoleOutput(app);
2068 } 2078 }
2069 2079
@@ -2073,6 +2083,127 @@ @@ -2073,6 +2083,127 @@
2073 2083
2074 // 存储最后显示的行数,避免重复加载 2084 // 存储最后显示的行数,避免重复加载
2075 let lastLineCount = {}; 2085 let lastLineCount = {};
  2086 +
  2087 + function getConsoleContainer() {
  2088 + return document.getElementById('consoleOutput');
  2089 + }
  2090 +
  2091 + function initializeConsoleLayers() {
  2092 + const container = getConsoleContainer();
  2093 + if (!container) return;
  2094 + container.innerHTML = '';
  2095 +
  2096 + consoleLayerApps.forEach(app => {
  2097 + const layer = document.createElement('div');
  2098 + layer.className = 'console-layer';
  2099 + layer.dataset.app = app;
  2100 + if (app === currentApp) {
  2101 + layer.classList.add('active');
  2102 + layer.style.display = 'block';
  2103 + activeConsoleLayer = app;
  2104 + } else {
  2105 + layer.style.display = 'none';
  2106 + }
  2107 +
  2108 + const placeholder = document.createElement('div');
  2109 + placeholder.className = 'console-line';
  2110 + placeholder.textContent = `[系统] ${appNames[app] || app} 日志就绪`;
  2111 + layer.appendChild(placeholder);
  2112 +
  2113 + container.appendChild(layer);
  2114 + consoleLayers[app] = layer;
  2115 + });
  2116 +
  2117 + container.scrollTop = container.scrollHeight;
  2118 + }
  2119 +
  2120 + function getConsoleLayer(app) {
  2121 + if (consoleLayers[app]) {
  2122 + return consoleLayers[app];
  2123 + }
  2124 +
  2125 + const container = getConsoleContainer();
  2126 + if (!container) return null;
  2127 +
  2128 + const layer = document.createElement('div');
  2129 + layer.className = 'console-layer';
  2130 + layer.dataset.app = app;
  2131 + layer.style.display = app === currentApp ? 'block' : 'none';
  2132 + if (app === currentApp) {
  2133 + layer.classList.add('active');
  2134 + activeConsoleLayer = app;
  2135 + }
  2136 +
  2137 + container.appendChild(layer);
  2138 + consoleLayers[app] = layer;
  2139 + return layer;
  2140 + }
  2141 +
  2142 + function setActiveConsoleLayer(app) {
  2143 + const container = getConsoleContainer();
  2144 + if (!container) return;
  2145 +
  2146 + if (activeConsoleLayer && consoleLayers[activeConsoleLayer]) {
  2147 + consoleLayerScrollPositions[activeConsoleLayer] = container.scrollTop;
  2148 + consoleLayers[activeConsoleLayer].classList.remove('active');
  2149 + consoleLayers[activeConsoleLayer].style.display = 'none';
  2150 + }
  2151 +
  2152 + const targetLayer = getConsoleLayer(app);
  2153 + if (!targetLayer) return;
  2154 +
  2155 + targetLayer.style.display = 'block';
  2156 + targetLayer.classList.add('active');
  2157 + activeConsoleLayer = app;
  2158 +
  2159 + const storedScroll = consoleLayerScrollPositions[app];
  2160 + if (typeof storedScroll === 'number') {
  2161 + container.scrollTop = storedScroll;
  2162 + } else {
  2163 + container.scrollTop = container.scrollHeight;
  2164 + }
  2165 + }
  2166 +
  2167 + function syncConsoleScroll(app) {
  2168 + if (app !== currentApp) {
  2169 + return;
  2170 + }
  2171 +
  2172 + const container = getConsoleContainer();
  2173 + if (container) {
  2174 + container.scrollTop = container.scrollHeight;
  2175 + consoleLayerScrollPositions[app] = container.scrollTop;
  2176 + }
  2177 + }
  2178 +
  2179 + function appendConsoleTextLine(app, text, className = 'console-line') {
  2180 + const layer = getConsoleLayer(app);
  2181 + if (!layer) return;
  2182 +
  2183 + const line = document.createElement('div');
  2184 + line.className = className;
  2185 + line.textContent = text;
  2186 + layer.appendChild(line);
  2187 + syncConsoleScroll(app);
  2188 + }
  2189 +
  2190 + function appendConsoleElement(app, element) {
  2191 + const layer = getConsoleLayer(app);
  2192 + if (!layer || !element) return;
  2193 +
  2194 + layer.appendChild(element);
  2195 + syncConsoleScroll(app);
  2196 + }
  2197 +
  2198 + function clearConsoleLayer(app, message = null) {
  2199 + const layer = getConsoleLayer(app);
  2200 + if (!layer) return;
  2201 +
  2202 + layer.innerHTML = '';
  2203 + if (message) {
  2204 + appendConsoleTextLine(app, message);
  2205 + }
  2206 + }
2076 2207
2077 // 加载控制台输出 2208 // 加载控制台输出
2078 function loadConsoleOutput(app) { 2209 function loadConsoleOutput(app) {
@@ -2090,21 +2221,15 @@ @@ -2090,21 +2221,15 @@
2090 .then(response => response.json()) 2221 .then(response => response.json())
2091 .then(data => { 2222 .then(data => {
2092 if (data.success && data.output.length > 0) { 2223 if (data.success && data.output.length > 0) {
2093 - const consoleOutput = document.getElementById('consoleOutput');  
2094 -  
2095 - // 只添加新的行  
2096 const lastCount = lastLineCount[app] || 0; 2224 const lastCount = lastLineCount[app] || 0;
2097 const newLines = data.output.slice(lastCount); 2225 const newLines = data.output.slice(lastCount);
2098 2226
2099 - newLines.forEach(line => {  
2100 - const div = document.createElement('div');  
2101 - div.className = 'console-line';  
2102 - div.textContent = line;  
2103 - consoleOutput.appendChild(div);  
2104 - });  
2105 -  
2106 - lastLineCount[app] = data.output.length;  
2107 - consoleOutput.scrollTop = consoleOutput.scrollHeight; 2227 + if (newLines.length > 0) {
  2228 + newLines.forEach(line => {
  2229 + appendConsoleTextLine(app, line);
  2230 + });
  2231 + lastLineCount[app] = data.output.length;
  2232 + }
2108 } 2233 }
2109 }) 2234 })
2110 .catch(error => { 2235 .catch(error => {
@@ -2129,22 +2254,15 @@ @@ -2129,22 +2254,15 @@
2129 .then(response => response.json()) 2254 .then(response => response.json())
2130 .then(data => { 2255 .then(data => {
2131 if (data.success && data.output.length > 0) { 2256 if (data.success && data.output.length > 0) {
2132 - const consoleOutput = document.getElementById('consoleOutput');  
2133 -  
2134 // 只添加新的行 2257 // 只添加新的行
2135 const lastCount = lastLineCount[currentApp] || 0; 2258 const lastCount = lastLineCount[currentApp] || 0;
2136 const newLines = data.output.slice(lastCount); 2259 const newLines = data.output.slice(lastCount);
2137 2260
2138 if (newLines.length > 0) { 2261 if (newLines.length > 0) {
2139 newLines.forEach(line => { 2262 newLines.forEach(line => {
2140 - const div = document.createElement('div');  
2141 - div.className = 'console-line';  
2142 - div.textContent = line;  
2143 - consoleOutput.appendChild(div); 2263 + appendConsoleTextLine(currentApp, line);
2144 }); 2264 });
2145 -  
2146 lastLineCount[currentApp] = data.output.length; 2265 lastLineCount[currentApp] = data.output.length;
2147 - consoleOutput.scrollTop = consoleOutput.scrollHeight;  
2148 } 2266 }
2149 } 2267 }
2150 }) 2268 })
@@ -2155,15 +2273,13 @@ @@ -2155,15 +2273,13 @@
2155 } 2273 }
2156 2274
2157 // 添加控制台输出 2275 // 添加控制台输出
2158 - function addConsoleOutput(line) {  
2159 - const consoleOutput = document.getElementById('consoleOutput');  
2160 - const div = document.createElement('div');  
2161 - div.className = 'console-line';  
2162 - div.textContent = line;  
2163 - consoleOutput.appendChild(div); 2276 + function addConsoleOutput(line, app = currentApp) {
  2277 + const targetApp = app || currentApp;
  2278 + appendConsoleTextLine(targetApp, line);
2164 2279
2165 - // 自动滚动到底部显示最新内容  
2166 - consoleOutput.scrollTop = consoleOutput.scrollHeight; 2280 + if (targetApp !== 'report') {
  2281 + lastLineCount[targetApp] = (lastLineCount[targetApp] || 0) + 1;
  2282 + }
2167 } 2283 }
2168 2284
2169 // 预加载的iframe存储 2285 // 预加载的iframe存储
@@ -2522,15 +2638,16 @@ @@ -2522,15 +2638,16 @@
2522 `; 2638 `;
2523 2639
2524 // 加载控制台日志 2640 // 加载控制台日志
2525 - const consoleOutput = document.getElementById('consoleOutput');  
2526 - consoleOutput.innerHTML = '<div class="console-line">[系统] Forum Engine 日志输出</div>';  
2527 -  
2528 if (data.log_lines && data.log_lines.length > 0) { 2641 if (data.log_lines && data.log_lines.length > 0) {
2529 - data.log_lines.forEach(line => {  
2530 - const div = document.createElement('div');  
2531 - div.className = 'console-line';  
2532 - div.textContent = line;  
2533 - consoleOutput.appendChild(div); 2642 + if (forumLogLineCount === 0) {
  2643 + clearConsoleLayer('forum', '[系统] Forum Engine 日志输出');
  2644 + }
  2645 +
  2646 + const newLines = data.log_lines.slice(forumLogLineCount);
  2647 + const linesToProcess = forumLogLineCount === 0 ? data.log_lines : newLines;
  2648 +
  2649 + linesToProcess.forEach(line => {
  2650 + appendConsoleTextLine('forum', line);
2534 2651
2535 // 解析并添加到对话区 2652 // 解析并添加到对话区
2536 const parsed = parseForumMessage(line); 2653 const parsed = parseForumMessage(line);
@@ -2553,7 +2670,6 @@ @@ -2553,7 +2670,6 @@
2553 }); 2670 });
2554 } 2671 }
2555 2672
2556 - consoleOutput.scrollTop = consoleOutput.scrollHeight;  
2557 } 2673 }
2558 }) 2674 })
2559 .catch(error => { 2675 .catch(error => {
@@ -2567,15 +2683,10 @@ @@ -2567,15 +2683,10 @@
2567 .then(response => response.json()) 2683 .then(response => response.json())
2568 .then(data => { 2684 .then(data => {
2569 if (data.success && data.log_lines.length > forumLogLineCount) { 2685 if (data.success && data.log_lines.length > forumLogLineCount) {
2570 - const consoleOutput = document.getElementById('consoleOutput');  
2571 -  
2572 // 只添加新的行 2686 // 只添加新的行
2573 const newLines = data.log_lines.slice(forumLogLineCount); 2687 const newLines = data.log_lines.slice(forumLogLineCount);
2574 newLines.forEach(line => { 2688 newLines.forEach(line => {
2575 - const div = document.createElement('div');  
2576 - div.className = 'console-line';  
2577 - div.textContent = line;  
2578 - consoleOutput.appendChild(div); 2689 + appendConsoleTextLine('forum', line);
2579 2690
2580 // 如果是论坛对话内容,也显示到左侧对话区 2691 // 如果是论坛对话内容,也显示到左侧对话区
2581 const parsed = parseForumMessage(line); 2692 const parsed = parseForumMessage(line);
@@ -2585,7 +2696,6 @@ @@ -2585,7 +2696,6 @@
2585 }); 2696 });
2586 2697
2587 forumLogLineCount = data.log_lines.length; 2698 forumLogLineCount = data.log_lines.length;
2588 - consoleOutput.scrollTop = consoleOutput.scrollHeight;  
2589 } 2699 }
2590 }) 2700 })
2591 .catch(error => { 2701 .catch(error => {
@@ -2650,19 +2760,13 @@ @@ -2650,19 +2760,13 @@
2650 .then(response => response.json()) 2760 .then(response => response.json())
2651 .then(data => { 2761 .then(data => {
2652 if (data.success && data.log_lines.length > reportLogLineCount) { 2762 if (data.success && data.log_lines.length > reportLogLineCount) {
2653 - const consoleOutput = document.getElementById('consoleOutput');  
2654 -  
2655 // 只添加新的行 2763 // 只添加新的行
2656 const newLines = data.log_lines.slice(reportLogLineCount); 2764 const newLines = data.log_lines.slice(reportLogLineCount);
2657 newLines.forEach(line => { 2765 newLines.forEach(line => {
2658 - const div = document.createElement('div');  
2659 - div.className = 'console-line';  
2660 - div.textContent = line;  
2661 - consoleOutput.appendChild(div); 2766 + appendConsoleTextLine('report', line);
2662 }); 2767 });
2663 2768
2664 reportLogLineCount = data.log_lines.length; 2769 reportLogLineCount = data.log_lines.length;
2665 - consoleOutput.scrollTop = consoleOutput.scrollHeight;  
2666 } 2770 }
2667 }) 2771 })
2668 .catch(error => { 2772 .catch(error => {
@@ -2676,15 +2780,16 @@ @@ -2676,15 +2780,16 @@
2676 .then(response => response.json()) 2780 .then(response => response.json())
2677 .then(data => { 2781 .then(data => {
2678 if (data.success) { 2782 if (data.success) {
2679 - const consoleOutput = document.getElementById('consoleOutput');  
2680 - consoleOutput.innerHTML = '<div class="console-line">[系统] Report Engine 日志监控已启动</div>';  
2681 - 2783 + if (reportLogLineCount === 0) {
  2784 + clearConsoleLayer('report', '[系统] Report Engine 日志监控已启动');
  2785 + }
  2786 +
2682 if (data.log_lines && data.log_lines.length > 0) { 2787 if (data.log_lines && data.log_lines.length > 0) {
2683 - data.log_lines.forEach(line => {  
2684 - const div = document.createElement('div');  
2685 - div.className = 'console-line';  
2686 - div.textContent = line;  
2687 - consoleOutput.appendChild(div); 2788 + const newLines = data.log_lines.slice(reportLogLineCount);
  2789 + const linesToProcess = reportLogLineCount === 0 ? data.log_lines : newLines;
  2790 +
  2791 + linesToProcess.forEach(line => {
  2792 + appendConsoleTextLine('report', line);
2688 }); 2793 });
2689 2794
2690 // 重置计数器以确保后续消息能正确显示 2795 // 重置计数器以确保后续消息能正确显示
@@ -2693,8 +2798,6 @@ @@ -2693,8 +2798,6 @@
2693 // 如果没有日志,重置计数器 2798 // 如果没有日志,重置计数器
2694 reportLogLineCount = 0; 2799 reportLogLineCount = 0;
2695 } 2800 }
2696 -  
2697 - consoleOutput.scrollTop = consoleOutput.scrollHeight;  
2698 } 2801 }
2699 }) 2802 })
2700 .catch(error => { 2803 .catch(error => {
@@ -3168,8 +3271,7 @@ @@ -3168,8 +3271,7 @@
3168 safeCloseReportStream(true); 3271 safeCloseReportStream(true);
3169 3272
3170 // 清空控制台显示 3273 // 清空控制台显示
3171 - const consoleOutput = document.getElementById('consoleOutput');  
3172 - consoleOutput.innerHTML = '<div class="console-line">[系统] 开始生成报告,日志已重置</div>'; 3274 + clearConsoleLayer('report', '[系统] 开始生成报告,日志已重置');
3173 resetReportStreamOutput('Report Engine 正在调度任务...'); 3275 resetReportStreamOutput('Report Engine 正在调度任务...');
3174 3276
3175 setGenerateButtonState(true); 3277 setGenerateButtonState(true);
@@ -3381,9 +3483,6 @@ @@ -3381,9 +3483,6 @@
3381 3483
3382 // 往黑色控制台输出区域追加一条流式日志 3484 // 往黑色控制台输出区域追加一条流式日志
3383 function appendReportStreamLine(message, level = 'info', options = {}) { 3485 function appendReportStreamLine(message, level = 'info', options = {}) {
3384 - const consoleOutput = document.getElementById('consoleOutput');  
3385 - if (!consoleOutput) return;  
3386 -  
3387 if (level === 'chunk' && !options.force) { 3486 if (level === 'chunk' && !options.force) {
3388 return; // 章节内容流式写入不再逐条输出 3487 return; // 章节内容流式写入不再逐条输出
3389 } 3488 }
@@ -3408,8 +3507,7 @@ @@ -3408,8 +3507,7 @@
3408 textSpan.textContent = message; 3507 textSpan.textContent = message;
3409 line.appendChild(textSpan); 3508 line.appendChild(textSpan);
3410 3509
3411 - consoleOutput.appendChild(line);  
3412 - consoleOutput.scrollTop = consoleOutput.scrollHeight; 3510 + appendConsoleElement('report', line);
3413 } 3511 }
3414 3512
3415 function startStreamHeartbeat() { 3513 function startStreamHeartbeat() {