@@ -729,27 +824,14 @@ function handleDeleteProvider(providerId: string) {
+
+
({
locale: { value: 'en-US' },
@@ -290,6 +290,46 @@ describe('speech store helpers', () => {
expect(listVoices).not.toHaveBeenCalled()
})
+ /**
+ * @example
+ * speechStore.ensureActiveSpeechModel()
+ */
+ it('keeps the synthetic Voice Pack model selected for the regular official provider', () => {
+ const providersStore = useProvidersStore()
+ const speechStore = useSpeechStore()
+ speechStore.activeSpeechProvider = OFFICIAL_SPEECH_PROVIDER_ID
+ speechStore.activeSpeechModel = VOICE_PACK_MODEL_ID
+ speechStore.activeSpeechVoiceId = 'voice-pack:vp-1'
+ providersStore.providerRuntimeState[OFFICIAL_SPEECH_PROVIDER_ID].models = [
+ { id: 'microsoft/v1', name: 'microsoft/v1', provider: OFFICIAL_SPEECH_PROVIDER_ID },
+ ]
+
+ speechStore.ensureActiveSpeechModel()
+
+ expect(speechStore.activeSpeechModel).toBe(VOICE_PACK_MODEL_ID)
+ expect(speechStore.activeSpeechVoiceId).toBe('voice-pack:vp-1')
+ })
+
+ /**
+ * @example
+ * await speechStore.loadVoicesForProvider(OFFICIAL_SPEECH_PROVIDER_ID, VOICE_PACK_MODEL_ID)
+ */
+ it('does not request raw official voices for the synthetic Voice Pack model', async () => {
+ const providersStore = useProvidersStore()
+ const speechStore = useSpeechStore()
+ const listVoices = vi.fn(async () => [{ id: 'raw-voice', name: 'Raw', provider: OFFICIAL_SPEECH_PROVIDER_ID, languages: [] }])
+ const metadata = providersStore.providerMetadata[OFFICIAL_SPEECH_PROVIDER_ID]
+ metadata.capabilities.listVoices = listVoices
+
+ const voices = await speechStore.loadVoicesForProvider(
+ OFFICIAL_SPEECH_PROVIDER_ID,
+ VOICE_PACK_MODEL_ID,
+ )
+
+ expect(voices).toEqual([])
+ expect(listVoices).not.toHaveBeenCalled()
+ })
+
/**
* @example
* speechStore.ensureActiveSpeechModel()
diff --git a/packages/stage-ui/src/stores/modules/speech.ts b/packages/stage-ui/src/stores/modules/speech.ts
index 95722252c..ea24fe375 100644
--- a/packages/stage-ui/src/stores/modules/speech.ts
+++ b/packages/stage-ui/src/stores/modules/speech.ts
@@ -42,6 +42,8 @@ interface VoicePackSpeechInput {
const voicePackSupportedParams = new Set(['pitch', 'rate', 'volume'])
+export const VOICE_PACK_MODEL_ID = 'voice-pack'
+
export function voicePackForSpeechProvider(
providerId: string | undefined,
voicePack: VoicePackSnapshot | undefined,
@@ -205,6 +207,10 @@ export const useSpeechStore = defineStore('speech', () => {
return []
}
+ if (provider === OFFICIAL_SPEECH_PROVIDER_ID && model === VOICE_PACK_MODEL_ID) {
+ return []
+ }
+
// Streaming provider visibility is server-driven and only confirmed after
// the auth probe force-configures it. Keep the gate at the public loader so
// pages cannot bypass it and issue `/voices/streaming` while unavailable.
@@ -282,6 +288,9 @@ export const useSpeechStore = defineStore('speech', () => {
if (!models.length)
return
+ if (activeSpeechModel.value === VOICE_PACK_MODEL_ID)
+ return
+
const hasValidSelection = !!activeSpeechModel.value && models.some(m => m.id === activeSpeechModel.value)
if (hasValidSelection)
return