SillyTavern/public/scripts/extensions/quick-reply/api/QuickReplyApi.js

481 lines
18 KiB
JavaScript
Raw Normal View History

2023-12-22 14:55:37 +01:00
// eslint-disable-next-line no-unused-vars
2023-12-21 21:05:42 +01:00
import { QuickReply } from '../src/QuickReply.js';
import { QuickReplyContextLink } from '../src/QuickReplyContextLink.js';
import { QuickReplySet } from '../src/QuickReplySet.js';
2023-12-22 14:55:37 +01:00
// eslint-disable-next-line no-unused-vars
2023-12-21 21:05:42 +01:00
import { QuickReplySettings } from '../src/QuickReplySettings.js';
2023-12-22 14:55:37 +01:00
// eslint-disable-next-line no-unused-vars
2023-12-22 13:56:06 +01:00
import { SettingsUi } from '../src/ui/SettingsUi.js';
2024-02-25 02:54:40 +01:00
import { onlyUnique } from '../../../utils.js';
2023-12-21 21:05:42 +01:00
export class QuickReplyApi {
/**@type {QuickReplySettings}*/ settings;
2023-12-22 13:56:06 +01:00
/**@type {SettingsUi}*/ settingsUi;
2023-12-21 21:05:42 +01:00
2023-12-22 13:56:06 +01:00
constructor(/**@type {QuickReplySettings}*/settings, /**@type {SettingsUi}*/settingsUi) {
2023-12-21 21:05:42 +01:00
this.settings = settings;
2023-12-22 13:56:06 +01:00
this.settingsUi = settingsUi;
2023-12-21 21:05:42 +01:00
}
/**
* Finds and returns an existing Quick Reply Set by its name.
*
* @param {String} name name of the quick reply set
* @returns the quick reply set, or undefined if not found
*/
getSetByName(name) {
return QuickReplySet.get(name);
}
/**
* 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
* @returns the quick reply, or undefined if not found
*/
getQrByLabel(setName, label) {
const set = this.getSetByName(setName);
if (!set) return;
return set.qrList.find(it=>it.label == label);
}
/**
* Executes a quick reply by its index and returns the result.
*
* @param {Number} idx the index (zero-based) of the quick reply to execute
* @returns the return value of the quick reply, or undefined if not found
*/
async executeQuickReplyByIndex(idx) {
const qr = [...this.settings.config.setList, ...(this.settings.chatConfig?.setList ?? [])]
.map(it=>it.set.qrList)
.flat()[idx]
;
if (qr) {
return await qr.onExecute();
} else {
throw new Error(`No quick reply at index "${idx}"`);
}
}
2023-12-28 02:28:28 +01:00
/**
* 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
*/
async executeQuickReply(setName, label, args = {}) {
const qr = this.getQrByLabel(setName, label);
if (!qr) {
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
}
return await qr.execute(args);
}
2023-12-21 21:05:42 +01:00
/**
* 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
*/
toggleGlobalSet(name, isVisible = true) {
const set = this.getSetByName(name);
2023-12-22 13:55:25 +01:00
if (!set) {
throw new Error(`No quick reply set with name "${name}" found.`);
}
2023-12-21 21:05:42 +01:00
if (this.settings.config.hasSet(set)) {
this.settings.config.removeSet(set);
} else {
this.settings.config.addSet(set, isVisible);
}
}
/**
* 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
*/
addGlobalSet(name, isVisible = true) {
const set = this.getSetByName(name);
2023-12-22 13:55:25 +01:00
if (!set) {
throw new Error(`No quick reply set with name "${name}" found.`);
}
2023-12-21 21:05:42 +01:00
this.settings.config.addSet(set, isVisible);
}
/**
* Removes a quick reply set from the list of globally active quick reply sets.
*
* @param {String} name the name of the set
*/
removeGlobalSet(name) {
const set = this.getSetByName(name);
2023-12-22 13:55:25 +01:00
if (!set) {
throw new Error(`No quick reply set with name "${name}" found.`);
}
2023-12-21 21:05:42 +01:00
this.settings.config.removeSet(set);
}
/**
* 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
*/
toggleChatSet(name, isVisible = true) {
if (!this.settings.chatConfig) return;
const set = this.getSetByName(name);
2023-12-22 13:55:25 +01:00
if (!set) {
throw new Error(`No quick reply set with name "${name}" found.`);
}
2023-12-21 21:05:42 +01:00
if (this.settings.chatConfig.hasSet(set)) {
this.settings.chatConfig.removeSet(set);
} else {
this.settings.chatConfig.addSet(set, isVisible);
}
}
/**
* 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
*/
addChatSet(name, isVisible = true) {
if (!this.settings.chatConfig) return;
const set = this.getSetByName(name);
2023-12-22 13:55:25 +01:00
if (!set) {
throw new Error(`No quick reply set with name "${name}" found.`);
}
2023-12-21 21:05:42 +01:00
this.settings.chatConfig.addSet(set, isVisible);
}
/**
* 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
*/
removeChatSet(name) {
if (!this.settings.chatConfig) return;
const set = this.getSetByName(name);
2023-12-22 13:55:25 +01:00
if (!set) {
throw new Error(`No quick reply set with name "${name}" found.`);
}
2023-12-21 21:05:42 +01:00
this.settings.chatConfig.removeSet(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} 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
2024-01-18 17:08:38 +01:00
* @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
2023-12-21 21:05:42 +01:00
* @returns {QuickReply} the new quick reply
*/
createQuickReply(setName, label, {
message,
title,
isHidden,
executeOnStartup,
executeOnUser,
executeOnAi,
executeOnChatChange,
2024-01-18 17:08:38 +01:00
executeOnGroupMemberDraft,
automationId,
2023-12-21 21:05:42 +01:00
} = {}) {
const set = this.getSetByName(setName);
if (!set) {
throw new Error(`No quick reply set with named "${setName}" found.`);
}
const qr = set.addQuickReply();
qr.label = label ?? '';
qr.message = message ?? '';
qr.title = title ?? '';
qr.isHidden = isHidden ?? false;
qr.executeOnStartup = executeOnStartup ?? false;
qr.executeOnUser = executeOnUser ?? false;
qr.executeOnAi = executeOnAi ?? false;
qr.executeOnChatChange = executeOnChatChange ?? false;
2024-01-18 17:08:38 +01:00
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false;
qr.automationId = automationId ?? '';
2023-12-21 21:05:42 +01:00
qr.onUpdate();
return qr;
}
/**
* 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
2024-01-18 17:08:38 +01:00
* @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
2023-12-21 21:05:42 +01:00
* @returns {QuickReply} the altered quick reply
*/
updateQuickReply(setName, label, {
newLabel,
message,
title,
isHidden,
executeOnStartup,
executeOnUser,
executeOnAi,
executeOnChatChange,
2024-01-18 17:08:38 +01:00
executeOnGroupMemberDraft,
automationId,
2023-12-21 21:05:42 +01:00
} = {}) {
const qr = this.getQrByLabel(setName, label);
if (!qr) {
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
}
qr.updateLabel(newLabel ?? qr.label);
qr.updateMessage(message ?? qr.message);
qr.updateTitle(title ?? qr.title);
2023-12-21 21:05:42 +01:00
qr.isHidden = isHidden ?? qr.isHidden;
qr.executeOnStartup = executeOnStartup ?? qr.executeOnStartup;
qr.executeOnUser = executeOnUser ?? qr.executeOnUser;
qr.executeOnAi = executeOnAi ?? qr.executeOnAi;
qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange;
2024-01-18 17:08:38 +01:00
qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft;
qr.automationId = automationId ?? qr.automationId;
2023-12-21 21:05:42 +01:00
qr.onUpdate();
return qr;
}
/**
* 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)
*/
deleteQuickReply(setName, label) {
const qr = this.getQrByLabel(setName, label);
if (!qr) {
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
}
qr.delete();
}
/**
* 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
*/
createContextItem(setName, label, contextSetName, isChained = false) {
const qr = this.getQrByLabel(setName, label);
const set = this.getSetByName(contextSetName);
if (!qr) {
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
}
if (!set) {
throw new Error(`No quick reply set with name "${contextSetName}" found.`);
}
const cl = new QuickReplyContextLink();
cl.set = set;
cl.isChained = isChained;
qr.addContextLink(cl);
}
/**
* 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
*/
deleteContextItem(setName, label, contextSetName) {
const qr = this.getQrByLabel(setName, label);
const set = this.getSetByName(contextSetName);
if (!qr) {
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
}
if (!set) {
throw new Error(`No quick reply set with name "${contextSetName}" found.`);
}
qr.removeContextLink(set.name);
}
/**
* 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
*/
clearContextMenu(setName, label) {
const qr = this.getQrByLabel(setName, label);
if (!qr) {
throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`);
}
qr.clearContextLinks();
}
/**
* 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
2023-12-22 13:56:06 +01:00
* @returns {Promise<QuickReplySet>} the new quick reply set
2023-12-21 21:05:42 +01:00
*/
2023-12-22 13:56:06 +01:00
async createSet(name, {
2023-12-21 21:05:42 +01:00
disableSend,
placeBeforeInput,
injectInput,
} = {}) {
const set = new QuickReplySet();
set.name = name;
set.disableSend = disableSend ?? false;
set.placeBeforeInput = placeBeforeInput ?? false;
set.injectInput = injectInput ?? false;
2023-12-22 13:56:06 +01:00
const oldSet = this.getSetByName(name);
if (oldSet) {
QuickReplySet.list.splice(QuickReplySet.list.indexOf(oldSet), 1, set);
} else {
const idx = QuickReplySet.list.findIndex(it=>it.name.localeCompare(name) == 1);
if (idx > -1) {
QuickReplySet.list.splice(idx, 0, set);
} else {
QuickReplySet.list.push(set);
}
}
await set.save();
this.settingsUi.rerender();
2023-12-21 21:05:42 +01:00
return set;
}
/**
* 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
2023-12-22 13:56:06 +01:00
* @returns {Promise<QuickReplySet>} the altered quick reply set
2023-12-21 21:05:42 +01:00
*/
2023-12-22 13:56:06 +01:00
async updateSet(name, {
2023-12-21 21:05:42 +01:00
disableSend,
placeBeforeInput,
injectInput,
} = {}) {
const set = this.getSetByName(name);
if (!set) {
throw new Error(`No quick reply set with name "${name}" found.`);
}
set.disableSend = disableSend ?? false;
set.placeBeforeInput = placeBeforeInput ?? false;
set.injectInput = injectInput ?? false;
2023-12-22 13:56:06 +01:00
await set.save();
this.settingsUi.rerender();
2023-12-21 21:05:42 +01:00
return set;
}
2023-12-22 13:56:06 +01:00
/**
* Delete an existing quick reply set.
*
* @param {String} name name of the existing quick reply set
*/
async deleteSet(name) {
const set = this.getSetByName(name);
if (!set) {
throw new Error(`No quick reply set with name "${name}" found.`);
}
await set.delete();
this.settingsUi.rerender();
}
/**
* Gets a list of all quick reply sets.
*
* @returns array with the names of all quick reply sets
*/
listSets() {
return QuickReplySet.list.map(it=>it.name);
}
/**
* Gets a list of all globally active quick reply sets.
*
* @returns array with the names of all quick reply sets
*/
listGlobalSets() {
return this.settings.config.setList.map(it=>it.set.name);
}
/**
* Gets a list of all quick reply sets activated by the current chat.
*
* @returns array with the names of all quick reply sets
*/
listChatSets() {
return this.settings.chatConfig?.setList?.flatMap(it=>it.set.name) ?? [];
}
/**
* Gets a list of all quick replies in the 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) {
const set = this.getSetByName(setName);
if (!set) {
throw new Error(`No quick reply set with name "${name}" found.`);
}
return set.qrList.map(it=>it.label);
}
2024-02-25 02:54:40 +01:00
/**
* Gets a list of all Automation IDs used by quick replies.
*
* @returns {String[]} array with all automation IDs used by quick replies
*/
listAutomationIds() {
return this
.listSets()
.flatMap(it => ({ set: it, qrs: this.listQuickReplies(it) }))
.map(it => it.qrs?.map(qr => this.getQrByLabel(it.set, qr)?.automationId))
.flat()
.filter(Boolean)
.filter(onlyUnique)
.map(String);
}
2023-12-21 21:05:42 +01:00
}