diff --git a/WorkBreakdown.md b/WorkBreakdown.md index 1c517b4..f00c950 100644 --- a/WorkBreakdown.md +++ b/WorkBreakdown.md @@ -223,8 +223,25 @@ Notes (M5): - [x] Cancel in-flight transcription (both engines) — browser engine via worker termination (D-018), server engine via aborting the `/api/transcribe` fetch (D-018 amendment; landed with M5's EngineToggle) -- [ ] Export transcript: .txt and .srt (timestamps already available) -- [ ] Storage management UI: list cached models, per-model delete +- [x] Export transcript: .txt and .srt (timestamps already available) — + pure formatters in `src/lib/export.ts` (unit-tested: SRT cue numbering, + HH:MM:SS,mmm timestamps, null-end segments borrow the next cue's start + or fall back to +2 s, filename derived from the source file); .txt/.srt + download buttons next to Copy in `TranscriptView`. Live-mode results + reuse the same `result` state with absolute timestamps (D-016), so both + flows export identically. +- [x] Storage management UI: list cached models, per-model delete — + collapsible "Storage" panel (`StorageManager`) listing cached models + with exact on-disk size (summed `blob().size`, measured lazily on + open) and per-model delete; delete is confirm-free (weights are just + cache — they re-download on next use) and refreshes the picker's + downloaded/offline badges. Helpers in `src/lib/modelCache.ts` take an + injectable Cache-Storage-shaped object (mocked in + `tests/modelCache.test.ts`). Scope is the transformers-cache only: + the SW's `whisper-runtime-*` cache (onnxruntime .wasm, shared by all + models, versioned/cleaned by the SW itself) is deliberately not + surfaced — deleting it per-model makes no sense and the SW would + just re-fetch it. - [ ] Basic a11y pass (keyboard, focus, aria on progress/live transcript) - [ ] Production build + `bun start` deployment check; README for deployers diff --git a/src/App.tsx b/src/App.tsx index 24725ce..561f223 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { FileDropzone } from "./components/FileDropzone"; import { LiveControls, type LiveStatus } from "./components/LiveControls"; import { ModelLoadProgress } from "./components/ModelLoadProgress"; import { ModelPicker } from "./components/ModelPicker"; +import { StorageManager } from "./components/StorageManager"; import { TranscriptView } from "./components/TranscriptView"; import { TranscriptionOptions } from "./components/TranscriptionOptions"; import { decodeAudioFile } from "./lib/audio"; @@ -626,6 +627,7 @@ export function App() { result={result} transcribing={liveActive || transcribing} progress={liveActive ? null : transcribeProgress} + exportName={fileName} placeholder={ liveActive ? "Listening for the first words…" : "Processing audio…" } @@ -654,6 +656,13 @@ export function App() { )} + {/* Storage management (M6): cached models with per-model delete. */} + +