diff --git a/apps/server/src/routes/openai/v1/index.ts b/apps/server/src/routes/openai/v1/index.ts index 22d23b607..d67f0eb15 100644 --- a/apps/server/src/routes/openai/v1/index.ts +++ b/apps/server/src/routes/openai/v1/index.ts @@ -647,11 +647,16 @@ export function createV1Routes( * configured yet so the client can render "no voices" instead of * exploding. */ - async function handleListStreamingVoices(_c: Context) { + async function handleListStreamingVoices(c: Context) { const upstream = await configKV.getOptional('STREAMING_TTS_UPSTREAM') if (!upstream || !upstream.baseURL) return Response.json({ voices: [], recommended: {} }) + // Pass through the api_resource_id (e.g. `seed-tts-2.0`). unspeech + // filters the embedded Volcengine catalogue server-side; absent model + // means "return everything streaming-safe". + const model = c.req.query('model') + let voicesURL: string try { const u = new URL(upstream.baseURL) @@ -659,7 +664,10 @@ export function createV1Routes( // stream and the REST voices endpoint on the same listener. u.protocol = u.protocol === 'wss:' ? 'https:' : 'http:' u.pathname = '/api/voices' - u.search = '?provider=volcengine' + const params = new URLSearchParams({ provider: 'volcengine' }) + if (model) + params.set('model', model) + u.search = `?${params.toString()}` voicesURL = u.toString() } catch (err) { diff --git a/packages/stage-pages/src/pages/settings/providers/speech/official-provider-speech-streaming.vue b/packages/stage-pages/src/pages/settings/providers/speech/official-provider-speech-streaming.vue index c6f23c0e1..cc2a8c283 100644 --- a/packages/stage-pages/src/pages/settings/providers/speech/official-provider-speech-streaming.vue +++ b/packages/stage-pages/src/pages/settings/providers/speech/official-provider-speech-streaming.vue @@ -48,7 +48,7 @@ const voicesLoading = ref(false) async function loadVoices() { voicesLoading.value = true try { - await speechStore.loadVoicesForProvider(providerId) + await speechStore.loadVoicesForProvider(providerId, model.value) } finally { voicesLoading.value = false @@ -62,11 +62,9 @@ onMounted(async () => { await loadVoices() }) -// Reload voices when the model variant changes. The streaming provider -// shares one voice catalogue across model variants (both Seed-TTS 2.0 and -// 1.0 expose the same `zh_female_*` ids), so this is mostly a refresh — -// but it keeps the flow consistent with provider pages where the variant -// actually does change the catalogue. +// Volcengine TTS 1.0 and 2.0 ship different voice catalogues (mars/moon/ICL +// vs uranus/saturn; see unspeech voices.go). Re-fetch on model change so the +// list switches accordingly. watch(model, async () => { await loadVoices() }) diff --git a/packages/stage-ui/src/components/scenarios/providers/speech-playground-openai-compatible.vue b/packages/stage-ui/src/components/scenarios/providers/speech-playground-openai-compatible.vue index e5c6c1aa3..8993fd512 100644 --- a/packages/stage-ui/src/components/scenarios/providers/speech-playground-openai-compatible.vue +++ b/packages/stage-ui/src/components/scenarios/providers/speech-playground-openai-compatible.vue @@ -178,11 +178,6 @@ defineExpose({ {{ errorMessage }}