diff --git a/scraper/internal/server/server.go b/scraper/internal/server/server.go index 7af9ad9..19c9006 100644 --- a/scraper/internal/server/server.go +++ b/scraper/internal/server/server.go @@ -130,8 +130,8 @@ func (s *Server) ListenAndServe(ctx context.Context) error { mux.HandleFunc("POST /ui/scrape/book", s.handleUIScrapeBook) mux.HandleFunc("GET /ui/scrape/status", s.handleUIScrapeStatus) mux.HandleFunc("GET /ui/ranking/status", s.handleRankingStatus) - // Plain-text chapter content for browser-side TTS - mux.HandleFunc("GET /ui/chapter-text/{slug}/{n}", s.handleChapterText) + // Plain-text chapter content (used server-side for audio generation) + mux.HandleFunc("GET /api/chapter-text/{slug}/{n}", s.handleChapterText) // Server-side audio generation via Kokoro /v1/audio/speech. // Generation can take several minutes, so wrap in its own timeout handler. audioGenHandler := http.TimeoutHandler( @@ -139,9 +139,9 @@ func (s *Server) ListenAndServe(ctx context.Context) error { 10*time.Minute, `{"error":"audio generation timed out"}`, ) - mux.Handle("POST /ui/audio/{slug}/{n}", audioGenHandler) + mux.Handle("POST /api/audio/{slug}/{n}", audioGenHandler) // Proxy route: fetches the generated file from Kokoro /v1/download/{filename}. - mux.HandleFunc("GET /ui/audio-proxy/{slug}/{n}", s.handleAudioProxy) + mux.HandleFunc("GET /api/audio-proxy/{slug}/{n}", s.handleAudioProxy) srv := &http.Server{ Addr: s.addr, @@ -289,7 +289,7 @@ func (s *Server) handleDeleteProgress(w http.ResponseWriter, r *http.Request) { } // handleChapterText returns the plain text of a chapter (markdown stripped) -// for browser-side TTS. The browser POSTs this directly to Kokoro-FastAPI. +// for server-side audio generation. Called by handleAudioGenerate internally. func (s *Server) handleChapterText(w http.ResponseWriter, r *http.Request) { slug := r.PathValue("slug") n, err := strconv.Atoi(r.PathValue("n")) @@ -309,7 +309,7 @@ func (s *Server) handleChapterText(w http.ResponseWriter, r *http.Request) { // ─── Audio generation via Kokoro /v1/audio/speech ──────────────────────────── // -// handleAudioGenerate handles POST /ui/audio/{slug}/{n}. +// 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 @@ -463,9 +463,9 @@ func (s *Server) generateSpeech(ctx context.Context, text, voice string, speed f } // writeAudioResponse writes the JSON response for a generated audio chapter. -// The URL points to our proxy handler which fetches from Kokoro on demand. +// The URL points to our proxy handler GET /api/audio-proxy/{slug}/{n}. func (s *Server) writeAudioResponse(w http.ResponseWriter, slug string, n int, voice string, speed float64, filename string) { - proxyURL := fmt.Sprintf("/ui/audio-proxy/%s/%d?voice=%s&speed=%.1f", slug, n, voice, speed) + proxyURL := fmt.Sprintf("/api/audio-proxy/%s/%d?voice=%s&speed=%.1f", slug, n, voice, speed) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]interface{}{ "url": proxyURL, @@ -473,7 +473,7 @@ func (s *Server) writeAudioResponse(w http.ResponseWriter, slug string, n int, v }) } -// handleAudioProxy handles GET /ui/audio-proxy/{slug}/{n}. +// handleAudioProxy handles GET /api/audio-proxy/{slug}/{n}. // It looks up the Kokoro download filename for this chapter (voice/speed) and // proxies GET /v1/download/{filename} from the Kokoro server back to the browser. func (s *Server) handleAudioProxy(w http.ResponseWriter, r *http.Request) { diff --git a/ui/src/routes/api/audio/[slug]/[n]/+server.ts b/ui/src/routes/api/audio/[slug]/[n]/+server.ts index c196bbe..3ceb2cb 100644 --- a/ui/src/routes/api/audio/[slug]/[n]/+server.ts +++ b/ui/src/routes/api/audio/[slug]/[n]/+server.ts @@ -40,8 +40,8 @@ export const POST: RequestHandler = async ({ params, request }) => { const data = (await scraperRes.json()) as { url: string; filename: string }; - // Rewrite the proxy URL from the scraper's /ui/audio-proxy/... to our own - // /api/audio/[slug]/[n]?voice=...&speed=... so the browser never calls the scraper directly. + // The scraper returns a proxy URL pointing to /api/audio-proxy/... — we rewrite + // it to our own /api/audio/[slug]/[n]?... so the browser never calls the scraper directly. const voice = body.voice ?? ''; const speed = body.speed ?? 1.0; const qs = new URLSearchParams();