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

eda7493a33785e949b7e04b40512515269a8defd

Wolfsblvt <wolfsblvt@gmail.com>

1 files changed, +138 -2Showing whitespace changes
public/scripts/extensions.js+138 -2
@@ -1,8 +1,14 @@
1import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';1import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
2import { hideLoader, showLoader } from './loader.js';2import { showLoader } from './loader.js';
3import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';3import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
4import { SlashCommand } from './slash-commands/SlashCommand.js';
5import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
6import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
7import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
8import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
9import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
4import { renderTemplate, renderTemplateAsync } from './templates.js';10import { renderTemplate, renderTemplateAsync } from './templates.js';
5import { isSubsetOf, setValueByPath } from './utils.js';11import { equalsIgnoreCaseAndAccents, isSubsetOf, isTrueBoolean, setValueByPath } from './utils.js';
6export {12export {
7 getContext,13 getContext,
8 getApiUrl,14 getApiUrl,
@@ -14,7 +20,9 @@ export {
14 ModuleWorkerWrapper,20 ModuleWorkerWrapper,
15};21};
1622
23/** @type {string[]} */
17export let extensionNames = [];24export let extensionNames = [];
25
18let manifests = {};26let manifests = {};
19const defaultUrl = 'http://localhost:5100';27const defaultUrl = 'http://localhost:5100';
2028
@@ -1041,7 +1049,135 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
1041 await installExtension(url);1049 await installExtension(url);
1042}1050}
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 */
1056function 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
1085function 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
1044export async function initExtensions() {1178export async function initExtensions() {
1179 registerExtensionSlashCommands();
1180
1045 await addExtensionsButtonAndMenu();1181 await addExtensionsButtonAndMenu();
1046 $('#extensionsMenuButton').css('display', 'flex');1182 $('#extensionsMenuButton').css('display', 'flex');
10471183