forked from open-webui/open-webui
		
	feat: response autocopy
This commit is contained in:
		
							parent
							
								
									47b7b63791
								
							
						
					
					
						commit
						6de9db6772
					
				
					 4 changed files with 186 additions and 0 deletions
				
			
		|  | @ -52,6 +52,7 @@ | ||||||
| 	// Addons | 	// Addons | ||||||
| 	let titleAutoGenerate = true; | 	let titleAutoGenerate = true; | ||||||
| 	let speechAutoSend = false; | 	let speechAutoSend = false; | ||||||
|  | 	let responseAutoCopy = false; | ||||||
| 
 | 
 | ||||||
| 	let gravatarEmail = ''; | 	let gravatarEmail = ''; | ||||||
| 	let OPENAI_API_KEY = ''; | 	let OPENAI_API_KEY = ''; | ||||||
|  | @ -123,6 +124,28 @@ | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
|  | 	const toggleResponseAutoCopy = async () => { | ||||||
|  | 		const permission = await navigator.clipboard | ||||||
|  | 			.readText() | ||||||
|  | 			.then(() => { | ||||||
|  | 				return 'granted'; | ||||||
|  | 			}) | ||||||
|  | 			.catch(() => { | ||||||
|  | 				return ''; | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
|  | 		console.log(permission); | ||||||
|  | 
 | ||||||
|  | 		if (permission === 'granted') { | ||||||
|  | 			responseAutoCopy = !responseAutoCopy; | ||||||
|  | 			saveSettings({ responseAutoCopy: responseAutoCopy }); | ||||||
|  | 		} else { | ||||||
|  | 			toast.error( | ||||||
|  | 				'Clipboard write permission denied. Please check your browser settings to grant the necessary access.' | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
| 	const toggleAuthHeader = async () => { | 	const toggleAuthHeader = async () => { | ||||||
| 		authEnabled = !authEnabled; | 		authEnabled = !authEnabled; | ||||||
| 	}; | 	}; | ||||||
|  | @ -319,6 +342,8 @@ | ||||||
| 		console.log(settings); | 		console.log(settings); | ||||||
| 
 | 
 | ||||||
| 		theme = localStorage.theme ?? 'dark'; | 		theme = localStorage.theme ?? 'dark'; | ||||||
|  | 		notificationEnabled = settings.notificationEnabled ?? false; | ||||||
|  | 
 | ||||||
| 		API_BASE_URL = settings.API_BASE_URL ?? OLLAMA_API_BASE_URL; | 		API_BASE_URL = settings.API_BASE_URL ?? OLLAMA_API_BASE_URL; | ||||||
| 		system = settings.system ?? ''; | 		system = settings.system ?? ''; | ||||||
| 
 | 
 | ||||||
|  | @ -334,6 +359,8 @@ | ||||||
| 
 | 
 | ||||||
| 		titleAutoGenerate = settings.titleAutoGenerate ?? true; | 		titleAutoGenerate = settings.titleAutoGenerate ?? true; | ||||||
| 		speechAutoSend = settings.speechAutoSend ?? false; | 		speechAutoSend = settings.speechAutoSend ?? false; | ||||||
|  | 		responseAutoCopy = settings.responseAutoCopy ?? false; | ||||||
|  | 
 | ||||||
| 		gravatarEmail = settings.gravatarEmail ?? ''; | 		gravatarEmail = settings.gravatarEmail ?? ''; | ||||||
| 		OPENAI_API_KEY = settings.OPENAI_API_KEY ?? ''; | 		OPENAI_API_KEY = settings.OPENAI_API_KEY ?? ''; | ||||||
| 
 | 
 | ||||||
|  | @ -887,6 +914,28 @@ | ||||||
| 										</button> | 										</button> | ||||||
| 									</div> | 									</div> | ||||||
| 								</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> | ||||||
|  | 
 | ||||||
|  | 										<button | ||||||
|  | 											class="p-1 px-3 text-xs flex rounded transition" | ||||||
|  | 											on:click={() => { | ||||||
|  | 												toggleResponseAutoCopy(); | ||||||
|  | 											}} | ||||||
|  | 											type="button" | ||||||
|  | 										> | ||||||
|  | 											{#if responseAutoCopy === true} | ||||||
|  | 												<span class="ml-2 self-center">On</span> | ||||||
|  | 											{:else} | ||||||
|  | 												<span class="ml-2 self-center">Off</span> | ||||||
|  | 											{/if} | ||||||
|  | 										</button> | ||||||
|  | 									</div> | ||||||
|  | 								</div> | ||||||
| 							</div> | 							</div> | ||||||
| 
 | 
 | ||||||
| 							<hr class=" dark:border-gray-700" /> | 							<hr class=" dark:border-gray-700" /> | ||||||
|  |  | ||||||
|  | @ -65,3 +65,38 @@ export const getGravatarURL = (email) => { | ||||||
| 	// Grab the actual image URL
 | 	// Grab the actual image URL
 | ||||||
| 	return `https://www.gravatar.com/avatar/${hash}`; | 	return `https://www.gravatar.com/avatar/${hash}`; | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | const copyToClipboard = (text) => { | ||||||
|  | 	if (!navigator.clipboard) { | ||||||
|  | 		var textArea = document.createElement('textarea'); | ||||||
|  | 		textArea.value = text; | ||||||
|  | 
 | ||||||
|  | 		// Avoid scrolling to bottom
 | ||||||
|  | 		textArea.style.top = '0'; | ||||||
|  | 		textArea.style.left = '0'; | ||||||
|  | 		textArea.style.position = 'fixed'; | ||||||
|  | 
 | ||||||
|  | 		document.body.appendChild(textArea); | ||||||
|  | 		textArea.focus(); | ||||||
|  | 		textArea.select(); | ||||||
|  | 
 | ||||||
|  | 		try { | ||||||
|  | 			var successful = document.execCommand('copy'); | ||||||
|  | 			var msg = successful ? 'successful' : 'unsuccessful'; | ||||||
|  | 			console.log('Fallback: Copying text command was ' + msg); | ||||||
|  | 		} catch (err) { | ||||||
|  | 			console.error('Fallback: Oops, unable to copy', err); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		document.body.removeChild(textArea); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	navigator.clipboard.writeText(text).then( | ||||||
|  | 		function () { | ||||||
|  | 			console.log('Async: Copying to clipboard was successful!'); | ||||||
|  | 		}, | ||||||
|  | 		function (err) { | ||||||
|  | 			console.error('Async: Could not copy text: ', err); | ||||||
|  | 		} | ||||||
|  | 	); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | @ -88,6 +88,41 @@ | ||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
|  | 	const copyToClipboard = (text) => { | ||||||
|  | 		if (!navigator.clipboard) { | ||||||
|  | 			var textArea = document.createElement('textarea'); | ||||||
|  | 			textArea.value = text; | ||||||
|  | 
 | ||||||
|  | 			// Avoid scrolling to bottom | ||||||
|  | 			textArea.style.top = '0'; | ||||||
|  | 			textArea.style.left = '0'; | ||||||
|  | 			textArea.style.position = 'fixed'; | ||||||
|  | 
 | ||||||
|  | 			document.body.appendChild(textArea); | ||||||
|  | 			textArea.focus(); | ||||||
|  | 			textArea.select(); | ||||||
|  | 
 | ||||||
|  | 			try { | ||||||
|  | 				var successful = document.execCommand('copy'); | ||||||
|  | 				var msg = successful ? 'successful' : 'unsuccessful'; | ||||||
|  | 				console.log('Fallback: Copying text command was ' + msg); | ||||||
|  | 			} catch (err) { | ||||||
|  | 				console.error('Fallback: Oops, unable to copy', err); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			document.body.removeChild(textArea); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		navigator.clipboard.writeText(text).then( | ||||||
|  | 			function () { | ||||||
|  | 				console.log('Async: Copying to clipboard was successful!'); | ||||||
|  | 			}, | ||||||
|  | 			function (err) { | ||||||
|  | 				console.error('Async: Could not copy text: ', err); | ||||||
|  | 			} | ||||||
|  | 		); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
| 	////////////////////////// | 	////////////////////////// | ||||||
| 	// Ollama functions | 	// Ollama functions | ||||||
| 	////////////////////////// | 	////////////////////////// | ||||||
|  | @ -236,6 +271,10 @@ | ||||||
| 										} | 										} | ||||||
| 									); | 									); | ||||||
| 								} | 								} | ||||||
|  | 
 | ||||||
|  | 								if ($settings.responseAutoCopy) { | ||||||
|  | 									copyToClipboard(responseMessage.content); | ||||||
|  | 								} | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
|  | @ -440,6 +479,18 @@ | ||||||
| 				stopResponseFlag = false; | 				stopResponseFlag = false; | ||||||
| 
 | 
 | ||||||
| 				await tick(); | 				await tick(); | ||||||
|  | 
 | ||||||
|  | 				if ($settings.notificationEnabled && !document.hasFocus()) { | ||||||
|  | 					const notification = new Notification(`OpenAI ${model}`, { | ||||||
|  | 						body: responseMessage.content, | ||||||
|  | 						icon: '/favicon.png' | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if ($settings.responseAutoCopy) { | ||||||
|  | 					copyToClipboard(responseMessage.content); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
| 				if (autoScroll) { | 				if (autoScroll) { | ||||||
| 					window.scrollTo({ top: document.body.scrollHeight }); | 					window.scrollTo({ top: document.body.scrollHeight }); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -102,6 +102,41 @@ | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
|  | 	const copyToClipboard = (text) => { | ||||||
|  | 		if (!navigator.clipboard) { | ||||||
|  | 			var textArea = document.createElement('textarea'); | ||||||
|  | 			textArea.value = text; | ||||||
|  | 
 | ||||||
|  | 			// Avoid scrolling to bottom | ||||||
|  | 			textArea.style.top = '0'; | ||||||
|  | 			textArea.style.left = '0'; | ||||||
|  | 			textArea.style.position = 'fixed'; | ||||||
|  | 
 | ||||||
|  | 			document.body.appendChild(textArea); | ||||||
|  | 			textArea.focus(); | ||||||
|  | 			textArea.select(); | ||||||
|  | 
 | ||||||
|  | 			try { | ||||||
|  | 				var successful = document.execCommand('copy'); | ||||||
|  | 				var msg = successful ? 'successful' : 'unsuccessful'; | ||||||
|  | 				console.log('Fallback: Copying text command was ' + msg); | ||||||
|  | 			} catch (err) { | ||||||
|  | 				console.error('Fallback: Oops, unable to copy', err); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			document.body.removeChild(textArea); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		navigator.clipboard.writeText(text).then( | ||||||
|  | 			function () { | ||||||
|  | 				console.log('Async: Copying to clipboard was successful!'); | ||||||
|  | 			}, | ||||||
|  | 			function (err) { | ||||||
|  | 				console.error('Async: Could not copy text: ', err); | ||||||
|  | 			} | ||||||
|  | 		); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
| 	////////////////////////// | 	////////////////////////// | ||||||
| 	// Ollama functions | 	// Ollama functions | ||||||
| 	////////////////////////// | 	////////////////////////// | ||||||
|  | @ -250,6 +285,10 @@ | ||||||
| 										} | 										} | ||||||
| 									); | 									); | ||||||
| 								} | 								} | ||||||
|  | 
 | ||||||
|  | 								if ($settings.responseAutoCopy) { | ||||||
|  | 									copyToClipboard(responseMessage.content); | ||||||
|  | 								} | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
|  | @ -454,6 +493,18 @@ | ||||||
| 				stopResponseFlag = false; | 				stopResponseFlag = false; | ||||||
| 
 | 
 | ||||||
| 				await tick(); | 				await tick(); | ||||||
|  | 
 | ||||||
|  | 				if ($settings.notificationEnabled && !document.hasFocus()) { | ||||||
|  | 					const notification = new Notification(`OpenAI ${model}`, { | ||||||
|  | 						body: responseMessage.content, | ||||||
|  | 						icon: '/favicon.png' | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if ($settings.responseAutoCopy) { | ||||||
|  | 					copyToClipboard(responseMessage.content); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
| 				if (autoScroll) { | 				if (autoScroll) { | ||||||
| 					window.scrollTo({ top: document.body.scrollHeight }); | 					window.scrollTo({ top: document.body.scrollHeight }); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek