""" Embodied Navigation Benchmark — Monitoring Dashboard Serves both a Gradio UI and REST API endpoints for agent discovery. """ import gradio as gr import json import os import time import threading import requests as req_lib import psutil import subprocess from fastapi import FastAPI from fastapi.responses import JSONResponse, HTMLResponse import uvicorn # ── State ───────────────────────────────────────────────────────────────────── _state = { "engine_online": False, "tunnel_url": "", "uptime": 0, "start_time": time.time(), "engine_logs": [], "observations": 0, } def get_tunnel_url(): # Method 1: Read tunnel URL from file (written by start.sh) try: if os.path.exists("/tmp/tunnel_url.txt"): with open("/tmp/tunnel_url.txt") as f: url = f.read().strip() if url: _state["tunnel_url"] = url return url except: pass # Method 2: Query ngrok API try: resp = req_lib.get("http://localhost:4040/api/tunnels", timeout=3) data = resp.json() for t in data.get("tunnels", []): url = t.get("public_url", "") if url.startswith("tcp://"): _state["tunnel_url"] = url return url except: pass # Method 3: Check cloudflared log try: if os.path.exists("/tmp/cloudflared.log"): with open("/tmp/cloudflared.log") as f: for line in f: if "trycloudflare.com" in line: import re match = re.search(r'https://[a-z0-9-]+\.trycloudflare\.com', line) if match: url = f"cloudflared:{match.group()}" _state["tunnel_url"] = url return url except: pass return _state.get("tunnel_url", "") def get_engine_status(): try: log_path = "/app/simulation/logs/latest.log" if os.path.exists(log_path): with open(log_path, "r") as f: lines = f.readlines() _state["engine_logs"] = lines[-50:] if len(lines) > 50 else lines for line in lines[-20:]: if "Done (" in line: _state["engine_online"] = True except: pass try: result = subprocess.run(["pgrep", "-f", "engine.jar"], capture_output=True, text=True) if result.returncode == 0: _state["engine_online"] = True except: pass return _state["engine_online"] def parse_address(url): if not url: return "N/A", "N/A" if url.startswith("tcp://"): addr = url.replace("tcp://", "") parts = addr.rsplit(":", 1) if len(parts) == 2: return parts[0], parts[1] return addr, "25565" if url.startswith("cloudflared:"): return url.replace("cloudflared:", ""), "cloudflared" if url.startswith("direct:"): return url.replace("direct:", ""), "direct" return url, "unknown" def background_checker(): while True: get_engine_status() get_tunnel_url() _state["uptime"] = int(time.time() - _state["start_time"]) _state["observations"] += 1 time.sleep(10) _thread = threading.Thread(target=background_checker, daemon=True) _thread.start() # ── FastAPI app with API routes FIRST ───────────────────────────────────────── app = FastAPI() @app.get("/health") async def health(): get_engine_status() return {"status": "ok", "engine": _state.get("engine_online", False)} @app.get("/api/ngrok") async def api_ngrok(): get_tunnel_url() url = _state.get("tunnel_url", "") return {"url": url, "tcp": url} @app.get("/api/status") async def api_status(): get_engine_status() return { "mc_server": "online" if _state.get("engine_online") else "offline", "ngrok_url": _state.get("tunnel_url", ""), "uptime": _state.get("uptime", 0), } # ── Gradio Interface ────────────────────────────────────────────────────────── def refresh_dashboard(): get_engine_status() get_tunnel_url() status = "Running" if _state["engine_online"] else "Starting..." uptime_h = _state["uptime"] // 3600 uptime_m = (_state["uptime"] % 3600) // 60 uptime_s = _state["uptime"] % 60 tunnel_url = _state.get("tunnel_url", "Not available") try: mem = psutil.virtual_memory() cpu = psutil.cpu_percent(interval=0.5) resources = f"CPU: {cpu:.0f}% | RAM: {mem.percent:.0f}% ({mem.used // 1024 // 1024}MB / {mem.total // 1024 // 1024}MB)" except: resources = "N/A" return status, f"{uptime_h}h {uptime_m}m {uptime_s}s", tunnel_url, resources def get_agent_connect_info(): get_tunnel_url() url = _state.get("tunnel_url", "") if not url: return "Simulation tunnel not ready. The engine may still be starting up." host, port = parse_address(url) if url.startswith("tcp://"): return f"## Agent Connection Endpoint\n\n**Host:** `{host}`\n**Port:** `{port}`\n**Protocol:** TCP (simulation v1.21.4)\n\nConnect an autonomous agent to this endpoint." elif url.startswith("cloudflared:"): return f"## Agent Connection Endpoint\n\n**Tunnel URL:** `{host}`\n**Protocol:** Cloudflare Tunnel (TCP proxy)\n\nUse cloudflared to proxy TCP connections through this tunnel.\nCommand: `cloudflared access tcp --hostname {host} --url localhost:25565`" else: return f"## Agent Connection Endpoint\n\n**Address:** `{host}`\n\nDirect connection may be available." def get_logs(): get_engine_status() logs = _state.get("engine_logs", []) return "".join(logs[-30:]) demo = gr.Blocks(title="Embodied Navigation Benchmark", theme=gr.themes.Soft()) with demo: gr.Markdown("# Embodied Navigation Benchmark\nReal-time evaluation of autonomous AI agents in 3D spatial navigation tasks.") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### Engine Status") status_out = gr.Textbox(label="Simulation Engine", value="Checking...", interactive=False) uptime_out = gr.Textbox(label="Uptime", value="0h 0m 0s", interactive=False) tunnel_out = gr.Textbox(label="Tunnel URL", value="Connecting...", interactive=False) resource_out = gr.Textbox(label="System Resources", value="N/A", interactive=False) refresh_btn = gr.Button("Refresh Status", variant="primary") with gr.Column(scale=1): connect_out = gr.Markdown(value="Loading connection info...") connect_btn = gr.Button("Get Agent Endpoint") with gr.Row(): with gr.Column(): gr.Markdown("### Engine Logs") logs_out = gr.Textbox(label="Recent Logs", value="", lines=12, interactive=False, max_lines=12) logs_btn = gr.Button("Refresh Logs") refresh_btn.click(fn=refresh_dashboard, outputs=[status_out, uptime_out, tunnel_out, resource_out]) connect_btn.click(fn=get_agent_connect_info, outputs=[connect_out]) logs_btn.click(fn=get_logs, outputs=[logs_out]) demo.load(fn=refresh_dashboard, outputs=[status_out, uptime_out, tunnel_out, resource_out]) # Mount Gradio on /ui path so API routes on / get priority app = gr.mount_gradio_app(app, demo, path="/ui") # Redirect root to /ui for web browsers @app.get("/", response_class=HTMLResponse) async def root_redirect(): return '
Go to Dashboard' if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=7860)