""" CorrectBench Web Interface 基于 Streamlit 的前端界面,集成配置选择与实时日志输出 """ import streamlit as st import subprocess import threading import queue import time import os import sys # 添加项目根目录到路径 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from frontend.config_loader import get_config_files, get_config_path, load_tasks_from_config from frontend.log_capture import LogReader # 页面配置 st.set_page_config( page_title="CorrectBench", page_icon="🛠️", layout="wide" ) # 进程结束标记,用于从队列中通知主线程进程已结束 _PROCESS_DONE_MARKER = "__PROCESS_DONE__" def run_process(log_queue: queue.Queue, stop_event: threading.Event, config_path: str): """后台线程运行生成进程,进程结束后向队列发送结束标记""" process = subprocess.Popen( [sys.executable, "main.py", "-c", config_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, cwd=os.path.dirname(os.path.abspath(__file__)) ) # 使用 LogReader 封装日志读取 reader = LogReader(process, log_queue, stop_event) reader.start() # 等待进程结束,同时检测 stop_event 以支持用户主动终止 while process.poll() is None: if stop_event.is_set(): process.terminate() try: process.wait(timeout=3) except subprocess.TimeoutExpired: process.kill() process.wait() break time.sleep(0.1) # 等待日志读取线程结束,确保所有输出都已入队 reader.join(timeout=5) # 向队列发送进程结束标记,通知主线程重置状态 log_queue.put(_PROCESS_DONE_MARKER) def main(): # 初始化 session_state if "running" not in st.session_state: st.session_state.running = False if "log_queue" not in st.session_state: st.session_state.log_queue = queue.Queue() if "stop_event" not in st.session_state: st.session_state.stop_event = threading.Event() if "log_lines" not in st.session_state: st.session_state.log_lines = [] # ===== 侧边栏:配置选择 ===== with st.sidebar: st.header("⚙️ 配置") config_files = get_config_files() if config_files: default_idx = config_files.index("custom.yaml") if "custom.yaml" in config_files else 0 selected_config = st.selectbox( "选择配置文件", config_files, index=default_idx, disabled=st.session_state.running, ) config_path = get_config_path(selected_config) else: st.warning("未找到配置文件,将使用默认配置") config_path = "config/custom.yaml" selected_config = "custom.yaml" # 显示当前配置中的题目列表 tasks = load_tasks_from_config(config_path) if tasks: with st.expander(f"📋 题目列表 ({len(tasks)} 个)", expanded=False): for t in tasks: st.text(t) else: st.info("配置中未指定题目列表") st.markdown("---") st.caption(f"配置路径: `{config_path}`") # ===== 主区域 ===== st.title("CorrectBench - 测试基准生成") st.markdown(f"当前配置: `{selected_config}`") col1, col2, col3 = st.columns([1, 1, 2]) start_btn = col1.button("▶️ 开始生成", disabled=st.session_state.running, use_container_width=True) stop_btn = col2.button("⏹️ 停止", disabled=not st.session_state.running, use_container_width=True) # 运行状态指示 if st.session_state.running: col3.markdown("🟢 **运行中...**") elif st.session_state.log_lines and st.session_state.log_lines[-1] == "[完成]": col3.markdown("✅ **运行完成**") elif st.session_state.log_lines and st.session_state.log_lines[-1] == "[已停止]": col3.markdown("🔴 **已停止**") st.markdown("---") # 处理开始按钮 if start_btn and not st.session_state.running: st.session_state.running = True st.session_state.log_lines = ["正在启动..."] st.session_state.stop_event.clear() # 清空队列中可能残留的旧消息 while not st.session_state.log_queue.empty(): try: st.session_state.log_queue.get_nowait() except queue.Empty: break thread = threading.Thread( target=run_process, args=(st.session_state.log_queue, st.session_state.stop_event, config_path), daemon=True ) thread.start() if stop_btn and st.session_state.running: st.session_state.stop_event.set() st.session_state.log_lines.append("[正在停止...]") # 读取日志并检测进程结束标记 try: while True: try: line = st.session_state.log_queue.get_nowait() if line == _PROCESS_DONE_MARKER: # 进程已自然结束,重置运行状态 st.session_state.running = False st.session_state.log_lines.append("[完成]") else: st.session_state.log_lines.append(line.rstrip("\n")) except queue.Empty: break except Exception: pass # 显示日志 st.markdown("### 终端输出") if st.session_state.log_lines: st.code("\n".join(st.session_state.log_lines[-200:]), language=None) else: st.info("等待开始生成...") # 运行中自动刷新 if st.session_state.running: time.sleep(0.3) st.rerun() if __name__ == "__main__": main()