Refactor extension slash commands into own file

- Weird circle imports again with the slash command classes
This commit is contained in:
Wolfsblvt 2024-09-25 23:18:37 +02:00
parent 1a6f0c0922
commit 169504aa68
3 changed files with 281 additions and 277 deletions

View File

@ -244,6 +244,7 @@ import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCo
import { initInputMarkdown } from './scripts/input-md-formatting.js';
import { AbortReason } from './scripts/util/AbortReason.js';
import { initSystemPrompts } from './scripts/sysprompt.js';
import { registerExtensionSlashCommands } from './scripts/extensions-slashcommands.js';
//exporting functions and vars for mods
export {
@ -957,6 +958,7 @@ async function firstLoadInit() {
initLogprobs();
initInputMarkdown();
initExtensions();
registerExtensionSlashCommands();
doDailyExtensionUpdatesCheck();
await hideLoader();
await fixViewport();

View File

@ -0,0 +1,276 @@
import { disableExtension, enableExtension, extension_settings, extensionNames } from './extensions.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { equalsIgnoreCaseAndAccents, isFalseBoolean, isTrueBoolean } from './utils.js';
/**
* @param {'enable' | 'disable' | 'toggle'} action - The action to perform on the extension
* @returns {(args: {[key: string]: string | SlashCommandClosure}, extensionName: string | SlashCommandClosure) => Promise<string>}
*/
function getExtensionActionCallback(action) {
return async (args, extensionName) => {
if (args?.reload instanceof SlashCommandClosure) throw new Error('\'reload\' argument cannot be a closure.');
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
if (!extensionName) {
toastr.warning(`Extension name must be provided as an argument to ${action} this extension.`);
return '';
}
const reload = isTrueBoolean(args?.reload);
const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName));
if (!internalExtensionName) {
toastr.warning(`Extension ${extensionName} does not exist.`);
return '';
}
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
if (action === 'enable' && isEnabled) {
toastr.info(`Extension ${extensionName} is already enabled.`);
return internalExtensionName;
}
if (action === 'disable' && !isEnabled) {
toastr.info(`Extension ${extensionName} is already disabled.`);
return internalExtensionName;
}
if (action === 'toggle') {
action = isEnabled ? 'disable' : 'enable';
}
reload && toastr.info(`${action.charAt(0).toUpperCase() + action.slice(1)}ing extension ${extensionName} and reloading...`);
if (action === 'enable') {
await enableExtension(internalExtensionName, reload);
} else {
await disableExtension(internalExtensionName, reload);
}
toastr.success(`Extension ${extensionName} ${action}d.`);
return internalExtensionName;
};
}
export function registerExtensionSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-enable',
callback: getExtensionActionCallback('enable'),
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'reload',
description: 'Whether to reload the page after enabling the extension',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'true',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Enables a specified extension.
</div>
<div>
By default, the page will be reloaded automatically, stopping any further commands.<br />
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay disabled until refreshed.
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-enable Summarize</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-disable',
callback: getExtensionActionCallback('enable'),
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'reload',
description: 'Whether to reload the page after disabling the extension',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'true',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Disables a specified extension.
</div>
<div>
By default, the page will be reloaded automatically, stopping any further commands.<br />
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay enabled until refreshed.
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-disable Summarize</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-toggle',
callback: async (args, extensionName) => {
if (args?.state instanceof SlashCommandClosure) throw new Error('\'state\' argument cannot be a closure.');
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
const action = isTrueBoolean(args?.state) ? 'enable' :
isFalseBoolean(args?.state) ? 'disable' :
'toggle';
return await getExtensionActionCallback(action)(args, extensionName);
},
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'reload',
description: 'Whether to reload the page after toggling the extension',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'true',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
SlashCommandNamedArgument.fromProps({
name: 'state',
description: 'Explicitly set the state of the extension (true to enable, false to disable). If not provided, the state will be toggled to the opposite of the current state.',
typeList: [ARGUMENT_TYPE.BOOLEAN],
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Toggles the state of a specified extension.
</div>
<div>
By default, the page will be reloaded automatically, stopping any further commands.<br />
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay in its current state until refreshed.
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-toggle Summarize</code></pre>
</li>
<li>
<pre><code class="language-stscript">/extension-toggle Summarize state=true</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-state',
callback: async (_, extensionName) => {
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName));
if (!internalExtensionName) {
toastr.warning(`Extension ${extensionName} does not exist.`);
return '';
}
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
return String(isEnabled);
},
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Returns the state of a specified extension (true if enabled, false if disabled).
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-state Summarize</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-exists',
aliases: ['extension-installed'],
callback: async (_, extensionName) => {
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
const exists = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName)) !== undefined;
return exists ? 'true' : 'false';
},
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Checks if a specified extension exists.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-exists LALib</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'reload-page',
callback: async () => {
toastr.info('Reloading the page...');
location.reload();
return '';
},
helpString: 'Reloads the current page. All further commands will not be processed.',
}));
}

View File

@ -1,14 +1,8 @@
import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
import { showLoader } from './loader.js';
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { renderTemplate, renderTemplateAsync } from './templates.js';
import { equalsIgnoreCaseAndAccents, isFalseBoolean, isSubsetOf, isTrueBoolean, setValueByPath } from './utils.js';
import { isSubsetOf, setValueByPath } from './utils.js';
export {
getContext,
getApiUrl,
@ -249,7 +243,7 @@ function onEnableExtensionClick() {
enableExtension(name, false);
}
async function enableExtension(name, reload = true) {
export async function enableExtension(name, reload = true) {
extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
stateChanged = true;
await saveSettings();
@ -260,7 +254,7 @@ async function enableExtension(name, reload = true) {
}
}
async function disableExtension(name, reload = true) {
export async function disableExtension(name, reload = true) {
extension_settings.disabledExtensions.push(name);
stateChanged = true;
await saveSettings();
@ -1049,277 +1043,9 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
await installExtension(url);
}
/**
* @param {'enable' | 'disable' | 'toggle'} action - The action to perform on the extension
* @returns {(args: {[key: string]: string | SlashCommandClosure}, extensionName: string | SlashCommandClosure) => Promise<string>}
*/
function getExtensionActionCallback(action) {
return async (args, extensionName) => {
if (args?.reload instanceof SlashCommandClosure) throw new Error('\'reload\' argument cannot be a closure.');
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
if (!extensionName) {
toastr.warning(`Extension name must be provided as an argument to ${action} this extension.`);
return '';
}
const reload = isTrueBoolean(args?.reload);
const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName));
if (!internalExtensionName) {
toastr.warning(`Extension ${extensionName} does not exist.`);
return '';
}
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
if (action === 'enable' && isEnabled) {
toastr.info(`Extension ${extensionName} is already enabled.`);
return internalExtensionName;
}
if (action === 'disable' && !isEnabled) {
toastr.info(`Extension ${extensionName} is already disabled.`);
return internalExtensionName;
}
if (action === 'toggle') {
action = isEnabled ? 'disable' : 'enable';
}
reload && toastr.info(`${action.charAt(0).toUpperCase() + action.slice(1)}ing extension ${extensionName} and reloading...`);
if (action === 'enable') {
await enableExtension(internalExtensionName, reload);
} else {
await disableExtension(internalExtensionName, reload);
}
toastr.success(`Extension ${extensionName} ${action}d.`);
return internalExtensionName;
};
}
function registerExtensionSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-enable',
callback: getExtensionActionCallback('enable'),
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'reload',
description: 'Whether to reload the page after enabling the extension',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'true',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Enables a specified extension.
</div>
<div>
By default, the page will be reloaded automatically, stopping any further commands.<br />
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay disabled until refreshed.
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-enable Summarize</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-disable',
callback: getExtensionActionCallback('enable'),
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'reload',
description: 'Whether to reload the page after disabling the extension',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'true',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Disables a specified extension.
</div>
<div>
By default, the page will be reloaded automatically, stopping any further commands.<br />
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay enabled until refreshed.
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-disable Summarize</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-toggle',
callback: async (args, extensionName) => {
if (args?.state instanceof SlashCommandClosure) throw new Error('\'state\' argument cannot be a closure.');
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
const action = isTrueBoolean(args?.state) ? 'enable' :
isFalseBoolean(args?.state) ? 'disable' :
'toggle';
return await getExtensionActionCallback(action)(args, extensionName);
},
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'reload',
description: 'Whether to reload the page after toggling the extension',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'true',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
SlashCommandNamedArgument.fromProps({
name: 'state',
description: 'Explicitly set the state of the extension (true to enable, false to disable). If not provided, the state will be toggled to the opposite of the current state.',
typeList: [ARGUMENT_TYPE.BOOLEAN],
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Toggles the state of a specified extension.
</div>
<div>
By default, the page will be reloaded automatically, stopping any further commands.<br />
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay in its current state until refreshed.
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-toggle Summarize</code></pre>
</li>
<li>
<pre><code class="language-stscript">/extension-toggle Summarize state=true</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-state',
callback: async (_, extensionName) => {
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName));
if (!internalExtensionName) {
toastr.warning(`Extension ${extensionName} does not exist.`);
return '';
}
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
return String(isEnabled);
},
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Returns the state of a specified extension (true if enabled, false if disabled).
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-state Summarize</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'extension-exists',
aliases: ['extension-installed'],
callback: async (_, extensionName) => {
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
const exists = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName)) !== undefined;
return exists ? 'true' : 'false';
},
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'Extension name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
forceEnum: true,
}),
],
helpString: `
<div>
Checks if a specified extension exists.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code class="language-stscript">/extension-exists LALib</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'reload-page',
callback: async () => {
toastr.info('Reloading the page...');
location.reload();
return '';
},
helpString: 'Reloads the current page. All further commands will not be processed.',
}));
}
export async function initExtensions() {
registerExtensionSlashCommands();
await addExtensionsButtonAndMenu();
$('#extensionsMenuButton').css('display', 'flex');