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>
- 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>
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>
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>
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>
- 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>
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>
piper_voice, openrouter_tts_model und openrouter_tts_voice bekommen
'testProvider' in FIELD_META. Der Test-Handler schickt diesen Provider
als tts_provider im Request mit — statt immer den System-Default zu
nutzen, der oft openrouter ist und dann mit 502 scheitert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.js: default_stt/llm/tts_provider und default_language von 'select'
auf 'combo' umgestellt, damit neben den vordefinierten Optionen jederzeit
ein beliebiger eigener Wert eingegeben werden kann (Zukunftssicherheit).
combo-Renderer unterstützt jetzt auch meta.labels (Sprachkürzel → Klarname).
tests/test_admin_settings.py: 28 Tests für GET/PUT/DELETE /api/admin/config —
Auth, bekannte Werte, Custom-Values, Persistenz, Runtime-Wirkung.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dropdown-Auswahl für Provider, Bool- und Enum-Felder
- Combo-Widget (Dropdown + Textfeld, synchronisiert) für Modelle/Stimmen
- Range-Slider für Temperature, Number-Input für Token/Limit-Felder
- Textarea für System-Prompt
- Test-Buttons: 🔊 TTS via WebAudio, 💬 LLM via /api/chat?debug=true, 🎤 STT-Verfügbarkeitscheck
- Inline-Save-Feedback (grün ✓ / rot ✗), kein Full-Reload mehr
- ENV-Variablenname in UPPERCASE angezeigt
- Sieben Gruppen-Sektionen statt flacher Liste
- Piper-Stimmentabelle in Doku korrigiert (nur installierte: thorsten/kerstin/karlsson)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>