From 683838bef1bf82c257dcd7bf3aa3c200f3d3f456 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Wed, 15 Jul 2026 13:41:08 -0500 Subject: [PATCH] feat(import): imports store tracking per-meeting import phase progress --- src/lib/stores/imports.svelte.ts | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/lib/stores/imports.svelte.ts diff --git a/src/lib/stores/imports.svelte.ts b/src/lib/stores/imports.svelte.ts new file mode 100644 index 0000000..56c9e55 --- /dev/null +++ b/src/lib/stores/imports.svelte.ts @@ -0,0 +1,63 @@ +// Live per-meeting progress of background media imports (feeds ImportTracker). +// Fed entirely by `import://progress` events emitted by `import_media`; kept in +// memory only (the meeting's `status` badge is the persistent story after a +// restart). See commands.rs `run_import_pipeline`. + +import { events, type ImportPhase, type ImportProgress, type MeetingId } from "../api"; + +export type PhaseState = "pending" | "active" | "done" | "error"; + +// The four phases in the order the backend runs (and the tracker renders) them. +export const IMPORT_PHASES: ImportPhase[] = ["prepare", "transcribe", "diarize", "finalize"]; + +export interface PhaseInfo { + phase: ImportPhase; + state: PhaseState; + elapsedMs: number | null; +} + +export interface ImportRun { + meetingId: MeetingId; + phases: PhaseInfo[]; + error: string | null; + done: boolean; +} + +function freshRun(meetingId: MeetingId): ImportRun { + return { + meetingId, + phases: IMPORT_PHASES.map((phase) => ({ phase, state: "pending", elapsedMs: null })), + error: null, + done: false, + }; +} + +class ImportsStore { + runs = $state>({}); + + get(meetingId: MeetingId): ImportRun | undefined { + return this.runs[meetingId]; + } + + async init() { + await events.onImportProgress((p) => this.apply(p)); + } + + private apply(p: ImportProgress) { + // Re-read through the record after inserting so we mutate the $state proxy, + // not the raw object (Svelte 5 deep reactivity only tracks the proxy). + if (!this.runs[p.meetingId]) this.runs[p.meetingId] = freshRun(p.meetingId); + const run = this.runs[p.meetingId]; + const info = run.phases.find((x) => x.phase === p.phase); + if (!info) return; + info.state = p.state; + if (p.state === "done") info.elapsedMs = p.elapsedMs; + if (p.state === "error") { + run.error = p.error; + run.done = true; + } + if (p.phase === "finalize" && p.state === "done") run.done = true; + } +} + +export const imports = new ImportsStore();