""" 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, 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) _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__})