Commit Graph

14 Commits

Author SHA1 Message Date
Giancarmine Salucci
01845bec25 test: comprehensive coverage for 503 retry loop and getModelStatus
submitJob — 503 retry behavior (10 new tests):
- calls onModelWaiting with correct state + retryAfterSecs on each 503
- retries until model ready and returns job_id
- tracks all three model states (unloaded, loading, waiting_for_gpu)
- uses retry_after_secs from response body
- falls back to Retry-After header when body field absent
- falls back to 15s when both body and header are absent
- throws after maxAttempts exhausted (fetch called exactly N times)
- does NOT call onModelWaiting for non-503 errors
- does NOT retry on non-503 errors (throws immediately, one fetch call)
- works correctly without an onModelWaiting callback

getModelStatus (6 new tests):
- returns parsed status for each model state tag
- includes optional fields (loaded_at, vram_*, retry_in_secs)
- calls the correct WHISPER_URL/model/status endpoint
- throws when server returns non-ok

Uses vi.useFakeTimers()/runAllTimersAsync() to eliminate real delays.
Rejection handler attached before timer advance to avoid unhandled-rejection
false positives from Vitest's detector.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 00:14:09 +02:00
Giancarmine Salucci
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 Salucci
ffd5d48c0d fix: increase body size limit to 500MB for audio uploads
All checks were successful
Build & Push Docker Image / build-and-push (push) Successful in 42s
Default SvelteKit node adapter body limit is 512KB — too small for
audio recordings (30s ~556KB, longer recordings much larger).
Set bodySize: 500MB in adapter config. Also set BODY_SIZE_LIMIT env
in production compose .env as belt-and-suspenders.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 19:32:28 +02:00
Giancarmine Salucci
ed5e88f5ca fix: install yt-dlp via pip instead of prebuilt binary
All checks were successful
Build & Push Docker Image / build-and-push (push) Successful in 42s
The prebuilt yt-dlp binary is compiled against glibc and fails on
Alpine Linux (musl libc) with 'cannot execute'. Install python3 +
py3-pip and use pip to install yt-dlp instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 19:17:18 +02:00
Giancarmine Salucci
dc65c028c1 fix: disable CSRF origin check to allow Web Share Target
All checks were successful
Build & Push Docker Image / build-and-push (push) Successful in 40s
SvelteKit's CSRF check runs before the handle hook and blocks POSTs
whose Origin header doesn't match the site origin. Web Share Target
POSTs from any external app (YouTube, Chrome share sheet, etc.) are
legitimately cross-origin.

checkOrigin: false is safe here — the app has no cookie-based session
auth, so there is no CSRF attack surface.

Also remove the ineffective hooks.server.ts approach.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 19:02:07 +02:00
Giancarmine Salucci
08adff1562 fix: bypass CSRF for Web Share Target POST
All checks were successful
Build & Push Docker Image / build-and-push (push) Successful in 41s
SvelteKit's CSRF guard rejects POST requests whose Origin header doesn't
match the site's own origin. Web Share Target POSTs legitimately arrive
from external origins (e.g. youtube.com, OS share sheet). Strip the
Origin header in a handle hook for /share POST only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 18:58:39 +02:00
Giancarmine Salucci
d1295ce343 feat: add retry/delete for jobs
All checks were successful
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 Salucci
37175ec791 fix: ffmpeg/yt-dlp/tz in image, UID 1000, reactive accent store
All checks were successful
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 Salucci
453029c139 fix: use npm install instead of npm ci in Dockerfile
All checks were successful
Build & Push Docker Image / build-and-push (push) Successful in 46s
npm ci fails with optional platform-specific dependencies (@emnapi/core,
@emnapi/runtime) that are not recorded in the lock file for Alpine Linux.
npm install handles optional dependencies correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:01:14 +02:00
Giancarmine Salucci
b43ad9ce9a chore: update package-lock.json to sync with package.json
Some checks failed
Build & Push Docker Image / build-and-push (push) Failing after 22s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 16:57:21 +02:00
Giancarmine Salucci
f1f04e13e5 ci: restore REGISTRY_USERNAME/REGISTRY_TOKEN secrets (now set on repo)
Some checks failed
Build & Push Docker Image / build-and-push (push) Failing after 22s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 16:54:58 +02:00
Giancarmine Salucci
b6566e4590 ci: use github.actor + github.token for registry login
Some checks failed
Build & Push Docker Image / build-and-push (push) Failing after 12s
github.actor and github.token are the correct Gitea Actions context
variables (gitea.* context doesn't exist in act_runner).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 16:53:29 +02:00
Giancarmine Salucci
95eea34011 ci: use auto-provided GITEA_TOKEN for registry login
Some checks failed
Build & Push Docker Image / build-and-push (push) Failing after 12s
Avoids needing to set custom REGISTRY_USERNAME/REGISTRY_TOKEN secrets.
The built-in secrets.GITEA_TOKEN has write:package access for pushing
to the Gitea container registry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 16:50:51 +02:00
Giancarmine Salucci
13a96b6efa Initial commit: Tonemark PWA
Some checks failed
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