feat(3c): OpenRouter-Modelle live & selbst-aktualisierend statt erfunden

- Neuer Endpunkt GET /api/admin/openrouter/models (10 Min gecacht) liefert
  die echte Modell-Liste von OpenRouter (live 339 Modelle).
- Konfiguration: openrouter_llm_model/-tts_model nutzen jetzt ein
  <datalist> aus dieser Live-Liste (tippen/auswählen) — die fest
  verdrahteten, teils erfundenen Namen (z.B. gemini-3.1-flash-lite) sind weg.
- Test mit gemocktem httpx. app.js v=48. 220 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-25 05:01:05 +02:00
commit 2bec4e72db
4 changed files with 75 additions and 3 deletions

View file

@ -1,6 +1,9 @@
import asyncio
import time
from pathlib import Path
import httpx
import yaml
from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket, WebSocketDisconnect
from fastapi.responses import FileResponse
@ -134,6 +137,33 @@ async def add_user_memory(user_id: str, payload: MemoryCreate):
return MemoryOut(id=memory.id, content=memory.content, created_at=memory.created_at)
_OR_MODELS_CACHE = {"ts": 0.0, "ids": []}
@router.get("/admin/openrouter/models", dependencies=[Depends(require_admin)])
async def openrouter_models():
"""Live-Liste der OpenRouter-Modell-IDs (10 Min gecacht) für die Modell-Auswahl.
So stehen in der Konfiguration immer aktuelle, gültige Modellnamen statt
fest verdrahteter, teils veralteter/erfundener Namen.
"""
now = time.time()
if _OR_MODELS_CACHE["ids"] and now - _OR_MODELS_CACHE["ts"] < 600:
return {"models": _OR_MODELS_CACHE["ids"], "cached": True}
key = settings.openrouter_api_key.strip()
try:
async with httpx.AsyncClient(timeout=8.0) as client:
headers = {"Authorization": f"Bearer {key}"} if key else {}
r = await client.get("https://openrouter.ai/api/v1/models", headers=headers)
r.raise_for_status()
data = r.json()
ids = sorted(m["id"] for m in data.get("data", []) if m.get("id"))
_OR_MODELS_CACHE.update(ts=now, ids=ids)
return {"models": ids, "cached": False}
except Exception as exc: # best effort -> alte Liste / leer
return {"models": _OR_MODELS_CACHE["ids"], "error": str(exc)}
@router.get("/admin/users", dependencies=[Depends(require_admin_or_user)])
async def list_users():
"""Listet die Nutzer (ohne Secrets). Fuer Admins (SSO/ADMIN_USERS) oder ADMIN_API_KEY."""