diff --git a/packages/stage-pages/src/pages/settings/providers/speech/comet-api-speech.vue b/packages/stage-pages/src/pages/settings/providers/speech/comet-api-speech.vue index d8a127a08..8eb3eda76 100644 --- a/packages/stage-pages/src/pages/settings/providers/speech/comet-api-speech.vue +++ b/packages/stage-pages/src/pages/settings/providers/speech/comet-api-speech.vue @@ -2,14 +2,16 @@ import type { SpeechProvider } from '@xsai-ext/providers/utils' import { - SpeechPlayground, + Alert, + SpeechPlaygroundOpenAICompatible, SpeechProviderSettings, } from '@proj-airi/stage-ui/components' +import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation' import { useSpeechStore } from '@proj-airi/stage-ui/stores/modules/speech' import { useProvidersStore } from '@proj-airi/stage-ui/stores/providers' -import { FieldRange } from '@proj-airi/ui' +import { FieldInput, FieldRange } from '@proj-airi/ui' import { storeToRefs } from 'pinia' -import { computed, ref, watch } from 'vue' +import { computed, onMounted, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' const speechStore = useSpeechStore() @@ -23,19 +25,77 @@ const defaultVoiceSettings = { // Get provider metadata const providerId = 'comet-api-speech' -const defaultModel = 'gpt-4o-mini-tts' +const defaultModel = 'tts' // https://www.cometapi.com/models/openai/tts/ -const speed = ref(1.0) +// Initialize speed from provider config or default +const speed = ref( + (providers.value[providerId] as any)?.voiceSettings?.speed + || (providers.value[providerId] as any)?.speed + || defaultVoiceSettings.speed, +) + +// Model selection +const model = computed({ + get: () => providers.value[providerId]?.model as string | undefined || defaultModel, + set: (value) => { + if (!providers.value[providerId]) + providers.value[providerId] = {} + providers.value[providerId].model = value + }, +}) + +const voice = computed({ + get: () => providers.value[providerId]?.voice || 'alloy', + set: (value) => { + if (!providers.value[providerId]) + providers.value[providerId] = {} + providers.value[providerId].voice = value + }, +}) + +// Watch provider config changes to sync local refs (for reset functionality) +watch( + () => providers.value[providerId], + (newConfig) => { + if (newConfig) { + const config = newConfig as any + const newSpeed = config.voiceSettings?.speed || config.speed || defaultVoiceSettings.speed + if (Math.abs(speed.value - newSpeed) > 0.001) + speed.value = newSpeed + + if (!config.model && model.value !== defaultModel) + model.value = defaultModel + + if (!config.voice && voice.value !== 'alloy') + voice.value = 'alloy' + } + else { + speed.value = defaultVoiceSettings.speed + model.value = defaultModel + voice.value = 'alloy' + } + }, + { deep: true, immediate: true }, +) // Check if API key is configured const apiKeyConfigured = computed(() => !!providers.value[providerId]?.apiKey) -const availableVoices = computed(() => { - return speechStore.availableVoices[providerId] || [] +// Ensure provider config is initialized on mount +onMounted(() => { + if (!providers.value[providerId]) { + providers.value[providerId] = {} + } + if (!providers.value[providerId].model) { + providers.value[providerId].model = defaultModel + } + if (!providers.value[providerId].voice) { + providers.value[providerId].voice = 'alloy' + } }) -// Generate speech with ElevenLabs-specific parameters -async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) { +// Generate speech with OpenAI-compatible parameters +async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean, modelId?: string) { const provider = await providersStore.getProviderInstance>(providerId) if (!provider) { throw new Error('Failed to initialize speech provider') @@ -44,26 +104,47 @@ async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: bo // Get provider configuration const providerConfig = providersStore.getProviderConfig(providerId) - // Get model from configuration or use default - const model = providerConfig.model as string | undefined || defaultModel + // Use the reactive model computed property (not a local variable) + const modelToUse = modelId || model.value || defaultModel - // ElevenLabs doesn't need SSML conversion, but if SSML is provided, use it directly return await speechStore.speech( provider, - model, + modelToUse, input, - voiceId, + voiceId || (voice.value as string), { ...providerConfig, ...defaultVoiceSettings, + speed: speed.value, }, ) } watch(speed, async () => { - const providerConfig = providersStore.getProviderConfig(providerId) - providerConfig.speed = speed.value + if (!providers.value[providerId]) + providers.value[providerId] = {} + providers.value[providerId].speed = speed.value }) + +watch(model, () => { + if (!providers.value[providerId]) + providers.value[providerId] = {} + providers.value[providerId].model = model.value +}) + +watch(voice, () => { + if (!providers.value[providerId]) + providers.value[providerId] = {} + providers.value[providerId].voice = voice.value +}) + +// Use the composable to get validation logic and state +const { + isValidating, + isValid, + validationMessage, + forceValid, +} = useProviderValidation(providerId)