diff --git a/src/lib/components/chat/SettingsModal.svelte b/src/lib/components/chat/SettingsModal.svelte index d7ee57a5..67617218 100644 --- a/src/lib/components/chat/SettingsModal.svelte +++ b/src/lib/components/chat/SettingsModal.svelte @@ -52,6 +52,7 @@ // Addons let titleAutoGenerate = true; let speechAutoSend = false; + let responseAutoCopy = false; let gravatarEmail = ''; 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 () => { authEnabled = !authEnabled; }; @@ -319,6 +342,8 @@ console.log(settings); theme = localStorage.theme ?? 'dark'; + notificationEnabled = settings.notificationEnabled ?? false; + API_BASE_URL = settings.API_BASE_URL ?? OLLAMA_API_BASE_URL; system = settings.system ?? ''; @@ -334,6 +359,8 @@ titleAutoGenerate = settings.titleAutoGenerate ?? true; speechAutoSend = settings.speechAutoSend ?? false; + responseAutoCopy = settings.responseAutoCopy ?? false; + gravatarEmail = settings.gravatarEmail ?? ''; OPENAI_API_KEY = settings.OPENAI_API_KEY ?? ''; @@ -887,6 +914,28 @@ + +
+
+
+ Response AutoCopy to Clipboard +
+ + +
+

diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 22096994..2d9f1b31 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -65,3 +65,38 @@ export const getGravatarURL = (email) => { // Grab the actual image URL 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); + } + ); +}; diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 88b4f4cc..d0b83b80 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -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 ////////////////////////// @@ -236,6 +271,10 @@ } ); } + + if ($settings.responseAutoCopy) { + copyToClipboard(responseMessage.content); + } } } } @@ -440,6 +479,18 @@ stopResponseFlag = false; 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) { window.scrollTo({ top: document.body.scrollHeight }); } diff --git a/src/routes/(app)/c/[id]/+page.svelte b/src/routes/(app)/c/[id]/+page.svelte index 9b1cf275..bf7207fb 100644 --- a/src/routes/(app)/c/[id]/+page.svelte +++ b/src/routes/(app)/c/[id]/+page.svelte @@ -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 ////////////////////////// @@ -250,6 +285,10 @@ } ); } + + if ($settings.responseAutoCopy) { + copyToClipboard(responseMessage.content); + } } } } @@ -454,6 +493,18 @@ stopResponseFlag = false; 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) { window.scrollTo({ top: document.body.scrollHeight }); }