From c4033e5392a03adf3ef1cd119bb7c2da380661c7 Mon Sep 17 00:00:00 2001 From: brunorezio Date: Sat, 25 Jul 2026 23:35:36 +0100 Subject: [PATCH] normalize TTS voice lists to strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenAI-compatible TTS servers disagree on the /v1/audio/voices payload: some return plain strings, Kokoro returns objects like { id, name }. The handler cast the response to { voices: string[] } without checking, so the objects reached the voice — expects strings, and an object reaching the picker crashes the settings page, so +// flatten to ids here and drop anything unrecognisable. +const normalizeVoices = (raw: unknown): string[] => { + if (!Array.isArray(raw)) return []; + return raw + .map((entry) => { + if (typeof entry === 'string') return entry; + if (entry && typeof entry === 'object') { + const { id, name, voice_id: voiceId } = entry as Record; + for (const candidate of [id, voiceId, name]) if (typeof candidate === 'string' && candidate) return candidate; + } + return null; + }) + .filter((voice): voice is string => voice !== null); +}; + ttsRouter.post('/voices', async (ctx) => { const body = await ctx.req.json<{ provider: string; url?: string; apiKey?: string; model?: string }>(); @@ -51,8 +69,8 @@ ttsRouter.post('/voices', async (ctx) => { headers: { 'xi-api-key': body.apiKey }, }); if (!res.ok) return ctx.json({ error: `ElevenLabs error: ${res.status}` }, 500); - const json = (await res.json()) as { voices: { voice_id: string; name: string }[] }; - return ctx.json({ voices: json.voices.map((v) => v.voice_id) }); + const json = (await res.json().catch(() => null)) as { voices?: unknown } | null; + return ctx.json({ voices: normalizeVoices(json?.voices) }); } // OpenAI-compatible: try local server first, fallback to HuggingFace @@ -63,8 +81,9 @@ ttsRouter.post('/voices', async (ctx) => { // Try /v1/audio/voices on the local server const localRes = await fetch(`${body.url.replace(/\/+$/, '')}/v1/audio/voices`, { headers }).catch(() => null); if (localRes?.ok) { - const json = (await localRes.json()) as { voices: string[] }; - return ctx.json({ voices: json.voices }); + const json = (await localRes.json().catch(() => null)) as { voices?: unknown } | null; + const voices = normalizeVoices(json?.voices); + if (voices.length > 0) return ctx.json({ voices }); } // Fallback: get model repo from /v1/models, then list voices from HuggingFace