Store.delete_user_history (loescht Sessions + Nachrichten eines Nutzers, ohne
Konto/Erinnerungen/Usage) + DELETE /api/admin/users/{id}/history (audit-geloggt)
+ Button im Admin-Nutzer-Bereich.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sonar liefert Citation-URLs; bisher verworfen, jetzt bis in die UI gereicht.
- ToolCallingLLM: _run_tool/_inject_forced_search geben Citations zurueck;
stream() sammelt sie ueber einen on_citations-Callback.
- Orchestrator: chat_stream reicht on_citations durch (via _stream_supports)
und legt die gesammelten URLs in PipelineTrace.citations.
- schemas: PipelineTrace.citations.
- ws.py: Citations im semantic-Event.
- Frontend (app.js): renderCitations() zeigt bis zu 4 Quellen-Links
(Hostname, neuer Tab) unter der Antwort-Bubble.
Nur ueber den Streaming-Pfad (= Web-UI). Live verifiziert: "Wer ist
Bundeskanzler?" -> 15 Quellen gesammelt (Bundeskanzler.de, Wikipedia, ...).
Tests: 312 gruen; JS-Syntax geprueft. Doc §8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher wurde der Filler im "Im Gerät"-Modus (Web Speech API) nur angezeigt,
aber nicht gesprochen (Server-Audio ist dort aus). Jetzt eigener Kanal:
- Orchestrator: chat_stream bekommt on_filler; _emit_filler bevorzugt den
dedizierten Filler-Kanal (sonst Fallback on_token). Ephemer wie bisher.
- ws.py: on_filler -> {type:"filler", text}.
- Frontend (app.js): {type:"filler"} -> transient in der STATUS-Zeile (nicht
in der Antwort-Bubble) und im Geräte-Modus sofort speechSynthesis.speak()
OHNE cancel (mehrere Filler -> Queue; die Antwort cancelt spaeter und
uebernimmt). Abgesichert ueber deviceVoicesReady()/deviceVoiceReady(lang) —
keine Verschaerfung der bekannten deviceVoiceReady-Regression.
Server-TTS-Modus unveraendert (Filler zusaetzlich als "Satz null" gesprochen),
zeigt den Filler nun ebenfalls in der Status-Zeile statt in der Bubble.
Tests: 312 gruen; JS-Syntax geprueft. Doc §5.4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher meldete ein Notruf ohne nutzbaren Kanal trotzdem Erfolg
(status="alerted", grüner Haken), obwohl tatsächlich niemand
benachrichtigt wurde — gefährlich für Senioren.
- emergency.py: drei klare Endzustände alerted / no_recipients /
delivery_failed; notify_readiness() prüft, ob ein Kanal wirklich
senden würde (SMTP+Empfaenger bzw. Webhook). Mehrsprachige Hinweise
fuer die Fehlfaelle (nennen Selbsthilfe 112). Antwort traegt jetzt
notified + notify_ready; Warn-Log, wenn nichts zugestellt wurde.
- /api/emergency/status liefert notify_ready fuer die Vorab-Warnung.
- Frontend: gelbe Vorab-Warnung im SOS-Dialog, wenn kein Empfaenger
eingerichtet ist; bei no_recipients/delivery_failed kein gruenes
Erfolgssignal mehr, sondern roter Fehlerzustand (app.js v54).
- Tests an die neue Semantik angepasst.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Anzeigename Voice Assistant -> Alexis (UI-Titel/Kopfzeile mit Untertitel
„Ihr Sprachbegleiter", FastAPI-Titel, README/Handbuch). Technische Identifier
(Dienst, Pfade, Paketname, Konfig-/DB-Dateinamen) bleiben unveraendert.
- Version als EINE Quelle: pyproject.toml = 0.2.0; app.__version__ liest sie
(pyproject zuerst, Paket-Metadaten als Fallback). Sichtbar in der UI-Kopfzeile,
unter /api/config (version) und in OpenAPI.
- CHANGELOG.md mit 0.2.0-Eintrag; Tests fuer /api/config.version + Branding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Variante 1: Senioren behalten ihren persönlichen Link; zusätzlich ein
Passwort-Einstieg ohne 2FA über Authelia.
- Neuer Endpunkt GET /api/login (app_login): hinter Authelia (nginx-Location
/login, one_factor), erkennt den per Remote-User weitergereichten Nutzer
(Admin ODER normaler Authelia-Nutzer), mintet das va_token-Cookie und leitet
in die App (/). Cookie-Mint in _issue_login_cookie() gemeinsam mit
/api/admin/login refaktoriert.
- "Persönlicher Zugang nötig"-Seite (main.py) bekommt einen großen Knopf
„Mit Benutzername & Passwort anmelden" -> /login. Bare / leitet bewusst NICHT
automatisch um (Senioren ohne Authelia-Konto würden sonst feststecken).
Begleitende Infra (außerhalb des Repos):
- nginx: location = /login (auth_request one_factor -> /api/login).
- Authelia: access_control-Regel voice.jamulix.de -> one_factor (über der
*.jamulix.de-Wildcard); damit sind App-Login UND Admin nur noch 1FA, andere
Subdomains bleiben 2FA.
Tests: /api/login für Nicht-Admin/Admin/ohne-Identität. 257 Offline-Tests grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher war die Wiederauslöse-Sperre nur global einstellbar. Jetzt zusätzlich
pro Nutzer im Notfall-Profil (Admin), hinter „Standort im Notfall mitsenden":
„Pause bis zum erneuten Alarm: [n] Minuten" (leer = globaler Standard, 0 = aus).
- Auflösung serverseitig: Nutzer-Pref > globale Einstellung > 5
(_resolve_cooldown_minutes in emergency.py; emergency_cooldown_status nutzt sie,
die Notruf-UI/der Status-Endpunkt liefern dadurch automatisch den per-User-Wert).
- Schema AdminUserPrefsUpdate.emergency_cooldown_minutes (String) mit Validator
(leer/0–60, sonst 422). /api/admin/users gibt das Feld jetzt mit aus
(sonst lädt das Formular leer).
- Frontend: Zahlenfeld + Hinweis im Notfall-Profil, über PROF_KEYS/PROF_VALIDATORS
verdrahtet. Bump app.js v=51 -> v=52.
- Doku: BEDIENUNGSANLEITUNG § Nutzer › Verwalten.
Tests: per-User-Override/Erben/Aus + Schema-Validierung. 254 Offline-Tests grün.
Live verifiziert: PUT prefs -> /status spiegelt 300->120s, 99 -> 422, Formular
zeigt/lädt das Feld hinter der Standort-Checkbox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1) Notruf-Sperre (Cooldown) gegen Mehrfach-Alarme verwirrter/verängstigter
Senioren:
- Neue Einstellung emergency_cooldown_minutes (Default 5), zur Laufzeit im
Admin-Bereich änderbar (RUNTIME_SETTABLE), 0 = aus.
- record_manual_emergency unterdrückt innerhalb der Sperre jeden weiteren
Alarm (kein zweiter Versand, kein Log-Eintrag) und liefert status=cooldown
mit minutes_ago/remaining_seconds. Neuer Endpunkt GET /api/emergency/status
für die Anzeige nach einem Seiten-Neuladen. Store: last_manual_emergency_at.
- UI: Nach dem Alarm wird der Button grün, zeigt „✓" und pulsiert für die
Dauer der Sperre. Erneuter Druck öffnet einen grünen Hinweis-Dialog:
„Ich habe Deine Helfer vor <n> Minuten alarmiert. Wahrscheinlich ist Deine
Hilfe schon unterwegs." Der grüne Zustand übersteht ein Neuladen
(Status-Abruf beim Start).
2) „Neues Gespräch" als Symbol (Stift) zurück in die Kopfzeile — zwischen
Sprachauswahl und Menü; löst dieselbe Aktion aus wie der Menüeintrag.
Tests: neue test_emergency_cooldown.py (Status/Suppression/Endpoint); zwei
bestehende Notruf-Tests an die neue Interface-Methode bzw. die Sperre
angepasst. 250 Offline-Tests grün. Frontend via Headless-Chrome verifiziert
(Alarm->grün/pulsierend, Hinweis-Dialog, Reload-Persistenz, Header-Button).
Bump app.js v=50 -> v=51.
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>
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>
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>
- 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>
- 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>
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>
- 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>
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>
- Aussprache-Lexikon (de/en) komplett im Browser editierbar: Einträge
hinzufügen/löschen in Sektionen Abkürzungen/Einheiten/Begriffe;
LRU-Cache wird nach jedem Schreibvorgang automatisch geleert.
- Live-Log-Tab: WebSocket auf /api/admin/log streamt journalctl
des voice-assistant.service live im Terminal-Style ins Admin-Panel.
- DB-Export: SQLite-Datenbank über /api/admin/db-export herunterladen.
- Metriken-Tab: CSS-Balkendiagramm (Anfragen je Nutzer) ergänzt.
- Dokumentation: §7.5 Admin-Web-Panel, B.5 API-Endpunkte, Sachregister.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hintergrund: SSO-Nutzer (va.linix.de) werden bereits beim ersten Besuch
automatisch registriert, hatten aber keinen echten Namen im LLM-Kontext
und konnten vom Admin nicht vorbereitet werden.
Änderungen:
- ws.py + chat.py: Nutzeridentität (display_name + Erinnerungen) wird als
führende System-Message bei jeder Anfrage injiziert; für anonyme
Dev-Nutzer (AUTH_ENABLED=false) wird diese Injection übersprungen
- store.py: update_display_name() im ABC und SQLiteStore
- schemas.py: UserUpdate (display_name)
- admin.py:
- PUT /api/admin/users/{id}: Anzeigenamen eines SSO-Nutzers setzen
- POST /api/admin/users/{id}/memories: initiale Erinnerungen vorbelegen
- BEDIENUNGSANLEITUNG §7.2: neuer Abschnitt "SSO-Nutzer — automatische
Registrierung" mit vollständigem Workflow; §7.3/7.4 neu nummeriert;
Anhang B.5 mit neuen Endpunkten ergänzt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- POST /api/admin/users/{user_id}/token: neues Bearer-Token ausstellen
(alter Token sofort ungültig, Nutzerdaten bleiben erhalten)
- Store ABC + SQLiteStore: reset_token() implementiert
- BEDIENUNGSANLEITUNG §7.2: erklärt warum Tokens nicht abrufbar sind (nur
SHA256-Hash gespeichert), wo ADMIN_API_KEY nachzuschauen ist (.env),
wie Token-Reset genutzt wird; praktisches Tipp zu ~/.bashrc
- Anhang B.5: neuen Endpunkt in REST-Referenz eingetragen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Beim SSO-Setup kennt das Gateway den Admin-Nutzer noch nicht (Henne-Ei). Der
Discovery-Endpoint /api/admin/request-headers akzeptiert daher zusaetzlich den
ADMIN_API_KEY als Query (?key=) oder X-Admin-Key-Header, damit man den von SSOwat
injizierten Identitaets-Header im Browser bestimmen kann. Doku entsprechend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- app/core/memory_extractor.py: LLM destilliert nach je N Turns dauerhafte
Fakten/Vorlieben aus dem Verlauf, dedupliziert gegen vorhandene Erinnerungen
und legt sie ab - best-effort, nicht-blockierend (Hintergrund-Task), eigener
Extraktions-Prompt (JSON, Reasoning aus), Cap-Begrenzung
- Trigger in /api/chat und /ws/voice nach dem Persistieren des Turns
- Konfig: MEMORY_EXTRACTION_ENABLED/_EVERY_N_TURNS/_MAX/_PROVIDER
- Tests: Extraktion, Dedup, kaputtes JSON, Cap, leeres Gespraech, Scheduling
- Doku: README + Architektur-Roadmap (Punkt 3 erledigt)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- voice_loop.py: --voice reicht die Stimme im Start-Frame durch (nur wenn gesetzt),
sonst greift der Provider-Default. Beispiele in Docstring/Help.
- ws.py/speak.py/chat.py: ohne explizite Stimme None durchreichen statt die
OpenRouter-Stimme zu erzwingen -> jeder Provider nimmt SEINEN Default
(OPENROUTER_TTS_VOICE bzw. PIPER_VOICE). Ungenutzten settings-Import in speak.py
entfernt.
- Doku: BEDIENUNGSANLEITUNG (--voice + Default-Stimme je Provider).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug: /ws/voice nutzte bei explizitem {"type":"end"} die Optionen aus dem END-Frame.
voice_loop schickt Provider und audio_stream aber im START-Frame -> diese wurden
komplett ignoriert. Folge: --stt/--llm/--tts-provider und --stream-audio hatten
ueber /ws/voice KEINE Wirkung (Hybrid lief faktisch ueber die Cloud-Defaults,
audio_stream wurde nie aktiv).
Fix: start-Frame als start_options merken und beim Turn mit dem end-Frame mergen
({**start_options, **end}); VAD-Auto-Ende nutzt ebenfalls start_options.
Verifiziert: Provider-Override aus dem START-Frame greift (neuer Test); mit
audio_stream kommen jetzt mehrere Audio-Chunks ~6-7 s VOR dem fertigen Text
(satzweises Vorlesen startet frueher). 65 Tests gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>