diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index e6c50ebb3..b1f84b208 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -173,8 +173,7 @@ parser.addCommand('gen', generateCallback, [], '(lock=on parser.addCommand('genraw', generateRawCallback, [], '(lock=on/off [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. /genraw instruct=off Why is the sky blue?. Use stop=... with a JSON-serialized array to add one-time custom stop strings, e.g. /genraw stop=["\\n"] Say hi', true, true); parser.addCommand('addswipe', addSwipeCallback, ['swipeadd'], '(text) – adds a swipe to the last chat message.', true, true); parser.addCommand('abort', abortCallback, [], ' – aborts the slash command batch execution', true, true); -parser.addCommand('fuzzy', fuzzyCallback, [], 'list=["a","b","c"] (search value) – performs a fuzzy match of the provided search using the provided list of value and passes the closest match to the next command through the pipe.', true, true); -parser.addCommand('pass', (_, arg) => arg, ['return'], '(text) – passes the text to the next command through the pipe.', true, true); +parser.addCommand('fuzzy', fuzzyCallback, [], 'list=["a","b","c"] threshold=0.4 (text to search) – performs a fuzzy match of each items of list within the text to search. If any item matches then its name is returned. If no item list matches the text to search then no value is returned. The optional threshold (default is 0.4) allows some control over the matching. A low value (min 0.0) means the match is very strict. At 1.0 (max) the match is very loose and probably matches anything. The returned value passes to the next command through the pipe.', true, true); parser.addCommand('pass', (_, arg) => arg, ['return'], '(text) – passes the text to the next command through the pipe.', true, true); parser.addCommand('delay', delayCallback, ['wait', 'sleep'], '(milliseconds) – delays the next command in the pipe by the specified number of milliseconds.', true, true); parser.addCommand('input', inputCallback, ['prompt'], '(default="string" large=on/off wide=on/off okButton="string" rows=number [text]) – Shows a popup with the provided text and an input field. The default argument is the default value of the input field, and the text argument is the text to display.', true, true); parser.addCommand('run', runCallback, ['call', 'exec'], '[key1=value key2=value ...] ([qrSet.]qrLabel) – runs a Quick Reply with the specified name from a currently active preset or from another preset, named arguments can be referenced in a QR with {{arg::key}}.', true, true); @@ -502,8 +501,17 @@ async function inputCallback(args, prompt) { return result || ''; } -function fuzzyCallback(args, value) { - if (!value) { +/** + * Each item in "args.list" is searched within "search_item" using fuzzy search. If any matches it returns the matched "item". + * @param {FuzzyCommandArgs} args - arguments containing "list" (JSON array) and optionaly "threshold" (float between 0.0 and 1.0) + * @param {string} searchInValue - the string where items of list are searched + * @returns {string} - the matched item from the list + * @typedef {{list: string, threshold: string}} FuzzyCommandArgs - arguments for /fuzzy command + * @example /fuzzy list=["down","left","up","right"] "he looks up" | /echo // should return "up" + * @link https://www.fusejs.io/ + */ +function fuzzyCallback(args, searchInValue) { + if (!searchInValue) { console.warn('WARN: No argument provided for /fuzzy command'); return ''; } @@ -520,14 +528,37 @@ function fuzzyCallback(args, value) { return ''; } - const fuse = new Fuse(list, { + const params = { includeScore: true, findAllMatches: true, ignoreLocation: true, - threshold: 0.7, - }); - const result = fuse.search(value); - return result[0]?.item; + threshold: 0.4, + }; + // threshold determines how strict is the match, low threshold value is very strict, at 1 (nearly?) everything matches + if ('threshold' in args) { + params.threshold = parseFloat(resolveVariable(args.threshold)); + if (isNaN(params.threshold)) { + console.warn('WARN: \'threshold\' argument must be a float between 0.0 and 1.0 for /fuzzy command'); + return ''; + } + if (params.threshold < 0) { + params.threshold = 0; + } + if (params.threshold > 1) { + params.threshold = 1; + } + } + + const fuse = new Fuse([searchInValue], params); + // each item in the "list" is searched within "search_item", if any matches it returns the matched "item" + for (const searchItem of list) { + const result = fuse.search(searchItem); + if (result.length > 0) { + console.info('fuzzyCallback Matched: ' + searchItem); + return searchItem; + } + } + return ''; } catch { console.warn('WARN: Invalid list argument provided for /fuzzy command'); return ''; @@ -1584,7 +1615,7 @@ async function executeSlashCommands(text, unescape = false) { ?.replace(/\\\|/g, '|') ?.replace(/\\\{/g, '{') ?.replace(/\\\}/g, '}') - ; + ; } for (const [key, value] of Object.entries(result.args)) { @@ -1593,7 +1624,7 @@ async function executeSlashCommands(text, unescape = false) { .replace(/\\\|/g, '|') .replace(/\\\{/g, '{') .replace(/\\\}/g, '}') - ; + ; } }