From eff7279b0add9b8421702be76052659bc1fa6650 Mon Sep 17 00:00:00 2001 From: burtenshaw Date: Fri, 17 Jul 2026 09:06:52 +0200 Subject: [PATCH] Refactor web interface Gradio block assembly --- src/openenv/core/env_server/web_interface.py | 103 ++++++++++++------- tests/core/test_web_interface_layout.py | 29 ++++++ 2 files changed, 96 insertions(+), 36 deletions(-) diff --git a/src/openenv/core/env_server/web_interface.py b/src/openenv/core/env_server/web_interface.py index b20c08cbc..41e4298e5 100644 --- a/src/openenv/core/env_server/web_interface.py +++ b/src/openenv/core/env_server/web_interface.py @@ -557,47 +557,18 @@ async def web_state(): quick_start_md = get_quick_start_markdown(metadata, action_cls, observation_cls) display_title = title_override or get_gradio_display_title(metadata) - default_blocks = build_gradio_app( + gradio_blocks = _build_gradio_blocks( web_manager, action_fields, metadata, is_chat_env, - title=metadata.name, - quick_start_md=quick_start_md, + quick_start_md, + display_title=display_title, + gradio_builder=gradio_builder, + custom_tab_name=custom_tab_name, + custom_tab_primary=custom_tab_primary, + show_default_tab=show_default_tab, ) - default_blocks.title = display_title - if gradio_builder is not None: - custom_blocks = gradio_builder( - web_manager, - action_fields, - metadata, - is_chat_env, - display_title, - quick_start_md, - ) - if not isinstance(custom_blocks, gr.Blocks): - raise TypeError( - f"gradio_builder must return a gr.Blocks instance, " - f"got {type(custom_blocks).__name__}" - ) - if not show_default_tab: - # No TabbedInterface wrapper to carry the app title. - custom_blocks.title = display_title - gradio_blocks = custom_blocks - else: - if custom_tab_primary: - tab_blocks = [custom_blocks, default_blocks] - tab_labels = [custom_tab_name, "Playground"] - else: - tab_blocks = [default_blocks, custom_blocks] - tab_labels = ["Playground", custom_tab_name] - gradio_blocks = gr.TabbedInterface( - tab_blocks, - tab_names=tab_labels, - title=display_title, - ) - else: - gradio_blocks = default_blocks app = gr.mount_gradio_app( app, gradio_blocks, @@ -609,6 +580,66 @@ async def web_state(): return app +def _build_gradio_blocks( + web_manager: WebInterfaceManager, + action_fields: List[Dict[str, Any]], + metadata: EnvironmentMetadata, + is_chat_env: bool, + quick_start_md: str, + *, + display_title: str, + gradio_builder: Optional[Callable[..., Any]], + custom_tab_name: str, + custom_tab_primary: bool, + show_default_tab: bool, +) -> gr.Blocks: + """Build the Gradio block tree mounted at `/web`.""" + default_blocks = build_gradio_app( + web_manager, + action_fields, + metadata, + is_chat_env, + title=metadata.name, + quick_start_md=quick_start_md, + ) + default_blocks.title = display_title + + if gradio_builder is None: + return default_blocks + + custom_blocks = gradio_builder( + web_manager, + action_fields, + metadata, + is_chat_env, + display_title, + quick_start_md, + ) + if not isinstance(custom_blocks, gr.Blocks): + raise TypeError( + f"gradio_builder must return a gr.Blocks instance, " + f"got {type(custom_blocks).__name__}" + ) + + if not show_default_tab: + # No TabbedInterface wrapper to carry the app title. + custom_blocks.title = display_title + return custom_blocks + + if custom_tab_primary: + tab_blocks = [custom_blocks, default_blocks] + tab_labels = [custom_tab_name, "Playground"] + else: + tab_blocks = [default_blocks, custom_blocks] + tab_labels = ["Playground", custom_tab_name] + + return gr.TabbedInterface( + tab_blocks, + tab_names=tab_labels, + title=display_title, + ) + + def _is_chat_env(action_cls: Type[Action]) -> bool: """Return True if the action class is a chat-style env (tokens field).""" if hasattr(action_cls, "model_fields"): diff --git a/tests/core/test_web_interface_layout.py b/tests/core/test_web_interface_layout.py index 5b818b661..5232d60ec 100644 --- a/tests/core/test_web_interface_layout.py +++ b/tests/core/test_web_interface_layout.py @@ -139,6 +139,35 @@ def builder(web_manager, action_fields, metadata, is_chat_env, title, quick_star assert captured["blocks"].title == "Tabbed Layout" +def test_custom_builder_defaults_to_playground_first(monkeypatch) -> None: + captured = _capture_mounted_blocks(monkeypatch) + + def fake_tabbed_interface(blocks, tab_names, title): + captured["tab_blocks"] = blocks + captured["tab_names"] = tab_names + captured["tab_title"] = title + return gr.Blocks(title=title) + + monkeypatch.setattr(web_interface.gr, "TabbedInterface", fake_tabbed_interface) + + def builder(web_manager, action_fields, metadata, is_chat_env, title, quick_start): + return gr.Blocks(title=title) + + create_web_interface_app( + LayoutEnvironment, + LayoutAction, + LayoutObservation, + env_name="layout_env", + gradio_builder=builder, + custom_tab_name="Custom View", + title_override="Tabbed Layout", + ) + + assert captured["tab_names"] == ["Playground", "Custom View"] + assert captured["tab_title"] == "Tabbed Layout" + assert len(captured["tab_blocks"]) == 2 + + def test_title_override_applies_without_custom_builder(monkeypatch) -> None: captured = _capture_mounted_blocks(monkeypatch)