Text-to-Speech
Transformers
ONNX
GGUF
Chinese
English
voice-dialogue
speech-recognition
large-language-model
asr
tts
llm
chinese
english
real-time
conversational
Instructions to use MoYoYoTech/VoiceDialogue with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use MoYoYoTech/VoiceDialogue with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-to-speech", model="MoYoYoTech/VoiceDialogue") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("MoYoYoTech/VoiceDialogue", dtype="auto") - llama-cpp-python
How to use MoYoYoTech/VoiceDialogue with llama-cpp-python:
# !pip install llama-cpp-python from llama_cpp import Llama llm = Llama.from_pretrained( repo_id="MoYoYoTech/VoiceDialogue", filename="assets/models/llm/qwen/Qwen3-8B-Q6_K.gguf", )
llm.create_chat_completion( messages = "\"The answer to the universe is 42\"" )
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use MoYoYoTech/VoiceDialogue with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf MoYoYoTech/VoiceDialogue:Q6_K # Run inference directly in the terminal: llama cli -hf MoYoYoTech/VoiceDialogue:Q6_K
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf MoYoYoTech/VoiceDialogue:Q6_K # Run inference directly in the terminal: llama cli -hf MoYoYoTech/VoiceDialogue:Q6_K
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf MoYoYoTech/VoiceDialogue:Q6_K # Run inference directly in the terminal: ./llama-cli -hf MoYoYoTech/VoiceDialogue:Q6_K
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf MoYoYoTech/VoiceDialogue:Q6_K # Run inference directly in the terminal: ./build/bin/llama-cli -hf MoYoYoTech/VoiceDialogue:Q6_K
Use Docker
docker model run hf.co/MoYoYoTech/VoiceDialogue:Q6_K
- LM Studio
- Jan
- Ollama
How to use MoYoYoTech/VoiceDialogue with Ollama:
ollama run hf.co/MoYoYoTech/VoiceDialogue:Q6_K
- Unsloth Studio
How to use MoYoYoTech/VoiceDialogue with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for MoYoYoTech/VoiceDialogue to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for MoYoYoTech/VoiceDialogue to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for MoYoYoTech/VoiceDialogue to start chatting
- Pi
How to use MoYoYoTech/VoiceDialogue with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf MoYoYoTech/VoiceDialogue:Q6_K
Configure the model in Pi
# Install Pi: npm install -g @mariozechner/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "MoYoYoTech/VoiceDialogue:Q6_K" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Hermes Agent new
How to use MoYoYoTech/VoiceDialogue with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf MoYoYoTech/VoiceDialogue:Q6_K
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default MoYoYoTech/VoiceDialogue:Q6_K
Run Hermes
hermes
- Atomic Chat new
- Docker Model Runner
How to use MoYoYoTech/VoiceDialogue with Docker Model Runner:
docker model run hf.co/MoYoYoTech/VoiceDialogue:Q6_K
- Lemonade
How to use MoYoYoTech/VoiceDialogue with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull MoYoYoTech/VoiceDialogue:Q6_K
Run and chat with the model
lemonade run user.VoiceDialogue-Q6_K
List all available models
lemonade list
| <script setup lang="ts"> | |
| import { ref, reactive, computed, watch, onUnmounted } from "vue"; | |
| import { Modal } from "ant-design-vue"; | |
| import { SoundTwoTone, SoundOutlined, TranslationOutlined, AudioOutlined } from "@ant-design/icons-vue"; | |
| import { useI18n } from "vue-i18n"; | |
| import axios from "axios"; | |
| import { useSettingsStore } from "@/stores/config.ts"; | |
| import { setUiLocale, UiLocale } from "@/i18n"; | |
| const props = defineProps({ | |
| open: { type: Boolean, default: false }, | |
| }); | |
| const emit = defineEmits(["update:open"]); | |
| const { t } = useI18n(); | |
| const base_url = axios.defaults.baseURL; | |
| const settingsStore = useSettingsStore(); | |
| const activeTab = ref<string>("main"); | |
| const loading = ref<boolean>(false); | |
| const appVersion = "1.2.0"; | |
| // ---- 各项设置的本地状态(打开时从 store / 后端同步)---- | |
| const uiLanguage = ref<UiLocale>((settingsStore.$state.uiLanguage as UiLocale) ?? "en"); | |
| const recognitionLanguage = ref<string>(settingsStore.$state.language || "zh"); | |
| const echoCancel = ref<boolean>(settingsStore.$state.echoCancel ?? true); | |
| const inputDeviceIndex = ref<number | null>(settingsStore.$state.inputDeviceIndex ?? null); | |
| const outputDeviceIndex = ref<number | null>(settingsStore.$state.outputDeviceIndex ?? null); | |
| const role = ref<string>(settingsStore.$state.role || ""); | |
| const languages = reactive<string[]>([]); | |
| const inputDevices = reactive<any[]>([]); | |
| const outputDevices = reactive<any[]>([]); | |
| const roles = reactive<any[]>([]); | |
| // ---- Prompt ---- | |
| const promptLang = ref<string>("zh"); | |
| const default_prompt_en = ref<string>(""); | |
| const default_prompt_zh = ref<string>(""); | |
| const current_prompt_en = ref<string>(""); | |
| const current_prompt_zh = ref<string>(""); | |
| const filteredRoles = computed(() => { | |
| const is_chinese = recognitionLanguage.value === "zh"; | |
| return roles.filter((r) => r["is_chinese_voice"] === is_chinese); | |
| }); | |
| // 切换识别语言后,自动选中第一个匹配音色 | |
| watch( | |
| () => recognitionLanguage.value, | |
| () => { | |
| if (filteredRoles.value.length > 0) { | |
| const exists = filteredRoles.value.find((r) => r["id"] === role.value); | |
| role.value = exists ? role.value : filteredRoles.value[0]["id"]; | |
| } else { | |
| role.value = ""; | |
| } | |
| } | |
| ); | |
| // 界面语言即时生效(让用户立刻看到切换效果) | |
| watch(uiLanguage, (v) => setUiLocale(v)); | |
| // ---- 数据加载 ---- | |
| const fetchASRLanguages = async () => { | |
| try { | |
| const res = await fetch(`${base_url}/asr/languages`); | |
| const data = await res.json(); | |
| if (data?.languages) { | |
| languages.splice(0, languages.length, ...data.languages); | |
| // 优先沿用本地已保存/默认的识别语言(默认中文),不被后端当前值覆盖 | |
| const saved = settingsStore.$state.language; | |
| recognitionLanguage.value = saved && data.languages.includes(saved) | |
| ? saved | |
| : (data.languages.includes('zh') ? 'zh' : data.languages[0]); | |
| } | |
| } catch (e) { | |
| console.error("Error fetching ASR languages:", e); | |
| } | |
| }; | |
| const fetchTTSRoles = async () => { | |
| try { | |
| const res = await fetch(`${base_url}/tts/models`); | |
| const data = await res.json(); | |
| if (data?.models) { | |
| roles.splice(0, roles.length, ...data.models); | |
| if (data.current_model_id) role.value = data.current_model_id; | |
| } | |
| } catch (e) { | |
| console.error("Error fetching TTS roles:", e); | |
| } | |
| }; | |
| const fetchInputDevices = async () => { | |
| try { | |
| const res = await fetch(`${base_url}/system/audio-devices`); | |
| const data = await res.json(); | |
| if (data?.devices) { | |
| inputDevices.splice(0, inputDevices.length, ...data.devices); | |
| const saved = settingsStore.$state.inputDeviceIndex; | |
| const exists = saved != null && data.devices.some((d: any) => d.index === saved); | |
| inputDeviceIndex.value = exists ? saved : (data.current_device_index ?? null); | |
| } | |
| if (data?.output_devices) { | |
| outputDevices.splice(0, outputDevices.length, ...data.output_devices); | |
| const saved = settingsStore.$state.outputDeviceIndex; | |
| const exists = saved != null && data.output_devices.some((d: any) => d.index === saved); | |
| outputDeviceIndex.value = exists ? saved : (data.current_output_device_index ?? null); | |
| } | |
| } catch (e) { | |
| console.error("Error fetching input devices:", e); | |
| } | |
| }; | |
| // 当前实际生效的 ASR 引擎(由后端返回,区分 Qwen / FunASR+Whisper 等) | |
| const asrEngineName = ref<string>(""); | |
| const asrEngineKeys = ref<string[]>([]); | |
| const ASR_ENGINE_LINKS: Record<string, { name: string; url: string }> = { | |
| qwen: { name: "Qwen3-ASR", url: "https://huggingface.co/Qwen/Qwen3-ASR-1.7B" }, | |
| whisper: { name: "whisper.cpp", url: "https://github.com/ggerganov/whisper.cpp" }, | |
| funasr: { name: "FunASR", url: "https://github.com/modelscope/FunASR" }, | |
| }; | |
| const asrEngineLinks = computed(() => { | |
| const keys = asrEngineKeys.value.length ? asrEngineKeys.value : ["whisper", "funasr"]; | |
| return keys.map((k) => ASR_ENGINE_LINKS[k]).filter(Boolean); | |
| }); | |
| const fetchAsrEngine = async () => { | |
| try { | |
| const res = await fetch(`${base_url}/system/asr-engine`); | |
| const data = await res.json(); | |
| if (data?.display_name) asrEngineName.value = data.display_name; | |
| if (data?.mappings) asrEngineKeys.value = [...new Set(Object.values(data.mappings) as string[])].sort(); | |
| } catch (e) { | |
| console.error("Error fetching ASR engine:", e); | |
| } | |
| }; | |
| const fetchPrompts = async () => { | |
| try { | |
| const [cur, def] = await Promise.all([ | |
| fetch(`${base_url}/settings/settings/prompts`).then((r) => r.json()), | |
| fetch(`${base_url}/settings/settings/prompts/default`).then((r) => r.json()), | |
| ]); | |
| if (cur) { | |
| current_prompt_en.value = cur.english_prompt; | |
| current_prompt_zh.value = cur.chinese_prompt; | |
| } | |
| if (def) { | |
| default_prompt_en.value = def.english_prompt; | |
| default_prompt_zh.value = def.chinese_prompt; | |
| } | |
| } catch (e) { | |
| console.error("Error fetching prompts:", e); | |
| } | |
| }; | |
| const resetPrompt = (lang: string) => { | |
| if (lang === "en") current_prompt_en.value = default_prompt_en.value; | |
| else current_prompt_zh.value = default_prompt_zh.value; | |
| }; | |
| // ---- 提交 / 取消 ---- | |
| const applySettings = async () => { | |
| loading.value = true; | |
| try { | |
| // 1. 持久化到本地 store | |
| settingsStore.$state.uiLanguage = uiLanguage.value; | |
| settingsStore.$state.language = recognitionLanguage.value; | |
| settingsStore.$state.role = role.value || ""; | |
| settingsStore.$state.echoCancel = echoCancel.value; | |
| settingsStore.$state.inputDeviceIndex = inputDeviceIndex.value; | |
| settingsStore.$state.outputDeviceIndex = outputDeviceIndex.value; | |
| // 输出设备保存即生效(会话中修改下一句生效) | |
| await fetch(`${base_url}/system/audio-output-device`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ output_device_index: outputDeviceIndex.value }), | |
| }); | |
| // 2. 下发 TTS 音色 + ASR 语言 | |
| if (role.value) { | |
| const r1 = await fetch(`${base_url}/tts/models/load`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ model_id: role.value }), | |
| }); | |
| if (!r1.ok) throw new Error(`TTS load failed: ${r1.status}`); | |
| } | |
| const r2 = await fetch(`${base_url}/asr/instance/create`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ language: recognitionLanguage.value }), | |
| }); | |
| if (!r2.ok) throw new Error(`ASR set failed: ${r2.status}`); | |
| // 3. 保存 Prompt | |
| await fetch(`${base_url}/settings/settings/prompts`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| chinese_prompt: current_prompt_zh.value, | |
| english_prompt: current_prompt_en.value, | |
| }), | |
| }); | |
| emit("update:open", false); | |
| } catch (err) { | |
| console.error("Error applying settings:", err); | |
| Modal.error({ title: t("common.error"), content: t("settings.applyFailed") }); | |
| } finally { | |
| loading.value = false; | |
| } | |
| }; | |
| const handleCancel = () => { | |
| // 还原本地状态与界面语言 | |
| uiLanguage.value = (settingsStore.$state.uiLanguage as UiLocale) ?? "en"; | |
| setUiLocale(uiLanguage.value); | |
| recognitionLanguage.value = settingsStore.$state.language || "zh"; | |
| echoCancel.value = settingsStore.$state.echoCancel ?? true; | |
| inputDeviceIndex.value = settingsStore.$state.inputDeviceIndex ?? null; | |
| outputDeviceIndex.value = settingsStore.$state.outputDeviceIndex ?? null; | |
| role.value = settingsStore.$state.role || ""; | |
| emit("update:open", false); | |
| }; | |
| watch( | |
| () => props.open, | |
| (isOpen) => { | |
| if (isOpen) { | |
| activeTab.value = "main"; | |
| uiLanguage.value = (settingsStore.$state.uiLanguage as UiLocale) ?? "en"; | |
| fetchASRLanguages(); | |
| fetchTTSRoles(); | |
| fetchInputDevices(); | |
| fetchPrompts(); | |
| fetchAsrEngine(); | |
| } | |
| } | |
| ); | |
| // ---- 音色试听 ---- | |
| const currentPlayingId = ref<string | null>(null); | |
| const currentAudio = ref<HTMLAudioElement | null>(null); | |
| const isPlaying = (id: string) => currentPlayingId.value === id; | |
| const playRefAudio = async (id: string, e: Event) => { | |
| e.stopPropagation(); | |
| e.preventDefault(); | |
| try { | |
| if (currentPlayingId.value === id && currentAudio.value) { | |
| currentAudio.value.pause(); | |
| currentAudio.value = null; | |
| currentPlayingId.value = null; | |
| return; | |
| } | |
| if (currentAudio.value) { | |
| currentAudio.value.pause(); | |
| currentAudio.value = null; | |
| } | |
| const audio = new Audio(`${base_url}/tts/models/${id}/reference-audio`); | |
| audio.addEventListener("ended", () => { | |
| currentPlayingId.value = null; | |
| currentAudio.value = null; | |
| }); | |
| await audio.play(); | |
| currentPlayingId.value = id; | |
| currentAudio.value = audio; | |
| } catch (err) { | |
| currentPlayingId.value = null; | |
| currentAudio.value = null; | |
| } | |
| }; | |
| onUnmounted(() => { | |
| if (currentAudio.value) currentAudio.value.pause(); | |
| }); | |
| </script> | |
| <template> | |
| <a-modal | |
| :open="props.open" | |
| :title="t('settings.title')" | |
| :mask-closable="false" | |
| :closable="true" | |
| :width="600" | |
| centered | |
| transition-name="ant-fade" | |
| @cancel="handleCancel" | |
| @update:open="(v: boolean) => emit('update:open', v)" | |
| > | |
| <template #footer> | |
| <a-button key="back" @click="handleCancel">{{ t('common.cancel') }}</a-button> | |
| <a-button key="confirm" type="primary" :loading="loading" @click="applySettings"> | |
| {{ t('common.confirm') }} | |
| </a-button> | |
| </template> | |
| <a-tabs v-model:activeKey="activeTab" class="settings-tabs"> | |
| <!-- 常用:输入源 + 回音消除 + 音色(大家最关心的) --> | |
| <a-tab-pane key="main" :tab="t('settings.tabs.main')"> | |
| <div class="tab-body"> | |
| <div class="setting-row"> | |
| <label>{{ t('settings.audio.microphone') }}</label> | |
| <a-select v-model:value="inputDeviceIndex" style="width: 100%;"> | |
| <a-select-option :value="null">{{ t('settings.audio.systemDefault') }}</a-select-option> | |
| <a-select-option v-for="dev in inputDevices" :value="dev.index" :key="dev.index"> | |
| {{ dev.name }}<template v-if="dev.max_input_channels > 1"> ({{ dev.max_input_channels }}{{ t('settings.audio.channelsSuffix') }})</template><template v-if="dev.is_default"> · {{ t('settings.audio.defaultSuffix') }}</template> | |
| </a-select-option> | |
| </a-select> | |
| </div> | |
| <div class="setting-row"> | |
| <label>{{ t('settings.audio.speaker') }}</label> | |
| <a-select v-model:value="outputDeviceIndex" style="width: 100%;"> | |
| <a-select-option :value="null">{{ t('settings.audio.systemDefault') }}</a-select-option> | |
| <a-select-option v-for="dev in outputDevices" :value="dev.index" :key="dev.index"> | |
| {{ dev.name }}<template v-if="dev.is_default"> · {{ t('settings.audio.defaultSuffix') }}</template> | |
| </a-select-option> | |
| </a-select> | |
| </div> | |
| <div class="setting-row"> | |
| <div class="row-inline"> | |
| <label>{{ t('settings.audio.echoCancellation') }}</label> | |
| <a-switch v-model:checked="echoCancel" /> | |
| </div> | |
| </div> | |
| <div class="setting-row"> | |
| <label>{{ t('settings.voice.role') }}</label> | |
| <a-radio-group v-model:value="role" class="voice-group"> | |
| <a-radio v-for="r in filteredRoles" :value="r['id']" :key="r['id']" class="voice-radio"> | |
| <span class="voice-name">{{ r['character_name'] }}</span> | |
| <a-button | |
| type="text" | |
| class="audio-play-btn" | |
| :class="{ playing: isPlaying(r['id']) }" | |
| @click="playRefAudio(r['id'], $event)" | |
| > | |
| <SoundTwoTone v-if="isPlaying(r['id'])" style="font-size: 16px; color: #52c41a;" /> | |
| <SoundOutlined v-else style="font-size: 16px; color: #1890ff;" /> | |
| </a-button> | |
| </a-radio> | |
| </a-radio-group> | |
| </div> | |
| </div> | |
| </a-tab-pane> | |
| <!-- 语言:界面语言 + 识别语言 --> | |
| <a-tab-pane key="language" :tab="t('settings.tabs.language')"> | |
| <div class="tab-body"> | |
| <div class="setting-row"> | |
| <label><TranslationOutlined class="label-icon" />{{ t('settings.general.interfaceLanguage') }}</label> | |
| <a-select v-model:value="uiLanguage" style="width: 100%;"> | |
| <a-select-option value="zh">{{ t('lang.zh') }}</a-select-option> | |
| <a-select-option value="en">{{ t('lang.en') }}</a-select-option> | |
| </a-select> | |
| <p class="hint">{{ t('settings.general.interfaceLanguageHint') }}</p> | |
| </div> | |
| <div class="setting-row"> | |
| <label><AudioOutlined class="label-icon" />{{ t('settings.recognition.language') }}</label> | |
| <a-select v-model:value="recognitionLanguage" style="width: 100%;"> | |
| <a-select-option v-for="lan in languages" :value="lan" :key="lan"> | |
| {{ t('lang.' + lan) }} | |
| </a-select-option> | |
| </a-select> | |
| <p class="hint">{{ t('settings.recognition.languageHint') }}</p> | |
| </div> | |
| </div> | |
| </a-tab-pane> | |
| <!-- 高级:系统提示词 --> | |
| <a-tab-pane key="advanced" :tab="t('settings.tabs.advanced')"> | |
| <div class="tab-body"> | |
| <div class="setting-row"> | |
| <label>{{ t('settings.prompt.title') }}</label> | |
| <a-radio-group button-style="solid" size="small" v-model:value="promptLang" style="margin-bottom: 12px;"> | |
| <a-radio-button value="zh">{{ t('lang.zh') }}</a-radio-button> | |
| <a-radio-button value="en">{{ t('lang.en') }}</a-radio-button> | |
| </a-radio-group> | |
| <div v-show="promptLang === 'zh'"> | |
| <a-textarea v-model:value="current_prompt_zh" :placeholder="default_prompt_zh" | |
| :auto-size="{ minRows: 6, maxRows: 10 }" show-count :maxlength="2000" allow-clear /> | |
| <a-button size="small" @click="resetPrompt('zh')" style="margin-top: 12px;">{{ t('common.reset') }}</a-button> | |
| </div> | |
| <div v-show="promptLang === 'en'"> | |
| <a-textarea v-model:value="current_prompt_en" :placeholder="default_prompt_en" | |
| :auto-size="{ minRows: 6, maxRows: 10 }" show-count :maxlength="2000" allow-clear /> | |
| <a-button size="small" @click="resetPrompt('en')" style="margin-top: 12px;">{{ t('common.reset') }}</a-button> | |
| </div> | |
| </div> | |
| </div> | |
| </a-tab-pane> | |
| <!-- 关于 --> | |
| <a-tab-pane key="about" :tab="t('settings.tabs.about')"> | |
| <div class="tab-body about"> | |
| <div class="about-head"> | |
| <div class="about-name">Voice Dialogue</div> | |
| <div class="about-ver">{{ t('settings.about.version') }} {{ appVersion }}</div> | |
| <div class="about-tagline">{{ t('settings.about.tagline') }}</div> | |
| </div> | |
| <div class="about-section"> | |
| <div class="about-section-title">{{ t('settings.about.modelsTitle') }}</div> | |
| <div class="about-item"> | |
| <div class="about-item-label">{{ t('settings.about.llm') }}</div> | |
| <div class="about-item-desc"> | |
| {{ t('settings.about.llmDesc') }} | |
| <a href="https://huggingface.co/Qwen/Qwen3-8B" target="_blank" rel="noopener">Qwen3 ↗</a> | |
| </div> | |
| </div> | |
| <div class="about-item"> | |
| <div class="about-item-label">{{ t('settings.about.asr') }}</div> | |
| <div class="about-item-desc"> | |
| {{ asrEngineName || t('settings.about.asrDesc') }} | |
| <a v-for="link in asrEngineLinks" :key="link.url" :href="link.url" target="_blank" | |
| rel="noopener">{{ link.name }} ↗</a> | |
| </div> | |
| </div> | |
| <div class="about-item"> | |
| <div class="about-item-label">{{ t('settings.about.tts') }}</div> | |
| <div class="about-item-desc"> | |
| {{ t('settings.about.ttsDesc') }} | |
| <a href="https://github.com/RVC-Boss/GPT-SoVITS" target="_blank" rel="noopener">GPT-SoVITS ↗</a> | |
| <a href="https://huggingface.co/hexgrad/Kokoro-82M" target="_blank" rel="noopener">Kokoro ↗</a> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="about-section"> | |
| <div class="about-section-title">{{ t('settings.about.linksTitle') }}</div> | |
| <div class="about-item"> | |
| <div class="about-item-label">{{ t('settings.about.repoApp') }}</div> | |
| <a class="about-link" href="https://huggingface.co/MoYoYoTech/VoiceDialogue" target="_blank" rel="noopener">huggingface.co/MoYoYoTech/VoiceDialogue</a> | |
| </div> | |
| <div class="about-item"> | |
| <div class="about-item-label">{{ t('settings.about.repoVoices') }}</div> | |
| <a class="about-link" href="https://huggingface.co/MoYoYoTech/tone-models" target="_blank" rel="noopener">huggingface.co/MoYoYoTech/tone-models</a> | |
| </div> | |
| </div> | |
| <div class="about-copyright">{{ t('settings.about.copyright') }}</div> | |
| </div> | |
| </a-tab-pane> | |
| </a-tabs> | |
| </a-modal> | |
| </template> | |
| <style lang="scss" scoped> | |
| // 固定内容区高度,切换 Tab 时横条不再跳动 | |
| .tab-body { | |
| height: 360px; | |
| overflow-y: auto; | |
| padding: 4px 8px 4px 2px; | |
| } | |
| .setting-row { | |
| margin-bottom: 20px; | |
| // 仅作用于字段标题(直接子 label),避免影响嵌套的 radio-button 等 <label> | |
| > label { | |
| display: block; | |
| font-size: 15px; | |
| font-weight: 500; | |
| margin-bottom: 8px; | |
| .label-icon { | |
| margin-right: 6px; | |
| color: #1890ff; | |
| } | |
| } | |
| .hint { | |
| font-size: 12px; | |
| color: #999; | |
| margin: 8px 0 0; | |
| } | |
| .row-inline { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| } | |
| .voice-group { | |
| display: flex; | |
| flex-direction: column; | |
| margin-top: 8px; | |
| } | |
| /* 关于页 */ | |
| .about { | |
| .about-head { | |
| text-align: center; | |
| margin-bottom: 24px; | |
| .about-name { | |
| font-size: 20px; | |
| font-weight: 600; | |
| } | |
| .about-ver { | |
| font-size: 13px; | |
| color: #888; | |
| margin-top: 2px; | |
| } | |
| .about-tagline { | |
| font-size: 12px; | |
| color: #999; | |
| margin-top: 4px; | |
| } | |
| } | |
| .about-section { | |
| margin-bottom: 20px; | |
| .about-section-title { | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: #666; | |
| margin-bottom: 10px; | |
| } | |
| } | |
| .about-item { | |
| margin-bottom: 12px; | |
| .about-item-label { | |
| font-size: 14px; | |
| font-weight: 500; | |
| } | |
| .about-item-desc { | |
| font-size: 12px; | |
| color: #777; | |
| margin-top: 2px; | |
| line-height: 1.6; | |
| a { margin-left: 6px; } | |
| } | |
| } | |
| a { | |
| color: #1677ff; | |
| text-decoration: none; | |
| &:hover { text-decoration: underline; } | |
| } | |
| .about-link { | |
| font-size: 13px; | |
| word-break: break-all; | |
| } | |
| .about-copyright { | |
| margin-top: 16px; | |
| font-size: 11px; | |
| color: #aaa; | |
| text-align: center; | |
| } | |
| } | |
| .voice-radio { | |
| display: flex; | |
| align-items: center; | |
| height: 40px; | |
| line-height: 40px; | |
| .voice-name { | |
| margin-right: 8px; | |
| } | |
| } | |
| .audio-play-btn { | |
| padding: 0 6px; | |
| border-radius: 4px; | |
| &.playing { | |
| background-color: #f6ffed; | |
| } | |
| } | |
| </style> | |