# ==================== Repo 任务模块 ==================== """ Repo 任务相关的所有函数和界面组件 支持多用户并发:使用 gr.State 管理每个用户会话的状态 使用统一进度管理模块存储数据 """ import json import os from typing import List, Tuple, Optional, Dict, Any import gradio as gr # 导入统一进度管理模块 import progress_manager # 导入 Repo 环境 import sys current_dir = os.path.dirname(os.path.abspath(__file__)) repoenv_path = os.path.join(current_dir, "RepoEnv") if os.path.exists(repoenv_path): sys.path.insert(0, repoenv_path) from RepoEnv_v7 import ComputerEnvSetupInductionEnvV7_5 # ------------------- 常量 ------------------- REPO_MAX_STEPS = 120 # ------------------- Example Text ------------------- REPO_EXAMPLE_TEXT = """ ## 📖 Repository Environment Usage Example ### Example Scenario You need to configure the Python environment and install the correct package versions so that the project can run successfully: python run.py ### Available Commands - `pip install python==3.10` - Install Python version - `pip install pkg0==1.2` - Install package (supports version constraints) - `pip uninstall pkg0` - Uninstall package - `pip list` - View current environment status - `repo tree` - View repository structure - `python run.py` - Run project (success means task completed) ### Example Hidden Rules (Users need to discover these in actual tasks) - Requires python>=3.10 - Requires pkg1==1.0 - Requires pkg2>=1.2,<=2.0 - Requires pkg3<=1.0 - All version numbers of pkg3 must match pkg1 (including integer and decimal parts) - Major version number of pkg2 must match pkg1 (integer part) ### Example Steps 1. **Step 1**: Input `pip install python==3.10`, click "Execute Action" - Environment feedback: Successfully installed python==3.10 - Reason: Successfully installed 2. **Step 2**: Input `python run.py`, click "Execute Action" - Environment feedback: ModuleNotFoundError: No module named 'pkg1'. - Reason: pkg1 not installed 3. **Step 3**: Input `pip install pkg1==1.0`, click "Execute Action" - Environment feedback: Successfully installed pkg1==1.0 - Reason: Successfully installed pkg1==1.0 4. **Step 4**: Input `python run.py`, click "Execute Action" - Environment feedback: ModuleNotFoundError: No module named 'pkg2'. - Reason: pkg2 not installed 5. **Step 5**: Input `pip install pkg2==2.0`, click "Execute Action" - Environment feedback: Successfully installed pkg2==2.0 - Reason: Successfully installed pkg2==2.0 6. **Step 6**: Input `python run.py`, click "Execute Action" - Environment feedback: RuntimeError: ABI mismatch detected between 'pkg6' and dependent packages. - Reason: Major version number of pkg2 does not match pkg1 7. **Step 7**: Input `pip install pkg2==1.0`, click "Execute Action" - Environment feedback: Successfully installed pkg3==1.0 - Reason: Successfully installed pkg3==1.2 8. **Step 8**: Input `python run.py`, click "Execute Action" - Environment feedback: ModuleNotFoundError: No module named 'pkg3'. - Reason: pkg2 not installed 9. **Step 9**: Input `pip install pkg3==1.0`, click "Execute Action" - Environment feedback: Successfully installed pkg3==1.0 - Reason: Successfully installed pkg3==0.1 10. **Step 10**: Input `python run.py`, click "Execute Action" - Environment feedback: RuntimeError: tightly-coupled components are out of sync with 'pkg1'. - Reason: All version numbers of pkg3 must match pkg1 (including integer and decimal parts) 11. **Step 11**: Input `pip install pkg3==1.0`, click "Execute Action" - Environment feedback: Successfully installed pkg3==1.0 - Reason: Successfully installed pkg3==1.0 12. **Step 12**: Input `python run.py`, click "Execute Action" - Environment feedback: 🎉 Task completed! Project runs successfully! - Reason: All conditions met ### Tips - Packages may have dependencies and version conflicts - Need to carefully handle version constraints - Maximum 120 steps allowed ### Goal Successfully run `python run.py` so that the project can execute normally """ # ------------------- 状态管理 ------------------- def create_repo_state() -> Dict[str, Any]: """创建初始的 Repo 任务状态(每个用户会话独立)""" return { 'env': None, # ComputerEnvSetupInductionEnvV7_5 实例 'test_data': [], # 测试数据 'current_env_idx': 0, # 当前环境索引 'history_records': [], # 操作历史记录 } # ------------------- 工具函数 ------------------- def format_repo_state(obs: Dict[str, Any]) -> str: """格式化 Repo 环境状态显示""" lines = [] if obs.get("python_version"): py_ver = obs["python_version"] lines.append(f"Python: {py_ver[0]}.{py_ver[1]}") else: lines.append("Python: ") installed = obs.get("installed", {}) if installed: lines.append("\nInstalled packages:") for pkg in sorted(installed.keys()): ver = installed[pkg] lines.append(f" - {pkg}=={ver[0]}.{ver[1]}") else: lines.append("\nInstalled packages: ") return "\n".join(lines) def load_repo_test_data(state: Dict[str, Any], current_dir: str) -> Tuple[Dict[str, Any], str]: """加载 Repo 测试数据""" test_file = os.path.join( current_dir, "test_data/repo/test_repo_lite_251217.json") if not os.path.exists(test_file): test_file = "test_data/repo/test_repo_lite_251217.json" try: with open(test_file, 'r', encoding='utf-8') as f: state['test_data'] = json.load(f) return state, f"✅ Successfully loaded {len(state['test_data'])} test environments" except FileNotFoundError: return state, f"❌ File not found: {test_file}" except Exception as e: return state, f"❌ Load failed: {str(e)}" def repo_save_progress_internal(state: Dict[str, Any], current_user_id: str, save_dir: str) -> str: """保存 Repo 环境进度(使用统一进度管理模块)""" # Auto-generate user ID if not provided if not current_user_id: import uuid current_user_id = f"user_{uuid.uuid4().hex[:8]}" env = state.get('env') if env is None: return "⚠️ No progress to save" try: obs = env._get_obs() current_env_idx = state.get('current_env_idx', 0) history_records = state.get('history_records', []) # 转换版本格式为 JSON 兼容格式 python_version_json = None if obs.get("python_version"): py_ver = obs["python_version"] if isinstance(py_ver, tuple): python_version_json = list(py_ver) else: python_version_json = py_ver installed_json = {} installed_raw = obs.get("installed", {}) for pkg, ver in installed_raw.items(): if isinstance(ver, tuple): installed_json[pkg] = list(ver) else: installed_json[pkg] = ver env_progress = { "user_id": current_user_id, "env_idx": current_env_idx, "env_idx_display": current_env_idx + 1, # 不再保存 world_spec,因为可以从 test_data[env_idx] 获取 "python_version": python_version_json, "installed": installed_json, "history": history_records, "num_steps": obs.get("step", 0), "done": obs.get("success", False), "success": obs.get("success", False), } result = progress_manager.save_task_environment_progress( current_user_id, save_dir, "repo", current_env_idx, env_progress ) return f"✅ Progress saved (Environment {current_env_idx + 1}, Steps {len(history_records)})" except Exception as e: return f"❌ Save failed: {str(e)}" def repo_load_environment(state: Dict[str, Any], env_idx_display: int, current_user_id: str, save_dir: str) -> Tuple[Dict[str, Any], str, str, str, str, str, str]: """加载 Repo 环境(使用统一进度管理模块) Returns: (state, info, state_display, logic, history_display, progress, steps_info) """ # Auto-generate user ID if not provided if not current_user_id: import uuid current_user_id = f"user_{uuid.uuid4().hex[:8]}" test_data = state.get('test_data', []) if not test_data: return state, "❌ Please load test data first", "", "", "", "Click 'View Uncompleted Problems' button to view progress", "0 / 120" env_idx = env_idx_display - 1 if env_idx < 0 or env_idx >= len(test_data): return state, f"❌ Environment index out of range (1-{len(test_data)})", "", "", "", "Click 'View Unfinished Problems' button to view progress", "0 / 120" # 使用统一进度管理模块检查是否有保存的进度 saved_progress_data = progress_manager.get_task_environment_progress( current_user_id, save_dir, "repo", env_idx ) # 如果有保存的进度,加载它 if saved_progress_data: state['current_env_idx'] = env_idx state['history_records'] = saved_progress_data.get("history", []) num_steps = saved_progress_data.get("num_steps", len(state['history_records'])) # 从 test_data 获取 world_spec(不再从保存的数据中获取,以节省存储空间) # 为了向后兼容,如果保存的数据中有 world_spec,优先使用(旧数据可能没有 test_data) world_spec = saved_progress_data.get("world_spec") if not world_spec and env_idx < len(test_data): world_spec = test_data[env_idx] if world_spec: state['env'] = ComputerEnvSetupInductionEnvV7_5(world_spec, max_steps=REPO_MAX_STEPS) state['env'].step_count = num_steps # 转换 Python 版本格式 py_ver = saved_progress_data.get("python_version") if py_ver and isinstance(py_ver, list): state['env'].python_version = tuple(py_ver) elif py_ver and isinstance(py_ver, tuple): state['env'].python_version = py_ver # 转换 installed 格式 installed_raw = saved_progress_data.get("installed", {}) installed = {} for pkg, ver in installed_raw.items(): if isinstance(ver, list): installed[pkg] = tuple(ver) elif isinstance(ver, tuple): installed[pkg] = ver state['env'].installed = installed state['env'].done = saved_progress_data.get("done", False) state['env'].success = saved_progress_data.get("success", False) obs = state['env']._get_obs() state_display = format_repo_state(obs) history_display = "\n\n".join(state['history_records']) if state['history_records'] else "No history records" info = f"✅ Environment {env_idx_display}/{len(test_data)} loaded\n" info += f"Steps: {len(state['history_records'])}" current_steps = state['env'].step_count steps_info = f"{current_steps} / {REPO_MAX_STEPS}" return state, info, state_display, "", history_display, "Click 'View Unfinished Problems' button to view progress", steps_info # 没有保存的进度,初始化新环境 state['current_env_idx'] = env_idx world_spec = test_data[env_idx] state['env'] = ComputerEnvSetupInductionEnvV7_5(world_spec, max_steps=REPO_MAX_STEPS) state['history_records'] = [] repo_save_progress_internal(state, current_user_id, save_dir) obs = state['env']._get_obs() state_display = format_repo_state(obs) history_display = "Environment initialized (new environment)\n" info = f"✅ Environment {env_idx_display}/{len(test_data)} initialized (new environment)\n" current_steps = state['env'].step_count steps_info = f"{current_steps} / {REPO_MAX_STEPS}" return state, info, state_display, "", history_display, "Click 'View Unfinished Problems' button to view progress", steps_info def repo_step_environment(state: Dict[str, Any], action_str: str, current_user_id: str, save_dir: str) -> Tuple[Dict[str, Any], str, str, str, bool, str]: """执行 Repo 环境一步动作 Returns: (state, feedback, state_display, history_display, done, steps_info) """ env = state.get('env') history_records = state.get('history_records', []) current_state_display = "" if env is not None: obs = env._get_obs() current_state_display = format_repo_state(obs) if env is None: return state, "❌ Please initialize environment first", current_state_display if current_state_display else "Please initialize environment first", "", False, "0 / 120" # Auto-generate user ID if not provided if not current_user_id: import uuid current_user_id = f"user_{uuid.uuid4().hex[:8]}" # 执行动作 obs, reward, done, info = env.step(action_str.strip()) state_display = format_repo_state(obs) history_records.append( f"Step {len(history_records) + 1}: {action_str}\nFeedback: {obs.get('last_message', '')}") state['history_records'] = history_records history_display = "\n\n".join(history_records) repo_save_progress_internal(state, current_user_id, save_dir) feedback_info = obs.get('last_message', '') if done and env.success: feedback_info += "\n🎉 Task completed! Project runs successfully!" current_steps = env.step_count steps_info = f"{current_steps} / {REPO_MAX_STEPS}" return state, feedback_info, state_display, history_display, done, steps_info def repo_reset_environment(state: Dict[str, Any], current_user_id: str, save_dir: str) -> Tuple[Dict[str, Any], str, str, str, str, str]: """重置 Repo 环境 Returns: (state, info, state_display, history_display, progress, steps_info) """ env = state.get('env') if env is None: return state, "❌ Please initialize environment first", "", "", "Click 'View Uncompleted Problems' button to view progress", "0 / 120" env.reset() state['history_records'] = [] repo_save_progress_internal(state, current_user_id, save_dir) obs = env._get_obs() state_display = format_repo_state(obs) history_display = "Environment reset\n" current_steps = env.step_count steps_info = f"{current_steps} / {REPO_MAX_STEPS}" return state, "✅ Environment reset", state_display, history_display, "Click 'View Unfinished Problems' button to view progress", steps_info def get_repo_current_env_idx(state: Dict[str, Any]) -> int: """获取当前 Repo 环境索引""" return state.get('current_env_idx', 0) def get_repo_test_data(state: Dict[str, Any]) -> List[dict]: """获取 Repo 测试数据""" return state.get('test_data', []) def get_repo_history_records(state: Dict[str, Any]) -> List[str]: """获取 Repo 历史记录""" return state.get('history_records', []) def get_repo_progress_summary(state: Dict[str, Any], user_id: str, save_dir: str) -> str: """获取 Repo 任务用户进度摘要(使用统一进度管理模块) Args: state: 会话状态 user_id: 用户ID save_dir: 保存目录 Returns: 格式化的进度摘要字符串 """ # Auto-generate user ID if not provided if not user_id or not user_id.strip(): import uuid user_id = f"user_{uuid.uuid4().hex[:8]}" user_id = user_id.strip() test_data = state.get('test_data', []) # 使用统一进度管理模块加载进度 task_data = progress_manager.load_task_progress(user_id, save_dir, "repo") environments = task_data.get("environments", {}) completed_envs = set() for env_key, progress_data in environments.items(): env_idx = progress_data.get("env_idx", -1) success = progress_data.get("success", False) done = progress_data.get("done", False) num_steps = progress_data.get("num_steps", 0) # 检查是否完成 is_completed = False if success or done: is_completed = True elif num_steps >= REPO_MAX_STEPS: is_completed = True if is_completed: completed_envs.add(env_idx) # 获取总环境数 total_envs = len(test_data) if test_data else 0 if total_envs == 0: return "⚠️ Please load test data first" # 找出未完成的环境 all_env_indices = set(range(total_envs)) incomplete_envs = sorted(all_env_indices - completed_envs) # 构建摘要信息 summary_lines = [] summary_lines.append(f"📊 Repo Task - Progress Summary for User {user_id}") summary_lines.append(f"Total environments: {total_envs}") summary_lines.append(f"Completed: {len(completed_envs)}/{total_envs}") summary_lines.append(f"Incomplete: {len(incomplete_envs)}/{total_envs}") if incomplete_envs: summary_lines.append("\n❌ Incomplete environments:") # 每行显示5个环境索引 for i in range(0, len(incomplete_envs), 5): env_display_list = [str(env_idx + 1) for env_idx in incomplete_envs[i:i+5]] summary_lines.append(" " + ", ".join(env_display_list)) else: summary_lines.append("\n🎉 Congratulations! All environments are completed!") return "\n".join(summary_lines) def create_repo_interface(current_dir: str, save_dir: str, user_id_input: gr.Textbox) -> Tuple[gr.Row, gr.Number, gr.Button, gr.Button, gr.Textbox, gr.Textbox, gr.Textbox, gr.Textbox, gr.Textbox, gr.Textbox]: """创建 Repo 任务界面组件 Returns: (repo_interface, repo_env_idx_input, repo_init_btn, repo_reset_btn, repo_env_info, repo_state_display, repo_steps_info_text, repo_action_input, repo_step_btn, repo_feedback_display, repo_history_display) 注意:环境控制组件(repo_env_idx_input, repo_init_btn, repo_reset_btn, repo_env_info) 需要在主界面中手动添加到进度摘要下方,不包含在 repo_interface 中。 为了保持函数签名一致,这里返回 None 作为占位符,主界面会忽略这些返回值。 """ # 创建主界面 Row(不包含环境控制) with gr.Row(visible=False) as repo_interface: # 左侧:步数信息和操作历史及环境反馈 with gr.Column(scale=1): repo_steps_info_text = gr.Textbox( label="Steps Info", value="0 / 120", interactive=False, visible=True, lines=2 ) gr.Markdown("### 📜 Action History & Environment Feedback") repo_history_display = gr.Textbox( label="Action History & Environment Feedback", interactive=False, lines=10 ) # Right side: Command input with gr.Column(scale=1): # Temporarily hide environment state display gr.Markdown("### 📦 Current State", visible=False) repo_state_display = gr.Textbox( label="Environment State", interactive=False, lines=10, value="Please load environment first", visible=False # Hide environment state display ) gr.Markdown("### 🎯 Command Input") repo_action_input = gr.Textbox( label="Input Command", placeholder="e.g., pip install python==3.10", info="Supported: pip install/uninstall, python run.py, pip list, repo tree" ) repo_step_btn = gr.Button("Execute Command", variant="primary") gr.Markdown("### 💬 Environment Feedback") repo_feedback_display = gr.Textbox( label="Feedback Info", interactive=False, lines=5, visible=True ) # 返回占位符(主界面会使用自己创建的环境控制组件) return (repo_interface, None, None, None, None, repo_state_display, repo_steps_info_text, repo_action_input, repo_step_btn, repo_feedback_display, repo_history_display)