feat(stage-web|stage-tamagotchi): supports Alibaba Cloud Model Studio & Volcano Engine
This commit is contained in:
@@ -218,6 +218,9 @@ settings:
|
||||
title: Voice Settings
|
||||
description: LLMs, speech providers, etc.
|
||||
provider:
|
||||
alibaba-cloud-model-studio:
|
||||
description: bailian.console.aliyun.com
|
||||
title: Alibaba Cloud Model Studio
|
||||
anthropic:
|
||||
description: anthropic.com
|
||||
title: Anthropic | Claude
|
||||
@@ -326,6 +329,14 @@ settings:
|
||||
vllm:
|
||||
description: vllm.ai
|
||||
title: vLLM
|
||||
volcengine:
|
||||
description: volcengine.com
|
||||
fields:
|
||||
field:
|
||||
appId:
|
||||
description: App ID of the project where you can obtain in Console
|
||||
label: App ID
|
||||
title: Volcano Engine
|
||||
xai:
|
||||
description: x.ai
|
||||
title: xAI
|
||||
|
||||
@@ -207,6 +207,9 @@ settings:
|
||||
title: 声音配置
|
||||
description: LLM,语音合成,语音识别提供商等
|
||||
provider:
|
||||
alibaba-cloud-model-studio:
|
||||
description: bailian.console.aliyun.com
|
||||
title: 阿里百炼
|
||||
anthropic:
|
||||
description: anthropic.com
|
||||
title: Anthropic | Claude
|
||||
@@ -313,6 +316,14 @@ settings:
|
||||
vllm:
|
||||
description: vllm.ai
|
||||
title: vLLM
|
||||
volcengine:
|
||||
description: volcengine.com
|
||||
fields:
|
||||
field:
|
||||
appId:
|
||||
description: 可在控制台获取的 App ID
|
||||
label: App ID
|
||||
title: 火山引擎
|
||||
xai:
|
||||
description: X.AI
|
||||
title: xAI
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
<script setup lang="ts">
|
||||
import type { UnElevenLabsOptions } from '@xsai-ext/providers-local'
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
FieldRange,
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const providerId = 'alibaba-cloud-model-studio'
|
||||
const defaultModel = 'cosyvoice-v1'
|
||||
|
||||
// Default voice settings specific to ElevenLabs
|
||||
const defaultVoiceSettings = {
|
||||
speed: 1.0,
|
||||
}
|
||||
|
||||
const pitch = ref<number>(0)
|
||||
const speed = ref<number>(1.0)
|
||||
const volume = ref<number>(0)
|
||||
|
||||
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)
|
||||
|
||||
// Get available voices for ElevenLabs
|
||||
const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
// Generate speech with ElevenLabs-specific parameters
|
||||
async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) {
|
||||
const provider = providersStore.getProviderInstance(providerId) as SpeechProviderWithExtraOptions<string, UnElevenLabsOptions>
|
||||
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
|
||||
|
||||
// ElevenLabs doesn't need SSML conversion, but if SSML is provided, use it directly
|
||||
return await speechStore.speech(
|
||||
provider,
|
||||
model,
|
||||
input,
|
||||
voiceId,
|
||||
{
|
||||
...providerConfig,
|
||||
...defaultVoiceSettings,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(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)
|
||||
}
|
||||
})
|
||||
|
||||
watch(pitch, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.pitch = pitch.value
|
||||
})
|
||||
|
||||
watch(speed, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.speed = speed.value
|
||||
})
|
||||
|
||||
watch(volume, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.volume = volume.value
|
||||
})
|
||||
|
||||
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"
|
||||
:additional-settings="defaultVoiceSettings"
|
||||
>
|
||||
<!-- Voice settings specific to ElevenLabs -->
|
||||
<template #voice-settings>
|
||||
<div flex="~ col gap-4">
|
||||
<!-- Pitch control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="pitch"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.pitch.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.pitch.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
|
||||
<!-- Speed control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="speed"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.speed.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.speed.description')"
|
||||
:min="0.5"
|
||||
:max="2.0" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Volume control - available in some providers -->
|
||||
<FieldRange
|
||||
v-model="volume"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.volume.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.volume.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 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 ElevenLabs voice synthesis."
|
||||
/>
|
||||
</template>
|
||||
</SpeechProviderSettings>
|
||||
</template>
|
||||
@@ -3,13 +3,15 @@ import type { UnElevenLabsOptions } from '@xsai-ext/providers-local'
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
FieldCheckbox,
|
||||
FieldRange,
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
SpeechVoiceSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const providerId = 'elevenlabs'
|
||||
const defaultModel = 'eleven_multilingual_v2'
|
||||
@@ -23,9 +25,18 @@ const defaultVoiceSettings = {
|
||||
useSpeakerBoost: true,
|
||||
}
|
||||
|
||||
const pitch = ref<number>(0)
|
||||
const speed = ref<number>(1.0)
|
||||
const volume = ref<number>(0)
|
||||
const style = ref<number>(0)
|
||||
const stability = ref<number>(0.5)
|
||||
const similarityBoost = ref<number>(0.75)
|
||||
const useSpeakerBoost = ref<boolean>(false)
|
||||
|
||||
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)
|
||||
@@ -35,11 +46,6 @@ const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
// Get available languages
|
||||
const availableLanguages = computed(() => {
|
||||
return speechStore.availableLanguages
|
||||
})
|
||||
|
||||
// Generate speech with ElevenLabs-specific parameters
|
||||
async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) {
|
||||
const provider = providersStore.getProviderInstance(providerId) as SpeechProviderWithExtraOptions<string, UnElevenLabsOptions>
|
||||
@@ -65,6 +71,65 @@ async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: bo
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(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)
|
||||
}
|
||||
})
|
||||
|
||||
watch(pitch, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.pitch = pitch.value
|
||||
})
|
||||
|
||||
watch(speed, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.speed = speed.value
|
||||
})
|
||||
|
||||
watch(volume, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.volume = volume.value
|
||||
})
|
||||
|
||||
watch(style, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.style = style.value
|
||||
})
|
||||
|
||||
watch(stability, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.stability = stability.value
|
||||
})
|
||||
|
||||
watch(similarityBoost, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.similarityBoost = similarityBoost.value
|
||||
})
|
||||
|
||||
watch(useSpeakerBoost, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.useSpeakerBoost = useSpeakerBoost.value
|
||||
})
|
||||
|
||||
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>
|
||||
@@ -74,23 +139,75 @@ async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: bo
|
||||
:additional-settings="defaultVoiceSettings"
|
||||
>
|
||||
<!-- Voice settings specific to ElevenLabs -->
|
||||
<template #voice-settings="{ voiceSettings, updateVoiceSettings }">
|
||||
<SpeechVoiceSettings
|
||||
:settings="voiceSettings"
|
||||
:show-similarity-boost="true"
|
||||
:show-stability="true"
|
||||
:show-speed="true"
|
||||
:show-style="true"
|
||||
:show-speaker-boost="true"
|
||||
@update="updateVoiceSettings"
|
||||
/>
|
||||
<template #voice-settings>
|
||||
<div flex="~ col gap-4">
|
||||
<!-- Pitch control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="pitch"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.pitch.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.pitch.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
|
||||
<!-- Speed control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="speed"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.speed.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.speed.description')"
|
||||
:min="0.5"
|
||||
:max="2.0" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Volume control - available in some providers -->
|
||||
<FieldRange
|
||||
v-model="volume"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.volume.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.volume.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
|
||||
<!-- Style control - specific to ElevenLabs -->
|
||||
<FieldRange
|
||||
v-model="style"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.style.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.style.description')"
|
||||
:min="0"
|
||||
:max="1" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Stability control - specific to ElevenLabs -->
|
||||
<FieldRange
|
||||
v-model="stability"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.description')"
|
||||
:min="0"
|
||||
:max="1" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Similarity Boost control - specific to ElevenLabs -->
|
||||
<FieldRange
|
||||
v-model="similarityBoost"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.description')"
|
||||
:min="0"
|
||||
:max="1" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Speaker Boost checkbox - specific to ElevenLabs -->
|
||||
<FieldCheckbox
|
||||
v-model="useSpeakerBoost"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.description')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Replace the default playground with our standalone component -->
|
||||
<template #playground>
|
||||
<SpeechPlayground
|
||||
:available-voices="availableVoices"
|
||||
:available-languages="availableLanguages"
|
||||
:generate-speech="handleGenerateSpeech"
|
||||
:api-key-configured="apiKeyConfigured"
|
||||
default-text="Hello! This is a test of the ElevenLabs voice synthesis."
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
FieldInput,
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
SpeechVoiceSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
@@ -52,11 +51,6 @@ const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
// Get available languages
|
||||
const availableLanguages = computed(() => {
|
||||
return speechStore.availableLanguages
|
||||
})
|
||||
|
||||
// Generate speech with Microsoft-specific parameters
|
||||
async function handleGenerateSpeech(input: string, voiceId: string, useSSML: boolean) {
|
||||
const provider = providersStore.getProviderInstance(providerId) as SpeechProviderWithExtraOptions<string, UnMicrosoftOptions>
|
||||
@@ -143,7 +137,6 @@ async function handleGenerateSpeech(input: string, voiceId: string, useSSML: boo
|
||||
<template #playground>
|
||||
<SpeechPlayground
|
||||
:available-voices="availableVoices"
|
||||
:available-languages="availableLanguages"
|
||||
:generate-speech="handleGenerateSpeech"
|
||||
:api-key-configured="apiKeyConfigured"
|
||||
default-text="Hello! This is a test of the Microsoft Speech synthesis."
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
import type { UnElevenLabsOptions } from '@xsai-ext/providers-local'
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
FieldInput,
|
||||
FieldRange,
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const providerId = 'volcengine'
|
||||
const defaultModel = 'v1'
|
||||
|
||||
const speedRatio = ref<number>(1.0)
|
||||
|
||||
const speechStore = useSpeechStore()
|
||||
const providersStore = useProvidersStore()
|
||||
const { providers } = storeToRefs(providersStore)
|
||||
const { t } = useI18n()
|
||||
|
||||
// Additional settings specific to Volcengine (appId)
|
||||
const appId = computed({
|
||||
get: () => (providers.value[providerId]?.app as any)?.appId as string | undefined || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
|
||||
providers.value[providerId].app = {
|
||||
appId: value,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Check if API key is configured
|
||||
const apiKeyConfigured = computed(() => !!providers.value[providerId]?.apiKey)
|
||||
|
||||
// Get available voices for ElevenLabs
|
||||
const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
// Generate speech with ElevenLabs-specific parameters
|
||||
async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) {
|
||||
const provider = providersStore.getProviderInstance(providerId) as SpeechProviderWithExtraOptions<string, UnElevenLabsOptions>
|
||||
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
|
||||
|
||||
// ElevenLabs doesn't need SSML conversion, but if SSML is provided, use it directly
|
||||
return await speechStore.speech(
|
||||
provider,
|
||||
model,
|
||||
input,
|
||||
voiceId,
|
||||
{
|
||||
...providerConfig,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(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)
|
||||
}
|
||||
})
|
||||
|
||||
watch(speedRatio, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
if (!providerConfig.audio) {
|
||||
providerConfig.audio = {}
|
||||
}
|
||||
|
||||
(providerConfig.audio as any).speedRatio = speedRatio.value
|
||||
})
|
||||
|
||||
watch([providers, appId], 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"
|
||||
>
|
||||
<!-- Voice settings specific to ElevenLabs -->
|
||||
<template #basic-settings>
|
||||
<div flex="~ col gap-4">
|
||||
<FieldInput
|
||||
v-model="appId"
|
||||
:label="t('settings.pages.providers.provider.volcengine.fields.field.appId.label')"
|
||||
:description="t('settings.pages.providers.provider.volcengine.fields.field.appId.description')"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #voice-settings>
|
||||
<!-- Speed control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="speedRatio"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.speed.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.speed.description')"
|
||||
:min="0.5"
|
||||
:max="2.0" :step="0.01"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 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 ElevenLabs voice synthesis."
|
||||
/>
|
||||
</template>
|
||||
</SpeechProviderSettings>
|
||||
</template>
|
||||
@@ -11,7 +11,7 @@
|
||||
--airi-theme-primary-900: #8e0d3b;
|
||||
--airi-theme-primary-950: #50011b;
|
||||
|
||||
--theme-colors-hue: 354.31;
|
||||
--theme-colors-hue: 220.44;
|
||||
--theme-colors-chroma: calc(0.18 + (cos(var(--theme-colors-hue) * 3.14159265 / 180) * 0.04));
|
||||
--theme-colors-chroma-50: calc(var(--theme-colors-chroma) * 0.3);
|
||||
--theme-colors-chroma-100: calc(var(--theme-colors-chroma) * 0.5);
|
||||
|
||||
@@ -227,6 +227,9 @@ settings:
|
||||
title: Voice Settings
|
||||
description: LLMs, speech providers, etc.
|
||||
provider:
|
||||
alibaba-cloud-model-studio:
|
||||
description: bailian.console.aliyun.com
|
||||
title: Alibaba Cloud Model Studio
|
||||
anthropic:
|
||||
description: anthropic.com
|
||||
title: Anthropic | Claude
|
||||
@@ -335,6 +338,14 @@ settings:
|
||||
vllm:
|
||||
description: vllm.ai
|
||||
title: vLLM
|
||||
volcengine:
|
||||
description: volcengine.com
|
||||
fields:
|
||||
field:
|
||||
appId:
|
||||
description: App ID of the project where you can obtain in Console
|
||||
label: App ID
|
||||
title: Volcano Engine
|
||||
xai:
|
||||
description: x.ai
|
||||
title: xAI
|
||||
|
||||
@@ -213,6 +213,9 @@ settings:
|
||||
title: 声音配置
|
||||
description: LLM,语音合成,语音识别提供商等
|
||||
provider:
|
||||
alibaba-cloud-model-studio:
|
||||
description: bailian.console.aliyun.com
|
||||
title: 阿里百炼
|
||||
anthropic:
|
||||
description: anthropic.com
|
||||
title: Anthropic | Claude
|
||||
@@ -319,6 +322,14 @@ settings:
|
||||
vllm:
|
||||
description: vllm.ai
|
||||
title: vLLM
|
||||
volcengine:
|
||||
description: volcengine.com
|
||||
fields:
|
||||
field:
|
||||
appId:
|
||||
description: 可在控制台获取的 App ID
|
||||
label: App ID
|
||||
title: 火山引擎
|
||||
xai:
|
||||
description: X.AI
|
||||
title: xAI
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
<script setup lang="ts">
|
||||
import type { UnElevenLabsOptions } from '@xsai-ext/providers-local'
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
FieldRange,
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const providerId = 'alibaba-cloud-model-studio'
|
||||
const defaultModel = 'cosyvoice-v1'
|
||||
|
||||
// Default voice settings specific to ElevenLabs
|
||||
const defaultVoiceSettings = {
|
||||
speed: 1.0,
|
||||
}
|
||||
|
||||
const pitch = ref<number>(0)
|
||||
const speed = ref<number>(1.0)
|
||||
const volume = ref<number>(0)
|
||||
|
||||
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)
|
||||
|
||||
// Get available voices for ElevenLabs
|
||||
const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
// Generate speech with ElevenLabs-specific parameters
|
||||
async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) {
|
||||
const provider = providersStore.getProviderInstance(providerId) as SpeechProviderWithExtraOptions<string, UnElevenLabsOptions>
|
||||
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
|
||||
|
||||
// ElevenLabs doesn't need SSML conversion, but if SSML is provided, use it directly
|
||||
return await speechStore.speech(
|
||||
provider,
|
||||
model,
|
||||
input,
|
||||
voiceId,
|
||||
{
|
||||
...providerConfig,
|
||||
...defaultVoiceSettings,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(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)
|
||||
}
|
||||
})
|
||||
|
||||
watch(pitch, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.pitch = pitch.value
|
||||
})
|
||||
|
||||
watch(speed, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.speed = speed.value
|
||||
})
|
||||
|
||||
watch(volume, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.volume = volume.value
|
||||
})
|
||||
|
||||
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"
|
||||
:additional-settings="defaultVoiceSettings"
|
||||
>
|
||||
<!-- Voice settings specific to ElevenLabs -->
|
||||
<template #voice-settings>
|
||||
<div flex="~ col gap-4">
|
||||
<!-- Pitch control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="pitch"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.pitch.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.pitch.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
|
||||
<!-- Speed control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="speed"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.speed.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.speed.description')"
|
||||
:min="0.5"
|
||||
:max="2.0" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Volume control - available in some providers -->
|
||||
<FieldRange
|
||||
v-model="volume"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.volume.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.volume.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 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 ElevenLabs voice synthesis."
|
||||
/>
|
||||
</template>
|
||||
</SpeechProviderSettings>
|
||||
</template>
|
||||
@@ -3,13 +3,15 @@ import type { UnElevenLabsOptions } from '@xsai-ext/providers-local'
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
FieldCheckbox,
|
||||
FieldRange,
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
SpeechVoiceSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const providerId = 'elevenlabs'
|
||||
const defaultModel = 'eleven_multilingual_v2'
|
||||
@@ -23,9 +25,18 @@ const defaultVoiceSettings = {
|
||||
useSpeakerBoost: true,
|
||||
}
|
||||
|
||||
const pitch = ref<number>(0)
|
||||
const speed = ref<number>(1.0)
|
||||
const volume = ref<number>(0)
|
||||
const style = ref<number>(0)
|
||||
const stability = ref<number>(0.5)
|
||||
const similarityBoost = ref<number>(0.75)
|
||||
const useSpeakerBoost = ref<boolean>(false)
|
||||
|
||||
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)
|
||||
@@ -35,11 +46,6 @@ const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
// Get available languages
|
||||
const availableLanguages = computed(() => {
|
||||
return speechStore.availableLanguages
|
||||
})
|
||||
|
||||
// Generate speech with ElevenLabs-specific parameters
|
||||
async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) {
|
||||
const provider = providersStore.getProviderInstance(providerId) as SpeechProviderWithExtraOptions<string, UnElevenLabsOptions>
|
||||
@@ -65,6 +71,65 @@ async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: bo
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(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)
|
||||
}
|
||||
})
|
||||
|
||||
watch(pitch, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.pitch = pitch.value
|
||||
})
|
||||
|
||||
watch(speed, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.speed = speed.value
|
||||
})
|
||||
|
||||
watch(volume, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.volume = volume.value
|
||||
})
|
||||
|
||||
watch(style, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.style = style.value
|
||||
})
|
||||
|
||||
watch(stability, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.stability = stability.value
|
||||
})
|
||||
|
||||
watch(similarityBoost, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.similarityBoost = similarityBoost.value
|
||||
})
|
||||
|
||||
watch(useSpeakerBoost, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
providerConfig.useSpeakerBoost = useSpeakerBoost.value
|
||||
})
|
||||
|
||||
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>
|
||||
@@ -74,23 +139,75 @@ async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: bo
|
||||
:additional-settings="defaultVoiceSettings"
|
||||
>
|
||||
<!-- Voice settings specific to ElevenLabs -->
|
||||
<template #voice-settings="{ voiceSettings, updateVoiceSettings }">
|
||||
<SpeechVoiceSettings
|
||||
:settings="voiceSettings"
|
||||
:show-similarity-boost="true"
|
||||
:show-stability="true"
|
||||
:show-speed="true"
|
||||
:show-style="true"
|
||||
:show-speaker-boost="true"
|
||||
@update="updateVoiceSettings"
|
||||
/>
|
||||
<template #voice-settings>
|
||||
<div flex="~ col gap-4">
|
||||
<!-- Pitch control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="pitch"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.pitch.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.pitch.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
|
||||
<!-- Speed control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="speed"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.speed.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.speed.description')"
|
||||
:min="0.5"
|
||||
:max="2.0" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Volume control - available in some providers -->
|
||||
<FieldRange
|
||||
v-model="volume"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.volume.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.volume.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
|
||||
<!-- Style control - specific to ElevenLabs -->
|
||||
<FieldRange
|
||||
v-model="style"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.style.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.style.description')"
|
||||
:min="0"
|
||||
:max="1" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Stability control - specific to ElevenLabs -->
|
||||
<FieldRange
|
||||
v-model="stability"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.description')"
|
||||
:min="0"
|
||||
:max="1" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Similarity Boost control - specific to ElevenLabs -->
|
||||
<FieldRange
|
||||
v-model="similarityBoost"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.description')"
|
||||
:min="0"
|
||||
:max="1" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Speaker Boost checkbox - specific to ElevenLabs -->
|
||||
<FieldCheckbox
|
||||
v-model="useSpeakerBoost"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.description')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Replace the default playground with our standalone component -->
|
||||
<template #playground>
|
||||
<SpeechPlayground
|
||||
:available-voices="availableVoices"
|
||||
:available-languages="availableLanguages"
|
||||
:generate-speech="handleGenerateSpeech"
|
||||
:api-key-configured="apiKeyConfigured"
|
||||
default-text="Hello! This is a test of the ElevenLabs voice synthesis."
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
FieldInput,
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
SpeechVoiceSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
@@ -52,11 +51,6 @@ const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
// Get available languages
|
||||
const availableLanguages = computed(() => {
|
||||
return speechStore.availableLanguages
|
||||
})
|
||||
|
||||
// Generate speech with Microsoft-specific parameters
|
||||
async function handleGenerateSpeech(input: string, voiceId: string, useSSML: boolean) {
|
||||
const provider = providersStore.getProviderInstance(providerId) as SpeechProviderWithExtraOptions<string, UnMicrosoftOptions>
|
||||
@@ -143,7 +137,6 @@ async function handleGenerateSpeech(input: string, voiceId: string, useSSML: boo
|
||||
<template #playground>
|
||||
<SpeechPlayground
|
||||
:available-voices="availableVoices"
|
||||
:available-languages="availableLanguages"
|
||||
:generate-speech="handleGenerateSpeech"
|
||||
:api-key-configured="apiKeyConfigured"
|
||||
default-text="Hello! This is a test of the Microsoft Speech synthesis."
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
import type { UnElevenLabsOptions } from '@xsai-ext/providers-local'
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
FieldInput,
|
||||
FieldRange,
|
||||
SpeechPlayground,
|
||||
SpeechProviderSettings,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const providerId = 'volcengine'
|
||||
const defaultModel = 'v1'
|
||||
|
||||
const speedRatio = ref<number>(1.0)
|
||||
|
||||
const speechStore = useSpeechStore()
|
||||
const providersStore = useProvidersStore()
|
||||
const { providers } = storeToRefs(providersStore)
|
||||
const { t } = useI18n()
|
||||
|
||||
// Additional settings specific to Volcengine (appId)
|
||||
const appId = computed({
|
||||
get: () => (providers.value[providerId]?.app as any)?.appId as string | undefined || '',
|
||||
set: (value) => {
|
||||
if (!providers.value[providerId])
|
||||
providers.value[providerId] = {}
|
||||
|
||||
providers.value[providerId].app = {
|
||||
appId: value,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Check if API key is configured
|
||||
const apiKeyConfigured = computed(() => !!providers.value[providerId]?.apiKey)
|
||||
|
||||
// Get available voices for ElevenLabs
|
||||
const availableVoices = computed(() => {
|
||||
return speechStore.availableVoices[providerId] || []
|
||||
})
|
||||
|
||||
// Generate speech with ElevenLabs-specific parameters
|
||||
async function handleGenerateSpeech(input: string, voiceId: string, _useSSML: boolean) {
|
||||
const provider = providersStore.getProviderInstance(providerId) as SpeechProviderWithExtraOptions<string, UnElevenLabsOptions>
|
||||
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
|
||||
|
||||
// ElevenLabs doesn't need SSML conversion, but if SSML is provided, use it directly
|
||||
return await speechStore.speech(
|
||||
provider,
|
||||
model,
|
||||
input,
|
||||
voiceId,
|
||||
{
|
||||
...providerConfig,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(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)
|
||||
}
|
||||
})
|
||||
|
||||
watch(speedRatio, async () => {
|
||||
const providerConfig = providersStore.getProviderConfig(providerId)
|
||||
if (!providerConfig.audio) {
|
||||
providerConfig.audio = {}
|
||||
}
|
||||
|
||||
(providerConfig.audio as any).speedRatio = speedRatio.value
|
||||
})
|
||||
|
||||
watch([providers, appId], 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"
|
||||
>
|
||||
<!-- Voice settings specific to ElevenLabs -->
|
||||
<template #basic-settings>
|
||||
<div flex="~ col gap-4">
|
||||
<FieldInput
|
||||
v-model="appId"
|
||||
:label="t('settings.pages.providers.provider.volcengine.fields.field.appId.label')"
|
||||
:description="t('settings.pages.providers.provider.volcengine.fields.field.appId.description')"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #voice-settings>
|
||||
<!-- Speed control - common to most providers -->
|
||||
<FieldRange
|
||||
v-model="speedRatio"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.speed.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.speed.description')"
|
||||
:min="0.5"
|
||||
:max="2.0" :step="0.01"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 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 ElevenLabs voice synthesis."
|
||||
/>
|
||||
</template>
|
||||
</SpeechProviderSettings>
|
||||
</template>
|
||||
@@ -11,7 +11,7 @@
|
||||
--airi-theme-primary-900: #8e0d3b;
|
||||
--airi-theme-primary-950: #50011b;
|
||||
|
||||
--theme-colors-hue: 354.31;
|
||||
--theme-colors-hue: 220.44;
|
||||
--theme-colors-chroma: calc(0.18 + (cos(var(--theme-colors-hue) * 3.14159265 / 180) * 0.04));
|
||||
--theme-colors-chroma-50: calc(var(--theme-colors-chroma) * 0.3);
|
||||
--theme-colors-chroma-100: calc(var(--theme-colors-chroma) * 0.5);
|
||||
|
||||
@@ -8,6 +8,8 @@ words:
|
||||
- airi
|
||||
- airi-vtuber
|
||||
- Alaya
|
||||
- alibabacloud
|
||||
- aliyun
|
||||
- APNG
|
||||
- astrojs
|
||||
- Attributify
|
||||
@@ -16,6 +18,7 @@ words:
|
||||
- Ayaka
|
||||
- baichuan
|
||||
- baiducloud
|
||||
- bailian
|
||||
- bigserial
|
||||
- Bitstream
|
||||
- browserbasehq
|
||||
@@ -26,6 +29,8 @@ words:
|
||||
- collectblock
|
||||
- composables
|
||||
- cooldown
|
||||
- cosyvoice
|
||||
- cozyvoice
|
||||
- crossws
|
||||
- csmmap
|
||||
- csmvector
|
||||
@@ -158,11 +163,13 @@ words:
|
||||
- unhead
|
||||
- unocss
|
||||
- unplugin
|
||||
- unspeech
|
||||
- valibot
|
||||
- vaul
|
||||
- velin
|
||||
- VITE
|
||||
- vllm
|
||||
- Volcengine
|
||||
- vrma
|
||||
- vueuse
|
||||
- wavefile
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"radix-vue": "^1.9.17",
|
||||
"reka-ui": "^2.2.0",
|
||||
"unist-builder": "^4.0.0",
|
||||
"unspeech": "^0.1.7",
|
||||
"xast-util-to-xml": "^4.0.0",
|
||||
"xastscript": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -10,7 +10,6 @@ const props = defineProps<{
|
||||
// Input fields
|
||||
defaultText?: string
|
||||
availableVoices: VoiceInfo[]
|
||||
availableLanguages: string[]
|
||||
|
||||
// Provider-specific handlers (provided from parent)
|
||||
generateSpeech: (input: string, voice: string, useSSML: boolean) => Promise<ArrayBuffer>
|
||||
@@ -29,7 +28,6 @@ const errorMessage = ref('')
|
||||
const audioPlayer = ref<HTMLAudioElement | null>(null)
|
||||
const useSSML = ref(false)
|
||||
const ssmlText = ref('')
|
||||
const selectedLanguage = ref(props.availableLanguages[0] || 'en-US')
|
||||
const selectedVoice = ref('')
|
||||
|
||||
// Watch for changes in available voices
|
||||
@@ -106,7 +104,6 @@ defineExpose({
|
||||
testText,
|
||||
ssmlText,
|
||||
useSSML,
|
||||
selectedLanguage,
|
||||
selectedVoice,
|
||||
isGenerating,
|
||||
audioUrl,
|
||||
@@ -156,27 +153,6 @@ defineExpose({
|
||||
</template>
|
||||
|
||||
<div flex="~ col gap-6">
|
||||
<label grid="~ cols-2 gap-4">
|
||||
<div>
|
||||
<div class="flex items-center gap-1 text-sm font-medium">
|
||||
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.language.label') }}
|
||||
</div>
|
||||
<div class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{{ t('settings.pages.providers.provider.elevenlabs.playground.fields.field.language.description') }}
|
||||
</div>
|
||||
</div>
|
||||
<select
|
||||
v-model="selectedLanguage"
|
||||
border="neutral-300 dark:neutral-800 solid 2 focus:neutral-400 dark:focus:neutral-600"
|
||||
transition="border duration-250 ease-in-out" w-full rounded-lg px-2 py-1 text-nowrap text-sm
|
||||
outline-none
|
||||
>
|
||||
<option v-for="language in availableLanguages" :key="language" :value="language">
|
||||
{{ language }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label grid="~ cols-2 gap-4">
|
||||
<div>
|
||||
<div class="flex items-center gap-1 text-sm font-medium">
|
||||
|
||||
@@ -29,10 +29,7 @@ const props = defineProps<{
|
||||
// Expose slots and emit events to allow customization
|
||||
defineSlots<{
|
||||
'basic-settings': (props: any) => any
|
||||
'voice-settings': (props: {
|
||||
voiceSettings: Record<string, any>
|
||||
updateVoiceSettings: (key: string, value: any) => void
|
||||
}) => any
|
||||
'voice-settings': (props: any) => any
|
||||
'advanced-settings': (props: any) => any
|
||||
'playground': (props: {
|
||||
isGenerating: boolean
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { FieldCheckbox, FieldRange } from '../index'
|
||||
|
||||
defineProps<{
|
||||
settings: Record<string, any>
|
||||
// Which settings to show
|
||||
showPitch?: boolean
|
||||
showSpeed?: boolean
|
||||
showStyle?: boolean
|
||||
showStability?: boolean
|
||||
showSimilarityBoost?: boolean
|
||||
showVolume?: boolean
|
||||
showSpeakerBoost?: boolean
|
||||
}>()
|
||||
|
||||
const pitch = defineModel<number>('pitch', { required: false, default: 0 })
|
||||
const speed = defineModel<number>('speed', { required: false, default: 1.0 })
|
||||
const volume = defineModel<number>('volume', { required: false, default: 0 })
|
||||
const style = defineModel<number>('style', { required: false, default: 0 })
|
||||
const stability = defineModel<number>('stability', { required: false, default: 0.5 })
|
||||
const similarityBoost = defineModel<number>('similarityBoost', { required: false, default: 0.75 })
|
||||
const useSpeakerBoost = defineModel<boolean>('useSpeakerBoost', { required: false, default: false })
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div flex="~ col gap-4">
|
||||
<!-- Pitch control - common to most providers -->
|
||||
<FieldRange
|
||||
v-if="showPitch"
|
||||
v-model="pitch"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.pitch.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.pitch.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
|
||||
<!-- Speed control - common to most providers -->
|
||||
<FieldRange
|
||||
v-if="showSpeed"
|
||||
v-model="speed"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.speed.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.speed.description')"
|
||||
:min="0.5"
|
||||
:max="2.0" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Volume control - available in some providers -->
|
||||
<FieldRange
|
||||
v-if="showVolume"
|
||||
v-model="volume"
|
||||
:label="t('settings.pages.providers.provider.common.fields.field.volume.label')"
|
||||
:description="t('settings.pages.providers.provider.common.fields.field.volume.description')"
|
||||
:min="-100"
|
||||
:max="100" :step="1" :format-value="value => `${value}%`"
|
||||
/>
|
||||
|
||||
<!-- Style control - specific to ElevenLabs -->
|
||||
<FieldRange
|
||||
v-if="showStyle"
|
||||
v-model="style"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.style.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.style.description')"
|
||||
:min="0"
|
||||
:max="1" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Stability control - specific to ElevenLabs -->
|
||||
<FieldRange
|
||||
v-if="showStability"
|
||||
v-model="stability"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.stability.description')"
|
||||
:min="0"
|
||||
:max="1" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Similarity Boost control - specific to ElevenLabs -->
|
||||
<FieldRange
|
||||
v-if="showSimilarityBoost"
|
||||
v-model="similarityBoost"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.simularity-boost.description')"
|
||||
:min="0"
|
||||
:max="1" :step="0.01"
|
||||
/>
|
||||
|
||||
<!-- Speaker Boost checkbox - specific to ElevenLabs -->
|
||||
<FieldCheckbox
|
||||
v-if="showSpeakerBoost"
|
||||
v-model="useSpeakerBoost"
|
||||
:label="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.label')"
|
||||
:description="t('settings.pages.providers.provider.elevenlabs.fields.field.speaker-boost.description')"
|
||||
/>
|
||||
|
||||
<!-- Slot for additional provider-specific controls -->
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -8,6 +8,4 @@ export { default as ProviderSettingsLayout2 } from './ProviderSettingsLayout2.vu
|
||||
export { default as ProviderSettingsLayout } from './ProviderSettingsLayout.vue'
|
||||
|
||||
export { default as SpeechPlayground } from './SpeechPlayground.vue'
|
||||
// New speech provider components
|
||||
export { default as SpeechProviderSettings } from './SpeechProviderSettings.vue'
|
||||
export { default as SpeechVoiceSettings } from './SpeechVoiceSettings.vue'
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
export enum Voice {
|
||||
// English
|
||||
Myriam = 'Myriam',
|
||||
Beatrice = 'Beatrice',
|
||||
Camilla_KM = 'Camilla_KM',
|
||||
SallySunshine = 'Sally Sunshine',
|
||||
Annie = 'Annie',
|
||||
KawaiiAerisita = 'Kawaii Aerisita',
|
||||
// Japanese
|
||||
Morioki = 'Morioki',
|
||||
}
|
||||
|
||||
export const voiceMap: Record<Voice, string> = {
|
||||
// English
|
||||
[Voice.Myriam]: 'lNxY9WuCBCZCISASyJ55',
|
||||
// Beatrice is not 'childish' like the others
|
||||
// voice: 'Beatrice',
|
||||
[Voice.Beatrice]: 'KAsXoQDshjF6ehsWa1mF',
|
||||
[Voice.Camilla_KM]: 'dLhSyo03JRp5WkGpUlz1',
|
||||
[Voice.SallySunshine]: 'qswttdunP3b44zVZKMRB',
|
||||
[Voice.Annie]: 'AfA1PA0ldViH0DA6pbml',
|
||||
[Voice.KawaiiAerisita]: 'vGQNBgLaiM3EdZtxIiuY',
|
||||
// Japanese
|
||||
[Voice.Morioki]: '8EkOjt4xTPGMclNlh1pk',
|
||||
}
|
||||
|
||||
// voice: 'ShanShan',
|
||||
// Quite good for English
|
||||
|
||||
export const enVoiceList = [
|
||||
Voice.Myriam,
|
||||
Voice.Beatrice,
|
||||
Voice.Camilla_KM,
|
||||
Voice.SallySunshine,
|
||||
Voice.Annie,
|
||||
Voice.KawaiiAerisita,
|
||||
]
|
||||
|
||||
export const jaVoiceList = [
|
||||
Voice.Morioki,
|
||||
]
|
||||
|
||||
export const voiceList: Record<string, Voice[]> = {
|
||||
'en': enVoiceList,
|
||||
'en-US': enVoiceList,
|
||||
'ja': jaVoiceList,
|
||||
'ja-JP': jaVoiceList,
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
export const llmInferenceEndToken = '<|llm_inference_end|>'
|
||||
|
||||
export * from './elevenlabs'
|
||||
export * from './emotions'
|
||||
export * from './prompts/system-v2'
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
import type { UnSpeechOptions } from '@xsai-ext/providers-local'
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
import type { VoiceProviderWithExtraOptions } from './voice'
|
||||
|
||||
import { merge } from '@xsai-ext/shared-providers'
|
||||
import { objCamelToSnake } from '@xsai/shared'
|
||||
|
||||
/** @see {@link https://elevenlabs.io/docs/api-reference/text-to-speech/convert#request} */
|
||||
export interface UnElevenLabsOptions {
|
||||
/**
|
||||
* This parameter controls text normalization with three modes: 'auto', 'on', and 'off'. When set to 'auto',
|
||||
* the system will automatically decide whether to apply text normalization (e.g., spelling out numbers).
|
||||
* With 'on', text normalization will always be applied, while with 'off', it will be skipped. Cannot be
|
||||
* turned on for 'eleven_turbo_v2_5' model.
|
||||
*/
|
||||
applyTextNormalization?: 'auto' | 'off' | 'on'
|
||||
/**
|
||||
* Language code (ISO 639-1) used to enforce a language for the model. Currently only Turbo v2.5
|
||||
* supports language enforcement. For other models, an error will be returned if language code is provided.
|
||||
*/
|
||||
languageCode?: string
|
||||
/**
|
||||
* A list of request_id of the samples that were generated before this generation. Can
|
||||
* be used to improve the flow of prosody when splitting up a large task into multiple
|
||||
* requests. The results will be best when the same model is used across the generations.
|
||||
*
|
||||
* In case both next_text and next_request_ids is send, next_text will be ignored.
|
||||
* A maximum of 3 request_ids can be send.
|
||||
*/
|
||||
nextRequestIds?: string[]
|
||||
/**
|
||||
* The text that comes after the text of the current request. Can be used to improve
|
||||
* the flow of prosody when concatenating together multiple generations or to influence
|
||||
* the prosody in the current generation.
|
||||
*/
|
||||
nextText?: string
|
||||
/**
|
||||
* A list of request_id of the samples that were generated before this generation. Can be
|
||||
* used to improve the flow of prosody when splitting up a large task into multiple requests.
|
||||
* The results will be best when the same model is used across the generations. In case both
|
||||
* previous_text and previous_request_ids is send, previous_text will be ignored. A maximum
|
||||
* of 3 request_ids can be send.
|
||||
*/
|
||||
previousRequestIds?: string[]
|
||||
/**
|
||||
* The text that came before the text of the current request. Can be used to improve the
|
||||
* flow of prosody when concatenating together multiple generations or to influence the
|
||||
* prosody in the current generation.
|
||||
*/
|
||||
previousText?: string
|
||||
/**
|
||||
* A list of pronunciation dictionary locators (id, version_id) to be applied to the text.
|
||||
* They will be applied in order. You may have up to 3 locators per request
|
||||
*/
|
||||
pronunciationDictionaryLocators?: {
|
||||
pronunciationDictionaryId: string
|
||||
versionId: string
|
||||
}[]
|
||||
/**
|
||||
* If specified, our system will make a best effort to sample deterministically, such that
|
||||
* repeated requests with the same seed and parameters should return the same result.
|
||||
* Determinism is not guaranteed. Must be integer between 0 and 4294967295.
|
||||
*/
|
||||
seed?: number
|
||||
/**
|
||||
* Voice settings overriding stored settings for the given voice. They are applied only on the given request.
|
||||
*/
|
||||
voiceSettings?: {
|
||||
/**
|
||||
* Determines how closely the AI should adhere to the original voice when attempting to replicate it.
|
||||
*/
|
||||
similarityBoost: number
|
||||
/**
|
||||
* Controls the speed of the generated speech. Values range from 0.7 to 1.2, with 1.0 being the default
|
||||
* speed. Lower values create slower, more deliberate speech while higher values produce faster-paced
|
||||
* speech. Extreme values can impact the quality of the generated speech.
|
||||
*
|
||||
* @default 1.0
|
||||
*/
|
||||
speed?: number
|
||||
/**
|
||||
* Determines how stable the voice is and the randomness between each generation. Lower values introduce
|
||||
* broader emotional range for the voice. Higher values can result in a monotonous voice with limited
|
||||
* emotion.
|
||||
*/
|
||||
stability: number
|
||||
/**
|
||||
* Determines the style exaggeration of the voice. This setting attempts to amplify the style of the original
|
||||
* speaker. It does consume additional computational resources and might increase latency if set to anything
|
||||
* other than 0.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
style?: number
|
||||
/**
|
||||
* This setting boosts the similarity to the original speaker. Using this setting requires a slightly higher
|
||||
* computational load, which in turn increases latency.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
useSpeakerBoost?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [ElevenLabs](https://elevenlabs.io/) provider for [UnSpeech](https://github.com/moeru-ai/unspeech)
|
||||
* only.
|
||||
*
|
||||
* [UnSpeech](https://github.com/moeru-ai/unspeech) is a open-source project that provides a
|
||||
* OpenAI-compatible audio & speech related API that can be used with various providers such
|
||||
* as ElevenLabs, Azure TTS, Google TTS, etc.
|
||||
*
|
||||
* @param apiKey - ElevenLabs API Key
|
||||
* @param baseURL - UnSpeech Instance URL
|
||||
* @returns SpeechProviderWithExtraOptions
|
||||
*/
|
||||
export function createUnElevenLabs(apiKey: string, baseURL = 'http://localhost:5933/v1/') {
|
||||
const toUnSpeechOptions = ({
|
||||
applyTextNormalization,
|
||||
languageCode,
|
||||
nextRequestIds,
|
||||
nextText,
|
||||
previousRequestIds,
|
||||
previousText,
|
||||
pronunciationDictionaryLocators,
|
||||
seed,
|
||||
voiceSettings,
|
||||
}: UnElevenLabsOptions): UnSpeechOptions => ({
|
||||
extraBody: objCamelToSnake({
|
||||
applyTextNormalization,
|
||||
languageCode,
|
||||
nextRequestIds,
|
||||
nextText,
|
||||
previousRequestIds,
|
||||
previousText,
|
||||
pronunciationDictionaryLocators: pronunciationDictionaryLocators
|
||||
? pronunciationDictionaryLocators.map(pdl => objCamelToSnake(pdl))
|
||||
: undefined,
|
||||
seed,
|
||||
voiceSettings: voiceSettings != null
|
||||
? objCamelToSnake(voiceSettings)
|
||||
: {
|
||||
similarityBoost: 0.75,
|
||||
stability: 0.5,
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const speechProvider: SpeechProviderWithExtraOptions<
|
||||
/** @see {@link https://elevenlabs.io/docs/developer-guides/models} */
|
||||
'eleven_english_sts_v2' | 'eleven_flash_v2' | 'eleven_flash_v2_5' | 'eleven_multilingual_sts_v2' | 'eleven_multilingual_v2',
|
||||
UnElevenLabsOptions
|
||||
> = {
|
||||
speech: (model, options) => ({
|
||||
...(options ? toUnSpeechOptions(options) : {}),
|
||||
apiKey,
|
||||
baseURL,
|
||||
model: `elevenlabs/${model}`,
|
||||
}),
|
||||
}
|
||||
|
||||
const voiceProvider: VoiceProviderWithExtraOptions<
|
||||
UnElevenLabsOptions
|
||||
> = {
|
||||
voice: (options) => {
|
||||
if (baseURL.endsWith('v1/')) {
|
||||
baseURL = baseURL.slice(0, -3)
|
||||
}
|
||||
else if (baseURL.endsWith('v1')) {
|
||||
baseURL = baseURL.slice(0, -2)
|
||||
}
|
||||
|
||||
return {
|
||||
query: `provider=elevenlabs`,
|
||||
...(options ? toUnSpeechOptions(options) : {}),
|
||||
apiKey,
|
||||
baseURL,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return merge(
|
||||
speechProvider,
|
||||
voiceProvider,
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { CommonRequestOptions } from '@xsai/shared'
|
||||
import type { Voice } from './voice'
|
||||
|
||||
import { requestHeaders, requestURL, responseJSON } from '@xsai/shared'
|
||||
|
||||
export interface ListVoicesOptions extends Omit<CommonRequestOptions, 'model'> {
|
||||
query?: string
|
||||
}
|
||||
|
||||
export interface ListVoicesResponse {
|
||||
voices: Voice[]
|
||||
}
|
||||
|
||||
export async function listVoices(options: ListVoicesOptions): Promise<Voice[]> {
|
||||
return (options.fetch ?? globalThis.fetch)(requestURL(options.query ? `api/voices?${options.query}` : 'api/voices', options.baseURL), {
|
||||
headers: requestHeaders({ ...options.headers }, options.apiKey),
|
||||
method: 'GET',
|
||||
signal: options.abortSignal,
|
||||
})
|
||||
.then(responseJSON<ListVoicesResponse>)
|
||||
.then(({ voices }) => voices)
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
import type { UnSpeechOptions } from '@xsai-ext/providers-local'
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
import type { VoiceProviderWithExtraOptions } from './voice'
|
||||
|
||||
import { merge } from '@xsai-ext/shared-providers'
|
||||
import { objCamelToSnake } from '@xsai/shared'
|
||||
|
||||
export type MicrosoftRegions =
|
||||
| 'australiaeast'
|
||||
| 'brazilsouth'
|
||||
| 'canadacentral'
|
||||
| 'centralindia'
|
||||
| 'centralus'
|
||||
| 'eastasia'
|
||||
| 'eastus2'
|
||||
| 'eastus'
|
||||
| 'francecentral'
|
||||
| 'germanywestcentral'
|
||||
| 'japaneast'
|
||||
| 'japanwest'
|
||||
| 'jioindiawest'
|
||||
| 'koreacentral'
|
||||
| 'northcentralus'
|
||||
| 'northeurope'
|
||||
| 'norwayeast'
|
||||
| 'southcentralus'
|
||||
| 'southeastasia'
|
||||
| 'swedencentral'
|
||||
| 'switzerlandnorth'
|
||||
| 'switzerlandwest'
|
||||
| 'uaenorth'
|
||||
| 'uksouth'
|
||||
| 'usgovarizona'
|
||||
| 'usgovvirginia'
|
||||
| 'westcentralus'
|
||||
| 'westeurope'
|
||||
| 'westus2'
|
||||
| 'westus3'
|
||||
| 'westus'
|
||||
|
||||
export interface UnMicrosoftOptionAutoSSML {
|
||||
gender:
|
||||
| 'Female'
|
||||
| 'Male'
|
||||
| 'Neutral'
|
||||
| string
|
||||
lang:
|
||||
| 'en-US'
|
||||
| string
|
||||
/**
|
||||
* Speech Studio - Voice Gallery
|
||||
* https://speech.microsoft.com/portal/018ba84135d64cf79106cc99c75ffa6a/voicegallery
|
||||
*/
|
||||
voice:
|
||||
| 'en-US-AndrewMultilingualNeural'
|
||||
| 'en-US-AriaNeural'
|
||||
| 'en-US-AvaMultilingualNeural'
|
||||
| 'en-US-BrianMultilingualNeural'
|
||||
| 'en-US-ChristopherMultilingualNeural'
|
||||
| 'en-US-EmmaMultilingualNeural'
|
||||
| 'en-US-JaneNeural'
|
||||
| string
|
||||
}
|
||||
|
||||
export interface UnMicrosoftOptionCommon {
|
||||
/**
|
||||
* Text to speech API reference (REST) - Speech service - Azure AI services | Microsoft Learn
|
||||
* https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=streaming#custom-neural-voices
|
||||
*/
|
||||
deploymentId?: string
|
||||
/**
|
||||
* Text to speech API reference (REST) - Speech service - Azure AI services | Microsoft Learn
|
||||
* https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=streaming#prebuilt-neural-voices
|
||||
*
|
||||
* NOTICE: Voices in preview are available in only these three regions: East US, West Europe, and Southeast Asia.
|
||||
*/
|
||||
region: MicrosoftRegions | string
|
||||
sampleRate?:
|
||||
| 8000
|
||||
| 16000
|
||||
| 22050
|
||||
| 24000
|
||||
| 44100
|
||||
| 48000
|
||||
| number
|
||||
}
|
||||
|
||||
export interface UnMicrosoftOptionCustomSSML {
|
||||
/**
|
||||
* By default, unspeech service will help you automatically convert OpenAI style plain text input
|
||||
* into SSML with lang, gender, voice parameters, but if you ever wanted to provide your own SSML
|
||||
* with all customizable parameters, you can set this option to `true` to disable the automatic
|
||||
* conversion and use your own SSML instead.
|
||||
*
|
||||
* About SSML (Speech Synthesis Markup Language), @see {@link https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup}
|
||||
*/
|
||||
disableSsml?: boolean
|
||||
}
|
||||
|
||||
/** @see {@link https://elevenlabs.io/docs/api-reference/text-to-speech/convert#request} */
|
||||
export type UnMicrosoftOptions = (UnMicrosoftOptionAutoSSML | UnMicrosoftOptionCustomSSML) & UnMicrosoftOptionCommon
|
||||
|
||||
/**
|
||||
* [Microsoft / Azure AI](https://speech.microsoft.com/portal) provider for [UnSpeech](https://github.com/moeru-ai/unspeech)
|
||||
* only.
|
||||
*
|
||||
* [UnSpeech](https://github.com/moeru-ai/unspeech) is a open-source project that provides a
|
||||
* OpenAI-compatible audio & speech related API that can be used with various providers such
|
||||
* as ElevenLabs, Azure TTS, Google TTS, etc.
|
||||
*
|
||||
* @param apiKey - Microsoft / Azure AI subscription key
|
||||
* @param baseURL - UnSpeech Instance URL
|
||||
* @returns SpeechProviderWithExtraOptions
|
||||
*/
|
||||
export function createUnMicrosoft(apiKey: string, baseURL = 'http://localhost:5933/v1/') {
|
||||
const toUnSpeechOptions = (options: UnMicrosoftOptions): UnSpeechOptions => {
|
||||
const { deploymentId, region, sampleRate } = options
|
||||
|
||||
const extraBody: Record<string, unknown> = {
|
||||
deploymentId,
|
||||
region,
|
||||
sampleRate,
|
||||
}
|
||||
|
||||
if ('disableSsml' in options) {
|
||||
extraBody.disableSsml = options.disableSsml
|
||||
}
|
||||
else if ('lang' in options) {
|
||||
extraBody.lang = options.lang
|
||||
extraBody.gender = options.gender
|
||||
extraBody.voice = options.voice
|
||||
}
|
||||
|
||||
return { extraBody: objCamelToSnake(extraBody) }
|
||||
}
|
||||
|
||||
const speechProvider: SpeechProviderWithExtraOptions<
|
||||
/** @see Currently, cognitive services are on v1 */
|
||||
'microsoft/v1',
|
||||
UnMicrosoftOptions
|
||||
> = {
|
||||
speech: (model, options) => ({
|
||||
...(options ? toUnSpeechOptions(options) : {}),
|
||||
apiKey,
|
||||
baseURL,
|
||||
model: `microsoft/${model}`,
|
||||
}),
|
||||
}
|
||||
|
||||
const voiceProvider: VoiceProviderWithExtraOptions<
|
||||
UnMicrosoftOptions
|
||||
> = {
|
||||
voice: (options) => {
|
||||
if (baseURL.endsWith('v1/')) {
|
||||
baseURL = baseURL.slice(0, -3)
|
||||
}
|
||||
else if (baseURL.endsWith('v1')) {
|
||||
baseURL = baseURL.slice(0, -2)
|
||||
}
|
||||
|
||||
return {
|
||||
query: `region=${options?.region}&provider=microsoft`,
|
||||
...(options ? toUnSpeechOptions(options) : {}),
|
||||
apiKey,
|
||||
baseURL,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return merge(
|
||||
speechProvider,
|
||||
voiceProvider,
|
||||
)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { CommonRequestOptions } from '@xsai/shared'
|
||||
|
||||
export interface Voice {
|
||||
compatible_models: string[]
|
||||
description: string
|
||||
formats: VoiceFormat[]
|
||||
id: string
|
||||
labels: Record<string, any> & {
|
||||
accent?: string
|
||||
age?: string
|
||||
gender?: string
|
||||
type?: string
|
||||
}
|
||||
languages: VoiceLanguage[]
|
||||
name: string
|
||||
predefined_options?: Record<string, any>
|
||||
preview_audio_url?: string
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export interface VoiceFormat {
|
||||
bitrate: number
|
||||
extension: string
|
||||
format_code: string
|
||||
mime_type: string
|
||||
name: string
|
||||
sample_rate: number
|
||||
}
|
||||
|
||||
export interface VoiceLanguage {
|
||||
code: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export interface VoiceProvider {
|
||||
voice: () => Omit<CommonRequestOptions, 'model'> & { query?: string }
|
||||
}
|
||||
|
||||
export interface VoiceProviderWithExtraOptions<T = undefined> {
|
||||
voice: (options?: T) => Omit<CommonRequestOptions, 'model'> & { query?: string } & Partial<T>
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { toXml } from 'xast-util-to-xml'
|
||||
import { x } from 'xastscript'
|
||||
|
||||
import { voiceList, voiceMap } from '../../constants/elevenlabs'
|
||||
import { useProvidersStore } from '../providers'
|
||||
|
||||
export const useSpeechStore = defineStore('speech', () => {
|
||||
@@ -69,32 +68,12 @@ export const useSpeechStore = defineStore('speech', () => {
|
||||
|
||||
const supportsSSML = computed(() => {
|
||||
// Currently only ElevenLabs and some other providers support SSML
|
||||
return ['elevenlabs', 'microsoft-speech', 'azure-speech', 'google'].includes(activeSpeechProvider.value)
|
||||
})
|
||||
|
||||
const availableLanguages = computed(() => {
|
||||
return Object.keys(voiceList)
|
||||
})
|
||||
|
||||
const availableVoicesForLanguage = computed(() => {
|
||||
const language = selectedLanguage.value
|
||||
if (!language || !voiceList[language]) {
|
||||
return []
|
||||
}
|
||||
|
||||
return voiceList[language].map(voiceEnum => ({
|
||||
id: voiceMap[voiceEnum],
|
||||
name: voiceEnum,
|
||||
provider: 'elevenlabs',
|
||||
language,
|
||||
}))
|
||||
return ['elevenlabs', 'microsoft-speech', 'azure-speech', 'google', 'alibaba-cloud-model-studio', 'volcengine'].includes(activeSpeechProvider.value)
|
||||
})
|
||||
|
||||
// Helper function to determine if a provider is a speech provider
|
||||
function isSpeechProvider(providerId: string): boolean {
|
||||
// This is a simplified check - in a real implementation, you might have a more robust way
|
||||
// to determine if a provider supports speech synthesis
|
||||
return ['elevenlabs', 'microsoft-speech', 'azure-speech', 'google', 'amazon'].includes(providerId)
|
||||
return ['elevenlabs', 'microsoft-speech', 'azure-speech', 'google', 'amazon', 'alibaba-cloud-model-studio', 'volcengine'].includes(providerId)
|
||||
}
|
||||
|
||||
async function loadVoicesForProvider(provider: string) {
|
||||
@@ -247,8 +226,6 @@ export const useSpeechStore = defineStore('speech', () => {
|
||||
// Computed
|
||||
availableSpeechProvidersMetadata,
|
||||
supportsSSML,
|
||||
availableLanguages,
|
||||
availableVoicesForLanguage,
|
||||
supportsModelListing,
|
||||
providerModels,
|
||||
isLoadingActiveProviderModels,
|
||||
|
||||
@@ -8,9 +8,13 @@ import type {
|
||||
TranscriptionProvider,
|
||||
TranscriptionProviderWithExtraOptions,
|
||||
} from '@xsai-ext/shared-providers'
|
||||
import type { UnElevenLabsOptions } from './fix/elevenlabs'
|
||||
import type { UnMicrosoftOptions } from './fix/microsoft'
|
||||
import type { VoiceProviderWithExtraOptions } from './fix/voice'
|
||||
import type {
|
||||
UnAlibabaCloudOptions,
|
||||
UnElevenLabsOptions,
|
||||
UnMicrosoftOptions,
|
||||
UnVolcengineOptions,
|
||||
VoiceProviderWithExtraOptions,
|
||||
} from 'unspeech'
|
||||
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import {
|
||||
@@ -31,12 +35,16 @@ import {
|
||||
import { createOllama } from '@xsai-ext/providers-local'
|
||||
import { listModels } from '@xsai/model'
|
||||
import { defineStore } from 'pinia'
|
||||
import {
|
||||
createUnAlibabaCloud,
|
||||
createUnElevenLabs,
|
||||
createUnMicrosoft,
|
||||
createUnVolcengine,
|
||||
listVoices,
|
||||
} from 'unspeech'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { createUnElevenLabs } from './fix/elevenlabs'
|
||||
import { listVoices } from './fix/list-voices'
|
||||
import { createUnMicrosoft } from './fix/microsoft'
|
||||
import { models as elevenLabsModels } from './providers/elevenlabs/list-models'
|
||||
|
||||
export interface ProviderMetadata {
|
||||
@@ -559,6 +567,112 @@ export const useProvidersStore = defineStore('providers', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
'alibaba-cloud-model-studio': {
|
||||
id: 'alibaba-cloud-model-studio',
|
||||
nameKey: 'settings.pages.providers.provider.alibaba-cloud-model-studio.title',
|
||||
name: 'Alibaba Cloud Model Studio',
|
||||
descriptionKey: 'settings.pages.providers.provider.alibaba-cloud-model-studio.description',
|
||||
description: 'bailian.console.aliyun.com',
|
||||
iconColor: 'i-lobe-icons:alibabacloud',
|
||||
defaultOptions: {
|
||||
baseUrl: 'https://unspeech.hyp3r.link/v1/',
|
||||
},
|
||||
createProvider: config => createUnAlibabaCloud((config.apiKey as string).trim(), (config.baseUrl as string).trim()),
|
||||
capabilities: {
|
||||
listVoices: async (config) => {
|
||||
const provider = createUnAlibabaCloud((config.apiKey as string).trim(), (config.baseUrl as string).trim()) as VoiceProviderWithExtraOptions<UnAlibabaCloudOptions>
|
||||
|
||||
const voices = await listVoices({
|
||||
...provider.voice(),
|
||||
})
|
||||
|
||||
return voices.map((voice) => {
|
||||
return {
|
||||
id: voice.id,
|
||||
name: voice.name,
|
||||
provider: 'alibaba-cloud-model-studio',
|
||||
previewURL: voice.preview_audio_url,
|
||||
languages: voice.languages,
|
||||
gender: voice.labels?.gender,
|
||||
}
|
||||
})
|
||||
},
|
||||
listModels: async () => {
|
||||
return [
|
||||
{
|
||||
id: 'cozyvoice-v1',
|
||||
name: 'CozyVoice',
|
||||
provider: 'alibaba-cloud-model-studio',
|
||||
description: '',
|
||||
contextLength: 0,
|
||||
deprecated: false,
|
||||
},
|
||||
{
|
||||
id: 'cozyvoice-v2',
|
||||
name: 'CozyVoice (New)',
|
||||
provider: 'alibaba-cloud-model-studio',
|
||||
description: '',
|
||||
contextLength: 0,
|
||||
deprecated: false,
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
validators: {
|
||||
validateProviderConfig: (config) => {
|
||||
return !!config.apiKey && !!config.baseUrl
|
||||
},
|
||||
},
|
||||
},
|
||||
'volcengine': {
|
||||
id: 'volcengine',
|
||||
nameKey: 'settings.pages.providers.provider.volcengine.title',
|
||||
name: 'settings.pages.providers.provider.volcengine.title',
|
||||
descriptionKey: 'settings.pages.providers.provider.volcengine.description',
|
||||
description: 'volcengine.com',
|
||||
iconColor: 'i-lobe-icons:volcengine',
|
||||
defaultOptions: {
|
||||
baseUrl: 'https://unspeech.hyp3r.link/v1/',
|
||||
},
|
||||
createProvider: config => createUnVolcengine((config.apiKey as string).trim(), (config.baseUrl as string).trim()),
|
||||
capabilities: {
|
||||
listVoices: async (config) => {
|
||||
const provider = createUnVolcengine((config.apiKey as string).trim(), (config.baseUrl as string).trim()) as VoiceProviderWithExtraOptions<UnVolcengineOptions>
|
||||
|
||||
const voices = await listVoices({
|
||||
...provider.voice(),
|
||||
})
|
||||
|
||||
return voices.map((voice) => {
|
||||
return {
|
||||
id: voice.id,
|
||||
name: voice.name,
|
||||
provider: 'volcano-engine',
|
||||
previewURL: voice.preview_audio_url,
|
||||
languages: voice.languages,
|
||||
gender: voice.labels?.gender,
|
||||
}
|
||||
})
|
||||
},
|
||||
listModels: async () => {
|
||||
return [
|
||||
{
|
||||
id: 'v1',
|
||||
name: 'v1',
|
||||
provider: 'volcano-engine',
|
||||
description: '',
|
||||
contextLength: 0,
|
||||
deprecated: false,
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
validators: {
|
||||
validateProviderConfig: (config) => {
|
||||
return !!config.apiKey && !!config.baseUrl && !!config.app && !!(config.app as any).appId
|
||||
},
|
||||
},
|
||||
},
|
||||
'together-ai': {
|
||||
id: 'together-ai',
|
||||
nameKey: 'settings.pages.providers.provider.together.title',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { converter } from 'culori'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
export const DEFAULT_THEME_COLORS_HUE = 178.17
|
||||
export const DEFAULT_THEME_COLORS_HUE = 220.44
|
||||
|
||||
const convert = converter('oklch')
|
||||
const getHueFrom = (color?: string) => color ? convert(color)?.h : DEFAULT_THEME_COLORS_HUE
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
const DEFAULT_THEME_COLORS_HUE = 178.17
|
||||
const DEFAULT_THEME_COLORS_HUE = 220.44
|
||||
|
||||
const themeColorsHue = ref(DEFAULT_THEME_COLORS_HUE)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
--airi-theme-primary-900: #8e0d3b;
|
||||
--airi-theme-primary-950: #50011b;
|
||||
|
||||
--theme-colors-hue: 354.31;
|
||||
--theme-colors-hue: 220.44;
|
||||
--theme-colors-chroma: calc(0.18 + (cos(var(--theme-colors-hue) * 3.14159265 / 180) * 0.04));
|
||||
--theme-colors-chroma-50: calc(var(--theme-colors-chroma) * 0.3);
|
||||
--theme-colors-chroma-100: calc(var(--theme-colors-chroma) * 0.5);
|
||||
|
||||
Generated
+11
@@ -1084,6 +1084,9 @@ importers:
|
||||
unist-builder:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
unspeech:
|
||||
specifier: ^0.1.7
|
||||
version: 0.1.7
|
||||
xast-util-to-xml:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
@@ -11151,6 +11154,9 @@ packages:
|
||||
unrs-resolver@1.3.2:
|
||||
resolution: {integrity: sha512-ZKQBC351Ubw0PY8xWhneIfb6dygTQeUHtCcNGd0QB618zabD/WbFMYdRyJ7xeVT+6G82K5v/oyZO0QSHFtbIuw==}
|
||||
|
||||
unspeech@0.1.7:
|
||||
resolution: {integrity: sha512-HQ49mqEfvkMIKsbYDfHXDxbpUuNnb+gJdLO6wjQ/iPP1VuUfXs5Ky20attrRr1Qp2S0FHWsuz7/nlv9CAHHuhg==}
|
||||
|
||||
unstorage@1.15.0:
|
||||
resolution: {integrity: sha512-m40eHdGY/gA6xAPqo8eaxqXgBuzQTlAKfmB1iF7oCKXE1HfwHwzDJBywK+qQGn52dta+bPlZluPF7++yR3p/bg==}
|
||||
peerDependencies:
|
||||
@@ -23885,6 +23891,11 @@ snapshots:
|
||||
'@unrs/resolver-binding-win32-ia32-msvc': 1.3.2
|
||||
'@unrs/resolver-binding-win32-x64-msvc': 1.3.2
|
||||
|
||||
unspeech@0.1.7:
|
||||
dependencies:
|
||||
'@xsai-ext/shared-providers': 0.2.0-beta.3
|
||||
'@xsai/shared': 0.2.0-beta.3
|
||||
|
||||
unstorage@1.15.0:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
|
||||
Reference in New Issue
Block a user