Build Wisp: on-device transcription studio (web + native, one codebase)
CI / test (push) Has been cancelled
CI / deploy-web (push) Has been cancelled
CI / build-apk (push) Has been cancelled

Private, offline speech-to-text that runs Whisper on the user's own device —
free, no account, no per-minute fees. Replaces Otter.ai / Rev.

- Pure, tested engine: chunking, overlap timestamp-stitching, exports
  (SRT/VTT/TXT/MD/JSON), WAV codec, resampler, job queue, model catalog (142 tests).
- Platform-abstracted TranscriptionEngine: transformers.js on web (loaded from
  CDN at runtime to dodge Metro's onnxruntime-web bundling limits), whisper.rn
  on native. Shared pipeline orchestrates decode -> chunk -> transcribe -> stitch.
- Cross-platform StorageRepo (Dexie web / expo-sqlite native), Zod-validated.
- UI: library + search, import, live-progress transcription, synced click-to-seek
  editor, multi-format export; model picker + privacy in settings.
- Web ships as a single-page PWA with COOP/COEP isolation for threaded WASM;
  Docker (nginx) image + Traefik compose for wisp.briggen.dev.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 17:54:21 +02:00
parent 97996c9846
commit 9f42ee2460
72 changed files with 7293 additions and 421 deletions
+60
View File
@@ -0,0 +1,60 @@
// The transcription engine interface: the single abstraction that hides the
// platform-specific Whisper backend (transformers.js on web, whisper.rn on
// native) behind one stable contract. The pure orchestration pipeline
// (pipeline.ts) is written entirely against this interface so it can be unit
// tested with a fake engine and never imports any heavy/native dependency.
//
// IMPORTANT contract detail: `transcribeChunk` returns CHUNK-LOCAL times
// (0-based, relative to the start of the chunk it was handed). The pipeline is
// responsible for offsetting those into absolute media time and stitching the
// overlapping windows together (see pipeline.ts + stitch.ts).
import type {
Backend,
ModelId,
PcmAudio,
Segment,
TranscribeOptions,
} from '../types';
/**
* What an engine discovered about the device it is running on. Used by the UI
* to gate model choices and to decide whether live-mic capture is available.
*/
export interface EngineCapabilities {
/** The compute backend the engine resolved to (e.g. 'webgpu', 'cpu'). */
backend: Backend;
/** Whether this engine can transcribe from a live microphone stream. */
supportsLiveMic: boolean;
/** Largest model we'd recommend running on this device/backend. */
maxRecommendedModel: ModelId;
}
/**
* A platform-agnostic Whisper engine. Both `engineImpl.web.ts` and
* `engineImpl.native.ts` export a singleton `engine` satisfying this.
*/
export interface TranscriptionEngine {
/** Which platform family this engine targets. */
readonly platform: 'web' | 'native';
/** Probe the device. May be async (e.g. WebGPU adapter request). */
capabilities(): Promise<EngineCapabilities>;
/**
* Ensure `modelId` is loaded and ready. `onProgress` (if given) receives a
* fraction in [0, 1] during download/initialization. Implementations should
* be idempotent: calling again for an already-loaded model is a no-op.
*/
loadModel(modelId: ModelId, onProgress?: (p: number) => void): Promise<void>;
/** Synchronous check: is this model already loaded in memory? */
isModelLoaded(modelId: ModelId): boolean;
/**
* Transcribe a single already-decoded 16 kHz mono chunk.
* Returns segments with CHUNK-LOCAL times (seconds, 0-based). The caller
* offsets and stitches; engines must NOT add the chunk's media offset.
*/
transcribeChunk(audio: PcmAudio, opts: TranscribeOptions): Promise<Segment[]>;
}