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 import __version__ 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="Alexis", version=__version__, 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