Commit Graph
15 Commits
Author SHA1 Message Date
Giancarmine Salucci 52715ec481 D2: remove source guard on retry buttons, show error in UI
Queue page:
- Remove && job.source?.startsWith('http') guard on retry button
- Move retry button outside .row-actions hover zone (always visible)
- Add job.error inline in meta line for failed/cancelled jobs
- data-status=failed attribute on failed rows
- Error tint on failed row title

Job detail page:
- Remove source guard from canRetry derived
- Enhanced error banner: warning icon, 'Transcription failed' title,
  error message, inline Retry + Delete action buttons
- btn, btn.primary, btn.ghost CSS styles

Home page:
- Add job.error as third line in recent items for failed jobs
- recent-failed CSS class with error-text tint on meta
- recent-err-line styling

fixes #story-d2-retry-widen-upload--d1-d6-evised
2026-07-09 03:29:18 +02:00
Giancarmine Salucci f03b757174 D2: Widen retry to upload-source jobs
- Remove http-only guard in retry endpoint (src/routes/api/jobs/[id]/retry/+server.ts)
- Extend pipeline.ts retryJob() to handle upload-source jobs:
  read file from persistent storage, pass buffer to runJob
- Change saveUploadedFile() to save to DATA_DIR/uploads/{jobId}/
  (survives cleanupJobTmp, available for retry on failure)
- Add getUploadPath(), cleanupUploadDir() to downloader.ts
- Remove cleanupFiles(rawAudioPath) from catch block in runJob()
  — cleanupJobTmp handles TMP_DIR for YouTube, uploads survive
- Add cleanupUploadDir() on webhook success path
- Add retry-endpoint.test.ts with 9 tests covering:
  - 404 for unknown job
  - 409 for non-retryable status (done, pending)
  - 200 for cancelled (retryable)
  - 200 for failed YouTube job (existing behavior)
  - 200 for failed upload-source job (new behavior, .webm and .mp3)
  - Negative: retryJob not called on 404/409
- Add downloader.test.ts tests for saveUploadedFile persistent path,
  getUploadPath, cleanupUploadDir, isolation, and non-existent cleanup
- Fix webhook.test.ts mock to include cleanupUploadDir

All 189 tests pass across 12 test files.
2026-07-08 01:25:49 +02:00
Giancarmine Salucci 92683e2047 feat: add /api/health endpoint for deployment health checks
Build & Push Docker Image / test (push) Successful in 15s
Build & Push Docker Image / build-and-push (push) Successful in 49s
2026-07-08 00:58:06 +02:00
mozempkandCopilot 1072679360 fix(whisper): handle model warmup events
Build & Push Docker Image / test (push) Successful in 12s
Build & Push Docker Image / build-and-push (push) Successful in 52s
- Ignore backend model lifecycle webhooks so model warmup does not
  mark jobs done early
- Parse batched SSE messages and relay model load states during
  submit retries

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-15 00:08:32 +02:00
mozempkandCopilot f70cefc5e9 fix(progress): separate model warmup state
Build & Push Docker Image / test (push) Successful in 11s
Build & Push Docker Image / build-and-push (push) Successful in 42s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-12 00:52:33 +02:00
mozempkandCopilot 929c482497 refactor(transcript): drop Tonemark rewrite
Build & Push Docker Image / test (push) Successful in 10s
Build & Push Docker Image / build-and-push (push) Successful in 50s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-12 00:10:32 +02:00
mozempkandCopilot 672b161cda fix(transcript): collapse rolling segment echoes
Build & Push Docker Image / test (push) Successful in 12s
Build & Push Docker Image / build-and-push (push) Successful in 45s
Normalize incremental backend hypothesis chains before persistence and ignore stale or replayed webhook callbacks so duplicate transcript text does not survive ingest.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 22:46:38 +02:00
Giancarmine SalucciandCopilot 470dd1642f fix: clear modelWarming notice when job completes via SSE done event
Build & Push Docker Image / test (push) Failing after 10s
Build & Push Docker Image / build-and-push (push) Has been skipped
Previously, if a job completed while the model-warming notice was shown
(e.g. model loaded mid-job), the 'Warming up model' banner persisted on
the Done screen because the SSE 'done' handler didn't clear modelWarming.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-10 15:52:28 +02:00
Giancarmine SalucciandCopilot 10a3669b42 fix: FormData stream exhausted on retry + undefined segments crash
Build & Push Docker Image / test (push) Successful in 32s
Build & Push Docker Image / build-and-push (push) Successful in 46s
Two bugs triggered together when the model was unloaded during a job:

1. submitJob() created FormData/createReadStream once outside the retry loop.
   After a 503, the audio ReadStream was consumed and subsequent retries sent
   an empty body to whisper, causing it to return segments:undefined.

2. webhook handler cast whisperJob.segments as Segment[] without guarding
   against undefined, so deduplicateSegments(undefined) crashed with
   'Cannot read properties of undefined (reading 'map')' — stored as job.error.

Fixes:
- Move FormData + createReadStream inside the retry loop (fresh stream per attempt)
- Use (whisperJob.segments ?? []) in webhook handler
- Add Array.isArray guard at top of deduplicateSegments() as belt-and-suspenders

Tests:
- New: verifies createReadStream called once per attempt (3 attempts = 3 streams)
- New: webhook handles segments:undefined without throwing
- New: webhook handles segments:null without throwing
- 150/150 passing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-10 15:37:07 +02:00
Giancarmine SalucciandCopilot 53f874aec7 feat: proxy POST /model/unload endpoint
Build & Push Docker Image / build-and-push (push) Successful in 43s
- Add unloadModel() to whisper.ts: POSTs to /model/unload with 10s
  timeout, returns parsed JSON body, throws on non-ok response
- Create src/routes/api/model/unload/+server.ts: thin POST proxy,
  passes whisper's response through, returns 502 if whisper unreachable
- Add 3 unloadModel tests (success, WHISPER_URL config, error propagation)
  — 147/147 passing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 15:48:47 +02:00
Giancarmine SalucciandCopilot 04142b17a8 feat: whisper-side cancellation + SSE-triggered retry
Build & Push Docker Image / build-and-push (push) Successful in 48s
- Add cancelJob() to whisper.ts: sends DELETE /jobs/:id to the whisper
  server (best-effort, errors silently ignored)
- DELETE /api/jobs/[id] now calls cancelJob() when cancelling an active
  job that has a whisperJobId, stopping GPU use immediately
- Webhook handler guards against locally-cancelled jobs: returns ok early
  so whisper's late completion cannot overwrite cancelled status or send
  a phantom 'Transcript ready' notification
- Replace blind sleep(Retry-After + 1s) in submitJob() with
  waitForModelReady(): subscribes to /model/events SSE and proceeds as
  soon as state:ready arrives; falls back to the Retry-After timeout if
  SSE is unreachable or closes without model_ready
- Refactor retry tests to use URL-aware makeJobFetch() helper; add 7 new
  tests (3 SSE-triggered retry, 3 cancelJob, 1 webhook cancelled-guard)
  — 144/144 passing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 00:40:40 +02:00
Giancarmine SalucciandCopilot b90d57984c feat: model-on-demand lifecycle — retry on 503, live status pill, warming indicator
- whisper.ts: add getModelStatus(); fix submitJob() to retry on 503 using
  Retry-After header instead of throwing; optional onModelWaiting callback
  lets the pipeline surface model state to the UI during the wait
- pipeline.ts: pass onModelWaiting callback → emits model_warming SSE event
  so the job detail page can show 'Warming up model…' while waiting
- types.ts: add ModelStateTag union and ModelStatus interface
- api/model/status: GET route proxies whisper /model/status (falls back to
  {state:'unloaded'} if whisper unreachable)
- api/model/events: GET route relays whisper SSE stream to the browser;
  AbortController tied to request.signal cleans up on disconnect
- layout.svelte: status pill is now live — initial fetch + EventSource on
  /api/model/events; dot colour + label reflect real model state with a
  pulsing animation while loading or waiting_for_gpu
- jobs/[id]/+page.svelte: handle model_warming event type → show a yellow
  'Warming up model…' sub-label with spinner inside the progress card
- whisper.test.ts: update submitJob mocks to status:202 to match real API

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 00:08:21 +02:00
Giancarmine SalucciandCopilot d1295ce343 feat: add retry/delete for jobs
Build & Push Docker Image / build-and-push (push) Successful in 41s
- db.ts: add resetJob() and deleteJob() statements + exports
- pipeline.ts: export retryJob() — resets job state and re-runs pipeline
- DELETE /api/jobs/[id]: hard-delete terminal jobs (done/failed/cancelled);
  keep cancel-only behavior for active jobs
- POST /api/jobs/[id]/retry: new endpoint; validates failed/cancelled URL job,
  resets and re-runs via retryJob()
- jobs/[id]/+page.svelte: wire Cancel/Retry/Delete buttons with fetch calls;
  fix hardcoded ACCENT → accent store
- jobs/+page.svelte: per-row Retry+Delete icon buttons (visible on hover);
  fix hardcoded ACCENT → accent store

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:42:54 +02:00
Giancarmine SalucciandCopilot 37175ec791 fix: ffmpeg/yt-dlp/tz in image, UID 1000, reactive accent store
Build & Push Docker Image / build-and-push (push) Successful in 42s
- runtime: use node user (uid=1000, gid=1000) instead of custom tonemark uid=1001
- add ffmpeg and yt-dlp to runtime image (required by audio pipeline)
- add tzdata, set TZ=Europe/Zurich
- +page.svelte: replace hardcoded ACCENT constant with $derived($accent.value)
  so the home page reacts to accent store changes from Settings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:35:39 +02:00
Giancarmine SalucciandCopilot 13a96b6efa Initial commit: Tonemark PWA
Build & Push Docker Image / build-and-push (push) Failing after 11s
Tonemark is a SvelteKit PWA for transcribing YouTube videos, audio
and video files, and microphone recordings using a local Whisper backend.

Features:
- Dark glassmorphic UI with electric-lime accent (5 switchable themes)
- Rail nav (desktop) / tab bar (mobile) layout
- Drop zone, YouTube URL input, and live audio recording inputs
- Audio mode waveform cards (none / standard / aggressive / auto)
- Real-time transcription progress with animated waveform
- Job queue with SSE streaming updates
- Push notifications on job completion
- PWA with native SvelteKit service worker
- SRT / TXT / MD / JSON transcript downloads

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 16:41:25 +02:00