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 });
}