diff --git a/public/scripts/extensions/quick-reply/api/QuickReplyApi.js b/public/scripts/extensions/quick-reply/api/QuickReplyApi.js index 85ff2da73..2e4260147 100644 --- a/public/scripts/extensions/quick-reply/api/QuickReplyApi.js +++ b/public/scripts/extensions/quick-reply/api/QuickReplyApi.js @@ -26,7 +26,7 @@ export class QuickReplyApi { /** * Finds and returns an existing Quick Reply Set by its name. * - * @param {String} name name of the quick reply set + * @param {string} name name of the quick reply set * @returns the quick reply set, or undefined if not found */ getSetByName(name) { @@ -36,13 +36,14 @@ export class QuickReplyApi { /** * Finds and returns an existing Quick Reply by its set's name and its label. * - * @param {String} setName name of the quick reply set - * @param {String} label label of the quick reply + * @param {string} setName name of the quick reply set + * @param {string|number} label label or numeric ID of the quick reply * @returns the quick reply, or undefined if not found */ getQrByLabel(setName, label) { const set = this.getSetByName(setName); if (!set) return; + if (Number.isInteger(label)) return set.qrList.find(it=>it.id == label); return set.qrList.find(it=>it.label == label); } @@ -70,9 +71,9 @@ export class QuickReplyApi { /** * Executes an existing quick reply. * - * @param {String} setName name of the existing quick reply set - * @param {String} label label of the existing quick reply (text on the button) - * @param {Object} [args] optional arguments + * @param {string} setName name of the existing quick reply set + * @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID + * @param {object} [args] optional arguments * @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options] optional execution options */ async executeQuickReply(setName, label, args = {}, options = {}) { @@ -87,8 +88,8 @@ export class QuickReplyApi { /** * Adds or removes a quick reply set to the list of globally active quick reply sets. * - * @param {String} name the name of the set - * @param {Boolean} isVisible whether to show the set's buttons or not + * @param {string} name the name of the set + * @param {boolean} isVisible whether to show the set's buttons or not */ toggleGlobalSet(name, isVisible = true) { const set = this.getSetByName(name); @@ -105,8 +106,8 @@ export class QuickReplyApi { /** * Adds a quick reply set to the list of globally active quick reply sets. * - * @param {String} name the name of the set - * @param {Boolean} isVisible whether to show the set's buttons or not + * @param {string} name the name of the set + * @param {boolean} isVisible whether to show the set's buttons or not */ addGlobalSet(name, isVisible = true) { const set = this.getSetByName(name); @@ -119,7 +120,7 @@ export class QuickReplyApi { /** * Removes a quick reply set from the list of globally active quick reply sets. * - * @param {String} name the name of the set + * @param {string} name the name of the set */ removeGlobalSet(name) { const set = this.getSetByName(name); @@ -133,8 +134,8 @@ export class QuickReplyApi { /** * Adds or removes a quick reply set to the list of the current chat's active quick reply sets. * - * @param {String} name the name of the set - * @param {Boolean} isVisible whether to show the set's buttons or not + * @param {string} name the name of the set + * @param {boolean} isVisible whether to show the set's buttons or not */ toggleChatSet(name, isVisible = true) { if (!this.settings.chatConfig) return; @@ -152,8 +153,8 @@ export class QuickReplyApi { /** * Adds a quick reply set to the list of the current chat's active quick reply sets. * - * @param {String} name the name of the set - * @param {Boolean} isVisible whether to show the set's buttons or not + * @param {string} name the name of the set + * @param {boolean} isVisible whether to show the set's buttons or not */ addChatSet(name, isVisible = true) { if (!this.settings.chatConfig) return; @@ -167,7 +168,7 @@ export class QuickReplyApi { /** * Removes a quick reply set from the list of the current chat's active quick reply sets. * - * @param {String} name the name of the set + * @param {string} name the name of the set */ removeChatSet(name) { if (!this.settings.chatConfig) return; @@ -182,18 +183,18 @@ export class QuickReplyApi { /** * Creates a new quick reply in an existing quick reply set. * - * @param {String} setName name of the quick reply set to insert the new quick reply into - * @param {String} label label for the new quick reply (text on the button) - * @param {Object} [props] - * @param {String} [props.message] the message to be sent or slash command to be executed by the new quick reply - * @param {String} [props.title] the title / tooltip to be shown on the quick reply button - * @param {Boolean} [props.isHidden] whether to hide or show the button - * @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts - * @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message - * @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message - * @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded - * @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected - * @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated + * @param {string} setName name of the quick reply set to insert the new quick reply into + * @param {string} label label for the new quick reply (text on the button) + * @param {object} [props] + * @param {string} [props.message] the message to be sent or slash command to be executed by the new quick reply + * @param {string} [props.title] the title / tooltip to be shown on the quick reply button + * @param {boolean} [props.isHidden] whether to hide or show the button + * @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts + * @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message + * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message + * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded + * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected + * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated * @returns {QuickReply} the new quick reply */ createQuickReply(setName, label, { @@ -229,19 +230,19 @@ export class QuickReplyApi { /** * Updates an existing quick reply. * - * @param {String} setName name of the existing quick reply set - * @param {String} label label of the existing quick reply (text on the button) - * @param {Object} [props] - * @param {String} [props.newLabel] new label for quick reply (text on the button) - * @param {String} [props.message] the message to be sent or slash command to be executed by the quick reply - * @param {String} [props.title] the title / tooltip to be shown on the quick reply button - * @param {Boolean} [props.isHidden] whether to hide or show the button - * @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts - * @param {Boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message - * @param {Boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message - * @param {Boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded - * @param {Boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected - * @param {String} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated + * @param {string} setName name of the existing quick reply set + * @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID + * @param {object} [props] + * @param {string} [props.newLabel] new label for quick reply (text on the button) + * @param {string} [props.message] the message to be sent or slash command to be executed by the quick reply + * @param {string} [props.title] the title / tooltip to be shown on the quick reply button + * @param {boolean} [props.isHidden] whether to hide or show the button + * @param {boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts + * @param {boolean} [props.executeOnUser] whether to execute the quick reply after a user has sent a message + * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message + * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded + * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected + * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated * @returns {QuickReply} the altered quick reply */ updateQuickReply(setName, label, { @@ -277,8 +278,8 @@ export class QuickReplyApi { /** * Deletes an existing quick reply. * - * @param {String} setName name of the existing quick reply set - * @param {String} label label of the existing quick reply (text on the button) + * @param {string} setName name of the existing quick reply set + * @param {string|number} label label of the existing quick reply (text on the button) or its numeric ID */ deleteQuickReply(setName, label) { const qr = this.getQrByLabel(setName, label); @@ -292,10 +293,10 @@ export class QuickReplyApi { /** * Adds an existing quick reply set as a context menu to an existing quick reply. * - * @param {String} setName name of the existing quick reply set containing the quick reply - * @param {String} label label of the existing quick reply - * @param {String} contextSetName name of the existing quick reply set to be used as a context menu - * @param {Boolean} isChained whether or not to chain the context menu quick replies + * @param {string} setName name of the existing quick reply set containing the quick reply + * @param {string|number} label label of the existing quick reply or its numeric ID + * @param {string} contextSetName name of the existing quick reply set to be used as a context menu + * @param {boolean} isChained whether or not to chain the context menu quick replies */ createContextItem(setName, label, contextSetName, isChained = false) { const qr = this.getQrByLabel(setName, label); @@ -315,9 +316,9 @@ export class QuickReplyApi { /** * Removes a quick reply set from a quick reply's context menu. * - * @param {String} setName name of the existing quick reply set containing the quick reply - * @param {String} label label of the existing quick reply - * @param {String} contextSetName name of the existing quick reply set to be used as a context menu + * @param {string} setName name of the existing quick reply set containing the quick reply + * @param {string|number} label label of the existing quick reply or its numeric ID + * @param {string} contextSetName name of the existing quick reply set to be used as a context menu */ deleteContextItem(setName, label, contextSetName) { const qr = this.getQrByLabel(setName, label); @@ -334,8 +335,8 @@ export class QuickReplyApi { /** * Removes all entries from a quick reply's context menu. * - * @param {String} setName name of the existing quick reply set containing the quick reply - * @param {String} label label of the existing quick reply + * @param {string} setName name of the existing quick reply set containing the quick reply + * @param {string|number} label label of the existing quick reply or its numeric ID */ clearContextMenu(setName, label) { const qr = this.getQrByLabel(setName, label); @@ -349,11 +350,11 @@ export class QuickReplyApi { /** * Create a new quick reply set. * - * @param {String} name name of the new quick reply set - * @param {Object} [props] - * @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box - * @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input - * @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply + * @param {string} name name of the new quick reply set + * @param {object} [props] + * @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box + * @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input + * @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply * @returns {Promise} the new quick reply set */ async createSet(name, { @@ -385,11 +386,11 @@ export class QuickReplyApi { /** * Update an existing quick reply set. * - * @param {String} name name of the existing quick reply set - * @param {Object} [props] - * @param {Boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box - * @param {Boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input - * @param {Boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply + * @param {string} name name of the existing quick reply set + * @param {object} [props] + * @param {boolean} [props.disableSend] whether or not to send the quick replies or put the message or slash command into the char input box + * @param {boolean} [props.placeBeforeInput] whether or not to place the quick reply contents before the existing user input + * @param {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply * @returns {Promise} the altered quick reply set */ async updateSet(name, { @@ -412,7 +413,7 @@ export class QuickReplyApi { /** * Delete an existing quick reply set. * - * @param {String} name name of the existing quick reply set + * @param {string} name name of the existing quick reply set */ async deleteSet(name) { const set = this.getSetByName(name); @@ -452,7 +453,7 @@ export class QuickReplyApi { /** * Gets a list of all quick replies in the quick reply set. * - * @param {String} setName name of the existing quick reply set + * @param {string} setName name of the existing quick reply set * @returns array with the labels of this set's quick replies */ listQuickReplies(setName) { diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index 16c3607c4..4f90464ea 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -50,6 +50,13 @@ export class SlashCommandHandler { return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr); }) ?? [], + /** All QRs inside a set, utilizing the "set" named argument, returns the QR's ID */ + qrIds: (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, enumIcons.qr, null, ()=>qr.id.toString(), true); + }) ?? [], + /** 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; @@ -237,8 +244,8 @@ export class SlashCommandHandler { name: 'label', description: 'text on the button, e.g., label=MyButton', typeList: [ARGUMENT_TYPE.STRING], - isRequired: true, - enumProvider: localEnumProviders.qrLabels, + isRequired: false, + enumProvider: localEnumProviders.qrEntries, }), 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'), @@ -250,6 +257,13 @@ export class SlashCommandHandler { ]; const qrUpdateArgs = [ new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false), + SlashCommandNamedArgument.fromProps({ + name: 'id', + description: 'numeric ID of the QR, e.g., id=42', + typeList: [ARGUMENT_TYPE.NUMBER], + isRequired: false, + enumProvider: localEnumProviders.qrIds, + }), ]; SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create', @@ -275,13 +289,61 @@ export class SlashCommandHandler { `, })); + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-get', + callback: (args, _) => { + return this.getQuickReply(args); + }, + namedArgumentList: [ + 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: false, + enumProvider: localEnumProviders.qrEntries, + }), + SlashCommandNamedArgument.fromProps({ + name: 'id', + description: 'numeric ID of the QR, e.g., id=42', + typeList: [ARGUMENT_TYPE.NUMBER], + isRequired: false, + enumProvider: localEnumProviders.qrIds, + }), + ], + returns: 'a dictionary with all the QR\'s properties', + helpString: ` +
Get a Quick Reply's properties.
+
+ Examples: +
    +
  • +
    /qr-get set=MyPreset label=MyButton | /echo
    +
    /qr-get set=MyPreset id=42 | /echo
    +
  • +
+
+ `, + })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update', callback: (args, message) => { this.updateQuickReply(args, message); return ''; }, returns: 'updated quick reply', - namedArgumentList: [...qrUpdateArgs, ...qrArgs], + namedArgumentList: [...qrUpdateArgs, ...qrArgs.map(it=>{ + if (it.name == 'label') { + const clone = SlashCommandNamedArgument.fromProps(it); + clone.isRequired = false; + return clone; + } + return it; + })], unnamedArgumentList: [ new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]), ], @@ -318,6 +380,12 @@ export class SlashCommandHandler { typeList: [ARGUMENT_TYPE.STRING], enumProvider: localEnumProviders.qrEntries, }), + SlashCommandNamedArgument.fromProps({ + name: 'id', + description: 'numeric ID of the QR, e.g., id=42', + typeList: [ARGUMENT_TYPE.NUMBER], + enumProvider: localEnumProviders.qrIds, + }), ], helpString: 'Deletes a Quick Reply from the specified set. If no label is provided, the entire set is deleted.', })); @@ -340,6 +408,12 @@ export class SlashCommandHandler { typeList: [ARGUMENT_TYPE.STRING], enumProvider: localEnumProviders.qrEntries, }), + SlashCommandNamedArgument.fromProps({ + name: 'id', + description: 'numeric ID of the QR, e.g., id=42', + typeList: [ARGUMENT_TYPE.NUMBER], + enumProvider: localEnumProviders.qrIds, + }), new SlashCommandNamedArgument( 'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), @@ -385,6 +459,12 @@ export class SlashCommandHandler { typeList: [ARGUMENT_TYPE.STRING], enumProvider: localEnumProviders.qrEntries, }), + SlashCommandNamedArgument.fromProps({ + name: 'id', + description: 'numeric ID of the QR, e.g., id=42', + typeList: [ARGUMENT_TYPE.NUMBER], + enumProvider: localEnumProviders.qrIds, + }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ @@ -421,6 +501,12 @@ export class SlashCommandHandler { isRequired: true, enumProvider: localEnumProviders.qrSets, }), + SlashCommandNamedArgument.fromProps({ + name: 'id', + description: 'numeric ID of the QR, e.g., id=42', + typeList: [ARGUMENT_TYPE.NUMBER], + enumProvider: localEnumProviders.qrIds, + }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ @@ -757,6 +843,13 @@ export class SlashCommandHandler { toastr.error(ex.message); } } + getQuickReply(args) { + try { + return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label)); + } catch (ex) { + toastr.error(ex.message); + } + } updateQuickReply(args, message) { try { this.api.updateQuickReply(