root c6164d946c feat(model): wählbares LLM-Modell pro User & pro Job
Mac-Worker:
- /api/summarize, /api/protocol, /api/preload akzeptieren optionales
  model-Feld → überschreibt den .env-Default pro Call, kein Restart nötig
- /api/models listet die lokal verfügbaren Ollama-Modelle + Default

LXC:
- User.default_model + Job.model (Migration für SQLite)
- /api/auth/me liefert default_model, PATCH speichert ihn sofort
- /api/mac/models proxiet die Liste an die UI
- Pipeline reicht job.model an Mac-Worker durch; leer = Worker-Default
- create_job & reprocess akzeptieren model-Override
- Snapshot-Meta nutzt job.model (genauer als /health-Snapshot)

UI:
- Header-Dropdown "Modell:" neben "Profil:" — sofort-speichert User-Default
- Jeder Job zeigt sein Modell als Tag (lila); leer bedeutet Worker-Default
- Reprocess öffnet einen Modal-Picker (statt simplem confirm), Auswahl
  aller verfügbaren Modelle inkl. "Worker-Default"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:48:11 +00:00

102 lines
3.3 KiB
Python

import logging
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
from starlette.types import Scope
from app.api.admin import router as admin_router
from app.api.auth import router as auth_router
from app.api.jobs import router as jobs_router
from app.api.profiles import router as profiles_router
from app.core.config import settings
from app.core.db import init_db
from app.core.mac_client import MacClient
from app.core.migrate import run_migrations
class NoCacheStaticFiles(StaticFiles):
"""StaticFiles, die Browser zu Revalidierung zwingen (verhindert hängende Caches nach Deploy)."""
async def get_response(self, path: str, scope: Scope):
response = await super().get_response(path, scope)
response.headers["Cache-Control"] = "no-cache, must-revalidate"
return response
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
log = logging.getLogger("voice-agent")
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
run_migrations()
if settings.session_secret == "CHANGE-ME-SET-IN-ENV":
log.warning("SESSION_SECRET nicht gesetzt — bitte in .env auf einen geheimen Wert ändern!")
log.info("DB initialized at %s", settings.db_url)
log.info("Upload dir: %s", settings.upload_dir)
log.info("Result dir: %s", settings.result_dir)
log.info("Mac API: %s", settings.mac_api_url)
yield
app = FastAPI(title=settings.app_name, lifespan=lifespan)
app.add_middleware(
SessionMiddleware,
secret_key=settings.session_secret,
session_cookie=settings.session_cookie_name,
max_age=settings.session_max_age,
same_site="lax",
https_only=False, # via Reverse-Proxy: ggf. auf True wenn HTTPS terminiert
)
app.include_router(auth_router)
app.include_router(admin_router)
app.include_router(profiles_router)
app.include_router(jobs_router)
STATIC_DIR = Path(__file__).resolve().parent / "static"
app.mount("/static", NoCacheStaticFiles(directory=STATIC_DIR), name="static")
@app.get("/")
def index():
return FileResponse(STATIC_DIR / "index.html", headers={"Cache-Control": "no-cache, must-revalidate"})
@app.get("/health")
def health():
return {"status": "ok", "service": settings.app_name}
@app.get("/api/mac/health")
async def mac_health():
try:
data = await MacClient().health()
return {"reachable": True, "mac": data}
except Exception as e: # noqa: BLE001
return JSONResponse(status_code=502, content={"reachable": False, "error": str(e)})
@app.get("/api/mac/models")
async def mac_models():
"""Proxiet die Modell-Liste vom Mac-Worker an die UI.
Antwort: {"models": [{"name", "size", "modified_at"}], "default": "<.env-Default>"}
Bei nicht erreichbarem Mac: leere Liste + Hinweis, damit die UI eine
klare Fehlermeldung anzeigen kann statt ein leeres Dropdown.
"""
try:
data = await MacClient().list_models()
return data
except Exception as e: # noqa: BLE001
return JSONResponse(
status_code=502,
content={"models": [], "default": "", "error": str(e)},
)