Add extension enable/disable commands - /extension-enable - /extension-disable - Optional "reload" parameter - /reload-page

eda7493a33785e949b7e04b40512515269a8defd

Wolfsblvt <wolfsblvt@gmail.com>

1 files changed, +138 -2Ignore whitespace
public/scripts/extensions.js+138 -2
@@ -1,8 +1,14 @@
11import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
22import { hideLoader, showLoader } from './loader.js';
33import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
4+import { SlashCommand } from './slash-commands/SlashCommand.js';
5+import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
6+import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
7+import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
8+import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
9+import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
410import { renderTemplate, renderTemplateAsync } from './templates.js';
511import { equalsIgnoreCaseAndAccents, isSubsetOf, isTrueBoolean, setValueByPath } from './utils.js';
612export {
713 getContext,
814 getApiUrl,
@@ -14,7 +20,9 @@ export {
1420 ModuleWorkerWrapper,
1521};
1622
23+/** @type {string[]} */
1724export let extensionNames = [];
25+
1826let manifests = {};
1927const defaultUrl = 'http://localhost:5100';
2028
@@ -1041,7 +1049,135 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
10411049 await installExtension(url);
10421050}
10431051
1052+/**
1053+ * @param {boolean} enable - Whether to enable or disable the extension
1054+ * @returns {(args: {[key: string]: string | SlashCommandClosure}, extensionName: string | SlashCommandClosure) => Promise<string>}
1055+ */
1056+function getExtensionToggleCallback(enable) {
1057+ return async (args, extensionName) => {
1058+ if (args?.reload instanceof SlashCommandClosure) throw new Error('\'reload\' argument cannot be a closure.');
1059+ if (typeof extensionName !== 'string') throw new Error('Extension name does only support string. Closures or arrays are not allowed.');
1060+ if (!extensionName) {
1061+ toastr.warning(`Extension name must be provided as an argument to ${enable ? 'enable' : 'disable'} this extension.`);
1062+ return '';
1063+ }
1064+
1065+ const reload = isTrueBoolean(args?.reload);
1066+
1067+ const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName));
1068+ if (!internalExtensionName) {
1069+ toastr.warning(`Extension ${extensionName} does not exist.`);
1070+ return '';
1071+ }
1072+ if (enable === !extension_settings.disabledExtensions.includes(internalExtensionName)) {
1073+ toastr.info(`Extension ${extensionName} is already ${enable ? 'enabled' : 'disabled'}.`);
1074+ return internalExtensionName;
1075+ }
1076+
1077+ reload && toastr.info(`${enable ? 'Enabling' : 'Disabling'} extension ${extensionName} and reloading...`);
1078+ await enableExtension(internalExtensionName, reload);
1079+ toastr.success(`Extension ${extensionName} ${enable ? 'enabled' : 'disabled'}.`);
1080+
1081+ return internalExtensionName;
1082+ };
1083+}
1084+
1085+function registerExtensionSlashCommands() {
1086+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1087+ name: 'extension-enable',
1088+ callback: getExtensionToggleCallback(true),
1089+ namedArgumentList: [
1090+ SlashCommandNamedArgument.fromProps({
1091+ name: 'reload',
1092+ description: 'Whether to reload the page after enabling the extension',
1093+ typeList: [ARGUMENT_TYPE.BOOLEAN],
1094+ defaultValue: 'true',
1095+ enumList: commonEnumProviders.boolean('trueFalse')(),
1096+ }),
1097+ ],
1098+ unnamedArgumentList: [
1099+ SlashCommandArgument.fromProps({
1100+ description: 'Extension name',
1101+ typeList: [ARGUMENT_TYPE.STRING],
1102+ isRequired: true,
1103+ enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
1104+ forceEnum: true,
1105+ }),
1106+ ],
1107+ helpString: `
1108+ <div>
1109+ Enables a specified extension.
1110+ </div>
1111+ <div>
1112+ By default, the page will be reloaded automatically, stopping any further commands.<br />
1113+ If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay disabled until refreshed.
1114+ The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
1115+ </div>
1116+ <div>
1117+ <strong>Example:</strong>
1118+ <ul>
1119+ <li>
1120+ <pre><code class="language-stscript">/extension-enable Summarize</code></pre>
1121+ </li>
1122+ </ul>
1123+ </div>
1124+ `,
1125+ }));
1126+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1127+ name: 'extension-disable',
1128+ callback: getExtensionToggleCallback(false),
1129+ namedArgumentList: [
1130+ SlashCommandNamedArgument.fromProps({
1131+ name: 'reload',
1132+ description: 'Whether to reload the page after disabling the extension',
1133+ typeList: [ARGUMENT_TYPE.BOOLEAN],
1134+ defaultValue: 'true',
1135+ enumList: commonEnumProviders.boolean('trueFalse')(),
1136+ }),
1137+ ],
1138+ unnamedArgumentList: [
1139+ SlashCommandArgument.fromProps({
1140+ description: 'Extension name',
1141+ typeList: [ARGUMENT_TYPE.STRING],
1142+ isRequired: true,
1143+ enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
1144+ forceEnum: true,
1145+ }),
1146+ ],
1147+ helpString: `
1148+ <div>
1149+ Disables a specified extension.
1150+ </div>
1151+ <div>
1152+ By default, the page will be reloaded automatically, stopping any further commands.<br />
1153+ If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay enabled until refreshed.
1154+ The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
1155+ </div>
1156+ <div>
1157+ <strong>Example:</strong>
1158+ <ul>
1159+ <li>
1160+ <pre><code class="language-stscript">/extension-disable Summarize</code></pre>
1161+ </li>
1162+ </ul>
1163+ </div>
1164+ `,
1165+ }));
1166+
1167+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1168+ name: 'reload-page',
1169+ callback: async () => {
1170+ toastr.info('Reloading the page...');
1171+ location.reload();
1172+ return '';
1173+ },
1174+ helpString: 'Reloads the current page. All further commands will not be processed.',
1175+ }));
1176+}
1177+
10441178export async function initExtensions() {
1179+ registerExtensionSlashCommands();
1180+
10451181 await addExtensionsButtonAndMenu();
10461182 $('#extensionsMenuButton').css('display', 'flex');
10471183