arkai2025 commited on
Commit
47d7945
ยท
1 Parent(s): 3230cef

refactor(app): extract helper functions for manual action updates

Browse files
Files changed (2) hide show
  1. README.md +4 -0
  2. app.py +176 -25
README.md CHANGED
@@ -10,6 +10,7 @@ pinned: true
10
  license: mit
11
  short_description: Multi-stage MCP-driven advisor fights fires autonomously
12
  tags:
 
13
  - mcp-in-action-track-creative
14
  - agent
15
  - mcp
@@ -188,6 +189,9 @@ uv run python app.py
188
 
189
  # Or plain Python
190
  python -m app
 
 
 
191
  ```
192
 
193
  Visit http://localhost:7860 to start simulating.
 
10
  license: mit
11
  short_description: Multi-stage MCP-driven advisor fights fires autonomously
12
  tags:
13
+ - mcp-1st-birthday
14
  - mcp-in-action-track-creative
15
  - agent
16
  - mcp
 
189
 
190
  # Or plain Python
191
  python -m app
192
+
193
+ # Or use the helper restart script for local runs
194
+ ./restart.sh
195
  ```
196
 
197
  Visit http://localhost:7860 to start simulating.
app.py CHANGED
@@ -775,6 +775,44 @@ def prepare_reset(
775
  return None, None
776
 
777
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
778
  def deploy_at_cell(
779
  x: int,
780
  y: int,
@@ -784,24 +822,12 @@ def deploy_at_cell(
784
  ):
785
  """Deploy unit or fire at specific cell, or remove if unit already exists there."""
786
  service = get_or_create_service(service)
787
- if display_cache is None:
788
- display_cache = _create_display_cache()
789
-
790
- # Get thinking state
791
- is_thinking = service.is_thinking()
792
- thinking_stage = service.get_thinking_stage()
793
 
794
  # Only allow deployment when simulation is actively running (not paused)
795
  if not service.is_running():
796
  gr.Warning("โš ๏ธ Please start the simulation first!")
797
- state = service.get_state()
798
- updates = get_all_button_updates(state)
799
- return [
800
- render_game_result(state.get("status", ""), state.get("after_action_report")),
801
- _get_combined_advisor_messages(service),
802
- service.get_event_log_text(),
803
- render_status_html(state, is_thinking, thinking_stage),
804
- ] + updates + [service, display_cache]
805
 
806
  # Handle fire placement
807
  if selection == "๐Ÿ”ฅ Fire":
@@ -823,19 +849,96 @@ def deploy_at_cell(
823
  error_msg = result.get("message", "Unknown error")
824
  gr.Warning(f"โš ๏ธ {error_msg}")
825
 
826
- state = service.get_state()
827
- updates = get_all_button_updates(state)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
828
 
829
- # Refresh thinking state after action
830
- is_thinking = service.is_thinking()
831
- thinking_stage = service.get_thinking_stage()
832
 
833
- return [
834
- render_game_result(state.get("status", ""), state.get("after_action_report")),
835
- _get_combined_advisor_messages(service),
836
- service.get_event_log_text(),
837
- render_status_html(state, is_thinking, thinking_stage),
838
- ] + updates + [service, display_cache]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
839
 
840
 
841
  def poll_after_action_report(service: Optional[SimulationService]):
@@ -2021,12 +2124,28 @@ def create_app() -> gr.Blocks:
2021
  )
2022
  grid_buttons.append((x, y, btn))
2023
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2024
  # Timers: main simulation refresh + after-action poller
2025
  timer = gr.Timer(value=1.0, active=False)
2026
  report_timer = gr.Timer(value=1.0, active=False)
2027
 
2028
  # Collect all button outputs for updates
2029
  all_buttons = [btn for (_, _, btn) in grid_buttons]
 
2030
 
2031
  # Event handlers for simulation controls
2032
  start_btn.click(
@@ -2111,6 +2230,38 @@ def create_app() -> gr.Blocks:
2111
  inputs=[model_selector, service_state],
2112
  outputs=[service_state],
2113
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2114
  app.load(
2115
  fn=_initialize_session_defaults,
2116
  inputs=[service_state, display_cache_state],
 
775
  return None, None
776
 
777
 
778
+ def _ensure_display_cache_initialized(display_cache: Optional[dict]) -> dict:
779
+ """Guarantee we always have a mutable display cache."""
780
+ return display_cache if display_cache is not None else _create_display_cache()
781
+
782
+
783
+ def _render_manual_action_update(service: SimulationService, display_cache: dict):
784
+ """Return the standard UI updates after a manual grid action."""
785
+ state = service.get_state()
786
+ updates = get_all_button_updates(state)
787
+ is_thinking = service.is_thinking()
788
+ thinking_stage = service.get_thinking_stage()
789
+ return [
790
+ render_game_result(state.get("status", ""), state.get("after_action_report")),
791
+ _get_combined_advisor_messages(service),
792
+ service.get_event_log_text(),
793
+ render_status_html(state, is_thinking, thinking_stage),
794
+ ] + updates + [service, display_cache]
795
+
796
+
797
+ def _normalize_manual_selection(selection: str) -> str:
798
+ """Map flexible text inputs to the radio labels used by the UI."""
799
+ if not selection:
800
+ return "๐Ÿš’ Truck"
801
+ normalized = selection.strip().lower()
802
+ mappings = {
803
+ "truck": "๐Ÿš’ Truck",
804
+ "fire_truck": "๐Ÿš’ Truck",
805
+ "firetruck": "๐Ÿš’ Truck",
806
+ "๐Ÿš’": "๐Ÿš’ Truck",
807
+ "heli": "๐Ÿš Heli",
808
+ "helicopter": "๐Ÿš Heli",
809
+ "๐Ÿš": "๐Ÿš Heli",
810
+ "fire": "๐Ÿ”ฅ Fire",
811
+ "๐Ÿ”ฅ": "๐Ÿ”ฅ Fire",
812
+ }
813
+ return mappings.get(normalized, selection if selection in ["๐Ÿš’ Truck", "๐Ÿš Heli", "๐Ÿ”ฅ Fire"] else "๐Ÿš’ Truck")
814
+
815
+
816
  def deploy_at_cell(
817
  x: int,
818
  y: int,
 
822
  ):
823
  """Deploy unit or fire at specific cell, or remove if unit already exists there."""
824
  service = get_or_create_service(service)
825
+ display_cache = _ensure_display_cache_initialized(display_cache)
 
 
 
 
 
826
 
827
  # Only allow deployment when simulation is actively running (not paused)
828
  if not service.is_running():
829
  gr.Warning("โš ๏ธ Please start the simulation first!")
830
+ return _render_manual_action_update(service, display_cache)
 
 
 
 
 
 
 
831
 
832
  # Handle fire placement
833
  if selection == "๐Ÿ”ฅ Fire":
 
849
  error_msg = result.get("message", "Unknown error")
850
  gr.Warning(f"โš ๏ธ {error_msg}")
851
 
852
+ return _render_manual_action_update(service, display_cache)
853
+
854
+
855
+ def handle_map_deploy(
856
+ selection: str,
857
+ x: int,
858
+ y: int,
859
+ service: Optional[SimulationService],
860
+ display_cache: Optional[dict],
861
+ ):
862
+ """Public API handler to deploy units/fires at a coordinate."""
863
+ normalized = _normalize_manual_selection(selection)
864
+ return deploy_at_cell(
865
+ int(x),
866
+ int(y),
867
+ normalized,
868
+ service,
869
+ display_cache,
870
+ )
871
+
872
+
873
+ def handle_map_remove(
874
+ x: int,
875
+ y: int,
876
+ service: Optional[SimulationService],
877
+ display_cache: Optional[dict],
878
+ ):
879
+ """Public API handler to remove a unit at coordinates."""
880
+ service = get_or_create_service(service)
881
+ display_cache = _ensure_display_cache_initialized(display_cache)
882
 
883
+ if not service.is_running():
884
+ gr.Warning("โš ๏ธ Please start the simulation first!")
885
+ return _render_manual_action_update(service, display_cache)
886
 
887
+ x = int(x)
888
+ y = int(y)
889
+
890
+ if not service.has_unit_at(x, y):
891
+ gr.Warning("โš ๏ธ No unit found at the requested coordinates.")
892
+ return _render_manual_action_update(service, display_cache)
893
+
894
+ result = service.remove_unit(x, y)
895
+ if result.get("status") != "ok":
896
+ error_msg = result.get("message", "Unknown error")
897
+ gr.Warning(f"โš ๏ธ {error_msg}")
898
+
899
+ return _render_manual_action_update(service, display_cache)
900
+
901
+
902
+ def handle_map_move(
903
+ source_x: int,
904
+ source_y: int,
905
+ target_x: int,
906
+ target_y: int,
907
+ service: Optional[SimulationService],
908
+ display_cache: Optional[dict],
909
+ ):
910
+ """Public API handler to move a unit between coordinates."""
911
+ service = get_or_create_service(service)
912
+ display_cache = _ensure_display_cache_initialized(display_cache)
913
+
914
+ if not service.is_running():
915
+ gr.Warning("โš ๏ธ Please start the simulation first!")
916
+ return _render_manual_action_update(service, display_cache)
917
+
918
+ sx, sy = int(source_x), int(source_y)
919
+ tx, ty = int(target_x), int(target_y)
920
+
921
+ if not service.has_unit_at(sx, sy):
922
+ gr.Warning("โš ๏ธ No unit found at the source coordinates.")
923
+ return _render_manual_action_update(service, display_cache)
924
+
925
+ removal = service.remove_unit(sx, sy)
926
+ if removal.get("status") != "ok":
927
+ error_msg = removal.get("message", "Failed to remove unit before moving.")
928
+ gr.Warning(f"โš ๏ธ {error_msg}")
929
+ return _render_manual_action_update(service, display_cache)
930
+
931
+ removed_unit = removal.get("unit") or {}
932
+ unit_type = removed_unit.get("type") or removal.get("removed_unit_type") or "fire_truck"
933
+
934
+ deploy_result = service.deploy_unit(unit_type, tx, ty, "player_move")
935
+ if deploy_result.get("status") != "ok":
936
+ # Attempt to restore the original placement
937
+ service.deploy_unit(unit_type, sx, sy, "player_move_rollback")
938
+ error_msg = deploy_result.get("message", "Failed to deploy unit at target location.")
939
+ gr.Warning(f"โš ๏ธ {error_msg}")
940
+
941
+ return _render_manual_action_update(service, display_cache)
942
 
943
 
944
  def poll_after_action_report(service: Optional[SimulationService]):
 
2124
  )
2125
  grid_buttons.append((x, y, btn))
2126
 
2127
+ # Hidden components that only exist to expose MCP-friendly APIs (kept out of UI)
2128
+ with gr.Column(visible=False):
2129
+ manual_deploy_selection = gr.Textbox(label="deploy_selection", value="๐Ÿš’ Truck")
2130
+ manual_deploy_x = gr.Number(label="deploy_x", value=0, precision=0)
2131
+ manual_deploy_y = gr.Number(label="deploy_y", value=0, precision=0)
2132
+ manual_move_source_x = gr.Number(label="move_source_x", value=0, precision=0)
2133
+ manual_move_source_y = gr.Number(label="move_source_y", value=0, precision=0)
2134
+ manual_move_target_x = gr.Number(label="move_target_x", value=0, precision=0)
2135
+ manual_move_target_y = gr.Number(label="move_target_y", value=0, precision=0)
2136
+ manual_remove_x = gr.Number(label="remove_x", value=0, precision=0)
2137
+ manual_remove_y = gr.Number(label="remove_y", value=0, precision=0)
2138
+ manual_deploy_trigger = gr.Button("manual_deploy_trigger", visible=False)
2139
+ manual_move_trigger = gr.Button("manual_move_trigger", visible=False)
2140
+ manual_remove_trigger = gr.Button("manual_remove_trigger", visible=False)
2141
+
2142
  # Timers: main simulation refresh + after-action poller
2143
  timer = gr.Timer(value=1.0, active=False)
2144
  report_timer = gr.Timer(value=1.0, active=False)
2145
 
2146
  # Collect all button outputs for updates
2147
  all_buttons = [btn for (_, _, btn) in grid_buttons]
2148
+ manual_action_outputs = [result_popup, advisor_display, event_log_display, status_display] + all_buttons + [service_state, display_cache_state]
2149
 
2150
  # Event handlers for simulation controls
2151
  start_btn.click(
 
2230
  inputs=[model_selector, service_state],
2231
  outputs=[service_state],
2232
  )
2233
+
2234
+ # Public MCP endpoints for manual deploy/move/remove without exposing all grid buttons
2235
+ manual_deploy_trigger.click(
2236
+ fn=handle_map_deploy,
2237
+ inputs=[manual_deploy_selection, manual_deploy_x, manual_deploy_y, service_state, display_cache_state],
2238
+ outputs=manual_action_outputs,
2239
+ api_name="deploy_unit_manual",
2240
+ api_visibility="public",
2241
+ )
2242
+
2243
+ manual_move_trigger.click(
2244
+ fn=handle_map_move,
2245
+ inputs=[
2246
+ manual_move_source_x,
2247
+ manual_move_source_y,
2248
+ manual_move_target_x,
2249
+ manual_move_target_y,
2250
+ service_state,
2251
+ display_cache_state,
2252
+ ],
2253
+ outputs=manual_action_outputs,
2254
+ api_name="move_unit_manual",
2255
+ api_visibility="public",
2256
+ )
2257
+
2258
+ manual_remove_trigger.click(
2259
+ fn=handle_map_remove,
2260
+ inputs=[manual_remove_x, manual_remove_y, service_state, display_cache_state],
2261
+ outputs=manual_action_outputs,
2262
+ api_name="remove_unit_manual",
2263
+ api_visibility="public",
2264
+ )
2265
  app.load(
2266
  fn=_initialize_session_defaults,
2267
  inputs=[service_state, display_cache_state],