import importlib
import inspect
import json
import math
import random
import time
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from typing import Any
import gradio as gr
try:
import spaces
except ImportError:
class _SpacesFallback:
@staticmethod
def GPU( # pylint: disable=invalid-name
*_args: object, **_kwargs: object
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
return func
return decorator
spaces = _SpacesFallback()
APP_TITLE = "HearthNet"
APP_SUBTITLE = "Phase 1 browser-mesh coordination, resilient AI assistance, and traceable local-first workflows."
@spaces.GPU(duration=1)
def zero_gpu_startup_probe() -> str:
return "HearthNet ZeroGPU probe ready"
@dataclass
class CoreAdapter:
name: str
module: Any | None
error: str | None = None
@property
def available(self) -> bool:
return self.module is not None
def _load_optional_core() -> CoreAdapter:
candidates = ("hearthnet",)
errors: list[str] = []
for name in candidates:
try:
return CoreAdapter(name=name, module=importlib.import_module(name))
except Exception as exc:
errors.append(f"{name}: {exc.__class__.__name__}")
return CoreAdapter(
name="demo", module=None, error=", ".join(errors) or "No Python core discovered."
)
CORE = _load_optional_core()
NODES = [
{"id": "home-hub", "role": "coordinator", "status": "online", "latency": 12, "load": 42},
{"id": "kitchen-panel", "role": "edge display", "status": "online", "latency": 19, "load": 24},
{"id": "workbench", "role": "rag worker", "status": "online", "latency": 31, "load": 67},
{
"id": "phone-relay",
"role": "emergency uplink",
"status": "standby",
"latency": 48,
"load": 18,
},
{
"id": "market-node",
"role": "local marketplace",
"status": "online",
"latency": 27,
"load": 39,
},
]
TRACE_EVENTS = [
("intent", "Classify request and safety envelope"),
("route", "Select local RAG, mesh peer, or emergency path"),
("retrieve", "Pull local knowledge snippets and trusted marketplace records"),
("synthesize", "Draft answer with cited memory fragments"),
("verify", "Attach confidence, failover state, and operator next step"),
]
def _now() -> str:
return datetime.now().strftime("%H:%M:%S")
def _call_core(names: tuple[str, ...], fallback: Callable[[], Any]) -> Any:
if not CORE.available:
return fallback()
for name in names:
target = getattr(CORE.module, name, None)
if callable(target):
try:
return target()
except Exception:
break
return fallback()
def core_status() -> tuple[str, str]:
if CORE.available:
return (
"Core linked",
f"Using optional Python module `{CORE.name}`. Demo fallbacks remain active for missing hooks.",
)
return (
"Demo mode",
"No optional HearthNet Python core was importable, so this Space is running polished Phase 1 fixtures.",
)
def overview_payload() -> tuple[str, str, str]:
status, detail = core_status()
capabilities = _call_core(
("get_capabilities", "capabilities", "status"),
lambda: {
"mesh": "WebRTC-ready topology model",
"rag": "Local knowledge retrieval demo",
"marketplace": "Neighborhood offer and request board",
"chat": "Operator chat with emergency escalation",
"trace": "Architecture event trail",
},
)
health = {
"phase": "1",
"mode": status,
"nodes": len(NODES),
"online": sum(1 for node in NODES if node["status"] == "online"),
"last_tick": _now(),
}
return detail, json.dumps(health, indent=2), json.dumps(capabilities, indent=2, default=str)
def topology_html(seed: int = 0) -> str:
rnd = random.Random(seed or int(time.time() // 8)) # nosec B311 - visual jitter only.
nodes = []
for index, node in enumerate(NODES):
angle = (index / len(NODES)) * 6.283
jitter = rnd.uniform(-10, 10)
x = 50 + 34 * math.cos(angle) + jitter * 0.12
y = 52 + 30 * math.sin(angle) + jitter * 0.12
pulse = max(8, min(92, int(str(node["load"])) + rnd.randint(-6, 6)))
nodes.append({**node, "x": round(x, 2), "y": round(y, 2), "pulse": pulse})
edges = [
("home-hub", "kitchen-panel"),
("home-hub", "workbench"),
("home-hub", "phone-relay"),
("home-hub", "market-node"),
("workbench", "market-node"),
]
by_id = {node["id"]: node for node in nodes}
lines = "\n".join(
f""
for a, b in edges
)
dot_parts = []
for node in nodes:
node_x = float(str(node["x"]))
node_y = float(str(node["y"]))
dot_parts.append(
f"""
{node["id"]}
{node["latency"]}ms / {node["pulse"]}%
"""
)
dots = "\n".join(dot_parts)
return f"""
"""
def mesh_snapshot(refresh_count: int) -> tuple[str, str, int]:
next_count = refresh_count + 1
rows = []
for node in NODES:
rows.append(
{
"node": node["id"],
"role": node["role"],
"state": node["status"],
"latency_ms": int(str(node["latency"])) + (next_count % 4),
"load_pct": max(
5,
min(95, int(str(node["load"])) + ((next_count % 3) - 1) * 3),
),
}
)
return topology_html(next_count), json.dumps(rows, indent=2), next_count
def rag_answer(question: str, mode: str) -> tuple[str, str]:
question = (question or "").strip()
if not question:
return "Ask a question to run the retrieval demo.", "[]"
snippets = [
{
"source": "mesh.handbook.local",
"score": 0.91,
"text": "Home-hub coordinates browser peers, keeps the trace log, and chooses local fallbacks first.",
},
{
"source": "emergency.playbook.local",
"score": 0.86,
"text": "Emergency mode prioritizes short instructions, phone relay status, and explicit escalation state.",
},
{
"source": "marketplace.cache.local",
"score": 0.78,
"text": "Marketplace cards are signed local offers with expiry, distance, and trust metadata.",
},
]
prefix = "Core-assisted" if CORE.available else "Demo"
answer = (
f"{prefix} {mode.lower()} response for: {question}\n\n"
"HearthNet would answer from local memory first, then ask mesh peers for missing context. "
"For Phase 1 this Space shows the routing contract, citations, and confidence surface without requiring a heavy model."
)
if CORE.available:
responder = getattr(CORE.module, "answer", None) or getattr(CORE.module, "query", None)
if callable(responder):
try:
answer = str(responder(question))
except Exception as exc:
answer += f"\n\nCore hook failed gracefully: {exc.__class__.__name__}."
return answer, json.dumps(snippets, indent=2)
def operator_chat(
message: str, history: list[tuple[str, str]], emergency: bool
) -> tuple[list[tuple[str, str]], str]:
history = history or []
message = (message or "").strip()
if not message:
return history, ""
if emergency:
reply = (
"Emergency mode is active. I would keep this terse: confirm immediate safety, surface the phone relay, "
"and preserve a trace entry for every operator action."
)
elif "market" in message.lower() or "offer" in message.lower():
reply = (
"Marketplace view: 3 local offers match the current household context. "
"Best candidate is `market-node` with fresh trust metadata and a 2 hour expiry."
)
else:
reply = (
"I routed that through the household coordinator. The next Phase 1 action is to retrieve local context, "
"ask one mesh peer for corroboration, then return an auditable answer."
)
history = [*history, (message, reply)]
return history, ""
def marketplace_cards(emergency: bool) -> str:
cards = [
("Power bank", "phone-relay", "available now", "92% trust"),
("Spare router", "workbench", "pickup window 18:00-20:00", "88% trust"),
("First-aid kit", "kitchen-panel", "household verified", "96% trust"),
]
if emergency:
cards.insert(
0, ("Emergency contact relay", "phone-relay", "priority path armed", "verified")
)
return "\n".join(
f"- **{name}** · {node} · {detail} · `{trust}`" for name, node, detail, trust in cards
)
def architecture_trace(intent: str) -> tuple[str, str]:
intent = (intent or "Explain how HearthNet routes a request.").strip()
events = []
for index, (stage, label) in enumerate(TRACE_EVENTS, start=1):
events.append(
{
"t": f"{_now()}.{index:02d}",
"stage": stage,
"event": label,
"input": intent if index == 1 else f"artifact:{TRACE_EVENTS[index - 2][0]}",
"output": f"{stage}.ok",
}
)
diagram = "\n".join(f"{item['stage']:>10} -> {item['output']}" for item in events)
return f"```text\n{diagram}\n```", json.dumps(events, indent=2)
CSS = """
:root {
--hn-bg: #0b0d10;
--hn-panel: #12161b;
--hn-panel-2: #181e24;
--hn-line: #26313b;
--hn-text: #e4e9ee;
--hn-muted: #8c99a5;
--hn-accent: #54d1b6;
--hn-warn: #e0b15f;
}
.gradio-container {
max-width: 1180px !important;
margin: 0 auto !important;
background: var(--hn-bg) !important;
color: var(--hn-text) !important;
}
body, .gradio-container, button, input, textarea {
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
}
#hero {
padding: 26px 0 12px;
border-bottom: 1px solid var(--hn-line);
margin-bottom: 18px;
}
#hero h1 {
margin: 0;
font-size: clamp(34px, 6vw, 68px);
line-height: 0.95;
letter-spacing: 0;
}
#hero p {
max-width: 760px;
color: var(--hn-muted);
font-size: 16px;
}
.hn-kicker {
color: var(--hn-accent);
font: 600 12px ui-monospace, SFMono-Regular, Menlo, monospace;
letter-spacing: .12em;
text-transform: uppercase;
}
.mesh-shell {
height: min(56vw, 520px);
min-height: 340px;
border: 1px solid var(--hn-line);
background: #090c0f;
overflow: hidden;
}
.mesh-shell svg {
width: 100%;
height: 100%;
display: block;
}
.mesh-shell rect {
fill: #090c0f;
}
.mesh-shell line {
stroke: #30404b;
stroke-width: .45;
}
.mesh-shell .node {
fill: #121b22;
stroke: var(--hn-accent);
stroke-width: .55;
}
.mesh-shell .node.standby {
stroke: var(--hn-warn);
}
.mesh-shell text {
fill: var(--hn-text);
font: 2.6px ui-monospace, SFMono-Regular, Menlo, monospace;
}
.mesh-shell text.meta {
fill: var(--hn-muted);
font-size: 2.1px;
}
.wrap, .block, .form {
border-radius: 8px !important;
}
button.primary {
background: var(--hn-accent) !important;
color: #06110f !important;
}
"""
def build_app() -> gr.Blocks:
blocks_kwargs: dict[str, Any] = {"title": APP_TITLE}
blocks_params = inspect.signature(gr.Blocks).parameters
if "css" in blocks_params:
blocks_kwargs["css"] = CSS
if "theme" in blocks_params:
blocks_kwargs["theme"] = gr.themes.Base()
with gr.Blocks(**blocks_kwargs) as demo:
refresh_state = gr.State(0)
gr.HTML(
f"""
Phase 1 Space UI
{APP_TITLE}
{APP_SUBTITLE}
"""
)
with gr.Tabs():
with gr.Tab("Overview"):
gr.Markdown("### System Posture")
with gr.Row():
core_detail = gr.Markdown()
health_json = gr.Code(label="Health", language="json")
capabilities_json = gr.Code(label="Capabilities", language="json")
overview_btn = gr.Button("Refresh Overview", variant="primary")
overview_btn.click(
overview_payload, outputs=[core_detail, health_json, capabilities_json]
)
with gr.Tab("Live Mesh"):
mesh_html = gr.HTML()
with gr.Row():
mesh_json = gr.Code(label="Peer Snapshot", language="json")
mesh_btn = gr.Button("Tick Topology", variant="primary")
mesh_btn.click(
mesh_snapshot,
inputs=[refresh_state],
outputs=[mesh_html, mesh_json, refresh_state],
)
with gr.Tab("AI / RAG Demo"):
with gr.Row():
question = gr.Textbox(
label="Question",
value="How should HearthNet respond if the internet is unavailable?",
lines=4,
)
mode = gr.Radio(
["Local RAG", "Mesh Assisted", "Emergency Brief"],
label="Route",
value="Local RAG",
)
ask_btn = gr.Button("Run Retrieval", variant="primary")
answer = gr.Textbox(label="Answer", lines=8)
citations = gr.Code(label="Retrieved Context", language="json")
ask_btn.click(rag_answer, inputs=[question, mode], outputs=[answer, citations])
with gr.Tab("Marketplace / Chat / Emergency"):
emergency = gr.Checkbox(label="Emergency Mode", value=False)
market = gr.Markdown()
emergency.change(marketplace_cards, inputs=[emergency], outputs=[market])
chatbot = gr.Chatbot(label="Operator Chat", height=320)
chat_box = gr.Textbox(
label="Message",
placeholder="Ask about an offer, a neighbor node, or an emergency workflow.",
)
chat_box.submit(
operator_chat,
inputs=[chat_box, chatbot, emergency],
outputs=[chatbot, chat_box],
)
with gr.Tab("Architecture Trace"):
trace_intent = gr.Textbox(
label="Intent",
value="A resident asks for help finding a verified first-aid kit during an outage.",
lines=3,
)
trace_btn = gr.Button("Generate Trace", variant="primary")
trace_diagram = gr.Markdown()
trace_json = gr.Code(label="Trace Events", language="json")
trace_btn.click(
architecture_trace, inputs=[trace_intent], outputs=[trace_diagram, trace_json]
)
demo.load(overview_payload, outputs=[core_detail, health_json, capabilities_json])
demo.load(
mesh_snapshot, inputs=[refresh_state], outputs=[mesh_html, mesh_json, refresh_state]
)
demo.load(marketplace_cards, inputs=[emergency], outputs=[market])
demo.load(architecture_trace, inputs=[trace_intent], outputs=[trace_diagram, trace_json])
return demo
def launch_demo() -> None:
launch_kwargs: dict[str, Any] = {}
launch_params = inspect.signature(gr.Blocks.launch).parameters
if "css" in launch_params:
launch_kwargs["css"] = CSS
if "theme" in launch_params:
launch_kwargs["theme"] = gr.themes.Base()
demo.launch(**launch_kwargs)
demo = build_app()
if __name__ == "__main__":
launch_demo()