Feat: Support of Index-TTS, state-of-art opensourced TTS model - locally deployable. (#351)
This commit is contained in:
@@ -105,7 +105,7 @@
|
||||
"@iconify-json/vscode-icons": "^1.2.23",
|
||||
"@iconify/utils": "^2.3.0",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
||||
"@proj-airi/lobe-icons": "^1.0.8",
|
||||
"@proj-airi/lobe-icons": "^1.0.10",
|
||||
"@proj-airi/ui-transitions": "workspace:^",
|
||||
"@proj-airi/unplugin-fetch": "^0.1.6",
|
||||
"@proj-airi/unplugin-live2d-sdk": "^0.1.5",
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import type { SpeechProvider } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
// import { useI18n } from 'vue-i18n'
|
||||
|
||||
// const { t } = useI18n()
|
||||
|
||||
const providerId = 'index-tts-vllm'
|
||||
const defaultModel = 'IndexTTS-1.5'
|
||||
|
||||
const speechStore = useSpeechStore()
|
||||
const providersStore = useProvidersStore()
|
||||
// const { providers } = storeToRefs(providersStore)
|
||||
|
||||
// Check if API key is configured
|
||||
// const apiKeyConfigured = computed(() => !!providers.value[providerId]?.apiKey)
|
||||
const apiKeyConfigured = true // Assuming API key is always configured as its not required
|
||||
|
||||
// Get available voices for Index TTS provider
|
||||
const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await speechStore.loadVoicesForProvider(providerId)
|
||||
})
|
||||
|
||||
watch([apiKeyConfigured], async () => {
|
||||
await speechStore.loadVoicesForProvider(providerId)
|
||||
})
|
||||
|
||||
async function handleGenerateSpeech(input: string, voiceId: string) {
|
||||
const provider = await providersStore.getProviderInstance(providerId) as SpeechProvider
|
||||
if (!provider) {
|
||||
throw new Error('Failed to initialize speech provider')
|
||||
}
|
||||
|
||||
// Get provider configuration
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
|
||||
// Get model from configuration or use default
|
||||
const model = providerConfig.model as string | undefined || defaultModel
|
||||
|
||||
const options = {
|
||||
...providerConfig,
|
||||
}
|
||||
|
||||
return await speechStore.speech(
|
||||
provider,
|
||||
model,
|
||||
input,
|
||||
voiceId,
|
||||
options,
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SpeechProviderSettings :provider-id="providerId" :default-model="defaultModel">
|
||||
<!-- Replace the default playground with our standalone component -->
|
||||
<template #playground>
|
||||
<SpeechPlayground
|
||||
:available-voices="availableVoices" :generate-speech="handleGenerateSpeech"
|
||||
:api-key-configured="apiKeyConfigured" :use-ssml="false"
|
||||
default-text="Hello! This is a test of the Index TTS Speech synthesis?."
|
||||
/>
|
||||
</template>
|
||||
</SpeechProviderSettings>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route>
|
||||
@@ -97,7 +97,7 @@
|
||||
"@iconify-json/svg-spinners": "^1.2.2",
|
||||
"@iconify-json/vscode-icons": "^1.2.23",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
||||
"@proj-airi/lobe-icons": "^1.0.8",
|
||||
"@proj-airi/lobe-icons": "^1.0.10",
|
||||
"@proj-airi/unplugin-fetch": "^0.1.6",
|
||||
"@proj-airi/unplugin-live2d-sdk": "^0.1.5",
|
||||
"@shikijs/markdown-it": "^3.9.1",
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import type { SpeechProvider } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, watch } from 'vue'
|
||||
// import { useI18n } from 'vue-i18n'
|
||||
|
||||
const providerId = 'index-tts-vllm'
|
||||
const defaultModel = 'IndexTTS-1.5'
|
||||
|
||||
const speechStore = useSpeechStore()
|
||||
const providersStore = useProvidersStore()
|
||||
const { providers } = storeToRefs(providersStore)
|
||||
// const { t } = useI18n()
|
||||
|
||||
// Check if API key is configured
|
||||
// const apiKeyConfigured = computed(() => !!providers.value[providerId]?.apiKey)
|
||||
const apiKeyConfigured = true // Assuming API key is always configured as its not required
|
||||
|
||||
// Get available voices for Index TTS
|
||||
const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
// Generate speech with IndexTTS-specific parameters
|
||||
async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) {
|
||||
const provider = await providersStore.getProviderInstance(providerId) as SpeechProvider
|
||||
if (!provider) {
|
||||
throw new Error('Failed to initialize speech provider')
|
||||
}
|
||||
|
||||
// Get provider configuration
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
|
||||
// Get model from configuration or use default
|
||||
const model = providerConfig.model as string | undefined || defaultModel
|
||||
|
||||
// Index TTS doesn't need SSML conversion, but if SSML is provided, use it directly
|
||||
return await speechStore.speech(
|
||||
provider,
|
||||
model,
|
||||
input,
|
||||
voiceId,
|
||||
{
|
||||
...providerConfig,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
watch([providers], async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
const providerMetadata = providersStore.getProviderMetadata(providerId)
|
||||
if (await providerMetadata.validators.validateProviderConfig(providerConfig)) {
|
||||
await speechStore.loadVoicesForProvider(providerId)
|
||||
}
|
||||
else {
|
||||
console.error('Failed to validate provider config', providerConfig)
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SpeechProviderSettings
|
||||
:provider-id="providerId"
|
||||
:default-model="defaultModel"
|
||||
>
|
||||
<!-- Replace the default playground with our standalone component -->
|
||||
<template #playground>
|
||||
<SpeechPlayground
|
||||
:available-voices="availableVoices"
|
||||
:generate-speech="handleGenerateSpeech"
|
||||
:api-key-configured="apiKeyConfigured"
|
||||
default-text="Hello! This is a test of the Index TTS voice synthesis."
|
||||
/>
|
||||
</template>
|
||||
</SpeechProviderSettings>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route>
|
||||
@@ -26,7 +26,7 @@ function handleClose() {
|
||||
|
||||
<template>
|
||||
<DialogRoot v-model:open="open">
|
||||
<DialogTrigger class="text-muted-foreground border-muted text-md md:bg-card hover:bg-muted flex items-center rounded-lg px-3 py-[7px] transition-colors duration-200 ease-in-out space-x-2 md:border md:text-sm">
|
||||
<DialogTrigger class="text-muted-foreground border-muted hover:bg-muted text-md md:bg-card flex items-center rounded-lg px-3 py-[7px] transition-colors duration-200 ease-in-out space-x-2 md:border md:text-sm">
|
||||
<Icon icon="lucide:search" />
|
||||
<span class="w-24 text-left hidden lg:w-40 md:inline-flex">{{ t('docs.theme.search.title') }}</span>
|
||||
<span class="text-xs hidden prose md:inline-flex">
|
||||
|
||||
@@ -404,6 +404,9 @@ pages:
|
||||
description: Speech Service region
|
||||
label: Region
|
||||
title: Microsoft / Azure Speech
|
||||
index-tts-vllm:
|
||||
description: https://index-tts.github.io/
|
||||
title: Bilibili / IndexTTS
|
||||
mistral:
|
||||
description: mistral.ai
|
||||
title: Mistral
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
"@iconify-json/svg-spinners": "^1.2.2",
|
||||
"@iconify-json/vscode-icons": "^1.2.23",
|
||||
"@moeru/std": "catalog:",
|
||||
"@proj-airi/lobe-icons": "^1.0.8",
|
||||
"@proj-airi/lobe-icons": "^1.0.10",
|
||||
"@types/culori": "^4.0.0",
|
||||
"@types/three": "^0.178.1",
|
||||
"@unocss/reset": "^66.3.3",
|
||||
|
||||
@@ -1063,6 +1063,55 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
'index-tts-vllm': {
|
||||
id: 'index-tts-vllm',
|
||||
category: 'speech',
|
||||
tasks: ['text-to-speech'],
|
||||
nameKey: 'settings.pages.providers.provider.index-tts-vllm.title',
|
||||
name: 'Index-TTS by Bilibili',
|
||||
descriptionKey: 'settings.pages.providers.provider.index-tts-vllm.description',
|
||||
description: 'index-tts.github.io',
|
||||
iconColor: 'i-lobe-icons:bilibiliindex',
|
||||
defaultOptions: () => ({
|
||||
baseUrl: 'http://localhost:11996/tts',
|
||||
}),
|
||||
createProvider: async (config) => {
|
||||
const provider: SpeechProvider = {
|
||||
speech: () => {
|
||||
const req = {
|
||||
baseURL: config.baseUrl as string,
|
||||
model: 'IndexTTS-1.5',
|
||||
}
|
||||
return req
|
||||
},
|
||||
}
|
||||
return provider
|
||||
},
|
||||
capabilities: {
|
||||
listVoices: async (config) => {
|
||||
const voicesUrl = config.baseUrl as string
|
||||
const response = await fetch(`${voicesUrl}/audio/voices`)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch voices: ${response.statusText}`)
|
||||
}
|
||||
const voices = await response.json()
|
||||
return Object.keys(voices).map((voice: any) => {
|
||||
return {
|
||||
id: voice,
|
||||
name: voice,
|
||||
provider: 'index-tts-vllm',
|
||||
// previewURL: voice.preview_audio_url,
|
||||
languages: [{ code: 'cn', title: 'Chinese' }, { code: 'en', title: 'English' }],
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
validators: {
|
||||
validateProviderConfig: (config) => {
|
||||
return !!config.baseUrl
|
||||
},
|
||||
},
|
||||
},
|
||||
'alibaba-cloud-model-studio': {
|
||||
id: 'alibaba-cloud-model-studio',
|
||||
category: 'speech',
|
||||
|
||||
Generated
+9
-9
@@ -591,8 +591,8 @@ importers:
|
||||
specifier: ^6.0.8
|
||||
version: 6.0.8(@vue/compiler-dom@3.5.18)(eslint@9.32.0(jiti@2.5.1))(rollup@4.46.2)(typescript@5.9.2)(vue-i18n@11.1.11(vue@3.5.18(typescript@5.9.2)))(vue@3.5.18(typescript@5.9.2))
|
||||
'@proj-airi/lobe-icons':
|
||||
specifier: ^1.0.8
|
||||
version: 1.0.8
|
||||
specifier: ^1.0.10
|
||||
version: 1.0.10
|
||||
'@proj-airi/ui-transitions':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/ui-transitions
|
||||
@@ -916,8 +916,8 @@ importers:
|
||||
specifier: ^6.0.8
|
||||
version: 6.0.8(@vue/compiler-dom@3.5.18)(eslint@9.32.0(jiti@2.5.1))(rollup@2.79.2)(typescript@5.9.2)(vue-i18n@11.1.11(vue@3.5.18(typescript@5.9.2)))(vue@3.5.18(typescript@5.9.2))
|
||||
'@proj-airi/lobe-icons':
|
||||
specifier: ^1.0.8
|
||||
version: 1.0.8
|
||||
specifier: ^1.0.10
|
||||
version: 1.0.10
|
||||
'@proj-airi/unplugin-fetch':
|
||||
specifier: ^0.1.6
|
||||
version: 0.1.6(@types/node@24.1.0)(jiti@2.5.1)(less@4.4.0)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
|
||||
@@ -1511,8 +1511,8 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 0.1.0-beta.7
|
||||
'@proj-airi/lobe-icons':
|
||||
specifier: ^1.0.8
|
||||
version: 1.0.8
|
||||
specifier: ^1.0.10
|
||||
version: 1.0.10
|
||||
'@types/culori':
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
@@ -4739,8 +4739,8 @@ packages:
|
||||
web-worker:
|
||||
optional: true
|
||||
|
||||
'@proj-airi/lobe-icons@1.0.8':
|
||||
resolution: {integrity: sha512-28EtouwEwjwOuT4eI9iSrdTyZDWHBrg5eyrZIGF8oCYRwYVsZ1Q78M2cX9faJ3XB6r4+suEbheOHbP8zj649bA==}
|
||||
'@proj-airi/lobe-icons@1.0.10':
|
||||
resolution: {integrity: sha512-zzQkwup4YFwheqYq5zTCMyol/Er1yP1lxdDTvrjZP3WHXop1IkOe9Jw44IG3Hsx1h/XV5WVizfELHVRFsveWQw==}
|
||||
|
||||
'@proj-airi/server-sdk@0.7.0-beta.1':
|
||||
resolution: {integrity: sha512-g4JtWrTDFA76hZIYHz5OMa5It/0LxOrBPvbVL9D3EWYeubjzGZoBHy8ZFYWq78r7yHQScTjLl1vtagLEMLQnyA==}
|
||||
@@ -15893,7 +15893,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@75lb/nature'
|
||||
|
||||
'@proj-airi/lobe-icons@1.0.8': {}
|
||||
'@proj-airi/lobe-icons@1.0.10': {}
|
||||
|
||||
'@proj-airi/server-sdk@0.7.0-beta.1':
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user