Commit graph

30 commits

Author SHA1 Message Date
66d6708f59 feat(costs): $-Tagesbudget pro Nutzer (Stufe 3)
cost_daily_limit_usd (Pref > global; 0 = unbegrenzt) in quota.py
(cost_within_budget, cost_limit). ws.py-Gate: Web-Such- ODER Kostenbudget
aufgebraucht -> Web-Suche fuer den Turn aus + freundlicher Hinweis. Admin:
Schema-Feld + list_users + UI-Eingabe ($/Tag) + globales Live-Setting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:17:54 +02:00
88c8c9df7f feat: Web-Such-Budget pro Nutzer/Tag
Neue Tabelle web_search_usage + Store-Zaehler (web_search_count /
add_web_search_usage, kaskadiert in delete_user). Pro-Nutzer-Limit
web_search_daily_limit (Pref > global; 0 = unbegrenzt) in quota.py
(web_search_within_budget). ws.py: Gate vor _resolve (Budget aufgebraucht ->
Web-Suche fuer den Turn aus) + Zaehlung nach dem Turn (trace.web_searches,
gezaehlt im Orchestrator je on_citations). Admin: Feld + UI-Eingabe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:22:06 +02:00
703a695bed feat(llm): Web-Suche per Tool-Calling (Weg 2) — Sonar + ToolCallingLLM
Frische-/Web-Such-Funktion: Das zentrale Modell entscheidet selbst via
web_search-Tool, ob es tagesaktuelle Fakten braucht, holt sie über
perplexity/sonar und formuliert die Antwort in Persona (Augment).

- SonarTool (app/tools/web_search.py): Fakten via perplexity/sonar,
  Citations als Metadaten, honest-punt-Sentinel bei Fehler/Timeout.
- ToolCallingLLM (app/providers/llm/tool_calling.py): agentischer Loop als
  LLMProvider; complete() + gestreamtes stream() mit SSE-Tool-Assembler;
  Persona- + Trigger- + Vorrang-Prompt (Tool-Ergebnis schlaegt Gedaechtnis).
- Verdrahtung: Registry-Eintrag openrouter-tools; web_search_enabled
  (global an, pro Nutzer/Profil abschaltbar) via Route-Layering;
  build_orchestrator waehlt tool-faehig vs. plain, Fallback-Kette erhalten.
- Filler: ephemerer Beruhigungssatz beim Tool-Start (sofort angezeigt UND
  gesprochen als Satz null), nie in semantic_response/History; on_tool_start
  defensiv durch die stream()-Kette gefaedelt (kein Bruch bestehender Provider).
- Modellwechsel: Standard auf mistralai/mistral-small-3.2-24b-instruct
  (tool-faehig; im Eval einziger Recall-Gate-Passer). 2501 ist tool-unfaehig.
- Eval-Harness (eval/tool_calling/): Datensatz + Runner zur Modellauswahl.

Doc: Docs/weg2-tool-calling.md. Tests: 290 gruen.
Offen (Schritt 5): Koreferenz-Vorstufe (nl-Pronomen) + Metrik-Zaehler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:37:08 +02:00
5e128a74ce feat(menu): nicht nutzbare TTS-Provider ausblenden + GPU-Erkennung
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>
2026-06-27 04:16:21 +02:00
8c152ee77c feat(notruf): Wiederauslöse-Sperre + grüner Hinweis; "Neues Gespräch" in die Kopfzeile
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>
2026-06-26 15:58:17 +02:00
f30b9b56c7 feat(stt): bessere Spracherkennung — small-Modell, VAD, feste Sprache
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>
2026-06-25 19:36:17 +02:00
1816a26734 feat: Notruf-Eskalation per SMS/Anruf (provider-agnostischer Webhook)
Zweiter Benachrichtigungskanal neben der E-Mail: bei einem Notruf geht
zusätzlich ein JSON-POST an EMERGENCY_WEBHOOK_URL mit Kanälen (sms,call),
Kontakt-Telefonnummern und fertigen SMS-/Anruf-Texten. Den konkreten
Gateway (Twilio, seven.io, sipgate, eigener Dienst) verdrahtet der
Empfänger dahinter — kein Provider-Lock-in im Code.

- config: EMERGENCY_WEBHOOK_URL aktiviert + _TOKEN (Bearer), _CONTACT_PHONE
  (CSV/E.164), _WEBHOOK_CHANNELS (default sms,call)
- emergency.py: async httpx-POST (best effort), Telefon-Auflösung analog
  zur E-Mail (Pref emergency_phones > Default), notified = email OR webhook;
  Hinweise kanal-neutral formuliert; Response um webhook_sent + channels ergänzt
- Tests: Webhook-Versand inkl. Payload/Bearer-Header + "ohne URL kein POST"
- Doku: §10 + Env-Var-Tabelle aktualisiert (zwei Kanäle, Payload-Format)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 10:17:49 +02:00
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
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
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
b2576fd465 feat(llm): Live-Parameter top_p + Live/Restart-Kennzeichnung — Plan-Schritt 4
- local_llm_top_p (Default 0.9) durch Config -> runtime_config (RUNTIME_SETTABLE,
  live ohne Neustart) -> local-openai-compatible Payload (top_p).
- Admin-Einstellungen: top_p als Slider (0–1); Hints "wirkt sofort (kein Neustart)"
  für temperature/top_p.
- Doku: top_p + Klarstellung Live-Parameter (temp/top_p/max_tokens) vs.
  Startup-Wert Kontextfenster (OLLAMA_CONTEXT_LENGTH / llama.cpp -c).

Verifiziert: top_p im Payload, Live-Config-Round-Trip (PUT/DELETE). 165 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:46:33 +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
5f01607b79 feat(tts): Chatterbox-Provider (hohe Qualitaet + Voice-Cloning) anbinden
Loest #7. Der chatterbox-Stub wird durch eine echte Anbindung an den lokalen
Chatterbox-HTTP-Dienst ersetzt (POST /speak -> /status pollen -> GET /audio, WAV).
no_playback=true -> der Dienst spielt nicht lokal ab, liefert nur Bytes. WAV->PCM
(24 kHz, Resampling bei Bedarf). Waehlbar via tts_provider=chatterbox; piper bleibt
der schnelle Default (chatterbox ist ~echtzeit-langsam, dafuer klonbare Stimme).

- config: CHATTERBOX_BASE_URL/_VOICE/_LANG/_SPEED/_TIMEOUT; Registry verdrahtet
- Tests: gemockter httpx (Synthese, WAV/Resample, Job-Fehler, Stimmenwahl)
- Doku: README, Architektur (#7), deploy/README (GPU-Pinning per UUID, no_playback)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 09:44:31 +02:00
Dieter Schlüter
881a5ac2de feat(auth): Forward-Auth via JWT-Cookie (YunoHost yunohost.portal)
Discovery zeigte: YunoHost reicht den Usernamen nicht als Header durch, sondern im
signierten Cookie "yunohost.portal" (JWT, Claim "user"). Die Forward-Auth liest jetzt
die Identitaet aus Header ODER Cookie - nur von der Proxy-Quell-IP akzeptiert.
HS256-Signaturpruefung optional via TRUSTED_AUTH_JWT_SECRET (stdlib hmac, kein Dep).

- config: trusted_auth_cookie / _cookie_claim / _jwt_secret
- nginx-Vorlage + deploy/README: keine Identitaets-Header-Zeile mehr noetig; SSO-Schutz
  der Subdomain ist Pflicht (sonst Spoofing)
- Tests: Cookie-Extraktion, Signaturpruefung, Proxy-IP-Trust

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 08:44:33 +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
28c729f1d4 feat(llm): lokales llama.cpp-Modell (va_llm) als zentrale, sprachoptimierte KI
- scripts/llm-server/: start/stop/status fuer llama.cpp-Server (Port 8001, GPU 1,
  Modell Qwen3.6-35B-A3B-Uncensored, Alias va_llm) - alles per ENV ueberschreibbar
- Defaults auf den lokalen Server umgestellt (config.py, .example-Configs, .env.example)
- Provider local-openai-compatible sprachoptimiert: Reasoning aus
  (chat_template_kwargs.enable_thinking=false) + knapper Sprach-System-Prompt,
  optional max_tokens/temperature - Antwort ~9x schneller, kurze vorlesbare Texte
- Makefile-Targets llm-up/llm-down/llm-status
- Doku (README, BEDIENUNGSANLEITUNG) auf llama.cpp statt Ollama aktualisiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 02:57:57 +02:00
Dieter Schlüter
c9d8552ec7 feat(tts): Aussprache-Normalisierung vor Piper (Ordinalia/Einheiten/Abk./Lexikon)
In-Process-Layer ausgebaut statt neuer Lib/CLI. Leitprinzip: nicht duplizieren,
was espeak-ng schon kann (Kardinal-/Dezimalzahlen bleiben unangetastet) -- nur die
belegten Luecken fuellen.

- german_numbers.py: deutsche Ordinalzahlen 1.-31. (attributiv/adverbial)
- tts_normalizer.py: Ordinalia (Datum '1. Mai'->'erster Mai', Folgen '1. 2. 3.'->
  'erstens, zweitens, ...'), Einheiten nach Zahl (kg/km/km-h/...), Abkuerzungen
  (Dr./z.B./usw.), optionales YAML-Lexikon (config/pronunciation.<lang>.yaml).
  Provider-abhaengige Stufen auto|full|light|off (TTS_NORMALIZE_LEVEL): piper=full,
  Cloud=light (laesst Zahlen/Abk. fuer das Cloud-Modell in Ruhe).
- spoken_response_adapter.py: nummerierte Listen -> Ordinalwoerter statt Loeschen.
- sentence_chunker.py: trennt nicht mehr nach Ziffer+Punkt, Einzelbuchstabe+Punkt
  ('z. B.', Initialen) oder bekannten Abkuerzungen -> behebt das Streaming-Symptom
  ('1.' wurde als eigener 'Satz' zu 'eins').
- orchestrator/dependencies: normalize_level durchgereicht (auto: piper->full).
- Tests: tests/test_tts_normalizer.py + Chunker-Faelle (85 gruen).
- Doku: BEDIENUNGSANLEITUNG (Aussprache verbessern), .env.example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 23:01:41 +02:00
Dieter Schlüter
cca423dac3 feat(tts): echtes lokales TTS via piper (kein Stub mehr)
PiperTTSProvider ruft das piper-Binary (--output-raw) async auf, liest die native
Sample-Rate aus der .onnx.json und resampelt per ffmpeg auf 24000 Hz (Gateway-Norm).
Nicht passende Stimmen (z. B. Cloud-Stimme 'Zephyr' aus der Route) fallen auf die
konfigurierte Default-Stimme zurueck. Damit ist eine voll-lokale Konstellation
(faster-whisper + Ollama + piper) moeglich -> keine API-Kosten, max. Datenschutz.

- config: PIPER_BIN/PIPER_VOICES_DIR/PIPER_VOICE/TTS_SAMPLE_RATE (+ .env.example)
- dependencies: piper-Factory mit Settings verdrahtet
- tests: tests/test_piper_tts.py (offline, Fake-Binary; Resample-Test skippt ohne ffmpeg);
  e2e/auth-Tests nutzen jetzt einen Stub-TTS statt 'piper' als Pseudo-Stub
- docs: README, BEDIENUNGSANLEITUNG (voll-lokal-Beispiel), Architektur-Roadmap

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 22:11:20 +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
c25f081f9f feat(stt): echtes lokales STT via faster-whisper (optional .[local])
- FasterWhisperProvider implementiert (CTranslate2): Modell prozessweit gecacht
  (lru_cache), Transkription in asyncio.to_thread; robuster CPU-Fallback wenn
  GPU/compute_type nicht verfuegbar
- Config: FASTER_WHISPER_MODEL/DEVICE/COMPUTE_TYPE (Defaults base/auto/default)
- pyproject: optionales Extra [local] = faster-whisper
- Test: transcribe-Endpunkt nutzt jetzt einen Stub-STT (kein Platzhalter mehr)
- Doku: Voraussetzungen, Hybrid-Beispiel (STT+LLM lokal, TTS remote), Architektur/README

Live verifiziert: TTS->WAV->faster-whisper transkribiert korrekt (de); voller Hybrid
ueber /ws/voice (faster-whisper + Ollama llama3.2 + OpenRouter-TTS) funktioniert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:28:20 +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
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