add aborting command execution

This commit is contained in:
LenAnderson
2024-04-27 09:11:54 -04:00
parent c8880a32f9
commit eee0504ff4
5 changed files with 77 additions and 31 deletions

View File

@ -155,7 +155,7 @@ import {
} from './scripts/utils.js'; } from './scripts/utils.js';
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js'; import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
import { COMMENT_NAME_DEFAULT, executeSlashCommands, getSlashCommandsHelp, processChatSlashCommands, registerSlashCommand } from './scripts/slash-commands.js'; import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, processChatSlashCommands, registerSlashCommand } from './scripts/slash-commands.js';
import { import {
tag_map, tag_map,
tags, tags,
@ -2395,31 +2395,26 @@ export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, q
* Executes slash commands and returns the new text and whether the generation was interrupted. * Executes slash commands and returns the new text and whether the generation was interrupted.
* @param {string} message Text to be sent * @param {string} message Text to be sent
* @returns {Promise<boolean>} Whether the message sending was interrupted * @returns {Promise<boolean>} Whether the message sending was interrupted
* @param {AbortController} abortController
*/ */
async function processCommands(message) { async function processCommands(message, abortController) {
if (!message || !message.trim().startsWith('/')) { if (!message || !message.trim().startsWith('/')) {
return false; return false;
} }
const previousText = String($('#send_textarea').val()); deactivateSendButtons();
const result = await executeSlashCommands(message, true, null, true); is_send_press = true;
if (!result || typeof result !== 'object') { document.querySelector('#send_textarea').value = '';
return false;
}
const currentText = String($('#send_textarea').val()); await executeSlashCommandsWithOptions(message, {
abortController: abortController,
});
if (previousText === currentText) { is_send_press = false;
$('#send_textarea').val(result.newText)[0].dispatchEvent(new Event('input', { bubbles:true })); activateSendButtons();
}
// interrupt generation if the input was nothing but a command return true;
if (message.length > 0 && result?.newText.length === 0) {
return true;
}
return result?.interrupt;
} }
function sendSystemMessage(type, text, extra = {}) { function sendSystemMessage(type, text, extra = {}) {
@ -3083,7 +3078,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
let message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `; let message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
if (!(dryRun || type == 'regenerate' || type == 'swipe' || type == 'quiet')) { if (!(dryRun || type == 'regenerate' || type == 'swipe' || type == 'quiet')) {
const interruptedByCommand = await processCommands(String($('#send_textarea').val())); const interruptedByCommand = await processCommands(String($('#send_textarea').val()), abortController);
if (interruptedByCommand) { if (interruptedByCommand) {
//$("#send_textarea").val('')[0].dispatchEvent(new Event('input', { bubbles:true })); //$("#send_textarea").val('')[0].dispatchEvent(new Event('input', { bubbles:true }));
@ -10177,11 +10172,10 @@ jQuery(async function () {
streamingProcessor = null; streamingProcessor = null;
} }
if (abortController) { if (abortController) {
abortController.abort(); abortController.abort('Clicked stop button');
hideStopButton(); hideStopButton();
} }
eventSource.emit(event_types.GENERATION_STOPPED); eventSource.emit(event_types.GENERATION_STOPPED);
activateSendButtons();
}); });
$('.drawer-toggle').on('click', function () { $('.drawer-toggle').on('click', function () {

View File

@ -37,7 +37,7 @@ import {
system_message_types, system_message_types,
this_chid, this_chid,
} from '../script.js'; } from '../script.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommandParserError } from './slash-commands/SlashCommandParserError.js'; import { SlashCommandParserError } from './slash-commands/SlashCommandParserError.js';
import { getMessageTimeStamp } from './RossAscends-mods.js'; import { getMessageTimeStamp } from './RossAscends-mods.js';
import { hideChatMessageRange } from './chats.js'; import { hideChatMessageRange } from './chats.js';
@ -61,7 +61,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
import { SlashCommandAutoComplete } from './slash-commands/SlashCommandAutoComplete.js'; import { SlashCommandAutoComplete } from './slash-commands/SlashCommandAutoComplete.js';
import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommand } from './slash-commands/SlashCommand.js';
export { export {
executeSlashCommands, getSlashCommandsHelp, registerSlashCommand, executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
}; };
export const parser = new SlashCommandParser(); export const parser = new SlashCommandParser();
@ -2548,21 +2548,45 @@ function modelCallback(_, model) {
} }
} }
/**
*
* @param {string} text Slash command txt
* @param {object} [options]
* @param {boolean} [options.handleParserErrors]
* @param {SlashCommandScope} [options.scope]
* @param {boolean} [options.handleExecutionErrors]
* @param {PARSER_FLAG[]} [options.parserFlags]
* @param {AbortController} [options.abortController]
* @returns {Promise<SlashCommandClosureResult>}
*/
async function executeSlashCommandsWithOptions(text, options = {}) {
return await executeSlashCommands(
text,
options.handleParserErrors ?? true,
options.scope ?? null,
options.handleExecutionErrors ?? false,
options.parserFlags ?? null,
options.abortController ?? null,
);
}
/** /**
* Executes slash commands in the provided text * Executes slash commands in the provided text
* @param {string} text Slash command text * @param {string} text Slash command text
* @param {boolean} handleParserErrors Whether to handle parser errors (show toast on error) or throw * @param {boolean} handleParserErrors Whether to handle parser errors (show toast on error) or throw
* @param {SlashCommandScope} scope The scope to be used when executing the commands. * @param {SlashCommandScope} scope The scope to be used when executing the commands.
* @param {boolean} handleExecutionErrors
* @param {PARSER_FLAG[]} parserFlags
* @param {AbortController} abortController
* @returns {Promise<SlashCommandClosureResult>} * @returns {Promise<SlashCommandClosureResult>}
*/ */
async function executeSlashCommands(text, handleParserErrors = true, scope = null, handleExecutionErrors = false, parserFlags = null) { async function executeSlashCommands(text, handleParserErrors = true, scope = null, handleExecutionErrors = false, parserFlags = null, abortController = null) {
if (!text) { if (!text) {
return null; return null;
} }
let closure; let closure;
try { try {
closure = parser.parse(text, true, parserFlags); closure = parser.parse(text, true, parserFlags, abortController);
closure.scope.parent = scope; closure.scope.parent = scope;
} catch (e) { } catch (e) {
if (handleParserErrors && e instanceof SlashCommandParserError) { if (handleParserErrors && e instanceof SlashCommandParserError) {
@ -2588,7 +2612,10 @@ async function executeSlashCommands(text, handleParserErrors = true, scope = nul
} }
try { try {
return await closure.execute(); const result = await closure.execute();
if (result.isAborted) {
toastr.warning(result.abortReason, 'Command execution aborted');
}
} catch (e) { } catch (e) {
if (handleExecutionErrors) { if (handleExecutionErrors) {
toastr.error(e.message); toastr.error(e.message);

View File

@ -7,13 +7,14 @@ import { SlashCommandScope } from './SlashCommandScope.js';
export class SlashCommandClosure { export class SlashCommandClosure {
/**@type {SlashCommandScope}*/ scope; /**@type {SlashCommandScope}*/ scope;
/**@type {Boolean}*/ executeNow = false; /**@type {boolean}*/ executeNow = false;
// @ts-ignore // @ts-ignore
/**@type {Object.<string,string|SlashCommandClosure>}*/ arguments = {}; /**@type {Object.<string,string|SlashCommandClosure>}*/ arguments = {};
// @ts-ignore // @ts-ignore
/**@type {Object.<string,string|SlashCommandClosure>}*/ providedArguments = {}; /**@type {Object.<string,string|SlashCommandClosure>}*/ providedArguments = {};
/**@type {SlashCommandExecutor[]}*/ executorList = []; /**@type {SlashCommandExecutor[]}*/ executorList = [];
/**@type {String}*/ keptText; /**@type {string}*/ keptText;
/**@type {AbortController}*/ abortController;
constructor(parent) { constructor(parent) {
this.scope = new SlashCommandScope(parent); this.scope = new SlashCommandScope(parent);
@ -77,6 +78,7 @@ export class SlashCommandClosure {
closure.providedArguments = this.providedArguments; closure.providedArguments = this.providedArguments;
closure.executorList = this.executorList; closure.executorList = this.executorList;
closure.keptText = this.keptText; closure.keptText = this.keptText;
closure.abortController = this.abortController;
return closure; return closure;
} }
@ -225,11 +227,29 @@ export class SlashCommandClosure {
; ;
} }
let abortResult;
// eslint-disable-next-line no-cond-assign
if (abortResult = this.testAbortController()) {
return abortResult;
}
this.scope.pipe = await executor.command.callback(args, value ?? ''); this.scope.pipe = await executor.command.callback(args, value ?? '');
// eslint-disable-next-line no-cond-assign
if (abortResult = this.testAbortController()) {
return abortResult;
}
} }
} }
/**@type {SlashCommandClosureResult} */ /**@type {SlashCommandClosureResult} */
const result = Object.assign(new SlashCommandClosureResult(), { interrupt, newText: this.keptText, pipe: this.scope.pipe }); const result = Object.assign(new SlashCommandClosureResult(), { interrupt, newText: this.keptText, pipe: this.scope.pipe });
return result; return result;
} }
testAbortController() {
if (this.abortController?.signal?.aborted) {
const result = new SlashCommandClosureResult();
result.isAborted = true;
result.abortReason = this.abortController.signal.reason.toString();
return result;
}
}
} }

View File

@ -1,5 +1,7 @@
export class SlashCommandClosureResult { export class SlashCommandClosureResult {
/**@type {Boolean}*/ interrupt = false; /**@type {boolean}*/ interrupt = false;
/**@type {String}*/ newText = ''; /**@type {string}*/ newText = '';
/**@type {String}*/ pipe; /**@type {string}*/ pipe;
/**@type {boolean}*/ isAborted = false;
/**@type {string}*/ abortReason;
} }

View File

@ -87,6 +87,7 @@ export class SlashCommandParser {
/**@type {string}*/ text; /**@type {string}*/ text;
/**@type {string}*/ keptText; /**@type {string}*/ keptText;
/**@type {number}*/ index; /**@type {number}*/ index;
/**@type {AbortController}*/ abortController;
/**@type {SlashCommandScope}*/ scope; /**@type {SlashCommandScope}*/ scope;
/**@type {SlashCommandClosure}*/ closure; /**@type {SlashCommandClosure}*/ closure;
@ -539,11 +540,12 @@ export class SlashCommandParser {
} }
parse(text, verifyCommandNames = true, flags = null) { parse(text, verifyCommandNames = true, flags = null, abortController = null) {
this.verifyCommandNames = verifyCommandNames; this.verifyCommandNames = verifyCommandNames;
for (const key of Object.keys(PARSER_FLAG)) { for (const key of Object.keys(PARSER_FLAG)) {
this.flags[PARSER_FLAG[key]] = flags?.[PARSER_FLAG[key]] ?? power_user.stscript.parser.flags[PARSER_FLAG[key]] ?? false; this.flags[PARSER_FLAG[key]] = flags?.[PARSER_FLAG[key]] ?? power_user.stscript.parser.flags[PARSER_FLAG[key]] ?? false;
} }
this.abortController = abortController;
this.text = `{:${text}:}`; this.text = `{:${text}:}`;
this.keptText = ''; this.keptText = '';
this.index = 0; this.index = 0;
@ -569,6 +571,7 @@ export class SlashCommandParser {
let injectPipe = true; let injectPipe = true;
this.take(2); // discard opening {: this.take(2); // discard opening {:
let closure = new SlashCommandClosure(this.scope); let closure = new SlashCommandClosure(this.scope);
closure.abortController = this.abortController;
this.scope = closure.scope; this.scope = closure.scope;
this.closure = closure; this.closure = closure;
this.discardWhitespace(); this.discardWhitespace();