From 87a8e95d668a66545d2c43fe1c3b131179e90c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ZhangKai=20=7C=20=E5=BC=A0=E5=87=AF?= Date: Sat, 16 May 2026 08:55:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20add=20MiMo=20TTS=20provider=20=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3TTS=E6=8F=90=E4=BE=9B=E6=8E=A5=E5=85=A5MiMo=20(#752)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add MiMo TTS provider with preset voices, voice design and voice clone * refactor: remove MiMo voice clone feature --- .../components/hermes/chat/MessageItem.vue | 42 ++++- .../hermes/settings/VoiceSettings.vue | 134 +++++++++++++++ packages/client/src/composables/useSpeech.ts | 158 +++++++++++++++++- .../src/composables/useVoiceSettings.ts | 52 +++++- packages/client/src/i18n/locales/de.ts | 26 +++ packages/client/src/i18n/locales/en.ts | 26 +++ packages/client/src/i18n/locales/es.ts | 26 +++ packages/client/src/i18n/locales/fr.ts | 26 +++ packages/client/src/i18n/locales/ja.ts | 26 +++ packages/client/src/i18n/locales/ko.ts | 26 +++ packages/client/src/i18n/locales/pt.ts | 26 +++ packages/client/src/i18n/locales/zh-TW.ts | 26 +++ packages/client/src/i18n/locales/zh.ts | 26 +++ 13 files changed, 609 insertions(+), 11 deletions(-) diff --git a/packages/client/src/components/hermes/chat/MessageItem.vue b/packages/client/src/components/hermes/chat/MessageItem.vue index 157ea80..c0582e9 100644 --- a/packages/client/src/components/hermes/chat/MessageItem.vue +++ b/packages/client/src/components/hermes/chat/MessageItem.vue @@ -371,22 +371,22 @@ const canPlaySpeech = computed(() => { // 只有 assistant 消息可以播放 if (props.message.role !== 'assistant') return false if (!copyableContent.value) return false - // OpenAI / Custom / Edge 不依赖浏览器 Web Speech API - if (voiceSettings.provider.value === 'openai' || voiceSettings.provider.value === 'custom' || voiceSettings.provider.value === 'edge') return true + // OpenAI / Custom / Edge / MiMo 不依赖浏览器 Web Speech API + if (voiceSettings.provider.value === 'openai' || voiceSettings.provider.value === 'custom' || voiceSettings.provider.value === 'edge' || voiceSettings.provider.value === 'mimo') return true return speech.isSupported }) const isPlayingThisMessage = computed(() => { - // OpenAI / Custom / Edge 模式 - if (voiceSettings.provider.value === 'openai' || voiceSettings.provider.value === 'custom' || voiceSettings.provider.value === 'edge') { + // OpenAI / Custom / Edge / MiMo 模式 + if (voiceSettings.provider.value === 'openai' || voiceSettings.provider.value === 'custom' || voiceSettings.provider.value === 'edge' || voiceSettings.provider.value === 'mimo') { return speech.currentCustomMessageId.value === props.message.id && speech.isCustomPlaying.value } return speech.currentMessageId.value === props.message.id && speech.isPlaying.value }) const isPausedThisMessage = computed(() => { - // OpenAI / Custom / Edge 模式 - if (voiceSettings.provider.value === 'openai' || voiceSettings.provider.value === 'custom' || voiceSettings.provider.value === 'edge') { + // OpenAI / Custom / Edge / MiMo 模式 + if (voiceSettings.provider.value === 'openai' || voiceSettings.provider.value === 'custom' || voiceSettings.provider.value === 'edge' || voiceSettings.provider.value === 'mimo') { return speech.currentCustomMessageId.value === props.message.id && speech.isCustomPaused.value } return speech.currentMessageId.value === props.message.id && speech.isPaused.value @@ -441,6 +441,24 @@ function handleSpeechToggle() { return } + // MiMo TTS 模式 + if (voiceSettings.provider.value === 'mimo') { + const apiKey = voiceSettings.mimoApiKey.value + if (!apiKey) { + console.warn('[MessageItem] MiMo TTS API Key 为空') + return + } + speech.mimoToggle(props.message.id, content, { + baseUrl: voiceSettings.mimoBaseUrl.value, + apiKey, + model: voiceSettings.mimoModel.value, + voice: voiceSettings.mimoVoice.value, + voiceDesignDesc: voiceSettings.mimoVoiceDesignDesc.value || undefined, + stylePrompt: voiceSettings.mimoStylePrompt.value || undefined, + }) + return + } + // Web Speech API 模式 if (voiceSettings.provider.value === 'webspeech') { const text = speech.extractReadableText(content) @@ -486,6 +504,18 @@ onMounted(() => { rate: speedToEdgeRate(voiceSettings.edgeRate.value), pitch: hzToEdgePitch(voiceSettings.edgePitchHz.value), }) + } else if (voiceSettings.provider.value === 'mimo') { + const apiKey = voiceSettings.mimoApiKey.value + if (apiKey) { + speech.mimoPlay(props.message.id, content, { + baseUrl: voiceSettings.mimoBaseUrl.value, + apiKey, + model: voiceSettings.mimoModel.value, + voice: voiceSettings.mimoVoice.value, + voiceDesignDesc: voiceSettings.mimoVoiceDesignDesc.value || undefined, + stylePrompt: voiceSettings.mimoStylePrompt.value || undefined, + }) + } } else if (voiceSettings.provider.value === 'webspeech') { const text = speech.extractReadableText(content) if (text) { diff --git a/packages/client/src/components/hermes/settings/VoiceSettings.vue b/packages/client/src/components/hermes/settings/VoiceSettings.vue index 215bde3..36c2a74 100644 --- a/packages/client/src/components/hermes/settings/VoiceSettings.vue +++ b/packages/client/src/components/hermes/settings/VoiceSettings.vue @@ -19,6 +19,7 @@ const providerOptions = [ { label: t('settings.voice.providerOpenai'), value: 'openai' }, { label: t('settings.voice.providerCustom'), value: 'custom' }, { label: t('settings.voice.providerEdge'), value: 'edge' }, + { label: t('settings.voice.providerMimo'), value: 'mimo' }, ] const openaiModelOptions = [ @@ -76,6 +77,28 @@ onMounted(() => { } }) +// ── MiMo TTS options ── +const mimoBaseUrlOptions = [ + { label: 'https://api.xiaomimimo.com/v1', value: 'https://api.xiaomimimo.com/v1' }, + { label: 'https://token-plan-cn.xiaomimimo.com/v1', value: 'https://token-plan-cn.xiaomimimo.com/v1' }, +] + +const mimoModelOptions = [ + { label: t('settings.voice.mimoModelPreset'), value: 'mimo-v2.5-tts' }, + { label: t('settings.voice.mimoModelVoiceDesign'), value: 'mimo-v2.5-tts-voicedesign' }, +] + +const mimoVoiceOptions = [ + { label: '冰糖 (中文·女)', value: '冰糖' }, + { label: '茉莉 (中文·女)', value: '茉莉' }, + { label: '苏打 (中文·男)', value: '苏打' }, + { label: '白桦 (中文·男)', value: '白桦' }, + { label: 'Mia (English·Female)', value: 'Mia' }, + { label: 'Chloe (English·Female)', value: 'Chloe' }, + { label: 'Milo (English·Male)', value: 'Milo' }, + { label: 'Dean (English·Male)', value: 'Dean' }, +] + async function handleTest() { const text = testText.value.trim() if (!text) return @@ -113,6 +136,19 @@ async function handleTest() { rate: speedToEdgeRate(vs.edgeRate.value), pitch: hzToEdgePitch(vs.edgePitchHz.value), }) + } else if (vs.provider.value === 'mimo') { + if (!vs.mimoApiKey.value) { + console.warn('[VoiceSettings] MiMo API Key empty') + return + } + await speech.mimoPlay('__test__', text, { + baseUrl: vs.mimoBaseUrl.value, + apiKey: vs.mimoApiKey.value, + model: vs.mimoModel.value, + voice: vs.mimoVoice.value, + voiceDesignDesc: vs.mimoVoiceDesignDesc.value || undefined, + stylePrompt: vs.mimoStylePrompt.value || undefined, + }) } } catch (err) { console.error('[VoiceSettings] Test failed:', err) @@ -312,6 +348,104 @@ async function handleTest() { + + +

{{ t('settings.voice.testTitle') }}

diff --git a/packages/client/src/composables/useSpeech.ts b/packages/client/src/composables/useSpeech.ts index 6bfbb56..7f20826 100644 --- a/packages/client/src/composables/useSpeech.ts +++ b/packages/client/src/composables/useSpeech.ts @@ -15,6 +15,15 @@ export interface OpenaiTtsOptions { pitch?: string // Edge TTS pitch format, e.g. "-8Hz" } +export interface MimoTtsOptions { + baseUrl: string + apiKey: string + model: string + voice: string // preset voice ID (preset mode) or data URI (clone mode) + voiceDesignDesc?: string // voice design description text (voice design mode) + stylePrompt?: string // natural language style instruction +} + export interface SpeechState { isPlaying: boolean isPaused: boolean @@ -333,20 +342,17 @@ export function useSpeech() { function openaiToggle(messageId: string, content: string, opts: OpenaiTtsOptions) { if (currentCustomMessageId.value === messageId && isCustomPlaying.value) { if (isCustomPaused.value) { - // Resume if (customAudio) { customAudio.play() } isCustomPaused.value = false } else { - // Pause if (customAudio) { customAudio.pause() } isCustomPaused.value = true } } else { - // Stop other speech and start new stop(false) if (customAudio) { customAudio.pause() @@ -356,6 +362,148 @@ export function useSpeech() { } } + // ─── MiMo TTS Engine ────────────────────────────────────────── + + async function mimoPlay( + messageId: string, + content: string, + opts: MimoTtsOptions, + ) { + const text = extractReadableText(content) + if (!text) return + + const token = ++playbackToken + + isCustomPlaying.value = true + isCustomPaused.value = false + currentCustomMessageId.value = messageId + + // Build messages based on model type + const messages: Array<{ role: string; content: string }> = [] + + if (opts.model === 'mimo-v2.5-tts-voicedesign') { + // Voice design: user message = voice description (+ appended style prompt) + const desc = opts.voiceDesignDesc || '' + const userContent = opts.stylePrompt + ? `${desc}\n风格指令:${opts.stylePrompt}` + : desc + messages.push({ role: 'user', content: userContent || '默认音色' }) + } else { + // Preset voices: user message = style prompt or empty + messages.push({ role: 'user', content: opts.stylePrompt || '' }) + } + + // assistant message = synthesis text + messages.push({ role: 'assistant', content: text }) + + const audio: Record = { format: 'wav' } + // Voice design model does not accept audio.voice + if (opts.model !== 'mimo-v2.5-tts-voicedesign') { + audio.voice = opts.voice + } + + const body: Record = { + model: opts.model, + messages, + audio, + } + + const url = `${opts.baseUrl.replace(/\/+$/, '')}/chat/completions` + + const headers: Record = { + 'Content-Type': 'application/json', + 'api-key': opts.apiKey, + } + + try { + const res = await fetch(url, { + method: 'POST', + headers, + body: JSON.stringify(body), + }) + + if (token !== playbackToken) return + + if (!res.ok) { + const errText = await res.text().catch(() => '') + throw new Error(`MiMo TTS 返回 ${res.status}: ${errText || res.statusText}`) + } + + const json = await res.json() + if (token !== playbackToken) return + + const audioBase64 = json?.choices?.[0]?.message?.audio?.data + if (!audioBase64) { + throw new Error('MiMo TTS 响应中未找到音频数据') + } + + // base64 → binary → Blob + const binaryStr = atob(audioBase64) + const bytes = new Uint8Array(binaryStr.length) + for (let i = 0; i < binaryStr.length; i++) { + bytes[i] = binaryStr.charCodeAt(i) + } + const audioBlob = new Blob([bytes], { type: 'audio/wav' }) + + if (token !== playbackToken) return + + const audioUrl = URL.createObjectURL(audioBlob) + const audio = new Audio(audioUrl) + customAudio = audio + + audio.onended = () => { + if (token !== playbackToken) return + URL.revokeObjectURL(audioUrl) + isCustomPlaying.value = false + isCustomPaused.value = false + currentCustomMessageId.value = null + customAudio = null + } + + audio.onerror = () => { + if (token !== playbackToken) return + URL.revokeObjectURL(audioUrl) + console.warn('[useSpeech] MiMo TTS audio playback error') + isCustomPlaying.value = false + isCustomPaused.value = false + currentCustomMessageId.value = null + customAudio = null + } + + await audio.play() + } catch (err) { + if (token !== playbackToken) return + console.error('[useSpeech] MiMo TTS 请求失败:', err) + isCustomPlaying.value = false + isCustomPaused.value = false + currentCustomMessageId.value = null + throw err + } + } + + function mimoToggle(messageId: string, content: string, opts: MimoTtsOptions) { + if (currentCustomMessageId.value === messageId && isCustomPlaying.value) { + if (isCustomPaused.value) { + if (customAudio) { + customAudio.play() + } + isCustomPaused.value = false + } else { + if (customAudio) { + customAudio.pause() + } + isCustomPaused.value = true + } + } else { + stop(false) + if (customAudio) { + customAudio.pause() + customAudio = null + } + mimoPlay(messageId, content, opts) + } + } + // ─── Unified speak ────────────────────────────────────────── function speak(messageId: string, text: string, options: SpeechOptions = {}) { @@ -473,6 +621,10 @@ export function useSpeech() { openaiPlay, openaiToggle, + // MiMo TTS + mimoPlay, + mimoToggle, + // Browser WebSpeech (直接调用避免 Rolldown 树摇) speakViaBrowser, } diff --git a/packages/client/src/composables/useVoiceSettings.ts b/packages/client/src/composables/useVoiceSettings.ts index 7c77f64..30aed0f 100644 --- a/packages/client/src/composables/useVoiceSettings.ts +++ b/packages/client/src/composables/useVoiceSettings.ts @@ -1,6 +1,6 @@ import { ref, watch } from 'vue' -export type TtsProvider = 'webspeech' | 'openai' | 'custom' | 'edge' +export type TtsProvider = 'webspeech' | 'openai' | 'custom' | 'edge' | 'mimo' export interface VoiceSettingsData { provider: TtsProvider @@ -23,6 +23,14 @@ export interface VoiceSettingsData { edgeVoice: string edgeRate: number // 语速倍率 0.5~2.0,1.0 = 正常 edgePitchHz: number // 音调偏移 Hz,-20~20,0 = 正常 + + // MiMo TTS + mimoApiKey: string + mimoBaseUrl: string + mimoModel: string // 'mimo-v2.5-tts' | 'mimo-v2.5-tts-voicedesign' + mimoVoice: string // 预置音色 ID + mimoVoiceDesignDesc: string // 音色设计描述文本 + mimoStylePrompt: string // 风格指令 } const STORAGE_KEY = 'hermes-tts-settings-v2' @@ -67,6 +75,13 @@ const DEFAULT: VoiceSettingsData = { edgeVoice: 'zh-CN-XiaoxiaoNeural', edgeRate: 1.0, edgePitchHz: 0, + + mimoApiKey: '', + mimoBaseUrl: 'https://api.xiaomimimo.com/v1', + mimoModel: 'mimo-v2.5-tts', + mimoVoice: '冰糖', + mimoVoiceDesignDesc: '', + mimoStylePrompt: '', } function sanitize(data: VoiceSettingsData): VoiceSettingsData { @@ -110,10 +125,19 @@ const edgeVoice = ref(load().edgeVoice) const edgeRate = ref(load().edgeRate) const edgePitchHz = ref(load().edgePitchHz) +// MiMo TTS +const mimoApiKey = ref(load().mimoApiKey) +const mimoBaseUrl = ref(load().mimoBaseUrl) +const mimoModel = ref(load().mimoModel) +const mimoVoice = ref(load().mimoVoice) +const mimoVoiceDesignDesc = ref(load().mimoVoiceDesignDesc) +const mimoStylePrompt = ref(load().mimoStylePrompt) + // Auto-persist on change watch( [provider, webspeechVoice, openaiApiKey, openaiBaseUrl, openaiModel, openaiVoice, - customUrl, customApiKey, edgeUrl, edgeVoice, edgeRate, edgePitchHz], + customUrl, customApiKey, edgeUrl, edgeVoice, edgeRate, edgePitchHz, + mimoApiKey, mimoBaseUrl, mimoModel, mimoVoice, mimoVoiceDesignDesc, mimoStylePrompt], () => { localStorage.setItem(STORAGE_KEY, JSON.stringify({ provider: provider.value, @@ -128,6 +152,12 @@ watch( edgeVoice: edgeVoice.value, edgeRate: edgeRate.value, edgePitchHz: edgePitchHz.value, + mimoApiKey: mimoApiKey.value, + mimoBaseUrl: mimoBaseUrl.value, + mimoModel: mimoModel.value, + mimoVoice: mimoVoice.value, + mimoVoiceDesignDesc: mimoVoiceDesignDesc.value, + mimoStylePrompt: mimoStylePrompt.value, })) }, ) @@ -146,6 +176,12 @@ export function useVoiceSettings() { edgeVoice, edgeRate, edgePitchHz, + mimoApiKey, + mimoBaseUrl, + mimoModel, + mimoVoice, + mimoVoiceDesignDesc, + mimoStylePrompt, setProvider(v: TtsProvider) { provider.value = v }, setWebSpeechVoice(v: string) { webspeechVoice.value = v }, @@ -159,6 +195,12 @@ export function useVoiceSettings() { setEdgeVoice(v: string) { edgeVoice.value = v }, setEdgeRate(v: number) { edgeRate.value = v }, setEdgePitchHz(v: number) { edgePitchHz.value = v }, + setMimoApiKey(v: string) { mimoApiKey.value = v }, + setMimoBaseUrl(v: string) { mimoBaseUrl.value = v }, + setMimoModel(v: string) { mimoModel.value = v }, + setMimoVoice(v: string) { mimoVoice.value = v }, + setMimoVoiceDesignDesc(v: string) { mimoVoiceDesignDesc.value = v }, + setMimoStylePrompt(v: string) { mimoStylePrompt.value = v }, reset() { provider.value = DEFAULT.provider @@ -173,6 +215,12 @@ export function useVoiceSettings() { edgeVoice.value = DEFAULT.edgeVoice edgeRate.value = DEFAULT.edgeRate edgePitchHz.value = DEFAULT.edgePitchHz + mimoApiKey.value = DEFAULT.mimoApiKey + mimoBaseUrl.value = DEFAULT.mimoBaseUrl + mimoModel.value = DEFAULT.mimoModel + mimoVoice.value = DEFAULT.mimoVoice + mimoVoiceDesignDesc.value = DEFAULT.mimoVoiceDesignDesc + mimoStylePrompt.value = DEFAULT.mimoStylePrompt }, } } diff --git a/packages/client/src/i18n/locales/de.ts b/packages/client/src/i18n/locales/de.ts index fdd490d..0a486a9 100644 --- a/packages/client/src/i18n/locales/de.ts +++ b/packages/client/src/i18n/locales/de.ts @@ -670,6 +670,32 @@ jobTriggered: 'Job ausgelost', testButton: 'Testen', testButtonPlaying: 'Wiedergabe...', testFailed: 'Test fehlgeschlagen: {error}', + + // MiMo TTS + providerMimo: 'MiMo TTS', + mimoHint: 'Xiaomi MiMo TTS — unterstützt Voreingestellte Stimmen, Stimmdesign und Stimmklonung', + mimoApiKey: 'API-Schluessel', + mimoApiKeyHint: 'Holen Sie sich Ihren Schluessel auf platform.xiaomimimo.com', + mimoApiKeyPlaceholder: 'MiMo API-Schluessel', + mimoBaseUrl: 'Basis-URL', + mimoBaseUrlHint: 'MiMo API-Endpunkt-URL', + mimoModel: 'Modell', + mimoModelHint: 'Sprachsynthesemodell auswählen', + mimoModelPreset: 'Voreingestellte Stimmen', + mimoModelVoiceDesign: 'Stimmdesign', + mimoModelVoiceClone: 'Stimmklonung', + mimoVoice: 'Stimme', + mimoVoiceHint: 'Voreingestellte Stimme auswählen', + mimoVoiceDesignPrompt: 'Stimmbeschreibung', + mimoVoiceDesignPromptHint: 'Beschreiben Sie die gewünschten Stimmmerkmale', + mimoVoiceDesignPromptPlaceholder: 'Z.B.: Eine warme junge Frauenstimme, etwas langsam, mit magnetischem Ton', + mimoCloneAudio: 'Audio hochladen', + mimoCloneAudioHint: 'Audio-Beispiel für Stimmklonung hochladen (mp3/wav, max. 10 MB)', + mimoCloneAudioUpload: 'Datei auswählen', + mimoCloneAudioClear: 'Löschen', + mimoStylePrompt: 'Stil-Eingabe', + mimoStylePromptHint: 'Optional — beschreiben Sie den Sprechstil in natürlicher Sprache', + mimoStylePromptPlaceholder: 'Z.B.: Heller, lebhafter Ton, schnelles Tempo', }, lockedIps: { title: 'Gesperrte IPs', diff --git a/packages/client/src/i18n/locales/en.ts b/packages/client/src/i18n/locales/en.ts index af32025..063c096 100644 --- a/packages/client/src/i18n/locales/en.ts +++ b/packages/client/src/i18n/locales/en.ts @@ -847,6 +847,32 @@ export default { testButton: 'Test', testButtonPlaying: 'Playing...', testFailed: 'Test failed: {error}', + + // MiMo TTS + providerMimo: 'MiMo TTS', + mimoHint: 'Xiaomi MiMo TTS — supports preset voices, voice design, and voice clone modes', + mimoApiKey: 'API Key', + mimoApiKeyHint: 'Get your key at platform.xiaomimimo.com', + mimoApiKeyPlaceholder: 'MiMo API Key', + mimoBaseUrl: 'Base URL', + mimoBaseUrlHint: 'MiMo API endpoint URL', + mimoModel: 'Model', + mimoModelHint: 'Select speech synthesis model', + mimoModelPreset: 'Preset Voices', + mimoModelVoiceDesign: 'Voice Design', + mimoModelVoiceClone: 'Voice Clone', + mimoVoice: 'Voice', + mimoVoiceHint: 'Select a preset voice', + mimoVoiceDesignPrompt: 'Voice Description', + mimoVoiceDesignPromptHint: 'Describe the voice characteristics you want', + mimoVoiceDesignPromptPlaceholder: 'e.g., A warm young female voice, slightly slow, with a magnetic tone', + mimoCloneAudio: 'Upload Audio', + mimoCloneAudioHint: 'Upload an audio sample for voice cloning (mp3/wav, max 10MB)', + mimoCloneAudioUpload: 'Choose File', + mimoCloneAudioClear: 'Clear', + mimoStylePrompt: 'Style Prompt', + mimoStylePromptHint: 'Optional — describe the speaking style in natural language', + mimoStylePromptPlaceholder: 'e.g., Bright and bouncy tone, fast pace', }, }, diff --git a/packages/client/src/i18n/locales/es.ts b/packages/client/src/i18n/locales/es.ts index e829f4a..04d4504 100644 --- a/packages/client/src/i18n/locales/es.ts +++ b/packages/client/src/i18n/locales/es.ts @@ -670,6 +670,32 @@ jobTriggered: 'Job ejecutado', testButton: 'Probar', testButtonPlaying: 'Reproduciendo...', testFailed: 'Prueba fallida: {error}', + + // MiMo TTS + providerMimo: 'MiMo TTS', + mimoHint: 'Xiaomi MiMo TTS — voces predefinidas, diseño de voz y clonación de voz', + mimoApiKey: 'Clave API', + mimoApiKeyHint: 'Obtenga su clave en platform.xiaomimimo.com', + mimoApiKeyPlaceholder: 'Clave API MiMo', + mimoBaseUrl: 'URL base', + mimoBaseUrlHint: 'URL del endpoint de la API MiMo', + mimoModel: 'Modelo', + mimoModelHint: 'Seleccione el modelo de síntesis de voz', + mimoModelPreset: 'Voces predefinidas', + mimoModelVoiceDesign: 'Diseño de voz', + mimoModelVoiceClone: 'Clonación de voz', + mimoVoice: 'Voz', + mimoVoiceHint: 'Seleccione una voz predefinida', + mimoVoiceDesignPrompt: 'Descripción de voz', + mimoVoiceDesignPromptHint: 'Describa las características de voz deseadas', + mimoVoiceDesignPromptPlaceholder: 'Ej: Una voz femenina cálida y joven, algo lenta, con tono magnético', + mimoCloneAudio: 'Subir audio', + mimoCloneAudioHint: 'Suba una muestra de audio para clonación (mp3/wav, máx. 10 MB)', + mimoCloneAudioUpload: 'Elegir archivo', + mimoCloneAudioClear: 'Borrar', + mimoStylePrompt: 'Indicador de estilo', + mimoStylePromptHint: 'Opcional — describa el estilo de habla en lenguaje natural', + mimoStylePromptPlaceholder: 'Ej: Tono brillante y animado, ritmo rápido', }, lockedIps: { title: 'IPs bloqueadas', diff --git a/packages/client/src/i18n/locales/fr.ts b/packages/client/src/i18n/locales/fr.ts index 1c25d6b..b7d7621 100644 --- a/packages/client/src/i18n/locales/fr.ts +++ b/packages/client/src/i18n/locales/fr.ts @@ -670,6 +670,32 @@ jobTriggered: 'Job declenche', testButton: 'Tester', testButtonPlaying: 'Lecture...', testFailed: 'Echec du test : {error}', + + // MiMo TTS + providerMimo: 'MiMo TTS', + mimoHint: 'Xiaomi MiMo TTS — voices predefinies, conception vocale et clonage vocal', + mimoApiKey: 'Cle API', + mimoApiKeyHint: 'Obtenez votre cle sur platform.xiaomimimo.com', + mimoApiKeyPlaceholder: 'Cle API MiMo', + mimoBaseUrl: 'URL de base', + mimoBaseUrlHint: 'URL de l\'endpoint API MiMo', + mimoModel: 'Modele', + mimoModelHint: 'Selectionnez le modele de synthese vocale', + mimoModelPreset: 'Voix predefinies', + mimoModelVoiceDesign: 'Conception vocale', + mimoModelVoiceClone: 'Clonage vocal', + mimoVoice: 'Voix', + mimoVoiceHint: 'Selectionnez une voix predefinie', + mimoVoiceDesignPrompt: 'Description vocale', + mimoVoiceDesignPromptHint: 'Decrivez les caracteristiques vocales souhaitees', + mimoVoiceDesignPromptPlaceholder: 'Ex : Une voix feminine chaude et jeune, legerement lente, avec un ton magnetique', + mimoCloneAudio: 'Televerser un audio', + mimoCloneAudioHint: 'Televersez un echantillon audio pour le clonage (mp3/wav, max 10 Mo)', + mimoCloneAudioUpload: 'Choisir un fichier', + mimoCloneAudioClear: 'Effacer', + mimoStylePrompt: 'Invite de style', + mimoStylePromptHint: 'Optionnel — decrivez le style de parole en langage naturel', + mimoStylePromptPlaceholder: 'Ex : Ton vif et entrain, rythme rapide', }, lockedIps: { title: 'IPs bloquees', diff --git a/packages/client/src/i18n/locales/ja.ts b/packages/client/src/i18n/locales/ja.ts index f87f975..ed8b4b7 100644 --- a/packages/client/src/i18n/locales/ja.ts +++ b/packages/client/src/i18n/locales/ja.ts @@ -670,6 +670,32 @@ export default { testButton: 'テスト', testButtonPlaying: '再生中...', testFailed: 'テスト失敗:{error}', + + // MiMo TTS + providerMimo: 'MiMo TTS', + mimoHint: 'Xiaomi MiMo TTS — プリセット音声、音声デザイン、音声クローンの3つのモードをサポート', + mimoApiKey: 'API Key', + mimoApiKeyHint: 'platform.xiaomimimo.com で取得', + mimoApiKeyPlaceholder: 'MiMo API Key', + mimoBaseUrl: 'Base URL', + mimoBaseUrlHint: 'MiMo API エンドポイントURL', + mimoModel: 'モデル', + mimoModelHint: '音声合成モデルを選択', + mimoModelPreset: 'プリセット音声', + mimoModelVoiceDesign: '音声デザイン', + mimoModelVoiceClone: '音声クローン', + mimoVoice: '音声', + mimoVoiceHint: 'プリセット音声を選択', + mimoVoiceDesignPrompt: '音声の説明', + mimoVoiceDesignPromptHint: '希望する音声の特徴を説明してください', + mimoVoiceDesignPromptPlaceholder: '例:温かみのある若い女性の声、少しゆっくり、磁力的なトーン', + mimoCloneAudio: '音声アップロード', + mimoCloneAudioHint: '音声クローン用の音声サンプルをアップロード(mp3/wav、最大10MB)', + mimoCloneAudioUpload: 'ファイルを選択', + mimoCloneAudioClear: 'クリア', + mimoStylePrompt: 'スタイルプロンプト', + mimoStylePromptHint: 'オプション — 自然言語で話すスタイルを説明', + mimoStylePromptPlaceholder: '例:明るく弾むようなトーン、速めのテンポ', }, lockedIps: { title: 'ロック済みIP管理', diff --git a/packages/client/src/i18n/locales/ko.ts b/packages/client/src/i18n/locales/ko.ts index ac48888..54ad186 100644 --- a/packages/client/src/i18n/locales/ko.ts +++ b/packages/client/src/i18n/locales/ko.ts @@ -670,6 +670,32 @@ export default { testButton: '테스트', testButtonPlaying: '재생 중...', testFailed: '테스트 실패: {error}', + + // MiMo TTS + providerMimo: 'MiMo TTS', + mimoHint: '샤오미 MiMo TTS — 프리셋 음성, 음성 디자인, 음성 클론 세 가지 모드 지원', + mimoApiKey: 'API Key', + mimoApiKeyHint: 'platform.xiaomimimo.com에서 발급', + mimoApiKeyPlaceholder: 'MiMo API Key', + mimoBaseUrl: 'Base URL', + mimoBaseUrlHint: 'MiMo API 엔드포인트 URL', + mimoModel: '모델', + mimoModelHint: '음성 합성 모델 선택', + mimoModelPreset: '프리셋 음성', + mimoModelVoiceDesign: '음성 디자인', + mimoModelVoiceClone: '음성 클론', + mimoVoice: '음성', + mimoVoiceHint: '프리셋 음성 선택', + mimoVoiceDesignPrompt: '음성 설명', + mimoVoiceDesignPromptHint: '원하는 음성 특징을 설명하세요', + mimoVoiceDesignPromptPlaceholder: '예: 따뜻한 젊은 여성 목소리, 약간 느린 속도, 마그네틱한 톤', + mimoCloneAudio: '오디오 업로드', + mimoCloneAudioHint: '음성 클론용 오디오 샘플 업로드 (mp3/wav, 최대 10MB)', + mimoCloneAudioUpload: '파일 선택', + mimoCloneAudioClear: '지우기', + mimoStylePrompt: '스타일 프롬프트', + mimoStylePromptHint: '선택사항 — 자연어로 말하기 스타일 설명', + mimoStylePromptPlaceholder: '예: 밝고 경쾌한 톤, 빠른 속도', }, lockedIps: { title: '잠긴 IP 관리', diff --git a/packages/client/src/i18n/locales/pt.ts b/packages/client/src/i18n/locales/pt.ts index 515d0d8..5fced91 100644 --- a/packages/client/src/i18n/locales/pt.ts +++ b/packages/client/src/i18n/locales/pt.ts @@ -670,6 +670,32 @@ jobTriggered: 'Job acionado', testButton: 'Testar', testButtonPlaying: 'Reproduzindo...', testFailed: 'Teste falhou: {error}', + + // MiMo TTS + providerMimo: 'MiMo TTS', + mimoHint: 'Xiaomi MiMo TTS — vozes predefinidas, design de voz e clonagem de voz', + mimoApiKey: 'Chave API', + mimoApiKeyHint: 'Obtenha sua chave em platform.xiaomimimo.com', + mimoApiKeyPlaceholder: 'Chave API MiMo', + mimoBaseUrl: 'URL base', + mimoBaseUrlHint: 'URL do endpoint da API MiMo', + mimoModel: 'Modelo', + mimoModelHint: 'Selecione o modelo de síntese de voz', + mimoModelPreset: 'Vozes predefinidas', + mimoModelVoiceDesign: 'Design de voz', + mimoModelVoiceClone: 'Clonagem de voz', + mimoVoice: 'Voz', + mimoVoiceHint: 'Selecione uma voz predefinida', + mimoVoiceDesignPrompt: 'Descrição da voz', + mimoVoiceDesignPromptHint: 'Descreva as características de voz desejadas', + mimoVoiceDesignPromptPlaceholder: 'Ex: Uma voz feminina quente e jovem, ligeiramente lenta, com tom magnético', + mimoCloneAudio: 'Enviar áudio', + mimoCloneAudioHint: 'Envie uma amostra de áudio para clonagem (mp3/wav, máx. 10 MB)', + mimoCloneAudioUpload: 'Escolher arquivo', + mimoCloneAudioClear: 'Limpar', + mimoStylePrompt: 'Prompt de estilo', + mimoStylePromptHint: 'Opcional — descreva o estilo de fala em linguagem natural', + mimoStylePromptPlaceholder: 'Ex: Tom brilhante e animado, ritmo rápido', }, lockedIps: { title: 'IPs bloqueadas', diff --git a/packages/client/src/i18n/locales/zh-TW.ts b/packages/client/src/i18n/locales/zh-TW.ts index 0e2d77e..f3b918d 100644 --- a/packages/client/src/i18n/locales/zh-TW.ts +++ b/packages/client/src/i18n/locales/zh-TW.ts @@ -836,6 +836,32 @@ export default { testButton: '試聽', testButtonPlaying: '播放中...', testFailed: '測試失敗:{error}', + + // MiMo TTS + providerMimo: 'MiMo TTS', + mimoHint: '小米 MiMo TTS,支援預設音色、音色設計、音色複製三種模式', + mimoApiKey: 'API Key', + mimoApiKeyHint: '在 platform.xiaomimimo.com 取得', + mimoApiKeyPlaceholder: 'MiMo API Key', + mimoBaseUrl: 'Base URL', + mimoBaseUrlHint: 'MiMo API 端點位址', + mimoModel: '模型', + mimoModelHint: '選擇語音合成模型', + mimoModelPreset: '預設音色', + mimoModelVoiceDesign: '音色設計', + mimoModelVoiceClone: '音色複製', + mimoVoice: '音色', + mimoVoiceHint: '選擇預設音色', + mimoVoiceDesignPrompt: '音色描述', + mimoVoiceDesignPromptHint: '描述你想要的音色特徵', + mimoVoiceDesignPromptPlaceholder: '例如:溫柔的年輕女聲,語速稍慢,帶著磁性', + mimoCloneAudio: '上傳音訊', + mimoCloneAudioHint: '上傳音訊樣本用於音色複製,支援 mp3/wav,最大 10MB', + mimoCloneAudioUpload: '選擇檔案', + mimoCloneAudioClear: '清除音訊', + mimoStylePrompt: '風格指令', + mimoStylePromptHint: '可選,用自然語言描述語音風格', + mimoStylePromptPlaceholder: '例如:用輕快上揚的語調,語速稍快', }, }, diff --git a/packages/client/src/i18n/locales/zh.ts b/packages/client/src/i18n/locales/zh.ts index 57c3de0..25c8396 100644 --- a/packages/client/src/i18n/locales/zh.ts +++ b/packages/client/src/i18n/locales/zh.ts @@ -839,6 +839,32 @@ export default { testButton: '试听', testButtonPlaying: '播放中...', testFailed: '测试失败:{error}', + + // MiMo TTS + providerMimo: 'MiMo TTS', + mimoHint: '小米 MiMo TTS,支持预置音色、音色设计、音色复刻三种模式', + mimoApiKey: 'API Key', + mimoApiKeyHint: '在 platform.xiaomimimo.com 获取', + mimoApiKeyPlaceholder: 'MiMo API Key', + mimoBaseUrl: 'Base URL', + mimoBaseUrlHint: 'MiMo API 端点地址', + mimoModel: '模型', + mimoModelHint: '选择语音合成模型', + mimoModelPreset: '预置音色', + mimoModelVoiceDesign: '音色设计', + mimoModelVoiceClone: '音色复刻', + mimoVoice: '音色', + mimoVoiceHint: '选择预置音色', + mimoVoiceDesignPrompt: '音色描述', + mimoVoiceDesignPromptHint: '描述你想要的音色特征', + mimoVoiceDesignPromptPlaceholder: '例如:温柔的年轻女声,语速稍慢,带着磁性', + mimoCloneAudio: '上传音频', + mimoCloneAudioHint: '上传音频样本用于音色复刻,支持 mp3/wav,最大 10MB', + mimoCloneAudioUpload: '选择文件', + mimoCloneAudioClear: '清除音频', + mimoStylePrompt: '风格指令', + mimoStylePromptHint: '可选,用自然语言描述语音风格', + mimoStylePromptPlaceholder: '例如:用轻快上扬的语调,语速稍快', }, },