戒酒的李白

Introduce the function of initially restoring the real-time output of console information.

@@ -74,14 +74,33 @@ def read_log_from_file(app_name, tail_lines=None): @@ -74,14 +74,33 @@ def read_log_from_file(app_name, tail_lines=None):
74 74
75 def read_process_output(process, app_name): 75 def read_process_output(process, app_name):
76 """读取进程输出并写入文件""" 76 """读取进程输出并写入文件"""
  77 + import select
  78 + import sys
  79 +
77 while True: 80 while True:
78 try: 81 try:
79 if process.poll() is not None: 82 if process.poll() is not None:
  83 + # 进程结束,读取剩余输出
  84 + remaining_output = process.stdout.read()
  85 + if remaining_output:
  86 + lines = remaining_output.decode('utf-8', errors='replace').split('\n')
  87 + for line in lines:
  88 + line = line.strip()
  89 + if line:
  90 + timestamp = datetime.now().strftime('%H:%M:%S')
  91 + formatted_line = f"[{timestamp}] {line}"
  92 + write_log_to_file(app_name, formatted_line)
  93 + socketio.emit('console_output', {
  94 + 'app': app_name,
  95 + 'line': formatted_line
  96 + })
80 break 97 break
81 98
  99 + # 使用非阻塞读取
  100 + if sys.platform == 'win32':
  101 + # Windows下使用不同的方法
82 output = process.stdout.readline() 102 output = process.stdout.readline()
83 if output: 103 if output:
84 - # 使用UTF-8解码,忽略错误字符  
85 line = output.decode('utf-8', errors='replace').strip() 104 line = output.decode('utf-8', errors='replace').strip()
86 if line: 105 if line:
87 timestamp = datetime.now().strftime('%H:%M:%S') 106 timestamp = datetime.now().strftime('%H:%M:%S')
@@ -95,6 +114,29 @@ def read_process_output(process, app_name): @@ -95,6 +114,29 @@ def read_process_output(process, app_name):
95 'app': app_name, 114 'app': app_name,
96 'line': formatted_line 115 'line': formatted_line
97 }) 116 })
  117 + else:
  118 + # 没有输出时短暂休眠
  119 + time.sleep(0.1)
  120 + else:
  121 + # Unix系统使用select
  122 + ready, _, _ = select.select([process.stdout], [], [], 0.1)
  123 + if ready:
  124 + output = process.stdout.readline()
  125 + if output:
  126 + line = output.decode('utf-8', errors='replace').strip()
  127 + if line:
  128 + timestamp = datetime.now().strftime('%H:%M:%S')
  129 + formatted_line = f"[{timestamp}] {line}"
  130 +
  131 + # 写入日志文件
  132 + write_log_to_file(app_name, formatted_line)
  133 +
  134 + # 发送到前端
  135 + socketio.emit('console_output', {
  136 + 'app': app_name,
  137 + 'line': formatted_line
  138 + })
  139 +
98 except Exception as e: 140 except Exception as e:
99 error_msg = f"Error reading output for {app_name}: {e}" 141 error_msg = f"Error reading output for {app_name}: {e}"
100 print(error_msg) 142 print(error_msg)
@@ -126,16 +168,19 @@ def start_streamlit_app(app_name, script_path, port): @@ -126,16 +168,19 @@ def start_streamlit_app(app_name, script_path, port):
126 '--server.port', str(port), 168 '--server.port', str(port),
127 '--server.headless', 'true', 169 '--server.headless', 'true',
128 '--browser.gatherUsageStats', 'false', 170 '--browser.gatherUsageStats', 'false',
129 - '--logger.level', 'info' 171 + '--logger.level', 'debug', # 增加日志详细程度
  172 + '--server.enableCORS', 'false'
130 ] 173 ]
131 174
132 - # 设置环境变量确保UTF-8编码 175 + # 设置环境变量确保UTF-8编码和减少缓冲
133 env = os.environ.copy() 176 env = os.environ.copy()
134 env.update({ 177 env.update({
135 'PYTHONIOENCODING': 'utf-8', 178 'PYTHONIOENCODING': 'utf-8',
136 'PYTHONUTF8': '1', 179 'PYTHONUTF8': '1',
137 'LANG': 'en_US.UTF-8', 180 'LANG': 'en_US.UTF-8',
138 - 'LC_ALL': 'en_US.UTF-8' 181 + 'LC_ALL': 'en_US.UTF-8',
  182 + 'PYTHONUNBUFFERED': '1', # 禁用Python缓冲
  183 + 'STREAMLIT_BROWSER_GATHER_USAGE_STATS': 'false'
139 }) 184 })
140 185
141 # 使用当前工作目录而不是脚本目录 186 # 使用当前工作目录而不是脚本目录
@@ -143,11 +188,12 @@ def start_streamlit_app(app_name, script_path, port): @@ -143,11 +188,12 @@ def start_streamlit_app(app_name, script_path, port):
143 cmd, 188 cmd,
144 stdout=subprocess.PIPE, 189 stdout=subprocess.PIPE,
145 stderr=subprocess.STDOUT, 190 stderr=subprocess.STDOUT,
146 - bufsize=1, 191 + bufsize=0, # 无缓冲
147 universal_newlines=False, 192 universal_newlines=False,
148 cwd=os.getcwd(), 193 cwd=os.getcwd(),
149 env=env, 194 env=env,
150 - encoding=None # 让我们手动处理编码 195 + encoding=None, # 让我们手动处理编码
  196 + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
151 ) 197 )
152 198
153 processes[app_name]['process'] = process 199 processes[app_name]['process'] = process
@@ -314,6 +360,27 @@ def get_output(app_name): @@ -314,6 +360,27 @@ def get_output(app_name):
314 'output': output_lines 360 'output': output_lines
315 }) 361 })
316 362
  363 +@app.route('/api/test_log/<app_name>')
  364 +def test_log(app_name):
  365 + """测试日志写入功能"""
  366 + if app_name not in processes:
  367 + return jsonify({'success': False, 'message': '未知应用'})
  368 +
  369 + # 写入测试消息
  370 + test_msg = f"[{datetime.now().strftime('%H:%M:%S')}] 测试日志消息 - {datetime.now()}"
  371 + write_log_to_file(app_name, test_msg)
  372 +
  373 + # 通过Socket.IO发送
  374 + socketio.emit('console_output', {
  375 + 'app': app_name,
  376 + 'line': test_msg
  377 + })
  378 +
  379 + return jsonify({
  380 + 'success': True,
  381 + 'message': f'测试消息已写入 {app_name} 日志'
  382 + })
  383 +
317 @app.route('/api/search', methods=['POST']) 384 @app.route('/api/search', methods=['POST'])
318 def search(): 385 def search():
319 """统一搜索接口""" 386 """统一搜索接口"""
@@ -172,32 +172,16 @@ @@ -172,32 +172,16 @@
172 /* 控制台输出 */ 172 /* 控制台输出 */
173 .console-output { 173 .console-output {
174 flex: 1; 174 flex: 1;
  175 + padding: 15px;
175 background-color: #000000; 176 background-color: #000000;
176 color: #00ff00; 177 color: #00ff00;
177 font-family: 'Courier New', monospace; 178 font-family: 'Courier New', monospace;
178 font-size: 12px; 179 font-size: 12px;
179 - overflow: hidden; /* 隐藏滚动条,由内部面板控制 */  
180 - position: relative;  
181 - min-height: 0; /* 允许内容缩小 */  
182 - max-height: 100%; /* 限制最大高度 */  
183 - }  
184 -  
185 - /* 控制台面板 */  
186 - .console-panel {  
187 - position: absolute;  
188 - top: 0;  
189 - left: 0;  
190 - width: 100%;  
191 - height: 100%;  
192 - padding: 15px;  
193 overflow-y: auto; 180 overflow-y: auto;
194 white-space: pre-wrap; 181 white-space: pre-wrap;
195 word-break: break-all; 182 word-break: break-all;
196 - display: none; /* 默认隐藏 */  
197 - }  
198 -  
199 - .console-panel.active {  
200 - display: block; /* 激活时显示 */ 183 + min-height: 0; /* 允许内容缩小 */
  184 + max-height: 100%; /* 限制最大高度 */
201 } 185 }
202 186
203 .console-line { 187 .console-line {
@@ -320,20 +304,7 @@ @@ -320,20 +304,7 @@
320 304
321 <!-- 控制台输出 --> 305 <!-- 控制台输出 -->
322 <div class="console-output" id="consoleOutput"> 306 <div class="console-output" id="consoleOutput">
323 - <!-- Insight Engine 控制台 -->  
324 - <div class="console-panel active" id="console-insight">  
325 - <div class="console-line">[系统] Insight Engine 控制台</div>  
326 - </div>  
327 -  
328 - <!-- Media Engine 控制台 -->  
329 - <div class="console-panel" id="console-media">  
330 - <div class="console-line">[系统] Media Engine 控制台</div>  
331 - </div>  
332 -  
333 - <!-- Query Engine 控制台 -->  
334 - <div class="console-panel" id="console-query">  
335 - <div class="console-line">[系统] Query Engine 控制台</div>  
336 - </div> 307 + <div class="console-line">[系统] 等待连接...</div>
337 </div> 308 </div>
338 </div> 309 </div>
339 </div> 310 </div>
@@ -367,13 +338,10 @@ @@ -367,13 +338,10 @@
367 checkStatus(); 338 checkStatus();
368 setInterval(checkStatus, 5000); 339 setInterval(checkStatus, 5000);
369 340
370 - // 初始化行计数  
371 - lastLineCount = { insight: 1, media: 1, query: 1 }; // 跳过初始消息  
372 -  
373 // 定期刷新控制台输出 341 // 定期刷新控制台输出
374 setInterval(() => { 342 setInterval(() => {
375 refreshConsoleOutput(); 343 refreshConsoleOutput();
376 - }, 2000); 344 + }, 1000);
377 345
378 // 延迟预加载iframe以确保应用启动完成 346 // 延迟预加载iframe以确保应用启动完成
379 setTimeout(() => { 347 setTimeout(() => {
@@ -395,16 +363,8 @@ @@ -395,16 +363,8 @@
395 }); 363 });
396 364
397 socket.on('console_output', function(data) { 365 socket.on('console_output', function(data) {
398 - // 直接添加到对应应用的控制台面板,不依赖currentApp  
399 - const consolePanel = document.getElementById(`console-${data.app}`);  
400 - if (consolePanel) {  
401 - const div = document.createElement('div');  
402 - div.className = 'console-line';  
403 - div.textContent = data.line;  
404 - consolePanel.appendChild(div);  
405 -  
406 - // 自动滚动到底部  
407 - consolePanel.scrollTop = consolePanel.scrollHeight; 366 + if (data.app === currentApp) {
  367 + addConsoleOutput(data.line);
408 } 368 }
409 }); 369 });
410 370
@@ -489,24 +449,12 @@ @@ -489,24 +449,12 @@
489 449
490 currentApp = app; 450 currentApp = app;
491 451
492 - // 切换控制台面板显示  
493 - document.querySelectorAll('.console-panel').forEach(panel => {  
494 - panel.classList.remove('active');  
495 - });  
496 - document.getElementById(`console-${app}`).classList.add('active'); 452 + // 清空并加载新的控制台输出
  453 + document.getElementById('consoleOutput').innerHTML = '<div class="console-line">[系统] 切换到 ' + app + ' 应用</div>';
497 454
498 - // 只在面板为空时加载输出,不重置行计数  
499 - const consolePanel = document.getElementById(`console-${app}`);  
500 - if (consolePanel && consolePanel.children.length <= 1) { // 只有初始消息 455 + // 重置行计数
  456 + lastLineCount[app] = 0;
501 loadConsoleOutput(app); 457 loadConsoleOutput(app);
502 - }  
503 -  
504 - // 切换后滚动到底部  
505 - setTimeout(() => {  
506 - if (consolePanel) {  
507 - consolePanel.scrollTop = consolePanel.scrollHeight;  
508 - }  
509 - }, 100);  
510 458
511 // 更新嵌入页面 459 // 更新嵌入页面
512 updateEmbeddedPage(app); 460 updateEmbeddedPage(app);
@@ -521,7 +469,7 @@ @@ -521,7 +469,7 @@
521 .then(response => response.json()) 469 .then(response => response.json())
522 .then(data => { 470 .then(data => {
523 if (data.success && data.output.length > 0) { 471 if (data.success && data.output.length > 0) {
524 - const consolePanel = document.getElementById(`console-${app}`); 472 + const consoleOutput = document.getElementById('consoleOutput');
525 473
526 // 只添加新的行 474 // 只添加新的行
527 const lastCount = lastLineCount[app] || 0; 475 const lastCount = lastLineCount[app] || 0;
@@ -531,11 +479,11 @@ @@ -531,11 +479,11 @@
531 const div = document.createElement('div'); 479 const div = document.createElement('div');
532 div.className = 'console-line'; 480 div.className = 'console-line';
533 div.textContent = line; 481 div.textContent = line;
534 - consolePanel.appendChild(div); 482 + consoleOutput.appendChild(div);
535 }); 483 });
536 484
537 lastLineCount[app] = data.output.length; 485 lastLineCount[app] = data.output.length;
538 - consolePanel.scrollTop = consolePanel.scrollHeight; 486 + consoleOutput.scrollTop = consoleOutput.scrollHeight;
539 } 487 }
540 }) 488 })
541 .catch(error => { 489 .catch(error => {
@@ -543,19 +491,17 @@ @@ -543,19 +491,17 @@
543 }); 491 });
544 } 492 }
545 493
546 - // 刷新所有应用的控制台输出 494 + // 刷新当前应用的控制台输出
547 function refreshConsoleOutput() { 495 function refreshConsoleOutput() {
548 - // 为每个应用刷新输出,不只是当前应用  
549 - Object.keys(appStatus).forEach(app => {  
550 - if (appStatus[app] === 'running' || appStatus[app] === 'starting') {  
551 - fetch(`/api/output/${app}`) 496 + if (appStatus[currentApp] === 'running' || appStatus[currentApp] === 'starting') {
  497 + fetch(`/api/output/${currentApp}`)
552 .then(response => response.json()) 498 .then(response => response.json())
553 .then(data => { 499 .then(data => {
554 if (data.success && data.output.length > 0) { 500 if (data.success && data.output.length > 0) {
555 - const consolePanel = document.getElementById(`console-${app}`); 501 + const consoleOutput = document.getElementById('consoleOutput');
556 502
557 // 只添加新的行 503 // 只添加新的行
558 - const lastCount = lastLineCount[app] || 0; 504 + const lastCount = lastLineCount[currentApp] || 0;
559 const newLines = data.output.slice(lastCount); 505 const newLines = data.output.slice(lastCount);
560 506
561 if (newLines.length > 0) { 507 if (newLines.length > 0) {
@@ -563,37 +509,30 @@ @@ -563,37 +509,30 @@
563 const div = document.createElement('div'); 509 const div = document.createElement('div');
564 div.className = 'console-line'; 510 div.className = 'console-line';
565 div.textContent = line; 511 div.textContent = line;
566 - consolePanel.appendChild(div); 512 + consoleOutput.appendChild(div);
567 }); 513 });
568 514
569 - lastLineCount[app] = data.output.length;  
570 - // 只有当前显示的面板才自动滚动  
571 - if (app === currentApp) {  
572 - consolePanel.scrollTop = consolePanel.scrollHeight;  
573 - } 515 + lastLineCount[currentApp] = data.output.length;
  516 + consoleOutput.scrollTop = consoleOutput.scrollHeight;
574 } 517 }
575 } 518 }
576 }) 519 })
577 .catch(error => { 520 .catch(error => {
578 - console.error(`刷新${app}输出失败:`, error); 521 + console.error('刷新输出失败:', error);
579 }); 522 });
580 } 523 }
581 - });  
582 } 524 }
583 525
584 // 添加控制台输出 526 // 添加控制台输出
585 function addConsoleOutput(line) { 527 function addConsoleOutput(line) {
586 - // 根据当前应用添加到对应的控制台面板  
587 - const consolePanel = document.getElementById(`console-${currentApp}`);  
588 - if (consolePanel) { 528 + const consoleOutput = document.getElementById('consoleOutput');
589 const div = document.createElement('div'); 529 const div = document.createElement('div');
590 div.className = 'console-line'; 530 div.className = 'console-line';
591 div.textContent = line; 531 div.textContent = line;
592 - consolePanel.appendChild(div); 532 + consoleOutput.appendChild(div);
593 533
594 // 自动滚动到底部显示最新内容 534 // 自动滚动到底部显示最新内容
595 - consolePanel.scrollTop = consolePanel.scrollHeight;  
596 - } 535 + consoleOutput.scrollTop = consoleOutput.scrollHeight;
597 } 536 }
598 537
599 // 预加载的iframe存储 538 // 预加载的iframe存储