fix video display v1
Browse files- gradio-web/config.py +1 -1
- gradio-web/test/test_ui_phase_machine_runtime_e2e.py +189 -0
- gradio-web/ui_layout.py +130 -102
gradio-web/config.py
CHANGED
|
@@ -40,7 +40,7 @@ DEMO_VIDEO_ENV_IDS = [
|
|
| 40 |
|
| 41 |
UI_TEXT = {
|
| 42 |
"log": {
|
| 43 |
-
"action_selection_prompt": "please select the action in the
|
| 44 |
"demo_video_prompt": 'press "Watch Video Input🎬" to watch a video\nNote: you can only watch the video once',
|
| 45 |
"session_error": "Session Error",
|
| 46 |
"reference_action_error": "Ground Truth Action Error: {error}",
|
|
|
|
| 40 |
|
| 41 |
UI_TEXT = {
|
| 42 |
"log": {
|
| 43 |
+
"action_selection_prompt": "please select the action in the left 👈,\nsome actions also need to select keypoint",
|
| 44 |
"demo_video_prompt": 'press "Watch Video Input🎬" to watch a video\nNote: you can only watch the video once',
|
| 45 |
"session_error": "Session Error",
|
| 46 |
"reference_action_error": "Ground Truth Action Error: {error}",
|
gradio-web/test/test_ui_phase_machine_runtime_e2e.py
CHANGED
|
@@ -100,6 +100,29 @@ def _read_coords_box_value(page) -> str | None:
|
|
| 100 |
)
|
| 101 |
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
def _read_live_obs_geometry(page) -> dict[str, dict[str, float] | None]:
|
| 104 |
return page.evaluate(
|
| 105 |
"""() => {
|
|
@@ -1051,6 +1074,172 @@ def test_header_task_env_normalization_and_fallback(monkeypatch, task_info_text,
|
|
| 1051 |
demo.close()
|
| 1052 |
|
| 1053 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1054 |
def test_phase_machine_runtime_local_video_path_end_transition():
|
| 1055 |
import gradio_callbacks as cb
|
| 1056 |
|
|
|
|
| 100 |
)
|
| 101 |
|
| 102 |
|
| 103 |
+
def _read_phase_visibility(page) -> dict[str, bool | str | None]:
|
| 104 |
+
return page.evaluate(
|
| 105 |
+
"""() => {
|
| 106 |
+
const visible = (id) => {
|
| 107 |
+
const el = document.getElementById(id);
|
| 108 |
+
if (!el) return false;
|
| 109 |
+
const st = getComputedStyle(el);
|
| 110 |
+
return st.display !== 'none' && st.visibility !== 'hidden' && el.getClientRects().length > 0;
|
| 111 |
+
};
|
| 112 |
+
const videoEl = document.querySelector('#demo_video video');
|
| 113 |
+
return {
|
| 114 |
+
videoPhase: visible('video_phase_group'),
|
| 115 |
+
video: visible('demo_video'),
|
| 116 |
+
actionPhase: visible('action_phase_group'),
|
| 117 |
+
action: visible('live_obs'),
|
| 118 |
+
controlPhase: visible('control_panel_group'),
|
| 119 |
+
control: visible('action_radio'),
|
| 120 |
+
currentSrc: videoEl ? videoEl.currentSrc : null,
|
| 121 |
+
};
|
| 122 |
+
}"""
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
def _read_live_obs_geometry(page) -> dict[str, dict[str, float] | None]:
|
| 127 |
return page.evaluate(
|
| 128 |
"""() => {
|
|
|
|
| 1074 |
demo.close()
|
| 1075 |
|
| 1076 |
|
| 1077 |
+
def test_header_task_switch_to_video_task_shows_demo_phase(monkeypatch):
|
| 1078 |
+
ui_layout = importlib.reload(importlib.import_module("ui_layout"))
|
| 1079 |
+
|
| 1080 |
+
fake_obs = np.zeros((24, 24, 3), dtype=np.uint8)
|
| 1081 |
+
fake_obs_img = Image.fromarray(fake_obs)
|
| 1082 |
+
demo_video_path = gr.get_video("world.mp4")
|
| 1083 |
+
switch_calls = []
|
| 1084 |
+
|
| 1085 |
+
def fake_init_app(request=None):
|
| 1086 |
+
_ = request
|
| 1087 |
+
return (
|
| 1088 |
+
"uid-header-video",
|
| 1089 |
+
gr.update(visible=True), # main_interface
|
| 1090 |
+
gr.update(value=fake_obs_img, interactive=False), # img_display
|
| 1091 |
+
"ready", # log_output
|
| 1092 |
+
gr.update(choices=[("pick", 0)], value=None), # options_radio
|
| 1093 |
+
"goal", # goal_box
|
| 1094 |
+
"No need for coordinates", # coords_box
|
| 1095 |
+
gr.update(value=None, visible=False), # video_display
|
| 1096 |
+
"PickXtimes (Episode 1)", # task_info_box
|
| 1097 |
+
"Completed: 0", # progress_info_box
|
| 1098 |
+
gr.update(interactive=True), # restart_episode_btn
|
| 1099 |
+
gr.update(interactive=True), # next_task_btn
|
| 1100 |
+
gr.update(interactive=True), # exec_btn
|
| 1101 |
+
gr.update(visible=False), # video_phase_group
|
| 1102 |
+
gr.update(visible=True), # action_phase_group
|
| 1103 |
+
gr.update(visible=True), # control_panel_group
|
| 1104 |
+
gr.update(value="hint"), # task_hint_display
|
| 1105 |
+
gr.update(visible=False), # loading_overlay
|
| 1106 |
+
gr.update(interactive=True), # reference_action_btn
|
| 1107 |
+
)
|
| 1108 |
+
|
| 1109 |
+
def fake_switch_env_wrapper(uid, selected_env):
|
| 1110 |
+
switch_calls.append((uid, selected_env))
|
| 1111 |
+
return (
|
| 1112 |
+
uid,
|
| 1113 |
+
gr.update(visible=True), # main_interface
|
| 1114 |
+
gr.update(value=fake_obs_img, interactive=False), # img_display
|
| 1115 |
+
"demo prompt", # log_output
|
| 1116 |
+
gr.update(choices=[("pick", 0)], value=None), # options_radio
|
| 1117 |
+
"video goal", # goal_box
|
| 1118 |
+
"No need for coordinates", # coords_box
|
| 1119 |
+
gr.update(value=demo_video_path, visible=True), # video_display
|
| 1120 |
+
"VideoPlaceButton (Episode 1)", # task_info_box
|
| 1121 |
+
"Completed: 0", # progress_info_box
|
| 1122 |
+
gr.update(interactive=True), # restart_episode_btn
|
| 1123 |
+
gr.update(interactive=True), # next_task_btn
|
| 1124 |
+
gr.update(interactive=True), # exec_btn
|
| 1125 |
+
gr.update(visible=True), # video_phase_group
|
| 1126 |
+
gr.update(visible=False), # action_phase_group
|
| 1127 |
+
gr.update(visible=False), # control_panel_group
|
| 1128 |
+
gr.update(value="video hint"), # task_hint_display
|
| 1129 |
+
gr.update(visible=False), # loading_overlay
|
| 1130 |
+
gr.update(interactive=True), # reference_action_btn
|
| 1131 |
+
)
|
| 1132 |
+
|
| 1133 |
+
monkeypatch.setattr(ui_layout, "init_app", fake_init_app)
|
| 1134 |
+
monkeypatch.setattr(ui_layout, "switch_env_wrapper", fake_switch_env_wrapper)
|
| 1135 |
+
monkeypatch.setattr(ui_layout.user_manager, "env_choices", ["PickXtimes", "VideoPlaceButton"])
|
| 1136 |
+
|
| 1137 |
+
demo = ui_layout.create_ui_blocks()
|
| 1138 |
+
|
| 1139 |
+
port = _free_port()
|
| 1140 |
+
host = "127.0.0.1"
|
| 1141 |
+
root_url = f"http://{host}:{port}/"
|
| 1142 |
+
|
| 1143 |
+
app = FastAPI(title="header-task-switch-video-phase-test")
|
| 1144 |
+
app = gr.mount_gradio_app(app, demo, path="/")
|
| 1145 |
+
|
| 1146 |
+
config = uvicorn.Config(app, host=host, port=port, log_level="error")
|
| 1147 |
+
server = uvicorn.Server(config)
|
| 1148 |
+
thread = threading.Thread(target=server.run, daemon=True)
|
| 1149 |
+
thread.start()
|
| 1150 |
+
_wait_http_ready(root_url)
|
| 1151 |
+
|
| 1152 |
+
try:
|
| 1153 |
+
with sync_playwright() as p:
|
| 1154 |
+
browser = p.chromium.launch(headless=True)
|
| 1155 |
+
page = browser.new_page(viewport={"width": 1280, "height": 900})
|
| 1156 |
+
page.goto(root_url, wait_until="domcontentloaded")
|
| 1157 |
+
page.wait_for_selector("#main_interface_root", state="visible", timeout=15000)
|
| 1158 |
+
page.wait_for_function(
|
| 1159 |
+
"""() => {
|
| 1160 |
+
const root = document.getElementById('header_task');
|
| 1161 |
+
const input = root ? root.querySelector('input') : null;
|
| 1162 |
+
return !!input && input.value.trim() === 'PickXtimes';
|
| 1163 |
+
}""",
|
| 1164 |
+
timeout=5000,
|
| 1165 |
+
)
|
| 1166 |
+
|
| 1167 |
+
page.click("#header_task input")
|
| 1168 |
+
page.get_by_role("option", name="VideoPlaceButton").click()
|
| 1169 |
+
|
| 1170 |
+
page.wait_for_function(
|
| 1171 |
+
"""() => {
|
| 1172 |
+
const visible = (id) => {
|
| 1173 |
+
const el = document.getElementById(id);
|
| 1174 |
+
if (!el) return false;
|
| 1175 |
+
const st = getComputedStyle(el);
|
| 1176 |
+
return st.display !== 'none' && st.visibility !== 'hidden' && el.getClientRects().length > 0;
|
| 1177 |
+
};
|
| 1178 |
+
const videoEl = document.querySelector('#demo_video video');
|
| 1179 |
+
return (
|
| 1180 |
+
visible('video_phase_group') &&
|
| 1181 |
+
visible('demo_video') &&
|
| 1182 |
+
!visible('action_phase_group') &&
|
| 1183 |
+
!visible('control_panel_group') &&
|
| 1184 |
+
!!(videoEl && videoEl.currentSrc)
|
| 1185 |
+
);
|
| 1186 |
+
}""",
|
| 1187 |
+
timeout=10000,
|
| 1188 |
+
)
|
| 1189 |
+
|
| 1190 |
+
phase_after_switch = _read_phase_visibility(page)
|
| 1191 |
+
assert phase_after_switch["videoPhase"] is True
|
| 1192 |
+
assert phase_after_switch["video"] is True
|
| 1193 |
+
assert phase_after_switch["actionPhase"] is False
|
| 1194 |
+
assert phase_after_switch["controlPhase"] is False
|
| 1195 |
+
assert phase_after_switch["currentSrc"]
|
| 1196 |
+
assert switch_calls == [("uid-header-video", "VideoPlaceButton")]
|
| 1197 |
+
|
| 1198 |
+
did_dispatch_end = page.evaluate(
|
| 1199 |
+
"""() => {
|
| 1200 |
+
const videoEl = document.querySelector('#demo_video video');
|
| 1201 |
+
if (!videoEl) return false;
|
| 1202 |
+
videoEl.dispatchEvent(new Event('ended', { bubbles: true }));
|
| 1203 |
+
return true;
|
| 1204 |
+
}"""
|
| 1205 |
+
)
|
| 1206 |
+
assert did_dispatch_end
|
| 1207 |
+
|
| 1208 |
+
page.wait_for_function(
|
| 1209 |
+
"""() => {
|
| 1210 |
+
const visible = (id) => {
|
| 1211 |
+
const el = document.getElementById(id);
|
| 1212 |
+
if (!el) return false;
|
| 1213 |
+
const st = getComputedStyle(el);
|
| 1214 |
+
return st.display !== 'none' && st.visibility !== 'hidden' && el.getClientRects().length > 0;
|
| 1215 |
+
};
|
| 1216 |
+
return (
|
| 1217 |
+
!visible('video_phase_group') &&
|
| 1218 |
+
!visible('demo_video') &&
|
| 1219 |
+
visible('action_phase_group') &&
|
| 1220 |
+
visible('control_panel_group') &&
|
| 1221 |
+
visible('live_obs') &&
|
| 1222 |
+
visible('action_radio')
|
| 1223 |
+
);
|
| 1224 |
+
}""",
|
| 1225 |
+
timeout=5000,
|
| 1226 |
+
)
|
| 1227 |
+
|
| 1228 |
+
phase_after_end = _read_phase_visibility(page)
|
| 1229 |
+
assert phase_after_end["videoPhase"] is False
|
| 1230 |
+
assert phase_after_end["video"] is False
|
| 1231 |
+
assert phase_after_end["actionPhase"] is True
|
| 1232 |
+
assert phase_after_end["action"] is True
|
| 1233 |
+
assert phase_after_end["controlPhase"] is True
|
| 1234 |
+
assert phase_after_end["control"] is True
|
| 1235 |
+
|
| 1236 |
+
browser.close()
|
| 1237 |
+
finally:
|
| 1238 |
+
server.should_exit = True
|
| 1239 |
+
thread.join(timeout=10)
|
| 1240 |
+
demo.close()
|
| 1241 |
+
|
| 1242 |
+
|
| 1243 |
def test_phase_machine_runtime_local_video_path_end_transition():
|
| 1244 |
import gradio_callbacks as cb
|
| 1245 |
|
gradio-web/ui_layout.py
CHANGED
|
@@ -290,6 +290,30 @@ def _with_phase_from_load(load_result):
|
|
| 290 |
return (*load_result, phase)
|
| 291 |
|
| 292 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
def create_ui_blocks():
|
| 294 |
"""构建 Gradio Blocks,并完成页面阶段状态(phase)的联动绑定。"""
|
| 295 |
|
|
@@ -337,6 +361,8 @@ def create_ui_blocks():
|
|
| 337 |
|
| 338 |
uid_state = gr.State(value=None)
|
| 339 |
ui_phase_state = gr.State(value=PHASE_INIT)
|
|
|
|
|
|
|
| 340 |
live_obs_timer = gr.Timer(value=1.0 / LIVE_OBS_REFRESH_HZ, active=True)
|
| 341 |
|
| 342 |
task_info_box = gr.Textbox(visible=False, elem_id="task_info_box")
|
|
@@ -450,6 +476,34 @@ def create_ui_blocks():
|
|
| 450 |
elem_id="task_hint_display",
|
| 451 |
)
|
| 452 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
def _normalize_env_choice(env_value, choices):
|
| 454 |
if env_value is None:
|
| 455 |
return None
|
|
@@ -463,7 +517,7 @@ def create_ui_blocks():
|
|
| 463 |
lower_map.setdefault(choice_text.lower(), choice_text)
|
| 464 |
return lower_map.get(env_text.lower(), env_text)
|
| 465 |
|
| 466 |
-
def
|
| 467 |
base_choices = list(user_manager.env_choices)
|
| 468 |
parsed_env = render_header_task(task_text)
|
| 469 |
selected_env = _normalize_env_choice(parsed_env, base_choices)
|
|
@@ -473,13 +527,23 @@ def create_ui_blocks():
|
|
| 473 |
choices = list(base_choices)
|
| 474 |
if selected_env and selected_env not in choices:
|
| 475 |
choices.append(selected_env)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
return gr.update(choices=choices, value=selected_env)
|
| 477 |
|
| 478 |
def sync_header_from_task(task_text, goal_text):
|
| 479 |
-
|
|
|
|
| 480 |
|
| 481 |
def sync_header_from_goal(goal_text, task_text, current_header_task):
|
| 482 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
|
| 484 |
def init_app_with_phase(request: gr.Request):
|
| 485 |
return _with_phase_from_load(init_app(request))
|
|
@@ -493,108 +557,87 @@ def create_ui_blocks():
|
|
| 493 |
def switch_env_with_phase(uid, selected_env):
|
| 494 |
return _with_phase_from_load(switch_env_wrapper(uid, selected_env))
|
| 495 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 496 |
task_info_box.change(
|
| 497 |
fn=sync_header_from_task,
|
| 498 |
inputs=[task_info_box, goal_box],
|
| 499 |
-
outputs=[header_task_box, header_goal_box],
|
| 500 |
)
|
| 501 |
goal_box.change(
|
| 502 |
fn=sync_header_from_goal,
|
| 503 |
inputs=[goal_box, task_info_box, header_task_box],
|
| 504 |
-
outputs=[header_task_box, header_goal_box],
|
| 505 |
)
|
| 506 |
|
| 507 |
-
header_task_box.
|
| 508 |
-
fn=
|
| 509 |
-
inputs=[
|
| 510 |
-
outputs=[
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
img_display,
|
| 514 |
-
log_output,
|
| 515 |
-
options_radio,
|
| 516 |
-
goal_box,
|
| 517 |
-
coords_box,
|
| 518 |
-
video_display,
|
| 519 |
-
task_info_box,
|
| 520 |
-
progress_info_box,
|
| 521 |
-
restart_episode_btn,
|
| 522 |
-
next_task_btn,
|
| 523 |
-
exec_btn,
|
| 524 |
-
video_phase_group,
|
| 525 |
-
action_phase_group,
|
| 526 |
-
control_panel_group,
|
| 527 |
-
task_hint_display,
|
| 528 |
-
loading_overlay,
|
| 529 |
-
reference_action_btn,
|
| 530 |
-
ui_phase_state,
|
| 531 |
-
],
|
| 532 |
).then(
|
| 533 |
-
fn=
|
| 534 |
-
inputs=[
|
| 535 |
-
outputs=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
)
|
| 537 |
|
| 538 |
next_task_btn.click(fn=show_loading_info, outputs=[loading_overlay]).then(
|
| 539 |
fn=load_next_task_with_phase,
|
| 540 |
inputs=[uid_state],
|
| 541 |
-
outputs=
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
coords_box,
|
| 549 |
-
video_display,
|
| 550 |
-
task_info_box,
|
| 551 |
-
progress_info_box,
|
| 552 |
-
restart_episode_btn,
|
| 553 |
-
next_task_btn,
|
| 554 |
-
exec_btn,
|
| 555 |
-
video_phase_group,
|
| 556 |
-
action_phase_group,
|
| 557 |
-
control_panel_group,
|
| 558 |
-
task_hint_display,
|
| 559 |
-
loading_overlay,
|
| 560 |
-
reference_action_btn,
|
| 561 |
-
ui_phase_state,
|
| 562 |
-
],
|
| 563 |
).then(
|
| 564 |
fn=sync_header_from_task,
|
| 565 |
inputs=[task_info_box, goal_box],
|
| 566 |
-
outputs=[header_task_box, header_goal_box],
|
| 567 |
)
|
| 568 |
|
| 569 |
restart_episode_btn.click(fn=show_loading_info, outputs=[loading_overlay]).then(
|
| 570 |
fn=restart_episode_with_phase,
|
| 571 |
inputs=[uid_state],
|
| 572 |
-
outputs=
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
coords_box,
|
| 580 |
-
video_display,
|
| 581 |
-
task_info_box,
|
| 582 |
-
progress_info_box,
|
| 583 |
-
restart_episode_btn,
|
| 584 |
-
next_task_btn,
|
| 585 |
-
exec_btn,
|
| 586 |
-
video_phase_group,
|
| 587 |
-
action_phase_group,
|
| 588 |
-
control_panel_group,
|
| 589 |
-
task_hint_display,
|
| 590 |
-
loading_overlay,
|
| 591 |
-
reference_action_btn,
|
| 592 |
-
ui_phase_state,
|
| 593 |
-
],
|
| 594 |
).then(
|
| 595 |
fn=sync_header_from_task,
|
| 596 |
inputs=[task_info_box, goal_box],
|
| 597 |
-
outputs=[header_task_box, header_goal_box],
|
| 598 |
)
|
| 599 |
|
| 600 |
video_display.end(
|
|
@@ -701,32 +744,17 @@ def create_ui_blocks():
|
|
| 701 |
demo.load(
|
| 702 |
fn=init_app_with_phase,
|
| 703 |
inputs=[],
|
| 704 |
-
outputs=
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
coords_box,
|
| 712 |
-
video_display,
|
| 713 |
-
task_info_box,
|
| 714 |
-
progress_info_box,
|
| 715 |
-
restart_episode_btn,
|
| 716 |
-
next_task_btn,
|
| 717 |
-
exec_btn,
|
| 718 |
-
video_phase_group,
|
| 719 |
-
action_phase_group,
|
| 720 |
-
control_panel_group,
|
| 721 |
-
task_hint_display,
|
| 722 |
-
loading_overlay,
|
| 723 |
-
reference_action_btn,
|
| 724 |
-
ui_phase_state,
|
| 725 |
-
],
|
| 726 |
).then(
|
| 727 |
fn=sync_header_from_task,
|
| 728 |
inputs=[task_info_box, goal_box],
|
| 729 |
-
outputs=[header_task_box, header_goal_box],
|
| 730 |
)
|
| 731 |
|
| 732 |
return demo
|
|
|
|
| 290 |
return (*load_result, phase)
|
| 291 |
|
| 292 |
|
| 293 |
+
def _skip_load_flow():
|
| 294 |
+
return tuple(gr.skip() for _ in range(20))
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def _phase_visibility_updates(phase):
|
| 298 |
+
if phase == PHASE_DEMO_VIDEO:
|
| 299 |
+
return (
|
| 300 |
+
gr.update(visible=True),
|
| 301 |
+
gr.update(visible=False),
|
| 302 |
+
gr.update(visible=False),
|
| 303 |
+
)
|
| 304 |
+
if phase in {PHASE_ACTION_KEYPOINT, PHASE_EXECUTION_PLAYBACK}:
|
| 305 |
+
return (
|
| 306 |
+
gr.update(visible=False),
|
| 307 |
+
gr.update(visible=True),
|
| 308 |
+
gr.update(visible=True),
|
| 309 |
+
)
|
| 310 |
+
return (
|
| 311 |
+
gr.update(visible=False),
|
| 312 |
+
gr.update(visible=False),
|
| 313 |
+
gr.update(visible=False),
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
|
| 317 |
def create_ui_blocks():
|
| 318 |
"""构建 Gradio Blocks,并完成页面阶段状态(phase)的联动绑定。"""
|
| 319 |
|
|
|
|
| 361 |
|
| 362 |
uid_state = gr.State(value=None)
|
| 363 |
ui_phase_state = gr.State(value=PHASE_INIT)
|
| 364 |
+
pending_header_task_state = gr.State(value=None)
|
| 365 |
+
programmatic_header_task_state = gr.State(value=None)
|
| 366 |
live_obs_timer = gr.Timer(value=1.0 / LIVE_OBS_REFRESH_HZ, active=True)
|
| 367 |
|
| 368 |
task_info_box = gr.Textbox(visible=False, elem_id="task_info_box")
|
|
|
|
| 476 |
elem_id="task_hint_display",
|
| 477 |
)
|
| 478 |
|
| 479 |
+
load_flow_outputs = [
|
| 480 |
+
uid_state,
|
| 481 |
+
main_interface,
|
| 482 |
+
img_display,
|
| 483 |
+
log_output,
|
| 484 |
+
options_radio,
|
| 485 |
+
goal_box,
|
| 486 |
+
coords_box,
|
| 487 |
+
video_display,
|
| 488 |
+
task_info_box,
|
| 489 |
+
progress_info_box,
|
| 490 |
+
restart_episode_btn,
|
| 491 |
+
next_task_btn,
|
| 492 |
+
exec_btn,
|
| 493 |
+
video_phase_group,
|
| 494 |
+
action_phase_group,
|
| 495 |
+
control_panel_group,
|
| 496 |
+
task_hint_display,
|
| 497 |
+
loading_overlay,
|
| 498 |
+
reference_action_btn,
|
| 499 |
+
ui_phase_state,
|
| 500 |
+
]
|
| 501 |
+
phase_visibility_outputs = [
|
| 502 |
+
video_phase_group,
|
| 503 |
+
action_phase_group,
|
| 504 |
+
control_panel_group,
|
| 505 |
+
]
|
| 506 |
+
|
| 507 |
def _normalize_env_choice(env_value, choices):
|
| 508 |
if env_value is None:
|
| 509 |
return None
|
|
|
|
| 517 |
lower_map.setdefault(choice_text.lower(), choice_text)
|
| 518 |
return lower_map.get(env_text.lower(), env_text)
|
| 519 |
|
| 520 |
+
def _resolve_header_task_state(task_text, fallback_env=None):
|
| 521 |
base_choices = list(user_manager.env_choices)
|
| 522 |
parsed_env = render_header_task(task_text)
|
| 523 |
selected_env = _normalize_env_choice(parsed_env, base_choices)
|
|
|
|
| 527 |
choices = list(base_choices)
|
| 528 |
if selected_env and selected_env not in choices:
|
| 529 |
choices.append(selected_env)
|
| 530 |
+
return choices, selected_env
|
| 531 |
+
|
| 532 |
+
def _build_header_task_update(task_text, fallback_env=None):
|
| 533 |
+
choices, selected_env = _resolve_header_task_state(task_text, fallback_env=fallback_env)
|
| 534 |
return gr.update(choices=choices, value=selected_env)
|
| 535 |
|
| 536 |
def sync_header_from_task(task_text, goal_text):
|
| 537 |
+
_, selected_env = _resolve_header_task_state(task_text)
|
| 538 |
+
return _build_header_task_update(task_text), render_header_goal(goal_text), selected_env
|
| 539 |
|
| 540 |
def sync_header_from_goal(goal_text, task_text, current_header_task):
|
| 541 |
+
_, selected_env = _resolve_header_task_state(task_text, fallback_env=current_header_task)
|
| 542 |
+
return (
|
| 543 |
+
_build_header_task_update(task_text, fallback_env=current_header_task),
|
| 544 |
+
render_header_goal(goal_text),
|
| 545 |
+
selected_env,
|
| 546 |
+
)
|
| 547 |
|
| 548 |
def init_app_with_phase(request: gr.Request):
|
| 549 |
return _with_phase_from_load(init_app(request))
|
|
|
|
| 557 |
def switch_env_with_phase(uid, selected_env):
|
| 558 |
return _with_phase_from_load(switch_env_wrapper(uid, selected_env))
|
| 559 |
|
| 560 |
+
def maybe_switch_env_with_phase(uid, selected_env):
|
| 561 |
+
if not selected_env:
|
| 562 |
+
return _skip_load_flow()
|
| 563 |
+
return switch_env_with_phase(uid, selected_env)
|
| 564 |
+
|
| 565 |
+
def prepare_header_task_switch(selected_env, programmatic_selected_env):
|
| 566 |
+
base_choices = list(user_manager.env_choices)
|
| 567 |
+
normalized_selected_env = _normalize_env_choice(selected_env, base_choices)
|
| 568 |
+
normalized_programmatic_env = _normalize_env_choice(programmatic_selected_env, base_choices)
|
| 569 |
+
|
| 570 |
+
if not normalized_selected_env:
|
| 571 |
+
return None, None, gr.update(visible=False)
|
| 572 |
+
if normalized_selected_env == normalized_programmatic_env:
|
| 573 |
+
return None, None, gr.update(visible=False)
|
| 574 |
+
return normalized_selected_env, None, show_loading_info()
|
| 575 |
+
|
| 576 |
task_info_box.change(
|
| 577 |
fn=sync_header_from_task,
|
| 578 |
inputs=[task_info_box, goal_box],
|
| 579 |
+
outputs=[header_task_box, header_goal_box, programmatic_header_task_state],
|
| 580 |
)
|
| 581 |
goal_box.change(
|
| 582 |
fn=sync_header_from_goal,
|
| 583 |
inputs=[goal_box, task_info_box, header_task_box],
|
| 584 |
+
outputs=[header_task_box, header_goal_box, programmatic_header_task_state],
|
| 585 |
)
|
| 586 |
|
| 587 |
+
header_task_box.change(
|
| 588 |
+
fn=prepare_header_task_switch,
|
| 589 |
+
inputs=[header_task_box, programmatic_header_task_state],
|
| 590 |
+
outputs=[pending_header_task_state, programmatic_header_task_state, loading_overlay],
|
| 591 |
+
queue=False,
|
| 592 |
+
show_progress="hidden",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
).then(
|
| 594 |
+
fn=maybe_switch_env_with_phase,
|
| 595 |
+
inputs=[uid_state, pending_header_task_state],
|
| 596 |
+
outputs=load_flow_outputs,
|
| 597 |
+
).then(
|
| 598 |
+
fn=_phase_visibility_updates,
|
| 599 |
+
inputs=[ui_phase_state],
|
| 600 |
+
outputs=phase_visibility_outputs,
|
| 601 |
+
queue=False,
|
| 602 |
+
show_progress="hidden",
|
| 603 |
+
).then(
|
| 604 |
+
fn=lambda _selected_env: None,
|
| 605 |
+
inputs=[pending_header_task_state],
|
| 606 |
+
outputs=[pending_header_task_state],
|
| 607 |
+
queue=False,
|
| 608 |
+
show_progress="hidden",
|
| 609 |
)
|
| 610 |
|
| 611 |
next_task_btn.click(fn=show_loading_info, outputs=[loading_overlay]).then(
|
| 612 |
fn=load_next_task_with_phase,
|
| 613 |
inputs=[uid_state],
|
| 614 |
+
outputs=load_flow_outputs,
|
| 615 |
+
).then(
|
| 616 |
+
fn=_phase_visibility_updates,
|
| 617 |
+
inputs=[ui_phase_state],
|
| 618 |
+
outputs=phase_visibility_outputs,
|
| 619 |
+
queue=False,
|
| 620 |
+
show_progress="hidden",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
).then(
|
| 622 |
fn=sync_header_from_task,
|
| 623 |
inputs=[task_info_box, goal_box],
|
| 624 |
+
outputs=[header_task_box, header_goal_box, programmatic_header_task_state],
|
| 625 |
)
|
| 626 |
|
| 627 |
restart_episode_btn.click(fn=show_loading_info, outputs=[loading_overlay]).then(
|
| 628 |
fn=restart_episode_with_phase,
|
| 629 |
inputs=[uid_state],
|
| 630 |
+
outputs=load_flow_outputs,
|
| 631 |
+
).then(
|
| 632 |
+
fn=_phase_visibility_updates,
|
| 633 |
+
inputs=[ui_phase_state],
|
| 634 |
+
outputs=phase_visibility_outputs,
|
| 635 |
+
queue=False,
|
| 636 |
+
show_progress="hidden",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
).then(
|
| 638 |
fn=sync_header_from_task,
|
| 639 |
inputs=[task_info_box, goal_box],
|
| 640 |
+
outputs=[header_task_box, header_goal_box, programmatic_header_task_state],
|
| 641 |
)
|
| 642 |
|
| 643 |
video_display.end(
|
|
|
|
| 744 |
demo.load(
|
| 745 |
fn=init_app_with_phase,
|
| 746 |
inputs=[],
|
| 747 |
+
outputs=load_flow_outputs,
|
| 748 |
+
).then(
|
| 749 |
+
fn=_phase_visibility_updates,
|
| 750 |
+
inputs=[ui_phase_state],
|
| 751 |
+
outputs=phase_visibility_outputs,
|
| 752 |
+
queue=False,
|
| 753 |
+
show_progress="hidden",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 754 |
).then(
|
| 755 |
fn=sync_header_from_task,
|
| 756 |
inputs=[task_info_box, goal_box],
|
| 757 |
+
outputs=[header_task_box, header_goal_box, programmatic_header_task_state],
|
| 758 |
)
|
| 759 |
|
| 760 |
return demo
|