import asyncio import time from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, Request from fastapi.staticfiles import StaticFiles from starlette.responses import RedirectResponse, JSONResponse, HTMLResponse from app.config import settings from urllib.parse import urlencode from app.auth import authenticate, _bearer_token, CAPABILITY_COOKIE from app.dependencies import get_store from app.core.warmup import warmup_local_models from app.metrics import metrics from app.api.health import router as health_router from app.api.chat import router as chat_router from app.api.transcribe import router as transcribe_router from app.api.speak import router as speak_router from app.api.devices import router as devices_router from app.api.sessions import router as sessions_router from app.api.config import router as config_router from app.api.admin import router as admin_router from app.api.me import router as me_router from app.api.metrics import router as metrics_router from app.api.ws import router as ws_router # --- Logging: App-eigene Logger sichtbar machen --------------------------- # uvicorn konfiguriert nur seine eigenen Logger; "app.*"-INFO-Logs (z. B. die # Notruf-Diagnose) würden sonst durch den Default-Level (WARNING) verschluckt. import logging import os _app_logger = logging.getLogger("app") _app_logger.setLevel(getattr(logging, os.getenv("LOG_LEVEL", "INFO").upper(), logging.INFO)) if not _app_logger.handlers: _log_handler = logging.StreamHandler() _log_handler.setFormatter(logging.Formatter("%(levelname)s:%(name)s:%(message)s")) _app_logger.addHandler(_log_handler) _app_logger.propagate = False @asynccontextmanager async def _lifespan(app: FastAPI): # Lokale Modelle im Hintergrund vorladen -> Server ist sofort verfuegbar, # der erste Nutzer zahlt nicht den Kaltstart. task = asyncio.create_task(warmup_local_models()) yield if not task.done(): task.cancel() app = FastAPI(title="Voice Assistant Gateway", lifespan=_lifespan) @app.middleware("http") async def record_metrics(request: Request, call_next): start = time.perf_counter() response = await call_next(request) duration = time.perf_counter() - start # Route-Template (z. B. /api/sessions/{session_id}/route) statt konkreter URL, # um die Label-Kardinalitaet niedrig zu halten. route = request.scope.get("route") path = getattr(route, "path", request.url.path) labels = {"method": request.method, "path": path} metrics.inc("http_requests_total", {**labels, "status": response.status_code}) metrics.observe("http_request_duration_seconds", duration, labels) return response # Pfade mit eigener Auth, Health-Checks und Favicons: nicht gaten. _NO_ACCESS_PAGE = """
Senioren: bitte die App über den persönlichen Zugangslink öffnen.
Betreuer und Admins können sich mit Benutzername und Passwort anmelden:
Mit Benutzername & Passwort anmelden