feat(admin): Phase 3 — Laufzeit-Konfiguration ohne Server-Neustart

Neue Tabelle config_overrides in SQLite; RuntimeSettings-Wrapper liest
überschreibbare Felder mit 30s TTL-Cache aus der DB und fällt auf
.env-Werte zurück. dependencies.py und quota.py nutzen runtime_settings
als Default statt des statischen Settings-Singletons.

16 Felder überschreibbar: STT/LLM/TTS-Provider, LLM-Modelle, Stimmen,
Systemprompt, Temperatur, Tageskontingent, Normalisierung u.a.

Backend: GET/PUT/DELETE /api/admin/config/{key}
Admin-UI: neuer Tab "⚙ Einstellungen" mit Inline-Edit und Reset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-19 01:39:36 +02:00
commit 5d022aaf63
7 changed files with 285 additions and 9 deletions

View file

@ -1,6 +1,7 @@
from dataclasses import dataclass
from app.config import Settings, settings
from app.runtime_config import runtime_settings
from app.errors import UnknownComponentError
from app.audio.router import AudioRouter
from app.audio.endpoints.input.local_default import LocalDefaultInput
@ -93,15 +94,18 @@ def _from_registry(registry: dict, name: str, kind: str, cfg: Settings):
return factory(cfg)
def get_stt_provider(name: str | None = None, cfg: Settings = settings):
def get_stt_provider(name: str | None = None, cfg=None):
cfg = cfg or runtime_settings
return _from_registry(STT_REGISTRY, name or cfg.default_stt_provider, "STT", cfg)
def get_llm_provider(name: str | None = None, cfg: Settings = settings):
def get_llm_provider(name: str | None = None, cfg=None):
cfg = cfg or runtime_settings
return _from_registry(LLM_REGISTRY, name or cfg.default_llm_provider, "LLM", cfg)
def get_tts_provider(name: str | None = None, cfg: Settings = settings):
def get_tts_provider(name: str | None = None, cfg=None):
cfg = cfg or runtime_settings
return _from_registry(TTS_REGISTRY, name or cfg.default_tts_provider, "TTS", cfg)
@ -190,8 +194,9 @@ def resolve_route(
user: User | None = None,
session_id: str | None = None,
overrides: dict | None = None,
cfg: Settings = settings,
cfg=None,
) -> ResolvedRoute:
cfg = cfg or runtime_settings
"""Loest die effektive Route auf.
Praezedenz (hoeher gewinnt): Defaults < Nutzer-Prefs < Session-Route < Request.
@ -247,7 +252,8 @@ def _resolve_normalize_level(tts_provider: str, cfg: Settings) -> str:
return level
def build_orchestrator(route: ResolvedRoute, cfg: Settings = settings) -> Orchestrator:
def build_orchestrator(route: ResolvedRoute, cfg=None) -> Orchestrator:
cfg = cfg or runtime_settings
return Orchestrator(
stt=_provider_chain(STT_REGISTRY, route.stt_provider, cfg.stt_fallback, "stt", cfg),
llm=_provider_chain(LLM_REGISTRY, route.llm_provider, cfg.llm_fallback, "llm", cfg),