from fastapi import APIRouter, HTTPException, Depends, Body from typing import Dict, Any, List, Optional import asyncio from app.config import UNIVERSES, UniverseConfig, get_universe from app.personas import load_persona, save_persona, clone_persona, list_clones from app.proxy import check_backend_health router = APIRouter(prefix="/api", tags=["Hub Management"]) @router.get("/universes") async def list_universes(): """ Returns the list of universes with live connectivity status and persona summaries. """ results = [] # Run health checks concurrently health_tasks = [check_backend_health(u.backend_url) for u in UNIVERSES.values()] health_results = await asyncio.gather(*health_tasks, return_exceptions=True) for (u_id, u_cfg), health in zip(UNIVERSES.items(), health_results): persona = load_persona(u_id) health_data = health if isinstance(health, dict) else {"status": "error", "error": str(health)} results.append({ "id": u_cfg.id, "name": u_cfg.name, "tagline": u_cfg.tagline, "description": u_cfg.description, "scope": u_cfg.scope, "accent_token": u_cfg.accent_token, "icon": u_cfg.icon, "backend_url": u_cfg.backend_url, "health": health_data, "persona": { "tone": persona.get("tone", ""), "style": persona.get("style", ""), "principles_count": len(persona.get("principles", [])), "version": persona.get("version", 1), "updated_at": persona.get("updated_at", "") } }) return results @router.get("/universes/{universe_id}") async def get_universe_details(universe_id: str): if universe_id not in UNIVERSES: raise HTTPException(status_code=404, detail="Universe not found") u_cfg = UNIVERSES[universe_id] health = await check_backend_health(u_cfg.backend_url) persona = load_persona(universe_id) return { "config": u_cfg.dict(), "health": health, "persona": persona } @router.get("/universes/{universe_id}/persona") async def get_universe_persona(universe_id: str): if universe_id not in UNIVERSES: raise HTTPException(status_code=404, detail="Universe not found") return load_persona(universe_id) @router.put("/universes/{universe_id}/persona") async def update_universe_persona(universe_id: str, data: Dict[str, Any] = Body(...)): if universe_id not in UNIVERSES: raise HTTPException(status_code=404, detail="Universe not found") save_persona(universe_id, data) return {"status": "saved", "persona": load_persona(universe_id)} @router.post("/universes/{universe_id}/clone") async def clone_universe_profile( universe_id: str, clone_name: str = Body(..., embed=True), overrides: Optional[Dict[str, Any]] = Body(None, embed=True) ): if universe_id not in UNIVERSES: raise HTTPException(status_code=404, detail="Universe not found") cloned = clone_persona(universe_id, clone_name, overrides) return {"status": "cloned", "clone": cloned} @router.get("/clones") async def get_clones(): return list_clones() @router.get("/health") async def hub_health(): return { "status": "healthy", "universes_configured": len(UNIVERSES) }