"""Thin operator web console for the API server.
This keeps the UI intentionally small: an aiohttp-mounted cockpit that
surfaces Hermes health, browser runtime state, and ecosystem discovery
without introducing a second heavyweight frontend architecture.
"""
from __future__ import annotations
import json
from html import escape
from typing import Any, Dict
from aiohttp import web
from tools.browser_tool import browser_runtime_heal, browser_runtime_status
_DISCOVERY_FRONTENDS = [
"Open WebUI",
"LobeChat",
"LibreChat",
"AnythingLLM",
"NextChat",
"ChatBox",
]
def _adapter(request: web.Request):
return request.app["api_server_adapter"]
def _auth_or_none(request: web.Request):
adapter = _adapter(request)
return adapter._check_auth(request)
def _render_console_html(adapter) -> str:
health = {
"platform": "api_server",
"host": adapter._host,
"port": adapter._port,
"model": adapter._model_name,
"auth_required": bool(adapter._api_key),
}
health_json = escape(json.dumps(health, indent=2, ensure_ascii=False))
return f'''
Hermes Web Console
Gateway Health
{health_json}
Ecosystem Discovery
GET /v1/models — OpenAI-compatible model discovery
POST /v1/chat/completions — chat frontend compatibility
POST /v1/responses — stateful responses API
POST /v1/runs + GET /v1/runs/{{run_id}}/events — SSE lifecycle stream
GET /api/gui/browser/status — browser runtime status
POST /api/gui/browser/heal — cleanup + orphan reaper
Loading...
'''
async def handle_web_console_index(request: web.Request) -> web.Response:
return web.Response(text=_render_console_html(_adapter(request)), content_type="text/html")
async def handle_gui_health(request: web.Request) -> web.Response:
adapter = _adapter(request)
return web.json_response({
"status": "ok",
"platform": "api_server",
"host": adapter._host,
"port": adapter._port,
"model": adapter._model_name,
"auth_required": bool(adapter._api_key),
})
async def handle_browser_status(request: web.Request) -> web.Response:
auth_err = _auth_or_none(request)
if auth_err is not None:
return auth_err
return web.json_response(browser_runtime_status())
async def handle_browser_heal(request: web.Request) -> web.Response:
auth_err = _auth_or_none(request)
if auth_err is not None:
return auth_err
return web.json_response(browser_runtime_heal())
async def handle_discovery(request: web.Request) -> web.Response:
adapter = _adapter(request)
return web.json_response({
"frontends": _DISCOVERY_FRONTENDS,
"operator_cockpit": {
"root": "/",
"health": "/api/gui/health",
"browser_status": "/api/gui/browser/status",
"browser_heal": "/api/gui/browser/heal",
},
"openai_compatible": {
"models": "/v1/models",
"chat_completions": "/v1/chat/completions",
"responses": "/v1/responses",
"runs": "/v1/runs",
"run_events": "/v1/runs/{run_id}/events",
"model_name": adapter._model_name,
},
})
def maybe_register_web_console(app: web.Application) -> None:
app.router.add_get("/", handle_web_console_index)
app.router.add_get("/api/gui/health", handle_gui_health)
app.router.add_get("/api/gui/browser/status", handle_browser_status)
app.router.add_post("/api/gui/browser/heal", handle_browser_heal)
app.router.add_get("/api/gui/discovery", handle_discovery)