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:
@@ -88,6 +88,15 @@ export async function ensureSchema() {
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP NULL DEFAULT NULL,
|
||||
ADD COLUMN IF NOT EXISTS title_locked TINYINT(1) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS doc_vec MEDIUMTEXT NULL`);
|
||||
// Kontextnotiz des Nutzers: was er direkt nach dem Stop über das Gespräch
|
||||
// notiert (wer war dabei, worum ging es, welche Eigennamen). Bewusst EIN
|
||||
// Freitextfeld statt Formularfeldern — die Box benutzen auch Menschen, die
|
||||
// kein Formular ausfüllen wollen; die Struktur (Namen, Begriffe) holt sich
|
||||
// die Analyse selbst aus der Prosa. Fließt als Hintergrundwissen in die
|
||||
// LLM-Analyse (llm.analyze -> contextBlock), NICHT als Gesprächsinhalt.
|
||||
await pool.query(`ALTER TABLE recordings
|
||||
ADD COLUMN IF NOT EXISTS context_note TEXT NULL,
|
||||
ADD COLUMN IF NOT EXISTS context_note_at TIMESTAMP NULL DEFAULT NULL`);
|
||||
// Search index. One row per passage (a few sentences of ONE speaker, see
|
||||
// search.js/buildPassages), carrying the time window so a hit can name the
|
||||
// exact minute and the player can jump there. `vec` = optional text embedding
|
||||
@@ -539,6 +548,29 @@ export async function renameRecording(id, title) {
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
// --- Kontextnotiz ------------------------------------------------------------
|
||||
// Leerer Text löscht die Notiz wieder (dann fällt die Analyse auf reines
|
||||
// Transkript zurück). Deckel bei CONTEXT_MAX: die Spalte ist TEXT (64 KB), aber
|
||||
// die Notiz landet in JEDEM Chunk-Prompt — ein Roman darin verdrängt Transkript.
|
||||
export const CONTEXT_MAX = 4000;
|
||||
export async function setContextNote(id, note) {
|
||||
const n = (note || '').toString().trim().slice(0, CONTEXT_MAX);
|
||||
await pool.execute(
|
||||
'UPDATE recordings SET context_note=?, context_note_at=IF(?<>\'\', CURRENT_TIMESTAMP, NULL) WHERE id=?',
|
||||
[n || null, n, id]);
|
||||
const [rows] = await pool.query(
|
||||
'SELECT id,context_note,context_note_at,status,analysis_status FROM recordings WHERE id=?', [id]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
// Nur die Notiz nachladen. Die Analyse liest sie so SPÄT wie möglich (kurz vor
|
||||
// dem LLM-Aufruf statt beim Start): Sprecher-Erkennung und Whisper laufen davor
|
||||
// minutenlang, und was der Nutzer in dieser Zeit noch tippt, soll mitzählen.
|
||||
export async function getContextNote(id) {
|
||||
const [rows] = await pool.query('SELECT context_note FROM recordings WHERE id=?', [id]);
|
||||
return (rows[0] && rows[0].context_note) || '';
|
||||
}
|
||||
|
||||
function parseJsonCols(row) {
|
||||
for (const c of ['todos', 'participants']) {
|
||||
if (row && typeof row[c] === 'string') {
|
||||
|
||||
Reference in New Issue
Block a user