Der prominente SOS-Button war zu groß. Jetzt ein kompaktes 32x32-Quadrat
(h-8 w-8) — exakt so hoch wie die Sprachauswahl daneben (gemessen 32px),
weiterhin rot gefüllt mit weißem „SOS". Der volle Titel passt wieder ohne
Kürzung.
Da ein 32px-Quadrat keine langen Statuswörter fasst, läuft das Feedback jetzt
über Farbe/Symbol: „SOS" (rot) → pulsiert beim Rufen → „✓" (grün) = Alarm
ausgelöst → „!" bei Fehler, dann zurück. Bestätigungsdialog unverändert.
Bump app.js v=49 -> v=50. Verifiziert via Headless-Chrome (SOS 32x32 == lang 32).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Notruf-Button war ein kleines 36px-Outline-Icon (🆘) — leicht zu übersehen.
Jetzt ein rot gefüllter Button (bg-red-600) mit großem, fettem weißem „SOS" in
der (immer sichtbaren) Kopfzeile — der Senior erkennt ihn sofort. Sprachwahl
und Menü bleiben unverändert an ihrem Platz.
- Bestätigung: statt native confirm() ein großer eigener Dialog (rote SOS-Kopf-
zeile, Warnzeile, große Knöpfe „JA, HILFE RUFEN" / „Abbrechen"). Ein Tipp auf
SOS, ein Tipp auf JA — schnell, aber gegen Fehlalarm geschützt.
- Feedback am Button selbst: setSosState idle|calling|done|error —
„Rufe …" (pulsierend) → „Gerufen ✓" (grün) → zurück zu „SOS". So sieht der
Senior unmittelbar am Knopf, dass der Alarm ausgelöst wurde.
Nur Frontend (app/web), live ohne Neustart (No-Cache-Static, Bump auf v=49).
Verifiziert via Headless-Chrome/CDP: Idle/Dialog/Calling/Done-Zustände.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die App-Oberflaeche (/) ist bewusst nur per persoenlichem Zugangslink
erreichbar (kein SSO, damit Senioren keine Login-Maske sehen). Ein Admin, der
seinen Link verliert, kam bisher nicht mehr in die App-UI.
Neuer Endpunkt GET /api/admin/login: liegt hinter Authelia (nginx-Location
/api/admin/ mit auth_request + error_page-Redirect, Remote-User durchgereicht),
erkennt den SSO-Admin, mintet ein frisches Capability-Token, setzt es als
va_token-Cookie und leitet in die App (/). Damit meldet sich der Admin allein
mit seinem Authelia-Passwort an. Jeder Aufruf rotiert den Token (alte Links
dieses Admins werden ungueltig) - fuer eine Recovery-Funktion korrekt.
Die huebsche URL https://voice.jamulix.de/admin-login wird per nginx-Alias auf
diesen Endpunkt gelegt (nginx-Config liegt ausserhalb des Repos).
Doku: BEDIENUNGSANLEITUNG.md § 7.6 - Selbstanmeldung, Recovery per
ADMIN_API_KEY (SSO-unabhaengig) und Haertung (zweiter Admin in ADMIN_USERS,
Key im Passwortmanager).
Tests: Cookie wird gesetzt + 303 -> /, gesetzter Token authentifiziert,
Token-Rotation, Nicht-Admin/ohne Identitaet -> 403.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Im Streaming-Pfad lief die satzweise TTS-Synthese bisher inline in der
LLM-Leseschleife: pro Satzgrenze wartete `chat_stream` per `await` auf die
Synthese, wodurch das Token-Streaming (Text in der Antwort-Bubble) satzweise
pausierte.
Jetzt Producer/Consumer: Die LLM-Leseschleife legt fertige Sätze in eine
asyncio.Queue und liest sofort weiter; ein einzelner Consumer-Task
synthetisiert sequenziell und ruft `on_audio` in Reihenfolge auf. Ein
einzelner Consumer garantiert die Audio-Reihenfolge (gapless-Invariante im
Client). Sentinel + await am Ende, Exceptions aus dem Consumer werden
propagiert, verwaiste Tasks werden im finally abgeräumt.
Deterministisch gemessen (Fake-LLM + Fake-TTS 3x800ms Synthese):
Token-Spanne 1845ms -> 242ms, größtes Token-Loch 821ms -> 20ms.
Audio-Chunks bleiben in Reihenfolge (seq 0..n). text_only- und
Nicht-Streaming-Pfad unverändert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
unlockTTS() schaltete bisher nur SpeechSynthesis frei. Der Web-Audio-Kontext
für die satzweise Chunk-Wiedergabe (Server-TTS-Modi) wurde erst lazy im
WS-Callback erzeugt — auf iOS startet ein so erzeugter AudioContext "suspended"
und resume() außerhalb einer Geste wird abgelehnt -> stumme/blockierte
Chunk-Wiedergabe.
Fix: unlockTTS() legt den AudioContext jetzt in der Nutzergeste an, ruft
resume() und spielt einen 1-Sample-Stille-Buffer (Standard-iOS-Unlock). Beide
Freischaltungen (Geräte-TTS + Web-Audio) laufen unabhängig, auch wenn
SpeechSynthesis fehlt.
Verifiziert (Desktop-Chrome): Kontext wird in der Geste erzeugt, ist "running"
und der Stille-Buffer läuft, bevor der erste Chunk eintrifft; Chunk-Wiedergabe
weiterhin gapless. Echtes iOS Safari hier nicht testbar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Beim Barge-in konnte ein Audio-Chunk, der im schmalen Fenster zwischen
gesendetem interrupt und dem interrupted-Event/ws.close noch eintraf, weiter
eingeplant und ~0,2-0,3 s abgespielt werden (im Browser verifiziert).
Fix: Beim Senden von interrupt wird die WS als _aborted markiert; der
onmessage-Binär-Handler verwirft danach eintreffende Chunks, statt sie via
scheduleChunk weiter abzuspielen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die satzweise vom Server gestreamten TTS-Chunks (audio-Event + Binär-Frame)
wurden im Frontend nur gepuffert und erst beim done-Event komplett abgespielt
— der Chunked-TTS-Aufwand brachte so keinen Nutzen.
Jetzt:
- scheduleChunk() hängt jeden ankommenden PCM-Chunk lückenlos an den bisher
geplanten Stream (Web-Audio-Scheduling über streamPlayTime). Die
Sprachausgabe beginnt, sobald der erste Satz synthetisiert ist.
- case "audio" markiert den folgenden Binär-Frame als Streaming-Chunk.
- done spielt den Gesamtpuffer nur noch ab, wenn nicht bereits gestreamt wurde
(kein doppeltes Abspielen); Replay-Cache bleibt befüllt.
- Die Chunk-Quellen liegen in activeSources -> Barge-in (stopAudio) stoppt auch
laufende Streaming-Wiedergabe; streamPlayTime wird dabei zurückgesetzt.
- Geräte-TTS-Modus überspringt die Chunks unverändert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gegen "Blödsinn", v. a. an Handys:
1) Modell-Default base -> small + compute_type int8 (Benchmark auf der VM:
base patzt schon bei sauberem Audio "an Dienstag und 10 Uhr", small korrekt
"am Dienstag um 10 Uhr"; ~4 s/6-s-Clip, gecacht).
2) faster_whisper.transcribe mit vad_filter=True (+min_silence 500ms) und
condition_on_previous_text=False -> weniger Stille-/Kontext-Halluzinationen.
3) stt_force_language (Default True): WS-Pfad gibt die feste Zielsprache vor
statt Auto-Detect -> robust bei kurzen/verrauschten Clips. Auto-Detect +
Fremdsprachen-Übersetzung jetzt opt-in (stt_force_language=False).
Tests: Config-Defaults + VAD/Language-Durchreichung; test_ws-Übersetzung auf
opt-in umgestellt. Suite 253 grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bestätigungsdialog jetzt zweizeilig: "Wirklich Hilfe rufen?" +
"Dann werden sofort Helfer alarmiert!" — macht die Konsequenz klar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
uvicorn konfiguriert nur seine eigenen Logger -> app.*-INFO-Logs (u. a. die
Notruf-Diagnosezeile geo/geo_error/device) wurden durch den Default-Level
(WARNING) verschluckt. main.py setzt jetzt den "app"-Logger auf LOG_LEVEL
(Default INFO) mit eigenem Handler (propagate=False, kein Doppel-Log).
Verifiziert: "INFO:app.safety.emergency:Notruf von … geo=… device=…" erscheint.
+1 Test, Suite 251 grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Speichern-Button aus dem einklappbaren „Notfall-Profil" auf die Karten-/
Einstellungs-Ebene verschoben (immer sichtbar) und in „Speichern" umbenannt.
- Speichert jetzt ALLE Daten: alle Profilfelder + Standort-Freigabe + erlaubte
Sprachen in einem PUT.
- Vor JEDEM Speichern Format-Validierung pro Feld; bei Verstoß rotes Feld
(border/ring-red) + Meckertext, Speichern wird abgebrochen. Rote Markierung
verschwindet beim Nachbessern (input-Listener).
- Regeln: PLZ = 5 Ziffern · Telefon = +/Ziffern/Leer/()/- (6–20) · Geburtsdatum
= TT.MM.JJJJ (echtes Datum) · Kontakt-E-Mails/-Telefone = CSV gültiger Werte.
Leere optionale Felder gelten als konform. Validierungslogik in node geprüft.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Notruf (Punkte 1+2):
- SMS enthält jetzt die Telefonnummer(n) der Person (Mobil/Festnetz).
- Geo-Diagnose (P1): captureGeo liefert bei Fehlschlag einen Grund
(Permission/insecure/Timeout/Code) statt still null; geht als geo_error an
/api/emergency, landet in E-Mail ("Standort nicht verfügbar (<Grund>)") und
im Server-Log (logger.info) — Fehlversuche sind jetzt erklärbar.
- Geräteerkennung (P2): device {mobile, platform} via User-Agent Client Hints
(Fallback UA-String); E-Mail zeigt "Auslösegerät: Mobilgerät/Festgerät (…)".
- schemas: DeviceInfo + EmergencyRequest.geo_error/device; chat reicht durch.
- Tests: Telefon in SMS, geo_error + Geräte-Zeile in E-Mail (240 grün).
Sprach-Bug (Punkt 3): allowed_languages wurde im Menü nur via <option hidden>
gefiltert, was manche Browser bei <select> ignorieren -> Sprachen blieben
wählbar. Jetzt zusätzlich opt.disabled (robust, nicht wählbar); Backend klemmt
ohnehin serverseitig.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 🆘-Handler erfasst beim Auslösen die Geoposition (One-Shot, navigator.
geolocation, enableHighAccuracy) und hängt sie als geo:{lat,lon,accuracy,ts}
an /api/emergency. Timeout-Race (8 s), damit hängendes GPS den Alarm nie
blockiert; nur mit Freigabe (location_consent).
- loadMe: Prefs global gehalten; bei location_consent Standort-Erlaubnis vorab
angefragt (kein Browser-Dialog im Panikmoment).
- Admin-User-Karte: einklappbares „Notfall-Profil" zum Pflegen von Name,
Adresse, Telefonen, Geburtsdatum, med. Hinweisen, Kontakt-E-Mail/-Telefon
und der Standort-Freigabe; speichert via PUT /api/admin/users/{id}/prefs.
Nutzt das in Phase 1+3 gebaute Backend; keine Backend-Änderung. app.js per
node --check validiert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die seven.io-Aufrufe (SMS + Voice/SSML) leben jetzt in der wiederverwendbaren
Library seven-send; die Bridge ist nur noch der HTTP-Adapter und ruft den
synchronen SevenClient via asyncio.to_thread. Eine getestete Stelle für die
API-Logik, ~50 Zeilen Duplikat raus.
- main.py: import SevenClient; _make_client() aus der Env; _dispatch() pro
Kanal/Nummer; ohne API-Key sauberes "nicht gesetzt"-Ergebnis (kein Versand)
- Tests: mocken jetzt seven_send.client.httpx.post; neuer Test "ohne API-Key
kein Versand" (6 Tests)
- Doku: Bridge-README + DEPLOYMENT 2.9 — seven-send als venv-Voraussetzung
(pip install git+https://kitux.de/forgejo/dschlueter/seven_send.git)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Konkreter Provider hinter dem provider-agnostischen EMERGENCY_WEBHOOK_URL:
ein kleiner FastAPI-Dienst (emergency_bridge/), der das Notruf-Payload der
App entgegennimmt und pro Telefonnummer SMS und/oder TTS-Anruf über die
seven.io-API auslöst.
- main.py: POST /notruf (Bearer-Token-Prüfung), GET /health; seven.io
SMS (/api/sms) + Voice (/api/voice, Text als SSML, XML-escaped), best
effort mit Statuscode-100-Auswertung; Kanäle/Texte kommen aus dem Payload
- Betrieb: eigener systemd-Service auf 127.0.0.1:8090, läuft in der
vorhandenen venv; .env.example + README mit Installations- und
Verdrahtungsschritten
- Tests: SMS+Anruf-Fan-out, SSML-Escaping, Token-Auth, Kanal-Filter (5 Tests)
- DEPLOYMENT.md: optionaler Abschnitt 2.9 mit Verweis auf die Bridge
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
§6.6 Sprache: Flex-Modus + language_mode entfernt; feste Sprache pro
Nutzer, automatische Übersetzung bei Fremdsprache und Admin-gesteuerte
erlaubte Sprachen (allowed_languages) dokumentiert.
§10 Notruf: automatische Erkennung (EMERGENCY_LLM_*) ersetzt durch
nutzerausgelösten 🆘-Knopf mit /api/emergency (category=manual) und
SMTP-E-Mail-Benachrichtigung (EMERGENCY_CONTACT_EMAIL, SMTP_*).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
Statt 7 technischer Gruppen jetzt 3: „Sprache & Stimme" und
„Antwortverhalten" (die alltagsrelevanten Schalter) immer sichtbar;
alle technischen Regler (STT/LLM-Provider, OpenRouter-Modelle, Temperatur,
top_p, Tokens, Systemprompt, Normalisierung …) unter „Erweitert", das
standardmäßig eingeklappt ist. app.js v=47.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nutzerverwaltung zeigt beim Anlegen/„Link erneuern" jetzt den vollen
Ein-Klick-Link (location.origin/?k=<token>) statt nur des Tokens.
- Neuer Admin-Schalter pro Nutzer (DB-Spalte users.is_admin + Migration).
is_admin_user() honoriert das Flag ODER ADMIN_USERS (SSO). Dadurch zeigt
/me den Admin-Button anhand des Flags — Grundlage für den nginx-Cutover.
- admin.py: PUT /admin/users/{id}/admin; list_users liefert is_admin.
Migration live verifiziert (Spalte vorhanden, bestehende Admins via
ADMIN_USERS weiterhin korrekt). app.js v=45. 219 passed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Zweiter Auth-Weg NEBEN Authelia (nichts schaltet um, kein Aussperr-Risiko):
- Link https://voice.jamulix.de/?k=<token> -> Token landet im va_token-Cookie
(HttpOnly, Secure, SameSite=Lax, 1 Jahr), Token wird aus der URL entfernt.
- Cookie/?k= authentifiziert App-Routen (require_user) und WS (/ws/voice,
/ws/chat). Token = vorhandenes Nutzer-Token (get_user_by_token).
- authenticate(): fehlt der SSO-Header, wird auf Token-Auth durchgefallen
(statt hartem 401) — Voraussetzung für die spätere nginx-Trennung.
Authelia/Forward-Auth bleibt voll funktionsfähig (Live verifiziert: beide
Wege gehen). Tests: Cookie-, ?k=- und Negativ-Pfad. 217 passed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Der Admin schaltet pro Nutzer Sprachen frei (Toggle-Chips in der
Nutzerverwaltung). Die Haupt-App zeigt dann nur diese Sprachen; bei genau
einer erlaubten Sprache wird das Sprachmenü ausgeblendet (kein Stress).
- admin.py: PUT /admin/users/{id}/prefs (allowed_languages), list_users
liefert allowed_languages mit.
- dependencies.py: resolve_route klemmt die Sprache auf eine erlaubte
(Backend-Durchsetzung, nicht nur UI).
- app.js: applyAllowedLanguages (Haupt-UI-Filter) + Sprach-Chips je Nutzer
im Admin. app.js v=44.
- Tests: Klemmung + Admin-Endpunkt. 213 passed.
Login-/nginx-Umbau (Ein-Klick-Link) folgt nach separater Design-Abstimmung.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Der env-Loader nimmt die ganze Zeile als Wert — Inline-Kommentare hinter
SMTP_HOST/PASSWORD/FROM landeten im Wert (DNS-/Auth-/Sender-Fehler). Vorlage
in env.example und DEPLOYMENT.md 2.5 auf reine Werte umgestellt, mit
Warnhinweis und dem Hinweis, dass SMTP_FROM dem SMTP_USER gehören muss.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Beim Notruf-Knopf wird – sofern SMTP konfiguriert ist – eine E-Mail an die
Kontaktperson(en) des Nutzers geschickt (Default: dieter.schlueter@linix.de).
Der Nutzer-Hinweis spiegelt ehrlich wider, ob wirklich etwas rausging:
"Kontaktperson per E-Mail benachrichtigt" vs. "noch keine Benachrichtigung".
- config.py: EMERGENCY_CONTACT_EMAIL + SMTP_* (ohne SMTP_HOST kein Versand);
veralteten LLM-Klassifikator-Kommentar entfernt.
- emergency.py: SMTP-Versand (best effort, im Thread), lokalisierte Hinweise
für "gesendet"/"nicht gesendet" (10 Sprachen). Pro-Nutzer-Kontakte via
prefs.emergency_contacts vorbereitet, Fallback auf Config-Default.
- Test mit gemocktem smtplib. env.example + DEPLOYMENT.md 2.5 dokumentiert.
SMS/Anruf-Eskalation folgt später (eigene Planung). 210 passed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spricht der Nutzer in einer anderen als der eingestellten Sprache, erkennt
das System die gesprochene Sprache automatisch (transcribe_detect) und
übersetzt die Anfrage per LLM in die Zielsprache — sie wird dann auch in
der Zielsprache angezeigt und verarbeitet. Hilft beim Sprachenlernen und in
der Kommunikation über Sprachgrenzen.
- Orchestrator.translate() (LLM-basiert, nur Übersetzung).
- ws.py Voice-STT: transcribe_detect + Übersetzung bei abweichender Sprache.
- Test-Stubs um transcribe_detect ergänzt; neuer Übersetzungstest. 209 passed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P1: Der prominente rote Footer-Banner war erdrückend ("erinnert ständig an
Not"). Ersatz: kleiner, quadratischer, dezenter 🆘-Button links neben der
Sprachauswahl im Header.
P4: journalctl -f wird via `stdbuf -oL` zeilengepuffert — neue Log-Zeilen
kommen jetzt sofort (statt blockweise) im Browser an und scrollen durch.
app.js-Cache-Bust auf v=43.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>