mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-13 18:40:11 +01:00
Refactor: move persona slash commands
This commit is contained in:
parent
49d1a16a18
commit
e0f3a22b80
@ -22,7 +22,7 @@ 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, onlyUnique, parseJsonFile, setInfoBlock } from './utils.js';
|
||||
import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock } from './utils.js';
|
||||
import { debounce_timeout } from './constants.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
import { groups, selected_group } from './group-chats.js';
|
||||
@ -32,6 +32,11 @@ import { openWorldInfoEditor, world_names } from './world-info.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
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 { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
|
||||
/**
|
||||
* @typedef {object} PersonaConnection A connection between a character and a character or group entity
|
||||
@ -1605,8 +1610,186 @@ async function migrateNonPersonaUser() {
|
||||
await getUserAvatars(true, user_avatar);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Locks or unlocks the persona of the current chat.
|
||||
* @param {{type: string}} _args Named arguments
|
||||
* @param {string} value The value to set the lock to
|
||||
* @returns {Promise<string>} The value of the lock after setting
|
||||
*/
|
||||
async function lockPersonaCallback(_args, value) {
|
||||
const type = /** @type {PersonaLockType} */ (_args.type ?? 'chat');
|
||||
|
||||
if (!['chat', 'character', 'default'].includes(type)) {
|
||||
toastr.warning(t`Unknown lock type "${type}"`, t`Persona Management`);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return String(isPersonaLocked(type));
|
||||
}
|
||||
|
||||
if (['toggle', 't'].includes(value.trim().toLowerCase())) {
|
||||
const result = await togglePersonaLock(type);
|
||||
return String(result);
|
||||
}
|
||||
|
||||
if (isTrueBoolean(value)) {
|
||||
await setPersonaLockState(true, type);
|
||||
return 'true';
|
||||
}
|
||||
|
||||
if (isFalseBoolean(value)) {
|
||||
await setPersonaLockState(false, type);
|
||||
return 'false';
|
||||
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a persona name and optionally an avatar.
|
||||
* @param {{mode: 'lookup' | 'temp' | 'all'}} namedArgs Named arguments
|
||||
* @param {string} name Name to set
|
||||
* @returns {string}
|
||||
*/
|
||||
function setNameCallback({ mode = 'all' }, name) {
|
||||
if (!name) {
|
||||
toastr.warning('You must specify a name to change to');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!['lookup', 'temp', 'all'].includes(mode)) {
|
||||
toastr.warning('Mode must be one of "lookup", "temp" or "all"');
|
||||
return '';
|
||||
}
|
||||
|
||||
name = name.trim();
|
||||
|
||||
// If the name matches a persona avatar, or a name, auto-select it
|
||||
if (['lookup', 'all'].includes(mode)) {
|
||||
let persona = Object.entries(power_user.personas).find(([avatar, _]) => avatar === name)?.[1];
|
||||
if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1];
|
||||
if (persona) {
|
||||
autoSelectPersona(persona);
|
||||
retriggerFirstMessageOnEmptyChat();
|
||||
return '';
|
||||
} else if (mode === 'lookup') {
|
||||
toastr.warning(`Persona ${name} not found`);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
if (['temp', 'all'].includes(mode)) {
|
||||
// Otherwise, set just the name
|
||||
setUserName(name); //this prevented quickReply usage
|
||||
retriggerFirstMessageOnEmptyChat();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function syncCallback() {
|
||||
$('#sync_name_button').trigger('click');
|
||||
return '';
|
||||
}
|
||||
|
||||
function registerPersonaSlashCommands() {
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'persona-lock',
|
||||
callback: lockPersonaCallback,
|
||||
returns: 'The current lock state for the given type',
|
||||
helpString: 'Locks/unlocks a persona (name and avatar) to the current chat. Gets the current lock state for the given type if no state is provided.',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'type',
|
||||
description: 'The type of the lock, where it should apply to',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'chat',
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('chat', 'Lock the persona to the current chat.'),
|
||||
new SlashCommandEnumValue('character', 'Lock this persona to the currently selected character. If the setting is enabled, mutliple personas can be locked to the same character.'),
|
||||
new SlashCommandEnumValue('default', 'Lock this persona as the default persona for all new chats.'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'state',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: commonEnumProviders.boolean('onOffToggle'),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
// TODO: Legacy command. Might be removed in the future and replaced by /persona-lock with aliases.
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'lock',
|
||||
/** @type {(args: { type: string }, value: string) => Promise<string>} */
|
||||
callback: (args, value) => {
|
||||
if (!value) {
|
||||
value = 'toggle';
|
||||
toastr.warning(t`Using /lock without a provided state to toggle the persona is deprecated. Please use /persona-lock instead.
|
||||
In the future this command with no state provided will return the current state, instead of toggling it.`, t`Deprecation Warning`);
|
||||
}
|
||||
return lockPersonaCallback(args, value);
|
||||
},
|
||||
returns: 'The current lock state for the given type',
|
||||
aliases: ['bind'],
|
||||
helpString: 'Locks/unlocks a persona (name and avatar) to the current chat. Gets the current lock state for the given type if no state is provided.',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'type',
|
||||
description: 'The type of the lock, where it should apply to',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'chat',
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('chat', 'Lock the persona to the current chat.'),
|
||||
new SlashCommandEnumValue('character', 'Lock this persona to the currently selected character. If the setting is enabled, mutliple personas can be locked to the same character.'),
|
||||
new SlashCommandEnumValue('default', 'Lock this persona as the default persona for all new chats.'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'state',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'toggle',
|
||||
enumProvider: commonEnumProviders.boolean('onOffToggle'),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'persona-set',
|
||||
callback: setNameCallback,
|
||||
aliases: ['persona', 'name'],
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'mode', 'The mode for persona selection. ("lookup" = search for existing persona, "temp" = create a temporary name, set a temporary name, "all" = allow both in the same command)',
|
||||
[ARGUMENT_TYPE.STRING], false, false, 'all', ['lookup', 'temp', 'all'],
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'persona name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.personas,
|
||||
}),
|
||||
],
|
||||
helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'persona-sync',
|
||||
aliases: ['sync'],
|
||||
callback: syncCallback,
|
||||
helpString: 'Syncs the user persona in user-attributed messages in the current chat.',
|
||||
}));
|
||||
}
|
||||
|
||||
export async function initPersonas() {
|
||||
await migrateNonPersonaUser();
|
||||
registerPersonaSlashCommands();
|
||||
$('#persona_delete_button').on('click', deleteUserAvatar);
|
||||
$('#lock_persona_default').on('click', () => togglePersonaLock('default'));
|
||||
$('#lock_user_name').on('click', () => togglePersonaLock('chat'));
|
||||
|
@ -38,7 +38,6 @@ import {
|
||||
setCharacterId,
|
||||
setCharacterName,
|
||||
setExtensionPrompt,
|
||||
setUserName,
|
||||
showMoreMessages,
|
||||
stopGeneration,
|
||||
substituteParams,
|
||||
@ -55,7 +54,7 @@ import { getContext, saveMetadataDebounced } from './extensions.js';
|
||||
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
|
||||
import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';
|
||||
import { chat_completion_sources, oai_settings, promptManager } from './openai.js';
|
||||
import { autoSelectPersona, isPersonaLocked, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
|
||||
import { user_avatar } from './personas.js';
|
||||
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';
|
||||
@ -75,7 +74,6 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
|
||||
import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
|
||||
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,
|
||||
@ -124,93 +122,6 @@ export function initDefaultSlashCommands() {
|
||||
})],
|
||||
helpString: 'Get help on macros, chat formatting and commands.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'persona',
|
||||
callback: setNameCallback,
|
||||
aliases: ['name'],
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'mode', 'The mode for persona selection. ("lookup" = search for existing persona, "temp" = create a temporary name, set a temporary name, "all" = allow both in the same command)',
|
||||
[ARGUMENT_TYPE.STRING], false, false, 'all', ['lookup', 'temp', 'all'],
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'persona name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.personas,
|
||||
}),
|
||||
],
|
||||
helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'sync',
|
||||
callback: syncCallback,
|
||||
helpString: 'Syncs the user persona in user-attributed messages in the current chat.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'persona-lock',
|
||||
callback: lockPersonaCallback,
|
||||
returns: 'The current lock state for the given type',
|
||||
helpString: 'Locks/unlocks a persona (name and avatar) to the current chat. Gets the current lock state for the given type if no state is provided.',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'type',
|
||||
description: 'The type of the lock, where it should apply to',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'chat',
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('chat', 'Lock the persona to the current chat.'),
|
||||
new SlashCommandEnumValue('character', 'Lock this persona to the currently selected character. If the setting is enabled, mutliple personas can be locked to the same character.'),
|
||||
new SlashCommandEnumValue('default', 'Lock this persona as the default persona for all new chats.'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'state',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: commonEnumProviders.boolean('onOffToggle'),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
// TODO: Legacy command. Might be removed in the future and replaced by /persona-lock with aliases.
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'lock',
|
||||
callback: (args, value) => {
|
||||
if (!value) {
|
||||
value = 'toggle';
|
||||
toastr.warning(t`Using /lock without a provided state to toggle the persona is deprecated. Please use /persona-lock instead.
|
||||
In the future this command with no state provided will return the current state, instead of toggling it.`, t`Deprecation Warning`);
|
||||
}
|
||||
return lockPersonaCallback(args, value);
|
||||
},
|
||||
returns: 'The current lock state for the given type',
|
||||
aliases: ['bind'],
|
||||
helpString: 'Locks/unlocks a persona (name and avatar) to the current chat. Gets the current lock state for the given type if no state is provided.',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'type',
|
||||
description: 'The type of the lock, where it should apply to',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'chat',
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('chat', 'Lock the persona to the current chat.'),
|
||||
new SlashCommandEnumValue('character', 'Lock this persona to the currently selected character. If the setting is enabled, mutliple personas can be locked to the same character.'),
|
||||
new SlashCommandEnumValue('default', 'Lock this persona as the default persona for all new chats.'),
|
||||
],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'state',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'toggle',
|
||||
enumProvider: commonEnumProviders.boolean('onOffToggle'),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'bg',
|
||||
callback: setBackgroundCallback,
|
||||
@ -3487,37 +3398,6 @@ export async function generateSystemMessage(_, prompt) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function syncCallback() {
|
||||
$('#sync_name_button').trigger('click');
|
||||
return '';
|
||||
}
|
||||
|
||||
async function lockPersonaCallback(_args, value) {
|
||||
const type = _args.type ?? 'chat';
|
||||
|
||||
if (!value) {
|
||||
return String(isPersonaLocked(type));
|
||||
}
|
||||
|
||||
if (['toggle', 't'].includes(value.trim().toLowerCase())) {
|
||||
const result = await togglePersonaLock(type);
|
||||
return String(result);
|
||||
}
|
||||
|
||||
if (isTrueBoolean(value)) {
|
||||
await setPersonaLockState(true, type);
|
||||
return 'true';
|
||||
}
|
||||
|
||||
if (isFalseBoolean(value)) {
|
||||
await setPersonaLockState(false, type);
|
||||
return 'false';
|
||||
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function setStoryModeCallback() {
|
||||
$('#chat_display').val(chat_styles.DOCUMENT).trigger('change');
|
||||
return '';
|
||||
@ -3533,48 +3413,6 @@ function setFlatModeCallback() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a persona name and optionally an avatar.
|
||||
* @param {{mode: 'lookup' | 'temp' | 'all'}} namedArgs Named arguments
|
||||
* @param {string} name Name to set
|
||||
* @returns {string}
|
||||
*/
|
||||
function setNameCallback({ mode = 'all' }, name) {
|
||||
if (!name) {
|
||||
toastr.warning('You must specify a name to change to');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!['lookup', 'temp', 'all'].includes(mode)) {
|
||||
toastr.warning('Mode must be one of "lookup", "temp" or "all"');
|
||||
return '';
|
||||
}
|
||||
|
||||
name = name.trim();
|
||||
|
||||
// If the name matches a persona avatar, or a name, auto-select it
|
||||
if (['lookup', 'all'].includes(mode)) {
|
||||
let persona = Object.entries(power_user.personas).find(([avatar, _]) => avatar === name)?.[1];
|
||||
if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1];
|
||||
if (persona) {
|
||||
autoSelectPersona(persona);
|
||||
retriggerFirstMessageOnEmptyChat();
|
||||
return '';
|
||||
} else if (mode === 'lookup') {
|
||||
toastr.warning(`Persona ${name} not found`);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
if (['temp', 'all'].includes(mode)) {
|
||||
// Otherwise, set just the name
|
||||
setUserName(name); //this prevented quickReply usage
|
||||
retriggerFirstMessageOnEmptyChat();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
async function setNarratorName(_, text) {
|
||||
const name = text || NARRATOR_NAME_DEFAULT;
|
||||
chat_metadata[NARRATOR_NAME_KEY] = name;
|
||||
|
Loading…
x
Reference in New Issue
Block a user