feat(analyse): Kontextnotiz zum Gespraech nach dem Stop

Ein Freitextfeld ("Kontext zum Gespraech") in der Detailansicht, nach dem
Stop aufgeklappt. Der Text geht als Hintergrundwissen in die LLM-Analyse:
Personen, Firmen, Anlass und Eigennamen muessen nicht mehr aus dem
fehlerbehafteten ASR-Text erraten werden.

Bewusst EIN Feld statt eines Formulars mit Teilnehmer-/Anlass-/Begriffs-
feldern (Entscheidung des Nutzers): die Box benutzen auch technisch nicht
versierte Menschen. Gefuehrt wird ueber den Platzhaltertext, die Struktur
zieht die Analyse selbst aus der Prosa.

- recordings.context_note/-_at, db.setContextNote/getContextNote
- PATCH /api/recordings/:id/context; Antwort `applies` sagt ehrlich, ob die
  Notiz die laufende Verarbeitung noch erreicht (auto/running/reanalyze)
- analyzeRecording liest die Notiz erst kurz vor dem LLM-Aufruf, damit
  waehrend der minutenlangen Vorstufen Getipptes noch zaehlt
- llm.contextBlock: eigener """-Block mit Verbot, daraus Aussagen, Ergebnisse
  oder Aufgaben abzuleiten (sonst schreibt das 4B-Modell die Notiz als
  Gesagtes zurueck); eingespeist in Single-, Whole- und Sektions-Pfad
- zwei Deckel: 1200 Zeichen fuer Prompts, die die Notiz einmal sehen,
  600 fuer die Chunk-Prompts (dort landet sie in jedem Chunk von 3200)
- GUI-Karte zeichnet sich selbst neu (kein openRecording -> Audio laeuft
  weiter), ungespeicherter Text ueberlebt den Neuaufbau nach der Analyse

Verifiziert an #97: Kontext greift (korrekte Namen, richtiges Thema), die
absichtlich eingelegten Fremdfakten der Testnotiz erscheinen nicht in der
Zusammenfassung. Alte Analyse danach zurueckgeschrieben.

Doku: Kapitel 05 (neuer Abschnitt + Flussdiagramm + Deckel-Tabelle),
Kapitel 03 (Sequenzdiagramm + API), Changelog 2026-08-17.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-17 16:10:38 +02:00
parent 3d628b4b96
commit 0f530b575a
8 changed files with 332 additions and 30 deletions
+61
View File
@@ -3,6 +3,67 @@
Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum,
Was, Warum, betroffene Dateien/Backups).
## 2026-08-17 — Kontext zum Gespräch: ein Freitextfeld nach dem Stop
**Wunsch:** nach dem Beenden einer Aufnahme die Möglichkeit haben, Kontext zum gerade geführten
Call/Meeting zu ergänzen — das verbessert die Analyse nach dem Transkript erheblich, weil das Modell
Personen, Firmen, Anlass und Eigennamen nicht mehr aus fehlerbehaftetem ASR-Text erraten muss.
**Entscheidung des Nutzers: EIN Freitextfeld, kein Formular.** Ein Vorschlag mit Teilnehmer-, Anlass-,
Projekt- und Begriffsfeldern wurde ausdrücklich verworfen: die Box benutzen später auch technisch nicht
versierte Menschen, und „Kontext hinzufügen“ ist eine Hürde weniger als vier Pflichtfelder. Geführt wird
über den Platzhaltertext, die Struktur (Namen, Begriffe) zieht sich die Analyse selbst aus der Prosa.
**Umsetzung:**
- Neue Spalten `recordings.context_note` / `context_note_at` (`ensureSchema`), `db.setContextNote()`
(Deckel 4 000 Zeichen, leerer Text löscht) und `db.getContextNote()`.
- `PATCH /api/recordings/:id/context`. Die Antwort enthält `applies` und sagt damit **ehrlich**, ob die
Notiz die laufende Verarbeitung noch erreicht: `auto` (wird noch transkribiert → fließt sicher ein),
`running` (Analyse läuft schon, kann sie verpasst haben), `reanalyze` (fertig → nur ein neuer Lauf
nutzt sie; die GUI fragt dann nach und startet `reanalyze-llm`).
- `analyzeRecording()` liest die Notiz **erst kurz vor dem LLM-Aufruf** (`db.getContextNote`), nicht aus
der Zeile vom Anfang: Sprecher-Erkennung und Transkription davor brauchen Minuten, und was der Nutzer
in dieser Zeit tippt, soll noch mitzählen.
- `llm.js`: neuer `contextBlock()` — die Notiz kommt in einem eigenen `"""`-Block mit der Auflage, sie
**nur** für Schreibweisen, Zuordnung, Anlass und Gewichtung zu nutzen und daraus keine Aussagen,
Ergebnisse oder Aufgaben abzuleiten. Eingespeist in alle Pfade: `analyzeSingle` (`PROMPT_CLEAN`,
`PROMPT_CLEAN_LABELED`, `PROMPT_JSON`), `analyzeWhole` (`PROMPT_WHOLE_ANALYZE`) und `analyzeLong`
(jeder Chunk-Prompt + Titel- und Überblick-Mikro-Call). Zwei Deckel: 1 200 Zeichen für die Prompts, die
die Notiz einmal sehen, 600 für die Chunk-Prompts (dort landet sie in **jedem** Chunk von 3 200
Zeichen). Auf dem letzten von drei Chunk-Versuchen fällt sie mit dem `hint` weg.
- GUI: Karte „📝 Kontext zum Gespräch“ über den Modulen der Detailansicht. Nach dem Stop steht sie offen
und fokussiert (`state.ctxPromptFor`), sonst eingeklappt mit dem gespeicherten Text und
„✏️ Bearbeiten“. Sie zeichnet sich **selbst** neu statt über `openRecording()` — ein Neuaufbau der
Detailansicht setzt den `<audio>`-Tag zurück und würde das Abspielen unterbrechen. Getippter,
ungespeicherter Text überlebt den Neuaufbau nach Ende der Analyse (`state.ctxDrafts`). Strg+Enter
speichert, Enter macht Absätze.
**Verifiziert** an der laufenden App: Endpunkt liefert `applies:"reanalyze"` bei fertiger Analyse und
`applies:"auto"` während einer laufenden Transkription (8-s-Wegwerfaufnahme #115, danach in den
Papierkorb). Echter LLM-Lauf auf #97 (37 s, Single-Pfad) mit einer Testnotiz: Log zeigt
`llm: Kontextnotiz des Nutzers wird mitgegeben (196 Zeichen)`, die Zusammenfassung wurde messbar besser
(„Stimmerkennung“ + korrekte Namen Sascha/Fabio statt des ASR-Fehlers „Audioaufnahme … Lichter-Modus“ mit
falscher Sprecherzuordnung) — und die absichtlich in die Notiz gelegten Fremdfakten („Rückruf ist für
Freitag geplant“, „Halle 3 wurde verkauft“) tauchten **nicht** in Zusammenfassung oder ToDos auf, die
Halluzinationsbremse hält also. Die alte Analyse von #97 war vor dem Test gesichert und wurde danach
zurückgeschrieben, die Testnotiz entfernt. `vite build` sauber, `systemctl restart callrec` erst nach
Prüfung auf `analysis_status='pending'`.
**Offene Grenze:** die Notiz verbessert Titel/Zusammenfassung/Teilnehmer/ToDos, nicht das Transkript —
Whisper ist zu dem Zeitpunkt schon gelaufen. Dauerhaft falsche Schreibweisen gehören ins Glossar.
Ebenfalls noch nicht: die Notiz im Suchindex (`passages`) und ein Hinweis „kein Kontext“ in der
Aufnahmenliste.
Dateien: `app/db.js` (`ensureSchema`, `setContextNote`, `getContextNote`, `CONTEXT_MAX`), `app/server.js`
(`PATCH …/context`, `analyzeRecording`), `app/llm.js` (`contextBlock`, `CONTEXT_MAX_CHARS`,
`CONTEXT_CHUNK_CHARS`, alle Prompt-Bausteine + `analyze/analyzeSingle/analyzeWhole/analyzeLong`),
`app/web/src/main.js` (Kontextkarte, `state.ctxPromptFor`/`ctxDrafts`, `toggleRecord`),
`app/web/src/style.css` (`.ctxbox`, `.ctx-*`).
Board-Backups: `.bak-ctxnote` (db.js, server.js, llm.js, web/src/main.js, web/src/style.css).
Kapitel 05 (neuer Abschnitt „Kontextnotiz des Nutzers" + Flussdiagramm + Deckel-Tabelle), Kapitel 03
(Sequenzdiagramm + API-Tabelle).
## 2026-08-14 — Angeheftete Aufnahmen per Drag & Drop zwischen Unterreitern verschieben
**Wunsch:** eine im Projekt abgelegte Aufnahme sollte sich innerhalb des Ordners umhängen lassen, z. B. von