diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 773719f70..fa669fd14 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -52,6 +52,8 @@ import { textgen_types, textgenerationwebui_settings } from './textgen-settings. import { decodeTextTokens, getFriendlyTokenizerName, getTextTokens, getTokenCount } from './tokenizers.js'; import { debounce, delay, escapeRegex, isFalseBoolean, isTrueBoolean, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js'; import { registerVariableCommands, resolveVariable } from './variables.js'; +import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; +import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; export { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand, }; @@ -523,21 +525,35 @@ function getMessagesCallback(args, value) { async function runCallback(args, name) { if (!name) { - toastr.warning('No name provided for /run command'); - return ''; + throw new Error('No name provided for /run command'); + } + + /**@type {SlashCommandScope} */ + const scope = args._scope; + if (scope.existsVariable(name)) { + const closure = scope.getVariable(name); + if (!(closure instanceof SlashCommandClosure)) { + throw new Error(`"${name}" is not callable.`); + } + closure.scope.parent = scope; + Object.keys(closure.arguments).forEach(key=>{ + if (Object.keys(args).includes(key)) { + closure.providedArguments[key] = args[key]; + } + }); + const result = await closure.execute(); + return result.pipe; } if (typeof window['executeQuickReplyByName'] !== 'function') { - toastr.warning('Quick Reply extension is not loaded'); - return ''; + throw new Error('Quick Reply extension is not loaded'); } try { name = name.trim(); return await window['executeQuickReplyByName'](name, args); } catch (error) { - toastr.error(`Error running Quick Reply "${name}": ${error.message}`, 'Error'); - return ''; + throw new Error(`Error running Quick Reply "${name}": ${error.message}`, 'Error'); } } diff --git a/public/scripts/slash-commands/SlashCommandClosure.js b/public/scripts/slash-commands/SlashCommandClosure.js index a86153c44..a6c6d0408 100644 --- a/public/scripts/slash-commands/SlashCommandClosure.js +++ b/public/scripts/slash-commands/SlashCommandClosure.js @@ -11,7 +11,7 @@ export class SlashCommandClosure { /**@type {Map}*/ arguments = {}; // @ts-ignore /**@type {Map}*/ providedArguments = {}; - /**@type {(SlashCommandExecutor|SlashCommandClosureExecutor)[]}*/ executorList = []; + /**@type {SlashCommandExecutor[]}*/ executorList = []; /**@type {String}*/ keptText; constructor(parent) { diff --git a/public/scripts/slash-commands/SlashCommandParser.js b/public/scripts/slash-commands/SlashCommandParser.js index c8b07f919..2ef687b4d 100644 --- a/public/scripts/slash-commands/SlashCommandParser.js +++ b/public/scripts/slash-commands/SlashCommandParser.js @@ -233,9 +233,6 @@ export class SlashCommandParser { this.discardWhitespace(); } while (!this.testClosureEnd()) { - if (this.testClosureCall()) { - closure.executorList.push(this.parseClosureCall()); - } if (this.testCommand()) { closure.executorList.push(this.parseCommand()); } else { @@ -244,57 +241,15 @@ export class SlashCommandParser { while (/\s|\|/.test(this.char)) this.take(); // discard whitespace and pipe (command separator) } this.take(2); // discard closing :} - this.discardWhitespace(); - if (this.char == '(') { - this.take(); // discard opening ( + if (this.char == '(' && this.ahead[0] == ')') { + this.take(2); // discard () closure.executeNow = true; - this.discardWhitespace(); - while (this.testNamedArgument()) { - const arg = this.parseNamedArgument(); - if (!Object.keys(closure.arguments).includes(arg.key)) throw new SlashCommandParserError(`Invalid named argument for closure "${arg.key}" at position ${this.index - 2}`, this.text, this.index); - closure.providedArguments[arg.key] = arg.value; - this.discardWhitespace(); - } - // @ts-ignore - if (this.char != ')') throw new SlashCommandParserError(`Missing closing ")" at position ${this.index - 2}.`, this.text, this.index); - this.take(); // discard closing ) } - while (/\s/.test(this.char)) this.take(); // discard trailing whitespace + this.discardWhitespace(); // discard trailing whitespace this.scope = closure.scope.parent; return closure; } - testClosureCall() { - return this.char == '/' - && this.behind.slice(-1) != '\\' - && !['/', '#'].includes(this.ahead[0]) - && /^\S+\(/.test(this.ahead) - ; - } - testClosureCallEnd() { - return this.char == ')' && this.behind.slice(-1) != '\\'; - } - parseClosureCall() { - this.take(); // discard / - let name = ''; - while (!/\s|\(/.test(this.char)) name += this.take(); // take chars until opening ( - this.take(); // discard opening ( - const executor = new SlashCommandClosureExecutor(); - executor.name = name; - this.discardWhitespace(); - while (this.testNamedArgument()) { - const arg = this.parseNamedArgument(); - executor.providedArguments[arg.key] = arg.value; - this.discardWhitespace(); - } - if (this.testClosureCallEnd()) { - this.take(); // discard closing ) - return executor; - } else { - throw new SlashCommandParserError(`Unexpected end of closure call at position ${this.index - 2}`, this.text, this.index); - } - } - testCommand() { return this.char == '/' && this.behind.slice(-1) != '\\' && !['/', '#'].includes(this.ahead[0]); }