Commit graph

218 commits

Author SHA1 Message Date
6ab24977f1 feat(deploy): Installations-Skript + Anleitung für natives YunoHost-SSO (jamulix.net)
Alternative zum Authelia-Target (voice.jamulix.de): eigenständiges
Ein-Maschinen-Setup, das YunoHosts Portal-Cookie (yunohost.portal) direkt
nutzt statt Authelia. deploy/install_va_on_yunohost.sh automatisiert Code-
Deploy, venv, systemd-Service, YunoHost-Domain und nginx-Reverse-Proxy inkl.
WebSocket-Support; INSTALLATION_AUF_YUNOHOST_SERVER.md dokumentiert Ablauf,
Sicherheitsmodell und offene manuelle Schritte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 10:26:12 +02:00
55d07187bb fix(api): /api/chat 500 (NameError emergency) beheben + Version 0.3.7
Toter Verweis auf die in 3389f75 entfernte Auto-Notfallerkennung entfernt
(if emergency/X-Emergency-Header). /ws/chat war nicht betroffen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 02:54:28 +02:00
f1eec47b9b chore: Version 0.3.6 + CHANGELOG (Modell-Browser) + qwen3.5-35b-a3b eval-verifiziert
CHANGELOG-Eintrag fuer den Cloud-Modell-Browser (0.3.6); qwen/qwen3.5-35b-a3b
in EVAL_VERIFIED (Tool-Calling-Test bestanden).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 02:49:28 +02:00
a87dc75a46 feat(admin): Modell-Browser (Cloud) mit Tool-Badge, Preisen & $/Antwort
Phase 1 (Cloud) des Modell-Auswahlmenues:
- Token je Turn im CostMeter (record_openrouter_cost liest prompt/completion_
  tokens) -> globales Turn-Profil (turn_profile, EMA) fuer die $/Antwort-Schaetzung.
- app/model_menu.py: gecachter OpenRouter-Katalog -> Chat-Modelle mit tool_supported
  (supported_parameters), eval_verified (kuratiert), Up/Down-Preisen pro Mio. Token
  und geschaetzter $/Antwort (Profil x Modellpreise).
- GET /api/admin/model-menu; durchsuchbarer Browser im System-Tab, "Verwenden"
  setzt openrouter_llm_model (Freitext/Preset bleiben).
Lokale Modelle (Durchsatz/Strom): Phase 2. Konzept in Ideen/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:37:36 +02:00
94740daa77 chore: Planungs-Docs in lokale Ideen-Sammlung verschieben
MULTITOOL_AUSBAU_PLAN.md und VERSCHLUESSELUNGS_ANALYSE.md nach Ideen/
(gitignored) verschoben -> aus dem Repo genommen, lokal erhalten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 20:47:52 +02:00
f4fbac9673 docs: Verschluesselungs-Analyse (DB & Backups, vHost + Heim-Server)
Bedrohungsmodell, Whole-DB vs. pro User, Methoden (SQLCipher/Volume/LUKS),
Schluesselmanagement (Start-Injektion/TPM), Off-Site-Backups, Heim-Server-Delta.
Diskussionsgrundlage, kein Code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 20:37:48 +02:00
9156962ee3 docs: CHANGELOG 0.3.5 + Version 0.3.5
Kostenerfassung vervollstaendigt (Filler-Uebersetzung exakt, OpenRouter-TTS
geschaetzt). Version 0.3.4 -> 0.3.5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 19:27:08 +02:00
a1e49dde11 feat(costs): Filler-Übersetzung + OpenRouter-TTS kostenerfasst
- fillers.py translate_pool: usage:{include:true} + Ist-Kosten (Kategorie llm).
- OpenRouter-TTS: geschaetzte Kosten (Zeichen × openrouter_tts_usd_per_char,
  Kategorie tts) — der Audio-Endpunkt liefert keine inline-usage. Preis (Default
  ~$15/1M Zeichen) live-setzbar; an Modellpreis anpassbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 19:25:34 +02:00
77f0ec90b5 docs: CHANGELOG 0.3.4 + Version 0.3.4
Admin-Loeschfunktionen (pro Nutzer Statistik/Erinnerungen, Bulk alle
Nutzerdaten inkl. Notruf-Protokolle) und alphabetische Sortierung
(Nutzerliste + Statistik-Tabelle). Version 0.3.3 -> 0.3.4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 19:13:05 +02:00
ce9ada63c9 feat(admin): Bulk-Wipe inkl. Notruf-Protokolle + Statistik-Tabelle alphabetisch
- wipe_all_user_data loescht zusaetzlich emergency_events (Notruf-Protokolle).
  Audit-Log bleibt unberuehrt (Logger-Stream, kein DB-Eintrag).
- Nutzungsstatistik-Tabelle alphabetisch (case-insensitive); die "Anfragen je
  Nutzer"-Balken bleiben bewusst nach Aktivitaet sortiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 18:56:35 +02:00
c337ea4309 feat(admin): Bulk "alle Nutzerdaten löschen (Konten behalten)" + Nutzerliste alphabetisch
- DELETE /api/admin/user-data: loescht fuer ALLE Nutzer Verlauf, Statistik
  (usage/web_search_usage/cost_usage) und Erinnerungen; Konten bleiben.
  Gefahrenzone-Knopf mit doppelter Bestaetigung, audit-geloggt.
- list_users jetzt ORDER BY LOWER(display_name) -> Nutzerkarten alphabetisch
  (ohne Gross-/Kleinschreibung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:43:56 +02:00
8fb5e42e1b feat(admin): Knöpfe "Statistik zurücksetzen" + "Erinnerungen löschen" pro Nutzer
DELETE /api/admin/users/{id}/stats (usage/web_search_usage/cost_usage) und
.../memories (alle Erinnerungen) — require_admin, audit-geloggt; Konto, Verlauf
und (jeweils) die andere Datenart bleiben. Store-Methoden delete_user_stats/
delete_user_memories + zwei UI-Buttons je Nutzerkarte.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:29:42 +02:00
f83c04f3d6 docs: CHANGELOG 0.3.3 + Version 0.3.3
Einstellbare Tarife, local_llm_base_url live-setzbar, 15 weitere Filler,
Mitteilung bei "kein Treffer". Version 0.3.2 -> 0.3.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:15:30 +02:00
880383ca5f feat(admin): local_llm_base_url live-setzbar (RUNTIME_SETTABLE)
Lokale LLM-Basis-URL ohne Deploy umstellbar — wirkt pro Request, da der
Provider je Anfrage aus runtime_settings gebaut wird. Wert unveraendert
(localhost Ollama); Heim-Host kann kuenftig per Admin-Live-Config gesetzt werden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:13:42 +02:00
f875e421de feat: Tarife einstellbar + 15 weitere Filler + Mitteilung bei Leer-Antwort
- Strompreis (EUR/kWh), Notruf-Kosten (EUR/Alarm), Cartesia ($/Zeichen) jetzt im
  Admin live einstellbar (RUNTIME_SETTABLE).
- OPENING-Beruhigungssaetze 12 -> 27 (random.choice; formale "Sie"-Uebersetzung
  ersetzt beim Neustart den alten "dich"-Cache).
- Leer-/kein-Treffer-Antwort: statt Stille eine freundliche lokalisierte
  Mitteilung ("Dazu konnte ich keine Informationen finden."), gesprochen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:53:46 +02:00
363d0ab3c9 docs: CHANGELOG 0.3.2 (Pro-Nutzer-Kostenerfassung) + Version 0.3.2
Kostenerfassung (OpenRouter-Ist-Kosten + geschaetzte Quellen Strom/Cartesia/
Notruf), $-Tagesbudget und Statistik-Spalte "mit Websuche" dokumentiert.
Version 0.3.1 -> 0.3.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:31:31 +02:00
d45e2c9ae3 feat(costs): Stufe 2 — geschätzte Quellen (lokaler Strom, Cartesia, Notruf)
Lokales LLM: Call-Dauer gemessen -> Stromkosten je Job ((max-idle)*load*n/
3.6e6*Preis(EUR/kWh)*usd_per_eur), Kategorie llm_local. Cartesia-TTS: Zeichen x
cartesia_usd_per_char (tts). Notruf: pro Webhook-Alarm 0,20€*usd_per_eur
(alert), nur Doku, nie Sperre. Helfer in costs.py; neue Settings (GPU-Watt/Last/
Strompreis/usd_per_eur/cartesia/alert). Statistik: "exakt vs. geschätzt"
(Tooltip + Summenzeile, Kategorien tts/alert/llm_local). Tests: 318.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:27:50 +02:00
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
d87f0ce424 feat(costs): Pro-Nutzer-Kostenerfassung Stufe 1 (OpenRouter-Ist-Kosten)
CostMeter via ContextVars (app/core/costs.py, multitool-ready). Alle
OpenRouter-Calls (LLM, Sonar, Koreferenz, Fallback) senden usage:{include:true}
und buchen die Ist-Kosten je Kategorie (llm/web_search) — inkl. finalem
Streaming-usage-Chunk. cost_usage-Tabelle (user/day/category) + Store-Methoden;
ws.py/chat.py start_meter pro Turn + persist_costs. Statistik: Spalten
"Kosten (Monat)" + "Ø/Anfrage" + Gesamt. Live: 1 Such-Turn ~ $0,0052
(web_search dominiert, LLM ~ $0,00007). Tests: 318.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:08:45 +02:00
21a5250282 docs: Konzept Pro-Nutzer-Kostenerfassung (USD)
CostMeter via ContextVars (multitool-ready), OpenRouter-Ist-Kosten inline,
geschaetzte Quellen (Cartesia/Notruf), cost_usage-Tabelle, Darstellung
(laufender Monat) + $-Tagesbudget. Docs/kosten-erfassung.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:57:20 +02:00
d74e94e6a4 feat(admin): Nutzungsstatistik um "mit Websuche" je Nutzer
get_all_usage liefert total_web_searches (Summe aus web_search_usage);
Statistik-Tabelle + Gesamtzeile zeigen die neue Spalte "mit Websuche".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:51:29 +02:00
d738921d25 docs: CHANGELOG 0.3.1 (Web-Such-Verwaltung pro Nutzer) + Version 0.3.1
Verlauf-loeschen, Pro-Nutzer-Web-Suche an/aus, Web-Such-Tagesbudget und
freundliche Hinweise dokumentiert. Version 0.3.0 -> 0.3.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:32:56 +02:00
4f1e15176b feat: freundliche Hinweise bei abgeschalteter/aufgebrauchter Web-Suche
Ist die Web-Suche bei einer offensichtlich tagesaktuellen Frage
(should_force_search) nicht verfuegbar, sendet ws.py einen {type:"notice"}-
Event mit einer kleinen, lokalisierten Senioren-Systemnachricht ("Web-Suche
aus" bzw. "Tageskontingent aufgebraucht"); Frontend zeigt sie als System-Bubble.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:25:10 +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
5609741039 feat(admin): Web-Suche pro Nutzer ein-/ausschaltbar
web_search_enabled in AdminUserPrefsUpdate (war nicht im Schema -> wurde wie
frueher voice_gender still verworfen) + in der /admin/users-Antwort (Checkbox-
Initialzustand, Default = globaler Wert) + Checkbox im Admin-Nutzer-Bereich
(speichert sofort). resolve_route respektierte das Pref schon -> wirkt jetzt
durchgaengig (AUS = plain Provider, AN = ToolCallingLLM).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:12:55 +02:00
69894b1e7b feat(admin): "Verlauf löschen" pro Nutzer (Konto bleibt)
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>
2026-06-30 09:08:43 +02:00
15d8714105 chore(tts-de): Aussprache "circa" -> "zirka"
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 08:11:40 +02:00
eea25455cb docs: Weg-2 (Web-Suche) dokumentiert + Version 0.3.0
Web-Such-/Tool-Calling-Funktion in der Doku nachgezogen: CHANGELOG (0.3.0),
README, CLAUDE.md (Querschnitt), Architektur-Doc (§6.1), Bedienungsanleitung
(§5.5 Nutzer/Admin) und DEPLOYMENT (Tool-Faehigkeit des Modells).
Version 0.2.0 -> 0.3.0 (pyproject.toml).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 08:11:40 +02:00
b70d6f364a feat: schedule_hours-Backstop, zentrale Datum/Uhrzeit, Filler-Timing
1) schedule_hours-Nudge: search_backstop erkennt jetzt auch Oeffnungszeiten,
   "hat X geoeffnet", Notdienst und Fahrplaene/naechste Abfahrt (de/en/nl/fr) und
   erzwingt dafuer die Suche. +6 Offline-Tests.

2) Zentrale Datum-/Uhrzeit-Auskunft (app/core/clock.py, now_context()): jedes
   Modul kann das aktuelle Datum + Uhrzeit (lokale TZ, Wochentag) erfragen.
   In die System-Prompts aller antwortenden LLMs injiziert (ToolCallingLLM,
   OpenRouter, lokal) -> relative Zeitangaben ("heute", "morgen", "in 3 Stunden",
   "naechsten Montag") werden aufgeloest. Live: "Welcher Wochentag/Datum?" ->
   korrekt OHNE Web-Suche.

3) Filler-Timing: Mindestabstand 10 s zwischen Opening und Geduldssaetzen
   (FILLER_PATIENCE_INTERVAL 4->10). Sobald die Antwort vorliegt, wird ein
   anstehender Filler/Geduldssatz NICHT mehr ausgegeben (answer_started-Gate).

Tests: 318 gruen. Doc §5.4/§5.7/§8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:43:08 +02:00
48a79da12f feat(llm): Härtung gegen tool-unfähiges Modell (no-tool-endpoints-404)
Waehlt jemand (Admin/Preset) ein Modell ohne Tool-faehigen Endpoint, lieferte
OpenRouter ein 404 "No endpoints found that support tool use" -> bisher 4x Retry
mit Backoff und danach stille, konfident-veraltete Fallback-Antwort.

- ToolCallingLLM erkennt das strukturelle 404 (_looks_tool_unsupported), merkt
  es fuer den Turn (_tools_unsupported), deaktiviert Tools und antwortet SOFORT
  plain (kein Haenger). Log-Warnung + Metrik tool_unsupported_total.
- Backstop-Inject jetzt als SYSTEM-Kontext statt synthetischem tool_call: das
  funktioniert mit tool-faehigen UND tool-unfaehigen Modellen (letztere lehnen
  tool-Messages mit 405 ab). Folge: heikle Kategorien bleiben auch auf einem
  tool-unfaehigen Modell korrekt; nur nicht-heikle Volatil-Fragen fallen plain.

Live verifiziert gegen mistral-2501 (tool-unfaehig): 3.7s statt Haenger,
Backstop liefert weiter "Merz"; gegen mistral-3.2 kein Regress (1 Suche).
Tests: 312 gruen. Doc §5.7/§5.8/§8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:21:38 +02:00
e753788a23 feat(citations): Web-Such-Quellen unter der Antwort-Bubble anzeigen
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>
2026-06-30 00:11:24 +02:00
fdf65e8623 feat(fillers): Beruhigungssätze auch im Geräte-TTS-Modus sprechen
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>
2026-06-29 23:08:48 +02:00
6cd6e783aa feat(search): deterministischer Backstop für heikle Kategorien
Schliesst die ~5-15 % Erst-Miss bei Fragen, deren veraltete Antwort
konfident-falsch waere (Amtstraeger, Wohnort lebender Personen, "lebt X noch").

- app/pipeline/search_backstop.py: should_force_search() erkennt solche
  Phrasierungen per mehrsprachiger Regex (de/en/nl + etwas fr/es) mit
  Gegenwarts-Schutz ("wer ist", nicht "wer war"). 22 Offline-Tests.
- ToolCallingLLM: bei Treffer wird die Suche DETERMINISTISCH erzwungen.
  Wichtig: NICHT per tool_choice-Forcen (das honoriert das Modell nicht
  zuverlaessig -> beobachtet: ignoriert, veraltete Antwort). Stattdessen
  fuehrt der Wrapper die Suche selbst aus und speist das Ergebnis als
  synthetischen Tool-Turn ein (_inject_forced_search). Metrik
  search_forced_total. Filler/Geduld feuern auch hier.
- Haertung: _chat faengt non-JSON-200 ab (war ein Crash-/Hänger-Ausloeser,
  betrifft auch Produktion).

Live verifiziert: "Wer ist Bundeskanzler?" sucht jetzt 3/3 -> Merz.
Tests: 312 gruen. Doc: §5.7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:58:03 +02:00
96dcd87ca8 feat(fillers): zufällige, übersetzte Senioren-Beruhigungssätze + Geduld-Schleife
Ersetzt die statischen 3-Satz-Filler durch einen kreativen, weniger
roboterhaften Mechanismus für schnelles Feedback waehrend der Web-Suche.

- app/pipeline/fillers.py: EINZIGE Pflege-Stelle. Englische Master-Listen
  (12 OPENING + 16 PATIENCE). Zur Laufzeit einmal je Sprache uebersetzt und
  gecacht (durchgaengig hoefliche Anrede "Sie"/"vous"/"usted"...), Fallback
  Englisch. Zufaellige Auswahl statt fester Reihenfolge.
- Geduld-Schleife: bei laengerer Recherche schiebt ein Hintergrund-Task alle
  FILLER_PATIENCE_INTERVAL Sekunden einen zufaelligen PATIENCE-Satz nach;
  Abbruch beim ersten Antwort-Delta und im finally (keine verwaisten Tasks).
- Ephemer wie bisher: laeuft ueber on_token/_dispatch, nie in
  semantic_response/History.
- Standardsprache wird beim Start vorgewaermt (warmup.py) -> erster Such-Turn
  ohne Uebersetzungs-Latenz. dependencies: get_fillers()-Singleton.

Doc: Docs/weg2-tool-calling.md §5.4. Tests: 290 gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:34:15 +02:00
c36b884aab feat(llm): Koreferenz-Vorstufe + Tool-/Sonar-Metriken (Weg 2, Schritt 5)
Schliesst die im Tool-Calling-Eval isolierte Restkante (nl + Pronomen-aus-
History, z. B. "Leeft hij nog?" -> "Leeft Rutger Hauer nog?") und macht die
Web-Suche metrisch beobachtbar.

- Decontextualizer (app/pipeline/decontextualizer.py): loest Pronomen der
  letzten Aeusserung anhand des Verlaufs auf. Bewusst gegated (kurze
  Folgefrage MIT Pronomen UND History) -> kein Extra-Call im Normalfall;
  best effort (bei Fehler Original behalten).
- ToolCallingLLM nutzt die Vorstufe vor complete()/stream() und zaehlt
  tool_calls_total{tool=...}.
- SonarTool zaehlt sonar_calls_total{status=ok|error}.
- Verdrahtung: Registry openrouter-tools reicht den Decontextualizer durch.

Damit ist v1 von Weg 2 vollstaendig. Tests: 290 gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:43:24 +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
19b3998948 chore(tts-de): Aussprache-Hinweis für "Stück"
Piper/espeak-ng spricht "Stück" sonst uneindeutig -> Lexikoneintrag
"Stück: Styck" für saubere Wiedergabe.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 17:52:11 +02:00
2de50e0675 chore: lokale Ideen-Sammlung (Ideen/) ignorieren
Konzeptentwürfe und Notizen unter Ideen/ bleiben bewusst unversioniert.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 17:52:11 +02:00
e814b5548d fix(tts): Geräte-TTS-Regression für Sprachen ohne Geschlechtsstimme beheben
Commit 5c63f7b hatte deviceGenderReady als harte Bedingung eingeführt: das
Gerät musste eine Stimme des WAHLgeschlechts haben, sonst Server-Fallback.
Die konservative Geschlechts-Heuristik liefert für viele Geräte-Stimmen null
(z.B. NL am iPhone: nur eine Stimme ohne erkennbares Geschlecht) -> der
Server-Fallback griff und lieferte eine schlechte/fremdsprachige Aussprache.
Für NL kam (im noch nicht neu gestarteten Prozess) die deutsche Default-
Stimme -> "deutscher Akzent".

Lösung: deviceGenderReady -> deviceVoiceReady. Eine sprachrichtige Stimme
reicht (pickVoice wählt cands[0] als Best-Effort-Fallback). Die m/f-Wahl
wirkt am Gerät nur für Sprachen mit mehreren Stimmen (DE, EN), sonst
folgenlos — aber eine falsche Sprache ist schlechter als ein folgenloser
Schalter.

+ Test: NL in test_piper_single_voice_languages_ignore_gender aufgenommen
(serverseitige Komplementär-Logik; sichert voice_for_route NL-Auflösung).
Der client-seitige app.js-Fix ist mangels JS-Test-Framework manuell
verifiziert (Geräte-TTS für NL spricht niederländisch, DE/EN behalten
Geschlechtswahl).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 16:15:04 +02:00
71cb6bdd7f fix(i18n): NL auch in HTML-Sprach-Optionslisten (index.html)
Das Sprach-Dropdown (lang-sel) und die Profil-Sprachauswahl (words-lang)
rendern aus hartcodierten <option>-Elementen in index.html, nicht aus der
ALL_LANGS-Konstante in app.js. NL fehlte in beiden Listen, deshalb tauchte
es nach Hard-Reload nicht im Menü auf (PT schon, weil es dort schon drin
war).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 15:25:45 +02:00
3d3973dc16 feat(i18n): Niederländisch (nl) als Sprache aufnehmen
NL war als Halbzustand angelegt (Piper-Modell nl_NL-mls-medium,
Chatterbox-nl.wav, LLM-Sprachname, Cartesia-Ausschluss, leeres
pronunciation.nl.yaml), fehlte aber im Sprachmenü und Backend-Mapping.

- app.js: NL in ALL_LANGS, LANG_BCP47, default_language-Auswahl und
  piper_voice-Liste (jeweils zwischen IT und PT)
- dependencies.py: LANG_TO_PIPER_VOICE nl -> nl_NL-mls-medium
  (kein GENDERED-Eintrag: nur eine Stimme, wie PT/AR)
- safety/emergency.py: NL-Texte in allen 4 Hinweis-Dicts

STT (faster-whisper large-v3) und LLM (_LANG_NAMES) unterstützen nl
bereits automatisch. Offen: deutsche Ordinalzahlen nur für language=de
(NL fällt durch, kosmetisch).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 15:13:29 +02:00
37b4c175d2 fix(notruf): ehrlicher Status, wenn niemand verständigt werden kann
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>
2026-06-27 16:50:03 +02:00
9eb5c31eb6 release(v0.2.0): Marke „Alexis" + sichtbare Versionsnummer v0.2.0
- 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>
2026-06-27 07:02:02 +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
6bf281fc30 fix(tts): voice_gender im UserPrefs-Schema -> Stimmwahl wirkt endlich
Eigentliche Ursache für 'immer weibliche Stimme, egal welcher Provider':
UserPrefs kannte kein Feld voice_gender, daher verwarf pydantic in
PUT /api/me/prefs die Geschlechtswahl still. resolve_route sah immer 'any'
-> Piper/Cartesia lieferten die weibliche Stimme (kerstin bzw. Alina).

- voice_gender zum Schema hinzugefügt; Frontend sendet es bereits, resolve_route
  liest es bereits (ROUTE_KEYS), voice_for_route mappt m/f bereits.
- Regressions-Test: voice_gender persistiert über /api/me/prefs.
- Docs/stimmen-inventar.md: zentrale Übersicht aller Stimmen je Provider/
  Sprache/Geschlecht (Name, Datei, UUID) inkl. Fundstellen im Code.

Live verifiziert (Profil jamulix, de): m -> de_DE-thorsten-high,
f -> de_DE-kerstin-low.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 03:13:27 +02:00
5c63f7b6cc fix(tts): Geräte-TTS folgt der Geschlechtswahl (m/f), sonst Server-Fallback
Der Geräte-TTS-Pfad (Browser-SpeechSynthesis) wählte die Stimme bisher nur
nach Sprache -> der m/f-Schalter blieb dort folgenlos. pickVoice bevorzugt
jetzt die geschlechtspassende Stimme (Heuristik über Name/voiceURI, da die
Web Speech API kein Geschlechtsfeld kennt). Findet das Gerät keine passende
Stimme, weichen deviceSpeak/deviceSpeakBtn auf Server-TTS aus (echte
Piper-m/f-Stimmen), damit die Wahl hörbar wirkt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 02:43:29 +02:00
25f35ad2cb docs: Planungsdokument Multitool-Ausbau (Orchestrierungs-KI, Safety, Nutzerprofile)
Selbst-enthaltendes Planungsdokument für den Umbau zu einer
Orchestrierungs-KI als zentrales Modell (ruft OpenRouter-Modelle für
Teildienste), inkl. Medizin-/Safety-Überlegungen und feinkörnigem
Management der Nutzerprofile (wer was darf). Status: PLAN, noch nichts
implementiert; Umsetzung ab nächster Woche.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:24:28 +02:00
18ad442c94 feat(admin): Dropdown „Zentrales KI-Modell" — erprobte Presets ruck-zuck umstellen
Neue Karte in Admin › System › Status: ein Dropdown kuratierter, erprobter
Modelle, das Provider + Modell in einem Klick als Laufzeit-Override umstellt
(wirkt sofort, kein Neustart).

- Presets in config/models.yaml (Label, optional note, set:{key:value} aus
  RUNTIME_SETTABLE, default:true = Config-Standard). Loader app/model_presets.py
  (nur erlaubte LLM-Keys: default_llm_provider, openrouter_llm_model,
  local_llm_model).
- Endpunkte: GET /api/admin/models (Presets + aktuell aktives LLM + active_index),
  POST /api/admin/models/select (Default -> Overrides löschen, sonst Override-Keys
  setzen) — nutzt set_/delete_config_override + invalidate_cache, mit Audit-Log.
- Frontend: Karte zeigt Dropdown + „Aktiv: <provider> · <modell>"; Auswahl wirkt
  sofort und lädt den Status neu. Bump app.js v=52 -> v=53.
- Startliste: Mistral Small 24B (Standard), DeepSeek V4 Flash/Pro, Ollama Gemma 3.

Tests: Laden/Default-aktiv/Select-setzt-Override/Default-löscht/404/Auth.
263 Offline-Tests grün. Live via Headless-Chrome verifiziert (Auswahl ->
Override gesetzt, Provider-Karte spiegelt das Modell).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:02:44 +02:00
2ccfd7f6d6 docs: § 7.6 um Passwort-Login (1FA) erweitern; Admin-Zugang ist jetzt 1FA
- Neuer Abschnitt 7.6.1 „Anmeldung mit Benutzername & Passwort (1FA, ohne 2FA)"
  (/login), Knopf auf der Zugangs-Seite, Konten aus dem Authelia-Datei-Backend.
- /admin-login als auf Admins beschränkte Variante (7.6.2).
- Recovery (ADMIN_API_KEY) -> 7.6.3, Härtung -> 7.6.4; Intro + TOC-Anchor angepasst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 19:25:12 +02:00
6792e1be86 feat(auth): Passwort-Login (1FA) für die App; Admin ebenfalls 1FA
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>
2026-06-26 19:04:37 +02:00