Skip to content

WP-05: Intake + ASR - #8

Merged
gitAbid merged 3 commits into
mainfrom
wp-05-intake-asr
Oct 27, 2026
Merged

gitAbid merged 3 commits into
mainfrom
wp-05-intake-asr

Conversation

@gitAbid

@gitAbid gitAbid commented Oct 27, 2026

Copy link
Copy Markdown
Owner

WP-05: Intake + ASR

Implements M2 (new episode intake), M3 (Groq transcription) and §8.3 on top of WP-02. Branch: wp-05-intake-asr, base main @ 810d8ff. Worktree: okra-wp05.

Deliverables

  • web/lib/asr/ — transcribe(chunks: {blobPath, offsetMs}[], opts: {vocab}) → Segment[]: Groq adapter (whisper-large-v3-turbo, verbose_json, timestamp_granularities[]=segment, language=en, vocab+guest prompt ≤200 tokens, concurrency 3), 2 s overlap dedupe by timestamp, sentence-group merge (≤45 s / ≤500 chars, seg_XXXXXX ids), AsrError ported from api/asr.js, language gate (supportedInput port from api/generate-suite.js), plus an in-memory fake adapter.
  • web/lib/transcripts/ — plain text, SRT, VTT, Riverside Name (00:12:03):, Otter [00:12:03] Name:, Descript Name:; detection returns {format, hasTimestamps, hasSpeakers, chip}; fixtures for every format incl. unsorted timestamps, CRLF, speakerless.
  • Browser intake /app/episodes/new (MK-4) — Upload tab (drop zone, mp3/m4a/wav/aac/ogg/webm/mp4/mov allow-list, ≤2 h; WebAudio compression ported from site/app.html to mono 16 kHz Opus/WebM 24 kbps; ≤10-min chunks ~1.8 MB with 2 s overlap; compress/upload % progress via @vercel/blob/client onUploadProgress) and Paste tab (format chip, speakers/timestamps preserved), optional title/number/guests, "Try with a sample episode" (/samples/demo-episode.txt, source=sample, free), resumable status view via ?id= (polls GET status every 2 s).
  • Routes (§7.5) — POST /api/uploads/token (type allow-list, ≤300 MB, quota check), POST /api/episodes (create; counts quota; paste parsed server-side into segments), POST /api/episodes/:id/transcribe (uploading→queued→transcribing→queued; unsupported_language/no_speech refund quota; maxDuration 300), GET /api/episodes/:id (status read path, U4), GET /api/audio/:episodeId?t= (short-lived signed URL for the chunk containing t).
  • Boundaries — additive episode/segment queries on the documented @/db/queries seam; @/lib/blob wraps all @vercel/blob calls; @/lib/billing/entitlements stub (free-plan limits) until WP-08.

Acceptance criteria → evidence

Criterion Evidence
U4 upload % progress + refresh resumes from server status EpisodeIntakeShell compress/upload progress + ?id= status polling; e2e U4: ?id= resumes from server status (transcribing) (network-intercepted); route handler tests: handleEpisodeGet — resumable status read path (U4) (mocked @/db/queries)
U5 paste Riverside → chip "Riverside · speakers and timestamps found"; speakers+times preserved e2e U5: pasting a Riverside transcript shows the exact format chip; unit transcripts.test.ts (23 tests) asserts chip + parsed speaker/ms segments; service test creates a paste episode, parsing speakers + timestamps into segments (seg_000000/Dev/12_000)
U19 non-English → exact copy "EpisodeKit works with English episodes for now. This one wasn't charged." e2e U19: failed unsupported-language episode shows the exact copy; asr-language.test.ts (Latin ratio <0.6, whisper-language gate); service tests: ASR unsupported_language → failed + countsQuota:false (refund); paste-side gate returns the same copy with nothing created
Fixtures per format web/tests/fixtures/transcripts/: plain, srt, srt-unsorted, vtt, riverside, riverside-crlf, descript, otter
Merge tests asr-merge.test.ts: overlap dedupe (drop + keep-when-uncovered), sentence grouping 45 s/500 chars bounds, id/idx renumbering
Compression logic tests compression.test.ts: planChunks boundaries + stub absorb, bitrate math (600 s × 24 kbps ≈ 1.8 MB), mime choice table. The AudioWorklet/MediaRecorder graph itself has no Playwright smoke: MediaRecorder + OfflineAudioContext need a real audio device/graph and a >realtime encode — not feasible headless in CI; the pure decision layer is fully unit-tested and the graph is a direct port of site/app.html (documented in DECISIONS.md)
Language-check tests asr-language.test.ts ports supportedInput() exactly (Latin/letters ≥0.6)

Checks

  • pnpm lint ✓ · pnpm typecheck ✓ (next typegen && tsc --noEmit) · pnpm test ✓ 240 unit tests · pnpm build ✓ · e2e ✓ 22/22 (7 new in tests/e2e/new-episode.spec.ts, run with E2E_PORT=3105; WP-02 suites unaffected)
  • Preview (keyless build): https://episodekit-1ou27e12v-pera-byte.vercel.app/app/episodes/new (200; paste flow is a client component under Suspense and mounts on hydration — verified by e2e against the identical production build)

Live ASR smoke — BLOCKED

.env.relaunch holds sanitized placeholders (values are literally "[SENSITIVE]", no gsk_… key, ASR_PROVIDER ≠ groq), so no live Groq call was possible. The adapter is covered instead by fetch-injected unit tests (request shape, error mapping groq_<status>, empty_transcript→no_speech, language gate). No synthetic speech fixture exists in docs/samples/ (only text), so none was generated.

Blockers for the owner

  1. BLOB_READ_WRITE_TOKEN is not configured anywhere. Uploads (/api/uploads/token answers 503 "Audio upload isn't set up in this preview."), signed playback (/api/audio/:id 503), and the client upload() flow are implemented against the real @vercel/blob API (verified against the installed 2.8.2 types) but cannot run until the token is set. Unit-tested with the boundary mocked; noted in web/.env.example.
  2. Groq key not available locally (sanitized .env.relaunch) — live ASR smoke pending; owner action above.
  3. Chunked audio means quote playback (M5/WP-06) plays the chunk containing the timestamp (?t= + chunkOffsetMs) — WP-06 should use the returned chunkOffsetMs for seeking.

Decisions logged (docs/relaunch/DECISIONS.md, newest first)

Chunk manifest JSON in audio_blob_path · chunkOffsetsMs[] extension to the §7.5 create body · format-label heuristics (paren→Riverside, bracket→Otter, speaker-only→Descript) · paste-side Latin-ratio gate · entitlements stub seam · ?t= chunk-resolved audio URL · compression port + chunking · overlap dedupe guard · trailing-stub absorb · quota checked at token / counted at create · additive db/queries extension.

Notes

  • WP-01 components not merged — plain Tailwind per §5.2, MK-4 structure; later WPs restyle.
  • Real-time encode caveat: MediaRecorder (as in site/app.html) encodes in real time, so a 60-min episode takes ~60 min to compress client-side. MVP-acceptable (matches legacy); WebCodecs noted as the later escape hatch.
  • No files outside WP-05 scope modified; db/queries.ts + lib/copy.ts additions are purely additive.

WP-05 agent and others added 3 commits October 27, 2026 19:42
- Transcript parsers (plain/SRT/VTT/Riverside/Descript/Otter) with format
  detection + U5 chip labels; fixtures per format incl. edge cases
- ASR: Groq whisper-large-v3-turbo adapter (verbose_json, segment
  granularity, lang=en, vocab prompt, concurrency 3), 2s overlap dedupe,
  sentence-group merge (<=45s/<=500 chars), language gate (U19), fake adapter
- Browser compression port from site/app.html: 16 kHz mono Opus ~24 kbps,
  <=10-min chunks with 2s overlap, plan/mime/bitrade pure functions tested
- Intake UI per MK-4: upload tab (drop zone, allow-list, compress/upload
  progress), paste tab (format chip), details, sample episode, resumable
  status view via ?id=
- Routes: POST /api/uploads/token, POST /api/episodes, POST
  /api/episodes/:id/transcribe, GET /api/episodes/:id, GET /api/audio/:id
  at the documented db/queries + blob + entitlements boundaries
- Entitlements stub (free-plan limits) until WP-08; quota wall 402 + §6 copy
- Tests: 240 unit (parsers, merge, language, Groq, service at mocked
  boundaries, compression plan) + 7 new e2e (U4/U5/U19 paths)
# Conflicts:
#	docs/relaunch/DECISIONS.md
#	web/.env.example
#	web/db/queries.ts
#	web/lib/billing/entitlements.ts
#	web/lib/copy.ts
@gitAbid
gitAbid merged commit a25040b into main Oct 27, 2026
2 checks passed
@gitAbid
gitAbid deleted the wp-05-intake-asr branch October 27, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant