feat: add Edge TTS rate/pitch sliders to voice settings (#629)

Add speed (rate) and pitch controls for Edge TTS provider:
- Frontend: speedToEdgeRate()/hzToEdgePitch() helpers + UI sliders
- Backend: rate/pitch passthrough in OpenaiTtsRequest and controller
- i18n: add edgeRate/edgePitch keys across all 8 languages
- Rate: 0.5x-2.0x slider, Pitch: -20Hz to +20Hz slider
This commit is contained in:
memeflyfly
2026-05-11 21:56:11 +08:00
committed by GitHub
parent 5e608ea338
commit a68b9bf01f
16 changed files with 142 additions and 4 deletions
@@ -17,6 +17,7 @@ import {
} from "./highlight";
import { useGlobalSpeech } from "@/composables/useSpeech";
import { useVoiceSettings } from "@/composables/useVoiceSettings";
import { speedToEdgeRate, hzToEdgePitch } from "@/utils/ttsHelpers";
const TOOL_PAYLOAD_DISPLAY_LIMIT = 2000;
@@ -420,6 +421,8 @@ function handleSpeechToggle() {
speech.openaiToggle(props.message.id, content, {
baseUrl: apiUrl,
voice: voiceSettings.edgeVoice.value,
rate: speedToEdgeRate(voiceSettings.edgeRate.value),
pitch: hzToEdgePitch(voiceSettings.edgePitchHz.value),
})
return
}
@@ -466,6 +469,8 @@ onMounted(() => {
speech.openaiPlay(props.message.id, content, {
baseUrl: '/api/tts/proxy',
voice: voiceSettings.edgeVoice.value,
rate: speedToEdgeRate(voiceSettings.edgeRate.value),
pitch: hzToEdgePitch(voiceSettings.edgePitchHz.value),
})
} else if (voiceSettings.provider.value === 'webspeech') {
const text = speech.extractReadableText(content)
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { NSelect, NInput, NButton } from 'naive-ui'
import { NSelect, NInput, NButton, NSlider } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { useVoiceSettings } from '@/composables/useVoiceSettings'
import { useSpeech } from '@/composables/useSpeech'
import { speedToEdgeRate, hzToEdgePitch } from '@/utils/ttsHelpers'
import SettingRow from './SettingRow.vue'
const { t } = useI18n()
@@ -103,6 +104,8 @@ async function handleTest() {
await speech.openaiPlay('__test__', text, {
baseUrl: '/api/tts/proxy',
voice: vs.edgeVoice.value,
rate: speedToEdgeRate(vs.edgeRate.value),
pitch: hzToEdgePitch(vs.edgePitchHz.value),
})
}
} catch (err) {
@@ -267,6 +270,40 @@ async function handleTest() {
/>
</SettingRow>
<SettingRow
:label="t('settings.voice.edgeRate')"
:hint="t('settings.voice.edgeRateHint')"
>
<div class="slider-row">
<NSlider
:value="vs.edgeRate.value"
:min="0.5"
:max="2.0"
:step="0.05"
style="width: 200px"
@update:value="vs.setEdgeRate"
/>
<span class="slider-value">{{ vs.edgeRate.value.toFixed(2) }}x ({{ speedToEdgeRate(vs.edgeRate.value) }})</span>
</div>
</SettingRow>
<SettingRow
:label="t('settings.voice.edgePitch')"
:hint="t('settings.voice.edgePitchHint')"
>
<div class="slider-row">
<NSlider
:value="vs.edgePitchHz.value"
:min="-20"
:max="20"
:step="1"
style="width: 200px"
@update:value="vs.setEdgePitchHz"
/>
<span class="slider-value">{{ vs.edgePitchHz.value > 0 ? '+' : '' }}{{ vs.edgePitchHz.value }} Hz ({{ hzToEdgePitch(vs.edgePitchHz.value) }})</span>
</div>
</SettingRow>
</template>
<!-- Test / Audition -->
@@ -324,4 +361,17 @@ async function handleTest() {
align-items: center;
}
}
.slider-row {
display: flex;
align-items: center;
gap: 12px;
}
.slider-value {
font-size: 12px;
color: #999;
white-space: nowrap;
min-width: 120px;
}
</style>