feat(tts): dual-engine voice list (kokoro + pocket-tts)
Expose all available voices from both TTS engines via the /api/voices endpoint. AudioPlayer and profile voice-selector now group voices by engine and show a labelled optgroup. Voice type carries an engine field so the chapter-reader can route synthesis to the correct backend.
This commit is contained in:
@@ -4,6 +4,7 @@ import type { RequestHandler } from './$types';
|
||||
import { getBook, listChapterIdx } from '$lib/server/pocketbase';
|
||||
import { log } from '$lib/server/logger';
|
||||
import { backendFetch } from '$lib/server/scraper';
|
||||
import type { Voice } from '$lib/types';
|
||||
|
||||
/**
|
||||
* GET /api/chapter/[slug]/[n]
|
||||
@@ -48,11 +49,11 @@ export const GET: RequestHandler = async ({ params, url, locals }) => {
|
||||
? '<p>' + chapterData.text.replace(/\n{2,}/g, '</p><p>').replace(/\n/g, '<br>') + '</p>'
|
||||
: '';
|
||||
|
||||
let voices: string[] = [];
|
||||
let voices: Voice[] = [];
|
||||
try {
|
||||
const vRes = await backendFetch('/api/voices');
|
||||
if (vRes.ok) {
|
||||
const d = (await vRes.json()) as { voices: string[] };
|
||||
const d = (await vRes.json()) as { voices: Voice[] };
|
||||
voices = d.voices ?? [];
|
||||
}
|
||||
} catch {
|
||||
@@ -85,10 +86,10 @@ export const GET: RequestHandler = async ({ params, url, locals }) => {
|
||||
const chapterIdx = chapters.find((c) => c.number === n);
|
||||
if (!chapterIdx) error(404, `Chapter ${n} not found`);
|
||||
|
||||
let voices: string[] = [];
|
||||
let voices: Voice[] = [];
|
||||
try {
|
||||
if (voicesRes?.ok) {
|
||||
const data = (await voicesRes.json()) as { voices: string[] };
|
||||
const data = (await voicesRes.json()) as { voices: Voice[] };
|
||||
voices = data.voices ?? [];
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { backendFetch } from '$lib/server/scraper';
|
||||
import type { Voice } from '$lib/types';
|
||||
|
||||
/**
|
||||
* GET /api/voices
|
||||
* Proxies the voice list from the backend → Kokoro.
|
||||
* Returns { voices: string[] }
|
||||
* Proxies the voice list from the backend (Kokoro + pocket-tts).
|
||||
* Returns { voices: Voice[] }
|
||||
*/
|
||||
export const GET: RequestHandler = async () => {
|
||||
try {
|
||||
@@ -13,7 +14,7 @@ export const GET: RequestHandler = async () => {
|
||||
if (!res.ok) {
|
||||
return json({ voices: [] });
|
||||
}
|
||||
const data = (await res.json()) as { voices: string[] };
|
||||
const data = (await res.json()) as { voices: Voice[] };
|
||||
return json({ voices: data.voices ?? [] });
|
||||
} catch {
|
||||
return json({ voices: [] });
|
||||
|
||||
Reference in New Issue
Block a user