mirror of
https://github.com/open-webui/open-webui
synced 2025-05-23 06:14:25 +00:00
Merge pull request #5313 from zabirauf/u/zabirauf/speech-speed
feat: Added speech playback speed control for Call mode
This commit is contained in:
commit
dd4cf102cc
@ -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 = speechRate;
|
||||||
|
|
||||||
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 = speechRate;
|
||||||
|
|
||||||
audioElement
|
audioElement
|
||||||
.play()
|
.play()
|
||||||
@ -430,6 +430,28 @@
|
|||||||
|
|
||||||
let audioAbortController = new AbortController();
|
let audioAbortController = new AbortController();
|
||||||
|
|
||||||
|
// Audio speed control
|
||||||
|
let speechRate = 1;
|
||||||
|
let showSpeedMenu = false;
|
||||||
|
|
||||||
|
const speedOptions = [2, 1.75, 1.5, 1.25, 1, 0.75, 0.5];
|
||||||
|
|
||||||
|
const setSpeedRate = (rate: number) => {
|
||||||
|
speechRate = rate;
|
||||||
|
showSpeedMenu = false;
|
||||||
|
updateAudioSpeed();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAudioSpeed = () => {
|
||||||
|
if (currentUtterance) {
|
||||||
|
currentUtterance.rate = speechRate;
|
||||||
|
}
|
||||||
|
const audioElement = document.getElementById('audioElement') as HTMLAudioElement;
|
||||||
|
if (audioElement) {
|
||||||
|
audioElement.playbackRate = speechRate;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Audio cache map where key is the content and value is the Audio object.
|
// Audio cache map where key is the content and value is the Audio object.
|
||||||
const audioCache = new Map();
|
const audioCache = new Map();
|
||||||
const emojiCache = new Map();
|
const emojiCache = new Map();
|
||||||
@ -918,6 +940,41 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
|
<Dropdown bind:show={showSpeedMenu}>
|
||||||
|
<button class="p-2 rounded-full bg-gray-50 dark:bg-gray-900">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polygon points="8,5 8,19 19,12" fill="currentColor"/>
|
||||||
|
<path d="M12 2A10 10 0 0 0 12 22" fill="none" stroke="currentColor" stroke-width="2" stroke-dasharray="2,2"/>
|
||||||
|
<path d="M12 2A10 10 0 0 1 12 22" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div slot="content">
|
||||||
|
<DropdownMenu.Content
|
||||||
|
class="w-full max-w-[180px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-[9999] bg-white dark:bg-gray-900 dark:text-white shadow-sm"
|
||||||
|
sideOffset={6}
|
||||||
|
side="top"
|
||||||
|
align="start"
|
||||||
|
transition={flyAndScale}
|
||||||
|
>
|
||||||
|
{#each speedOptions as speed}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md {speechRate === speed ? 'bg-gray-200 dark:bg-gray-600' : ''}"
|
||||||
|
on:click={() => setSpeedRate(speed)}
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="line-clamp-1">
|
||||||
|
{speed}x
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{/each}
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class=" p-3 rounded-full bg-gray-50 dark:bg-gray-900"
|
class=" p-3 rounded-full bg-gray-50 dark:bg-gray-900"
|
||||||
|
Loading…
Reference in New Issue
Block a user