From 5622499877f2cc387dc900550fa29626e0eb6877 Mon Sep 17 00:00:00 2001 From: Alexander Zielonka Date: Fri, 25 Sep 2026 00:17:17 +0200 Subject: [PATCH] Vorlesen: Wortfilter trifft nicht mehr jedes w, Stimme Thorsten "www" in der Blockliste wurde beim Zusammenziehen gezogener Buchstaben zu "w" und traf als kurzer Eintrag jedes Wort, das mit w anfaengt. Im Stream flog damit fast jede Nachricht raus ("wie", "weg"), die Punkte gingen zurueck. Was nach dem Normalisieren unter 3 Buchstaben hat, trifft jetzt nur noch als ganzes Wort. de_DE-mls-medium, Sprecher 0, klang verwaschen. Die Phoneme stimmten, es lag am Modell. Vorgabe ist jetzt de_DE-thorsten-medium; im Hub schon so eingestellt. Co-Authored-By: Claude Opus 5.5 (1M context) --- apps/hub/src/rewards.test.ts | 9 +++++++++ apps/hub/src/rewards.ts | 3 +++ config/config.example.yaml | 8 ++++---- docs/handoff.md | 28 ++++++++++++++++++++++------ packages/core/src/config.ts | 2 +- scripts/install-piper.sh | 4 ++-- 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/apps/hub/src/rewards.test.ts b/apps/hub/src/rewards.test.ts index d86ce9a..4e1314d 100644 --- a/apps/hub/src/rewards.test.ts +++ b/apps/hub/src/rewards.test.ts @@ -25,6 +25,15 @@ test('Wortfilter: kurze Eintraege nur am Wortanfang, sonst trifft es Harmloses', assert.equal(wortfilterTreffer('N4Z1', LISTE), 'nazi'); }); +test('Wortfilter: "www" wird zu "w" und darf nur als ganzes Wort treffen', () => { + // So im Stream passiert: jede Nachricht mit "wie" oder "weg" flog raus. + const liste = ['www', 'http']; + assert.equal(wortfilterTreffer('wie kann es sein, dass ich nicht mod bin?!', liste), undefined); + assert.equal(wortfilterTreffer('Ladiesman hat einen weg', liste), undefined); + assert.equal(wortfilterTreffer('geh auf www.example.de', liste), 'www'); + assert.equal(wortfilterTreffer('https://example.de', liste), 'http'); +}); + test('Wortfilter: normaler Text geht durch', () => { assert.equal(wortfilterTreffer('Grüße an Abdu, geiler Stream!', LISTE), undefined); }); diff --git a/apps/hub/src/rewards.ts b/apps/hub/src/rewards.ts index e54f6fd..6fc8cff 100644 --- a/apps/hub/src/rewards.ts +++ b/apps/hub/src/rewards.ts @@ -148,6 +148,8 @@ export function fuerFilter(text: string, mitLuecken: boolean): string { * Lange Eintraege (ab 5 Buchstaben) werden im zusammengezogenen Text * gesucht, damit Punkte und Leerzeichen dazwischen nicht helfen. Kurze nur * am Wortanfang: zusammengezogen stuende "nazi" sonst auch in "kann azubi". + * Was danach kuerzer als 3 Buchstaben ist, nur als ganzes Wort: aus "www" + * wird "w", und das stuende am Anfang von "wie" und "weg". */ export function wortfilterTreffer(text: string, blockliste: string[]): string | undefined { const dicht = fuerFilter(text, false); @@ -155,6 +157,7 @@ export function wortfilterTreffer(text: string, blockliste: string[]): string | return blockliste.find((eintrag) => { const e = fuerFilter(eintrag, false); if (!e) return false; + if (e.length < 3) return woerter.includes(e); return e.length >= 5 ? dicht.includes(e) : woerter.some((w) => w.startsWith(e)); }); } diff --git a/config/config.example.yaml b/config/config.example.yaml index 2f0d326..da9987a 100644 --- a/config/config.example.yaml +++ b/config/config.example.yaml @@ -155,10 +155,10 @@ rewards: # Gesprochen wird im Hub mit Piper, nicht im Browser - dort haengt die # Stimme am Rechner, der streamt. Name einer Datei unter models/piper/ # (ohne .onnx) oder ein absoluter Pfad. Einrichten: scripts/install-piper.sh - stimme: de_DE-mls-medium - # mls hat 236 Sprecher in einem Modell; hier steht die Nummer (0-235). - # Bei Stimmen mit nur einem Sprecher weglassen. - sprecher: 0 + stimme: de_DE-thorsten-medium + # Nur bei Stimmen mit mehreren Sprechern, z. B. de_DE-mls-medium (0-235). + # Die klang im Stream allerdings verwaschen. + # sprecher: 0 # Overlay-Server. Zugleich die einzige Tuer nach aussen, wenn der Dienst # zentral laeuft: Uebersicht, Einrichtung und OAuth-Rueckruf haengen daran. diff --git a/docs/handoff.md b/docs/handoff.md index ec0da7b..c594fcb 100644 --- a/docs/handoff.md +++ b/docs/handoff.md @@ -1,6 +1,6 @@ # Übergabe -Stand **2026-09-23**, letztes gebautes Paket 0.5.4. Wer hier weitermacht, +Stand **2026-09-25**, letztes gebautes Paket 0.5.4. Wer hier weitermacht, liest zuerst diese Seite, dann [`07-mvp-plan.md`](07-mvp-plan.md). Für Abdu selbst gibt es das [Handbuch](handbuch/handbuch.pdf). @@ -20,7 +20,7 @@ Auslöser bis zum fertigen Short steht im Code vollständig. Geprüft ist sie | S5 Onboarding + Sichtung | offen | | | S6 Erstchatter-Begrüßung | offen | | -280 Tests, alle grün. Was noch fehlt, steht vollständig in +281 Tests, alle grün. Was noch fehlt, steht vollständig in [`offene-punkte.md`](offene-punkte.md). ## Neu: der Dienst läuft zentral @@ -120,15 +120,20 @@ prüft (`apps/hub/src/rewards.ts`), Piper spricht, `/tts` spielt ab. | Belohnung | „Nachricht vorlesen", 1000 Punkte, Texteingabe Pflicht | | Freigabe | keine, wird direkt vorgelesen | | Schutz | Wortfilter (29 Einträge), 200 Zeichen, 3 je Person und Stunde, „Sofort still" auf der Übersichtsseite | -| Stimme | Piper, `de_DE-mls-medium`, Sprecher 0 | +| Stimme | Piper, `de_DE-thorsten-medium` (mls klang verwaschen) | **Die Stimme entsteht im Hub, nicht im Browser.** Zuerst lief es über `speechSynthesis` in der Browser-Quelle. Das hat in OBS gesprochen, aber amerikanisch: OBS' Browser sah keine deutsche Stimme und die Seite nahm die erste beliebige. Piper liegt unter `/opt/piper` im LXC (2 Kerne: 200 Zeichen in 0,7 s), das Stimmenmodell unter `models/piper/`. Beides überlebt jedes -Ausrollen; eingerichtet mit `scripts/install-piper.sh`. Proben aller 236 -mls-Sprecher lassen sich mit `--speaker N` erzeugen. +Ausrollen; eingerichtet mit `scripts/install-piper.sh`. + +**Die erste Stimme war `de_DE-mls-medium`, Sprecher 0**, und klang im Stream +„wie jemand mit Schlaganfall". Die Phoneme stimmten (`piper --debug`), es lag +am Modell: Hörbuch-Aufnahmen von 236 Laien, Sprecher 0 ist ein beliebiger. +Seit 2026-09-24 läuft `de_DE-thorsten-medium`, ein Sprecher, darum kein +`sprecher` mehr in der Konfiguration. Das mls-Modell liegt noch daneben. **Die Belohnung muss über die API angelegt sein** (`abdulive rewards:anlegen`). Nur die App, die eine Belohnung angelegt hat, darf deren Einlösungen @@ -142,6 +147,15 @@ zusammengezogenen Text gesucht, kürzere nur am Wortanfang, sonst stünde „nazi" in „kann azubi". Die Liste steht nur in der Hub-Konfiguration, nicht im Repo. +**Der Filter hat eine Stunde lang fast alles abgelehnt.** `www` in der Liste +wurde zusammengezogen zu `w` und traf damit jedes Wort, das mit w anfängt +(„wie", „weg"). Die Punkte gingen zurück, im Log stand nur `Wortfilter`. +Behoben am 2026-09-24: was nach dem Zusammenziehen unter 3 Buchstaben hat, +trifft nur noch als ganzes Wort. **Noch offen:** lange Einträge treffen über +Wortgrenzen hinweg, „Spaß tut gut" fliegt wegen `spast` raus, „eine Gerade" +wegen `neger`. Wer eine Ablehnung nachvollziehen will: der Text steht im +Ereignis `points.redeemed` in der Datenbank. + **Der YAML-Leser konnte keine Listen aus Maps.** `- titel: X` zerfiel in Zeichenketten, jede Belohnung war ohne Meldung unwirksam - auch das Beispiel in `config.example.yaml`. Behoben und getestet; die bestehenden Dateien @@ -408,7 +422,9 @@ Wirkung. **`apps/recap` bauen.** Angefangen, aber nicht committet: `apps/recap/src/recap.ts` und Ergänzungen in `packages/db/src/repos.ts` liegen nur im Arbeitsstand (die Typprüfung meckert dort noch über `url: undefined`). Der Webhook liegt -bereit, der Hub hat die Daten. +bereit, der Hub hat die Daten. Achtung: `deploy-hub.sh` schiebt den ganzen +Arbeitsstand, der halbe Recap liegt also schon im LXC. Er wird nur von nichts +aufgerufen. **Danach S5.** Nicht wegen des Codes, sondern weil das Werkzeug ohne Sichtungsseite für Abdu nicht bedienbar ist. Die Kette funktioniert, aber sie diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 0d5747f..7ea17d3 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -146,7 +146,7 @@ const DEFAULTS = { maxZeichen: 200, blockliste: [] as string[], proPersonProStunde: 3, - stimme: 'de_DE-mls-medium', + stimme: 'de_DE-thorsten-medium', }, }, overlay: { port: 4791, enabled: true }, diff --git a/scripts/install-piper.sh b/scripts/install-piper.sh index e29f525..402fd30 100644 --- a/scripts/install-piper.sh +++ b/scripts/install-piper.sh @@ -13,12 +13,12 @@ # Umgebung (mit Vorgaben): # PVE_HOST=192.168.2.20 Proxmox-Host # CT=141 Container-ID -# STIMME=de_DE-mls-medium Name aus huggingface.co/rhasspy/piper-voices +# STIMME=de_DE-thorsten-medium Name aus huggingface.co/rhasspy/piper-voices set -euo pipefail PVE_HOST="${PVE_HOST:-192.168.2.20}" CT="${CT:-141}" -STIMME="${STIMME:-de_DE-mls-medium}" +STIMME="${STIMME:-de_DE-thorsten-medium}" # de_DE-thorsten-medium -> de/de_DE/thorsten/medium IFS=- read -r LOCALE SPRECHER QUALI <<< "$STIMME"