mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
remove old parser
This commit is contained in:
@ -37,7 +37,7 @@ import {
|
|||||||
system_message_types,
|
system_message_types,
|
||||||
this_chid,
|
this_chid,
|
||||||
} from '../script.js';
|
} from '../script.js';
|
||||||
import { SlashCommandParser as NewSlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||||
import { SlashCommandParserError } from './slash-commands/SlashCommandParserError.js';
|
import { SlashCommandParserError } from './slash-commands/SlashCommandParserError.js';
|
||||||
import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js';
|
import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js';
|
||||||
import { getMessageTimeStamp } from './RossAscends-mods.js';
|
import { getMessageTimeStamp } from './RossAscends-mods.js';
|
||||||
@ -62,179 +62,7 @@ export {
|
|||||||
executeSlashCommands, getSlashCommandsHelp, registerSlashCommand,
|
executeSlashCommands, getSlashCommandsHelp, registerSlashCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SlashCommandParser {
|
export const parser = new SlashCommandParser();
|
||||||
static COMMENT_KEYWORDS = ['#', '/'];
|
|
||||||
static RESERVED_KEYWORDS = [
|
|
||||||
...this.COMMENT_KEYWORDS,
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
/**
|
|
||||||
* @type {Record<string, SlashCommand>} - Slash commands registered in the parser
|
|
||||||
*/
|
|
||||||
this.commands = {};
|
|
||||||
/**
|
|
||||||
* @type {Record<string, string>} - Help strings for each command
|
|
||||||
*/
|
|
||||||
this.helpStrings = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a slash command to the parser.
|
|
||||||
* @param {string} command - The command name
|
|
||||||
* @param {function} callback - The callback function to execute
|
|
||||||
* @param {string[]} aliases - The command aliases
|
|
||||||
* @param {string} helpString - The help string for the command
|
|
||||||
* @param {boolean} [interruptsGeneration] - Whether the command interrupts message generation
|
|
||||||
* @param {boolean} [purgeFromMessage] - Whether the command should be purged from the message
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
addCommand(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
|
|
||||||
const fnObj = { callback, helpString, interruptsGeneration, purgeFromMessage };
|
|
||||||
|
|
||||||
if ([command, ...aliases].some(x => SlashCommandParser.RESERVED_KEYWORDS.includes(x))) {
|
|
||||||
console.error('ERROR: Reserved slash command keyword used!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([command, ...aliases].some(x => Object.hasOwn(this.commands, x))) {
|
|
||||||
console.trace('WARN: Duplicate slash command registered!');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.commands[command] = fnObj;
|
|
||||||
|
|
||||||
if (Array.isArray(aliases)) {
|
|
||||||
aliases.forEach((alias) => {
|
|
||||||
this.commands[alias] = fnObj;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let stringBuilder = `<span class="monospace">/${command}</span> ${helpString} `;
|
|
||||||
if (Array.isArray(aliases) && aliases.length) {
|
|
||||||
let aliasesString = `(alias: ${aliases.map(x => `<span class="monospace">/${x}</span>`).join(', ')})`;
|
|
||||||
stringBuilder += aliasesString;
|
|
||||||
}
|
|
||||||
this.helpStrings[command] = stringBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a slash command to extract the command name, the (named) arguments and the remaining text
|
|
||||||
* @param {string} text - Slash command text
|
|
||||||
* @returns {{command: SlashCommand, args: object, value: string, commandName: string}} - The parsed command, its arguments and the remaining text
|
|
||||||
*/
|
|
||||||
parse(text) {
|
|
||||||
// Parses a command even when spaces are present in arguments
|
|
||||||
// /buttons labels=["OK","I do not accept"] some text
|
|
||||||
// /fuzzy list=[ "red pink" , "yellow" ] threshold=" 0.6 " he yelled when the color was reddish and not pink | /echo
|
|
||||||
const excludedFromRegex = ['sendas'];
|
|
||||||
let command = '';
|
|
||||||
const argObj = {};
|
|
||||||
let unnamedArg = '';
|
|
||||||
|
|
||||||
// extract the command " /fuzzy " => "fuzzy"
|
|
||||||
text = text.trim();
|
|
||||||
let remainingText = '';
|
|
||||||
const commandArgPattern = /^\/([^\s]+)\s*(.*)$/s;
|
|
||||||
let match = commandArgPattern.exec(text);
|
|
||||||
if (match !== null && match[1].length > 0) {
|
|
||||||
command = match[1];
|
|
||||||
remainingText = match[2];
|
|
||||||
console.debug('command:' + command);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SlashCommandParser.COMMENT_KEYWORDS.includes(command)) {
|
|
||||||
return {
|
|
||||||
commandName: command,
|
|
||||||
command: {
|
|
||||||
callback: () => {},
|
|
||||||
helpString: '',
|
|
||||||
interruptsGeneration: false,
|
|
||||||
purgeFromMessage: true,
|
|
||||||
},
|
|
||||||
args: {},
|
|
||||||
value: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the rest of the string to extract named arguments, the remainder is the "unnamedArg" which is usually text, like the prompt to send
|
|
||||||
while (remainingText.length > 0) {
|
|
||||||
// does the remaining text is like nameArg=[value] or nameArg=[value,value] or nameArg=[ value , value , value]
|
|
||||||
// where value can be a string like " this is some text " , note previously it was not possible to have have spaces
|
|
||||||
// where value can be a scalar like AScalar
|
|
||||||
// where value can be a number like +9 -1005.44
|
|
||||||
// where value can be a macro like {{getvar::name}}
|
|
||||||
const namedArrayArgPattern = /^(\w+)=\[\s*(((?<quote>["'])[^"]*(\k<quote>)|{{[^}]*}}|[+-]?\d*\.?\d+|\w*)\s*,?\s*)+\]/s;
|
|
||||||
match = namedArrayArgPattern.exec(remainingText);
|
|
||||||
if (match !== null && match[0].length > 0) {
|
|
||||||
//console.log(`matching: ${match[0]}`);
|
|
||||||
const posFirstEqual = match[0].indexOf('=');
|
|
||||||
const key = match[0].substring(0, posFirstEqual).trim();
|
|
||||||
const value = match[0].substring(posFirstEqual + 1).trim();
|
|
||||||
|
|
||||||
// Remove the quotes around the value, if any
|
|
||||||
argObj[key] = value.replace(/(^")|("$)/g, '');
|
|
||||||
remainingText = remainingText.slice(match[0].length + 1).trim();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// does the remaining text is like nameArg=value
|
|
||||||
// where value can be a string like " this is some text " , note previously it was not possible to have have spaces
|
|
||||||
// where value can be a scalar like AScalar
|
|
||||||
// where value can be a number like +9 -1005.44
|
|
||||||
// where value can be a macro like {{getvar::name}}
|
|
||||||
const namedScalarArgPattern = /^(\w+)=(((?<quote>["'])[^"]*(\k<quote>)|{{[^}]*}}|[+-]?\d*\.?\d+|\w*))/s;
|
|
||||||
match = namedScalarArgPattern.exec(remainingText);
|
|
||||||
if (match !== null && match[0].length > 0) {
|
|
||||||
//console.log(`matching: ${match[0]}`);
|
|
||||||
const posFirstEqual = match[0].indexOf('=');
|
|
||||||
const key = match[0].substring(0, posFirstEqual).trim();
|
|
||||||
const value = match[0].substring(posFirstEqual + 1).trim();
|
|
||||||
|
|
||||||
// Remove the quotes around the value, if any
|
|
||||||
argObj[key] = value.replace(/(^")|("$)/g, '');
|
|
||||||
remainingText = remainingText.slice(match[0].length + 1).trim();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the remainder that matches no named argument is the "unamedArg" previously mentioned
|
|
||||||
unnamedArg = remainingText.trim();
|
|
||||||
remainingText = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Excluded commands format in their own function
|
|
||||||
if (!excludedFromRegex.includes(command)) {
|
|
||||||
console.debug(`parse: !excludedFromRegex.includes(${command}`);
|
|
||||||
console.debug(` parse: unnamedArg before: ${unnamedArg}`);
|
|
||||||
unnamedArg = getRegexedString(
|
|
||||||
unnamedArg,
|
|
||||||
regex_placement.SLASH_COMMAND,
|
|
||||||
);
|
|
||||||
console.debug(` parse: unnamedArg after: ${unnamedArg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// your weird complex command is now transformed into a juicy tiny text or something useful :)
|
|
||||||
if (this.commands[command]) {
|
|
||||||
return { command: this.commands[command], args: argObj, value: unnamedArg, commandName: command };
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getHelpString() {
|
|
||||||
const listItems = Object
|
|
||||||
.entries(this.helpStrings)
|
|
||||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
||||||
.map(x => x[1])
|
|
||||||
.map(x => `<li>${x}</li>`)
|
|
||||||
.join('\n');
|
|
||||||
return `<p>Slash commands:</p><ol>${listItems}</ol>
|
|
||||||
<small>Slash commands can be batched into a single input by adding a pipe character | at the end, and then writing a new slash command.</small>
|
|
||||||
<ul><li><small>Example:</small><code>/cut 1 | /sys Hello, | /continue</code></li>
|
|
||||||
<li>This will remove the first message in chat, send a system message that starts with 'Hello,', and then ask the AI to continue the message.</li></ul>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const parser = new NewSlashCommandParser();
|
|
||||||
const registerSlashCommand = parser.addCommand.bind(parser);
|
const registerSlashCommand = parser.addCommand.bind(parser);
|
||||||
const getSlashCommandsHelp = parser.getHelpString.bind(parser);
|
const getSlashCommandsHelp = parser.getHelpString.bind(parser);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user