mirror of
https://github.com/open-webui/open-webui
synced 2025-06-26 18:26:48 +00:00
Merge pull request #5509 from open-webui/dev-playback-controls
feat: playback controls
This commit is contained in:
commit
9fc6b999d0
@ -1,15 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { config, models, settings, showCallOverlay } from '$lib/stores';
|
import { config, models, settings, showCallOverlay } from '$lib/stores';
|
||||||
import { onMount, tick, getContext, onDestroy, createEventDispatcher } from 'svelte';
|
import { onMount, tick, getContext, onDestroy, createEventDispatcher } from 'svelte';
|
||||||
|
import { DropdownMenu } from 'bits-ui';
|
||||||
|
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||||
|
import { flyAndScale } from '$lib/utils/transitions';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
import {
|
import { blobToFile } from '$lib/utils';
|
||||||
blobToFile,
|
|
||||||
calculateSHA256,
|
|
||||||
extractSentencesForAudio,
|
|
||||||
findWordIndices
|
|
||||||
} from '$lib/utils';
|
|
||||||
import { generateEmoji } from '$lib/apis';
|
import { generateEmoji } from '$lib/apis';
|
||||||
import { synthesizeOpenAISpeech, transcribeAudio } from '$lib/apis/audio';
|
import { synthesizeOpenAISpeech, transcribeAudio } from '$lib/apis/audio';
|
||||||
|
|
||||||
@ -360,6 +358,7 @@
|
|||||||
?.at(0) ?? undefined;
|
?.at(0) ?? undefined;
|
||||||
|
|
||||||
currentUtterance = new SpeechSynthesisUtterance(content);
|
currentUtterance = new SpeechSynthesisUtterance(content);
|
||||||
|
currentUtterance.rate = $settings.audio?.tts?.speedRate ?? 1;
|
||||||
|
|
||||||
if (voice) {
|
if (voice) {
|
||||||
currentUtterance.voice = voice;
|
currentUtterance.voice = voice;
|
||||||
@ -381,11 +380,12 @@
|
|||||||
const playAudio = (audio) => {
|
const playAudio = (audio) => {
|
||||||
if ($showCallOverlay) {
|
if ($showCallOverlay) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const audioElement = document.getElementById('audioElement');
|
const audioElement = document.getElementById('audioElement') as HTMLAudioElement;
|
||||||
|
|
||||||
if (audioElement) {
|
if (audioElement) {
|
||||||
audioElement.src = audio.src;
|
audioElement.src = audio.src;
|
||||||
audioElement.muted = true;
|
audioElement.muted = true;
|
||||||
|
audioElement.playbackRate = $settings.audio?.tts?.speedRate ?? 1;
|
||||||
|
|
||||||
audioElement
|
audioElement
|
||||||
.play()
|
.play()
|
||||||
|
@ -204,6 +204,8 @@
|
|||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const blobUrl = URL.createObjectURL(blob);
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
const audio = new Audio(blobUrl);
|
const audio = new Audio(blobUrl);
|
||||||
|
audio.playbackRate = $settings.audio?.tts?.speedRate ?? 1;
|
||||||
|
|
||||||
audioParts[idx] = audio;
|
audioParts[idx] = audio;
|
||||||
loadingSpeech = false;
|
loadingSpeech = false;
|
||||||
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
|
lastPlayedAudioPromise = lastPlayedAudioPromise.then(() => playAudio(idx));
|
||||||
@ -226,6 +228,7 @@
|
|||||||
console.log(voice);
|
console.log(voice);
|
||||||
|
|
||||||
const speak = new SpeechSynthesisUtterance(message.content);
|
const speak = new SpeechSynthesisUtterance(message.content);
|
||||||
|
speak.rate = $settings.audio?.tts?.speedRate ?? 1;
|
||||||
|
|
||||||
console.log(speak);
|
console.log(speak);
|
||||||
|
|
||||||
@ -410,7 +413,7 @@
|
|||||||
const isEnterPressed = e.key === 'Enter';
|
const isEnterPressed = e.key === 'Enter';
|
||||||
|
|
||||||
if (isCmdOrCtrlPressed && isEnterPressed) {
|
if (isCmdOrCtrlPressed && isEnterPressed) {
|
||||||
document.getElementById('save-edit-message-button')?.click();
|
document.getElementById('confirm-edit-message-button')?.click();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -418,7 +421,7 @@
|
|||||||
<div class=" mt-2 mb-1 flex justify-between text-sm font-medium">
|
<div class=" mt-2 mb-1 flex justify-between text-sm font-medium">
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
id="close-edit-message-button"
|
id="save-new-message-button"
|
||||||
class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
|
class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
saveNewMessageHandler();
|
saveNewMessageHandler();
|
||||||
|
@ -23,6 +23,10 @@
|
|||||||
let voices = [];
|
let voices = [];
|
||||||
let voice = '';
|
let voice = '';
|
||||||
|
|
||||||
|
// Audio speed control
|
||||||
|
let speechRate = 1;
|
||||||
|
const speedOptions = [2, 1.75, 1.5, 1.25, 1, 0.75, 0.5];
|
||||||
|
|
||||||
const getVoices = async () => {
|
const getVoices = async () => {
|
||||||
if ($config.audio.tts.engine === '') {
|
if ($config.audio.tts.engine === '') {
|
||||||
const getVoicesLoop = setInterval(async () => {
|
const getVoicesLoop = setInterval(async () => {
|
||||||
@ -56,6 +60,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
speechRate = $settings.audio?.tts?.speedRate ?? 1;
|
||||||
conversationMode = $settings.conversationMode ?? false;
|
conversationMode = $settings.conversationMode ?? false;
|
||||||
speechAutoSend = $settings.speechAutoSend ?? false;
|
speechAutoSend = $settings.speechAutoSend ?? false;
|
||||||
responseAutoPlayback = $settings.responseAutoPlayback ?? false;
|
responseAutoPlayback = $settings.responseAutoPlayback ?? false;
|
||||||
@ -83,6 +88,7 @@
|
|||||||
engine: STTEngine !== '' ? STTEngine : undefined
|
engine: STTEngine !== '' ? STTEngine : undefined
|
||||||
},
|
},
|
||||||
tts: {
|
tts: {
|
||||||
|
speedRate: speechRate,
|
||||||
voice: voice !== '' ? voice : undefined,
|
voice: voice !== '' ? voice : undefined,
|
||||||
defaultVoice: $config?.audio?.tts?.voice ?? '',
|
defaultVoice: $config?.audio?.tts?.voice ?? '',
|
||||||
nonLocalVoices: $config.audio.tts.engine === '' ? nonLocalVoices : undefined
|
nonLocalVoices: $config.audio.tts.engine === '' ? nonLocalVoices : undefined
|
||||||
@ -153,6 +159,21 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class=" py-0.5 flex w-full justify-between">
|
||||||
|
<div class=" self-center text-xs font-medium">{$i18n.t('Speed Rate')}</div>
|
||||||
|
|
||||||
|
<div class="flex items-center relative">
|
||||||
|
<select
|
||||||
|
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
|
||||||
|
bind:value={speechRate}
|
||||||
|
>
|
||||||
|
{#each speedOptions as option}
|
||||||
|
<option value={option} selected={speechRate === option}>{option}x</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class=" dark:border-gray-850" />
|
<hr class=" dark:border-gray-850" />
|
||||||
|
Loading…
Reference in New Issue
Block a user