piper_voice, openrouter_tts_model und openrouter_tts_voice bekommen
'testProvider' in FIELD_META. Der Test-Handler schickt diesen Provider
als tts_provider im Request mit — statt immer den System-Default zu
nutzen, der oft openrouter ist und dann mit 502 scheitert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.js: default_stt/llm/tts_provider und default_language von 'select'
auf 'combo' umgestellt, damit neben den vordefinierten Optionen jederzeit
ein beliebiger eigener Wert eingegeben werden kann (Zukunftssicherheit).
combo-Renderer unterstützt jetzt auch meta.labels (Sprachkürzel → Klarname).
tests/test_admin_settings.py: 28 Tests für GET/PUT/DELETE /api/admin/config —
Auth, bekannte Werte, Custom-Values, Persistenz, Runtime-Wirkung.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README + BEDIENUNGSANLEITUNG § 4.7: vollständige Kommandos für den Wechsel
zwischen llama.cpp und Ollama (inkl. Stopp-Befehle für den jeweils anderen
Backend-Prozess). TOC und Sachregister aktualisiert.
Docs/Neues_Konzept_mit_TTS_auf_Mobil_Geraet.md: neue Recherche-/Konzept-Datei
zu On-Device-TTS auf iOS/Android hinzugefügt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README: neuer Abschnitt „Starten — alle Szenarien" mit allen Optionen für
cloud/hybrid/local-dev, llama.cpp-ENV-Tabelle, Ollama-Sequenz und make-Targets.
BEDIENUNGSANLEITUNG: § 4.5 (llama.cpp) um direkten Script-Aufruf, docker logs,
vollständige ENV-Tabelle erweitert; neues § 4.6 (Ollama) mit systemctl/serve,
pull/list/ps, .env-Snippet und Reasoning-Hinweis. TOC und Sachregister aktualisiert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dropdown-Auswahl für Provider, Bool- und Enum-Felder
- Combo-Widget (Dropdown + Textfeld, synchronisiert) für Modelle/Stimmen
- Range-Slider für Temperature, Number-Input für Token/Limit-Felder
- Textarea für System-Prompt
- Test-Buttons: 🔊 TTS via WebAudio, 💬 LLM via /api/chat?debug=true, 🎤 STT-Verfügbarkeitscheck
- Inline-Save-Feedback (grün ✓ / rot ✗), kein Full-Reload mehr
- ENV-Variablenname in UPPERCASE angezeigt
- Sieben Gruppen-Sektionen statt flacher Liste
- Piper-Stimmentabelle in Doku korrigiert (nur installierte: thorsten/kerstin/karlsson)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neue Tabelle config_overrides in SQLite; RuntimeSettings-Wrapper liest
überschreibbare Felder mit 30s TTL-Cache aus der DB und fällt auf
.env-Werte zurück. dependencies.py und quota.py nutzen runtime_settings
als Default statt des statischen Settings-Singletons.
16 Felder überschreibbar: STT/LLM/TTS-Provider, LLM-Modelle, Stimmen,
Systemprompt, Temperatur, Tageskontingent, Normalisierung u.a.
Backend: GET/PUT/DELETE /api/admin/config/{key}
Admin-UI: neuer Tab "⚙ Einstellungen" mit Inline-Edit und Reset.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
escHtml() rief .replace() auf Number-Werten auf (httpTotal aus Metriken)
→ TypeError. Fix: immer String(str) konvertieren. Außerdem console.error
im Status-Tab-catch für einfachere Fehlerdiagnose.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Vorher pruefte require_admin nur den X-Admin-Key-Header — SSO-Admins
(aus ADMIN_USERS) bekamen 401. Das brach alle Admin-Panel-Tabs ausser
der Nutzerliste im Browser.
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>
Barge-in im Sprech-Loop (voice_loop.py) war nur während der LLM-Phase
zuverlässig. Während der Audio-Wiedergabe (nach done-Event) war der
_stdin_watcher bereits gecancelt, sodass Enter ignoriert wurde oder
als Spurious-Input in record_utterance() landete.
Lösung:
- _stdin_watcher läuft jetzt durch beide Phasen (LLM + Audio)
- Phase 2: _close_player wird als asyncio-Task gegen den Watcher geract;
Enter während der Wiedergabe killt den Player sofort
- termios.tcflush nach jedem Turn verhindert Rest-Enters im stdin-Buffer
Außerdem: OPENROUTER_API_KEY in .env ergänzt (systemd sourct kein
.bashrc); Dokumentation entsprechend aktualisiert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server sendet nach Interrupt {'type':'interrupted'}, aber der Client-Handler
fehlte: WS blieb offen, runTurn loeste nie auf, Button blieb dauerhaft gelb.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Web-Interface (app.js):
- Mic-Button hat jetzt 3 Zustände: idle (grün 🎤) / Aufnahme (rot pulsierend) /
KI antwortet (amber ⏹, klicken = Barge-in)
- Barge-in sendet {"type":"interrupt"} auf der aktiven WS-Verbindung (activeWs)
und stoppt alle laufenden AudioBufferSourceNodes sofort (stopAudio)
- busy + Button-Reset erfolgen automatisch wenn die WS schliesst
Terminal (voice_loop.py):
- _stdin_watcher(): polling via select.select (nicht-blockierend, kein Thread-Leak)
wartet auf Enter-Tastendruck waehrend Server antwortet
- _send_and_play() raced one_turn gegen _stdin_watcher mit asyncio.wait;
bei Barge-in: interrupt-Frame an Server, Player.terminate(), feeder beenden
- Feedback: "↩ Unterbrochen — drücke [Enter] für neue Aufnahme …"
Server-Seite war bereits vollstaendig implementiert (ws.py, kein Handlungsbedarf).
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>
- README: Ollama als LLM-Backend (Alternative zu llama.cpp) mit .env-Snippet
- README: Warnung dass Gateway ohne LLM-Server startet, Fehler erst beim ersten Request
- BEDIENUNGSANLEITUNG: Abschnitt 4 zu vollstaendiger Profilbeschreibung ausgebaut
(cloud / hybrid / local-dev je mit Hardware, Software, Kosten, Latenz, Qualitaet,
Einrichtungsbefehlen und Vergleichstabelle)
- BEDIENUNGSANLEITUNG: neues Kapitel A0 Web-Interface (Oberflaeche, Ablaeufe,
Fehlertabelle)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- getUserMedia/MediaRecorder-Support pruefen, klare Fehlermeldung mit error.name
(In-App-Browser/Privat-Modus/Permission) statt pauschalem "HTTPS noetig?"
- Safari liefert audio/mp4 (nicht webm): tatsaechlichen mimeType verwenden und als
format mitschicken; Server (faster-whisper) erkennt das Format am Inhalt (mp4/AAC
verifiziert). Asset-Version v7
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dropdown im Menue setzt tts_provider als Request-Override fuer Text- und Sprach-Turns
-> live umschaltbar zwischen schnellem piper, hochwertigem chatterbox und Cloud.
Asset-Version v5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Vorlage deploy/voice-assistant.user.service + Anleitung (loginctl enable-linger,
systemctl --user). Laeuft ohne root und ueberlebt Logout/Reboot; .env wird aus dem
WorkingDirectory gelesen. va_llm-Container kommt via Docker restart-policy hoch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die feste Session "web-main" gehoerte dem ersten Nutzer (lokal: anonymous) ->
SSO-Nutzer bekam "Session gehoert einem anderen Nutzer" (403). Die Web-UI leitet
die Session jetzt aus /api/me ab (web-<user_id>); ensureSession() wartet vor dem
ersten Turn auf /api/me. Asset-Version v4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
README/BEDIENUNGSANLEITUNG/Architektur: piper laeuft in-process (gecachtes
Stimmmodell, kein Subprozess pro Satz), In-Process-Resampling, lokale Modelle
werden beim Start vorgeladen. .[local] installiert jetzt faster-whisper UND
piper-tts. Latenz-Hinweis erster Ton 5,8 s -> ~1,6 s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lifespan-Hintergrundtask laedt profilabhaengig piper-Stimme + faster-whisper-Modell
vor (nur was die aktive Konfiguration nutzt; Cloud-Profile = No-op). Server ist
sofort verfuegbar; der erste Nutzer zahlt nicht mehr den Kaltstart.
Messung: erster Turn direkt nach Start first_audio 1,62 s (statt ~4,5 s mit Kaltstart).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Profiling zeigte ~2,2 s Fixkosten pro Satz durch Modell-Start (piper-Binary je
Aufruf). Der Provider nutzt jetzt die piper-Python-API: Stimmmodell wird einmal
geladen (lru_cache), Synthese laeuft im Thread. Resampling in-process (audioop,
numpy-Fallback fuer Py3.13) statt ffmpeg-Subprozess. Binary bleibt als Fallback.
Messung (lokales Setup): erster Ton 5,84 s -> 2,66 s, Turn-Ende 9,82 s -> 4,04 s.
- pyproject: piper-tts zum [local]-Extra
- Tests pruefen weiter den Binary-Fallback (erzwingen _PIPER_LIB=False)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Safari cachte CSS/JS aggressiv -> Aenderungen wurden nicht sichtbar. StaticFiles
liefert die UI nun mit Cache-Control: no-cache aus; Asset-Links zusaetzlich
versioniert (?v=3), damit die neue Version sicher geladen wird.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Layout auf echte Viewport-Hoehe (100dvh, vh-Fallback); nur der Nachrichten-
bereich scrollt (min-height:0), Eingabe + Mic-Button bleiben immer sichtbar
- Mobile: Menue als kompakte Kopfleiste, Chat fuellt den Rest
- Auto-Scroll zentralisiert (scrollToBottom via rAF) und bei token/semantic/done/
neuer Nachricht ausgeloest; reagiert auf Tastatur (visualViewport) + Resize/Focus
-> die neueste Antwort ist immer vollstaendig sichtbar
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kuerzer und ohne die Verwechslungsgefahr assistent/assistant (de/en).
nginx-Vorlage umbenannt + alle Referenzen in deploy/README.md angepasst.
Co-Authored-By: Claude Opus 4.8 <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>
Hinweis, dass der Server bereits auf 0.0.0.0 lauscht und fuer LAN-Zugriff nur die
Firewall geoeffnet werden muss; Mikrofon braucht HTTPS/localhost (secure context).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Qwen3 verlangt genau eine System-Nachricht ganz am Anfang. Sobald ein Nutzer
Erinnerungen hatte, injizierte das Gateway eine zweite System-Nachricht zusaetzlich
zum Sprach-System-Prompt -> 400 "System message must be at the beginning".
Der lokale Provider fuehrt jetzt Sprach-Prompt + System-Inhalte aus der History
(z. B. Nutzer-Erinnerungen) zu einer einzigen System-Nachricht zusammen.
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>
20 Gemini-Stimmen gegen den laufenden Server getestet -> liefern Audio
(Zephyr..Sulafat). Als 'live verifiziert' im Stimmen-Abschnitt markiert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Preview-Modelle liefern gelegentlich HTTP 200 mit leerem Body (oder 5xx) -> bisher
brach das die ganze Sprech-Runde ab ('empty audio content', 502). Jetzt bis zu 3
Versuche mit linearem Backoff; 4xx (z. B. ungueltige Stimme) wird sofort gemeldet
(kein Retry). Damit klappt das Durchprobieren von Stimmen zuverlaessig (Live-Test:
alle 20 getesteten Gemini-Stimmen inkl. Leda liefern Audio).
Tests: tests/test_openrouter_tts.py (leer->ok, 4xx sofort, alles-leer, 5xx->ok).
Doku-Hinweis in BEDIENUNGSANLEITUNG.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'auto'-Recorder folgt jetzt dem System-Standard-Mikrofon und prueft per Kurztest
(~0,6 s), dass das Werkzeug WIRKLICH Audio liefert -> nie ein totes Geraet.
Kandidaten ffmpeg(-f pulse) -> parecord -> arecord -> pw-record; auf PipeWire-
Systemen ohne aktive Session gewinnt ffmpeg (gemessen: pw-record/arecord-default/
parecord liefern hier nichts). Neuer Recorder 'ffmpeg'. Player: paplay vor aplay
(folgt System-Default-Sink). --device nur noch, wenn man bewusst abweichen will.
Doku (Bedienung + B2 + Fehlerbehebung) entsprechend aktualisiert.
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>
BEDIENUNGSANLEITUNG (B1): neuer Abschnitt 'Stimme des Cloud-TTS (OpenRouter) waehlen'
-- Gateway pflegt keine eigene Liste, reicht OPENROUTER_TTS_VOICE durch; Stimmen
haengen vom Modell ab (Gemini-Familie ~30 Stimmen, OpenAI gpt-4o-mini-tts), Umstellen
global (.env) und pro Aufruf (voice-Feld in /api/speak,/api/chat), Hinweis auf
fehlende --voice-Option in voice_loop. .env.example: Verweis bei OPENROUTER_TTS_VOICE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>