HongzeFu commited on
Commit
bcecf76
·
1 Parent(s): 79a56bd

config text update

Browse files
gradio-web/config.py CHANGED
@@ -35,6 +35,37 @@ DEMO_VIDEO_ENV_IDS = [
35
  "RouteStick"
36
  ]
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  def should_show_demo_video(env_id):
39
  """
40
  判断指定的环境ID是否应该显示demonstration video
 
35
  "RouteStick"
36
  ]
37
 
38
+ UI_TEXT = {
39
+ "log": {
40
+ "action_selection_prompt": "please select the action below 👇🏻,\nsome actions also need to select keypoint",
41
+ "demo_video_prompt": 'press "Watch Video Input🎬" to watch a video\nNote: you can only watch the video once',
42
+ "session_error": "Session Error",
43
+ "reference_action_error": "Ground Truth Action Error: {error}",
44
+ "reference_action_message": "Ground Truth Action: {option_label}. {option_action}",
45
+ "reference_action_message_with_coords": "Ground Truth Action: {option_label}. {option_action} | coords: {coords_text}",
46
+ "reference_action_status": "Ground Truth Action: {message}",
47
+ "execute_missing_action": "Error: No action selected",
48
+ "episode_success_banner": "********************************\n**** episode success ****\n********************************\n ---please press change episode---- ",
49
+ "episode_failed_banner": "********************************\n**** episode failed ****\n********************************\n ---please press change episode---- ",
50
+ },
51
+ "coords": {
52
+ "not_needed": "No need for coordinates",
53
+ "select_keypoint": "please click the keypoint selection image",
54
+ "select_keypoint_before_execute": "please click the keypoint selection image before execute!",
55
+ },
56
+ "errors": {
57
+ "load_missing_task": "Error loading task: missing current_task",
58
+ "load_invalid_task": "Error loading task: invalid task payload",
59
+ "load_episode_error": "Error: {load_msg}",
60
+ "next_task_failed": "Failed to load next task",
61
+ "restart_missing_task": "Failed to restart episode: missing current task",
62
+ "restart_invalid_task": "Failed to restart episode: invalid task payload",
63
+ "switch_env_failed": "Failed to switch environment to '{selected_env}'",
64
+ "init_failed": "Initialization error: {error}",
65
+ "reference_action_resolve_failed": "Failed to resolve ground truth action.",
66
+ },
67
+ }
68
+
69
  def should_show_demo_video(env_id):
70
  """
71
  判断指定的环境ID是否应该显示demonstration video
gradio-web/gradio_callbacks.py CHANGED
@@ -36,6 +36,7 @@ from config import (
36
  KEYFRAME_DOWNSAMPLE_FACTOR,
37
  LIVE_OBS_REFRESH_HZ,
38
  SESSION_TIMEOUT,
 
39
  USE_SEGMENTED_VIEW,
40
  should_show_demo_video,
41
  )
@@ -50,6 +51,11 @@ _LIVE_OBS_REFRESH_LOCK = threading.Lock()
50
  LOGGER = logging.getLogger("robomme.callbacks")
51
 
52
 
 
 
 
 
 
53
  def _should_enqueue_sample(sample_index: int) -> bool:
54
  factor = max(1, int(KEYFRAME_DOWNSAMPLE_FACTOR))
55
  return sample_index % factor == 0
@@ -198,7 +204,7 @@ def on_video_end(uid):
198
  Called when the demonstration video finishes playing.
199
  Updates the system log to prompt for action selection.
200
  """
201
- return format_log_markdown("please select the action below 👇🏻,\nsome actions also need to select keypoint")
202
 
203
 
204
  def switch_to_execute_phase(uid):
@@ -382,7 +388,7 @@ def on_video_end_transition(uid):
382
  gr.update(visible=False), # video_phase_group
383
  gr.update(visible=True), # action_phase_group
384
  gr.update(visible=True), # control_panel_group
385
- format_log_markdown("please select the action below 👇🏻,\nsome actions also need to select keypoint")
386
  )
387
 
388
 
@@ -395,7 +401,7 @@ def _task_load_failed_response(uid, message):
395
  format_log_markdown(message), # log_output
396
  gr.update(choices=[], value=None), # options_radio
397
  "", # goal_box
398
- "No need for coordinates", # coords_box
399
  gr.update(value=None, visible=False), # video_display
400
  "", # task_info_box
401
  "", # progress_info_box
@@ -415,12 +421,12 @@ def _load_status_task(uid, status):
415
  """Load status.current_task to session and build the standard UI update tuple."""
416
  current_task = status.get("current_task") if isinstance(status, dict) else None
417
  if not current_task:
418
- return _task_load_failed_response(uid, "Error loading task: missing current_task")
419
 
420
  env_id = current_task.get("env_id")
421
  ep_num = current_task.get("episode_idx")
422
  if env_id is None or ep_num is None:
423
- return _task_load_failed_response(uid, "Error loading task: invalid task payload")
424
 
425
  try:
426
  completed_count = int(status.get("completed_count", 0))
@@ -472,10 +478,10 @@ def _load_status_task(uid, status):
472
  uid,
473
  gr.update(visible=True), # main_interface
474
  gr.update(value=None, interactive=False), # img_display
475
- format_log_markdown(f"Error: {load_msg}"), # log_output
476
  gr.update(choices=[], value=None), # options_radio
477
  "", # goal_box
478
- "No need for coordinates", # coords_box
479
  gr.update(value=None, visible=False), # video_display
480
  f"{actual_env_id} (Episode {ep_num})", # task_info_box
481
  progress_text, # progress_info_box
@@ -518,11 +524,11 @@ def _load_status_task(uid, status):
518
  demo_video_path = None
519
  has_demo_video = False
520
  should_show = should_show_demo_video(actual_env_id) if actual_env_id else False
521
- initial_log_msg = format_log_markdown("please select the action below 👇🏻,\nsome actions also need to select keypoint")
522
 
523
  if should_show:
524
  has_demo_video = True
525
- initial_log_msg = format_log_markdown('press "Watch Video Input🎬" to watch a video\nNote: you can only watch the video once')
526
  if session.demonstration_frames:
527
  try:
528
  demo_video_path = save_video(session.demonstration_frames, "demo")
@@ -553,7 +559,7 @@ def _load_status_task(uid, status):
553
  initial_log_msg, # log_output
554
  gr.update(choices=radio_choices, value=None), # options_radio
555
  goal_text, # goal_box
556
- "No need for coordinates", # coords_box
557
  gr.update(value=demo_video_path, visible=True), # video_display
558
  f"{actual_env_id} (Episode {ep_num})", # task_info_box
559
  progress_text, # progress_info_box
@@ -577,7 +583,7 @@ def _load_status_task(uid, status):
577
  initial_log_msg, # log_output
578
  gr.update(choices=radio_choices, value=None), # options_radio
579
  goal_text, # goal_box
580
- "No need for coordinates", # coords_box
581
  gr.update(value=None, visible=False), # video_display (no video)
582
  f"{actual_env_id} (Episode {ep_num})", # task_info_box
583
  progress_text, # progress_info_box
@@ -630,7 +636,7 @@ def load_next_task_wrapper(uid):
630
  LOGGER.info("load_next_task_wrapper uid=%s", _uid_for_log(uid))
631
  status = user_manager.next_episode_same_env(uid)
632
  if not status:
633
- return _task_load_failed_response(uid, "Failed to load next task")
634
  return _load_status_task(uid, status)
635
 
636
 
@@ -647,12 +653,12 @@ def restart_episode_wrapper(uid):
647
  status = user_manager.get_session_status(uid)
648
  current_task = status.get("current_task") if isinstance(status, dict) else None
649
  if not current_task:
650
- return _task_load_failed_response(uid, "Failed to restart episode: missing current task")
651
 
652
  env_id = current_task.get("env_id")
653
  ep_num = current_task.get("episode_idx")
654
  if env_id is None or ep_num is None:
655
- return _task_load_failed_response(uid, "Failed to restart episode: invalid task payload")
656
 
657
  return _load_status_task(uid, status)
658
 
@@ -677,7 +683,10 @@ def switch_env_wrapper(uid, selected_env):
677
  status = user_manager.get_session_status(uid)
678
 
679
  if not status:
680
- return _task_load_failed_response(uid, f"Failed to switch environment to '{selected_env}'")
 
 
 
681
 
682
  return _load_status_task(uid, status)
683
 
@@ -693,7 +702,7 @@ def on_map_click(uid, option_value, evt: gr.SelectData):
693
  session = get_session(uid)
694
  if not session:
695
  LOGGER.warning("on_map_click: missing session uid=%s", _uid_for_log(uid))
696
- return None, "Session Error"
697
 
698
  # Check if current option actually needs coordinates
699
  needs_coords = False
@@ -720,7 +729,7 @@ def on_map_click(uid, option_value, evt: gr.SelectData):
720
  # Return current state without changes (or reset to default message if needed, but it should already be there)
721
  # We return the clean image and the "No need" message to enforce state
722
  base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW)
723
- return base_img, "No need for coordinates"
724
 
725
  x, y = evt.index[0], evt.index[1]
726
  LOGGER.debug(
@@ -746,7 +755,11 @@ def _is_valid_coords_text(coords_text: str) -> bool:
746
  if not isinstance(coords_text, str):
747
  return False
748
  text = coords_text.strip()
749
- if text in {"", "please click the keypoint selection image", "No need for coordinates"}:
 
 
 
 
750
  return False
751
  if "," not in text:
752
  return False
@@ -763,7 +776,7 @@ def on_option_select(uid, option_value, coords_str=None):
763
  """
764
  处理选项选择事件
765
  """
766
- default_msg = "No need for coordinates"
767
 
768
  if option_value is None:
769
  LOGGER.debug("on_option_select uid=%s option=None", _uid_for_log(uid))
@@ -796,7 +809,7 @@ def on_option_select(uid, option_value, coords_str=None):
796
  )
797
  if _is_valid_coords_text(coords_str):
798
  return coords_str, gr.update(interactive=True)
799
- return "please click the keypoint selection image", gr.update(interactive=True)
800
 
801
  LOGGER.debug("on_option_select uid=%s option=%s requires_coords=False", _uid_for_log(uid), option_idx)
802
  return default_msg, gr.update(interactive=False)
@@ -815,8 +828,8 @@ def on_reference_action(uid):
815
  return (
816
  None,
817
  gr.update(),
818
- "No need for coordinates",
819
- format_log_markdown("Session Error"),
820
  )
821
 
822
  LOGGER.info("on_reference_action uid=%s env=%s", _uid_for_log(uid), getattr(session, "env_id", None))
@@ -830,18 +843,18 @@ def on_reference_action(uid):
830
  current_img,
831
  gr.update(),
832
  gr.update(),
833
- format_log_markdown(f"Ground Truth Action Error: {exc}"),
834
  )
835
 
836
  if not isinstance(reference, dict) or not reference.get("ok", False):
837
- message = "Failed to resolve ground truth action."
838
  if isinstance(reference, dict) and reference.get("message"):
839
  message = str(reference.get("message"))
840
  return (
841
  current_img,
842
  gr.update(),
843
  gr.update(),
844
- format_log_markdown(f"Ground Truth Action: {message}"),
845
  )
846
 
847
  option_idx = reference.get("option_idx")
@@ -851,15 +864,26 @@ def on_reference_action(uid):
851
  coords_xy = reference.get("coords_xy")
852
 
853
  updated_img = current_img
854
- coords_text = "No need for coordinates"
855
- log_text = f"Ground Truth Action: {option_label}. {option_action}".strip()
 
 
 
 
 
856
 
857
  if need_coords and isinstance(coords_xy, (list, tuple)) and len(coords_xy) >= 2:
858
  x = int(coords_xy[0])
859
  y = int(coords_xy[1])
860
  updated_img = draw_marker(current_img, x, y)
861
  coords_text = f"{x}, {y}"
862
- log_text = f"Ground Truth Action: {option_label}. {option_action} | coords: {coords_text}"
 
 
 
 
 
 
863
  LOGGER.debug(
864
  "on_reference_action resolved uid=%s option_idx=%s need_coords=%s coords=%s",
865
  _uid_for_log(uid),
@@ -897,7 +921,7 @@ def init_app(request: gr.Request):
897
  except Exception as e:
898
  LOGGER.exception("init_app exception")
899
  # Return a safe fallback that hides the loading overlay and shows error
900
- return _task_load_failed_response("", f"Initialization error: {e}")
901
 
902
 
903
  def precheck_execute_inputs(uid, option_idx, coords_str):
@@ -911,7 +935,7 @@ def precheck_execute_inputs(uid, option_idx, coords_str):
911
  session = get_session(uid)
912
  if not session:
913
  LOGGER.error("precheck_execute_inputs: missing session uid=%s", _uid_for_log(uid))
914
- raise gr.Error("Session Error")
915
 
916
  parsed_option_idx = option_idx
917
  if isinstance(option_idx, tuple):
@@ -919,7 +943,7 @@ def precheck_execute_inputs(uid, option_idx, coords_str):
919
 
920
  if parsed_option_idx is None:
921
  LOGGER.debug("precheck_execute_inputs uid=%s missing option", _uid_for_log(uid))
922
- raise gr.Error("Error: No action selected")
923
 
924
  needs_coords = False
925
  if (
@@ -936,7 +960,7 @@ def precheck_execute_inputs(uid, option_idx, coords_str):
936
  parsed_option_idx,
937
  coords_str,
938
  )
939
- raise gr.Error("please click the keypoint selection image before execute!")
940
  LOGGER.debug(
941
  "precheck_execute_inputs passed uid=%s option=%s needs_coords=%s",
942
  _uid_for_log(uid),
@@ -971,7 +995,14 @@ def execute_step(uid, option_idx, coords_str):
971
  session = get_session(uid)
972
  if not session:
973
  LOGGER.error("execute_step missing session uid=%s", _uid_for_log(uid))
974
- return None, format_log_markdown("Session Error"), gr.update(), gr.update(), gr.update(interactive=False), gr.update(interactive=False)
 
 
 
 
 
 
 
975
 
976
  # 检查 execute 次数限制(在执行前检查,如果达到限制则模拟失败状态)
977
  execute_limit_reached = False
@@ -1002,7 +1033,14 @@ def execute_step(uid, option_idx, coords_str):
1002
 
1003
  if option_idx is None:
1004
  LOGGER.debug("execute_step uid=%s aborted: option_idx is None", _uid_for_log(uid))
1005
- return session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW), format_log_markdown("Error: No action selected"), gr.update(), gr.update(), gr.update(interactive=False), gr.update(interactive=True)
 
 
 
 
 
 
 
1006
 
1007
  # 检查当前选项是否需要坐标
1008
  needs_coords = False
@@ -1013,21 +1051,7 @@ def execute_step(uid, option_idx, coords_str):
1013
 
1014
  # 如果选项需要坐标,检查是否已经点击了图片
1015
  if needs_coords:
1016
- # 检查 coords_str 是否是有效的坐标(不是提示信息)
1017
- is_valid_coords = False
1018
- if coords_str and "," in coords_str:
1019
- try:
1020
- parts = coords_str.split(",")
1021
- x = int(parts[0].strip())
1022
- y = int(parts[1].strip())
1023
- # 如果成功解析为数字,且不是提示信息,则认为是有效坐标
1024
- if coords_str.strip() not in ["please click the keypoint selection image", "No need for coordinates"]:
1025
- is_valid_coords = True
1026
- except:
1027
- pass
1028
-
1029
- # 如果需要坐标但没有有效坐标,返回错误提示
1030
- if not is_valid_coords:
1031
  LOGGER.debug(
1032
  "execute_step uid=%s option=%s missing valid coords, coords_str=%s",
1033
  _uid_for_log(uid),
@@ -1035,7 +1059,7 @@ def execute_step(uid, option_idx, coords_str):
1035
  coords_str,
1036
  )
1037
  current_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW)
1038
- error_msg = "please click the keypoint selection image before execute!"
1039
  return current_img, format_log_markdown(error_msg), gr.update(), gr.update(), gr.update(interactive=False), gr.update(interactive=True)
1040
 
1041
  # Parse coords
@@ -1143,9 +1167,9 @@ def execute_step(uid, option_idx, coords_str):
1143
  # Episode完成时,格式化System Log的状态消息
1144
  # 使用固定模板,所有行长度一致(32个字符),无空行
1145
  if final_log_status == "success":
1146
- status = "********************************\n**** episode success ****\n********************************\n ---please press change episode---- "
1147
  else:
1148
- status = "********************************\n**** episode failed ****\n********************************\n ---please press change episode---- "
1149
 
1150
  # 更新累计完成计数,不再推进固定任务索引
1151
  if uid:
 
36
  KEYFRAME_DOWNSAMPLE_FACTOR,
37
  LIVE_OBS_REFRESH_HZ,
38
  SESSION_TIMEOUT,
39
+ UI_TEXT,
40
  USE_SEGMENTED_VIEW,
41
  should_show_demo_video,
42
  )
 
51
  LOGGER = logging.getLogger("robomme.callbacks")
52
 
53
 
54
+ def _ui_text(section, key, **kwargs):
55
+ template = UI_TEXT[section][key]
56
+ return template.format(**kwargs) if kwargs else template
57
+
58
+
59
  def _should_enqueue_sample(sample_index: int) -> bool:
60
  factor = max(1, int(KEYFRAME_DOWNSAMPLE_FACTOR))
61
  return sample_index % factor == 0
 
204
  Called when the demonstration video finishes playing.
205
  Updates the system log to prompt for action selection.
206
  """
207
+ return format_log_markdown(_ui_text("log", "action_selection_prompt"))
208
 
209
 
210
  def switch_to_execute_phase(uid):
 
388
  gr.update(visible=False), # video_phase_group
389
  gr.update(visible=True), # action_phase_group
390
  gr.update(visible=True), # control_panel_group
391
+ format_log_markdown(_ui_text("log", "action_selection_prompt"))
392
  )
393
 
394
 
 
401
  format_log_markdown(message), # log_output
402
  gr.update(choices=[], value=None), # options_radio
403
  "", # goal_box
404
+ _ui_text("coords", "not_needed"), # coords_box
405
  gr.update(value=None, visible=False), # video_display
406
  "", # task_info_box
407
  "", # progress_info_box
 
421
  """Load status.current_task to session and build the standard UI update tuple."""
422
  current_task = status.get("current_task") if isinstance(status, dict) else None
423
  if not current_task:
424
+ return _task_load_failed_response(uid, _ui_text("errors", "load_missing_task"))
425
 
426
  env_id = current_task.get("env_id")
427
  ep_num = current_task.get("episode_idx")
428
  if env_id is None or ep_num is None:
429
+ return _task_load_failed_response(uid, _ui_text("errors", "load_invalid_task"))
430
 
431
  try:
432
  completed_count = int(status.get("completed_count", 0))
 
478
  uid,
479
  gr.update(visible=True), # main_interface
480
  gr.update(value=None, interactive=False), # img_display
481
+ format_log_markdown(_ui_text("errors", "load_episode_error", load_msg=load_msg)), # log_output
482
  gr.update(choices=[], value=None), # options_radio
483
  "", # goal_box
484
+ _ui_text("coords", "not_needed"), # coords_box
485
  gr.update(value=None, visible=False), # video_display
486
  f"{actual_env_id} (Episode {ep_num})", # task_info_box
487
  progress_text, # progress_info_box
 
524
  demo_video_path = None
525
  has_demo_video = False
526
  should_show = should_show_demo_video(actual_env_id) if actual_env_id else False
527
+ initial_log_msg = format_log_markdown(_ui_text("log", "action_selection_prompt"))
528
 
529
  if should_show:
530
  has_demo_video = True
531
+ initial_log_msg = format_log_markdown(_ui_text("log", "demo_video_prompt"))
532
  if session.demonstration_frames:
533
  try:
534
  demo_video_path = save_video(session.demonstration_frames, "demo")
 
559
  initial_log_msg, # log_output
560
  gr.update(choices=radio_choices, value=None), # options_radio
561
  goal_text, # goal_box
562
+ _ui_text("coords", "not_needed"), # coords_box
563
  gr.update(value=demo_video_path, visible=True), # video_display
564
  f"{actual_env_id} (Episode {ep_num})", # task_info_box
565
  progress_text, # progress_info_box
 
583
  initial_log_msg, # log_output
584
  gr.update(choices=radio_choices, value=None), # options_radio
585
  goal_text, # goal_box
586
+ _ui_text("coords", "not_needed"), # coords_box
587
  gr.update(value=None, visible=False), # video_display (no video)
588
  f"{actual_env_id} (Episode {ep_num})", # task_info_box
589
  progress_text, # progress_info_box
 
636
  LOGGER.info("load_next_task_wrapper uid=%s", _uid_for_log(uid))
637
  status = user_manager.next_episode_same_env(uid)
638
  if not status:
639
+ return _task_load_failed_response(uid, _ui_text("errors", "next_task_failed"))
640
  return _load_status_task(uid, status)
641
 
642
 
 
653
  status = user_manager.get_session_status(uid)
654
  current_task = status.get("current_task") if isinstance(status, dict) else None
655
  if not current_task:
656
+ return _task_load_failed_response(uid, _ui_text("errors", "restart_missing_task"))
657
 
658
  env_id = current_task.get("env_id")
659
  ep_num = current_task.get("episode_idx")
660
  if env_id is None or ep_num is None:
661
+ return _task_load_failed_response(uid, _ui_text("errors", "restart_invalid_task"))
662
 
663
  return _load_status_task(uid, status)
664
 
 
683
  status = user_manager.get_session_status(uid)
684
 
685
  if not status:
686
+ return _task_load_failed_response(
687
+ uid,
688
+ _ui_text("errors", "switch_env_failed", selected_env=selected_env),
689
+ )
690
 
691
  return _load_status_task(uid, status)
692
 
 
702
  session = get_session(uid)
703
  if not session:
704
  LOGGER.warning("on_map_click: missing session uid=%s", _uid_for_log(uid))
705
+ return None, _ui_text("log", "session_error")
706
 
707
  # Check if current option actually needs coordinates
708
  needs_coords = False
 
729
  # Return current state without changes (or reset to default message if needed, but it should already be there)
730
  # We return the clean image and the "No need" message to enforce state
731
  base_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW)
732
+ return base_img, _ui_text("coords", "not_needed")
733
 
734
  x, y = evt.index[0], evt.index[1]
735
  LOGGER.debug(
 
755
  if not isinstance(coords_text, str):
756
  return False
757
  text = coords_text.strip()
758
+ if text in {
759
+ "",
760
+ _ui_text("coords", "select_keypoint"),
761
+ _ui_text("coords", "not_needed"),
762
+ }:
763
  return False
764
  if "," not in text:
765
  return False
 
776
  """
777
  处理选项选择事件
778
  """
779
+ default_msg = _ui_text("coords", "not_needed")
780
 
781
  if option_value is None:
782
  LOGGER.debug("on_option_select uid=%s option=None", _uid_for_log(uid))
 
809
  )
810
  if _is_valid_coords_text(coords_str):
811
  return coords_str, gr.update(interactive=True)
812
+ return _ui_text("coords", "select_keypoint"), gr.update(interactive=True)
813
 
814
  LOGGER.debug("on_option_select uid=%s option=%s requires_coords=False", _uid_for_log(uid), option_idx)
815
  return default_msg, gr.update(interactive=False)
 
828
  return (
829
  None,
830
  gr.update(),
831
+ _ui_text("coords", "not_needed"),
832
+ format_log_markdown(_ui_text("log", "session_error")),
833
  )
834
 
835
  LOGGER.info("on_reference_action uid=%s env=%s", _uid_for_log(uid), getattr(session, "env_id", None))
 
843
  current_img,
844
  gr.update(),
845
  gr.update(),
846
+ format_log_markdown(_ui_text("log", "reference_action_error", error=exc)),
847
  )
848
 
849
  if not isinstance(reference, dict) or not reference.get("ok", False):
850
+ message = _ui_text("errors", "reference_action_resolve_failed")
851
  if isinstance(reference, dict) and reference.get("message"):
852
  message = str(reference.get("message"))
853
  return (
854
  current_img,
855
  gr.update(),
856
  gr.update(),
857
+ format_log_markdown(_ui_text("log", "reference_action_status", message=message)),
858
  )
859
 
860
  option_idx = reference.get("option_idx")
 
864
  coords_xy = reference.get("coords_xy")
865
 
866
  updated_img = current_img
867
+ coords_text = _ui_text("coords", "not_needed")
868
+ log_text = _ui_text(
869
+ "log",
870
+ "reference_action_message",
871
+ option_label=option_label,
872
+ option_action=option_action,
873
+ ).strip()
874
 
875
  if need_coords and isinstance(coords_xy, (list, tuple)) and len(coords_xy) >= 2:
876
  x = int(coords_xy[0])
877
  y = int(coords_xy[1])
878
  updated_img = draw_marker(current_img, x, y)
879
  coords_text = f"{x}, {y}"
880
+ log_text = _ui_text(
881
+ "log",
882
+ "reference_action_message_with_coords",
883
+ option_label=option_label,
884
+ option_action=option_action,
885
+ coords_text=coords_text,
886
+ )
887
  LOGGER.debug(
888
  "on_reference_action resolved uid=%s option_idx=%s need_coords=%s coords=%s",
889
  _uid_for_log(uid),
 
921
  except Exception as e:
922
  LOGGER.exception("init_app exception")
923
  # Return a safe fallback that hides the loading overlay and shows error
924
+ return _task_load_failed_response("", _ui_text("errors", "init_failed", error=e))
925
 
926
 
927
  def precheck_execute_inputs(uid, option_idx, coords_str):
 
935
  session = get_session(uid)
936
  if not session:
937
  LOGGER.error("precheck_execute_inputs: missing session uid=%s", _uid_for_log(uid))
938
+ raise gr.Error(_ui_text("log", "session_error"))
939
 
940
  parsed_option_idx = option_idx
941
  if isinstance(option_idx, tuple):
 
943
 
944
  if parsed_option_idx is None:
945
  LOGGER.debug("precheck_execute_inputs uid=%s missing option", _uid_for_log(uid))
946
+ raise gr.Error(_ui_text("log", "execute_missing_action"))
947
 
948
  needs_coords = False
949
  if (
 
960
  parsed_option_idx,
961
  coords_str,
962
  )
963
+ raise gr.Error(_ui_text("coords", "select_keypoint_before_execute"))
964
  LOGGER.debug(
965
  "precheck_execute_inputs passed uid=%s option=%s needs_coords=%s",
966
  _uid_for_log(uid),
 
995
  session = get_session(uid)
996
  if not session:
997
  LOGGER.error("execute_step missing session uid=%s", _uid_for_log(uid))
998
+ return (
999
+ None,
1000
+ format_log_markdown(_ui_text("log", "session_error")),
1001
+ gr.update(),
1002
+ gr.update(),
1003
+ gr.update(interactive=False),
1004
+ gr.update(interactive=False),
1005
+ )
1006
 
1007
  # 检查 execute 次数限制(在执行前检查,如果达到限制则模拟失败状态)
1008
  execute_limit_reached = False
 
1033
 
1034
  if option_idx is None:
1035
  LOGGER.debug("execute_step uid=%s aborted: option_idx is None", _uid_for_log(uid))
1036
+ return (
1037
+ session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW),
1038
+ format_log_markdown(_ui_text("log", "execute_missing_action")),
1039
+ gr.update(),
1040
+ gr.update(),
1041
+ gr.update(interactive=False),
1042
+ gr.update(interactive=True),
1043
+ )
1044
 
1045
  # 检查当前选项是否需要坐标
1046
  needs_coords = False
 
1051
 
1052
  # 如果选项需要坐标,检查是否已经点击了图片
1053
  if needs_coords:
1054
+ if not _is_valid_coords_text(coords_str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1055
  LOGGER.debug(
1056
  "execute_step uid=%s option=%s missing valid coords, coords_str=%s",
1057
  _uid_for_log(uid),
 
1059
  coords_str,
1060
  )
1061
  current_img = session.get_pil_image(use_segmented=USE_SEGMENTED_VIEW)
1062
+ error_msg = _ui_text("coords", "select_keypoint_before_execute")
1063
  return current_img, format_log_markdown(error_msg), gr.update(), gr.update(), gr.update(interactive=False), gr.update(interactive=True)
1064
 
1065
  # Parse coords
 
1167
  # Episode完成时,格式化System Log的状态消息
1168
  # 使用固定模板,所有行长度一致(32个字符),无空行
1169
  if final_log_status == "success":
1170
+ status = _ui_text("log", "episode_success_banner")
1171
  else:
1172
+ status = _ui_text("log", "episode_failed_banner")
1173
 
1174
  # 更新累计完成计数,不再推进固定任务索引
1175
  if uid:
gradio-web/test/test_episode98_removed_behavior.py CHANGED
@@ -4,6 +4,7 @@ import time
4
 
5
 
6
  def test_load_next_task_wrapper_treats_episode98_as_normal(monkeypatch, reload_module):
 
7
  callbacks = reload_module("gradio_callbacks")
8
 
9
  expected = ("SENTINEL",)
@@ -22,6 +23,7 @@ def test_load_next_task_wrapper_treats_episode98_as_normal(monkeypatch, reload_m
22
 
23
 
24
  def test_restart_episode_wrapper_reloads_same_episode(monkeypatch, reload_module):
 
25
  callbacks = reload_module("gradio_callbacks")
26
 
27
  load_calls = []
@@ -48,6 +50,7 @@ def test_restart_episode_wrapper_reloads_same_episode(monkeypatch, reload_module
48
 
49
 
50
  def test_restart_episode_wrapper_missing_status_returns_login_failed(monkeypatch, reload_module):
 
51
  callbacks = reload_module("gradio_callbacks")
52
 
53
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
@@ -55,10 +58,11 @@ def test_restart_episode_wrapper_missing_status_returns_login_failed(monkeypatch
55
 
56
  result = callbacks.restart_episode_wrapper("uid1")
57
 
58
- assert "Failed to restart episode" in result[3]
59
 
60
 
61
  def test_execute_step_failed_episode98_still_advances(monkeypatch, reload_module):
 
62
  callbacks = reload_module("gradio_callbacks")
63
 
64
  class _FakeSession:
@@ -99,7 +103,7 @@ def test_execute_step_failed_episode98_still_advances(monkeypatch, reload_module
99
 
100
  monkeypatch.setattr(callbacks.user_manager, "complete_current_task", _fake_complete_current_task)
101
 
102
- result = callbacks.execute_step("uid1", 0, "No need for coordinates")
103
 
104
  assert len(complete_calls) == 1
105
  assert complete_calls[0]["episode_idx"] == 98
 
4
 
5
 
6
  def test_load_next_task_wrapper_treats_episode98_as_normal(monkeypatch, reload_module):
7
+ reload_module("config")
8
  callbacks = reload_module("gradio_callbacks")
9
 
10
  expected = ("SENTINEL",)
 
23
 
24
 
25
  def test_restart_episode_wrapper_reloads_same_episode(monkeypatch, reload_module):
26
+ reload_module("config")
27
  callbacks = reload_module("gradio_callbacks")
28
 
29
  load_calls = []
 
50
 
51
 
52
  def test_restart_episode_wrapper_missing_status_returns_login_failed(monkeypatch, reload_module):
53
+ config = reload_module("config")
54
  callbacks = reload_module("gradio_callbacks")
55
 
56
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
 
58
 
59
  result = callbacks.restart_episode_wrapper("uid1")
60
 
61
+ assert config.UI_TEXT["errors"]["restart_missing_task"] in result[3]
62
 
63
 
64
  def test_execute_step_failed_episode98_still_advances(monkeypatch, reload_module):
65
+ config = reload_module("config")
66
  callbacks = reload_module("gradio_callbacks")
67
 
68
  class _FakeSession:
 
103
 
104
  monkeypatch.setattr(callbacks.user_manager, "complete_current_task", _fake_complete_current_task)
105
 
106
+ result = callbacks.execute_step("uid1", 0, config.UI_TEXT["coords"]["not_needed"])
107
 
108
  assert len(complete_calls) == 1
109
  assert complete_calls[0]["episode_idx"] == 98
gradio-web/test/test_precheck_execute_inputs.py CHANGED
@@ -9,30 +9,33 @@ class _FakeSession:
9
 
10
 
11
  def test_precheck_execute_inputs_requires_action(monkeypatch, reload_module):
 
12
  callbacks = reload_module("gradio_callbacks")
13
  monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeSession(available=False))
14
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
15
 
16
  with pytest.raises(Exception) as excinfo:
17
- callbacks.precheck_execute_inputs("uid-1", None, "No need for coordinates")
18
 
19
- assert "No action selected" in str(excinfo.value)
20
 
21
 
22
  def test_precheck_execute_inputs_requires_coords_when_option_needs_it(monkeypatch, reload_module):
 
23
  callbacks = reload_module("gradio_callbacks")
24
  monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeSession(available=True))
25
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
26
 
27
  with pytest.raises(Exception) as excinfo:
28
  callbacks.precheck_execute_inputs(
29
- "uid-1", 0, "please click the keypoint selection image"
30
  )
31
 
32
- assert "before execute" in str(excinfo.value)
33
 
34
 
35
  def test_precheck_execute_inputs_accepts_valid_coords(monkeypatch, reload_module):
 
36
  callbacks = reload_module("gradio_callbacks")
37
  monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeSession(available=True))
38
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
@@ -43,6 +46,7 @@ def test_precheck_execute_inputs_accepts_valid_coords(monkeypatch, reload_module
43
 
44
 
45
  def test_precheck_execute_inputs_session_error(monkeypatch, reload_module):
 
46
  callbacks = reload_module("gradio_callbacks")
47
  monkeypatch.setattr(callbacks, "get_session", lambda uid: None)
48
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
@@ -50,4 +54,4 @@ def test_precheck_execute_inputs_session_error(monkeypatch, reload_module):
50
  with pytest.raises(Exception) as excinfo:
51
  callbacks.precheck_execute_inputs("uid-missing", 0, "1, 2")
52
 
53
- assert "Session Error" in str(excinfo.value)
 
9
 
10
 
11
  def test_precheck_execute_inputs_requires_action(monkeypatch, reload_module):
12
+ config = reload_module("config")
13
  callbacks = reload_module("gradio_callbacks")
14
  monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeSession(available=False))
15
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
16
 
17
  with pytest.raises(Exception) as excinfo:
18
+ callbacks.precheck_execute_inputs("uid-1", None, config.UI_TEXT["coords"]["not_needed"])
19
 
20
+ assert config.UI_TEXT["log"]["execute_missing_action"] in str(excinfo.value)
21
 
22
 
23
  def test_precheck_execute_inputs_requires_coords_when_option_needs_it(monkeypatch, reload_module):
24
+ config = reload_module("config")
25
  callbacks = reload_module("gradio_callbacks")
26
  monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeSession(available=True))
27
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
28
 
29
  with pytest.raises(Exception) as excinfo:
30
  callbacks.precheck_execute_inputs(
31
+ "uid-1", 0, config.UI_TEXT["coords"]["select_keypoint"]
32
  )
33
 
34
+ assert config.UI_TEXT["coords"]["select_keypoint_before_execute"] in str(excinfo.value)
35
 
36
 
37
  def test_precheck_execute_inputs_accepts_valid_coords(monkeypatch, reload_module):
38
+ reload_module("config")
39
  callbacks = reload_module("gradio_callbacks")
40
  monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeSession(available=True))
41
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
 
46
 
47
 
48
  def test_precheck_execute_inputs_session_error(monkeypatch, reload_module):
49
+ config = reload_module("config")
50
  callbacks = reload_module("gradio_callbacks")
51
  monkeypatch.setattr(callbacks, "get_session", lambda uid: None)
52
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
 
54
  with pytest.raises(Exception) as excinfo:
55
  callbacks.precheck_execute_inputs("uid-missing", 0, "1, 2")
56
 
57
+ assert config.UI_TEXT["log"]["session_error"] in str(excinfo.value)
gradio-web/test/test_reference_action_callbacks.py CHANGED
@@ -1,4 +1,5 @@
1
  from __future__ import annotations
 
2
  from PIL import Image
3
 
4
 
@@ -20,6 +21,7 @@ class _FakeOptionSession:
20
 
21
 
22
  def test_on_reference_action_success_updates_option_and_coords(monkeypatch, reload_module):
 
23
  callbacks = reload_module("gradio_callbacks")
24
 
25
  session = _FakeSession(
@@ -43,10 +45,16 @@ def test_on_reference_action_success_updates_option_and_coords(monkeypatch, relo
43
  assert img.getpixel((5, 6)) != (0, 0, 0)
44
  assert option_update.get("value") == 2
45
  assert coords_text == "5, 6"
46
- assert "Ground Truth Action" in log_html
 
 
 
 
 
47
 
48
 
49
  def test_on_reference_action_session_missing(monkeypatch, reload_module):
 
50
  callbacks = reload_module("gradio_callbacks")
51
 
52
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
@@ -56,11 +64,12 @@ def test_on_reference_action_session_missing(monkeypatch, reload_module):
56
 
57
  assert img is None
58
  assert option_update.get("__type__") == "update"
59
- assert coords_text == "No need for coordinates"
60
- assert "Session Error" in log_html
61
 
62
 
63
  def test_on_reference_action_error_message_from_reference(monkeypatch, reload_module):
 
64
  callbacks = reload_module("gradio_callbacks")
65
 
66
  session = _FakeSession({"ok": False, "message": "bad ref"})
@@ -68,10 +77,11 @@ def test_on_reference_action_error_message_from_reference(monkeypatch, reload_mo
68
  monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
69
 
70
  _img, _opt, _coords, log_html = callbacks.on_reference_action("uid-1")
71
- assert "bad ref" in log_html
72
 
73
 
74
  def test_on_option_select_keeps_valid_coords_when_option_needs_coords(monkeypatch, reload_module):
 
75
  callbacks = reload_module("gradio_callbacks")
76
 
77
  session = _FakeOptionSession()
 
1
  from __future__ import annotations
2
+
3
  from PIL import Image
4
 
5
 
 
21
 
22
 
23
  def test_on_reference_action_success_updates_option_and_coords(monkeypatch, reload_module):
24
+ config = reload_module("config")
25
  callbacks = reload_module("gradio_callbacks")
26
 
27
  session = _FakeSession(
 
45
  assert img.getpixel((5, 6)) != (0, 0, 0)
46
  assert option_update.get("value") == 2
47
  assert coords_text == "5, 6"
48
+ expected_log = config.UI_TEXT["log"]["reference_action_message_with_coords"].format(
49
+ option_label="c",
50
+ option_action="press the button",
51
+ coords_text="5, 6",
52
+ )
53
+ assert log_html == expected_log
54
 
55
 
56
  def test_on_reference_action_session_missing(monkeypatch, reload_module):
57
+ config = reload_module("config")
58
  callbacks = reload_module("gradio_callbacks")
59
 
60
  monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
 
64
 
65
  assert img is None
66
  assert option_update.get("__type__") == "update"
67
+ assert coords_text == config.UI_TEXT["coords"]["not_needed"]
68
+ assert log_html == config.UI_TEXT["log"]["session_error"]
69
 
70
 
71
  def test_on_reference_action_error_message_from_reference(monkeypatch, reload_module):
72
+ config = reload_module("config")
73
  callbacks = reload_module("gradio_callbacks")
74
 
75
  session = _FakeSession({"ok": False, "message": "bad ref"})
 
77
  monkeypatch.setattr(callbacks, "get_session", lambda uid: session)
78
 
79
  _img, _opt, _coords, log_html = callbacks.on_reference_action("uid-1")
80
+ assert log_html == config.UI_TEXT["log"]["reference_action_status"].format(message="bad ref")
81
 
82
 
83
  def test_on_option_select_keeps_valid_coords_when_option_needs_coords(monkeypatch, reload_module):
84
+ reload_module("config")
85
  callbacks = reload_module("gradio_callbacks")
86
 
87
  session = _FakeOptionSession()
gradio-web/test/test_ui_text_config.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+
6
+ class _FakeOptionSession:
7
+ def __init__(self):
8
+ self.raw_solve_options = [{"available": True}]
9
+
10
+
11
+ def test_on_option_select_uses_configured_select_keypoint_message(monkeypatch, reload_module):
12
+ reload_module("config")
13
+ callbacks = reload_module("gradio_callbacks")
14
+
15
+ monkeypatch.setitem(callbacks.UI_TEXT["coords"], "select_keypoint", "pick a point from config")
16
+ monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
17
+ monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeOptionSession())
18
+
19
+ coords_text, img_update = callbacks.on_option_select("uid-1", 0, None)
20
+
21
+ assert coords_text == "pick a point from config"
22
+ assert img_update.get("interactive") is True
23
+
24
+
25
+ def test_precheck_execute_inputs_uses_configured_before_execute_message(monkeypatch, reload_module):
26
+ reload_module("config")
27
+ callbacks = reload_module("gradio_callbacks")
28
+
29
+ monkeypatch.setitem(callbacks.UI_TEXT["coords"], "select_keypoint", "pick a point from config")
30
+ monkeypatch.setitem(
31
+ callbacks.UI_TEXT["coords"],
32
+ "select_keypoint_before_execute",
33
+ "pick a point before execute from config",
34
+ )
35
+ monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
36
+ monkeypatch.setattr(callbacks, "get_session", lambda uid: _FakeOptionSession())
37
+
38
+ with pytest.raises(Exception) as excinfo:
39
+ callbacks.precheck_execute_inputs("uid-1", 0, "pick a point from config")
40
+
41
+ assert "pick a point before execute from config" in str(excinfo.value)
42
+
43
+
44
+ def test_on_video_end_transition_uses_configured_action_prompt(monkeypatch, reload_module):
45
+ reload_module("config")
46
+ callbacks = reload_module("gradio_callbacks")
47
+
48
+ monkeypatch.setitem(callbacks.UI_TEXT["log"], "action_selection_prompt", "choose an action from config")
49
+
50
+ result = callbacks.on_video_end_transition("uid-1")
51
+
52
+ assert result[3] == "choose an action from config"
53
+
54
+
55
+ def test_missing_session_paths_use_configured_session_error(monkeypatch, reload_module):
56
+ reload_module("config")
57
+ callbacks = reload_module("gradio_callbacks")
58
+
59
+ monkeypatch.setitem(callbacks.UI_TEXT["log"], "session_error", "Session Error From Config")
60
+ monkeypatch.setattr(callbacks, "update_session_activity", lambda uid: None)
61
+ monkeypatch.setattr(callbacks, "get_session", lambda uid: None)
62
+
63
+ _img, _option_update, coords_text, log_text = callbacks.on_reference_action("uid-missing")
64
+ map_img, map_text = callbacks.on_map_click("uid-missing", None, None)
65
+
66
+ assert coords_text == callbacks.UI_TEXT["coords"]["not_needed"]
67
+ assert log_text == "Session Error From Config"
68
+ assert map_img is None
69
+ assert map_text == "Session Error From Config"