add referencing QRs by ID

This commit is contained in:
LenAnderson 2024-07-10 22:39:21 -04:00
parent 36265579a2
commit 1a18b5b180
2 changed files with 161 additions and 67 deletions

View File

@ -26,7 +26,7 @@ export class QuickReplyApi {
/** /**
* Finds and returns an existing Quick Reply Set by its name. * 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 * @returns the quick reply set, or undefined if not found
*/ */
getSetByName(name) { getSetByName(name) {
@ -36,13 +36,14 @@ export class QuickReplyApi {
/** /**
* Finds and returns an existing Quick Reply by its set's name and its label. * 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} setName name of the quick reply set
* @param {String} label label of the quick reply * @param {string|number} label label or numeric ID of the quick reply
* @returns the quick reply, or undefined if not found * @returns the quick reply, or undefined if not found
*/ */
getQrByLabel(setName, label) { getQrByLabel(setName, label) {
const set = this.getSetByName(setName); const set = this.getSetByName(setName);
if (!set) return; if (!set) return;
if (Number.isInteger(label)) return set.qrList.find(it=>it.id == label);
return set.qrList.find(it=>it.label == label); return set.qrList.find(it=>it.label == label);
} }
@ -70,9 +71,9 @@ export class QuickReplyApi {
/** /**
* Executes an existing quick reply. * Executes an existing quick reply.
* *
* @param {String} setName name of the existing quick reply set * @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|number} label label of the existing quick reply (text on the button) or its numeric ID
* @param {Object} [args] optional arguments * @param {object} [args] optional arguments
* @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options] optional execution options * @param {import('../../../slash-commands.js').ExecuteSlashCommandsOptions} [options] optional execution options
*/ */
async executeQuickReply(setName, label, args = {}, 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. * 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 {string} name the name of the set
* @param {Boolean} isVisible whether to show the set's buttons or not * @param {boolean} isVisible whether to show the set's buttons or not
*/ */
toggleGlobalSet(name, isVisible = true) { toggleGlobalSet(name, isVisible = true) {
const set = this.getSetByName(name); 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. * Adds a quick reply set to the list of globally active quick reply sets.
* *
* @param {String} name the name of the set * @param {string} name the name of the set
* @param {Boolean} isVisible whether to show the set's buttons or not * @param {boolean} isVisible whether to show the set's buttons or not
*/ */
addGlobalSet(name, isVisible = true) { addGlobalSet(name, isVisible = true) {
const set = this.getSetByName(name); 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. * 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) { removeGlobalSet(name) {
const set = this.getSetByName(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. * 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 {string} name the name of the set
* @param {Boolean} isVisible whether to show the set's buttons or not * @param {boolean} isVisible whether to show the set's buttons or not
*/ */
toggleChatSet(name, isVisible = true) { toggleChatSet(name, isVisible = true) {
if (!this.settings.chatConfig) return; 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. * 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 {string} name the name of the set
* @param {Boolean} isVisible whether to show the set's buttons or not * @param {boolean} isVisible whether to show the set's buttons or not
*/ */
addChatSet(name, isVisible = true) { addChatSet(name, isVisible = true) {
if (!this.settings.chatConfig) return; 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. * 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) { removeChatSet(name) {
if (!this.settings.chatConfig) return; if (!this.settings.chatConfig) return;
@ -182,18 +183,18 @@ export class QuickReplyApi {
/** /**
* Creates a new quick reply in an existing quick reply set. * 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} 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 {string} label label for the new quick reply (text on the button)
* @param {Object} [props] * @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.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 {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.isHidden] whether to hide or show the button
* @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts * @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.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.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.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 {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} [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 * @returns {QuickReply} the new quick reply
*/ */
createQuickReply(setName, label, { createQuickReply(setName, label, {
@ -229,19 +230,19 @@ export class QuickReplyApi {
/** /**
* Updates an existing quick reply. * Updates an existing quick reply.
* *
* @param {String} setName name of the existing quick reply set * @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|number} label label of the existing quick reply (text on the button) or its numeric ID
* @param {Object} [props] * @param {object} [props]
* @param {String} [props.newLabel] new label for quick reply (text on the button) * @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.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 {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.isHidden] whether to hide or show the button
* @param {Boolean} [props.executeOnStartup] whether to execute the quick reply when SillyTavern starts * @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.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.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.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 {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} [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 * @returns {QuickReply} the altered quick reply
*/ */
updateQuickReply(setName, label, { updateQuickReply(setName, label, {
@ -277,8 +278,8 @@ export class QuickReplyApi {
/** /**
* Deletes an existing quick reply. * Deletes an existing quick reply.
* *
* @param {String} setName name of the existing quick reply set * @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|number} label label of the existing quick reply (text on the button) or its numeric ID
*/ */
deleteQuickReply(setName, label) { deleteQuickReply(setName, label) {
const qr = this.getQrByLabel(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. * 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} setName name of the existing quick reply set containing the quick reply
* @param {String} label label of the existing 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 {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 {boolean} isChained whether or not to chain the context menu quick replies
*/ */
createContextItem(setName, label, contextSetName, isChained = false) { createContextItem(setName, label, contextSetName, isChained = false) {
const qr = this.getQrByLabel(setName, label); 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. * 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} setName name of the existing quick reply set containing the quick reply
* @param {String} label label of the existing 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 {string} contextSetName name of the existing quick reply set to be used as a context menu
*/ */
deleteContextItem(setName, label, contextSetName) { deleteContextItem(setName, label, contextSetName) {
const qr = this.getQrByLabel(setName, label); const qr = this.getQrByLabel(setName, label);
@ -334,8 +335,8 @@ export class QuickReplyApi {
/** /**
* Removes all entries from a quick reply's context menu. * 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} setName name of the existing quick reply set containing the quick reply
* @param {String} label label of the existing quick reply * @param {string|number} label label of the existing quick reply or its numeric ID
*/ */
clearContextMenu(setName, label) { clearContextMenu(setName, label) {
const qr = this.getQrByLabel(setName, label); const qr = this.getQrByLabel(setName, label);
@ -349,11 +350,11 @@ export class QuickReplyApi {
/** /**
* Create a new quick reply set. * Create a new quick reply set.
* *
* @param {String} name name of the new quick reply set * @param {string} name name of the new quick reply set
* @param {Object} [props] * @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.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.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 {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
* @returns {Promise<QuickReplySet>} the new quick reply set * @returns {Promise<QuickReplySet>} the new quick reply set
*/ */
async createSet(name, { async createSet(name, {
@ -385,11 +386,11 @@ export class QuickReplyApi {
/** /**
* Update an existing quick reply set. * Update 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
* @param {Object} [props] * @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.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.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 {boolean} [props.injectInput] whether or not to automatically inject the user input at the end of the quick reply
* @returns {Promise<QuickReplySet>} the altered quick reply set * @returns {Promise<QuickReplySet>} the altered quick reply set
*/ */
async updateSet(name, { async updateSet(name, {
@ -412,7 +413,7 @@ export class QuickReplyApi {
/** /**
* Delete an existing quick reply set. * 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) { async deleteSet(name) {
const set = this.getSetByName(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. * 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 * @returns array with the labels of this set's quick replies
*/ */
listQuickReplies(setName) { listQuickReplies(setName) {

View File

@ -50,6 +50,13 @@ export class SlashCommandHandler {
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr); 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 */ /** All QRs as a set.name string, to be able to execute, for example via the /run command */
qrExecutables: () => { qrExecutables: () => {
const globalSetList = this.api.settings.config.setList; const globalSetList = this.api.settings.config.setList;
@ -237,8 +244,8 @@ export class SlashCommandHandler {
name: 'label', name: 'label',
description: 'text on the button, e.g., label=MyButton', description: 'text on the button, e.g., label=MyButton',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
isRequired: true, isRequired: false,
enumProvider: localEnumProviders.qrLabels, enumProvider: localEnumProviders.qrEntries,
}), }),
new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), 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('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 = [ const qrUpdateArgs = [
new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false), 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', SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create',
@ -275,13 +289,61 @@ export class SlashCommandHandler {
</div> </div>
`, `,
})); }));
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: `
<div>Get a Quick Reply's properties.</div>
<div>
<strong>Examples:</strong>
<ul>
<li>
<pre><code>/qr-get set=MyPreset label=MyButton | /echo</code></pre>
<pre><code>/qr-get set=MyPreset id=42 | /echo</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update', SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update',
callback: (args, message) => { callback: (args, message) => {
this.updateQuickReply(args, message); this.updateQuickReply(args, message);
return ''; return '';
}, },
returns: 'updated quick reply', 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: [ unnamedArgumentList: [
new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]), new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
], ],
@ -318,6 +380,12 @@ export class SlashCommandHandler {
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.qrEntries, 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.', 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], typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.qrEntries, 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( new SlashCommandNamedArgument(
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', 'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
), ),
@ -385,6 +459,12 @@ export class SlashCommandHandler {
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.qrEntries, 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: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
@ -421,6 +501,12 @@ export class SlashCommandHandler {
isRequired: true, isRequired: true,
enumProvider: localEnumProviders.qrSets, 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: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
@ -757,6 +843,13 @@ export class SlashCommandHandler {
toastr.error(ex.message); 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) { updateQuickReply(args, message) {
try { try {
this.api.updateQuickReply( this.api.updateQuickReply(