From 4d90f56b42124b58d2bb2676baaeab21be88654e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=92=E9=85=92=E7=9A=84=E6=9D=8E=E7=99=BD?= <670939375@qq.com> Date: Sun, 24 Aug 2025 17:13:00 +0800 Subject: [PATCH] Introduce the function of initially restoring the real-time output of console information. --- app.py | 109 +++++++++++++++++++++++------ templates/index.html | 159 +++++++++++++------------------------------ 2 files changed, 137 insertions(+), 131 deletions(-) diff --git a/app.py b/app.py index cfd8e56..4aaf81a 100644 --- a/app.py +++ b/app.py @@ -74,27 +74,69 @@ def read_log_from_file(app_name, tail_lines=None): def read_process_output(process, app_name): """读取进程输出并写入文件""" + import select + import sys + while True: try: if process.poll() is not None: + # 进程结束,读取剩余输出 + remaining_output = process.stdout.read() + if remaining_output: + lines = remaining_output.decode('utf-8', errors='replace').split('\n') + for line in lines: + line = line.strip() + if line: + timestamp = datetime.now().strftime('%H:%M:%S') + formatted_line = f"[{timestamp}] {line}" + write_log_to_file(app_name, formatted_line) + socketio.emit('console_output', { + 'app': app_name, + 'line': formatted_line + }) break - output = process.stdout.readline() - if output: - # 使用UTF-8解码,忽略错误字符 - line = output.decode('utf-8', errors='replace').strip() - if line: - timestamp = datetime.now().strftime('%H:%M:%S') - formatted_line = f"[{timestamp}] {line}" - - # 写入日志文件 - write_log_to_file(app_name, formatted_line) - - # 发送到前端 - socketio.emit('console_output', { - 'app': app_name, - 'line': formatted_line - }) + # 使用非阻塞读取 + if sys.platform == 'win32': + # Windows下使用不同的方法 + output = process.stdout.readline() + if output: + line = output.decode('utf-8', errors='replace').strip() + if line: + timestamp = datetime.now().strftime('%H:%M:%S') + formatted_line = f"[{timestamp}] {line}" + + # 写入日志文件 + write_log_to_file(app_name, formatted_line) + + # 发送到前端 + socketio.emit('console_output', { + 'app': app_name, + 'line': formatted_line + }) + else: + # 没有输出时短暂休眠 + time.sleep(0.1) + else: + # Unix系统使用select + ready, _, _ = select.select([process.stdout], [], [], 0.1) + if ready: + output = process.stdout.readline() + if output: + line = output.decode('utf-8', errors='replace').strip() + if line: + timestamp = datetime.now().strftime('%H:%M:%S') + formatted_line = f"[{timestamp}] {line}" + + # 写入日志文件 + write_log_to_file(app_name, formatted_line) + + # 发送到前端 + socketio.emit('console_output', { + 'app': app_name, + 'line': formatted_line + }) + except Exception as e: error_msg = f"Error reading output for {app_name}: {e}" print(error_msg) @@ -126,16 +168,19 @@ def start_streamlit_app(app_name, script_path, port): '--server.port', str(port), '--server.headless', 'true', '--browser.gatherUsageStats', 'false', - '--logger.level', 'info' + '--logger.level', 'debug', # 增加日志详细程度 + '--server.enableCORS', 'false' ] - # 设置环境变量确保UTF-8编码 + # 设置环境变量确保UTF-8编码和减少缓冲 env = os.environ.copy() env.update({ 'PYTHONIOENCODING': 'utf-8', 'PYTHONUTF8': '1', 'LANG': 'en_US.UTF-8', - 'LC_ALL': 'en_US.UTF-8' + 'LC_ALL': 'en_US.UTF-8', + 'PYTHONUNBUFFERED': '1', # 禁用Python缓冲 + 'STREAMLIT_BROWSER_GATHER_USAGE_STATS': 'false' }) # 使用当前工作目录而不是脚本目录 @@ -143,11 +188,12 @@ def start_streamlit_app(app_name, script_path, port): cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - bufsize=1, + bufsize=0, # 无缓冲 universal_newlines=False, cwd=os.getcwd(), env=env, - encoding=None # 让我们手动处理编码 + encoding=None, # 让我们手动处理编码 + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 ) processes[app_name]['process'] = process @@ -314,6 +360,27 @@ def get_output(app_name): 'output': output_lines }) +@app.route('/api/test_log/') +def test_log(app_name): + """测试日志写入功能""" + if app_name not in processes: + return jsonify({'success': False, 'message': '未知应用'}) + + # 写入测试消息 + test_msg = f"[{datetime.now().strftime('%H:%M:%S')}] 测试日志消息 - {datetime.now()}" + write_log_to_file(app_name, test_msg) + + # 通过Socket.IO发送 + socketio.emit('console_output', { + 'app': app_name, + 'line': test_msg + }) + + return jsonify({ + 'success': True, + 'message': f'测试消息已写入 {app_name} 日志' + }) + @app.route('/api/search', methods=['POST']) def search(): """统一搜索接口""" diff --git a/templates/index.html b/templates/index.html index 83e579b..a8ff2b4 100644 --- a/templates/index.html +++ b/templates/index.html @@ -172,32 +172,16 @@ /* 控制台输出 */ .console-output { flex: 1; + padding: 15px; background-color: #000000; color: #00ff00; font-family: 'Courier New', monospace; font-size: 12px; - overflow: hidden; /* 隐藏滚动条,由内部面板控制 */ - position: relative; - min-height: 0; /* 允许内容缩小 */ - max-height: 100%; /* 限制最大高度 */ - } - - /* 控制台面板 */ - .console-panel { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - padding: 15px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; - display: none; /* 默认隐藏 */ - } - - .console-panel.active { - display: block; /* 激活时显示 */ + min-height: 0; /* 允许内容缩小 */ + max-height: 100%; /* 限制最大高度 */ } .console-line { @@ -320,20 +304,7 @@
- -
-
[系统] Insight Engine 控制台
-
- - -
-
[系统] Media Engine 控制台
-
- - -
-
[系统] Query Engine 控制台
-
+
[系统] 等待连接...
@@ -367,13 +338,10 @@ checkStatus(); setInterval(checkStatus, 5000); - // 初始化行计数 - lastLineCount = { insight: 1, media: 1, query: 1 }; // 跳过初始消息 - // 定期刷新控制台输出 setInterval(() => { refreshConsoleOutput(); - }, 2000); + }, 1000); // 延迟预加载iframe以确保应用启动完成 setTimeout(() => { @@ -395,16 +363,8 @@ }); socket.on('console_output', function(data) { - // 直接添加到对应应用的控制台面板,不依赖currentApp - const consolePanel = document.getElementById(`console-${data.app}`); - if (consolePanel) { - const div = document.createElement('div'); - div.className = 'console-line'; - div.textContent = data.line; - consolePanel.appendChild(div); - - // 自动滚动到底部 - consolePanel.scrollTop = consolePanel.scrollHeight; + if (data.app === currentApp) { + addConsoleOutput(data.line); } }); @@ -489,24 +449,12 @@ currentApp = app; - // 切换控制台面板显示 - document.querySelectorAll('.console-panel').forEach(panel => { - panel.classList.remove('active'); - }); - document.getElementById(`console-${app}`).classList.add('active'); + // 清空并加载新的控制台输出 + document.getElementById('consoleOutput').innerHTML = '
[系统] 切换到 ' + app + ' 应用
'; - // 只在面板为空时加载输出,不重置行计数 - const consolePanel = document.getElementById(`console-${app}`); - if (consolePanel && consolePanel.children.length <= 1) { // 只有初始消息 - loadConsoleOutput(app); - } - - // 切换后滚动到底部 - setTimeout(() => { - if (consolePanel) { - consolePanel.scrollTop = consolePanel.scrollHeight; - } - }, 100); + // 重置行计数 + lastLineCount[app] = 0; + loadConsoleOutput(app); // 更新嵌入页面 updateEmbeddedPage(app); @@ -521,7 +469,7 @@ .then(response => response.json()) .then(data => { if (data.success && data.output.length > 0) { - const consolePanel = document.getElementById(`console-${app}`); + const consoleOutput = document.getElementById('consoleOutput'); // 只添加新的行 const lastCount = lastLineCount[app] || 0; @@ -531,11 +479,11 @@ const div = document.createElement('div'); div.className = 'console-line'; div.textContent = line; - consolePanel.appendChild(div); + consoleOutput.appendChild(div); }); lastLineCount[app] = data.output.length; - consolePanel.scrollTop = consolePanel.scrollHeight; + consoleOutput.scrollTop = consoleOutput.scrollHeight; } }) .catch(error => { @@ -543,57 +491,48 @@ }); } - // 刷新所有应用的控制台输出 + // 刷新当前应用的控制台输出 function refreshConsoleOutput() { - // 为每个应用刷新输出,不只是当前应用 - Object.keys(appStatus).forEach(app => { - if (appStatus[app] === 'running' || appStatus[app] === 'starting') { - fetch(`/api/output/${app}`) - .then(response => response.json()) - .then(data => { - if (data.success && data.output.length > 0) { - const consolePanel = document.getElementById(`console-${app}`); + if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') { + fetch(`/api/output/${currentApp}`) + .then(response => response.json()) + .then(data => { + if (data.success && data.output.length > 0) { + const consoleOutput = document.getElementById('consoleOutput'); + + // 只添加新的行 + const lastCount = lastLineCount[currentApp] || 0; + const newLines = data.output.slice(lastCount); + + if (newLines.length > 0) { + newLines.forEach(line => { + const div = document.createElement('div'); + div.className = 'console-line'; + div.textContent = line; + consoleOutput.appendChild(div); + }); - // 只添加新的行 - const lastCount = lastLineCount[app] || 0; - const newLines = data.output.slice(lastCount); - - if (newLines.length > 0) { - newLines.forEach(line => { - const div = document.createElement('div'); - div.className = 'console-line'; - div.textContent = line; - consolePanel.appendChild(div); - }); - - lastLineCount[app] = data.output.length; - // 只有当前显示的面板才自动滚动 - if (app === currentApp) { - consolePanel.scrollTop = consolePanel.scrollHeight; - } - } + lastLineCount[currentApp] = data.output.length; + consoleOutput.scrollTop = consoleOutput.scrollHeight; } - }) - .catch(error => { - console.error(`刷新${app}输出失败:`, error); - }); - } - }); + } + }) + .catch(error => { + console.error('刷新输出失败:', error); + }); + } } // 添加控制台输出 function addConsoleOutput(line) { - // 根据当前应用添加到对应的控制台面板 - const consolePanel = document.getElementById(`console-${currentApp}`); - if (consolePanel) { - const div = document.createElement('div'); - div.className = 'console-line'; - div.textContent = line; - consolePanel.appendChild(div); + const consoleOutput = document.getElementById('consoleOutput'); + const div = document.createElement('div'); + div.className = 'console-line'; + div.textContent = line; + consoleOutput.appendChild(div); - // 自动滚动到底部显示最新内容 - consolePanel.scrollTop = consolePanel.scrollHeight; - } + // 自动滚动到底部显示最新内容 + consoleOutput.scrollTop = consoleOutput.scrollHeight; } // 预加载的iframe存储