HongzeFu commited on
Commit
a970300
·
1 Parent(s): 5459e22

fix sys log after success fail

Browse files
gradio-web/gradio_callbacks.py CHANGED
@@ -62,6 +62,29 @@ def _point_selection_log():
62
  return format_log_markdown(_ui_text("log", "point_selection_prompt"))
63
 
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  def _session_error_text():
66
  return _ui_text("log", "session_error")
67
 
@@ -439,13 +462,22 @@ def _normalize_post_execute_controls_state(state):
439
  }
440
 
441
 
442
- def on_execute_video_end_transition(uid, post_execute_controls_state=True):
 
 
 
 
443
  """Transition from execute video phase back to the action phase."""
444
  controls_state = _normalize_post_execute_controls_state(post_execute_controls_state)
 
 
 
 
445
  LOGGER.debug(
446
- "on_execute_video_end_transition uid=%s controls_state=%s",
447
  _uid_for_log(uid),
448
  controls_state,
 
449
  )
450
  return (
451
  gr.update(visible=False), # execution_video_group
@@ -456,6 +488,7 @@ def on_execute_video_end_transition(uid, post_execute_controls_state=True):
456
  gr.update(interactive=True), # restart_episode_btn
457
  gr.update(interactive=True), # next_task_btn
458
  _live_obs_update(interactive=False), # img_display
 
459
  gr.update(interactive=controls_state["reference_action_interactive"]), # reference_action_btn
460
  gr.update(interactive=True), # task_hint_display
461
  "action_point", # ui_phase_state
@@ -487,6 +520,7 @@ def _task_load_failed_response(uid, message):
487
  gr.update(visible=False), # control_panel_group
488
  gr.update(value=""), # task_hint_display
489
  gr.update(interactive=False), # reference_action_btn
 
490
  )
491
 
492
 
@@ -564,6 +598,7 @@ def _load_status_task(uid, status):
564
  gr.update(visible=True), # control_panel_group
565
  gr.update(value=get_task_hint(env_id) if env_id else ""), # task_hint_display
566
  gr.update(interactive=False), # reference_action_btn
 
567
  )
568
 
569
  goal_text = capitalize_first_letter(session.language_goal) if session.language_goal else ""
@@ -630,6 +665,7 @@ def _load_status_task(uid, status):
630
  gr.update(visible=False), # control_panel_group
631
  gr.update(value=get_task_hint(actual_env_id)), # task_hint_display
632
  gr.update(interactive=True), # reference_action_btn
 
633
  )
634
 
635
  set_ui_phase(uid, "executing_task")
@@ -656,6 +692,7 @@ def _load_status_task(uid, status):
656
  gr.update(visible=True), # control_panel_group
657
  gr.update(value=get_task_hint(actual_env_id)), # task_hint_display
658
  gr.update(interactive=True), # reference_action_btn
 
659
  )
660
 
661
 
@@ -840,11 +877,18 @@ def _is_valid_coords_text(coords_text: str) -> bool:
840
  return True
841
 
842
 
843
- def on_option_select(uid, option_value, coords_str=None, suppress_next_option_change=False):
 
 
 
 
 
 
844
  """
845
  处理选项选择事件
846
  """
847
  default_msg = _ui_text("coords", "not_needed")
 
848
 
849
  if suppress_next_option_change:
850
  LOGGER.debug(
@@ -852,18 +896,44 @@ def on_option_select(uid, option_value, coords_str=None, suppress_next_option_ch
852
  _uid_for_log(uid),
853
  option_value,
854
  )
855
- return gr.update(), gr.update(), gr.update(), False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
856
 
857
  if option_value is None:
858
  LOGGER.debug("on_option_select uid=%s option=None", _uid_for_log(uid))
859
  session = get_session(uid) if uid else None
860
  base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW) if session else _LIVE_OBS_UPDATE_SKIP
861
- return default_msg, _live_obs_update(value=base_img, interactive=False), _action_selection_log(), False
 
 
 
 
 
 
862
 
863
  session = get_session(uid)
864
  if not session:
865
  LOGGER.warning("on_option_select: missing session uid=%s", _uid_for_log(uid))
866
- return default_msg, _live_obs_update(interactive=False), format_log_markdown(_session_error_text()), False
 
 
 
 
 
 
867
 
868
  option_idx = _parse_option_idx(option_value)
869
  base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW)
@@ -881,10 +951,17 @@ def on_option_select(uid, option_value, coords_str=None, suppress_next_option_ch
881
  _live_obs_update(value=base_img, interactive=True, waiting_for_point=True),
882
  _point_selection_log(),
883
  False,
 
884
  )
885
 
886
  LOGGER.debug("on_option_select uid=%s option=%s requires_coords=False", _uid_for_log(uid), option_idx)
887
- return default_msg, _live_obs_update(value=base_img, interactive=False), _action_selection_log(), False
 
 
 
 
 
 
888
 
889
 
890
  def on_reference_action(uid, current_option_value=None):
@@ -1063,6 +1140,7 @@ def execute_step(uid, option_idx, coords_str):
1063
  reference_update=gr.update(interactive=True),
1064
  task_hint_update=gr.update(interactive=True),
1065
  post_execute_controls_state=None,
 
1066
  show_execution_video=False,
1067
  ui_phase="action_point",
1068
  ):
@@ -1078,6 +1156,7 @@ def execute_step(uid, option_idx, coords_str):
1078
  normalized_post_execute_controls_state = _normalize_post_execute_controls_state(
1079
  post_execute_controls_state
1080
  )
 
1081
  return (
1082
  img_update,
1083
  log_update,
@@ -1095,6 +1174,7 @@ def execute_step(uid, option_idx, coords_str):
1095
  reference_update,
1096
  task_hint_update,
1097
  normalized_post_execute_controls_state,
 
1098
  ui_phase,
1099
  )
1100
 
@@ -1114,6 +1194,7 @@ def execute_step(uid, option_idx, coords_str):
1114
  "exec_btn_interactive": False,
1115
  "reference_action_interactive": False,
1116
  },
 
1117
  show_execution_video=False,
1118
  )
1119
 
@@ -1160,6 +1241,7 @@ def execute_step(uid, option_idx, coords_str):
1160
  "exec_btn_interactive": True,
1161
  "reference_action_interactive": True,
1162
  },
 
1163
  show_execution_video=False,
1164
  )
1165
 
@@ -1188,6 +1270,7 @@ def execute_step(uid, option_idx, coords_str):
1188
  "exec_btn_interactive": True,
1189
  "reference_action_interactive": True,
1190
  },
 
1191
  show_execution_video=False,
1192
  )
1193
 
@@ -1326,6 +1409,7 @@ def execute_step(uid, option_idx, coords_str):
1326
  "exec_btn_interactive": not done,
1327
  "reference_action_interactive": not done,
1328
  }
 
1329
  restart_episode_update = gr.update(interactive=False) if show_execution_video else gr.update(interactive=True)
1330
  next_task_update = gr.update(interactive=False) if show_execution_video else gr.update(interactive=True)
1331
  exec_btn_update = (
@@ -1344,13 +1428,19 @@ def execute_step(uid, option_idx, coords_str):
1344
 
1345
  # 格式化日志消息为 HTML 格式(支持颜色显示)
1346
  formatted_status = format_log_markdown(status)
 
 
 
 
 
1347
  LOGGER.debug(
1348
- "execute_step done uid=%s env=%s ep=%s done=%s post_execute_controls=%s show_execution_video=%s",
1349
  _uid_for_log(uid),
1350
  getattr(session, "env_id", None),
1351
  getattr(session, "episode_idx", None),
1352
  done,
1353
  post_execute_controls_state,
 
1354
  show_execution_video,
1355
  )
1356
 
@@ -1368,6 +1458,7 @@ def execute_step(uid, option_idx, coords_str):
1368
  reference_update=reference_update,
1369
  task_hint_update=task_hint_update,
1370
  post_execute_controls_state=post_execute_controls_state,
 
1371
  show_execution_video=show_execution_video,
1372
  ui_phase="execution_video" if show_execution_video else "action_point",
1373
  )
 
62
  return format_log_markdown(_ui_text("log", "point_selection_prompt"))
63
 
64
 
65
+ def _default_post_execute_log_state():
66
+ return {
67
+ "preserve_terminal_log": False,
68
+ "terminal_log_value": None,
69
+ }
70
+
71
+
72
+ def _normalize_post_execute_log_state(state):
73
+ """Normalize terminal-log preservation payloads across callback boundaries."""
74
+ if isinstance(state, dict):
75
+ preserve_terminal_log = bool(state.get("preserve_terminal_log", False))
76
+ terminal_log_value = state.get("terminal_log_value")
77
+ if terminal_log_value is None:
78
+ preserve_terminal_log = False
79
+ else:
80
+ terminal_log_value = str(terminal_log_value)
81
+ return {
82
+ "preserve_terminal_log": preserve_terminal_log,
83
+ "terminal_log_value": terminal_log_value,
84
+ }
85
+ return _default_post_execute_log_state()
86
+
87
+
88
  def _session_error_text():
89
  return _ui_text("log", "session_error")
90
 
 
462
  }
463
 
464
 
465
+ def on_execute_video_end_transition(
466
+ uid,
467
+ post_execute_controls_state=True,
468
+ post_execute_log_state=None,
469
+ ):
470
  """Transition from execute video phase back to the action phase."""
471
  controls_state = _normalize_post_execute_controls_state(post_execute_controls_state)
472
+ log_state = _normalize_post_execute_log_state(post_execute_log_state)
473
+ log_update = gr.update()
474
+ if log_state["preserve_terminal_log"]:
475
+ log_update = gr.update(value=log_state["terminal_log_value"])
476
  LOGGER.debug(
477
+ "on_execute_video_end_transition uid=%s controls_state=%s log_state=%s",
478
  _uid_for_log(uid),
479
  controls_state,
480
+ log_state,
481
  )
482
  return (
483
  gr.update(visible=False), # execution_video_group
 
488
  gr.update(interactive=True), # restart_episode_btn
489
  gr.update(interactive=True), # next_task_btn
490
  _live_obs_update(interactive=False), # img_display
491
+ log_update, # log_output
492
  gr.update(interactive=controls_state["reference_action_interactive"]), # reference_action_btn
493
  gr.update(interactive=True), # task_hint_display
494
  "action_point", # ui_phase_state
 
520
  gr.update(visible=False), # control_panel_group
521
  gr.update(value=""), # task_hint_display
522
  gr.update(interactive=False), # reference_action_btn
523
+ _default_post_execute_log_state(), # post_execute_log_state
524
  )
525
 
526
 
 
598
  gr.update(visible=True), # control_panel_group
599
  gr.update(value=get_task_hint(env_id) if env_id else ""), # task_hint_display
600
  gr.update(interactive=False), # reference_action_btn
601
+ _default_post_execute_log_state(), # post_execute_log_state
602
  )
603
 
604
  goal_text = capitalize_first_letter(session.language_goal) if session.language_goal else ""
 
665
  gr.update(visible=False), # control_panel_group
666
  gr.update(value=get_task_hint(actual_env_id)), # task_hint_display
667
  gr.update(interactive=True), # reference_action_btn
668
+ _default_post_execute_log_state(), # post_execute_log_state
669
  )
670
 
671
  set_ui_phase(uid, "executing_task")
 
692
  gr.update(visible=True), # control_panel_group
693
  gr.update(value=get_task_hint(actual_env_id)), # task_hint_display
694
  gr.update(interactive=True), # reference_action_btn
695
+ _default_post_execute_log_state(), # post_execute_log_state
696
  )
697
 
698
 
 
877
  return True
878
 
879
 
880
+ def on_option_select(
881
+ uid,
882
+ option_value,
883
+ coords_str=None,
884
+ suppress_next_option_change=False,
885
+ post_execute_log_state=None,
886
+ ):
887
  """
888
  处理选项选择事件
889
  """
890
  default_msg = _ui_text("coords", "not_needed")
891
+ normalized_post_execute_log_state = _normalize_post_execute_log_state(post_execute_log_state)
892
 
893
  if suppress_next_option_change:
894
  LOGGER.debug(
 
896
  _uid_for_log(uid),
897
  option_value,
898
  )
899
+ return gr.update(), gr.update(), gr.update(), False, normalized_post_execute_log_state
900
+
901
+ if normalized_post_execute_log_state["preserve_terminal_log"]:
902
+ LOGGER.debug(
903
+ "on_option_select preserving terminal log uid=%s option=%s",
904
+ _uid_for_log(uid),
905
+ option_value,
906
+ )
907
+ return (
908
+ gr.update(),
909
+ gr.update(),
910
+ gr.update(value=normalized_post_execute_log_state["terminal_log_value"]),
911
+ False,
912
+ normalized_post_execute_log_state,
913
+ )
914
 
915
  if option_value is None:
916
  LOGGER.debug("on_option_select uid=%s option=None", _uid_for_log(uid))
917
  session = get_session(uid) if uid else None
918
  base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW) if session else _LIVE_OBS_UPDATE_SKIP
919
+ return (
920
+ default_msg,
921
+ _live_obs_update(value=base_img, interactive=False),
922
+ _action_selection_log(),
923
+ False,
924
+ normalized_post_execute_log_state,
925
+ )
926
 
927
  session = get_session(uid)
928
  if not session:
929
  LOGGER.warning("on_option_select: missing session uid=%s", _uid_for_log(uid))
930
+ return (
931
+ default_msg,
932
+ _live_obs_update(interactive=False),
933
+ format_log_markdown(_session_error_text()),
934
+ False,
935
+ normalized_post_execute_log_state,
936
+ )
937
 
938
  option_idx = _parse_option_idx(option_value)
939
  base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW)
 
951
  _live_obs_update(value=base_img, interactive=True, waiting_for_point=True),
952
  _point_selection_log(),
953
  False,
954
+ normalized_post_execute_log_state,
955
  )
956
 
957
  LOGGER.debug("on_option_select uid=%s option=%s requires_coords=False", _uid_for_log(uid), option_idx)
958
+ return (
959
+ default_msg,
960
+ _live_obs_update(value=base_img, interactive=False),
961
+ _action_selection_log(),
962
+ False,
963
+ normalized_post_execute_log_state,
964
+ )
965
 
966
 
967
  def on_reference_action(uid, current_option_value=None):
 
1140
  reference_update=gr.update(interactive=True),
1141
  task_hint_update=gr.update(interactive=True),
1142
  post_execute_controls_state=None,
1143
+ post_execute_log_state=None,
1144
  show_execution_video=False,
1145
  ui_phase="action_point",
1146
  ):
 
1156
  normalized_post_execute_controls_state = _normalize_post_execute_controls_state(
1157
  post_execute_controls_state
1158
  )
1159
+ normalized_post_execute_log_state = _normalize_post_execute_log_state(post_execute_log_state)
1160
  return (
1161
  img_update,
1162
  log_update,
 
1174
  reference_update,
1175
  task_hint_update,
1176
  normalized_post_execute_controls_state,
1177
+ normalized_post_execute_log_state,
1178
  ui_phase,
1179
  )
1180
 
 
1194
  "exec_btn_interactive": False,
1195
  "reference_action_interactive": False,
1196
  },
1197
+ post_execute_log_state=_default_post_execute_log_state(),
1198
  show_execution_video=False,
1199
  )
1200
 
 
1241
  "exec_btn_interactive": True,
1242
  "reference_action_interactive": True,
1243
  },
1244
+ post_execute_log_state=_default_post_execute_log_state(),
1245
  show_execution_video=False,
1246
  )
1247
 
 
1270
  "exec_btn_interactive": True,
1271
  "reference_action_interactive": True,
1272
  },
1273
+ post_execute_log_state=_default_post_execute_log_state(),
1274
  show_execution_video=False,
1275
  )
1276
 
 
1409
  "exec_btn_interactive": not done,
1410
  "reference_action_interactive": not done,
1411
  }
1412
+ post_execute_log_state = _default_post_execute_log_state()
1413
  restart_episode_update = gr.update(interactive=False) if show_execution_video else gr.update(interactive=True)
1414
  next_task_update = gr.update(interactive=False) if show_execution_video else gr.update(interactive=True)
1415
  exec_btn_update = (
 
1428
 
1429
  # 格式化日志消息为 HTML 格式(支持颜色显示)
1430
  formatted_status = format_log_markdown(status)
1431
+ if done:
1432
+ post_execute_log_state = {
1433
+ "preserve_terminal_log": True,
1434
+ "terminal_log_value": formatted_status,
1435
+ }
1436
  LOGGER.debug(
1437
+ "execute_step done uid=%s env=%s ep=%s done=%s post_execute_controls=%s post_execute_log=%s show_execution_video=%s",
1438
  _uid_for_log(uid),
1439
  getattr(session, "env_id", None),
1440
  getattr(session, "episode_idx", None),
1441
  done,
1442
  post_execute_controls_state,
1443
+ post_execute_log_state,
1444
  show_execution_video,
1445
  )
1446
 
 
1458
  reference_update=reference_update,
1459
  task_hint_update=task_hint_update,
1460
  post_execute_controls_state=post_execute_controls_state,
1461
+ post_execute_log_state=post_execute_log_state,
1462
  show_execution_video=show_execution_video,
1463
  ui_phase="execution_video" if show_execution_video else "action_point",
1464
  )
gradio-web/test/test_reference_action_callbacks.py CHANGED
@@ -122,24 +122,26 @@ def test_on_option_select_resets_to_point_wait_state_for_point_action(monkeypatc
122
  session = _FakeOptionSession()
123
  monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
124
 
125
- coords_text, img_update, log_text, suppress_flag = callbacks.on_option_select("uid-1", 0, "12, 34", False)
126
 
127
  assert coords_text == config.UI_TEXT["coords"]["select_point"]
128
  assert img_update.get("interactive") is True
129
  assert img_update.get("elem_classes") == config.get_live_obs_elem_classes(waiting_for_point=True)
130
  assert log_text == config.UI_TEXT["log"]["point_selection_prompt"]
131
  assert suppress_flag is False
 
132
 
133
 
134
  def test_on_option_select_suppresses_programmatic_reference_change(reload_module):
135
  callbacks = reload_module("gradio_callbacks")
136
 
137
- coords_update, img_update, log_update, suppress_flag = callbacks.on_option_select("uid-1", 0, "12, 34", True)
138
 
139
  assert coords_update.get("__type__") == "update"
140
  assert img_update.get("__type__") == "update"
141
  assert log_update.get("__type__") == "update"
142
  assert suppress_flag is False
 
143
 
144
 
145
  def test_on_map_click_clears_wait_state_and_restores_action_prompt(monkeypatch, reload_module):
 
122
  session = _FakeOptionSession()
123
  monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
124
 
125
+ coords_text, img_update, log_text, suppress_flag, log_state = callbacks.on_option_select("uid-1", 0, "12, 34", False)
126
 
127
  assert coords_text == config.UI_TEXT["coords"]["select_point"]
128
  assert img_update.get("interactive") is True
129
  assert img_update.get("elem_classes") == config.get_live_obs_elem_classes(waiting_for_point=True)
130
  assert log_text == config.UI_TEXT["log"]["point_selection_prompt"]
131
  assert suppress_flag is False
132
+ assert log_state == callbacks._default_post_execute_log_state()
133
 
134
 
135
  def test_on_option_select_suppresses_programmatic_reference_change(reload_module):
136
  callbacks = reload_module("gradio_callbacks")
137
 
138
+ coords_update, img_update, log_update, suppress_flag, log_state = callbacks.on_option_select("uid-1", 0, "12, 34", True)
139
 
140
  assert coords_update.get("__type__") == "update"
141
  assert img_update.get("__type__") == "update"
142
  assert log_update.get("__type__") == "update"
143
  assert suppress_flag is False
144
+ assert log_state == callbacks._default_post_execute_log_state()
145
 
146
 
147
  def test_on_map_click_clears_wait_state_and_restores_action_prompt(monkeypatch, reload_module):
gradio-web/test/test_ui_phase_machine_runtime_e2e.py CHANGED
@@ -2556,7 +2556,13 @@ def test_header_task_switch_to_video_task_shows_demo_phase(monkeypatch):
2556
  demo.close()
2557
 
2558
 
2559
- def _run_local_execute_video_transition_test(*, status_text, done, expect_terminal_buttons_disabled):
 
 
 
 
 
 
2560
  import gradio_callbacks as cb
2561
  import config as config_module
2562
 
@@ -2614,6 +2620,12 @@ def _run_local_execute_video_transition_test(*, status_text, done, expect_termin
2614
  "reference_action_interactive": True,
2615
  }
2616
  )
 
 
 
 
 
 
2617
  suppress_state = gr.State(value=False)
2618
  with gr.Column(visible=True, elem_id="main_interface") as main_interface:
2619
  with gr.Column(visible=False, elem_id="video_phase_group") as video_phase_group:
@@ -2682,20 +2694,21 @@ def _run_local_execute_video_transition_test(*, status_text, done, expect_termin
2682
  reference_action_btn,
2683
  task_hint_display,
2684
  post_execute_controls_state,
 
2685
  phase_state,
2686
  ],
2687
  queue=False,
2688
  )
2689
  options_radio.change(
2690
  fn=cb.on_option_select,
2691
- inputs=[uid_state, options_radio, coords_box, suppress_state],
2692
- outputs=[coords_box, img_display, log_output, suppress_state],
2693
  queue=False,
2694
  )
2695
 
2696
  execute_video_display.end(
2697
  fn=cb.on_execute_video_end_transition,
2698
- inputs=[uid_state, post_execute_controls_state],
2699
  outputs=[
2700
  execution_video_group,
2701
  action_phase_group,
@@ -2705,6 +2718,7 @@ def _run_local_execute_video_transition_test(*, status_text, done, expect_termin
2705
  restart_episode_btn,
2706
  next_task_btn,
2707
  img_display,
 
2708
  reference_action_btn,
2709
  task_hint_display,
2710
  phase_state,
@@ -2713,7 +2727,7 @@ def _run_local_execute_video_transition_test(*, status_text, done, expect_termin
2713
  )
2714
  execute_video_display.stop(
2715
  fn=cb.on_execute_video_end_transition,
2716
- inputs=[uid_state, post_execute_controls_state],
2717
  outputs=[
2718
  execution_video_group,
2719
  action_phase_group,
@@ -2723,6 +2737,7 @@ def _run_local_execute_video_transition_test(*, status_text, done, expect_termin
2723
  restart_episode_btn,
2724
  next_task_btn,
2725
  img_display,
 
2726
  reference_action_btn,
2727
  task_hint_display,
2728
  phase_state,
@@ -2841,6 +2856,13 @@ def _run_local_execute_video_transition_test(*, status_text, done, expect_termin
2841
  "execDisabled": True,
2842
  "refDisabled": True,
2843
  }
 
 
 
 
 
 
 
2844
  else:
2845
  button_snapshot = page.evaluate(
2846
  """() => {
@@ -2907,6 +2929,7 @@ def test_phase_machine_runtime_local_video_path_end_transition_terminal_success(
2907
  status_text="SUCCESS",
2908
  done=True,
2909
  expect_terminal_buttons_disabled=True,
 
2910
  )
2911
 
2912
 
@@ -2915,4 +2938,5 @@ def test_phase_machine_runtime_local_video_path_end_transition_terminal_failed()
2915
  status_text="Executing: pick | FAILED",
2916
  done=True,
2917
  expect_terminal_buttons_disabled=True,
 
2918
  )
 
2556
  demo.close()
2557
 
2558
 
2559
+ def _run_local_execute_video_transition_test(
2560
+ *,
2561
+ status_text,
2562
+ done,
2563
+ expect_terminal_buttons_disabled,
2564
+ expected_terminal_log=None,
2565
+ ):
2566
  import gradio_callbacks as cb
2567
  import config as config_module
2568
 
 
2620
  "reference_action_interactive": True,
2621
  }
2622
  )
2623
+ post_execute_log_state = gr.State(
2624
+ value={
2625
+ "preserve_terminal_log": False,
2626
+ "terminal_log_value": None,
2627
+ }
2628
+ )
2629
  suppress_state = gr.State(value=False)
2630
  with gr.Column(visible=True, elem_id="main_interface") as main_interface:
2631
  with gr.Column(visible=False, elem_id="video_phase_group") as video_phase_group:
 
2694
  reference_action_btn,
2695
  task_hint_display,
2696
  post_execute_controls_state,
2697
+ post_execute_log_state,
2698
  phase_state,
2699
  ],
2700
  queue=False,
2701
  )
2702
  options_radio.change(
2703
  fn=cb.on_option_select,
2704
+ inputs=[uid_state, options_radio, coords_box, suppress_state, post_execute_log_state],
2705
+ outputs=[coords_box, img_display, log_output, suppress_state, post_execute_log_state],
2706
  queue=False,
2707
  )
2708
 
2709
  execute_video_display.end(
2710
  fn=cb.on_execute_video_end_transition,
2711
+ inputs=[uid_state, post_execute_controls_state, post_execute_log_state],
2712
  outputs=[
2713
  execution_video_group,
2714
  action_phase_group,
 
2718
  restart_episode_btn,
2719
  next_task_btn,
2720
  img_display,
2721
+ log_output,
2722
  reference_action_btn,
2723
  task_hint_display,
2724
  phase_state,
 
2727
  )
2728
  execute_video_display.stop(
2729
  fn=cb.on_execute_video_end_transition,
2730
+ inputs=[uid_state, post_execute_controls_state, post_execute_log_state],
2731
  outputs=[
2732
  execution_video_group,
2733
  action_phase_group,
 
2737
  restart_episode_btn,
2738
  next_task_btn,
2739
  img_display,
2740
+ log_output,
2741
  reference_action_btn,
2742
  task_hint_display,
2743
  phase_state,
 
2856
  "execDisabled": True,
2857
  "refDisabled": True,
2858
  }
2859
+ terminal_log_before = _read_log_output_value(page)
2860
+ assert terminal_log_before is not None
2861
+ assert expected_terminal_log is not None
2862
+ assert expected_terminal_log in terminal_log_before
2863
+ page.locator("#action_radio input[type='radio']").nth(1).check(force=True)
2864
+ page.wait_for_timeout(300)
2865
+ assert _read_log_output_value(page) == terminal_log_before
2866
  else:
2867
  button_snapshot = page.evaluate(
2868
  """() => {
 
2929
  status_text="SUCCESS",
2930
  done=True,
2931
  expect_terminal_buttons_disabled=True,
2932
+ expected_terminal_log="episode success",
2933
  )
2934
 
2935
 
 
2938
  status_text="Executing: pick | FAILED",
2939
  done=True,
2940
  expect_terminal_buttons_disabled=True,
2941
+ expected_terminal_log="episode failed",
2942
  )
gradio-web/test/test_ui_text_config.py CHANGED
@@ -42,13 +42,14 @@ def test_on_option_select_uses_configured_select_point_and_log_messages(monkeypa
42
  )
43
  monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeOptionSession())
44
 
45
- coords_text, img_update, log_text, suppress_flag = callbacks.on_option_select("uid-1", 0, None, False)
46
 
47
  assert coords_text == "pick a point from config"
48
  assert img_update.get("interactive") is True
49
  assert callbacks.get_live_obs_elem_classes(waiting_for_point=True) == img_update.get("elem_classes")
50
  assert log_text == "custom log prompt from config"
51
  assert suppress_flag is False
 
52
 
53
 
54
  def test_precheck_execute_inputs_uses_configured_before_execute_message(monkeypatch, reload_module):
@@ -92,6 +93,7 @@ def test_on_execute_video_end_transition_restores_controls_for_non_terminal_stat
92
  "exec_btn_interactive": True,
93
  "reference_action_interactive": True,
94
  },
 
95
  )
96
 
97
  assert result[0]["visible"] is False
@@ -101,9 +103,10 @@ def test_on_execute_video_end_transition_restores_controls_for_non_terminal_stat
101
  assert result[4]["interactive"] is True
102
  assert result[5]["interactive"] is True
103
  assert result[6]["interactive"] is True
104
- assert result[8]["interactive"] is True
105
  assert result[9]["interactive"] is True
106
- assert result[10] == "action_point"
 
107
 
108
 
109
  def test_on_execute_video_end_transition_keeps_terminal_buttons_disabled(reload_module):
@@ -115,6 +118,10 @@ def test_on_execute_video_end_transition_keeps_terminal_buttons_disabled(reload_
115
  "exec_btn_interactive": False,
116
  "reference_action_interactive": False,
117
  },
 
 
 
 
118
  )
119
 
120
  assert result[0]["visible"] is False
@@ -124,9 +131,34 @@ def test_on_execute_video_end_transition_keeps_terminal_buttons_disabled(reload_
124
  assert result[4]["interactive"] is False
125
  assert result[5]["interactive"] is True
126
  assert result[6]["interactive"] is True
127
- assert result[8]["interactive"] is False
128
- assert result[9]["interactive"] is True
129
- assert result[10] == "action_point"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
 
132
  def test_on_demo_video_play_disables_button_and_sets_single_use_state(monkeypatch, reload_module):
 
42
  )
43
  monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeOptionSession())
44
 
45
+ coords_text, img_update, log_text, suppress_flag, log_state = callbacks.on_option_select("uid-1", 0, None, False)
46
 
47
  assert coords_text == "pick a point from config"
48
  assert img_update.get("interactive") is True
49
  assert callbacks.get_live_obs_elem_classes(waiting_for_point=True) == img_update.get("elem_classes")
50
  assert log_text == "custom log prompt from config"
51
  assert suppress_flag is False
52
+ assert log_state == callbacks._default_post_execute_log_state()
53
 
54
 
55
  def test_precheck_execute_inputs_uses_configured_before_execute_message(monkeypatch, reload_module):
 
93
  "exec_btn_interactive": True,
94
  "reference_action_interactive": True,
95
  },
96
+ callbacks._default_post_execute_log_state(),
97
  )
98
 
99
  assert result[0]["visible"] is False
 
103
  assert result[4]["interactive"] is True
104
  assert result[5]["interactive"] is True
105
  assert result[6]["interactive"] is True
106
+ assert result[8].get("__type__") == "update"
107
  assert result[9]["interactive"] is True
108
+ assert result[10]["interactive"] is True
109
+ assert result[11] == "action_point"
110
 
111
 
112
  def test_on_execute_video_end_transition_keeps_terminal_buttons_disabled(reload_module):
 
118
  "exec_btn_interactive": False,
119
  "reference_action_interactive": False,
120
  },
121
+ {
122
+ "preserve_terminal_log": True,
123
+ "terminal_log_value": "terminal banner",
124
+ },
125
  )
126
 
127
  assert result[0]["visible"] is False
 
131
  assert result[4]["interactive"] is False
132
  assert result[5]["interactive"] is True
133
  assert result[6]["interactive"] is True
134
+ assert result[8]["value"] == "terminal banner"
135
+ assert result[9]["interactive"] is False
136
+ assert result[10]["interactive"] is True
137
+ assert result[11] == "action_point"
138
+
139
+
140
+ def test_on_option_select_preserves_terminal_log_state(reload_module):
141
+ callbacks = reload_module("gradio_callbacks")
142
+
143
+ coords_update, img_update, log_update, suppress_flag, log_state = callbacks.on_option_select(
144
+ "uid-1",
145
+ None,
146
+ None,
147
+ False,
148
+ {
149
+ "preserve_terminal_log": True,
150
+ "terminal_log_value": "episode success banner",
151
+ },
152
+ )
153
+
154
+ assert coords_update.get("__type__") == "update"
155
+ assert img_update.get("__type__") == "update"
156
+ assert log_update["value"] == "episode success banner"
157
+ assert suppress_flag is False
158
+ assert log_state == {
159
+ "preserve_terminal_log": True,
160
+ "terminal_log_value": "episode success banner",
161
+ }
162
 
163
 
164
  def test_on_demo_video_play_disables_button_and_sets_single_use_state(monkeypatch, reload_module):
gradio-web/ui_layout.py CHANGED
@@ -872,6 +872,13 @@ def _normalize_load_result(load_result):
872
  if len(normalized) in {19, 20}:
873
  normalized.insert(8, gr.update(value=None, visible=False, playback_position=0))
874
  normalized.insert(16, gr.update(visible=False))
 
 
 
 
 
 
 
875
  return tuple(normalized)
876
 
877
 
@@ -958,6 +965,12 @@ def create_ui_blocks():
958
  "reference_action_interactive": True,
959
  }
960
  )
 
 
 
 
 
 
961
  current_task_env_state = gr.State(value=None)
962
  suppress_next_option_change_state = gr.State(value=False)
963
 
@@ -1123,6 +1136,7 @@ def create_ui_blocks():
1123
  control_panel_group,
1124
  task_hint_display,
1125
  reference_action_btn,
 
1126
  ui_phase_state,
1127
  native_progress_host,
1128
  ]
@@ -1398,7 +1412,7 @@ def create_ui_blocks():
1398
 
1399
  execute_video_display.end(
1400
  fn=on_execute_video_end_transition,
1401
- inputs=[uid_state, post_execute_controls_state],
1402
  outputs=[
1403
  execution_video_group,
1404
  action_phase_group,
@@ -1408,6 +1422,7 @@ def create_ui_blocks():
1408
  restart_episode_btn,
1409
  next_task_btn,
1410
  img_display,
 
1411
  reference_action_btn,
1412
  task_hint_display,
1413
  ui_phase_state,
@@ -1423,7 +1438,7 @@ def create_ui_blocks():
1423
  )
1424
  execute_video_display.stop(
1425
  fn=on_execute_video_end_transition,
1426
- inputs=[uid_state, post_execute_controls_state],
1427
  outputs=[
1428
  execution_video_group,
1429
  action_phase_group,
@@ -1433,6 +1448,7 @@ def create_ui_blocks():
1433
  restart_episode_btn,
1434
  next_task_btn,
1435
  img_display,
 
1436
  reference_action_btn,
1437
  task_hint_display,
1438
  ui_phase_state,
@@ -1463,8 +1479,8 @@ def create_ui_blocks():
1463
 
1464
  options_radio.change(
1465
  fn=on_option_select,
1466
- inputs=[uid_state, options_radio, coords_box, suppress_next_option_change_state],
1467
- outputs=[coords_box, img_display, log_output, suppress_next_option_change_state],
1468
  queue=False,
1469
  show_progress="hidden",
1470
  ).then(
@@ -1548,6 +1564,7 @@ def create_ui_blocks():
1548
  reference_action_btn,
1549
  task_hint_display,
1550
  post_execute_controls_state,
 
1551
  ui_phase_state,
1552
  ],
1553
  show_progress="hidden",
 
872
  if len(normalized) in {19, 20}:
873
  normalized.insert(8, gr.update(value=None, visible=False, playback_position=0))
874
  normalized.insert(16, gr.update(visible=False))
875
+ if len(normalized) == 21:
876
+ normalized.append(
877
+ {
878
+ "preserve_terminal_log": False,
879
+ "terminal_log_value": None,
880
+ }
881
+ )
882
  return tuple(normalized)
883
 
884
 
 
965
  "reference_action_interactive": True,
966
  }
967
  )
968
+ post_execute_log_state = gr.State(
969
+ value={
970
+ "preserve_terminal_log": False,
971
+ "terminal_log_value": None,
972
+ }
973
+ )
974
  current_task_env_state = gr.State(value=None)
975
  suppress_next_option_change_state = gr.State(value=False)
976
 
 
1136
  control_panel_group,
1137
  task_hint_display,
1138
  reference_action_btn,
1139
+ post_execute_log_state,
1140
  ui_phase_state,
1141
  native_progress_host,
1142
  ]
 
1412
 
1413
  execute_video_display.end(
1414
  fn=on_execute_video_end_transition,
1415
+ inputs=[uid_state, post_execute_controls_state, post_execute_log_state],
1416
  outputs=[
1417
  execution_video_group,
1418
  action_phase_group,
 
1422
  restart_episode_btn,
1423
  next_task_btn,
1424
  img_display,
1425
+ log_output,
1426
  reference_action_btn,
1427
  task_hint_display,
1428
  ui_phase_state,
 
1438
  )
1439
  execute_video_display.stop(
1440
  fn=on_execute_video_end_transition,
1441
+ inputs=[uid_state, post_execute_controls_state, post_execute_log_state],
1442
  outputs=[
1443
  execution_video_group,
1444
  action_phase_group,
 
1448
  restart_episode_btn,
1449
  next_task_btn,
1450
  img_display,
1451
+ log_output,
1452
  reference_action_btn,
1453
  task_hint_display,
1454
  ui_phase_state,
 
1479
 
1480
  options_radio.change(
1481
  fn=on_option_select,
1482
+ inputs=[uid_state, options_radio, coords_box, suppress_next_option_change_state, post_execute_log_state],
1483
+ outputs=[coords_box, img_display, log_output, suppress_next_option_change_state, post_execute_log_state],
1484
  queue=False,
1485
  show_progress="hidden",
1486
  ).then(
 
1564
  reference_action_btn,
1565
  task_hint_display,
1566
  post_execute_controls_state,
1567
+ post_execute_log_state,
1568
  ui_phase_state,
1569
  ],
1570
  show_progress="hidden",