feat(menu): nicht nutzbare TTS-Provider ausblenden + GPU-Erkennung

Menue zeigt nur tatsaechlich nutzbare Vorlese-Optionen, um Verwirrung durch
folgenlose Eintraege zu vermeiden:
- Backend ist alleinige Quelle: /api/config liefert fertiges tts_menu
  (Chatterbox nur wenn erreichbar, Cloud nur mit OpenRouter-TTS, Cartesia nur
  mit Key). piper bleibt garantierter, immer sichtbarer Default.
- Frontend blendet aus statt auszugrauen; 'Im Geraet' nur auf Mobilgeraeten
  (isMobile). Aktive/gespeicherte Auswahl auf einen sichtbaren Provider
  geklemmt -> kein Server-422.
- 4x refresh*Preset + 3x /api/config-Fetch -> 1x refreshTtsMenu; /api/config
  serverseitig 30s gecacht (spart die bisher kostenpflichtige OpenRouter-Probe
  pro Seitenladen).

GPU-Erkennung zentralisiert (keine Dopplung):
- app/capabilities.py: detect_physical_gpus/resolve_gpus (nvidia-smi).
- Neues Setting GPUS (auto|none|0,1,2), beim Start an CUDA_VISIBLE_DEVICES
  gekoppelt -> angezeigt = real nutzbar. admin_llm nutzt dieselbe Erkennung.

Tests: tests/test_capabilities.py (Maske, Kopplung, Menue-Sichtbarkeit, GPUs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-27 04:16:21 +02:00
commit 5e128a74ce
7 changed files with 333 additions and 114 deletions

View file

@ -135,6 +135,11 @@ class Settings(BaseSettings):
faster_whisper_device: str = "auto" # auto|cpu|cuda
faster_whisper_compute_type: str = "int8" # default|int8|float16|int8_float16
stt_force_language: bool = True # STT mit fester Zielsprache (robust); False = Auto-Detect + Übersetzung
# GPU-Sichtbarkeit: "auto" = erkennen (respektiert vorhandenes CUDA_VISIBLE_DEVICES),
# "none"/leer = keine GPU erzwingen, "0,1,2" = nur diese Indizes. Wird beim Start an
# CUDA_VISIBLE_DEVICES gekoppelt (siehe _apply_gpu_visibility), damit das, was das Menü
# anzeigt, auch real von torch/faster-whisper genutzt wird.
gpus: str = "auto"
# --- Lokales TTS (piper) -------------------------------------------------
piper_bin: str = "piper" # Pfad/Name des piper-Binaries
piper_voices_dir: str = str(Path.home() / ".local" / "share" / "piper" / "voices")
@ -234,3 +239,29 @@ class Settings(BaseSettings):
settings = Settings()
def _parse_gpu_indices(value: str) -> list[int]:
"""'0,2' -> [0, 2]; leer/ungültig -> []."""
return sorted({int(t) for t in (value or "").split(",") if t.strip().isdigit()})
def _apply_gpu_visibility() -> None:
"""Koppelt settings.gpus an CUDA_VISIBLE_DEVICES, BEVOR torch/CUDA initialisiert.
- "auto": nichts erzwingen (ein bereits gesetztes CUDA_VISIBLE_DEVICES bleibt gültig).
- "none"/leer/"-1": CUDA_VISIBLE_DEVICES="" -> faster-whisper "auto" fällt auf CPU.
- "0,1,...": genau diese Indizes sichtbar machen (explizit gewinnt).
"""
mode = (settings.gpus or "auto").strip().lower()
if mode == "auto":
return
if mode in ("none", "off", "false", "-1", ""):
os.environ["CUDA_VISIBLE_DEVICES"] = ""
return
indices = _parse_gpu_indices(settings.gpus)
if indices:
os.environ["CUDA_VISIBLE_DEVICES"] = ",".join(str(i) for i in indices)
_apply_gpu_visibility()