Sfoglia il codice sorgente

Merge pull request #5313 from zabirauf/u/zabirauf/speech-speed

feat: Added speech playback speed control for Call mode
Timothy Jaeryang Baek 7 mesi fa
parent
commit
dd4cf102cc
1 ha cambiato i file con 64 aggiunte e 7 eliminazioni
  1. 64 7
      src/lib/components/chat/MessageInput/CallOverlay.svelte

+ 64 - 7
src/lib/components/chat/MessageInput/CallOverlay.svelte

@@ -1,15 +1,13 @@
 <script lang="ts">
 	import { config, models, settings, showCallOverlay } from '$lib/stores';
 	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();
 
-	import {
-		blobToFile,
-		calculateSHA256,
-		extractSentencesForAudio,
-		findWordIndices
-	} from '$lib/utils';
+	import { blobToFile } from '$lib/utils';
 	import { generateEmoji } from '$lib/apis';
 	import { synthesizeOpenAISpeech, transcribeAudio } from '$lib/apis/audio';
 
@@ -360,6 +358,7 @@
 								?.at(0) ?? undefined;
 
 						currentUtterance = new SpeechSynthesisUtterance(content);
+						currentUtterance.rate = speechRate;
 
 						if (voice) {
 							currentUtterance.voice = voice;
@@ -381,11 +380,12 @@
 	const playAudio = (audio) => {
 		if ($showCallOverlay) {
 			return new Promise((resolve) => {
-				const audioElement = document.getElementById('audioElement');
+				const audioElement = document.getElementById('audioElement') as HTMLAudioElement;
 
 				if (audioElement) {
 					audioElement.src = audio.src;
 					audioElement.muted = true;
+					audioElement.playbackRate = speechRate;
 
 					audioElement
 						.play()
@@ -430,6 +430,28 @@
 
 	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.
 	const audioCache = new Map();
 	const emojiCache = new Map();
@@ -918,6 +940,41 @@
 				</button>
 			</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>
 				<button
 					class=" p-3 rounded-full bg-gray-50 dark:bg-gray-900"