"""M08 — UI: HearthNet Gradio dashboard. The UI's strict rule: it NEVER imports a service module directly. All data comes via bus.call() or bus introspection APIs. """ from __future__ import annotations from typing import Any try: import gradio as gr HAS_GRADIO = True except ImportError: HAS_GRADIO = False class UiApp: def __init__(self, bus=None, state_bus=None, config=None, **meta): self._bus = bus self._state_bus = state_bus self._config = config self._meta = meta self._demo = None def build(self) -> Any: """Build and return the Gradio Blocks app.""" if not HAS_GRADIO: raise ImportError("gradio not installed") from hearthnet.ui.tabs.ask import build_ask_tab from hearthnet.ui.tabs.chat import build_chat_tab from hearthnet.ui.tabs.emergency import build_emergency_tab from hearthnet.ui.tabs.files import build_files_tab from hearthnet.ui.tabs.getting_started import build_getting_started_tab from hearthnet.ui.tabs.marketplace import build_marketplace_tab from hearthnet.ui.tabs.mesh import build_mesh_tab from hearthnet.ui.tabs.settings import build_settings_tab # Pull identity from bus when not explicitly provided in meta if self._bus is not None: self._meta.setdefault("node_id", getattr(self._bus, "node_id_full", "unknown")) self._meta.setdefault("community_id", getattr(self._bus, "community_id", "unknown")) node_id_display = self._meta.get("node_id", "unknown") display_name = self._meta.get("display_name", node_id_display[:20]) with gr.Blocks(title=f"HearthNet — {display_name}") as demo: gr.Markdown(f"# 🔥 HearthNet — {display_name}") with gr.Row(): gr.HTML(value="● ONLINE") gr.Markdown(f"Node: `{node_id_display[:40]}`") gr.Markdown(f"Community: `{self._meta.get('community_id', 'unknown')[:30]}`") with gr.Tabs(): with gr.Tab("Ask"): build_ask_tab(self._bus) with gr.Tab("Chat"): build_chat_tab(self._bus) with gr.Tab("Mesh"): build_mesh_tab(self._bus) with gr.Tab("Marketplace"): build_marketplace_tab(self._bus) with gr.Tab("Files"): build_files_tab(self._bus) with gr.Tab("Emergency"): build_emergency_tab(self._bus, self._state_bus) with gr.Tab("Settings"): build_settings_tab(self._config, self._meta, bus=self._bus) with gr.Tab("Getting Started"): build_getting_started_tab() self._demo = demo return demo async def shutdown(self) -> None: if self._demo: try: self._demo.close() except Exception: pass def build_ui(bus, state_bus=None, config=None, **meta) -> UiApp: """Convenience constructor used by node.py.""" return UiApp(bus=bus, state_bus=state_bus, config=config, **meta)