Drei vom Nutzer angeforderte Funktionen.
Titel umbenennen: PATCH /api/recordings/:id/title. Neue Spalte recordings.title_locked,
damit eine spaetere KI-Analyse einen von Hand gesetzten Titel nicht wieder ueberschreibt.
Papierkorb: DELETE setzt nur noch deleted_at, nichts wird angefasst. Wiederherstellen,
endgueltig loeschen, Aufraeumlauf beim Boot und taeglich (Boot-Lauf, weil das Board oft
tagelang aus ist). Behebt nebenbei einen Altfehler: das bisherige Loeschen liess die
words-Zeilen verwaist in der DB und die WAV/MP3-Dateien auf der Karte zurueck.
Inhaltssuche: neue Tabelle passages (1689 Zeilen aus 22 Aufnahmen) mit Sprecher und
Zeitfenster je Passage. Trefferliste kommt aus SQL in Millisekunden, die formulierte
Antwort mit Belegen [1][2] wird nachgereicht (Qwen3-4B, ~140 s) - deshalb zweistufig.
Hybrid: MariaDB FULLTEXT + Passage-Embedding + Dokument-Embedding aus Titel/Zusammenfassung
(multilingual-e5-small, ONNX/CPU, taskset A55). Ohne Modell degradiert die Suche sauber
auf Volltext statt auszufallen.
Gemessene Grenzen sind dokumentiert statt geschoent: e5-small trennt auf verrauschtem
ASR-Text schwach (Kosinus-Spanne aller Passagen nur 0,8085-0,8560; eine irrelevante
Passage schlug die richtige Antwort). Dokument-Ebene und IDF-Wertung heben die richtige
Passage auf Rang 11 von 296, aber nicht in die Top-8, die das LLM liest. Naechster Hebel
waere multilingual-e5-base.
Relative Datumsangaben werden deterministisch aufgeloest, nicht vom LLM - ein falsches
Kalenderfenster waere schlimmer als gar keins.
Neu: app/search.js, app/emb.js, app/inference/embed_text.py, docs/09-suche-und-verwaltung.md
Board-Backups: .bak-search
Testanruf 13:51 loggte nur "-callrec_mix", kein +micgate und kein +far-end: v2 hat
WirePlumbers Link getrennt und dann nichts verlinkt, der Uplink hatte keine Quelle
und die Gegenseite hörte Stille.
Ursache: `pw-link -l <node>` filtert nicht. Mit einem Node-Namen, der nicht als
Port auflösbar ist, kommt der ganze Graph zurück — und der enthält immer
micgate.out und callmix. Also war jedes "ist das schon verlinkt?"-grep wahr und nur
das Trennen lief. Derselbe Ausdruck stand schon in v1, dort aber hinter dem
Einmal-Guard, der nie erreicht wurde.
v3:
- links_of PORT parst die Peers eines exakten Ports aus einem Graph-Dump (awk),
kein grep mehr über die gesamte Ausgabe.
- Erst verlinken, dann trennen: callrec_mix->Uplink fliegt nur raus, wenn
micgate.out am Uplink-Port bestätigt anliegt. Sonst bleibt der WirePlumber-Link
und es wird !no-micgate-uplink-keeping-callrec_mix geloggt. Ein Echo ist
schlecht, unhörbar sein ist schlimmer.
- Log-Zustand als Array statt String: " " hätte -n passiert und bei fertig
geroutetem Anruf jede Sekunde eine Journal-Zeile geschrieben.
Getestet: links_of gegen den echten Graph (verlinkt/unverlinkt/nicht existent);
Entscheidungslogik gegen einen synthetischen Mono-HFP-Graph — Default-Zustand,
fertig geroutet (No-op), micgate nicht bestätigt (behält callrec_mix).
Gegenprobe am echten Anruf steht noch aus.
aime-iphone-route.sh v1 hat seit dem Reboot am 29.07. nie gegriffen (0 Zeilen in
journalctl -t aime-iphone-route über den ganzen Boot). Zwei Gründe:
1. Die Portnamen waren hartcodiert auf input_FL/input_FR. HFP/mSBC ist mono, die
Ports heißen playback_MONO/capture_MONO — der grep matchte nie.
2. Der Guard "micgate.out schon verlinkt -> überspringen" war einmal pro Anruf.
WirePlumber legt die HFP-Nodes nicht atomar an, der Uplink erscheint vor der
Gegenseite. War bluez_input in genau diesem Durchlauf noch nicht da, wurde die
Gegenseite für den ganzen Anruf nicht mehr nachverlinkt. Ein erst mitten im
Anruf gesetztes callrec_mix->Uplink wurde ebenso nie entfernt.
Folge: Gegenseite -> callmix fehlte (nicht in der Aufnahme, #80/#81 hatten je nur
1 Sprecher) und der Uplink führte den ungegateten Mix statt micgate.out, also
wieder das Echo von vor dem Fix am 13.07.
v2 ermittelt die Portnamen über pw-link -i/-o und prüft alle drei Links in jedem
Durchlauf einzeln nach. link_all legt genau einen Link pro Ziel-Port an: eine
Mono-Quelle speist beide Kanäle, eine Stereo-Quelle auf einem Mono-HFP-Port sendet
nur FL, weil beide Links in denselben Port summieren würden (+6 dB Uplink).
Gegenprobe am echten Anruf steht noch aus.
#79 verlor einen von vier Sprechern und lieferte eine 1082-Zeichen-Zusammenfassung.
Diarisierung: ein zweiter, falsch verankerter Marker verschmolz Saschas Cluster
in Merles (Kosinus -0,727). must-link prueft jetzt die akustische Aehnlichkeit,
Anker werden nach Trefferqualitaet gewichtet, und enrolled Stimmen wirken als
cannot-link -- damit ist der Box-Besitzer immer ein eigener Sprecher.
LLM: der Whole-Pass komprimierte das erste Thema weg. Sektions-Pfad jetzt ab
9000 Zeichen, kleinere Chunks, woertlicher Kontext aus dem Vorgaenger-Chunk und
ein gezielter Pruef-Aufruf gegen ueberdehnte Einzelaussagen.
#79: 4 Sprecher, Zusammenfassung ~5300 Zeichen, 7 Themenbloecke.
Regression: #70 -> 4, #68 -> 2, #74 -> 20 Sprecher unveraendert.
speaker_labels speichert das Label jedes Clusters im LLM-Text; relabelAnalysis()
ersetzt beim Umbenennen wortgenau in summary/todos/transcript_clean und baut die
Teilnehmerliste neu. #74: keine 'Sprecher N' mehr.
identifySpeakers() embettete den laengsten Lauf ungekuerzt; TitaNet-ONNX bricht
ab 180 s ab (broadcast 12288 by 17998) und riss den spk_embed-Helfer mit. Da
spk.js die Aufrufe ueber eine Promise-Kette serialisiert, schlugen danach ALLE
weiteren Sprecher mit 'spk beendet' fehl.
- ID_CUT_SEC=25s in identifySpeakers
- Kette in spk.js und tdChain in recorder.js failure-isoliert
- pro Name gewinnt nur der beste Kosinus (Fabio war auf 2 Clustern)
#74 (22 Teilnehmer) ergab automatisch 44 Sprecher, dieselbe Stimme mehrfach.
Ursache gemessen: ein Zentroid pro sherpa-Cluster aus max. 3 Schnitten, fester
Kosinus auf rohen Embeddings (Kreuz-Turn-Kosinus ~0,4 = keine Trennschaerfe).
v2: sherpa liefert nur Turn-Grenzen, Identitaet aus 3-s-Fenster-Embeddings,
Mittelwert-Zentrierung, Average-Linkage-AHC, Weak-Fold, Schwelle pro Aufnahme
per 2-Gauss-EM gelernt. Validiert: #74 44->20, #70 ->4, #68 ->2.
Live-Namensmarker: Namen waehrend der Aufnahme tippen; Zeitstempel benennt
spaeter den Cluster und dient als must-/cannot-link-Zwangsbedingung.
Whole-Pass versagte auf #74 (13-Sprecher-Weekly, 31k Zeichen): Summary
enthielt fast nur die letzten Sprecher. analyze() routet labeled
Transkripte mit >=5 Sprechern jetzt in analyzeLong; Sektions-Pfad
liefert Themen-Überschriften, jede Person, Smalltalk-Dämpfung, Todos
mit Verantwortlichem, Überblick-Mikro-Call, Cap 16000. Kap. 05 +
Changelog aktualisiert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Umschalter (3-Personen-Icon <-> Textblatt); Figuren in Sprecherfarben um
abgerundeten Tisch, Sprechblase mit Live-Text zeigt zum Sprecher, alle
anderen schauen hin, Mund animiert. Sitzlayouts 1-6, localStorage-persistent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113vDKdSNtD3Dwcw4QMu9yV
- Detail-Poll bricht bei Auswahlwechsel ab, pollt nur Status, EIN Rebuild
bei Fertigstellung mit Player-Restore
- Themes via CSS-Variablen (data-theme, localStorage, OS-Default),
Kupfer-Akzent, Wellenform-Wortmarke, Pill-Tabs, focus-visible,
prefers-reduced-motion
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113vDKdSNtD3Dwcw4QMu9yV
Cepstral-Beweis auf #71: stabiler 80ms-Peak nur auf Gegenseiten-Segmenten
= Muschel-Bleed durchs offene Mikro zusaetzlich zum digitalen Pfad.
cr_mic2mix-Loopback entfernt, stattdessen pw-link micgate.out -> callmix
(Fallback ohne Gate bleibt). Verschmierte Embeddings (verschmolzene
Remote-Sprecher) sollten damit ebenfalls weg sein - echter Call offen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113vDKdSNtD3Dwcw4QMu9yV
llm.stopServer() am Anfang von rediarize() und analyzeRecording():
spk-embed starb sonst OOM (48 Roh-Sprecher auf #71). E2E #71: 48->2
Sprecher + saubere Whole-Pass-Summary. Offen: identify-spk-Fehler
auch mit freiem RAM (Voice-Auto-Naming tot).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113vDKdSNtD3Dwcw4QMu9yV
GUI-Titel/Header auf dem Board ebenfalls umgestellt; technische
Bezeichner (callrec.service, /opt/callrec, DB, CallRecMix) bleiben.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>