From 48077d200bacd9bfa9c440426cbb6df63d4f8dd4 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 21 Jun 2024 20:04:55 +0200 Subject: [PATCH] More slash command enums (nearly done) --- public/script.js | 16 +- .../scripts/extensions/attachments/index.js | 2 +- public/scripts/extensions/caption/index.js | 2 +- .../scripts/extensions/expressions/index.js | 14 +- public/scripts/extensions/gallery/index.js | 4 +- .../quick-reply/src/SlashCommandHandler.js | 47 ++- public/scripts/extensions/regex/index.js | 13 +- .../extensions/stable-diffusion/index.js | 2 +- public/scripts/extensions/tts/index.js | 11 +- public/scripts/power-user.js | 12 +- public/scripts/preset-manager.js | 1 - public/scripts/slash-commands.js | 354 ++++++++++++------ .../SlashCommandCommonEnumsProvider.js | 106 ++++-- public/scripts/tags.js | 8 +- public/scripts/variables.js | 1 + public/scripts/world-info.js | 4 +- 16 files changed, 415 insertions(+), 182 deletions(-) diff --git a/public/script.js b/public/script.js index bed9072c3..7069e855f 100644 --- a/public/script.js +++ b/public/script.js @@ -8818,18 +8818,15 @@ jQuery(async function () { SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'api', callback: connectAPISlash, - namedArgumentList: [], unnamedArgumentList: [ - new SlashCommandArgument( - 'API to connect to', - [ARGUMENT_TYPE.STRING], - true, - false, - null, - Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => + SlashCommandArgument.fromProps({ + description: 'API to connect to', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(uniqueAPIs.findIndex(x => x === selected)), selected[0].toUpperCase() ?? enumIcons.default)), - ), + }), ], helpString: `
@@ -8918,7 +8915,6 @@ jQuery(async function () { name: 'instruct', callback: selectInstructCallback, returns: 'current preset', - namedArgumentList: [], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'instruct preset name', diff --git a/public/scripts/extensions/attachments/index.js b/public/scripts/extensions/attachments/index.js index 061e83a51..f5296072d 100644 --- a/public/scripts/extensions/attachments/index.js +++ b/public/scripts/extensions/attachments/index.js @@ -213,7 +213,7 @@ jQuery(async () => { return attachments.map(attachment => new SlashCommandEnumValue( returnField === 'name' ? attachment.name : attachment.url, - `${extension_settings.disabled_attachments.includes(attachment.url) ? enumIcons.false : enumIcons.true} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`, + `${enumIcons.getStateIcon(!extension_settings.disabled_attachments.includes(attachment.url))} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`, enumTypes.enum, enumIcons.file)); }, }; diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index 4bbc701ed..b88926e10 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -453,7 +453,7 @@ jQuery(async function () { name: 'id', description: 'get image from a message with this ID', typeList: [ARGUMENT_TYPE.NUMBER], - enumProvider: commonEnumProviders.messages, + enumProvider: commonEnumProviders.messages(), }), ], unnamedArgumentList: [ diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index 146b7e571..eaed37355 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -10,7 +10,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from '../../slash-commands/SlashCommandArgument.js'; import { isFunctionCallingSupported } from '../../openai.js'; -import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; +import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; export { MODULE_NAME }; @@ -2045,6 +2045,14 @@ function migrateSettings() { }); eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced); eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced); + + const localEnumProviders = { + expressions: () => getCachedExpressions().map(expression => { + const isCustom = extension_settings.expressions.custom?.includes(expression); + return new SlashCommandEnumValue(expression, null, isCustom ? enumTypes.name : enumTypes.enum, isCustom ? 'C' : 'D'); + }), + } + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sprite', aliases: ['emote'], @@ -2054,7 +2062,7 @@ function migrateSettings() { description: 'spriteId', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => getCachedExpressions().map((x) => new SlashCommandEnumValue(x)), + enumProvider: localEnumProviders.expressions, }), ], helpString: 'Force sets the sprite for the current character.', @@ -2080,7 +2088,7 @@ function migrateSettings() { description: 'character name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], helpString: 'Returns the last set sprite / expression for the named character.', diff --git a/public/scripts/extensions/gallery/index.js b/public/scripts/extensions/gallery/index.js index cb275a4f8..ce9a12b32 100644 --- a/public/scripts/extensions/gallery/index.js +++ b/public/scripts/extensions/gallery/index.js @@ -417,13 +417,13 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery name: 'char', description: 'character name', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: commonEnumProviders.charName('character'), + enumProvider: commonEnumProviders.characters('character'), }), SlashCommandNamedArgument.fromProps({ name: 'group', description: 'group name', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: commonEnumProviders.charName('group'), + enumProvider: commonEnumProviders.characters('group'), }), ], helpString: 'List images in the gallery of the current char / group or a specified char / group.', diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index 86e6f9016..a2c54e9ee 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -36,15 +36,37 @@ export class SlashCommandHandler { } const localEnumProviders = { - qrSets: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, enumTypes.enum, 'S')), + /** All quick reply sets, optionally filtering out sets that wer already used in the "set" named argument */ + qrSets: (executor) => QuickReplySet.list.filter(qrSet => qrSet.name != String(executor.namedArgumentList.find(x => x.name == 'set')?.value)) + .map(qrSet => new SlashCommandEnumValue(qrSet.name, null, enumTypes.enum, 'S')), + /** All QRs inside a set, utilizing the "set" named argument */ qrEntries: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => { const icons = getExecutionIcons(qr); const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim(); - return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, 'QR'); + return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr); }) ?? [], + + /** All QRs as a set.name string, to be able to execute, for example via the /run command */ + qrExecutables: () => { + const globalSetList = this.api.settings.config.setList; + const chatSetList = this.api.settings.chatConfig?.setList; + + const globalQrs = globalSetList.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat(); + const chatQrs = chatSetList?.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat() ?? []; + const otherQrs = QuickReplySet.list.filter(set => !globalSetList.some(link => link.set.name === set.name && !chatSetList?.some(link => link.set.name === set.name))) + .map(set => set.qrList.map(qr => ({ set, qr }))).flat(); + + return [ + ...globalQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[global] ${x.qr.title || x.qr.message}`, enumTypes.name, enumIcons.qr)), + ...chatQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[chat] ${x.qr.title || x.qr.message}`, enumTypes.enum, enumIcons.qr)), + ...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)), + ]; + }, } + window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables; + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr', callback: (_, value) => this.executeQuickReplyByIndex(Number(value)), unnamedArgumentList: [ @@ -178,7 +200,7 @@ export class SlashCommandHandler { namedArgumentList: [], unnamedArgumentList: [ new SlashCommandArgument( - 'set type', [ARGUMENT_TYPE.STRING], false, false, null, ['all', 'global', 'chat'], + 'set type', [ARGUMENT_TYPE.STRING], false, false, 'all', ['all', 'global', 'chat'], ), ], helpString: 'Gets a list of the names of all quick reply sets.', @@ -201,8 +223,20 @@ export class SlashCommandHandler { })); const qrArgs = [ - new SlashCommandNamedArgument('label', 'text on the button, e.g., label=MyButton', [ARGUMENT_TYPE.STRING]), - new SlashCommandNamedArgument('set', 'name of the QR set, e.g., set=PresetName1', [ARGUMENT_TYPE.STRING]), + SlashCommandNamedArgument.fromProps({ + name: 'set', + description: 'name of the QR set, e.g., set=PresetName1', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: localEnumProviders.qrSets, + }), + SlashCommandNamedArgument.fromProps({ + name: 'label', + description: 'text on the button, e.g., label=MyButton', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: localEnumProviders.qrLabels, + }), new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), new SlashCommandNamedArgument('user', 'auto execute on user message, e.g., user=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), @@ -246,7 +280,7 @@ export class SlashCommandHandler { returns: 'updated quick reply', namedArgumentList: [...qrUpdateArgs, ...qrArgs], unnamedArgumentList: [ - new SlashCommandArgument('message', [ARGUMENT_TYPE.STRING]), + new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]), ], helpString: `
@@ -425,6 +459,7 @@ export class SlashCommandHandler { typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: localEnumProviders.qrSets, + forceEnum: false, }), ], helpString: ` diff --git a/public/scripts/extensions/regex/index.js b/public/scripts/extensions/regex/index.js index 81632c9e2..7108b9315 100644 --- a/public/scripts/extensions/regex/index.js +++ b/public/scripts/extensions/regex/index.js @@ -3,7 +3,8 @@ import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } import { selected_group } from '../../group-chats.js'; import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; -import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; +import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; +import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js'; import { resolveVariable } from '../../variables.js'; @@ -571,6 +572,14 @@ jQuery(async () => { await loadRegexScripts(); $('#saved_regex_scripts').sortable('enable'); + const localEnumProviders = { + regexScripts: () => getRegexScripts().map(script => { + const isGlobal = extension_settings.regex?.some(x => x.scriptName === script.scriptName); + return new SlashCommandEnumValue(script.scriptName, `${enumIcons.getStateIcon(!script.disabled)} [${isGlobal ? 'global' : 'scoped'}] ${script.findRegex}`, + isGlobal ? enumTypes.enum : enumTypes.name, isGlobal ? 'G' : 'S'); + }), + }; + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'regex', callback: runRegexCallback, @@ -581,7 +590,7 @@ jQuery(async () => { description: 'script name', typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: () => getRegexScripts().map(script => new SlashCommandEnumValue(script.scriptName, null, 'regex', '🔍')), + enumProvider: localEnumProviders.regexScripts, }), ], unnamedArgumentList: [ diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index 2bd8cc15c..d3da529c4 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -3384,7 +3384,7 @@ jQuery(async () => { description: 'workflow name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(x => new SlashCommandEnumValue(x, null, 'workflow', 'W')), + enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(workflow => new SlashCommandEnumValue(workflow)), }), ], helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g.
/imagine-comfy-workflow MyWorkflow
', diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 9eed3205a..2a9b75ffc 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -19,6 +19,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; import { debounce_timeout } from '../../constants.js'; +import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; export { talkingAnimation }; const UPDATE_INTERVAL = 1000; @@ -1210,9 +1211,13 @@ $(document).ready(function () { }, aliases: ['narrate', 'tts'], namedArgumentList: [ - new SlashCommandNamedArgument( - 'voice', 'character voice name', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandNamedArgument.fromProps({ + name: 'voice', + description: 'character voice name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: false, + enumProvider: () => Object.keys(voiceMap).map(voiceName => new SlashCommandEnumValue(voiceName, null, enumTypes.enum, voiceName)), + }), ], unnamedArgumentList: [ new SlashCommandArgument( diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index cf4e2d747..2a34984d2 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -47,7 +47,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js'; import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; -import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; +import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; export { loadPowerUserSettings, @@ -3939,9 +3939,13 @@ $(document).ready(() => { callback: doMesCut, returns: 'the text of cut messages separated by a newline', unnamedArgumentList: [ - new SlashCommandArgument( - 'number or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], true, - ), + SlashCommandArgument.fromProps({ + description: 'number or range', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + isRequired: true, + acceptsMultiple: true, + enumProvider: commonEnumProviders.messages(), + }), ], helpString: `
diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index 4c6ef88a5..036bc1aa8 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -481,7 +481,6 @@ export async function initPresetManager() { SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'preset', callback: presetCommandCallback, returns: 'current preset', - namedArgumentList: [], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'name', diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index db37443c7..4c28fdec9 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -61,9 +61,9 @@ import { AutoComplete } from './autocomplete/AutoComplete.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashCommandNamedArgumentAssignment.js'; -import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; +import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; -import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; +import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; export { executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, }; @@ -79,9 +79,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: '?', callback: helpCommandCallback, aliases: ['help'], - unnamedArgumentList: [new SlashCommandArgument( - 'help topic', ARGUMENT_TYPE.STRING, false, false, null, ['slash', 'format', 'hotkeys', 'macros'], - )], + unnamedArgumentList: [SlashCommandArgument.fromProps({ + description: 'help topic', + typeList: [ARGUMENT_TYPE.STRING], + enumList: [ + new SlashCommandEnumValue('slash', 'slash commands (STscript)', enumTypes.command, '/'), + new SlashCommandEnumValue('macros', '{{macros}} (text replacement)', enumTypes.macro, '{{'), + new SlashCommandEnumValue('format', 'chat/text formatting', enumTypes.name, '★'), + new SlashCommandEnumValue('hotkeys', 'keyboard shortcuts', enumTypes.enum, '⏎'), + ], + })], helpString: 'Get help on macros, chat formatting and commands.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ @@ -94,9 +101,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'persona name', [ARGUMENT_TYPE.STRING], true, - ), + 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.', aliases: ['name'], @@ -123,9 +133,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: () => [...document.querySelectorAll('.bg_example')] - .map((it) => new SlashCommandEnumValue(it.getAttribute('bgfile'))) - .filter(it => it.value?.length) - , + .map(it => new SlashCommandEnumValue(it.getAttribute('bgfile'))) + .filter(it => it.value?.length), }), ], helpString: ` @@ -146,9 +155,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sendas', callback: sendMessageAs, namedArgumentList: [ - new SlashCommandNamedArgument( - 'name', 'Character name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'Character name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.characters('character'), + forceEnum: false, + }), new SlashCommandNamedArgument( 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), @@ -156,8 +170,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.variables('all'), - forceEnum: false, + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }), }), ], unnamedArgumentList: [ @@ -200,8 +213,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.variables('all'), - forceEnum: false, + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }) }), ], unnamedArgumentList: [ @@ -255,8 +267,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.variables('all'), - forceEnum: false, + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }) }), ], unnamedArgumentList: [ @@ -340,7 +351,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.charName('all'), + enumProvider: commonEnumProviders.characters('all'), }), ], helpString: 'Opens up a chat with the character or group by its name', @@ -388,7 +399,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'character name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.charName('all'), + enumProvider: commonEnumProviders.characters('all'), }), ], unnamedArgumentList: [ @@ -407,7 +418,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.charName('all'), + enumProvider: commonEnumProviders.characters('all'), }), ], aliases: ['cancel'], @@ -441,17 +452,18 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.variables('all'), - forceEnum: false, + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }) + }), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'display name', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], + defaultValue: '{{user}}', + enumProvider: () => [ + ...commonEnumProviders.characters('character')(), + ...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }) + ], }), - new SlashCommandNamedArgument( - 'name', - 'display name', - [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], - false, - false, - '{{user}}', - ), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -513,7 +525,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'message index or range', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], isRequired: true, - enumProvider: () => chat.map((_, i) => new SlashCommandEnumValue(String(i), null, 'number', '1ī¸âƒŖ')), + enumProvider: commonEnumProviders.messages(), }), ], helpString: 'Hides a chat message from the prompt.', @@ -522,9 +534,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unhide', callback: unhideMessageCallback, unnamedArgumentList: [ - new SlashCommandArgument( - 'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], true, - ), + SlashCommandArgument.fromProps({ + description: 'message index or range', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + isRequired: true, + enumProvider: commonEnumProviders.messages(), + }), ], helpString: 'Unhides a message from the prompt.', })); @@ -537,9 +552,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'member index or name', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => [ - ...getGroupMembers().map((character, i) => new SlashCommandEnumValue(String(i), character.name, 'name', '👤')), - ], + enumProvider: commonEnumProviders.groupMembers(), }), ], helpString: 'Disables a group member from being drafted for replies.', @@ -549,9 +562,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['enable', 'enablemember', 'memberenable'], callback: enableGroupMemberCallback, unnamedArgumentList: [ - new SlashCommandArgument( - 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'member index or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.groupMembers(), + }), ], helpString: 'Enables a group member to be drafted for replies.', })); @@ -560,9 +576,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: addGroupMemberCallback, aliases: ['addmember', 'memberadd'], unnamedArgumentList: [ - new SlashCommandArgument( - 'character name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'character name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [], + }), ], helpString: `
@@ -583,9 +602,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: removeGroupMemberCallback, aliases: ['removemember', 'memberremove'], unnamedArgumentList: [ - new SlashCommandArgument( - 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'member index or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.groupMembers(), + }), ], helpString: `
@@ -607,9 +629,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: moveGroupMemberUpCallback, aliases: ['upmember', 'memberup'], unnamedArgumentList: [ - new SlashCommandArgument( - 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'member index or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.groupMembers(), + }), ], helpString: 'Moves a group member up in the group chat list.', })); @@ -618,9 +643,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: moveGroupMemberDownCallback, aliases: ['downmember', 'memberdown'], unnamedArgumentList: [ - new SlashCommandArgument( - 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'member index or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.groupMembers(), + }), ], helpString: 'Moves a group member down in the group chat list.', })); @@ -628,9 +656,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'peek', callback: peekCallback, unnamedArgumentList: [ - new SlashCommandArgument( - 'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], false, true, - ), + SlashCommandArgument.fromProps({ + description: 'message index or range', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + isRequired: true, + enumProvider: commonEnumProviders.messages(), + }), ], helpString: `
@@ -656,9 +687,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: deleteSwipeCallback, aliases: ['swipedel'], unnamedArgumentList: [ - new SlashCommandArgument( - '1-based swipe id', [ARGUMENT_TYPE.NUMBER], - ), + SlashCommandArgument.fromProps({ + description: '1-based swipe id', + typeList: [ARGUMENT_TYPE.NUMBER], + isRequired: true, + enumProvider: () => Array.isArray(chat[chat.length - 1]?.swipes) ? + chat[chat.length - 1].swipes.map((/** @type {string} */ swipe, /** @type {number} */ i) => new SlashCommandEnumValue(String(i + 1), swipe, enumTypes.enum, enumIcons.message)) + : [], + }), ], helpString: `
@@ -687,9 +723,18 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ new SlashCommandNamedArgument( 'title', 'title of the toast message', [ARGUMENT_TYPE.STRING], false, ), - new SlashCommandNamedArgument( - 'severity', 'severity level of the toast message', [ARGUMENT_TYPE.STRING], false, false, null, ['info', 'warning', 'error', 'success'], - ), + SlashCommandNamedArgument.fromProps({ + name: 'severity', + description: 'severity level of the toast message', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: 'info', + enumProvider: () => [ + new SlashCommandEnumValue('info', 'info', enumTypes.macro, 'ℹī¸'), + new SlashCommandEnumValue('warning', 'warning', enumTypes.enum, '⚠ī¸'), + new SlashCommandEnumValue('error', 'error', enumTypes.enum, '❗'), + new SlashCommandEnumValue('success', 'success', enumTypes.enum, '✅'), + ], + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -716,17 +761,28 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ returns: 'generated text', namedArgumentList: [ new SlashCommandNamedArgument( - 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'], - ), - new SlashCommandNamedArgument( - 'name', 'in-prompt name for instruct mode', [ARGUMENT_TYPE.STRING], false, false, 'System', + 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(), ), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'in-prompt name for instruct mode', + 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, ), - new SlashCommandNamedArgument( - 'as', 'role of the output prompt', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'char'], - ), + SlashCommandNamedArgument.fromProps({ + name: 'as', + description: 'role of the output prompt', + typeList: [ARGUMENT_TYPE.STRING], + enumList: [ + new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant), + new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character), + ], + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -748,17 +804,23 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ returns: 'generated text', namedArgumentList: [ new SlashCommandNamedArgument( - 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'], + 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( - 'instruct', 'use instruct mode', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', ['on', 'off'], + 'instruct', 'use instruct mode', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( 'stop', 'one-time custom stop strings', [ARGUMENT_TYPE.LIST], false, ), - new SlashCommandNamedArgument( - 'as', 'role of the output prompt', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'char'], - ), + SlashCommandNamedArgument.fromProps({ + name: 'as', + description: 'role of the output prompt', + typeList: [ARGUMENT_TYPE.STRING], + enumList: [ + new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant), + new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character), + ], + }), new SlashCommandNamedArgument( 'system', 'system prompt at the start', [ARGUMENT_TYPE.STRING], false, ), @@ -861,7 +923,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pass', - callback: (_, arg) => arg, + callback: (_, arg) => { + // We do not support arrays of closures. Arrays of strings will be send as JSON + if (Array.isArray(arg) && arg.some(x => x instanceof SlashCommandClosure)) throw new Error('Command /pass does not support multiple closures'); + if (Array.isArray(arg)) return JSON.stringify(arg); + return arg; + }, returns: 'the provided value', unnamedArgumentList: [ new SlashCommandArgument( @@ -914,10 +981,10 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ 'default', 'default value of the input field', [ARGUMENT_TYPE.STRING], false, false, '"string"', ), new SlashCommandNamedArgument( - 'large', 'show large input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', ['on', 'off'], + 'large', 'show large input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( - 'wide', 'show wide input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', ['on', 'off'], + 'wide', 'show wide input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( 'okButton', 'text for the ok button', [ARGUMENT_TYPE.STRING], false, @@ -949,9 +1016,15 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'scoped variable or qr label', [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'scoped variable or qr label', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => [ + ...commonEnumProviders.variables('scope')(), + ...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [], + ], + }), ], helpString: `
@@ -966,19 +1039,29 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['message'], namedArgumentList: [ new SlashCommandNamedArgument( - 'names', 'show message author names', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', ['off', 'on'], + 'names', 'show message author names', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( - 'hidden', 'include hidden messages', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', ['off', 'on'], - ), - new SlashCommandNamedArgument( - 'role', 'filter messages by role', [ARGUMENT_TYPE.STRING], false, false, null, ['system', 'assistant', 'user'], + 'hidden', 'include hidden messages', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(), ), + SlashCommandNamedArgument.fromProps({ + name: 'role', + description: 'filter messages by role', + typeList: [ARGUMENT_TYPE.STRING], + enumList: [ + new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system), + new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant), + new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user), + ], + }), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], false, true, - ), + SlashCommandArgument.fromProps({ + description: 'message index or range', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + isRequired: true, + enumProvider: commonEnumProviders.messages(), + }), ], returns: 'the specified message or range of messages as a string', helpString: ` @@ -1034,10 +1117,10 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ returns: 'popup text', namedArgumentList: [ new SlashCommandNamedArgument( - 'large', 'show large popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'], + 'large', 'show large popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( - 'wide', 'show wide popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'], + 'wide', 'show wide popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( 'okButton', 'text for the OK button', [ARGUMENT_TYPE.STRING], false, @@ -1100,9 +1183,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ new SlashCommandNamedArgument( 'limit', 'number of tokens to keep', [ARGUMENT_TYPE.NUMBER], true, ), - new SlashCommandNamedArgument( - 'direction', 'trim direction', [ARGUMENT_TYPE.STRING], true, false, null, ['start', 'end'], - ), + SlashCommandNamedArgument.fromProps({ + name: 'direction', + description: 'trim direction', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumList: [ + new SlashCommandEnumValue('start', null, enumTypes.enum, 'âŦ…ī¸'), + new SlashCommandEnumValue('end', null, enumTypes.enum, '➡ī¸'), + ], + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -1173,11 +1263,19 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ new SlashCommandNamedArgument( 'scan', 'include injection content into World Info scans', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), + SlashCommandNamedArgument.fromProps({ + name: 'role', + description: 'role for in-chat injections', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: false, + enumList: [ + new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system), + new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant), + new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user), + ], + }), new SlashCommandNamedArgument( - 'role', 'role for in-chat injections', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'user', 'assistant'], - ), - new SlashCommandNamedArgument( - 'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'], + 'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), ], unnamedArgumentList: [ @@ -1196,16 +1294,25 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'flushinject', aliases: ['flushinjects'], unnamedArgumentList: [ - new SlashCommandArgument( - 'injection ID or a variable name pointing to ID', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], false, false, '', - ), + SlashCommandArgument.fromProps({ + description: 'injection ID or a variable name pointing to ID', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], + defaultValue: '', + enumProvider: () => [ + ...commonEnumProviders.injects(), + ...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }), + ], + }) ], callback: flushInjectsCallback, helpString: 'Removes a script injection for the current chat. If no ID is provided, removes all script injections.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'tokens', - callback: (_, text) => getTokenCountAsync(text), + callback: (_, text) => { + if (text instanceof SlashCommandClosure || Array.isArray(text)) throw new Error('Unnamed argument cannot be a closure for command /tokens') + return getTokenCountAsync(text).then(count => String(count)); + }, returns: 'number of tokens', unnamedArgumentList: [ new SlashCommandArgument( @@ -1230,12 +1337,28 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['setpromptentries'], callback: setPromptEntryCallback, namedArgumentList: [ - new SlashCommandNamedArgument( - 'identifier', 'Prompt entry identifier(s) to target', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], false, true, - ), - new SlashCommandNamedArgument( - 'name', 'Prompt entry name(s) to target', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], false, true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'identifier', + description: 'Prompt entry identifier(s) to target', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], + acceptsMultiple: true, + enumProvider: () => { + const promptManager = setupChatCompletionPromptManager(oai_settings); + const prompts = promptManager.serviceSettings.prompts; + return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum)); + }, + }), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'Prompt entry name(s) to target', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], + acceptsMultiple: true, + enumProvider: () => { + const promptManager = setupChatCompletionPromptManager(oai_settings); + const prompts = promptManager.serviceSettings.prompts; + return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum)); + } + }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ @@ -1244,7 +1367,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ isRequired: true, acceptsMultiple: false, defaultValue: 'toggle', // unnamed arguments don't support default values yet - enumList: ['on', 'off', 'toggle'], + enumList: commonEnumProviders.boolean('onOffToggle')(), }), ], helpString: 'Sets the specified prompt manager entry/entries on or off.', @@ -1521,7 +1644,7 @@ async function popupCallback(args, value) { await delay(1); await callGenericPopup(safeValue, POPUP_TYPE.TEXT, '', popupOptions); await delay(1); - return value; + return String(value); } function getMessagesCallback(args, value) { @@ -1625,12 +1748,11 @@ async function runCallback(args, name) { /** * - * @param {object} param0 - * @param {SlashCommandAbortController} param0._abortController - * @param {string} [param0.quiet] + * @param {import('./slash-commands/SlashCommand.js').NamedArguments} param0 * @param {string} [reason] */ function abortCallback({ _abortController, quiet }, reason) { + if (quiet instanceof SlashCommandClosure) throw new Error('argument \'quiet\' cannot be a closure for command /abort'); _abortController.abort((reason ?? '').toString().length == 0 ? '/abort command executed' : reason, !isFalseBoolean(quiet ?? 'true')); return ''; } @@ -1661,9 +1783,9 @@ async function inputCallback(args, prompt) { }; // Do not remove this delay, otherwise the prompt will not show up await delay(1); - const result = await callPopup(safeValue, 'input', defaultInput, popupOptions); + const result = await callGenericPopup(safeValue, POPUP_TYPE.INPUT, defaultInput, popupOptions); await delay(1); - return result || ''; + return String(result); } /** diff --git a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js index 6026f82e1..22f25a281 100644 --- a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js +++ b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js @@ -1,6 +1,7 @@ -import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles } from "../../script.js"; +import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles, extension_prompt_types } from "../../script.js"; import { extension_settings } from "../extensions.js"; -import { groups } from "../group-chats.js"; +import { getGroupMembers, groups, selected_group } from "../group-chats.js"; +import { power_user } from "../power-user.js"; import { searchCharByName, getTagsList, tags } from "../tags.js"; import { SlashCommandClosure } from "./SlashCommandClosure.js"; import { SlashCommandEnumValue, enumTypes } from "./SlashCommandEnumValue.js"; @@ -21,11 +22,13 @@ export const enumIcons = { // Common types character: '👤', group: '🧑‍🤝‍🧑', - qr: '🤖', + persona: '🧙‍♂ī¸', + qr: 'QR', tag: '🏷ī¸', world: '🌐', preset: '⚙ī¸', file: '📄', + message: 'đŸ’Ŧ', true: '✔ī¸', false: '❌', @@ -50,6 +53,16 @@ export const enumIcons = { disabled: '❌', vectorized: '🔗', + /** + * Returns the appropriate state icon based on a boolean + * + * @param {boolean} state - The state to determine the icon for + * @returns {string} The corresponding state icon + */ + getStateIcon: (state) => { + return state ? enumIcons.true : enumIcons.false; + }, + /** * Returns the appropriate WI icon based on the entry * @@ -77,6 +90,18 @@ export const enumIcons = { default: return enumIcons.default; } }, + + /** + * A function to get the data type icon + * + * @param {string} type - The type of the data + * @returns {string} The corresponding data type icon + */ + getDataTypeIcon: (type) => { + // Remove possible nullable types definition to match type icon + type = type.replace(/\?$/, ''); + return enumIcons[type] ?? enumIcons.default; + } } /** @@ -88,6 +113,7 @@ export const commonEnumProviders = { /** * Enum values for booleans. Either using true/false or on/off * Optionally supports "toggle". + * * @param {('onOff'|'onOffToggle'|'trueFalse')?} [mode='trueFalse'] - The mode to use. Default is 'trueFalse'. * @returns {() => SlashCommandEnumValue[]} */ @@ -112,9 +138,9 @@ export const commonEnumProviders = { const types = type.flat(); const isAll = types.includes('all'); return [ - ...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(x => new SlashCommandEnumValue(x, null, enumTypes.macro, enumIcons.globalVariable)) : [], - ...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(x => new SlashCommandEnumValue(x, null, enumTypes.name, enumIcons.localVariable)) : [], - ...isAll || types.includes('scope') ? [].map(x => new SlashCommandEnumValue(x, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny + ...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [], + ...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [], + ...isAll || types.includes('scope') ? [].map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny ] }, @@ -124,13 +150,28 @@ export const commonEnumProviders = { * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return * @returns {() => SlashCommandEnumValue[]} */ - charName: (mode = 'all') => () => { + characters: (mode = 'all') => () => { return [ - ...['all', 'character'].includes(mode) ? characters.map(it => new SlashCommandEnumValue(it.name, null, enumTypes.name, enumIcons.character)) : [], - ...['all', 'group'].includes(mode) ? groups.map(it => new SlashCommandEnumValue(it.name, null, enumTypes.qr, enumIcons.group)) : [], + ...['all', 'character'].includes(mode) ? characters.map(char => new SlashCommandEnumValue(char.name, null, enumTypes.name, enumIcons.character)) : [], + ...['all', 'group'].includes(mode) ? groups.map(group => new SlashCommandEnumValue(group.name, null, enumTypes.qr, enumIcons.group)) : [], ]; }, + /** + * All group members of the given group, or default the current active one + * + * @param {string?} groupId - The id of the group - pass in `undefined` to use the current active group + * @returns {() =>SlashCommandEnumValue[]} + */ + groupMembers: (groupId = undefined) => () => getGroupMembers(groupId).map((character, index) => new SlashCommandEnumValue(String(index), character.name, enumTypes.enum, enumIcons.character)), + + /** + * All possible personas + * + * @returns {SlashCommandEnumValue[]} + */ + personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)), + /** * All possible tags for a given char/group entity * @@ -144,14 +185,26 @@ export const commonEnumProviders = { const key = searchCharByName(substituteParams(charName), { suppressLogging: true }); const assigned = key ? getTagsList(key) : []; return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it)) - .map(it => new SlashCommandEnumValue(it.name, null, enumTypes.command, enumIcons.tag)); + .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag)); }, /** * All messages in the current chat, returning the message id - * @returns {SlashCommandEnumValue[]} + * + * Optionally supports variable names, and/or a placeholder for the last/new message id + * + * @param {object} [options={}] - Optional arguments + * @param {boolean} [options.allowIdAfter=false] - Whether to add an enum option for the new message id after the last message + * @param {boolean} [options.allowVars=false] - Whether to add enum option for variable names + * @returns {() => SlashCommandEnumValue[]} */ - messages: () => chat.map((mes, i) => new SlashCommandEnumValue(String(i), `${mes.name}: ${mes.mes}`, enumTypes.number, mes.is_user ? enumIcons.user : mes.is_system ? enumIcons.system : enumIcons.assistant)), + messages: ({ allowIdAfter = false, allowVars = false } = {}) => () => { + return [ + ...chat.map((message, index) => new SlashCommandEnumValue(String(index), `${message.name}: ${message.mes}`, enumTypes.number, message.is_user ? enumIcons.user : message.is_system ? enumIcons.system : enumIcons.assistant)), + ...allowIdAfter ? [new SlashCommandEnumValue(String(chat.length), '>> After Last Message >>', enumTypes.enum, '➕')] : [], + ...allowVars ? commonEnumProviders.variables('all')() : [], + ]; + }, /** * All existing worlds / lorebooks @@ -159,18 +212,19 @@ export const commonEnumProviders = { * @returns {SlashCommandEnumValue[]} */ worlds: () => $('#world_info').children().toArray().map(x => new SlashCommandEnumValue(x.textContent, null, enumTypes.name, enumIcons.world)), -}; -/** - * Get the unicode icon for the given enum value type - * - * Can also confert nullable data types to their non-nullable counterparts - * - * @param {string} type The type of the enum value - * @returns {string} the unicode icon - */ -export function getEnumIcon(type) { - // Remove possible nullable types definition to match type icon - type = type.replace(/\?$/, ''); - return enumIcons[type] ?? enumIcons.default; -} + /** + * All existing injects for the current chat + * + * @returns {SlashCommandEnumValue[]} + */ + injects: () => { + if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) return []; + return Object.entries(chat_metadata.script_injects) + .map(([id, inject]) => { + const positionName = (Object.entries(extension_prompt_types)).find(([_, value]) => value === inject.position)?.[0] ?? 'unknown'; + return new SlashCommandEnumValue(id, `${enumIcons.getRoleIcon(inject.role ?? extension_prompt_roles.SYSTEM)}[Inject](${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}) ${inject.value}`, + enumTypes.enum, '💉'); + }); + }, +}; diff --git a/public/scripts/tags.js b/public/scripts/tags.js index c8185d8d5..d8aae0617 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -1825,7 +1825,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], unnamedArgumentList: [ @@ -1870,7 +1870,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], unnamedArgumentList: [ @@ -1913,7 +1913,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], unnamedArgumentList: [ @@ -1956,7 +1956,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], helpString: ` diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 49537568b..2a869fc6f 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -372,6 +372,7 @@ async function whileCallback(args, value) { * @returns */ async function timesCallback(args, value) { + if (args.guard instanceof SlashCommandClosure) throw new Error('argument \'guard\' cannot be a closure for command /while'); let repeats; let command; if (Array.isArray(value)) { diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 16343ccbd..4cb7bc6ca 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -14,7 +14,7 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; -import { commonEnumProviders, enumIcons, getEnumIcon } from './slash-commands/SlashCommandCommonEnumsProvider.js'; +import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; @@ -707,7 +707,7 @@ function registerWorldInfoSlashCommands() { /** All possible fields that can be set in a WI entry */ wiEntryFields: () => Object.entries(newEntryDefinition).map(([key, value]) => new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : value.default)}`, - enumTypes.enum, getEnumIcon(value.type))), + enumTypes.enum, enumIcons.getDataTypeIcon(value.type))), /** All existing UIDs based on the file argument as world name */ wiUids: (/** @type {SlashCommandExecutor} */ executor) => {