feat(ui): recording store gains live notes state
notesText (debounced save, flushed on stop before finalize reads it), segmentNotes Map keyed by anchor_ms, setNotesText/setSegmentNote.
This commit is contained in:
1 parent
90b2d51d08
commit
6622ae81ff
1 file changed
+52
@@ -3,6 +3,7 @@
|
||||
|
||||
import { api, events, type TranscriptSegment, type MeetingId } from "../api";
|
||||
import { settings } from "./settings.svelte";
|
||||
import { SvelteMap } from "svelte/reactivity";
|
||||
|
||||
class RecordingStore {
|
||||
meetingId = $state<MeetingId | null>(null);
|
||||
@@ -16,6 +17,11 @@ class RecordingStore {
|
||||
levelPeak = $state(0);
|
||||
/** Set while a capture-device reconnect is in progress; cleared on recovery (FR-CAP-6). */
|
||||
deviceNotice = $state<string | null>(null);
|
||||
/** Live notes redesign: freeform text typed in the Notes pane while recording. */
|
||||
notesText = $state("");
|
||||
/** anchor_ms (a segment's start_ms) -> note text, for moments annotated this recording. */
|
||||
segmentNotes = new SvelteMap<number, string>();
|
||||
private notesSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
async init() {
|
||||
await events.onRecordingState((p) => {
|
||||
@@ -53,6 +59,8 @@ class RecordingStore {
|
||||
this.segments = [];
|
||||
this.retention = record;
|
||||
this.deviceNotice = null;
|
||||
this.notesText = "";
|
||||
this.segmentNotes.clear();
|
||||
// T8.7/FR-TRX-4: whatever language is currently configured in Settings
|
||||
// becomes this meeting's requested language, persisted on its record.
|
||||
const language = settings.settings.whisper_language ?? undefined;
|
||||
@@ -61,6 +69,9 @@ class RecordingStore {
|
||||
}
|
||||
|
||||
async stop() {
|
||||
// The debounced save below can lag up to 500ms behind typing — flush
|
||||
// whatever's pending first so stop_recording's merge sees the latest text.
|
||||
await this.flushNotes();
|
||||
if (this.meetingId) await api.stopRecording(this.meetingId);
|
||||
this.state = "idle";
|
||||
this.levelRms = 0;
|
||||
@@ -70,6 +81,10 @@ class RecordingStore {
|
||||
|
||||
/** Abandon an accidental recording: stop capture and delete it entirely. */
|
||||
async cancel() {
|
||||
if (this.notesSaveTimer) {
|
||||
clearTimeout(this.notesSaveTimer);
|
||||
this.notesSaveTimer = null;
|
||||
}
|
||||
if (this.meetingId) await api.cancelRecording(this.meetingId);
|
||||
this.meetingId = null;
|
||||
this.segments = [];
|
||||
@@ -77,6 +92,8 @@ class RecordingStore {
|
||||
this.levelRms = 0;
|
||||
this.levelPeak = 0;
|
||||
this.deviceNotice = null;
|
||||
this.notesText = "";
|
||||
this.segmentNotes.clear();
|
||||
}
|
||||
|
||||
/** Toggle audio retention mid-meeting (FR-REC-1); caller must have gated consent already. */
|
||||
@@ -85,6 +102,41 @@ class RecordingStore {
|
||||
await api.setRecordingRetention(this.meetingId, record);
|
||||
this.retention = record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Live notes redesign: freeform text typed in the Notes pane while
|
||||
* recording. Debounced 500ms (same cadence as the post-finalize editor's
|
||||
* `scheduleSave`) so every keystroke doesn't round-trip to the backend.
|
||||
*/
|
||||
setNotesText(text: string) {
|
||||
this.notesText = text;
|
||||
if (!this.meetingId) return;
|
||||
const meetingId = this.meetingId;
|
||||
if (this.notesSaveTimer) clearTimeout(this.notesSaveTimer);
|
||||
this.notesSaveTimer = setTimeout(() => {
|
||||
this.notesSaveTimer = null;
|
||||
api.updateLiveNotes(meetingId, text).catch(() => {});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private async flushNotes() {
|
||||
if (this.notesSaveTimer) {
|
||||
clearTimeout(this.notesSaveTimer);
|
||||
this.notesSaveTimer = null;
|
||||
}
|
||||
if (this.meetingId) {
|
||||
await api.updateLiveNotes(this.meetingId, this.notesText).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
/** Attach (or clear, with `text: ""`) a note to a clicked transcript
|
||||
* segment's moment (the "click a transcript line, add a note" feature). */
|
||||
async setSegmentNote(anchorMs: number, text: string) {
|
||||
if (!this.meetingId) return;
|
||||
if (text.trim() === "") this.segmentNotes.delete(anchorMs);
|
||||
else this.segmentNotes.set(anchorMs, text);
|
||||
await api.setSegmentNote(this.meetingId, anchorMs, text);
|
||||
}
|
||||
}
|
||||
|
||||
export const recording = new RecordingStore();
|
||||
Reference in new issue
Block a user