From 88f42132c22e7f068752c6cb81ab7aebd3469331 Mon Sep 17 00:00:00 2001 From: parsedone <161969366+parsedone@users.noreply.github.com> Date: Sun, 3 Mar 2024 03:43:44 +0100 Subject: [PATCH 1/2] Update slash-commands.js [BUG] STscript /fuzzy returning wrong answer Implements fix of the bug #1883 "[BUG] STscript /fuzzy returning wrong answer". Fix the params so tha /fuzzy detect when a "candidate" item is found (using fuzzy matching) in the text passed without argument name. Also added optional "threshold" that allows to change the value used by Fuse in order to have stricter or looselier matching. Also updated the parser.addCommand('fuzzy', fuzzyCallback --- public/scripts/slash-commands.js | 55 +++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index e6c50ebb3..5376d14b1 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 undefined 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,7 +501,24 @@ async function inputCallback(args, prompt) { return result || ''; } -function fuzzyCallback(args, value) { +/** + * Each item in "args.list" is searched within "search_item" using fuzzy search. If any matches it returns the matched "item". + * @param {any} args - arguments containing "list" (JSON array) and optionaly "threshold" (float between 0.0 and 1.0) + * @param {list} args.list - list of words you search into search_in_value + * @param {number} args.threshold - sensitivity of search, the lower the strictier, default value is 0.4 + * @param {string} search_in_value - the string where items of list are searched + * @returns {string} + */ +function fuzzyCallback(args, search_in_value) { + // + // args.list : list of words (no space) you search into search_in_value + // args.threshold : sensitivity of search, lower the more strict (added to give more flexibility) + // search_in_value: the text where you want to search + // + // /fuzzy list=["down","left","up","right"] "he looks up" | /echo + // should return "up" + // https://www.fusejs.io/ + if (!value) { console.warn('WARN: No argument provided for /fuzzy command'); return ''; @@ -520,14 +536,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([search_in_value], params); + // each item in the "list" is searched within "search_item", if any matches it returns the matched "item" + for (let search_item of list) { + let result = fuse.search(search_item); + if ( result.length > 0 ) { + console.info('fuzzyCallback Matched: ' + search_item); + return search_item; + } + } + return ''; } catch { console.warn('WARN: Invalid list argument provided for /fuzzy command'); return ''; From 975206fd06cd5716fe267e90ff096ae4dc4f9208 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 3 Mar 2024 16:04:48 +0200 Subject: [PATCH 2/2] Clean-up /fuzzy command doc comments --- public/scripts/slash-commands.js | 50 ++++++++++++++------------------ 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 5376d14b1..b1f84b208 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -173,7 +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"] 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 undefined 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('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); @@ -503,23 +503,15 @@ async function inputCallback(args, prompt) { /** * Each item in "args.list" is searched within "search_item" using fuzzy search. If any matches it returns the matched "item". - * @param {any} args - arguments containing "list" (JSON array) and optionaly "threshold" (float between 0.0 and 1.0) - * @param {list} args.list - list of words you search into search_in_value - * @param {number} args.threshold - sensitivity of search, the lower the strictier, default value is 0.4 - * @param {string} search_in_value - the string where items of list are searched - * @returns {string} + * @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, search_in_value) { - // - // args.list : list of words (no space) you search into search_in_value - // args.threshold : sensitivity of search, lower the more strict (added to give more flexibility) - // search_in_value: the text where you want to search - // - // /fuzzy list=["down","left","up","right"] "he looks up" | /echo - // should return "up" - // https://www.fusejs.io/ - - if (!value) { +function fuzzyCallback(args, searchInValue) { + if (!searchInValue) { console.warn('WARN: No argument provided for /fuzzy command'); return ''; } @@ -543,27 +535,27 @@ function fuzzyCallback(args, search_in_value) { 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 ) { + if ('threshold' in args) { params.threshold = parseFloat(resolveVariable(args.threshold)); - if ( isNaN(params.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 ) { + if (params.threshold < 0) { params.threshold = 0; } - if ( params.threshold > 1 ) { + if (params.threshold > 1) { params.threshold = 1; } } - const fuse = new Fuse([search_in_value], params); + 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 (let search_item of list) { - let result = fuse.search(search_item); - if ( result.length > 0 ) { - console.info('fuzzyCallback Matched: ' + search_item); - return search_item; + for (const searchItem of list) { + const result = fuse.search(searchItem); + if (result.length > 0) { + console.info('fuzzyCallback Matched: ' + searchItem); + return searchItem; } } return ''; @@ -1623,7 +1615,7 @@ async function executeSlashCommands(text, unescape = false) { ?.replace(/\\\|/g, '|') ?.replace(/\\\{/g, '{') ?.replace(/\\\}/g, '}') - ; + ; } for (const [key, value] of Object.entries(result.args)) { @@ -1632,7 +1624,7 @@ async function executeSlashCommands(text, unescape = false) { .replace(/\\\|/g, '|') .replace(/\\\{/g, '{') .replace(/\\\}/g, '}') - ; + ; } }