fix(stage-ui): build

This commit is contained in:
Neko Ayaka
2025-03-15 15:04:32 +08:00
parent f255ef37e0
commit 5d85f2ae46
5 changed files with 443 additions and 3 deletions
@@ -0,0 +1,186 @@
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,
)
}
@@ -0,0 +1,22 @@
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)
}
@@ -0,0 +1,174 @@
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,
)
}
+41
View File
@@ -0,0 +1,41 @@
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>
}
+20 -3
View File
@@ -9,6 +9,7 @@ import type {
TranscriptionProvider,
TranscriptionProviderWithExtraOptions,
} from '@xsai-ext/shared-providers'
import type { VoiceProviderWithExtraOptions } from './fix/voice'
import { useLocalStorage } from '@vueuse/core'
import {
@@ -24,12 +25,16 @@ import {
createWorkersAI,
createXAI,
} from '@xsai-ext/providers-cloud'
import { createOllama, createUnElevenLabs } from '@xsai-ext/providers-local'
import { createOllama } from '@xsai-ext/providers-local'
import { listModels } from '@xsai/model'
import { defineStore } from 'pinia'
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'
export interface ProviderMetadata {
id: string
nameKey: string // i18n key for provider name
@@ -309,8 +314,20 @@ export const useProvidersStore = defineStore('providers', () => {
listModels: async () => {
return []
},
listVoices: async () => {
return []
listVoices: async (config) => {
const provider = createUnElevenLabs(config.apiKey as string, config.baseUrl as string) as VoiceProviderWithExtraOptions<UnElevenLabsOptions>
const voices = await listVoices({
...provider.voice(),
})
return voices.map((voice) => {
return {
id: voice.id,
name: voice.name,
provider: 'elevenlabs',
}
})
},
},
},