Download shared/engine/app_builder.py from lyzr-ai/analytics: direct link, hf CLI and curl.
- Browser
- Download file 9.21 kB
-
https://huggingface.co/spaces/lyzr-ai/analytics/resolve/main/shared/engine/app_builder.py
- Command line
-
hf download hf://spaces/lyzr-ai/analytics/shared/engine/app_builder.py
-
curl -L -o app_builder.py https://huggingface.co/spaces/lyzr-ai/analytics/resolve/main/shared/engine/app_builder.py
9.21 kB
| """ | |
| App builder engine - builds Gradio apps from configuration. | |
| This module provides a configuration-driven approach to building | |
| demo applications powered by blueprint agent orchestration. | |
| """ | |
| import gradio as gr | |
| from typing import Callable, Any | |
| from dataclasses import dataclass, field | |
| # Support both local and HF Spaces imports | |
| try: | |
| from apps.shared.pages import ListPage, DetailPage, FormPage, DashboardPage | |
| from apps.shared.pages.form_page import create_processing_html | |
| from apps.shared.ui_components import create_footer | |
| except ImportError: | |
| from shared.pages import ListPage, DetailPage, FormPage, DashboardPage | |
| from shared.pages.form_page import create_processing_html | |
| from shared.ui_components import create_footer | |
| class AppConfig: | |
| """Configuration for a demo application.""" | |
| id: str | |
| title: str | |
| description: str | None = None | |
| # Header links | |
| github_url: str | None = None | |
| studio_url: str | None = None | |
| readme_url: str | None = None | |
| # Entity naming | |
| entity_name: str = "item" | |
| entity_name_plural: str = "items" | |
| # Sidebar navigation | |
| sidebar: list[dict] = field(default_factory=list) | |
| # Pages configuration | |
| pages: dict[str, dict] = field(default_factory=dict) | |
| # Data sources | |
| data: dict[str, Any] = field(default_factory=dict) | |
| # Orchestrator for processing | |
| orchestrator: Any = None | |
| class DemoApp: | |
| """ | |
| Demo application builder. | |
| Builds a complete Gradio app from configuration with: | |
| - Sidebar navigation | |
| - Header with links | |
| - Page templates | |
| - Data binding | |
| - Orchestrator integration | |
| """ | |
| PAGE_TEMPLATES = { | |
| "list": ListPage, | |
| "detail": DetailPage, | |
| "form": FormPage, | |
| "dashboard": DashboardPage, | |
| } | |
| def __init__(self, config: AppConfig | dict): | |
| if isinstance(config, dict): | |
| self.config = AppConfig(**config) | |
| else: | |
| self.config = config | |
| self._current_page = "/" | |
| def build(self) -> gr.Blocks: | |
| """Build the complete Gradio application.""" | |
| with gr.Blocks(title=self.config.title) as demo: | |
| # Force light mode | |
| gr.HTML(""" | |
| <script> | |
| document.body.classList.remove('dark'); | |
| document.documentElement.style.colorScheme = 'light'; | |
| </script> | |
| """, visible=False) | |
| # State for current item (for detail views) | |
| current_item = gr.State(None) | |
| # Header | |
| self._build_header() | |
| # Main layout: Sidebar + Content | |
| with gr.Row(): | |
| # Sidebar | |
| with gr.Column(scale=1, min_width=200): | |
| self._build_sidebar() | |
| # Content area | |
| with gr.Column(scale=4): | |
| # Build all pages as separate containers | |
| page_containers = {} | |
| for path, page_config in self.config.pages.items(): | |
| is_home = path == "/" | |
| with gr.Column(visible=is_home, elem_id=f"page-{path.replace('/', '-')}") as container: | |
| self._build_page(path, page_config) | |
| page_containers[path] = container | |
| # Footer | |
| create_footer() | |
| return demo | |
| def _build_header(self) -> None: | |
| """Build app header with title and links.""" | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| gr.Markdown(f"# {self.config.title}") | |
| if self.config.description: | |
| gr.Markdown(f"*{self.config.description}*") | |
| with gr.Column(scale=1, min_width=300): | |
| links_html = '<div style="display: flex; gap: 16px; justify-content: flex-end; align-items: center; padding-top: 8px;">' | |
| if self.config.github_url: | |
| links_html += f'<a href="{self.config.github_url}" target="_blank" style="text-decoration: none; color: #374151; font-size: 14px;">โญ GitHub</a>' | |
| if self.config.studio_url: | |
| links_html += f'<a href="{self.config.studio_url}" target="_blank" style="text-decoration: none; color: #374151; font-size: 14px;">๐ Lyzr Studio</a>' | |
| if self.config.readme_url: | |
| links_html += f'<a href="{self.config.readme_url}" target="_blank" style="text-decoration: none; color: #374151; font-size: 14px;">๐ Readme</a>' | |
| links_html += '</div>' | |
| gr.HTML(links_html) | |
| def _build_sidebar(self) -> None: | |
| """Build sidebar navigation.""" | |
| for item in self.config.sidebar: | |
| icon = item.get("icon", "") | |
| label = item.get("label", "") | |
| gr.Button( | |
| f"{icon} {label}".strip(), | |
| variant="secondary", | |
| size="sm", | |
| ) | |
| def _build_page(self, path: str, page_config: dict) -> None: | |
| """Build a single page from configuration.""" | |
| page_type = page_config.get("page", "list") | |
| variant = page_config.get("variant", "standard") | |
| config = page_config.get("config", {}) | |
| config["variant"] = variant | |
| # Get data for this page | |
| data = self._get_page_data(path, page_config) | |
| if page_type == "list": | |
| template = ListPage(config) | |
| template.render(data) | |
| elif page_type == "detail": | |
| template = DetailPage(config) | |
| if data: | |
| template.render(data) | |
| else: | |
| gr.Markdown("*Select an item to view details*") | |
| elif page_type == "form": | |
| template = FormPage(config) | |
| submit_btn, fields, result_container, processing_html, result_output = template.render() | |
| # Connect form submission to orchestrator | |
| if self.config.orchestrator: | |
| self._connect_form_to_orchestrator( | |
| submit_btn, fields, template, | |
| result_container, processing_html, result_output | |
| ) | |
| elif page_type == "dashboard": | |
| template = DashboardPage(config) | |
| template.render(data) | |
| def _get_page_data(self, path: str, page_config: dict) -> Any: | |
| """Get data for a page from configured data sources.""" | |
| data_key = page_config.get("data_key") | |
| if data_key and data_key in self.config.data: | |
| return self.config.data[data_key] | |
| # Fallback: check if there's mock_data in page config | |
| if "mock_data" in page_config: | |
| return page_config["mock_data"] | |
| return [] | |
| def _connect_form_to_orchestrator( | |
| self, | |
| submit_btn: gr.Button, | |
| fields: dict, | |
| template: FormPage, | |
| result_container: gr.Column, | |
| processing_html: gr.HTML, | |
| result_output: gr.Markdown, | |
| ) -> None: | |
| """Connect form submission to orchestrator processing.""" | |
| def process_form(*field_values): | |
| # Build input dict from field values | |
| field_keys = template.get_field_keys() | |
| input_data = dict(zip(field_keys, field_values)) | |
| # Process through orchestrator | |
| if self.config.orchestrator: | |
| result = self.config.orchestrator.process(input_data) | |
| return ( | |
| gr.update(visible=True), # Show result container | |
| "", # Clear processing (done) | |
| f"### Result\n\n{self._format_result(result)}" | |
| ) | |
| else: | |
| return ( | |
| gr.update(visible=True), | |
| "", | |
| "### Result\n\n*Processing complete (no orchestrator configured)*" | |
| ) | |
| submit_btn.click( | |
| fn=process_form, | |
| inputs=list(fields.values()), | |
| outputs=[result_container, processing_html, result_output], | |
| ) | |
| def _format_result(self, result: dict) -> str: | |
| """Format orchestrator result as markdown.""" | |
| if not result: | |
| return "*No result*" | |
| md = "" | |
| for key, value in result.items(): | |
| label = key.replace("_", " ").title() | |
| if isinstance(value, list): | |
| md += f"**{label}:**\n" | |
| for item in value: | |
| if isinstance(item, dict): | |
| md += f"- {item.get('event', item.get('title', str(item)))}\n" | |
| else: | |
| md += f"- {item}\n" | |
| else: | |
| md += f"**{label}:** {value}\n\n" | |
| return md | |
| def launch(self, **kwargs) -> None: | |
| """Build and launch the app.""" | |
| demo = self.build() | |
| # Use Glass theme with light mode | |
| if "theme" not in kwargs: | |
| kwargs["theme"] = gr.themes.Glass() | |
| demo.launch(**kwargs) | |
| def build_app(config: dict | AppConfig) -> gr.Blocks: | |
| """ | |
| Build a Gradio app from configuration. | |
| Args: | |
| config: App configuration dict or AppConfig instance | |
| Returns: | |
| gr.Blocks instance ready to launch | |
| """ | |
| app = DemoApp(config) | |
| return app.build() | |