Merge branch 'staging' into persona-improvements

This commit is contained in:
Wolfsblvt
2025-02-22 19:23:59 +01:00
150 changed files with 5337 additions and 2596 deletions

View File

@ -59,7 +59,7 @@ import { autoSelectPersona, isPersonaLocked, retriggerFirstMessageOnEmptyChat, s
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';
import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, regexFromString, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
import { registerVariableCommands, resolveVariable } from './variables.js';
import { background_settings } from './backgrounds.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
@ -76,6 +76,7 @@ import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakC
import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
import { t } from './i18n.js';
import { accountStorage } from './util/AccountStorage.js';
export {
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
};
@ -283,7 +284,6 @@ export function initDefaultSlashCommands() {
description: 'Character name - or unique character identifier (avatar key)',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: commonEnumProviders.characters('character'),
forceEnum: false,
}),
],
helpString: `
@ -322,7 +322,6 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.characters('character'),
forceEnum: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'avatar',
@ -566,7 +565,6 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.characters('all'),
forceEnum: true,
}),
],
helpString: 'Opens up a chat with the character or group by its name',
@ -781,6 +779,57 @@ export function initDefaultSlashCommands() {
],
helpString: 'Unhides a message from the prompt.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'member-get',
aliases: ['getmember', 'memberget'],
callback: (async ({ field = 'name' }, arg) => {
if (!selected_group) {
toastr.warning('Cannot run /member-get command outside of a group chat.');
return '';
}
if (field === '') {
toastr.warning('\'/member-get field=\' argument required!');
return '';
}
field = field.toString();
arg = arg.toString();
if (!['name', 'index', 'id', 'avatar'].includes(field)) {
toastr.warning('\'/member-get field=\' argument required!');
return '';
}
const isId = !isNaN(parseInt(arg));
const groupMember = findGroupMemberId(arg, true);
if (!groupMember) {
toastr.warn(`No group member found using ${isId ? 'id' : 'string'} ${arg}`);
return '';
}
return groupMember[field];
}),
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'field',
description: 'Whether to retrieve the name, index, id, or avatar.',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
defaultValue: 'name',
enumList: [
new SlashCommandEnumValue('name', 'Character name'),
new SlashCommandEnumValue('index', 'Group member index'),
new SlashCommandEnumValue('avatar', 'Character avatar'),
new SlashCommandEnumValue('id', 'Character index'),
],
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'member index (starts with 0), name, or avatar',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.groupMembers(),
}),
],
helpString: 'Retrieves a group member\'s name, index, id, or avatar.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'member-disable',
callback: disableGroupMemberCallback,
@ -891,7 +940,8 @@ export function initDefaultSlashCommands() {
helpString: 'Moves a group member down in the group chat list.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'peek',
name: 'member-peek',
aliases: ['peek', 'memberpeek', 'peekmember'],
callback: peekCallback,
unnamedArgumentList: [
SlashCommandArgument.fromProps({
@ -1057,7 +1107,6 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'System',
enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],
forceEnum: false,
}),
new SlashCommandNamedArgument(
'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false,
@ -1951,7 +2000,7 @@ export function initDefaultSlashCommands() {
returns: 'uppercase string',
unnamedArgumentList: [
new SlashCommandArgument(
'string', [ARGUMENT_TYPE.STRING], true, false,
'text to affect', [ARGUMENT_TYPE.STRING], true, false,
),
],
helpString: 'Converts the provided string to uppercase.',
@ -1963,7 +2012,7 @@ export function initDefaultSlashCommands() {
returns: 'lowercase string',
unnamedArgumentList: [
new SlashCommandArgument(
'string', [ARGUMENT_TYPE.STRING], true, false,
'text to affect', [ARGUMENT_TYPE.STRING], true, false,
),
],
helpString: 'Converts the provided string to lowercase.',
@ -1983,7 +2032,7 @@ export function initDefaultSlashCommands() {
],
unnamedArgumentList: [
new SlashCommandArgument(
'string', [ARGUMENT_TYPE.STRING], true, false,
'text to affect', [ARGUMENT_TYPE.STRING], true, false,
),
],
helpString: `
@ -2047,6 +2096,62 @@ export function initDefaultSlashCommands() {
return '';
},
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'replace',
aliases: ['re'],
callback: (async ({ mode = 'literal', pattern, replacer = '' }, text) => {
if (pattern === '')
throw new Error('Argument of \'pattern=\' cannot be empty');
switch (mode) {
case 'literal':
return text.replaceAll(pattern, replacer);
case 'regex':
return text.replace(regexFromString(pattern), replacer);
default:
throw new Error('Invalid \'/replace mode=\' argument specified!');
}
}),
returns: 'replaced text',
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'mode',
description: 'Replaces occurrence(s) of a pattern',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'literal',
enumList: ['literal', 'regex'],
}),
new SlashCommandNamedArgument(
'pattern', 'pattern to search with', [ARGUMENT_TYPE.STRING], true, false,
),
new SlashCommandNamedArgument(
'replacer', 'replacement text for matches', [ARGUMENT_TYPE.STRING], false, false, '',
),
],
unnamedArgumentList: [
new SlashCommandArgument(
'text to affect', [ARGUMENT_TYPE.STRING], true, false,
),
],
helpString: `
<div>
Replaces text within the provided string based on the pattern.
</div>
<div>
If <code>mode</code> is <code>literal</code> (or omitted), <code>pattern</code> is a literal search string (case-sensitive).<br />
If <code>mode</code> is <code>regex</code>, <code>pattern</code> is parsed as an ECMAScript Regular Expression.<br />
The <code>replacer</code> replaces based on the <code>pattern</code> in the input text.<br />
If <code>replacer</code> is omitted, the replacement(s) will be an empty string.<br />
</div>
<div>
<strong>Example:</strong>
<pre>/let x Blue house and blue car || </pre>
<pre>/replace pattern="blue" {{var::x}} | /echo |/# Blue house and car ||</pre>
<pre>/replace pattern="blue" replacer="red" {{var::x}} | /echo |/# Blue house and red car ||</pre>
<pre>/replace mode=regex pattern="/blue/i" replacer="red" {{var::x}} | /echo |/# red house and blue car ||</pre>
<pre>/replace mode=regex pattern="/blue/gi" replacer="red" {{var::x}} | /echo |/# red house and red car ||</pre>
</div>
`,
}));
registerVariableCommands();
}
@ -3039,7 +3144,7 @@ function performGroupMemberAction(chid, action) {
async function disableGroupMemberCallback(_, arg) {
if (!selected_group) {
toastr.warning('Cannot run /disable command outside of a group chat.');
toastr.warning('Cannot run /member-disable command outside of a group chat.');
return '';
}
@ -3056,7 +3161,7 @@ async function disableGroupMemberCallback(_, arg) {
async function enableGroupMemberCallback(_, arg) {
if (!selected_group) {
toastr.warning('Cannot run /enable command outside of a group chat.');
toastr.warning('Cannot run /member-enable command outside of a group chat.');
return '';
}
@ -3073,7 +3178,7 @@ async function enableGroupMemberCallback(_, arg) {
async function moveGroupMemberUpCallback(_, arg) {
if (!selected_group) {
toastr.warning('Cannot run /memberup command outside of a group chat.');
toastr.warning('Cannot run /member-up command outside of a group chat.');
return '';
}
@ -3090,7 +3195,7 @@ async function moveGroupMemberUpCallback(_, arg) {
async function moveGroupMemberDownCallback(_, arg) {
if (!selected_group) {
toastr.warning('Cannot run /memberdown command outside of a group chat.');
toastr.warning('Cannot run /member-down command outside of a group chat.');
return '';
}
@ -3107,12 +3212,12 @@ async function moveGroupMemberDownCallback(_, arg) {
async function peekCallback(_, arg) {
if (!selected_group) {
toastr.warning('Cannot run /peek command outside of a group chat.');
toastr.warning('Cannot run /member-peek command outside of a group chat.');
return '';
}
if (is_group_generating) {
toastr.warning('Cannot run /peek command while the group reply is generating.');
toastr.warning('Cannot run /member-peek command while the group reply is generating.');
return '';
}
@ -3129,12 +3234,7 @@ async function peekCallback(_, arg) {
async function removeGroupMemberCallback(_, arg) {
if (!selected_group) {
toastr.warning('Cannot run /memberremove command outside of a group chat.');
return '';
}
if (is_group_generating) {
toastr.warning('Cannot run /memberremove command while the group reply is generating.');
toastr.warning('Cannot run /member-remove command outside of a group chat.');
return '';
}
@ -3242,12 +3342,7 @@ function findPersonaByName(name) {
}
async function sendUserMessageCallback(args, text) {
if (!text) {
toastr.warning('You must specify text to send');
return;
}
text = text.trim();
text = String(text ?? '').trim();
const compact = isTrueBoolean(args?.compact);
const bias = extractMessageBias(text);
@ -3562,24 +3657,18 @@ export function getNameAndAvatarForMessage(character, name = null) {
}
export async function sendMessageAs(args, text) {
if (!text) {
toastr.warning('You must specify text to send as');
return '';
}
let name = args.name?.trim();
let mesText;
if (!name) {
const namelessWarningKey = 'sendAsNamelessWarningShown';
if (localStorage.getItem(namelessWarningKey) !== 'true') {
if (accountStorage.getItem(namelessWarningKey) !== 'true') {
toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 });
localStorage.setItem(namelessWarningKey, 'true');
accountStorage.setItem(namelessWarningKey, 'true');
}
name = name2;
}
mesText = text.trim();
let mesText = String(text ?? '').trim();
// Requires a regex check after the slash command is pushed to output
mesText = getRegexedString(mesText, regex_placement.SLASH_COMMAND, { characterOverride: name });
@ -3657,11 +3746,7 @@ export async function sendMessageAs(args, text) {
}
export async function sendNarratorMessage(args, text) {
if (!text) {
toastr.warning('You must specify text to send');
return '';
}
text = String(text ?? '');
const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
// Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias(text);
@ -3752,18 +3837,13 @@ export async function promptQuietForLoudResponse(who, text) {
}
async function sendCommentMessage(args, text) {
if (!text) {
toastr.warning('You must specify text to send');
return '';
}
const compact = isTrueBoolean(args?.compact);
const message = {
name: COMMENT_NAME_DEFAULT,
is_user: false,
is_system: true,
send_date: getMessageTimeStamp(),
mes: substituteParams(text.trim()),
mes: substituteParams(String(text ?? '').trim()),
force_avatar: comment_avatar,
extra: {
type: system_message_types.COMMENT,