mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-03 12:47:35 +01:00
subcommand and /abort fixes
- use AbortController in /abort instead of execption - allow quiet abort - allow loud abort - allow abort reason - abort when aborted in subcommand - break out of loops when aborted inside - fix parsing of subcommands with multiple commands
This commit is contained in:
parent
909ec4191d
commit
87cc28ae28
@ -702,6 +702,19 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'addswipe',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'abort',
|
||||
callback: abortCallback,
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({ name: 'quiet',
|
||||
description: 'Whether to suppress the toast message notifying about the /abort call.',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'true',
|
||||
enumList: ['true', 'false'],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({ description: 'The reason for aborting command execution. Shown when quiet=false',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
}),
|
||||
],
|
||||
helpString: 'Aborts the slash command batch execution.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'fuzzy',
|
||||
@ -1419,9 +1432,15 @@ async function runCallback(args, name) {
|
||||
}
|
||||
}
|
||||
|
||||
function abortCallback() {
|
||||
$('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles:true }));
|
||||
throw new Error('/abort command executed');
|
||||
/**
|
||||
*
|
||||
* @param {object} param0
|
||||
* @param {SlashCommandAbortController} param0._abortController
|
||||
* @param {string} [param0.quiet]
|
||||
* @param {string} [reason]
|
||||
*/
|
||||
function abortCallback({ _abortController, quiet }, reason) {
|
||||
_abortController.abort((reason ?? '').toString().length == 0 ? '/abort command executed' : reason, !isFalseBoolean(quiet ?? 'true'));
|
||||
}
|
||||
|
||||
async function delayCallback(_, amount) {
|
||||
@ -2782,7 +2801,7 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
||||
|
||||
let closure;
|
||||
try {
|
||||
closure = parser.parse(text, true, options.parserFlags, options.abortController);
|
||||
closure = parser.parse(text, true, options.parserFlags, options.abortController ?? new SlashCommandAbortController());
|
||||
closure.scope.parent = options.scope;
|
||||
closure.onProgress = options.onProgress;
|
||||
} catch (e) {
|
||||
@ -2809,7 +2828,7 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
||||
|
||||
try {
|
||||
const result = await closure.execute();
|
||||
if (result.isAborted) {
|
||||
if (result.isAborted && !result.isQuietlyAborted) {
|
||||
toastr.warning(result.abortReason, 'Command execution aborted');
|
||||
}
|
||||
return result;
|
||||
|
@ -5,7 +5,8 @@ export class SlashCommandAbortController {
|
||||
constructor() {
|
||||
this.signal = new SlashCommandAbortSignal();
|
||||
}
|
||||
abort(reason = 'No reason.') {
|
||||
abort(reason = 'No reason.', isQuiet = false) {
|
||||
this.signal.isQuiet = isQuiet;
|
||||
this.signal.aborted = true;
|
||||
this.signal.reason = reason;
|
||||
}
|
||||
@ -20,8 +21,8 @@ export class SlashCommandAbortController {
|
||||
}
|
||||
|
||||
export class SlashCommandAbortSignal {
|
||||
/**@type {boolean}*/ isQuiet = false;
|
||||
/**@type {boolean}*/ paused = false;
|
||||
/**@type {boolean}*/ aborted = false;
|
||||
/**@type {string}*/ reason = null;
|
||||
|
||||
}
|
||||
|
@ -161,6 +161,7 @@ export class SlashCommandClosure {
|
||||
let args = {
|
||||
_scope: this.scope,
|
||||
_parserFlags: executor.parserFlags,
|
||||
_abortController: this.abortController,
|
||||
};
|
||||
let value;
|
||||
// substitute named arguments
|
||||
@ -254,6 +255,7 @@ export class SlashCommandClosure {
|
||||
if (this.abortController?.signal?.aborted) {
|
||||
const result = new SlashCommandClosureResult();
|
||||
result.isAborted = true;
|
||||
result.isQuietlyAborted = this.abortController.signal.isQuiet;
|
||||
result.abortReason = this.abortController.signal.reason.toString();
|
||||
return result;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ export class SlashCommandClosureResult {
|
||||
/**@type {boolean}*/ interrupt = false;
|
||||
/**@type {string}*/ pipe;
|
||||
/**@type {boolean}*/ isAborted = false;
|
||||
/**@type {boolean}*/ isQuietlyAborted = false;
|
||||
/**@type {string}*/ abortReason;
|
||||
/**@type {boolean}*/ isError = false;
|
||||
/**@type {string}*/ errorMessage;
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js';
|
||||
import { extension_settings, saveMetadataDebounced } from './extensions.js';
|
||||
import { executeSlashCommands } from './slash-commands.js';
|
||||
import { executeSlashCommands, executeSlashCommandsWithOptions } from './slash-commands.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||
import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
|
||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
|
||||
import { isFalseBoolean } from './utils.js';
|
||||
|
||||
@ -317,58 +319,101 @@ function listVariablesCallback() {
|
||||
async function whileCallback(args, command) {
|
||||
const isGuardOff = isFalseBoolean(args.guard);
|
||||
const iterations = isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS;
|
||||
if (command) {
|
||||
if (command[0] instanceof SlashCommandClosure) {
|
||||
command = command[0];
|
||||
} else {
|
||||
command = command.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
let commandResult;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const { a, b, rule } = parseBooleanOperands(args);
|
||||
const result = evalBoolean(rule, a, b);
|
||||
|
||||
if (result && command) {
|
||||
if (command instanceof SlashCommandClosure) await command.execute();
|
||||
else await executeSubCommands(command, args._scope, args._parserFlags);
|
||||
if (command instanceof SlashCommandClosure) {
|
||||
commandResult = await command.execute();
|
||||
} else {
|
||||
commandResult = await executeSubCommands(command, args._scope, args._parserFlags, args._abortController);
|
||||
if (commandResult.isAborted) {
|
||||
args._abortController.abort(commandResult.abortReason, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (commandResult) {
|
||||
return commandResult.pipe;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args
|
||||
* @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value
|
||||
* @returns
|
||||
*/
|
||||
async function timesCallback(args, value) {
|
||||
let repeats;
|
||||
let command;
|
||||
if (Array.isArray(value)) {
|
||||
[repeats, command] = value;
|
||||
[repeats, ...command] = value;
|
||||
if (command[0] instanceof SlashCommandClosure) {
|
||||
command = command[0];
|
||||
} else {
|
||||
[repeats, ...command] = value.split(' ');
|
||||
command = command.join(' ');
|
||||
}
|
||||
} else {
|
||||
[repeats, ...command] = /**@type {string}*/(value).split(' ');
|
||||
command = command.join(' ');
|
||||
}
|
||||
const isGuardOff = isFalseBoolean(args.guard);
|
||||
const iterations = Math.min(Number(repeats), isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS);
|
||||
let result;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
/**@type {SlashCommandClosureResult}*/
|
||||
if (command instanceof SlashCommandClosure) {
|
||||
command.scope.setMacro('timesIndex', i);
|
||||
await command.execute();
|
||||
result = await command.execute();
|
||||
}
|
||||
else {
|
||||
await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i), args._scope, args._parserFlags);
|
||||
result = await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i.toString()), args._scope, args._parserFlags);
|
||||
if (result.isAborted) {
|
||||
args._abortController.abort(result.abortReason, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
return result?.pipe ?? '';
|
||||
}
|
||||
|
||||
async function ifCallback(args, command) {
|
||||
const { a, b, rule } = parseBooleanOperands(args);
|
||||
const result = evalBoolean(rule, a, b);
|
||||
|
||||
let commandResult;
|
||||
if (result && command) {
|
||||
if (command instanceof SlashCommandClosure) return (await command.execute()).pipe;
|
||||
return await executeSubCommands(command, args._scope, args._parserFlags);
|
||||
commandResult = await executeSubCommands(command, args._scope, args._parserFlags);
|
||||
} else if (!result && args.else && ((typeof args.else === 'string' && args.else !== '') || args.else instanceof SlashCommandClosure)) {
|
||||
if (args.else instanceof SlashCommandClosure) return (await args.else.execute(args._scope)).pipe;
|
||||
return await executeSubCommands(args.else, args._scope, args._parserFlags);
|
||||
commandResult = await executeSubCommands(args.else, args._scope, args._parserFlags);
|
||||
}
|
||||
|
||||
if (commandResult) {
|
||||
if (commandResult.isAborted) {
|
||||
args._abortController.abort(commandResult.abortReason, true);
|
||||
}
|
||||
return commandResult.pipe;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -511,20 +556,25 @@ function evalBoolean(rule, a, b) {
|
||||
/**
|
||||
* Executes a slash command from a string (may be enclosed in quotes) and returns the result.
|
||||
* @param {string} command Command to execute. May contain escaped macro and batch separators.
|
||||
* @returns {Promise<string>} Pipe result
|
||||
* @param {SlashCommandScope} [scope] The scope to use.
|
||||
* @param {PARSER_FLAG[]} [parserFlags] The parser flags to use.
|
||||
* @returns {Promise<SlashCommandClosureResult>} Closure execution result
|
||||
*/
|
||||
async function executeSubCommands(command, scope = null, parserFlags = null) {
|
||||
if (command.startsWith('"') && command.endsWith('"')) {
|
||||
command = command.slice(1, -1);
|
||||
}
|
||||
|
||||
const result = await executeSlashCommands(command, true, scope, true, parserFlags);
|
||||
const abortController = new SlashCommandAbortController();
|
||||
const result = await executeSlashCommandsWithOptions(command, {
|
||||
handleExecutionErrors: false,
|
||||
handleParserErrors: false,
|
||||
parserFlags,
|
||||
scope,
|
||||
abortController,
|
||||
});
|
||||
|
||||
if (!result || typeof result !== 'object') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return result?.pipe || '';
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1132,6 +1182,7 @@ export function registerVariableCommands() {
|
||||
'command to execute while true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], true,
|
||||
),
|
||||
],
|
||||
splitUnnamedArgument: true,
|
||||
helpString: `
|
||||
<div>
|
||||
Compares the value of the left operand <code>a</code> with the value of the right operand <code>b</code>,
|
||||
@ -1184,6 +1235,7 @@ export function registerVariableCommands() {
|
||||
true,
|
||||
),
|
||||
],
|
||||
splitUnnamedArgument: true,
|
||||
helpString: `
|
||||
<div>
|
||||
Execute any valid slash command enclosed in quotes <code>repeats</code> number of times.
|
||||
|
Loading…
x
Reference in New Issue
Block a user