fix(stage-ui): should have option to completely disable Speech module

This commit is contained in:
Neko Ayaka
2026-03-05 18:06:49 +08:00
parent b24ba8cc26
commit c1d315b84f
5 changed files with 90 additions and 4 deletions
@@ -615,6 +615,9 @@ pages:
deepgram-tts:
description: deepgram.com
title: Deepgram
speech-noop:
title: None
description: No speech output.
deepseek:
description: deepseek.com
title: DeepSeek
@@ -238,6 +238,21 @@ function updateCustomVoiceName(value: string | undefined) {
function updateCustomModelName(value: string | undefined) {
activeSpeechModel.value = value || ''
}
function handleDeleteProvider(providerId: string) {
if (providerId === 'speech-noop') {
return
}
if (activeSpeechProvider.value === providerId) {
activeSpeechProvider.value = 'speech-noop'
activeSpeechModel.value = ''
activeSpeechVoiceId.value = ''
activeSpeechVoice.value = undefined
}
providersStore.deleteProvider(providerId)
}
</script>
<template>
@@ -268,7 +283,18 @@ function updateCustomModelName(value: string | undefined) {
:title="metadata.localizedName || 'Unknown'"
:description="metadata.localizedDescription"
@click="trackProviderClick(metadata.id, 'speech')"
/>
>
<template #topRight>
<button
v-if="metadata.id !== 'speech-noop'"
type="button"
class="rounded bg-neutral-100 p-1 text-neutral-600 transition-colors dark:bg-neutral-800/60 hover:bg-neutral-200 dark:text-neutral-300 dark:hover:bg-neutral-700/60"
@click.stop.prevent="handleDeleteProvider(metadata.id)"
>
<div i-solar:trash-bin-trash-bold-duotone class="text-base" />
</button>
</template>
</RadioCardSimple>
<RouterLink
to="/settings/providers#speech"
border="2px solid"
@@ -303,7 +329,7 @@ function updateCustomModelName(value: string | undefined) {
</div>
<div>
<!-- Model selection section -->
<div v-if="activeSpeechProvider">
<div v-if="activeSpeechProvider && activeSpeechProvider !== 'speech-noop'">
<div flex="~ col gap-4">
<div>
<h2 class="text-lg md:text-2xl">
@@ -395,7 +421,7 @@ function updateCustomModelName(value: string | undefined) {
</div>
<!-- Voice Configuration Section -->
<div v-if="activeSpeechProvider">
<div v-if="activeSpeechProvider && activeSpeechProvider !== 'speech-noop'">
<div flex="~ col gap-4">
<div>
<h2 class="text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
@@ -240,6 +240,9 @@ const speechPipeline = createSpeechPipeline<AudioBuffer>({
if (signal.aborted)
return null
if (activeSpeechProvider.value === 'speech-noop')
return null
if (!activeSpeechProvider.value)
return null
+27 -1
View File
@@ -25,7 +25,7 @@ export const useSpeechStore = defineStore('speech', () => {
const { allAudioSpeechProvidersMetadata } = storeToRefs(providersStore)
// State
const activeSpeechProvider = useLocalStorageManualReset<string>('settings/speech/active-provider', '')
const activeSpeechProvider = useLocalStorageManualReset<string>('settings/speech/active-provider', 'speech-noop')
const activeSpeechModel = useLocalStorageManualReset<string>('settings/speech/active-model', '')
const activeSpeechVoiceId = useLocalStorageManualReset<string>('settings/speech/voice', '')
const activeSpeechVoice = refManualReset<VoiceInfo | undefined>(undefined)
@@ -124,6 +124,29 @@ export const useSpeechStore = defineStore('speech', () => {
immediate: true,
})
if (!activeSpeechProvider.value) {
activeSpeechProvider.value = 'speech-noop'
}
watch(
() => providersStore.configuredSpeechProvidersMetadata.map(provider => provider.id),
(configuredProviderIds) => {
if (!activeSpeechProvider.value)
return
// NOTICE: clear stale selection when the currently selected speech provider
// is no longer configured to avoid implicit fallback behavior from persisted state.
if (!configuredProviderIds.includes(activeSpeechProvider.value)) {
activeSpeechProvider.value = 'speech-noop'
activeSpeechModel.value = ''
activeSpeechVoiceId.value = ''
activeSpeechVoice.value = undefined
}
},
{ immediate: true },
)
onMounted(() => {
loadVoicesForProvider(activeSpeechProvider.value).then(() => {
if (activeSpeechVoiceId.value) {
@@ -232,6 +255,9 @@ export const useSpeechStore = defineStore('speech', () => {
}
const configured = computed(() => {
if (activeSpeechProvider.value === 'speech-noop')
return false
if (!activeSpeechProvider.value)
return false
+28
View File
@@ -237,6 +237,34 @@ export const useProvidersStore = defineStore('providers', () => {
// Centralized provider metadata with provider factory functions
const providerMetadata: Record<string, ProviderMetadata> = {
'speech-noop': {
id: 'speech-noop',
category: 'speech',
tasks: ['text-to-speech', 'tts'],
nameKey: 'settings.pages.providers.provider.speech-noop.title',
name: 'None',
descriptionKey: 'settings.pages.providers.provider.speech-noop.description',
description: 'No speech output.',
icon: 'i-solar:volume-cross-bold-duotone',
defaultOptions: () => ({}),
createProvider: async () => ({
speech: () => ({
baseURL: 'http://speech-noop.invalid/v1/',
model: 'noop',
}),
}),
capabilities: {
listModels: async () => [],
listVoices: async () => [],
},
validators: {
validateProviderConfig: () => ({
errors: [],
reason: '',
valid: true,
}),
},
},
'app-local-audio-speech': buildOpenAICompatibleProvider({
id: 'app-local-audio-speech',
name: 'App (Local)',