OdysseyArena / GUI_Light_Task.py
beatccjiang's picture
Upload project files to Hugging Face Spaces
5af1af7 verified
# ==================== Light 任务模块 ====================
"""
Light 任务相关的所有函数和界面组件
支持多用户并发:使用 gr.State 管理每个用户会话的状态
使用统一进度管理模块存储数据
"""
import json
import os
from typing import List, Tuple, Optional, Dict, Any
import gradio as gr
# 导入统一进度管理模块
import progress_manager
# 导入 Light 环境
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
lightenv_path = os.path.join(current_dir, "LightEnv")
if os.path.exists(lightenv_path):
sys.path.insert(0, lightenv_path)
from TextEnv_v2 import LightBulbEnv
# ------------------- 常量 -------------------
LIGHT_MAX_STEPS = 200
# ------------------- Example Text -------------------
LIGHT_EXAMPLE_TEXT = """
## 📖 Light Bulb Environment Usage Example
### Example Scenario
Assume there are 3 light bulbs (indices 0, 1, 2), all initially turned off (○).
### Example Logic (Only shown in examples. In actual tasks, these rules are hidden and need to be inferred by users)
- B0: True # B0 can be turned on under any circumstances
- B1: B0 # B1 can only be turned on when B0 is on
- B2: not B1 and B0 # B2 can only be turned on when B1 is off and B0 is on
### Example Steps
1. **Step 1**: Input action `1`, click "Execute Action"
- Environment state after execution: ○ ○ ○
- Environment feedback: B1 remains inactive... remaining bulbs should be in specific mode.
- Reason: B1 can only be turned on when B0 is on, but B0 is off, so B1 cannot be turned on.
2. **Step 2**: Input action `0`, click "Execute Action"
- Environment state after execution: 💡 ○ ○
- Environment feedback: Toggled B1 to True
- Reason: B0 can be turned on at any time.
3. **Step 3**: Input action `2`, click "Execute Action"
- Environment state after execution: 💡 ○ 💡
- Environment feedback: Toggled B2 to True
- Reason: B2 can only be turned on when B1 is off and B0 is on, so B2 is turned on.
4. **Step 4**: Input action `1`, click "Execute Action"
- Environment state after execution: 💡 💡 💡 (Task completed)
- Environment feedback: Toggled B1 to True
- Reason: B1 can only be turned on when B0 is on, so B1 is turned on.
### Tips
- 💡 indicates the bulb is lit
- ○ indicates the bulb is not lit
- The availability of each bulb may depend on the state of other bulbs
- You need to discover hidden rules through experimentation
- Maximum 200 steps allowed
### Goal
Turn on all bulbs (all bulbs display as 💡)
"""
# ------------------- 状态管理 -------------------
def create_light_state() -> Dict[str, Any]:
"""创建初始的 Light 任务状态(每个用户会话独立)"""
return {
'env': None, # LightBulbEnv 实例
'test_data': [], # 测试数据
'current_env_idx': 0, # 当前环境索引
'history_records': [], # 操作历史记录
}
# ------------------- 工具函数 -------------------
def format_bulb_state(obs: List[bool]) -> str:
"""格式化灯泡状态显示(不显示数字)"""
state_parts = []
for b in obs:
bulb = "💡" if b else "○"
state_parts.append(bulb)
return " ".join(state_parts)
def load_light_test_data(state: Dict[str, Any], current_dir: str) -> Tuple[Dict[str, Any], str]:
"""加载 Light 测试数据"""
test_file = os.path.join(
current_dir, "test_data/turnonlights/test_turnonlights_lite_251030.json")
if not os.path.exists(test_file):
test_file = "test_data/turnonlights/test_turnonlights_lite_251030.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 light_save_progress_internal(state: Dict[str, Any], current_user_id: str, save_dir: str) -> str:
"""保存 Light 环境进度(使用统一进度管理模块)"""
if not current_user_id:
return "⚠️ Please enter user ID first"
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', [])
test_data = state.get('test_data', [])
env_progress = {
"user_id": current_user_id,
"env_idx": current_env_idx,
"env_idx_display": current_env_idx + 1,
"bulb_states": obs,
"history": history_records,
"num_steps": env.steps,
"level": env.num_bulbs,
"custom_logic": test_data[current_env_idx].get("custom_logic", {}) if current_env_idx < len(test_data) else {}
}
result = progress_manager.save_task_environment_progress(
current_user_id, save_dir, "light", 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 light_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]:
"""加载 Light 环境(使用统一进度管理模块)
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 / 200"
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 / 200"
# 使用统一进度管理模块检查是否有保存的进度
saved_progress_data = progress_manager.get_task_environment_progress(
current_user_id, save_dir, "light", env_idx
)
# 如果有保存的进度,加载它
if saved_progress_data:
state['current_env_idx'] = env_idx
bulb_states = saved_progress_data.get("bulb_states", [])
state['history_records'] = saved_progress_data.get("history", [])
level = saved_progress_data.get("level", 0)
num_steps = saved_progress_data.get("num_steps", len(state['history_records']))
custom_logic = saved_progress_data.get("custom_logic", {})
if not custom_logic and env_idx < len(test_data):
custom_logic = test_data[env_idx].get("custom_logic", {})
if env_idx < len(test_data) and level > 0:
state['env'] = LightBulbEnv(custom_logic=custom_logic, num_bulbs=level)
state['env'].steps = num_steps
for i, bulb_state in enumerate(bulb_states):
if i < state['env'].num_bulbs:
bulb_name = f"B{i}"
if bulb_name in state['env'].bulbs:
state['env'].bulbs[bulb_name] = bulb_state
obs = state['env']._get_obs()
state_display = format_bulb_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"Number of bulbs: {level}\n"
info += f"Steps: {len(state['history_records'])}"
current_steps = state['env'].steps
steps_info = f"{current_steps} / {LIGHT_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
d = test_data[env_idx]
state['env'] = LightBulbEnv(custom_logic=d["custom_logic"], num_bulbs=d["level"])
state['history_records'] = []
light_save_progress_internal(state, current_user_id, save_dir)
obs = state['env']._get_obs()
state_display = format_bulb_state(obs)
history_display = "Environment initialized (new environment)\n"
info = f"✅ Environment {env_idx_display}/{len(test_data)} initialized (new environment)\n"
info += f"Number of bulbs: {d['level']}\n"
info += f"Initial state: {state_display.split(chr(10))[0]}"
current_steps = state['env'].steps
steps_info = f"{current_steps} / {LIGHT_MAX_STEPS}"
return state, info, state_display, "", history_display, "Click 'View Unfinished Problems' button to view progress", steps_info
def light_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]:
"""执行 Light 环境一步动作
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_bulb_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 / 200"
# Auto-generate user ID if not provided
if not current_user_id:
import uuid
current_user_id = f"user_{uuid.uuid4().hex[:8]}"
# 解析动作
action = None
action_error = None
try:
action = int(action_str.strip())
if action < 0 or action >= env.num_bulbs:
action_error = f"Action out of range (0-{env.num_bulbs-1})"
except ValueError:
action_error = f"Invalid action format: {action_str}"
# 检查是否已经达到步骤上限
if env.steps >= LIGHT_MAX_STEPS:
history_display = "\n\n".join(history_records) if history_records else ""
light_save_progress_internal(state, current_user_id, save_dir)
feedback_info = f"⚠️ Reached step limit ({LIGHT_MAX_STEPS} steps)\n"
feedback_info += "Task ended (failed to complete within the specified number of steps)\n"
feedback_info += "Cannot continue executing actions\n"
current_steps = env.steps
steps_info = f"{current_steps} / {LIGHT_MAX_STEPS}"
return state, feedback_info, current_state_display, history_display, True, steps_info
# 如果动作无效
if action_error:
# 获取当前状态(无效动作后状态不变)
obs_current = env._get_obs()
state_current_str = format_bulb_state(obs_current)
step_num = len(history_records) + 1
history_record = f"Step {step_num}:\n"
history_record += f"State: {state_current_str}\n"
history_record += f"Action: {action_str} (invalid)\n"
history_record += f"Feedback: ❌ {action_error}"
history_records.append(history_record)
state['history_records'] = history_records
history_display = "\n\n".join(history_records)
env.steps += 1
if env.steps >= LIGHT_MAX_STEPS:
history_records.append(
f"Step {len(history_records) + 1}: Reached step limit ({LIGHT_MAX_STEPS} steps), task ended")
state['history_records'] = history_records
history_display = "\n\n".join(history_records)
light_save_progress_internal(state, current_user_id, save_dir)
feedback_info = f"Action: {action_str}\nFeedback: ❌ {action_error}\n"
feedback_info += f"⚠️ Reached step limit ({LIGHT_MAX_STEPS} steps)\n"
feedback_info += "Task ended (failed to complete within the specified number of steps)\n"
current_steps = env.steps
steps_info = f"{current_steps} / {LIGHT_MAX_STEPS}"
return state, feedback_info, current_state_display, history_display, True, steps_info
light_save_progress_internal(state, current_user_id, save_dir)
feedback_info = f"Action: {action_str}\nFeedback: ❌ {action_error}\n"
current_steps = env.steps
steps_info = f"{current_steps} / {LIGHT_MAX_STEPS}"
return state, feedback_info, current_state_display, history_display, False, steps_info
# 执行有效动作
# 在执行动作前获取当前状态
obs_before = env._get_obs()
state_before_str = format_bulb_state(obs_before)
# 执行动作
obs, feedback, done, _ = env.step(action)
state_display = format_bulb_state(obs)
# 记录历史,包含执行后的状态(显示亮的灯泡)
step_num = len(history_records) + 1
state_after_str = format_bulb_state(obs)
history_record = f"Step {step_num}:\n"
history_record += f"State after execution:\n{state_after_str}\n"
history_record += f"Action: {action}\n"
history_record += f"Feedback: {feedback}"
history_records.append(history_record)
state['history_records'] = history_records
history_display = "\n\n".join(history_records)
if env.steps >= LIGHT_MAX_STEPS:
done = True
if not all(obs):
feedback = f"{feedback}\n⚠️ Reached step limit ({LIGHT_MAX_STEPS} steps), task ended (failed to complete within the specified number of steps)"
light_save_progress_internal(state, current_user_id, save_dir)
feedback_info = f"Action: {action}\nFeedback: {feedback}\n"
if done:
if all(obs):
feedback_info += "🎉 Task completed! All bulbs are lit!\n"
else:
feedback_info += f"⚠️ Task ended (reached step limit {LIGHT_MAX_STEPS} steps)\n"
current_steps = env.steps
steps_info = f"{current_steps} / {LIGHT_MAX_STEPS}"
return state, feedback_info, state_display, history_display, done, steps_info
def light_reset_environment(state: Dict[str, Any], current_user_id: str, save_dir: str) -> Tuple[Dict[str, Any], str, str, str, str, str]:
"""重置 Light 环境
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 / 200"
env.reset()
state['history_records'] = []
light_save_progress_internal(state, current_user_id, save_dir)
obs = env._get_obs()
state_display = format_bulb_state(obs)
history_display = "Environment reset\n"
current_steps = env.steps
steps_info = f"{current_steps} / {LIGHT_MAX_STEPS}"
return state, "✅ Environment reset", state_display, history_display, "Click 'View Unfinished Problems' button to view progress", steps_info
def get_light_current_env_idx(state: Dict[str, Any]) -> int:
"""获取当前 Light 环境索引"""
return state.get('current_env_idx', 0)
def get_light_test_data(state: Dict[str, Any]) -> List[dict]:
"""获取 Light 测试数据"""
return state.get('test_data', [])
def get_light_history_records(state: Dict[str, Any]) -> List[str]:
"""获取 Light 历史记录"""
return state.get('history_records', [])
def get_light_progress_summary(state: Dict[str, Any], user_id: str, save_dir: str) -> str:
"""获取 Light 任务用户进度摘要(使用统一进度管理模块)
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, "light")
environments = task_data.get("environments", {})
completed_envs = set()
for env_key, progress_data in environments.items():
env_idx = progress_data.get("env_idx", -1)
bulb_states = progress_data.get("bulb_states", [])
num_steps = progress_data.get("num_steps", 0)
# 检查是否完成
is_completed = False
if bulb_states and all(bulb_states):
is_completed = True
elif num_steps >= LIGHT_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"📊 Light 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_light_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]:
"""创建 Light 任务界面组件
Returns: (light_interface, light_env_idx_input, light_init_btn, light_reset_btn,
light_env_info, light_state_display, light_steps_info_text,
light_action_input, light_step_btn, light_feedback_display, light_history_display)
注意:环境控制组件(light_env_idx_input, light_init_btn, light_reset_btn, light_env_info)
需要在主界面中手动添加到进度摘要下方,不包含在 light_interface 中。
为了保持函数签名一致,这里返回 None 作为占位符,主界面会忽略这些返回值。
"""
# 创建主界面 Row(不包含环境控制)
with gr.Row(visible=True) as light_interface:
with gr.Column(scale=1):
light_steps_info_text = gr.Textbox(
label="Steps Info",
value="0 / 200",
interactive=False,
visible=True,
lines=2
)
gr.Markdown("### 📜 Action History")
light_history_display = gr.Textbox(
label="Action History",
interactive=False,
lines=10
)
with gr.Column(scale=2):
gr.Markdown("### 💡 Current State")
light_state_display = gr.Textbox(
label="Light Bulb State",
interactive=False,
lines=3,
value="Please load environment first"
)
gr.Markdown("### 🎯 Action Input")
light_action_input = gr.Textbox(
label="Input Action (Bulb Index)",
placeholder="e.g., 0",
info="Input the bulb index to toggle (starting from 0)"
)
light_step_btn = gr.Button("Execute Action", variant="primary")
gr.Markdown("### 💬 Environment Feedback")
light_feedback_display = gr.Textbox(
label="Feedback Info",
interactive=False,
lines=5
)
# 返回占位符(主界面会使用自己创建的环境控制组件)
return (light_interface, None, None, None,
None, light_state_display, light_steps_info_text,
light_action_input, light_step_btn, light_feedback_display, light_history_display)