forked from open-webui/open-webui
		
	Merge pull request #689 from ollama-webui/tts-playback
feat: tts automatic playback
This commit is contained in:
		
						commit
						cb5520c519
					
				
					 5 changed files with 100 additions and 47 deletions
				
			
		|  | @ -458,6 +458,7 @@ | |||
| 										</button> | ||||
| 
 | ||||
| 										<button | ||||
| 											id="speak-button-{message.id}" | ||||
| 											class="{isLastMessage | ||||
| 												? 'visible' | ||||
| 												: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white transition" | ||||
|  |  | |||
|  | @ -10,7 +10,6 @@ | |||
| 
 | ||||
| 	// Addons | ||||
| 	let titleAutoGenerate = true; | ||||
| 	let speechAutoSend = false; | ||||
| 	let responseAutoCopy = false; | ||||
| 	let titleAutoGenerateModel = ''; | ||||
| 
 | ||||
|  | @ -23,12 +22,6 @@ | |||
| 		saveSettings({ showUsername: showUsername }); | ||||
| 	}; | ||||
| 
 | ||||
| 
 | ||||
| 	const toggleSpeechAutoSend = async () => { | ||||
| 		speechAutoSend = !speechAutoSend; | ||||
| 		saveSettings({ speechAutoSend: speechAutoSend }); | ||||
| 	}; | ||||
| 
 | ||||
| 	const toggleTitleAutoGenerate = async () => { | ||||
| 		titleAutoGenerate = !titleAutoGenerate; | ||||
| 		saveSettings({ titleAutoGenerate: titleAutoGenerate }); | ||||
|  | @ -69,7 +62,6 @@ | |||
| 		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}'); | ||||
| 
 | ||||
| 		titleAutoGenerate = settings.titleAutoGenerate ?? true; | ||||
| 		speechAutoSend = settings.speechAutoSend ?? false; | ||||
| 		responseAutoCopy = settings.responseAutoCopy ?? false; | ||||
| 		showUsername = settings.showUsername ?? false; | ||||
| 		titleAutoGenerateModel = settings.titleAutoGenerateModel ?? ''; | ||||
|  | @ -107,26 +99,6 @@ | |||
| 				</div> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div> | ||||
| 				<div class=" py-0.5 flex w-full justify-between"> | ||||
| 					<div class=" self-center text-xs font-medium">Voice Input Auto-Send</div> | ||||
| 
 | ||||
| 					<button | ||||
| 						class="p-1 px-3 text-xs flex rounded transition" | ||||
| 						on:click={() => { | ||||
| 							toggleSpeechAutoSend(); | ||||
| 						}} | ||||
| 						type="button" | ||||
| 					> | ||||
| 						{#if speechAutoSend === true} | ||||
| 							<span class="ml-2 self-center">On</span> | ||||
| 						{:else} | ||||
| 							<span class="ml-2 self-center">Off</span> | ||||
| 						{/if} | ||||
| 					</button> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div> | ||||
| 				<div class=" py-0.5 flex w-full justify-between"> | ||||
| 					<div class=" self-center text-xs font-medium">Response AutoCopy to Clipboard</div> | ||||
|  | @ -146,9 +118,12 @@ | |||
| 					</button> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div> | ||||
| 				<div class=" py-0.5 flex w-full justify-between"> | ||||
| 					<div class=" self-center text-xs font-medium">Display the username instead of "You" in the Chat</div> | ||||
| 					<div class=" self-center text-xs font-medium"> | ||||
| 						Display the username instead of "You" in the Chat | ||||
| 					</div> | ||||
| 
 | ||||
| 					<button | ||||
| 						class="p-1 px-3 text-xs flex rounded transition" | ||||
|  |  | |||
|  | @ -5,6 +5,10 @@ | |||
| 	export let saveSettings: Function; | ||||
| 
 | ||||
| 	// Voice | ||||
| 
 | ||||
| 	let speechAutoSend = false; | ||||
| 	let responseAutoPlayback = false; | ||||
| 
 | ||||
| 	let engines = ['', 'openai']; | ||||
| 	let engine = ''; | ||||
| 
 | ||||
|  | @ -33,9 +37,22 @@ | |||
| 		}, 100); | ||||
| 	}; | ||||
| 
 | ||||
| 	const toggleResponseAutoPlayback = async () => { | ||||
| 		responseAutoPlayback = !responseAutoPlayback; | ||||
| 		saveSettings({ responseAutoPlayback: responseAutoPlayback }); | ||||
| 	}; | ||||
| 
 | ||||
| 	const toggleSpeechAutoSend = async () => { | ||||
| 		speechAutoSend = !speechAutoSend; | ||||
| 		saveSettings({ speechAutoSend: speechAutoSend }); | ||||
| 	}; | ||||
| 
 | ||||
| 	onMount(async () => { | ||||
| 		let settings = JSON.parse(localStorage.getItem('settings') ?? '{}'); | ||||
| 
 | ||||
| 		speechAutoSend = settings.speechAutoSend ?? false; | ||||
| 		responseAutoPlayback = settings.responseAutoPlayback ?? false; | ||||
| 
 | ||||
| 		engine = settings?.speech?.engine ?? ''; | ||||
| 		speaker = settings?.speech?.speaker ?? ''; | ||||
| 
 | ||||
|  | @ -60,11 +77,14 @@ | |||
| 	}} | ||||
| > | ||||
| 	<div class=" space-y-3"> | ||||
| 		<div> | ||||
| 			<div class=" mb-1 text-sm font-medium">TTS Settings</div> | ||||
| 
 | ||||
| 			<div class=" py-0.5 flex w-full justify-between"> | ||||
| 			<div class=" self-center text-sm font-medium">Speech Engine</div> | ||||
| 				<div class=" self-center text-xs font-medium">Speech Engine</div> | ||||
| 				<div class="flex items-center relative"> | ||||
| 					<select | ||||
| 					class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right" | ||||
| 						class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right" | ||||
| 						bind:value={engine} | ||||
| 						placeholder="Select a mode" | ||||
| 						on:change={(e) => { | ||||
|  | @ -83,6 +103,43 @@ | |||
| 				</div> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div class=" py-0.5 flex w-full justify-between"> | ||||
| 				<div class=" self-center text-xs font-medium">Voice Input Auto-Send</div> | ||||
| 
 | ||||
| 				<button | ||||
| 					class="p-1 px-3 text-xs flex rounded transition" | ||||
| 					on:click={() => { | ||||
| 						toggleSpeechAutoSend(); | ||||
| 					}} | ||||
| 					type="button" | ||||
| 				> | ||||
| 					{#if speechAutoSend === true} | ||||
| 						<span class="ml-2 self-center">On</span> | ||||
| 					{:else} | ||||
| 						<span class="ml-2 self-center">Off</span> | ||||
| 					{/if} | ||||
| 				</button> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div class=" py-0.5 flex w-full justify-between"> | ||||
| 				<div class=" self-center text-xs font-medium">TTS Automatic Playback</div> | ||||
| 
 | ||||
| 				<button | ||||
| 					class="p-1 px-3 text-xs flex rounded transition" | ||||
| 					on:click={() => { | ||||
| 						toggleResponseAutoPlayback(); | ||||
| 					}} | ||||
| 					type="button" | ||||
| 				> | ||||
| 					{#if responseAutoPlayback === true} | ||||
| 						<span class="ml-2 self-center">On</span> | ||||
| 					{:else} | ||||
| 						<span class="ml-2 self-center">Off</span> | ||||
| 					{/if} | ||||
| 				</button> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<hr class=" dark:border-gray-700" /> | ||||
| 
 | ||||
| 		{#if engine === ''} | ||||
|  |  | |||
|  | @ -448,6 +448,11 @@ | |||
| 									if ($settings.responseAutoCopy) { | ||||
| 										copyToClipboard(responseMessage.content); | ||||
| 									} | ||||
| 
 | ||||
| 									if ($settings.responseAutoPlayback) { | ||||
| 										await tick(); | ||||
| 										document.getElementById(`speak-button-${responseMessage.id}`)?.click(); | ||||
| 									} | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
|  | @ -633,6 +638,11 @@ | |||
| 					copyToClipboard(responseMessage.content); | ||||
| 				} | ||||
| 
 | ||||
| 				if ($settings.responseAutoPlayback) { | ||||
| 					await tick(); | ||||
| 					document.getElementById(`speak-button-${responseMessage.id}`)?.click(); | ||||
| 				} | ||||
| 
 | ||||
| 				if (autoScroll) { | ||||
| 					window.scrollTo({ top: document.body.scrollHeight }); | ||||
| 				} | ||||
|  |  | |||
|  | @ -462,6 +462,11 @@ | |||
| 									if ($settings.responseAutoCopy) { | ||||
| 										copyToClipboard(responseMessage.content); | ||||
| 									} | ||||
| 
 | ||||
| 									if ($settings.responseAutoPlayback) { | ||||
| 										await tick(); | ||||
| 										document.getElementById(`speak-button-${responseMessage.id}`)?.click(); | ||||
| 									} | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
|  | @ -647,6 +652,11 @@ | |||
| 					copyToClipboard(responseMessage.content); | ||||
| 				} | ||||
| 
 | ||||
| 				if ($settings.responseAutoPlayback) { | ||||
| 					await tick(); | ||||
| 					document.getElementById(`speak-button-${responseMessage.id}`)?.click(); | ||||
| 				} | ||||
| 
 | ||||
| 				if (autoScroll) { | ||||
| 					window.scrollTo({ top: document.body.scrollHeight }); | ||||
| 				} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Jaeryang Baek
						Timothy Jaeryang Baek