""" Mission Control - eine schlanke Steuerzentrale fuer einen lokalen llama-swap Stack. Dieser Einstieg haelt nur noch das Geruest zusammen: er baut die FastAPI-App, haengt die Router ein und liefert das statische UI aus. Die eigentliche Logik liegt nach Concern getrennt in: - config.py Env-Vars / Konstanten - auth.py optionale Token-Auth - jobengine.py Hintergrund-Jobs mit Live-Log - llamaswap.py Reden mit llama-swap + config.yaml lesen/schreiben - routers/* ein Router je Bereich (models, jobs, maintenance, ...) Bewusst KISS: kein Build-Schritt, kein Framework ueber FastAPI hinaus, keine DB. Neue Bereiche kommen als routers/.py + static/js/panels/.js dazu. """ from pathlib import Path from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse, JSONResponse, Response from fastapi.staticfiles import StaticFiles from routers import jobs, hermes, hermes_ui, maintenance, memory, models, system, cookbook, integration, news app = FastAPI(title="Mission Control") @app.middleware("http") async def _no_cache_static(request, call_next): """UI + statische Module immer revalidieren lassen (304 wenn unveraendert), damit Aenderungen nach einem rsync sofort wirken und kein Stale-JS haengen bleibt.""" response = await call_next(request) path = request.url.path if path == "/" or path.startswith("/static"): response.headers["Cache-Control"] = "no-cache" return response app.include_router(models.router) app.include_router(jobs.router) app.include_router(maintenance.router) app.include_router(system.router) app.include_router(cookbook.router) app.include_router(integration.router) app.include_router(news.router) app.include_router(memory.router) app.include_router(hermes.router) app.include_router(hermes_ui.router) _STATIC = Path(__file__).parent / "static" @app.get("/") def index(): return FileResponse(_STATIC / "index.html") _FAVICON = ( b"" b"" b"" b"" ) @app.get("/favicon.ico") def favicon(): return Response(content=_FAVICON, media_type="image/svg+xml") app.mount("/static", StaticFiles(directory=_STATIC), name="static") @app.exception_handler(HTTPException) def _http_exc(_req, exc: HTTPException): return JSONResponse(status_code=exc.status_code, content={"error": exc.detail}) @app.exception_handler(Exception) def _any_exc(_req, exc: Exception): """Unerwartete Fehler als lesbare Meldung ans (vertrauenswuerdige LAN-)UI geben, statt nur einen generischen 500 ohne Hinweis. Erleichtert Anfaengern die Diagnose.""" return JSONResponse(status_code=500, content={"error": str(exc) or exc.__class__.__name__})