From 97f770a0e41f19854f80ad5cfbd7c92fa1e1a002 Mon Sep 17 00:00:00 2001 From: Maxime PERALTA Date: Wed, 26 Aug 2026 16:21:38 +0200 Subject: [PATCH] (feature) Add tab support for GUI --- apparun/gui/modules.py | 45 ++++++++----- samples/conf/multi_tab_sample_gui.yaml | 65 +++++++++++++++++++ tests/data/conf/functional_multi_tab_gui.yaml | 16 +++++ tests/end_to_end/test_gui.py | 20 ++++++ 4 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 samples/conf/multi_tab_sample_gui.yaml create mode 100644 tests/data/conf/functional_multi_tab_gui.yaml diff --git a/apparun/gui/modules.py b/apparun/gui/modules.py index c340904..9dd91cb 100644 --- a/apparun/gui/modules.py +++ b/apparun/gui/modules.py @@ -27,6 +27,7 @@ class Module(BaseModel): name: Optional[str] = None + tab: Optional[str] = None lca_data_path: Optional[str] = None impact_model_path: Optional[str] = None input_panel: Optional[InputPanel] = None @@ -105,6 +106,7 @@ def run(self): class GUI(BaseModel): name: Optional[str] = None favicon_path: Optional[str] = None + tabs: Optional[List[str]] = None modules: List[Module] def setup_layout(self): @@ -127,18 +129,7 @@ def setup_layout(self): """ ) - def gen_titles_modules(self): - if self.name is not None: - self.modules.insert( - 0, - Module( - **{ - "output_panels": [ - {"type": "markdown", "message": f"# {self.name}"} - ] - } - ), - ) + def gen_titles_modules(self, tabs): modules_with_titles = [] for module in self.modules: if module.name is None: @@ -149,7 +140,8 @@ def gen_titles_modules(self): **{ "output_panels": [ {"type": "markdown", "message": f"## {module.name}"} - ] + ], + "tab": module.tab, } ) ) @@ -157,8 +149,25 @@ def gen_titles_modules(self): self.modules = modules_with_titles def run(self): - self.gen_titles_modules() - containers = [st.container() for i in range(len(self.modules))] - for i in range(len(self.modules)): - with containers[i]: - self.modules[i].run() + if self.name is not None: + Module( + **{"output_panels": [{"type": "markdown", "message": f"# {self.name}"}]} + ).run() + self.gen_titles_modules(self.tabs) + if self.tabs is not None: + tabs = st.tabs(self.tabs) + for i in range(len(self.tabs)): + tab_name = self.tabs[i] + with tabs[i]: + tab_modules = [ + module for module in self.modules if module.tab == tab_name + ] + containers = [tabs[i].container() for _ in range(len(tab_modules))] + for j in range(len(tab_modules)): + with containers[j]: + tab_modules[j].run() + else: + containers = [st.container() for i in range(len(self.modules))] + for i in range(len(self.modules)): + with containers[i]: + self.modules[i].run() diff --git a/samples/conf/multi_tab_sample_gui.yaml b/samples/conf/multi_tab_sample_gui.yaml new file mode 100644 index 0000000..290918b --- /dev/null +++ b/samples/conf/multi_tab_sample_gui.yaml @@ -0,0 +1,65 @@ +name: "Preconfigured tool for NVIDIA AI GPU" +favicon_path: "https://www.cea.fr/PublishingImages/cea.jpg" +tabs: + - "1st tab" + - "2nd tab" +modules: + - impact_model_path: "samples/impact_models/nvidia_ai_gpu_chip.yaml" + name: "Scenario comparison by phase" + tab: "1st tab" + input_panel: + type: input_scenario_form_panel + name: "GPU parameters" + fields: + - type: float + name: cuda_core + min: 0 + max: 1024 + default: 512 + - type: enum + name: architecture + options: + - Pascal + - Maxwell + - type: float + name: lifespan + min: 0 + max: 5 + default: 2 + - type: enum + name: usage_location + options: + - FR + - EU + output_panels: + - type: scenario_comparison_dynamic_output_panel + y: EFV3_CLIMATE_CHANGE + hue: phase + by_property: "phase" + - name: "Random explanation" + tab: "1st tab" + output_panels: + - type: markdown + message: "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + - impact_model_path: "samples/impact_models/nvidia_ai_gpu_chip.yaml" + name: "Test" + tab: "2nd tab" + input_panel: + type: input_scenario_form_panel + name: "Still GPU parameters but less" + fields: + - type: float + name: cuda_core + min: 0 + max: 1024 + default: 512 + - type: enum + name: architecture + options: + - Pascal + - Maxwell + default: Pascal + output_panels: + - type: scenario_comparison_dynamic_output_panel + y: EFV3_CLIMATE_CHANGE + hue: component diff --git a/tests/data/conf/functional_multi_tab_gui.yaml b/tests/data/conf/functional_multi_tab_gui.yaml new file mode 100644 index 0000000..5c48172 --- /dev/null +++ b/tests/data/conf/functional_multi_tab_gui.yaml @@ -0,0 +1,16 @@ +name: "Preconfigured tool for NVIDIA AI GPU" +favicon_path: "https://www.cea.fr/PublishingImages/cea.jpg" +tabs: + - "1st tab" + - "2nd tab" +modules: + - name: "Random explanation" + tab: "1st tab" + output_panels: + - type: markdown + message: "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + - name: "Random explanation" + tab: "2nd tab" + output_panels: + - type: markdown + message: "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." diff --git a/tests/end_to_end/test_gui.py b/tests/end_to_end/test_gui.py index 613092a..322185e 100644 --- a/tests/end_to_end/test_gui.py +++ b/tests/end_to_end/test_gui.py @@ -16,6 +16,12 @@ def run_test_gui(): generate_gui("tests/data/conf/functional_gui.yaml") +def run_multi_tab_test_gui(): + from apparun.cli.main import generate_gui + + generate_gui("tests/data/conf/functional_multi_tab_gui.yaml") + + def test_streamlit_app_is_deploying(): """ Check that the streamlit app initialized by sample conf is deploying. @@ -27,6 +33,20 @@ def test_streamlit_app_is_deploying(): assert len(at.markdown) == 3 +def test_multi_tab_streamlit_app_is_deploying(): + """ + Check that the streamlit app initialized by sample conf is deploying. + """ + + at = AppTest.from_function(run_multi_tab_test_gui, default_timeout=10) + at.run() + # five md widgets: one title, plus one header and one text block per tab. + assert len(at.tabs) == 2 + assert len(at.tabs[0]) == 2 + assert len(at.tabs[1]) == 2 + assert len(at.markdown) == 5 + + def test_favicons(): """ Check that local and remote favicon can be loaded.