Commit graph

38 commits

Author SHA1 Message Date
c35fe6e187 feat(P2): Notruf verschickt E-Mail an Kontaktperson (Testphase)
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>
2026-06-25 03:37:18 +02:00
a7f718192f feat(P3): Diktat in Fremdsprache → Übersetzung in die Zielsprache
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>
2026-06-25 03:30:37 +02:00
40291d318b fix(ui): Notruf-Button dezent in den Header; Log live & durchscrollend
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>
2026-06-25 03:26:50 +02:00
3389f75af0 feat(1b): Automatische Notfallerkennung raus → manueller Notruf-Knopf
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>
2026-06-25 03:02:52 +02:00
be5206c7ed feat(1a): Flex-Sprachmodus vollständig entfernen
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>
2026-06-25 02:51:17 +02:00
f3f9151894 fix(admin): Phase 0 — Log & Restart reparieren, Status auf Wahrheit
Wurzel-Ursache: Dienst läuft als System-Dienst, Code nutzte aber
systemctl/journalctl --user → Log leer, Restart wirkungslos.

- Log-WS (admin.py): --user → System-journalctl. Erfordert voice in
  Gruppe systemd-journal (siehe DEPLOYMENT.md 2.7.1).
- Restart (admin_llm.py): --user → sudo -n systemctl restart über eng
  begrenzte sudoers-Regel; is-active ebenfalls auf System-Dienst.
- Status (app.js): irrelevante Ollama/llama.cpp-Karte + Backend-Wechsel
  entfernt; zeigt jetzt echtes aktives LLM-Modell (aus Runtime-Config);
  Neustart in eigener "Wartung"-Karte (zieht in Phase 3 nach Konfiguration).
- DEPLOYMENT.md 2.7.1: sudoers-Regel + journal-Gruppe dokumentiert.

Live verifiziert: Log lesbar, Restart ohne Passwort, /health nach 3s zurück.
Tests: 214 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 02:27:18 +02:00
a6866a6e7f feat: UI-Hinweis bei Cartesia + nicht unterstützter Sprache
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>
2026-06-25 00:49:28 +02:00
91b9ef3374 Jamulix: Optimierungen übernehmen, lokale Voice-Modelle ignorieren 2026-06-24 22:01:38 +02:00
Dieter Schlüter
3f6bf53a7f refactor(ui): Geräte-STT entfernt + Menü-Redesign (schlanke Kopfzeile + ⋮-Sheet)
Geräte-STT (Web Speech API) entfernt — auf Android nur Cloud, Qualität < Whisper,
verwirrend. STT läuft jetzt überall serverseitig (Whisper). Geräte-TTS bleibt.
- Backend: allow_cloud_stt (config/runtime_config/api me) + Tests entfernt.
- Frontend: SpeechRecognition-Code, STT-Dropdown, "Lokal"-Button raus.

Menü-Redesign (verständlicher + passt auf Handys):
- Kopfzeile schlank: Mund-Icon + Titel + Sprache + ⋮-Menübutton (kein Überlauf mehr).
- ⋮ öffnet ein Einstellungs-Sheet: Ton als 3 Presets (📱 Im Gerät / ☁ Server /
   Beste Qualität) statt zweier kryptischer "Gerät"-Dropdowns; Neues Gespräch;
  Tag-/Nachtmodus; Konto (Identität, Admin, Abmelden).
- #tts bleibt als verborgenes Quell-Select -> bestehende TTS-Logik unverändert.
- Doku §5.1.2 neu, §6.3 STT-Hinweis. 167 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:26:20 +02:00
Dieter Schlüter
ab9c4f4938 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>
2026-06-20 20:08:10 +02:00
Dieter Schlüter
33d7189418 feat(admin): Audit-Logging schreibender Admin-Aktionen — Plan-Schritt 5
- app/audit.py: eigener Logger "va.audit" (eigener stdout-Handler -> Journal/Log-Tab).
  log_admin_action() protokolliert Aktion + Auslöser (SSO-Name oder admin-key).
- Verdrahtet in: config_set/config_reset (PUT/DELETE /admin/config),
  llm_backend_switch (+ _rejected), gateway_restart.
- Tests: caplog prüft Audit-Zeilen für config_set und abgelehnten Backend-Switch.
- Doku §7.5: Audit-Format im Log-Tab.

Schließt den Plan (Admin-gesteuerte LLM-/Gateway-Verwaltung, Schritte 1–5) ab. 167 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:50:55 +02:00
Dieter Schlüter
5b6f0f8ef2 feat(admin): Backend-Wechsel + Gateway-Neustart aus dem Admin-Panel — Plan-Schritt 3
- 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>
2026-06-20 18:39:59 +02:00
Dieter Schlüter
97ae0a5d34 feat(admin): LLM-/GPU-Status-Karte (read-only) — Plan-Schritt 2
- app/admin_llm.py: read-only Statusabfragen ohne sudo (docker ps, ollama ps,
  nvidia-smi, systemctl --user) mit sicheren Defaults bei fehlenden Tools.
- GET /api/admin/llm/status (require_admin): Backend, Modell, Backend-Status,
  geladene Ollama-Modelle, GPU-Auslastung, Gateway-Dienst-Status.
- Admin Status-Tab: LLM-Backend-Karte mit GPU-Balken.
- Tests: Auth-Gate + Antwortschema (160 grün).
- Doku §7.5: Status-Tab um LLM/GPU-Karte ergänzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:33:06 +02:00
Dieter Schlüter
878bf785dd feat: Geräte-TTS, native Stimmen, Favicon, Auth-Gate, UI-Fixes
Web-UI / TTS:
- Geräte-TTS ("📱 Gerät"): Antwort wird on-device vorgelesen (Web Speech
  API), Server sendet nur Text (text_only) -> spart Bandbreite/Kosten.
  Mobil-Default, geräte-lokale Speicherung, iOS-Autoplay-Freischaltung.
- Vorlese-Symbol (🔊) je Bubble: Hybrid-Replay (Assistent-PCM gecacht,
  Eingabe via /api/speak); SVG-Icon mit kontrastreicher Farbe.
- Kombiniertes Sprachmenü (Flex + feste Sprachen) statt separatem Modus-Menü.
- "Neues Gespräch"-Button (frische Session gegen Sprach-Trägheit).
- Dark-Mode: lesbare <option>-Popups (Kontrast-Fix).
- Favicon (SVG + PNG-Fallbacks) aus mund.png.

TTS-Backend:
- Sprache wird an alle TTS-Provider durchgereicht; Piper-Stimme folgt der
  Sprache; Chatterbox mehrsprachig + cross-lingual.
- Native Referenz-Stimmen je Sprache (config/voices/<lang>.wav, FLEURS CC-BY),
  loudness-normalisiert.

LLM-Sprache:
- Antwort folgt zuverlässig der gewählten Sprache (verstärkte Anweisung +
  Erinnerung an der letzten Nutzer-Nachricht gegen History-Trägheit).

Admin / Auth:
- Wörterbuch: alle 8 Sprachen, Zeilen editierbar, alphabetische Sortierung.
- Web-UI hinter Auth-Gate (Redirect auf SSO_LOGIN_URL / 401); Favicons offen.
- Log-Tab: Hinweis, wenn der systemd-Dienst nicht aktiv ist.
- Einstellungen: Hinweis "pro Nutzer überschreibbar" bei Sprache/Modus/Qualität.

Doku (BEDIENUNGSANLEITUNG.md): Geräte-TTS §6.5.0, Fix/Flex §6.6, native
Stimmen §6.5.3, llama.cpp<->Ollama-Wechsel §4.7, Auth/SSO §7.4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 13:12:04 +02:00
Dieter Schlüter
5d022aaf63 feat(admin): Phase 3 — Laufzeit-Konfiguration ohne Server-Neustart
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>
2026-06-19 01:39:36 +02:00
Dieter Schlüter
366d71ba26 feat(admin): Admin-Panel Phase 2 — Wörterbuch-CRUD, Live-Log, DB-Export, Metriken-Balken
- 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>
2026-06-19 01:15:28 +02:00
Dieter Schlüter
b38992cf28 feat(admin): vollständiges Admin-Panel in der Web-UI (Phase 1)
Ersetzt das rudimentäre Nutzer-Dropdown durch ein vollwertiges
Admin-Panel-Overlay mit 5 Tabs:

Nutzer:      Anlegen (Token einmalig), Umbenennen, Token-Reset,
             Löschen, Erinnerungen verwalten (anzeigen/add/delete)
Gespräche:   Nutzer → Sessions → Transkript (Chat-Bubble-Ansicht)
Notfälle:    Tabelle aller emergency_events mit Zeitstempel/Kategorie
Status:      Gateway-Status, Provider-Config, Laufzeit-Metriken
Metriken:    Nutzungsstatistik pro Nutzer + Gesamtsumme

Backend: 7 neue Admin-Endpunkte in admin.py, 5 neue Store-Methoden.
Design: Tailwind CSS, Tag/Nacht-Modus, responsiv.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 01:04:20 +02:00
Dieter Schlüter
6d63b8d063 fix(stt): CUDA-OOM vermeiden + Fehlermeldung bei leerer Transkription verbessern
- .env: FASTER_WHISPER_DEVICE=cpu gesetzt; llama.cpp belegt GPU 1 komplett,
  faster-whisper (small-Modell) laeuft problemlos auf CPU (~1-2s Latenz)
- ws.py: "empty transcript" -> "Keine Sprache erkannt — bitte erneut sprechen."

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 18:48:36 +02:00
Dieter Schlüter
68b02f42a1 feat(users): SSO-Nutzer-Personalisierung + Admin-Endpunkte
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>
2026-06-18 17:55:05 +02:00
Dieter Schlüter
b76ec76c45 feat(admin): Token-Reset-Endpunkt + Doku-Klarstellung zu Token-Verwaltung
- 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>
2026-06-18 17:36:41 +02:00
Dieter Schlüter
4970bdc85a feat(admin): DELETE /api/admin/users/{id} — Nutzer und alle Daten löschen
- Store.delete_user() löscht User + Sessions + Nachrichten + Erinnerungen + Nutzung
  (atomic, anonymer Nutzer geschützt)
- DELETE /api/admin/users/{user_id} (Admin-Key erforderlich)
  → 200 {"deleted":"..."} | 404 | 400 (anonymous)
- BEDIENUNGSANLEITUNG: $URL-Erklärung am Anfang, § 7.2 vollständig mit
  Anlegen/Anzeigen/Löschen-Beispielen inkl. realer Beispiel-Antworten

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 17:23:25 +02:00
Dieter Schlüter
887faa315e feat(admin): Header-Discovery per ?key= durchs SSO aufrufbar
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>
2026-06-18 05:19:41 +02:00
Dieter Schlüter
76df695111 feat(web): Remote-Web-UI mit Mikrofon + Forward-Auth (YunoHost-SSO)
- Minimale Web-UI (app/web/, vanilla, same-origin -> kein CORS): Text-Prompt +
  Mikrofon-Button (Aufnahme im Browser -> /ws/voice -> Antwort wird vorgelesen),
  Token-Streaming, PCM-Wiedergabe, Identitaet/Logout/Admin im Menue
- Forward-/Trusted-Header-Auth (app/auth.py): Identitaet aus SSO-Header, nur von
  TRUSTED_PROXY_IPS akzeptiert; sonst Token/Anonymous-Fallback. Auto-Provisioning
  via store.get_or_create_user_by_external_id (+ external_id-Spalte/Migration)
- /api/me um is_admin + sso_logout_url erweitert; GET /api/admin/users (Liste) und
  GET /api/admin/request-headers (SSO-Header-Discovery), Admin-gated
- StaticFiles-Mount; Config: TRUSTED_AUTH_HEADER/_PROXY_IPS, ADMIN_USERS, SSO_LOGOUT_URL
- WS-Auth liest Identitaet aus dem Handshake-Header
- Deploy: nginx-Vorlage (WS-Upgrade!) + deploy/README.md (HTTPS/SSO/Firewall/Discovery)
- Tests: Forward-Auth (Provisioning, Admin-Flag, Proxy-IP-Trust, 401/403, Static)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 04:37:16 +02:00
Dieter Schlüter
855fc71a1e feat(safety): zweistufige Notfall-Erkennung mit LLM-Klassifikation
- app/safety/llm_classifier.py: LLM klassifiziert Aeusserungen (medical/self_harm/
  help/none) mit Konfidenz-Schwelle, eigener JSON-Prompt, Reasoning aus, temp 0
- emergency.py: Eskalation in _escalate() gebuendelt (Log/Metrik/Webhook, source-Label);
  schedule_llm_emergency_check() faehrt Stufe 2 als Hintergrund-Task NUR wenn die
  Stichwort-Heuristik nichts fand -> faengt verpasste Formulierungen ohne Mehrlatenz
- /ws/voice schickt bei LLM-Treffer ein nachgelagertes emergency-Event (source=llm)
- Konfig: EMERGENCY_LLM_ENABLED/_PROVIDER/_MIN_CONFIDENCE
- Tests: Parsing, Konfidenz-Schwelle, Skip-bei-Keyword-Treffer, Eskalation+Callback,
  disabled, kaputtes JSON
- Doku: README + Architektur-Roadmap (Punkt 6)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 03:24:25 +02:00
Dieter Schlüter
aa64ccf585 feat(memory): automatische Erinnerungs-Extraktion aus Gespraechen
- 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>
2026-06-18 03:15:08 +02:00
Dieter Schlüter
9d0019274a feat(voice_loop): --voice-Option + saubere Default-Stimme je TTS-Provider
- 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>
2026-06-17 23:42:23 +02:00
Dieter Schlüter
7c1f00662c feat: Audio-Streaming als Default (Admin abschaltbar via AUDIO_STREAM_DEFAULT)
- Config audio_stream_default=True; _run_turn nutzt es, wenn die Anfrage audio_stream
  nicht explizit setzt (explizite Anfrage gewinnt)
- voice_loop: immer durchgehender Player (spielt 1 oder N Haeppchen luekenlos);
  --stream-audio / --no-stream-audio als Tri-State (sonst entscheidet der Server-Default)
- conftest: audio_stream_default=False fuer deterministische Tests; neuer Test fuer
  Default-an-Streaming ueber /ws/voice; 67 Tests gruen
- Doku + .env.example aktualisiert (AUDIO_STREAM_DEFAULT, --no-stream-audio)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 18:07:36 +02:00
Dieter Schlüter
2d288be630 fix(ws/voice): start-Frame-Optionen (Provider, audio_stream, language) ehren
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>
2026-06-17 17:32:12 +02:00
Dieter Schlüter
1eb79c1f09 feat: Tageskontingent und Notfall-Eskalation (#6)
- Quota (app/quota.py): Anfragen pro Nutzer/Tag (usage-Tabelle), DAILY_REQUEST_LIMIT,
  pro Nutzer via prefs uebersteuerbar; 429 (REST) bzw. error-Event (WS); Metrik
- Notfall (app/safety/emergency.py): heuristische Erkennung (de/en); Log im Store
  (emergency_events) + optionaler Webhook (best-effort) + X-Emergency/emergency-Event;
  Metrik emergency_total; Notfaelle umgehen das Kontingent
- verdrahtet in chat/speak/transcribe + WS-Turns
- Tests: 64 gruen (+6); Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur, .env.example)

Hinweis: Notfall-Erkennung ist eine Heuristik (kein Lebensretter); erkannte Texte
sind sensibel -> DSGVO beachten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 05:29:30 +02:00
Dieter Schlüter
6422444017 feat: Resilienz (Fallback-Ketten) und Metriken (#5)
- Fallback-Provider (app/providers/fallback.py) fuer STT/LLM/TTS: Provider-Kette
  der Reihe nach; Config *_FALLBACK; build_orchestrator baut Ketten (dedupliziert)
- LLM-Stream-Fallback nur solange kein Token gesendet wurde
- Metriken (app/metrics.py): In-Memory Counter/Timer, keine externe Dependency
- HTTP-Middleware (Requests/Latenz/Status je Pfad); Pipeline-Stufen-Timing stt/llm/tts;
  Fallback-/Fehlerzaehler; GET /api/metrics (JSON + Prometheus)
- Tests: 58 gruen (+6); Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur, .env.example)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 05:19:07 +02:00
Dieter Schlüter
093da817d8 feat: Barge-in/Turn-Manager und VAD-Aeusserungserkennung (#4 Ausbau)
- Barge-in: Antwort-Turn als abbrechbarer asyncio.Task; {"type":"interrupt"} oder
  neue Eingabe bricht laufende Antwort ab -> interrupted-Event (/ws/chat + /ws/voice)
- VAD (app/audio/vad.py): energie-basierte Stille-Erkennung (reines Python, int16-PCM)
- /ws/voice opt-in {"type":"start","vad":true}: automatisches Aeusserungsende ohne end
- Tests: 52 gruen (+5: VAD-Unit, Barge-in, VAD-Auto-Segmentierung)
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur)

Offen (schwere Deps/Dienste): echte partielle Live-Transkripte (Streaming-STT),
WebRTC (aiortc). STT laeuft heute pro Aeusserung.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 05:06:58 +02:00
Dieter Schlüter
9340d3f998 feat: Audio-Eingang über WebSocket (/ws/voice) - Sprach-zu-Sprach (#4 Ausbau)
- /ws/voice: binaere Audio-Frames puffern, {"type":"end"} -> STT-Transkription
  -> transcript-Event -> bestehende Antwort-Pipeline (ack/token/audio/semantic/done)
- ws.py refaktoriert: gemeinsame _resolve + _run_turn fuer /ws/chat und /ws/voice
- stream/audio_stream auch fuer Sprach-Turns nutzbar; mehrere Utterances pro Verbindung
- Tests: 47 gruen (+2: Transkript->Antwort, leerer Puffer)
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur)

Hinweis: STT pro Aeusserung (gepuffert); partielle Live-Transkripte (Streaming-STT
mit VAD) bleiben naechster Increment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 04:51:49 +02:00
Dieter Schlüter
b5913b0a44 feat: Audio-Streaming (chunked TTS) über WebSocket (#4 Ausbau)
- SentenceChunker (pipeline/sentence_chunker.py): inkrementelle Satzsegmentierung
- Orchestrator.chat_stream(on_audio): satzweise TTS, Audio-Chunk pro fertigem Satz;
  Gesamtaudio zusaetzlich an den Output-Endpunkt
- WS /ws/chat {"audio_stream":true}: audio-Events (json seq + binaerer Frame) live,
  kein finales Vollaudio; mit stream kombinierbar
- Tests: 45 gruen (+2: Sentence-Chunker, Audio-Streaming)
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 04:43:50 +02:00
Dieter Schlüter
379e002460 feat: Token-Level-LLM-Streaming über WebSocket (#4 Ausbau)
- LLMProvider.stream: Basis-Default (Fallback über complete) + SSE-Streaming
  fuer OpenRouter und lokalen OpenAI-kompatiblen Provider; gemeinsamer Parser sse_delta
- Orchestrator.chat_stream: LLM-Token live via on_token-Callback, danach
  Spoken-Adapter/Normalizer/TTS/Output; Fallback fuer Provider ohne stream
- WS /ws/chat: opt-in {"stream":true} -> ack -> token* -> semantic -> audio -> done
- Tests: 43 gruen (+5: SSE-Parsing, Default-Fallback, WS-Token-Flow)
- Doku aktualisiert; .gitignore: *.wav (generierte Audio-Ausgaben)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 04:37:37 +02:00
Dieter Schlüter
531b57e08d feat: Langzeit-Erinnerungen (#3b) und WebSocket-Streaming-Chat (#4, erster Increment)
#3b Langzeit-Erinnerungen:
- Store: memories-Tabelle + add/get/delete_memory (pro Nutzer)
- API: GET/POST/DELETE /api/me/memories
- chat.py injiziert Nutzer-Erinnerungen als System-Kontext ins LLM (sessionunabhaengig)
- ?debug zeigt memories_len

#4 Echtzeit (erster Increment):
- WS /ws/chat: dauerhafter Kanal, Event-Folge ack -> semantic -> audio (binaer) -> done
- Auth (Token-Query), Session-Gedaechtnis und Erinnerungen wie bei POST /api/chat
- Fehler als error-Event (422/403/502)

- Tests: 38 gruen (Erinnerungs-CRUD/Injektion, WebSocket-Streaming/Auth)
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 04:26:55 +02:00
Dieter Schlüter
16a964032e feat: Konversationsgedaechtnis - Kurzzeit-Gespraechsverlauf pro Session
- Store: messages-Tabelle + append_message/get_recent_messages (mit Mandanten-Schutz)
- LLMProvider.complete um history erweitert (openrouter + local bauen system+history+user)
- Orchestrator.chat_text reicht den Verlauf ans LLM weiter
- chat.py: bei session_id letzte HISTORY_MAX_MESSAGES laden, danach User-/Assistant-Turn speichern
- ohne session_id weiterhin zustandslos; ?debug zeigt history_len
- Config HISTORY_MAX_MESSAGES (Default 10)
- Tests: 32 gruen (3 neue Gedaechtnis-Tests)
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 04:16:35 +02:00
Dieter Schlüter
e0e69fdf15 feat: Cloud-Fundament - Auth, Persistenz und Mandanten-Trennung
- SQLite-Store (app/store.py) hinter Store-Interface: Nutzer + Sessions persistent
- Bearer-Token-Auth (app/auth.py); Nutzerverwaltung via Admin-Key (POST /api/admin/users)
- GET /api/me, PUT /api/me/prefs (dauerhafte Nutzer-Praeferenzen)
- chat/speak/transcribe/sessions auth-geschuetzt; Mandanten-Trennung (fremde Session -> 403)
- Route-Aufloesung: Defaults < Profil < ENV < Nutzer-Prefs < Session < Request
- SessionManager (in-memory) durch Store ersetzt
- AUTH_ENABLED-Schalter (prod an, dev/Tests aus); DB_PATH/ADMIN_API_KEY
- Doku aktualisiert (README, BEDIENUNGSANLEITUNG, Architektur, deploy-env); data/ gitignored
- Tests: 29 gruen (Auth, Mandanten, Persistenz, Routing)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 02:14:25 +02:00
Dieter Schlüter
293ed257db Initial commit: Voice Assistant Gateway mit Konfig-/Routing-Fundament
- FastAPI-Gateway mit REST-Endpunkten (chat/speak/transcribe/devices/sessions/config)
- Geschichtete Konfiguration mit Profilen (local-dev/hybrid/cloud) via TOML + ENV
- Registry-Pattern + einheitliche Route-Aufloesung (Default->Profil->ENV->Session->Request)
- Device Router (strikt, Singleton) und Output-Lifecycle im Orchestrator
- OpenRouter-Adapter (STT multipart/LLM/TTS) + lokale Provider-Stubs
- Regelbasierte Pipeline (Cleaner/Spoken-Adapter/TTS-Normalizer)
- 22 automatisierte Tests; Doku: README, BEDIENUNGSANLEITUNG, Architektur
- Secrets ausschliesslich ueber Umgebung; .env und lokale config gitignored

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 01:48:56 +02:00