Die unzuverlässige/intransparente Auto-Erkennung (Stichwörter + LLM) ist
ersatzlos entfernt: emergency.py neu (nur manueller, vom Nutzer bestätigter
Notruf), llm_classifier.py gelöscht, Aufrufe in chat.py/ws.py raus,
emergency_llm_* aus der Config.
Neu: prominenter "🆘 Hilfe rufen"-Knopf (Bestätigung) → POST /api/emergency
→ protokolliert + liefert Hinweis in Nutzersprache. Eskalation (Benachrichtigung
an Angehörige) folgt später; vorerst Platzhalter "ACHTUNG: Es ist noch keine
Benachrichtigung eingebaut." (10 Sprachen). Admin-Tab "Notfälle" → "Notrufe".
Tests: test_emergency_llm.py entfernt, test_quota_safety.py auf Notruf-Endpunkt
umgeschrieben. 208 passed. Live verifiziert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sprache ist immer fest. Entfernt: language_mode aus Route/Schema/Config/
Runtime-Config, die Flex-Verzweigung in Orchestrator (effective_language =
language) und ws.py (immer normales transcribe), die "🔄 Flex"-Option im
Frontend samt zugehöriger app.js-Logik (langOverrides, loadMe, Turn-Sprache).
Die generische STT-Methode transcribe_detect bleibt als wiederverwendbare
Infrastruktur erhalten (nicht mehr aufgerufen). Test: Route trägt kein
language_mode mehr. 215 passed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Zweite Ursache des toten Log-Tabs: /api/admin/log landete in location /
ohne Upgrade-Header → WS-Handshake scheiterte ("Verbindungsfehler").
Dedizierter location-Block mit Upgrade + Remote-User (Auth via Authelia).
In DEPLOYMENT.md 2.8 dokumentiert; live in nginx eingespielt + verifiziert
(WS streamt echte Journal-Zeilen als Admin 'dschlueter').
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scripts/git-hooks/pre-push lässt pytest vor jedem Push laufen und bricht
bei roten Tests ab — automatischer Regressionsschutz ohne Forgejo-Runner.
scripts/install-hooks.sh installiert die versionierten Hooks nach .git/hooks
(das selbst nicht versioniert ist). In DEPLOYMENT.md Teil 4.0 dokumentiert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
audioop ist seit 3.11 deprecated und ab 3.13 entfernt. Es bleibt der
schnelle Default (stdlib, kein numpy nötig — wichtig fürs schlanke
Test-/CI-venv); der Import wird nur in catch_warnings gekapselt. Ab 3.13
greift weiterhin der bestehende ImportError->numpy-Fallback.
Suite-Warnungen damit von 2 auf 1 (verbleibend: Starlette-TestClient,
bibliotheksintern).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wählt der Nutzer Cartesia und eine feste Sprache, die Cartesia nicht
spricht (AR/IT/RU/PL …), erscheint ein Hinweis, dass automatisch die
lokale Piper-Stimme verwendet wird. Macht den tts_fallback sichtbar,
statt lautlos eine andere Stimme zu liefern.
- /api/config liefert neu available.cartesia_unsupported_languages
(Single Source of Truth: _UNSUPPORTED_LANGS aus cartesia.py).
- app.js zeigt/aktualisiert den Hinweis bei Sprach-/Provider-Wechsel.
- Test: /api/config listet die nicht unterstützten Sprachen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Läuft die Suite (213 Tests) offline mit nur '.[test]' — keine schweren
KI-Libs nötig. In einem frischen Checkout-venv verifiziert: 213 passed.
Voraussetzung: Actions-Runner auf der Forgejo-Instanz registriert und
für das Repo aktiviert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Der Cartesia→Piper-Fallback (IT/RU/AR/PL) funktioniert nur mit dieser
Einstellung. Da config/voice-assistant.toml gitignored ist, fehlte sie
sonst bei jedem Neu-Deployment und der Fallback wäre still tot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Schließt die Test-Lücken der Gender-/Stimm-Arbeit der letzten Tage:
- test_routing.py: voice_for_route über alle Sprachen × Geschlechter
(inkl. ES Multi-Speaker #0/#1, "any"→weiblich, Gegengeschlecht-Fallback,
AR/PT/ZH Einzelstimmen, unbekannte Sprache → None).
- test_piper_tts.py: _parse_speaker (#id) + Gender-Code-Auflösung in
synthesize() + speaker_id-Durchreichung für Multi-Speaker.
- test_cartesia_tts.py (neu): Voice-Map-Parsing, _resolve_voice_id-Präzedenz,
nicht unterstützte Sprachen, direkte-UUID-Durchreichung, Cartesia→Piper-Fallback.
Hermetik-Fix in conftest.py: Deployment-Fallback-Ketten (stt/llm/tts_fallback)
werden pro Test auf "" gesetzt. Vorher schlugen 3 WS-Streaming-Tests fehl, weil
llm_fallback="openrouter" aus der TOML durchsickerte und Stub-LLMs ohne stream()
auf den echten OpenRouter durchfielen ("OPENROUTER_API_KEY is empty").
Ergebnis: 213 passed (vorher 164 passed / 3 failed). DEPLOYMENT.md Teil 4.0
dokumentiert den Pflicht-Testlauf vor jedem Deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PiperTTSProvider.synthesize() erkennt jetzt Gender-Codes ("f"/"m") und
löst sie per language-Parameter in echte Stimmmodell-Namen auf. Dadurch
greift FallbackTTSProvider transparent auf Piper durch, wenn Cartesia
eine Sprache nicht unterstützt.
tts_fallback = "piper" muss in voice-assistant.toml gesetzt sein
(nicht versioniert; siehe DEPLOYMENT.md Abschnitt 2.5).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Piper alle 10 Sprachen (DE/EN/FR/ES/IT/RU/PL ♂+♀, AR/PT/ZH) und
Cartesia DE/EN/FR/ES/PT/ZH vollständig getestet und verifiziert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scripts/download_voices.py lädt die 16 benötigten Piper-Stimmen von
HuggingFace herunter. Prüft vorhandene Dateien und lädt nur fehlende.
Optionen: --voices-dir, --force. Keine externen Abhängigkeiten (stdlib).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verifikationsabschnitt: Piper-Test deckt jetzt alle 10 Sprachen ab.
AR/PT/ZH haben kein Geschlechtspaar — liefern dieselbe Stimme für ♂+♀
(kareem, faber, huayan). Kommentare im Test machen das explizit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verifikationsabschnitt: Cartesia-Test deckt jetzt alle 6 unterstützten
Sprachpaare ab (DE/EN/FR/ES/PT/ZH je ♂+♀). ZH und PT erfolgreich getestet
(Hua/Kai, Isabella/Bruno).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BEDIENUNGSANLEITUNG.md: neue Piper-Stimmen (DE/EN/FR/ES Geschlechtspaare,
PT/PL/AR), Geschlechtspräferenz (♂/♀-Buttons) dokumentiert, NL durch
PT/PL/AR ersetzt, Stimmtabelle auf 18 Einträge erweitert
- DEPLOYMENT.md: vollständiger jamulix-Deployment-Guide neu hinzugefügt
(nginx, Authelia, Piper, Cartesia, Speaker-ID-Unterstützung, alle Änderungen
gegenüber dem Upstream-Repo)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- refreshDevicePreset(): graut das "Im Gerät"-Preset aus (disabled + opacity + Hinweis
"keine Sprachstimmen im Browser"), wenn getVoices() leer ist; entgraut automatisch
bei voiceschanged (+ 1,5s-Backstop).
- Kein Aufzwingen: device wird nur (wieder) gewählt, wenn es die gespeicherte Präferenz
ist; ohne Stimmen wird die aktive Wahl auf piper gesetzt (Präferenz bleibt erhalten).
- Server-Fallback bleibt für fehlende Einzelsprachen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Identitätsleiste (Nutzer links, Abmelden-Link rechts) wieder direkt unter dem Header
statt im ⋮-Menü. Logout-Link kommt wie bisher aus SSO_LOGOUT_URL.
- Admin-Button bleibt im ⋮-Menü (als "⚙ Admin-Bereich"); IDs unverändert.
- Doku §5.1.2 angepasst.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Vorlesen-Presets wieder nach Tempo/Qualität benannt: 📱 Im Gerät · ⚡ Schnell ·
✨ Hohe Qualität · ☁ Cloud. Cloud (OpenRouter) wieder als sichtbarer Knopf.
- syncPresetUI: exakte Markierung (openrouter hat eigenen Knopf, kein Sonderfall mehr).
- Doku §5.1.2 angepasst.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Linux-Desktop-Browser haben oft keine speechSynthesis-Stimmen -> "Im Gerät" blieb
stumm (auto + Replay). deviceVoicesReady() prüft getVoices(); fehlen Stimmen, holt
serverSpeakFallback() Audio via /api/speak (Server-Default, nie "device") und spielt es.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Übersicht: Kacheln (Nutzer/Anfragen/Notfälle/LLM) springen per Klick in den Bereich;
Notfall-Kachel wird rot mit "ansehen", wenn Ereignisse vorliegen, sonst "keine".
- Notfall-Tabelle: Textausschnitt umbricht statt abzuschneiden -> auf Mobil lesbar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Senden ➤ als runder Icon-Button (gleich groß wie Mikrofon) statt Text -> passt
auch im Querformat/schmalen Geräten, kein Abschneiden mehr.
- Eingabefeld min-w-0, Buttons shrink-0 -> Reihe kann nicht überlaufen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- resolveSttMode behandelt iOS-Safari (keine available()-API) wieder als "local"
-> on-device SpeechRecognition mit Live-Interim funktioniert auf iPhone wieder.
- Server-Fallback zeigt jetzt "Server-Erkennung. Live-Vorschau nur mit lokalem/
Cloud-STT" (startRecording optionaler Status-Text) statt generischem Hinweis.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Geräte-STT prüft vor lokaler Erkennung das On-Device-Sprachpaket via
SpeechRecognition.available(); fehlt es, install() im Hintergrund + Fallback auf
Cloud (falls erlaubt) bzw. Server. processLocally wird nur bei "available" gesetzt
-> behebt "language not supported" auf Android-Chrome.
- onerror/Start-Fehler (language-not-supported/network/service) -> sauberer
Server-STT-Fallback statt Fehlermeldung.
- Schnellwahl "📴 Lokal": setzt STT + TTS gemeinsam auf Gerät (nur Text).
- Doku §6.3.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
- admin_llm: switch_backend() mit strikter Allowlist (backend ∈ {ollama,llamacpp},
Modell gegen 'ollama list' + Format-Regex), detached (Self-Restart-sicher),
niemals shell=True. restart_gateway_detached() für systemd-User-Dienst.
- switch-llm.sh: flock-Lock gegen parallele Backend-Wechsel (Exit 75).
- Endpunkte POST /api/admin/llm/backend (422 bei ungültig) und
POST /api/admin/gateway/restart (require_admin).
- Status-Tab: Steuerung (Backend-Dropdown + Modell, Wechseln/Neustart) mit
Poll bis das Gateway wieder antwortet; Hinweis auf systemd-Voraussetzung.
- Tests: Auth + Allowlist (Shell-Metazeichen/unbekanntes Modell -> 422). 165 grün.
- Doku §7.5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- spoken_response_adapter: Emoji-/Symbol-Strip (Emoticons, Symbole, Flaggen,
Pfeile, Dingbats, ZWJ/Variationsselektoren). Markdown wurde bereits entfernt.
- Geräte-TTS (app.js v28) liest das bereinigte "spoken"-Feld statt des Rohtexts
(automatisch + beim Replay); Anzeige bleibt Originaltext.
- deploy/ollama-keepalive.conf: Drop-in OLLAMA_KEEP_ALIVE=5m -> GPU im Leerlauf frei.
- Doku §4.10: Dauerbetrieb als Dienst + warum der Gateway die GPU nicht blockiert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- scripts/llm-server/switch-llm.sh: gibt GPU des anderen Backends frei
(llama.cpp-Container stoppen bzw. Ollama-Modelle entladen, Dienst bleibt),
startet das gewünschte Backend, passt LOCAL_LLM_* in .env an und startet
das Gateway (als Dienst) neu bzw. weist auf manuellen Neustart hin.
- Makefile: Targets `llm-ollama` / `llm-llamacpp` (Modell via OLLAMA_MODEL=...).
- Doku §4.7 + Schnellbefehle §4.0: Make-Targets als empfohlener Weg, inkl.
Erklärung warum der Gateway-Neustart nötig ist (Makefile exportiert .env als
Env-Variablen -> Vorrang vor .env-Datei).
Getestet: Round-Trip ollama -> llamacpp -> ollama; llama.cpp 2,3s, gemma3 warm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher wurde `language` zwar an STT/TTS übergeben, aber nie an den LLM.
Lokales Modell antwortete deshalb immer auf Englisch/Deutsch, egal ob
Französisch, Spanisch o.a. eingestellt war.
Lösung: `language`-Parameter durch die gesamte LLM-Schicht gezogen:
- base.py: `lang_instruction()` helper + Signatur erweitert
- local_openai_compatible.py: Sprachanweisung ("Respond in Français.")
wird als letzter System-Part in den Message-Stack eingefügt
- openrouter.py: Explizite Sprachanweisung ergänzt den bestehenden
"Answer in the same language as the user"-Prompt
- fallback.py: FallbackLLMProvider leitet `language` durch
- orchestrator.py: alle 3 LLM-Aufrufstellen übergeben `language`
- Tests: alle Stub-LLMs um `language=None` ergänzt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neu installiert: en_US-lessac-high, en_GB-cori-high, es_ES-sharvard-medium,
ru_RU-irina-medium, zh_CN-huayan-medium, it_IT-paola-medium, nl_NL-mls-medium.
Gelöscht: de_DE-kerstin-low, de_DE-karlsson-low, fr_FR-gilles-low,
es_ES-davefx-medium. Alle verbleibenden Stimmen haben 130–159 Phoneme;
es/fr/it/nl/ru/zh haben kein high — medium ist das Maximum bei piper.
Dropdown und Doku aktualisiert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fr_FR-gilles-low (low, 130 Phoneme) fehlt Kombinations-Tilde U+0303
→ Nasalvokale (ã, ɔ̃) werden nicht korrekt ausgesprochen.
fr_FR-siwis-medium (medium, 154 Phoneme) enthält alle nötigen Phoneme.
Dropdown und Doku aktualisiert. espeak-ng-Daten waren bereits vollständig
in piper gebündelt — kein separater Download nötig.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Makefile: neue Targets 'stop' (Gateway + llama.cpp), 'start' (llm-up + run),
'restart' (stop + start). CONTAINER_NAME als überschreibbare Variable.
BEDIENUNGSANLEITUNG § 4: neuer Schnellbefehle-Block (§ 4.0) mit Tabellen für
alle Start-/Stop-Situationen; § 4.8 Stoppen und § 4.9 Neustart als dedizierte
Abschnitte. README: Stoppen, Neu starten und make-Targets aktualisiert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>