diff --git a/public/script.js b/public/script.js
index 2523ff9ba..38f069968 100644
--- a/public/script.js
+++ b/public/script.js
@@ -154,7 +154,7 @@ import {
isValidUrl,
ensureImageFormatSupported,
flashHighlight,
- checkOverwriteExistingData,
+ isTrueBoolean,
} from './scripts/utils.js';
import { debounce_timeout } from './scripts/constants.js';
@@ -232,7 +232,7 @@ import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
import { ScraperManager } from './scripts/scrapers.js';
import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';
-import { ARGUMENT_TYPE, SlashCommandArgument } from './scripts/slash-commands/SlashCommandArgument.js';
+import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';
import { SlashCommandBrowser } from './scripts/slash-commands/SlashCommandBrowser.js';
import { initCustomSelectedSamplers, validateDisabledSamplers } from './scripts/samplerSelect.js';
import { DragAndDropHandler } from './scripts/dragdrop.js';
@@ -8446,9 +8446,30 @@ async function importFromURL(items, files) {
}
}
-async function doImpersonate(_, prompt) {
- $('#send_textarea').val('');
- $('#option_impersonate').trigger('click', { fromSlashCommand: true, additionalPrompt: prompt });
+async function doImpersonate(args, prompt) {
+ const options = prompt?.trim() ? { quiet_prompt: prompt.trim(), quietToLoud: true } : {};
+ const shouldAwait = isTrueBoolean(args?.await);
+ const outerPromise = new Promise((outerResolve) => setTimeout(async () => {
+ try {
+ await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
+ } catch {
+ console.warn('Timeout waiting for generation unlock');
+ toastr.warning('Cannot run /impersonate command while the reply is being generated.');
+ return '';
+ }
+
+ // Prevent generate recursion
+ $('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));
+
+ outerResolve(new Promise(innerResolve => setTimeout(() => innerResolve(Generate('impersonate', options)), 1)));
+ }, 1));
+
+ if (shouldAwait) {
+ const innerPromise = await outerPromise;
+ await innerPromise;
+ }
+
+ return '';
}
async function doDeleteChat() {
@@ -8764,6 +8785,16 @@ jQuery(async function () {
name: 'impersonate',
callback: doImpersonate,
aliases: ['imp'],
+ namedArgumentList: [
+ new SlashCommandNamedArgument(
+ 'await',
+ 'Whether to await for the triggered generation before continuing',
+ [ARGUMENT_TYPE.BOOLEAN],
+ false,
+ false,
+ 'false',
+ ),
+ ],
unnamedArgumentList: [
new SlashCommandArgument(
'prompt', [ARGUMENT_TYPE.STRING], false,
@@ -8773,6 +8804,9 @@ jQuery(async function () {
Calls an impersonation response, with an optional additional prompt.
+
+ If await=true
named argument is passed, the command will wait for the impersonation to end before continuing.
+
Example:
diff --git a/public/scripts/chats.js b/public/scripts/chats.js
index 5021858b1..32a188c20 100644
--- a/public/scripts/chats.js
+++ b/public/scripts/chats.js
@@ -1032,43 +1032,61 @@ async function openAttachmentManager() {
localStorage.setItem('DataBank_sortOrder', sortOrder);
renderAttachments();
});
- template.find('.bulkActionDelete').on('click', async () => {
- const selectedAttachments = document.querySelectorAll('.attachmentListItemCheckboxContainer .attachmentListItemCheckbox:checked');
+ function handleBulkAction(action) {
+ return async () => {
+ const selectedAttachments = document.querySelectorAll('.attachmentListItemCheckboxContainer .attachmentListItemCheckbox:checked');
- if (selectedAttachments.length === 0) {
- toastr.info('No attachments selected.', 'Data Bank');
- return;
- }
-
- const confirm = await callGenericPopup('Are you sure you want to delete the selected attachments?', POPUP_TYPE.CONFIRM);
-
- if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
- return;
- }
-
- const attachments = getDataBankAttachments();
- selectedAttachments.forEach(async (checkbox) => {
- const listItem = checkbox.closest('.attachmentListItem');
- if (!(listItem instanceof HTMLElement)) {
+ if (selectedAttachments.length === 0) {
+ toastr.info('No attachments selected.', 'Data Bank');
return;
}
- const url = listItem.dataset.attachmentUrl;
- const source = listItem.dataset.attachmentSource;
- const attachment = attachments.find(a => a.url === url);
- if (!attachment) {
- return;
- }
- await deleteAttachment(attachment, source, () => {}, false);
- });
- document.querySelectorAll('.attachmentListItemCheckbox, .attachmentsBulkEditCheckbox').forEach(checkbox => {
- if (checkbox instanceof HTMLInputElement) {
- checkbox.checked = false;
+ if (action.confirmMessage) {
+ const confirm = await callGenericPopup(action.confirmMessage, POPUP_TYPE.CONFIRM);
+ if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
+ return;
+ }
}
- });
- await renderAttachments();
- });
+ const includeDisabled = true;
+ const attachments = getDataBankAttachments(includeDisabled);
+ selectedAttachments.forEach(async (checkbox) => {
+ const listItem = checkbox.closest('.attachmentListItem');
+ if (!(listItem instanceof HTMLElement)) {
+ return;
+ }
+ const url = listItem.dataset.attachmentUrl;
+ const source = listItem.dataset.attachmentSource;
+ const attachment = attachments.find(a => a.url === url);
+ if (!attachment) {
+ return;
+ }
+ await action.perform(attachment, source);
+ });
+
+ document.querySelectorAll('.attachmentListItemCheckbox, .attachmentsBulkEditCheckbox').forEach(checkbox => {
+ if (checkbox instanceof HTMLInputElement) {
+ checkbox.checked = false;
+ }
+ });
+
+ await renderAttachments();
+ };
+ }
+
+ template.find('.bulkActionDisable').on('click', handleBulkAction({
+ perform: (attachment) => disableAttachment(attachment, () => { }),
+ }));
+
+ template.find('.bulkActionEnable').on('click', handleBulkAction({
+ perform: (attachment) => enableAttachment(attachment, () => { }),
+ }));
+
+ template.find('.bulkActionDelete').on('click', handleBulkAction({
+ confirmMessage: 'Are you sure you want to delete the selected attachments?',
+ perform: async (attachment, source) => await deleteAttachment(attachment, source, () => { }, false),
+ }));
+
template.find('.bulkActionSelectAll').on('click', () => {
$('.attachmentListItemCheckbox:visible').each((_, checkbox) => {
if (checkbox instanceof HTMLInputElement) {
diff --git a/public/scripts/extensions/attachments/manager.html b/public/scripts/extensions/attachments/manager.html
index dad00cac8..2ae3009be 100644
--- a/public/scripts/extensions/attachments/manager.html
+++ b/public/scripts/extensions/attachments/manager.html
@@ -53,6 +53,14 @@
Select None
+
+