From 89f0dfb11335ba89325d1e5b3e02ad672422b70c Mon Sep 17 00:00:00 2001 From: Admin Date: Sat, 7 Mar 2026 20:12:08 +0500 Subject: [PATCH] Add async audio generation: job tracking in PocketBase + UI polling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace blocking POST /api/audio with a non-blocking 202 flow: the Go handler immediately enqueues a job in a new `audio_jobs` PocketBase collection and returns {job_id, status}. A background goroutine runs the actual Kokoro TTS work and updates job status (pending → generating → done/failed). A new GET /api/audio/status/{slug}/{n} endpoint lets clients poll progress. The SvelteKit proxy and AudioPlayer.svelte are updated to POST, then poll the status route every 2s until done. --- .../orchestrator/orchestrator_test.go | 12 + scraper/internal/server/handlers_audio.go | 222 +++++++++++++----- scraper/internal/server/server.go | 25 +- scraper/internal/storage/hybrid.go | 60 +++++ scraper/internal/storage/pocketbase.go | 84 +++++++ scraper/internal/storage/store.go | 27 +++ ui/src/lib/components/AudioPlayer.svelte | 100 +++++++- ui/src/routes/api/audio/[slug]/[n]/+server.ts | 26 +- .../api/audio/status/[slug]/[n]/+server.ts | 67 ++++++ 9 files changed, 540 insertions(+), 83 deletions(-) create mode 100644 ui/src/routes/api/audio/status/[slug]/[n]/+server.ts diff --git a/scraper/internal/orchestrator/orchestrator_test.go b/scraper/internal/orchestrator/orchestrator_test.go index d1366c6..b73d9eb 100644 --- a/scraper/internal/orchestrator/orchestrator_test.go +++ b/scraper/internal/orchestrator/orchestrator_test.go @@ -173,6 +173,18 @@ func (s *mockStore) UpdateScrapeTask(_ context.Context, _ string, _ storage.Scra func (s *mockStore) ListScrapeTasks(_ context.Context) ([]storage.ScrapeTask, error) { return nil, nil } +func (s *mockStore) CreateAudioJob(_ context.Context, _ string, _ int, _ string) (string, error) { + return "audio-job-id", nil +} +func (s *mockStore) UpdateAudioJob(_ context.Context, _, _, _ string, _ time.Time) error { + return nil +} +func (s *mockStore) GetAudioJob(_ context.Context, _ string) (storage.AudioJob, bool, error) { + return storage.AudioJob{}, false, nil +} +func (s *mockStore) ListAudioJobs(_ context.Context) ([]storage.AudioJob, error) { + return nil, nil +} // ── helpers ─────────────────────────────────────────────────────────────────── diff --git a/scraper/internal/server/handlers_audio.go b/scraper/internal/server/handlers_audio.go index f756c29..5c19111 100644 --- a/scraper/internal/server/handlers_audio.go +++ b/scraper/internal/server/handlers_audio.go @@ -16,17 +16,15 @@ import ( // // handleAudioGenerate handles POST /api/audio/{slug}/{n}. // -// It calls Kokoro's POST /v1/audio/speech with return_download_link=true. -// Kokoro generates the audio, saves it to its own temp storage, and returns -// the download filename in the X-Download-Path response header. -// We cache that filename (in memory, keyed by slug/chapter/voice) and -// return a proxy URL that the browser sets as audio.src. +// The handler is non-blocking: it creates an audio_jobs record in PocketBase +// with status="pending", then fires a background goroutine to call Kokoro. +// The caller should poll GET /api/audio/status/{slug}/{n} to track progress. // -// TTS is always generated at speed 1.0; playback speed is controlled -// client-side via the