diff --git a/packages/stage-ui/src/stores/modules/speech.test.ts b/packages/stage-ui/src/stores/modules/speech.test.ts index 4d8f65c52..efdcc0a7a 100644 --- a/packages/stage-ui/src/stores/modules/speech.test.ts +++ b/packages/stage-ui/src/stores/modules/speech.test.ts @@ -126,6 +126,39 @@ describe('speech store helpers', () => { expect(request.input).toContain('volume="-5%"') }) + /** + * @example + * speechStore.resolveVoicePackSpeechInput({ text, voice, forceSSML: true, supportsSSML: false, supportsAdapterProsody: true }) + */ + it('keeps official adapter-backed speech input as plain text when global SSML is enabled', () => { + const speechStore = useSpeechStore() + const voice = { + id: 'voice-1', + name: 'Voice 1', + provider: OFFICIAL_SPEECH_PROVIDER_ID, + languages: [{ code: 'en-US', title: 'English' }], + gender: 'neutral', + } + + // ROOT CAUSE: + // + // Auto TTS can enable global SSML before the server routes the official + // speech provider to DashScope CosyVoice. DashScope rejects `...` + // payloads with `SSML text is not supported at the moment!`, so providers + // that apply prosody through adapter options must keep the text field plain. + const request = speechStore.resolveVoicePackSpeechInput({ + text: 'hello', + voice, + providerConfig: { pitch: 0 }, + forceSSML: true, + supportsSSML: false, + supportsAdapterProsody: true, + }) + + expect(request.input).toBe('hello') + expect(request.input).not.toContain(' { function resolveVoicePackSpeechInput(options: VoicePackSpeechInputOptions): VoicePackSpeechInput { const providerConfig = { ...options.providerConfig } + const canUseSSML = options.supportsSSML === true if (!options.params) { return { - input: options.forceSSML + input: options.forceSSML === true && canUseSSML ? generateSSML(options.text, options.voice, providerConfig) : options.text, providerConfig, @@ -493,6 +494,8 @@ export const useSpeechStore = defineStore('speech', () => { if (speed != null) providerConfig.speed = speed + const shouldUseSSML = canUseSSML && (options.forceSSML === true || (needsProsody && !options.supportsAdapterProsody)) + if (options.voicePack) { providerConfig.extraBody = { ...(providerConfig.extraBody as Record | undefined), @@ -511,11 +514,11 @@ export const useSpeechStore = defineStore('speech', () => { voice_pack: { pitch, volume }, } } - else if (needsProsody && !options.forceSSML && !options.supportsSSML) { + else if (needsProsody && !options.supportsAdapterProsody && !shouldUseSSML) { throw new Error('Voice Pack pitch and volume parameters require an SSML-capable speech provider.') } - if (!options.forceSSML && (!needsProsody || options.supportsAdapterProsody)) { + if (!shouldUseSSML && (!needsProsody || options.supportsAdapterProsody)) { return { input: options.text, providerConfig,