Enhanced /persona-sync Command with Range, Name Filter, and Quiet Mode (#5460)
* Add range and silent parameters to /persona-sync command with optional confirmation suppression - Add optional start/end range parameters to syncUserNameToPersona() function - Add silent parameter to suppress confirmation popup when needed - Update /persona-sync slash command to accept range argument (index or range string) and named silent argument - Parse range using stringToRange() utility, default to full chat if not provided - Update confirmation message to reflect whether syncing all messages or specified * Add `from` named argument to /persona-sync command for filtering by persona name - Add `from` named argument to filter messages by persona name (case-insensitive) - Rename `silent` argument to `quiet` with inverted default (true) for consistency - Add userMessageNamesEnumProvider() to provide autocomplete for existing user message names in chat - Update syncUserNameToPersona() to accept nameFilter parameter and filter messages accordingly - Update confirmation message to reflect name filtering when * Add async/await wrapper to sync_name_button click handler for proper promise handling Function now has arguments, so using just the function as the event is shown as wrong usage * Post-merge imports fix * Use canonical command name in examples --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
+126
-13
@@ -24,7 +24,30 @@ import {
|
||||
} from '../script.js';
|
||||
import { persona_description_positions, power_user } from './power-user.js';
|
||||
import { getTokenCountAsync } from './tokenizers.js';
|
||||
import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler, addLongPressEvent, uuidv4 } from './utils.js';
|
||||
import {
|
||||
PAGINATION_TEMPLATE,
|
||||
clearInfoBlock,
|
||||
debounce,
|
||||
delay,
|
||||
download,
|
||||
ensureImageFormatSupported,
|
||||
flashHighlight,
|
||||
getBase64Async,
|
||||
getCharIndex,
|
||||
isFalseBoolean,
|
||||
isTrueBoolean,
|
||||
onlyUnique,
|
||||
parseJsonFile,
|
||||
setInfoBlock,
|
||||
localizePagination,
|
||||
renderPaginationDropdown,
|
||||
paginationDropdownChangeHandler,
|
||||
addLongPressEvent,
|
||||
stringToRange,
|
||||
sortIgnoreCaseAndAccents,
|
||||
equalsIgnoreCaseAndAccents,
|
||||
uuidv4,
|
||||
} from './utils.js';
|
||||
import { debounce_timeout } from './constants.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
import { groups, selected_group } from './group-chats.js';
|
||||
@@ -36,8 +59,8 @@ import { saveMetadataDebounced } from './extensions.js';
|
||||
import { accountStorage } from './util/AccountStorage.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { isFirefox } from './browser-fixes.js';
|
||||
|
||||
@@ -1760,15 +1783,36 @@ async function onPersonasRestoreInput(e) {
|
||||
$('#personas_restore_input').val('');
|
||||
}
|
||||
|
||||
async function syncUserNameToPersona() {
|
||||
const confirmation = await Popup.show.confirm(t`Are you sure?`, t`All user-sent messages in this chat will be attributed to ${name1}.`);
|
||||
/**
|
||||
* Synchronizes user-sent messages in the chat to the current persona.
|
||||
* @param {object} [options={}] - Optional parameters
|
||||
* @param {number} [options.start=0] - Start index of the message range (inclusive)
|
||||
* @param {number} [options.end=chat.length - 1] - End index of the message range (inclusive)
|
||||
* @param {boolean} [options.quiet=false] - If true, skips the confirmation popup
|
||||
* @param {string} [options.nameFilter=''] - Filter messages by name (case-insensitive)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function syncUserNameToPersona({ start = 0, end = chat.length - 1, quiet = false, nameFilter = '' } = {}) {
|
||||
const isRangeAll = start === 0 && end === chat.length - 1;
|
||||
const hasNameFilter = nameFilter?.trim();
|
||||
const confirmMessage = isRangeAll && !hasNameFilter
|
||||
? t`All user-sent messages in this chat will be attributed to ${name1}.`
|
||||
: isRangeAll && hasNameFilter
|
||||
? t`User-sent messages with name "${nameFilter}" will be attributed to ${name1}.`
|
||||
: !isRangeAll && !hasNameFilter
|
||||
? t`User-sent messages in the specified range will be attributed to ${name1}.`
|
||||
: t`User-sent messages with name "${nameFilter}" in the specified range will be attributed to ${name1}.`;
|
||||
|
||||
if (!confirmation) {
|
||||
return;
|
||||
if (!quiet) {
|
||||
const confirmation = await Popup.show.confirm(t`Are you sure?`, confirmMessage);
|
||||
if (!confirmation) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const mes of chat) {
|
||||
if (mes.is_user) {
|
||||
for (let i = start; i <= end; i++) {
|
||||
const mes = chat[i];
|
||||
if (mes?.is_user && (!hasNameFilter || equalsIgnoreCaseAndAccents(mes.name, nameFilter))) {
|
||||
mes.name = name1;
|
||||
mes.force_avatar = getThumbnailUrl('persona', user_avatar);
|
||||
}
|
||||
@@ -1931,11 +1975,37 @@ async function setNameCallback({ mode = 'all' }, name) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function syncCallback() {
|
||||
$('#sync_name_button').trigger('click');
|
||||
async function syncCallback(args, value) {
|
||||
const range = value ? stringToRange(value, 0, chat.length - 1) : null;
|
||||
|
||||
if (value && !range) {
|
||||
console.warn(`WARN: Invalid range provided for /persona-sync command: ${value}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const quiet = !isFalseBoolean(args?.quiet);
|
||||
const nameFilter = typeof args?.from === 'string' ? args.from.trim() : '';
|
||||
const start = range ? range.start : 0;
|
||||
const end = range ? range.end : chat.length - 1;
|
||||
|
||||
await syncUserNameToPersona({ start, end, quiet, nameFilter });
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all unique user message names in the current chat for enum autocomplete.
|
||||
* @returns {SlashCommandEnumValue[]}
|
||||
*/
|
||||
function userMessageNamesEnumProvider() {
|
||||
return chat
|
||||
.filter(mes => mes.is_user)
|
||||
.map(mes => mes.name)
|
||||
.filter(onlyUnique)
|
||||
.sort(sortIgnoreCaseAndAccents)
|
||||
.map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.persona));
|
||||
}
|
||||
|
||||
function registerPersonaSlashCommands() {
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'persona-lock',
|
||||
@@ -1988,7 +2058,50 @@ function registerPersonaSlashCommands() {
|
||||
name: 'persona-sync',
|
||||
aliases: ['sync'],
|
||||
callback: syncCallback,
|
||||
helpString: 'Syncs the user persona in user-attributed messages in the current chat.',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'from',
|
||||
description: t`only sync messages from a certain persona name`,
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: userMessageNamesEnumProvider,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'quiet',
|
||||
description: t`suppress the confirmation popup`,
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
defaultValue: 'true',
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: t`message index (starts with 0) or range, syncs all user messages if not provided`,
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],
|
||||
defaultValue: '0-{{lastMessageId}}',
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
${t`Syncs the user persona (name and avatar) in user-attributed messages in the current chat.`}
|
||||
</div>
|
||||
<div>
|
||||
${t`If <code>from</code> is set, only messages with that specific persona name will be synced. Useful when multiple personas have been used in the same chat.`}
|
||||
</div>
|
||||
<div>
|
||||
${t`If <code>quiet</code> is set to <code>false</code>, a confirmation popup will be shown before syncing.`}
|
||||
</div>
|
||||
<div>
|
||||
<strong>${t`Examples:`}</strong>
|
||||
<ul>
|
||||
<li><pre><code>/persona-sync</code></pre> ${t`- Sync all user messages`}</li>
|
||||
<li><pre><code>/persona-sync 5</code></pre> ${t`- Sync only message 5`}</li>
|
||||
<li><pre><code>/persona-sync 0-10</code></pre> ${t`- Sync messages 0 through 10`}</li>
|
||||
<li><pre><code>/persona-sync from=OldPersona 0-20</code></pre> ${t`- Sync only messages with name "OldPersona" in range 0-20`}</li>
|
||||
<li><pre><code>/persona-sync quiet=false</code></pre> ${t`- Sync all with confirmation popup`}</li>
|
||||
<li><pre><code>/persona-sync from=TempName quiet=false 5-15</code></pre> ${t`- Sync messages with name "TempName" in range 5-15 with confirmation`}</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -2046,7 +2159,7 @@ export async function initPersonas() {
|
||||
debouncedPersonaSearch(searchQuery);
|
||||
});
|
||||
|
||||
$('#sync_name_button').on('click', syncUserNameToPersona);
|
||||
$('#sync_name_button').on('click', async () => await syncUserNameToPersona());
|
||||
$('#avatar_upload_file').on('change', changeUserAvatar);
|
||||
|
||||
$(document).on('click', '#user_avatar_block .avatar-container', async function () {
|
||||
|
||||
Reference in New Issue
Block a user