feat(stt): Geräte-STT auf Mobilgeräten (Web Speech API) — getrennt, Datenschutz-Linie C

- Geräte-STT erkennt Sprache lokal und sendet nur Text über den Text-Turn; spart
  Audio-Upload + Server-STT. Getrennter Schalter (STT ▾) unabhängig vom TTS.
- Linie C: nur bei nachweislich lokaler Erkennung (iOS / Chrome on-device); Cloud
  (z. B. Chrome-Desktop -> Google) nur mit Admin-Flag ALLOW_CLOUD_STT.
  -> config.allow_cloud_stt, RUNTIME_SETTABLE, /api/me, Admin-Toggle.
- Fix-only (SpeechRecognition braucht Sprach-Hint); Flex -> Server-STT-Fallback.
  Kein/instabiles SpeechRecognition (z. B. Firefox) -> Server-STT. Live-Interim im
  Eingabefeld. Terminal/Desktop/Laptop unverändert serverseitig (Option nur sichtbar,
  wenn das Gerät lokale Erkennung bietet).
- Tests: /api/me-Flag + runtime-setzbar (169 grün). Doku §5.1.2 + §6.3.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-20 20:08:10 +02:00
commit ab9c4f4938
7 changed files with 150 additions and 4 deletions

View file

@ -42,3 +42,19 @@ def test_backend_switch_rejection_is_audited(caplog):
json={"backend": "boese"}, headers=ADM_HDR)
assert r.status_code == 422
assert "action=llm_backend_switch_rejected" in "\n".join(caplog.messages)
def test_me_exposes_allow_cloud_stt():
# /api/me liefert das Datenschutz-Flag (Client-Gate für Geräte-STT).
d = client.get("/api/me").json()
assert "allow_cloud_stt" in d
assert isinstance(d["allow_cloud_stt"], bool)
def test_allow_cloud_stt_runtime_settable():
from app.runtime_config import RUNTIME_SETTABLE
assert "allow_cloud_stt" in RUNTIME_SETTABLE
r = client.put("/api/admin/config/allow_cloud_stt", json={"value": "true"}, headers=ADM_HDR)
assert r.status_code == 200
assert client.get("/api/me").json()["allow_cloud_stt"] is True
client.delete("/api/admin/config/allow_cloud_stt", headers=ADM_HDR)