Merge branch 'staging' into send-commands-return-value

d82dc4952bca39b7eb88a5a64c5e3d7a3095fe61

Cohee <18619528+Cohee1207@users.noreply.github.com>

12 files changed, +446 -182Showing whitespace changes
public/script.js+2 -1
@@ -230,7 +230,7 @@ import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros
230import { currentUser, setUserControls } from './scripts/user.js';230import { currentUser, setUserControls } from './scripts/user.js';
231import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';231import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
232import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';232import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
233import { ScraperManager } from './scripts/scrapers.js';233import { initScrapers, ScraperManager } from './scripts/scrapers.js';
234import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';234import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
235import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';235import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';
236import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';236import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';
@@ -959,6 +959,7 @@ async function firstLoadInit() {
959 initCfg();959 initCfg();
960 initLogprobs();960 initLogprobs();
961 initInputMarkdown();961 initInputMarkdown();
962 await initScrapers();
962 doDailyExtensionUpdatesCheck();963 doDailyExtensionUpdatesCheck();
963 await hideLoader();964 await hideLoader();
964 await fixViewport();965 await fixViewport();
public/scripts/extensions/expressions/index.js+9 -3
@@ -2,7 +2,7 @@ import { callPopup, eventSource, event_types, generateRaw, getRequestHeaders, ma
2import { dragElement, isMobile } from '../../RossAscends-mods.js';2import { dragElement, isMobile } from '../../RossAscends-mods.js';
3import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';3import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
4import { loadMovingUIState, power_user } from '../../power-user.js';4import { loadMovingUIState, power_user } from '../../power-user.js';
5import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition } from '../../utils.js';5import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar } from '../../utils.js';
6import { hideMutedSprites } from '../../group-chats.js';6import { hideMutedSprites } from '../../group-chats.js';
7import { isJsonSchemaSupported } from '../../textgen-settings.js';7import { isJsonSchemaSupported } from '../../textgen-settings.js';
8import { debounce_timeout } from '../../constants.js';8import { debounce_timeout } from '../../constants.js';
@@ -2107,14 +2107,20 @@ function migrateSettings() {
2107 }));2107 }));
2108 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2108 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2109 name: 'lastsprite',2109 name: 'lastsprite',
2110 callback: (_, value) => lastExpression[String(value).trim()] ?? '',2110 callback: (_, name) => {
2111 if (typeof name !== 'string') throw new Error('name must be a string');
2112 const char = findChar({ name: name });
2113 const sprite = lastExpression[char?.name ?? name] ?? '';
2114 return sprite;
2115 },
2111 returns: 'the last set sprite / expression for the named character.',2116 returns: 'the last set sprite / expression for the named character.',
2112 unnamedArgumentList: [2117 unnamedArgumentList: [
2113 SlashCommandArgument.fromProps({2118 SlashCommandArgument.fromProps({
2114 description: 'character name',2119 description: 'Character name - or unique character identifier (avatar key)',
2115 typeList: [ARGUMENT_TYPE.STRING],2120 typeList: [ARGUMENT_TYPE.STRING],
2116 isRequired: true,2121 isRequired: true,
2117 enumProvider: commonEnumProviders.characters('character'),2122 enumProvider: commonEnumProviders.characters('character'),
2123 forceEnum: true,
2118 }),2124 }),
2119 ],2125 ],
2120 helpString: 'Returns the last set sprite / expression for the named character.',2126 helpString: 'Returns the last set sprite / expression for the named character.',
public/scripts/extensions/gallery/index.js+1 -0
@@ -441,6 +441,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
441 description: 'character name',441 description: 'character name',
442 typeList: [ARGUMENT_TYPE.STRING],442 typeList: [ARGUMENT_TYPE.STRING],
443 enumProvider: commonEnumProviders.characters('character'),443 enumProvider: commonEnumProviders.characters('character'),
444 forceEnum: true,
444 }),445 }),
445 SlashCommandNamedArgument.fromProps({446 SlashCommandNamedArgument.fromProps({
446 name: 'group',447 name: 'group',
public/scripts/scrapers.js+16 -7
@@ -13,6 +13,7 @@ import { isValidUrl } from './utils.js';
13 * @property {string} description13 * @property {string} description
14 * @property {string} iconClass14 * @property {string} iconClass
15 * @property {boolean} iconAvailable15 * @property {boolean} iconAvailable
16 * @property {() => Promise<void>} [init=null]
16 * @property {() => Promise<boolean>} isAvailable17 * @property {() => Promise<boolean>} isAvailable
17 * @property {() => Promise<File[]>} scrape18 * @property {() => Promise<File[]>} scrape
18 */19 */
@@ -36,12 +37,16 @@ export class ScraperManager {
36 * Register a scraper to be used by the Data Bank.37 * Register a scraper to be used by the Data Bank.
37 * @param {Scraper} scraper Instance of a scraper to register38 * @param {Scraper} scraper Instance of a scraper to register
38 */39 */
39 static registerDataBankScraper(scraper) {40 static async registerDataBankScraper(scraper) {
40 if (ScraperManager.#scrapers.some(s => s.id === scraper.id)) {41 if (ScraperManager.#scrapers.some(s => s.id === scraper.id)) {
41 console.warn(`Scraper with ID ${scraper.id} already registered`);42 console.warn(`Scraper with ID ${scraper.id} already registered`);
42 return;43 return;
43 }44 }
4445
46 if (scraper.init) {
47 await scraper.init();
48 }
49
45 ScraperManager.#scrapers.push(scraper);50 ScraperManager.#scrapers.push(scraper);
46 }51 }
4752
@@ -462,7 +467,9 @@ class YouTubeScraper {
462 this.description = 'Download a transcript from a YouTube video.';467 this.description = 'Download a transcript from a YouTube video.';
463 this.iconClass = 'fa-brands fa-youtube';468 this.iconClass = 'fa-brands fa-youtube';
464 this.iconAvailable = true;469 this.iconAvailable = true;
470 }
465471
472 async init() {
466 SlashCommandParser.addCommandObject(SlashCommand.fromProps({473 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
467 name: 'yt-script',474 name: 'yt-script',
468 callback: async (args, url) => {475 callback: async (args, url) => {
@@ -564,9 +571,11 @@ class YouTubeScraper {
564 }571 }
565}572}
566573
567ScraperManager.registerDataBankScraper(new FileScraper());574export async function initScrapers() {
568ScraperManager.registerDataBankScraper(new Notepad());575 await ScraperManager.registerDataBankScraper(new FileScraper());
569ScraperManager.registerDataBankScraper(new WebScraper());576 await ScraperManager.registerDataBankScraper(new Notepad());
570ScraperManager.registerDataBankScraper(new MediaWikiScraper());577 await ScraperManager.registerDataBankScraper(new WebScraper());
571ScraperManager.registerDataBankScraper(new FandomScraper());578 await ScraperManager.registerDataBankScraper(new MediaWikiScraper());
572ScraperManager.registerDataBankScraper(new YouTubeScraper());579 await ScraperManager.registerDataBankScraper(new FandomScraper());
580 await ScraperManager.registerDataBankScraper(new YouTubeScraper());
581}
public/scripts/slash-commands.js+205 -99
@@ -55,7 +55,7 @@ import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockStat
55import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';55import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
56import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';56import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
57import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';57import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';
58import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';58import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
59import { registerVariableCommands, resolveVariable } from './variables.js';59import { registerVariableCommands, resolveVariable } from './variables.js';
60import { background_settings } from './backgrounds.js';60import { background_settings } from './backgrounds.js';
61import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';61import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
@@ -68,7 +68,6 @@ import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashComma
68import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';68import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
69import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';69import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
70import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';70import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
71import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
72import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';71import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
73import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';72import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
74import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';73import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
@@ -175,21 +174,97 @@ export function initDefaultSlashCommands() {
175 `,174 `,
176 }));175 }));
177 SlashCommandParser.addCommandObject(SlashCommand.fromProps({176 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
177 name: 'char-find',
178 aliases: ['findchar'],
179 callback: (args, name) => {
180 if (typeof name !== 'string') throw new Error('name must be a string');
181 if (args.preferCurrent instanceof SlashCommandClosure || Array.isArray(args.preferCurrent)) throw new Error('preferCurrent cannot be a closure or array');
182 if (args.quiet instanceof SlashCommandClosure || Array.isArray(args.quiet)) throw new Error('quiet cannot be a closure or array');
183
184 const char = findChar({ name: name, filteredByTags: validateArrayArgString(args.tag, 'tag'), preferCurrentChar: !isFalseBoolean(args.preferCurrent), quiet: isTrueBoolean(args.quiet) });
185 return char?.avatar ?? '';
186 },
187 returns: 'the avatar key (unique identifier) of the character',
188 namedArgumentList: [
189 SlashCommandNamedArgument.fromProps({
190 name: 'tag',
191 description: 'Supply one or more tags to filter down to the correct character for the provided name, if multiple characters have the same name.',
192 typeList: [ARGUMENT_TYPE.STRING],
193 enumProvider: commonEnumProviders.tags('assigned'),
194 acceptsMultiple: true,
195 }),
196 SlashCommandNamedArgument.fromProps({
197 name: 'preferCurrent',
198 description: 'Prefer current character or characters in a group, if multiple characters match',
199 typeList: [ARGUMENT_TYPE.BOOLEAN],
200 defaultValue: 'true',
201 }),
202 SlashCommandNamedArgument.fromProps({
203 name: 'quiet',
204 description: 'Do not show warning if multiple charactrers are found',
205 typeList: [ARGUMENT_TYPE.BOOLEAN],
206 defaultValue: 'false',
207 enumProvider: commonEnumProviders.boolean('trueFalse'),
208 }),
209 ],
210 unnamedArgumentList: [
211 SlashCommandArgument.fromProps({
212 description: 'Character name - or unique character identifier (avatar key)',
213 typeList: [ARGUMENT_TYPE.STRING],
214 enumProvider: commonEnumProviders.characters('character'),
215 forceEnum: false,
216 }),
217 ],
218 helpString: `
219 <div>
220 Searches for a character and returns its avatar key.
221 </div>
222 <div>
223 This can be used to choose the correct character for something like <code>/sendas</code> or other commands in need of a character name
224 if you have multiple characters with the same name.
225 </div>
226 <div>
227 <strong>Example:</strong>
228 <ul>
229 <li>
230 <pre><code>/char-find name="Chloe"</code></pre>
231 Returns the avatar key for "Chloe".
232 </li>
233 <li>
234 <pre><code>/search name="Chloe" tag="friend"</code></pre>
235 Returns the avatar key for the character "Chloe" that is tagged with "friend".
236 This is useful if you for example have multiple characters named "Chloe", and the others are "foe", "goddess", or anything else,
237 so you can actually select the character you are looking for.
238 </li>
239 </ul>
240 </div>
241 `,
242 }));
243 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
178 name: 'sendas',244 name: 'sendas',
179 callback: sendMessageAs,245 callback: sendMessageAs,
180 returns: 'Optionally the text of the sent message, if specified in the "return" argument',246 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
181 namedArgumentList: [247 namedArgumentList: [
182 SlashCommandNamedArgument.fromProps({248 SlashCommandNamedArgument.fromProps({
183 name: 'name',249 name: 'name',
184 description: 'Character name',250 description: 'Character name - or unique character identifier (avatar key)',
185 typeList: [ARGUMENT_TYPE.STRING],251 typeList: [ARGUMENT_TYPE.STRING],
186 isRequired: true,252 isRequired: true,
187 enumProvider: commonEnumProviders.characters('character'),253 enumProvider: commonEnumProviders.characters('character'),
188 forceEnum: false,254 forceEnum: false,
189 }),255 }),
190 new SlashCommandNamedArgument(256 SlashCommandNamedArgument.fromProps({
191 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',257 name: 'avatar',
192 ),258 description: 'Character avatar override (Can be either avatar key or just the character name to pull the avatar from)',
259 typeList: [ARGUMENT_TYPE.STRING],
260 enumProvider: commonEnumProviders.characters('character'),
261 }),
262 SlashCommandNamedArgument.fromProps({
263 name: 'compact',
264 description: 'Use compact layout',
265 typeList: [ARGUMENT_TYPE.BOOLEAN],
266 defaultValue: 'false',
267 }),
193 SlashCommandNamedArgument.fromProps({268 SlashCommandNamedArgument.fromProps({
194 name: 'at',269 name: 'at',
195 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',270 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',
@@ -221,6 +296,10 @@ export function initDefaultSlashCommands() {
221 <pre><code>/sendas name="Chloe" Hello, guys!</code></pre>296 <pre><code>/sendas name="Chloe" Hello, guys!</code></pre>
222 will send "Hello, guys!" from "Chloe".297 will send "Hello, guys!" from "Chloe".
223 </li>298 </li>
299 <li>
300 <pre><code>/sendas name="Chloe" avatar="BigBadBoss" Hehehe, I am the big bad evil, fear me.</code></pre>
301 will send a message as the character "Chloe", but utilizing the avatar from a character named "BigBadBoss".
302 </li>
224 </ul>303 </ul>
225 </div>304 </div>
226 <div>305 <div>
@@ -409,12 +488,14 @@ export function initDefaultSlashCommands() {
409 SlashCommandParser.addCommandObject(SlashCommand.fromProps({488 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
410 name: 'go',489 name: 'go',
411 callback: goToCharacterCallback,490 callback: goToCharacterCallback,
491 returns: 'The character/group name',
412 unnamedArgumentList: [492 unnamedArgumentList: [
413 SlashCommandArgument.fromProps({493 SlashCommandArgument.fromProps({
414 description: 'name',494 description: 'Character name - or unique character identifier (avatar key)',
415 typeList: [ARGUMENT_TYPE.STRING],495 typeList: [ARGUMENT_TYPE.STRING],
416 isRequired: true,496 isRequired: true,
417 enumProvider: commonEnumProviders.characters('all'),497 enumProvider: commonEnumProviders.characters('all'),
498 forceEnum: true,
418 }),499 }),
419 ],500 ],
420 helpString: 'Opens up a chat with the character or group by its name',501 helpString: 'Opens up a chat with the character or group by its name',
@@ -460,7 +541,7 @@ export function initDefaultSlashCommands() {
460 namedArgumentList: [541 namedArgumentList: [
461 SlashCommandNamedArgument.fromProps({542 SlashCommandNamedArgument.fromProps({
462 name: 'name',543 name: 'name',
463 description: 'character name',544 description: 'Character name - or unique character identifier (avatar key)',
464 typeList: [ARGUMENT_TYPE.STRING],545 typeList: [ARGUMENT_TYPE.STRING],
465 isRequired: true,546 isRequired: true,
466 enumProvider: commonEnumProviders.characters('character'),547 enumProvider: commonEnumProviders.characters('character'),
@@ -487,7 +568,7 @@ export function initDefaultSlashCommands() {
487 namedArgumentList: [],568 namedArgumentList: [],
488 unnamedArgumentList: [569 unnamedArgumentList: [
489 SlashCommandArgument.fromProps({570 SlashCommandArgument.fromProps({
490 description: 'name',571 description: 'Character name - or unique character identifier (avatar key)',
491 typeList: [ARGUMENT_TYPE.STRING],572 typeList: [ARGUMENT_TYPE.STRING],
492 isRequired: true,573 isRequired: true,
493 enumProvider: commonEnumProviders.characters('character'),574 enumProvider: commonEnumProviders.characters('character'),
@@ -663,7 +744,7 @@ export function initDefaultSlashCommands() {
663 aliases: ['addmember', 'memberadd'],744 aliases: ['addmember', 'memberadd'],
664 unnamedArgumentList: [745 unnamedArgumentList: [
665 SlashCommandArgument.fromProps({746 SlashCommandArgument.fromProps({
666 description: 'character name',747 description: 'Character name - or unique character identifier (avatar key)',
667 typeList: [ARGUMENT_TYPE.STRING],748 typeList: [ARGUMENT_TYPE.STRING],
668 isRequired: true,749 isRequired: true,
669 enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [],750 enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [],
@@ -901,7 +982,7 @@ export function initDefaultSlashCommands() {
901 ),982 ),
902 SlashCommandNamedArgument.fromProps({983 SlashCommandNamedArgument.fromProps({
903 name: 'name',984 name: 'name',
904 description: 'in-prompt name for instruct mode',985 description: 'in-prompt character name for instruct mode (or unique character identifier (avatar key), which will be used as name)',
905 typeList: [ARGUMENT_TYPE.STRING],986 typeList: [ARGUMENT_TYPE.STRING],
906 defaultValue: 'System',987 defaultValue: 'System',
907 enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],988 enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],
@@ -2353,7 +2434,8 @@ async function generateCallback(args, value) {
23532434
2354 setEphemeralStopStrings(resolveVariable(args?.stop));2435 setEphemeralStopStrings(resolveVariable(args?.stop));
2355 const name = args?.name;2436 const name = args?.name;
2356 const result = await generateQuietPrompt(value, quietToLoud, false, '', name, length);2437 const char = findChar({ name: name });
2438 const result = await generateQuietPrompt(value, quietToLoud, false, '', char?.name ?? name, length);
2357 return result;2439 return result;
2358 } catch (err) {2440 } catch (err) {
2359 console.error('Error on /gen generation', err);2441 console.error('Error on /gen generation', err);
@@ -2541,26 +2623,22 @@ async function askCharacter(args, text) {
2541 return '';2623 return '';
2542 }2624 }
25432625
2544 let name = '';2626 if (!args.name) {
2545
2546 if (args?.name) {
2547 name = args.name.trim();
2548
2549 if (!name) {
2550 toastr.warning('You must specify a name of the character to ask.');2627 toastr.warning('You must specify a name of the character to ask.');
2551 return '';2628 return '';
2552 }2629 }
2553 }
25542630
2555 const prevChId = this_chid;2631 const prevChId = this_chid;
25562632
2557 // Find the character2633 // Find the character
2558 const chId = characters.findIndex((e) => e.name === name || e.avatar === name);2634 const character = findChar({ name: args?.name });
2559 if (!characters[chId] || chId === -1) {2635 if (!character) {
2560 toastr.warning('Character not found.');2636 toastr.error('Character not found.');
2561 return '';2637 return '';
2562 }2638 }
25632639
2640 const chId = getCharIndex(character);
2641
2564 if (text) {2642 if (text) {
2565 const mesText = getRegexedString(text.trim(), regex_placement.SLASH_COMMAND);2643 const mesText = getRegexedString(text.trim(), regex_placement.SLASH_COMMAND);
2566 // Sending a message implicitly saves the chat, so this needs to be done before changing the character2644 // Sending a message implicitly saves the chat, so this needs to be done before changing the character
@@ -2571,19 +2649,9 @@ async function askCharacter(args, text) {
2571 // Override character and send a user message2649 // Override character and send a user message
2572 setCharacterId(String(chId));2650 setCharacterId(String(chId));
25732651
2574 const character = characters[chId];2652 const { name, force_avatar, original_avatar } = getNameAndAvatarForMessage(character, args?.name);
2575 let force_avatar, original_avatar;
25762653
2577 if (character && character.avatar !== 'none') {2654 setCharacterName(name);
2578 force_avatar = getThumbnailUrl('avatar', character.avatar);
2579 original_avatar = character.avatar;
2580 }
2581 else {
2582 force_avatar = default_avatar;
2583 original_avatar = default_avatar;
2584 }
2585
2586 setCharacterName(character.name);
25872655
2588 const restoreCharacter = () => {2656 const restoreCharacter = () => {
2589 if (String(this_chid) !== String(chId)) {2657 if (String(this_chid) !== String(chId)) {
@@ -2601,7 +2669,7 @@ async function askCharacter(args, text) {
2601 // Only force the new avatar if the character name is the same2669 // Only force the new avatar if the character name is the same
2602 // This skips if an error was fired2670 // This skips if an error was fired
2603 const lastMessage = chat[chat.length - 1];2671 const lastMessage = chat[chat.length - 1];
2604 if (lastMessage && lastMessage?.name === character.name) {2672 if (lastMessage && lastMessage?.name === name) {
2605 lastMessage.force_avatar = force_avatar;2673 lastMessage.force_avatar = force_avatar;
2606 lastMessage.original_avatar = original_avatar;2674 lastMessage.original_avatar = original_avatar;
2607 }2675 }
@@ -2612,7 +2680,7 @@ async function askCharacter(args, text) {
2612 // Run generate and restore previous character2680 // Run generate and restore previous character
2613 try {2681 try {
2614 eventSource.once(event_types.MESSAGE_RECEIVED, restoreCharacter);2682 eventSource.once(event_types.MESSAGE_RECEIVED, restoreCharacter);
2615 toastr.info(`Asking ${character.name} something...`);2683 toastr.info(`Asking ${name} something...`);
2616 askResult = await Generate('ask_command');2684 askResult = await Generate('ask_command');
2617 } catch (error) {2685 } catch (error) {
2618 restoreCharacter();2686 restoreCharacter();
@@ -2808,26 +2876,23 @@ async function removeGroupMemberCallback(_, arg) {
2808 return '';2876 return '';
2809}2877}
28102878
2811async function addGroupMemberCallback(_, arg) {2879async function addGroupMemberCallback(_, name) {
2812 if (!selected_group) {2880 if (!selected_group) {
2813 toastr.warning('Cannot run /memberadd command outside of a group chat.');2881 toastr.warning('Cannot run /memberadd command outside of a group chat.');
2814 return '';2882 return '';
2815 }2883 }
28162884
2817 if (!arg) {2885 if (!name) {
2818 console.warn('WARN: No argument provided for /memberadd command');2886 console.warn('WARN: No argument provided for /memberadd command');
2819 return '';2887 return '';
2820 }2888 }
28212889
2822 arg = arg.trim();2890 const character = findChar({ name: name, preferCurrentChar: false });
2823 const chid = findCharacterIndex(arg);2891 if (!character) {
28242892 console.warn(`WARN: No character found for argument ${name}`);
2825 if (chid === -1) {
2826 console.warn(`WARN: No character found for argument ${arg}`);
2827 return '';2893 return '';
2828 }2894 }
28292895
2830 const character = characters[chid];
2831 const group = groups.find(x => x.id === selected_group);2896 const group = groups.find(x => x.id === selected_group);
28322897
2833 if (!group || !Array.isArray(group.members)) {2898 if (!group || !Array.isArray(group.members)) {
@@ -2896,7 +2961,7 @@ function findPersonaByName(name) {
2896 }2961 }
28972962
2898 for (const persona of Object.entries(power_user.personas)) {2963 for (const persona of Object.entries(power_user.personas)) {
2899 if (persona[1].toLowerCase() === name.toLowerCase()) {2964 if (equalsIgnoreCaseAndAccents(persona[1], name)) {
2900 return persona[0];2965 return persona[0];
2901 }2966 }
2902 }2967 }
@@ -2940,7 +3005,9 @@ async function deleteMessagesByNameCallback(_, name) {
2940 return;3005 return;
2941 }3006 }
29423007
2943 name = name.trim();3008 // Search for a matching character to get the real name, or take the name provided
3009 const character = findChar({ name: name });
3010 name = character?.name || name;
29443011
2945 const messagesToDelete = [];3012 const messagesToDelete = [];
2946 chat.forEach((value) => {3013 chat.forEach((value) => {
@@ -2969,60 +3036,34 @@ async function deleteMessagesByNameCallback(_, name) {
2969 return '';3036 return '';
2970}3037}
29713038
2972function findCharacterIndex(name) {
2973 const matchTypes = [
2974 (a, b) => a === b,
2975 (a, b) => a.startsWith(b),
2976 (a, b) => a.includes(b),
2977 ];
2978
2979 const exactAvatarMatch = characters.findIndex(x => x.avatar === name);
2980
2981 if (exactAvatarMatch !== -1) {
2982 return exactAvatarMatch;
2983 }
2984
2985 for (const matchType of matchTypes) {
2986 const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase()));
2987 if (index !== -1) {
2988 return index;
2989 }
2990 }
2991
2992 return -1;
2993}
2994
2995async function goToCharacterCallback(_, name) {3039async function goToCharacterCallback(_, name) {
2996 if (!name) {3040 if (!name) {
2997 console.warn('WARN: No character name provided for /go command');3041 console.warn('WARN: No character name provided for /go command');
2998 return;3042 return;
2999 }3043 }
30003044
3001 name = name.trim();3045 const character = findChar({ name: name });
3002 const characterIndex = findCharacterIndex(name);3046 if (character) {
30033047 const chid = getCharIndex(character);
3004 if (characterIndex !== -1) {3048 await openChat(new String(chid));
3005 await openChat(new String(characterIndex));3049 setActiveCharacter(character.avatar);
3006 setActiveCharacter(characters[characterIndex]?.avatar);
3007 setActiveGroup(null);3050 setActiveGroup(null);
3008 return characters[characterIndex]?.name;3051 return character.name;
3009 } else {3052 }
3010 const group = groups.find(it => it.name.toLowerCase() == name.toLowerCase());3053 const group = groups.find(it => equalsIgnoreCaseAndAccents(it.name, name));
3011 if (group) {3054 if (group) {
3012 await openGroupById(group.id);3055 await openGroupById(group.id);
3013 setActiveCharacter(null);3056 setActiveCharacter(null);
3014 setActiveGroup(group.id);3057 setActiveGroup(group.id);
3015 return group.name;3058 return group.name;
3016 } else {3059 }
3017 console.warn(`No matches found for name "${name}"`);3060 console.warn(`No matches found for name "${name}"`);
3018 return '';3061 return '';
3019}3062}
3020 }
3021}
30223063
3023async function openChat(id) {3064async function openChat(chid) {
3024 resetSelectedGroup();3065 resetSelectedGroup();
3025 setCharacterId(id);3066 setCharacterId(chid);
3026 await delay(1);3067 await delay(1);
3027 await reloadCurrentChat();3068 await reloadCurrentChat();
3028}3069}
@@ -3168,6 +3209,79 @@ async function setNarratorName(_, text) {
3168 return '';3209 return '';
3169}3210}
31703211
3212/**
3213 * Checks if an argument is a string array (or undefined), and if not, throws an error
3214 * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined} arg The named argument to check
3215 * @param {string} name The name of the argument for the error message
3216 * @param {object} [options={}] - The optional arguments
3217 * @param {boolean} [options.allowUndefined=false] - Whether the argument can be undefined
3218 * @throws {Error} If the argument is not an array
3219 * @returns {string[]}
3220 */
3221export function validateArrayArgString(arg, name, { allowUndefined = true } = {}) {
3222 if (arg === undefined) {
3223 if (allowUndefined) return undefined;
3224 throw new Error(`Argument "${name}" is undefined, but must be a string array`);
3225 }
3226 if (!Array.isArray(arg)) throw new Error(`Argument "${name}" must be an array`);
3227 if (!arg.every(x => typeof x === 'string')) throw new Error(`Argument "${name}" must be an array of strings`);
3228 return arg;
3229}
3230
3231/**
3232 * Checks if an argument is a string or closure array (or undefined), and if not, throws an error
3233 * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined} arg The named argument to check
3234 * @param {string} name The name of the argument for the error message
3235 * @param {object} [options={}] - The optional arguments
3236 * @param {boolean} [options.allowUndefined=false] - Whether the argument can be undefined
3237 * @throws {Error} If the argument is not an array of strings or closures
3238 * @returns {(string|SlashCommandClosure)[]}
3239 */
3240export function validateArrayArg(arg, name, { allowUndefined = true } = {}) {
3241 if (arg === undefined) {
3242 if (allowUndefined) return [];
3243 throw new Error(`Argument "${name}" is undefined, but must be an array of strings or closures`);
3244 }
3245 if (!Array.isArray(arg)) throw new Error(`Argument "${name}" must be an array`);
3246 if (!arg.every(x => typeof x === 'string' || x instanceof SlashCommandClosure)) throw new Error(`Argument "${name}" must be an array of strings or closures`);
3247 return arg;
3248}
3249
3250
3251/**
3252 * Retrieves the name and avatar information for a message
3253 *
3254 * The name of the character will always have precendence over the one given as argument. If you want to specify a different name for the message,
3255 * explicitly implement this in the code using this.
3256 *
3257 * @param {object?} character - The character object to get the avatar data for
3258 * @param {string?} name - The name to get the avatar data for
3259 * @returns {{name: string, force_avatar: string, original_avatar: string}} An object containing the name for the message, forced avatar URL, and original avatar
3260 */
3261export function getNameAndAvatarForMessage(character, name = null) {
3262 const isNeutralCharacter = !character && name2 === neutralCharacterName && name === neutralCharacterName;
3263 const currentChar = characters[this_chid];
3264
3265 let force_avatar, original_avatar;
3266 if (character?.avatar === currentChar?.avatar || isNeutralCharacter) {
3267 // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars
3268 }
3269 else if (character && character.avatar !== 'none') {
3270 force_avatar = getThumbnailUrl('avatar', character.avatar);
3271 original_avatar = character.avatar;
3272 }
3273 else {
3274 force_avatar = default_avatar;
3275 original_avatar = default_avatar;
3276 }
3277
3278 return {
3279 name: character?.name || name,
3280 force_avatar: force_avatar,
3281 original_avatar: original_avatar,
3282 };
3283}
3284
3171export async function sendMessageAs(args, text) {3285export async function sendMessageAs(args, text) {
3172 if (!text) {3286 if (!text) {
3173 toastr.warning('You must specify text to send as');3287 toastr.warning('You must specify text to send as');
@@ -3196,26 +3310,18 @@ export async function sendMessageAs(args, text) {
3196 const isSystem = bias && !removeMacros(mesText).length;3310 const isSystem = bias && !removeMacros(mesText).length;
3197 const compact = isTrueBoolean(args?.compact);3311 const compact = isTrueBoolean(args?.compact);
31983312
3199 const character = characters.find(x => x.avatar === name) ?? characters.find(x => x.name === name);3313 const character = findChar({ name: name });
3200 let force_avatar, original_avatar;
3201
3202 const chatCharacter = this_chid !== undefined ? characters[this_chid] : null;
3203 const isNeutralCharacter = !chatCharacter && name2 === neutralCharacterName && name === neutralCharacterName;
32043314
3205 if (chatCharacter === character || isNeutralCharacter) {3315 const avatarCharacter = args.avatar ? findChar({ name: args.avatar }) : character;
3206 // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars3316 if (args.avatar && !avatarCharacter) {
3207 }3317 toastr.warning(`Character for avatar ${args.avatar} not found`);
3208 else if (character && character.avatar !== 'none') {3318 return '';
3209 force_avatar = getThumbnailUrl('avatar', character.avatar);
3210 original_avatar = character.avatar;
3211 }
3212 else {
3213 force_avatar = default_avatar;
3214 original_avatar = default_avatar;
3215 }3319 }
32163320
3321 const { name: avatarCharName, force_avatar, original_avatar } = getNameAndAvatarForMessage(avatarCharacter, name);
3322
3217 const message = {3323 const message = {
3218 name: name,3324 name: character?.name || name || avatarCharName,
3219 is_user: false,3325 is_user: false,
3220 is_system: isSystem,3326 is_system: isSystem,
3221 send_date: getMessageTimeStamp(),3327 send_date: getMessageTimeStamp(),
public/scripts/slash-commands/SlashCommand.js+2 -2
@@ -15,13 +15,13 @@ import { SlashCommandScope } from './SlashCommandScope.js';
15 * _abortController:SlashCommandAbortController,15 * _abortController:SlashCommandAbortController,
16 * _debugController:SlashCommandDebugController,16 * _debugController:SlashCommandDebugController,
17 * _hasUnnamedArgument:boolean,17 * _hasUnnamedArgument:boolean,
18 * [id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[],18 * [id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined,
19 * }} NamedArguments19 * }} NamedArguments
20 */20 */
2121
22/**22/**
23 * Alternative object for local JSDocs, where you don't need existing pipe, scope, etc. arguments23 * Alternative object for local JSDocs, where you don't need existing pipe, scope, etc. arguments
24 * @typedef {{[id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]}} NamedArgumentsCapture24 * @typedef {{[id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined}} NamedArgumentsCapture
25 */25 */
2626
27/**27/**
public/scripts/slash-commands/SlashCommandClosure.js+8 -0
@@ -508,6 +508,14 @@ export class SlashCommandClosure {
508 return v;508 return v;
509 });509 });
510 }510 }
511
512 value ??= '';
513
514 // Make sure that if unnamed args are split, it should always return an array
515 if (executor.command.splitUnnamedArgument && !Array.isArray(value)) {
516 value = [value];
517 }
518
511 return value;519 return value;
512 }520 }
513521
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+43 -2
@@ -2,7 +2,7 @@ import { chat_metadata, characters, substituteParams, chat, extension_prompt_rol
2import { extension_settings } from '../extensions.js';2import { extension_settings } from '../extensions.js';
3import { getGroupMembers, groups } from '../group-chats.js';3import { getGroupMembers, groups } from '../group-chats.js';
4import { power_user } from '../power-user.js';4import { power_user } from '../power-user.js';
5import { searchCharByName, getTagsList, tags } from '../tags.js';5import { searchCharByName, getTagsList, tags, tag_map } from '../tags.js';
6import { world_names } from '../world-info.js';6import { world_names } from '../world-info.js';
7import { SlashCommandClosure } from './SlashCommandClosure.js';7import { SlashCommandClosure } from './SlashCommandClosure.js';
8import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js';8import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js';
@@ -154,6 +154,35 @@ export const commonEnumProviders = {
154 },154 },
155155
156 /**156 /**
157 * Enum values for numbers and variable names
158 *
159 * Includes all variable names and the ability to specify any number
160 *
161 * @param {SlashCommandExecutor} executor - The executor of the slash command
162 * @param {SlashCommandScope} scope - The scope of the slash command
163 * @returns {SlashCommandEnumValue[]} The enum values
164 */
165 numbersAndVariables: (executor, scope) => [
166 ...commonEnumProviders.variables('all')(executor, scope),
167 new SlashCommandEnumValue(
168 'any variable name',
169 null,
170 enumTypes.variable,
171 enumIcons.variable,
172 (input) => /^\w*$/.test(input),
173 (input) => input,
174 ),
175 new SlashCommandEnumValue(
176 'any number',
177 null,
178 enumTypes.number,
179 enumIcons.number,
180 (input) => input == '' || !Number.isNaN(Number(input)),
181 (input) => input,
182 ),
183 ],
184
185 /**
157 * All possible char entities, like characters and groups. Can be filtered down to just one type.186 * All possible char entities, like characters and groups. Can be filtered down to just one type.
158 *187 *
159 * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return188 * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return
@@ -183,6 +212,18 @@ export const commonEnumProviders = {
183 personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)),212 personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)),
184213
185 /**214 /**
215 * All possible tags, or only those that have been assigned
216 *
217 * @param {('all' | 'assigned')} [mode='all'] - Which types of tags to show
218 * @returns {() => SlashCommandEnumValue[]}
219 */
220 tags: (mode = 'all') => () => {
221 let assignedTags = mode === 'assigned' ? new Set(Object.values(tag_map).flat()) : new Set();
222 return tags.filter(tag => mode === 'all' || (mode === 'assigned' && assignedTags.has(tag.id)))
223 .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));
224 },
225
226 /**
186 * All possible tags for a given char/group entity227 * All possible tags for a given char/group entity
187 *228 *
188 * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show229 * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show
@@ -194,7 +235,7 @@ export const commonEnumProviders = {
194 if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');235 if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');
195 const key = searchCharByName(substituteParams(charName), { suppressLogging: true });236 const key = searchCharByName(substituteParams(charName), { suppressLogging: true });
196 const assigned = key ? getTagsList(key) : [];237 const assigned = key ? getTagsList(key) : [];
197 return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it))238 return tags.filter(it => mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it))
198 .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));239 .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));
199 },240 },
200241
public/scripts/tags.js+8 -8
@@ -15,7 +15,7 @@ import {
15import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';15import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
1616
17import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';17import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
18import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight, equalsIgnoreCaseAndAccents, includesIgnoreCaseAndAccents, removeFromArray, getFreeName, debounce } from './utils.js';18import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight, equalsIgnoreCaseAndAccents, includesIgnoreCaseAndAccents, removeFromArray, getFreeName, debounce, findChar } from './utils.js';
19import { power_user } from './power-user.js';19import { power_user } from './power-user.js';
20import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';20import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
21import { SlashCommand } from './slash-commands/SlashCommand.js';21import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -50,7 +50,6 @@ export {
50 removeTagFromMap,50 removeTagFromMap,
51};51};
5252
53/** @typedef {import('../scripts/popup.js').Popup} Popup */
54/** @typedef {import('../script.js').Character} Character */53/** @typedef {import('../script.js').Character} Character */
5554
56const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';55const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
@@ -507,7 +506,7 @@ export function getTagKeyForEntityElement(element) {
507 */506 */
508export function searchCharByName(charName, { suppressLogging = false } = {}) {507export function searchCharByName(charName, { suppressLogging = false } = {}) {
509 const entity = charName508 const entity = charName
510 ? (characters.find(x => x.name === charName) || groups.find(x => x.name == charName))509 ? (findChar({ name: charName }) || groups.find(x => equalsIgnoreCaseAndAccents(x.name, charName)))
511 : (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]);510 : (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]);
512 const key = getTagKeyForEntity(entity);511 const key = getTagKeyForEntity(entity);
513 if (!key) {512 if (!key) {
@@ -1861,8 +1860,9 @@ function registerTagsSlashCommands() {
1861 return String(result);1860 return String(result);
1862 },1861 },
1863 namedArgumentList: [1862 namedArgumentList: [
1864 SlashCommandNamedArgument.fromProps({ name: 'name',1863 SlashCommandNamedArgument.fromProps({
1865 description: 'Character name',1864 name: 'name',
1865 description: 'Character name - or unique character identifier (avatar key)',
1866 typeList: [ARGUMENT_TYPE.STRING],1866 typeList: [ARGUMENT_TYPE.STRING],
1867 defaultValue: '{{char}}',1867 defaultValue: '{{char}}',
1868 enumProvider: commonEnumProviders.characters(),1868 enumProvider: commonEnumProviders.characters(),
@@ -1907,7 +1907,7 @@ function registerTagsSlashCommands() {
1907 },1907 },
1908 namedArgumentList: [1908 namedArgumentList: [
1909 SlashCommandNamedArgument.fromProps({ name: 'name',1909 SlashCommandNamedArgument.fromProps({ name: 'name',
1910 description: 'Character name',1910 description: 'Character name - or unique character identifier (avatar key)',
1911 typeList: [ARGUMENT_TYPE.STRING],1911 typeList: [ARGUMENT_TYPE.STRING],
1912 defaultValue: '{{char}}',1912 defaultValue: '{{char}}',
1913 enumProvider: commonEnumProviders.characters(),1913 enumProvider: commonEnumProviders.characters(),
@@ -1950,7 +1950,7 @@ function registerTagsSlashCommands() {
1950 namedArgumentList: [1950 namedArgumentList: [
1951 SlashCommandNamedArgument.fromProps({1951 SlashCommandNamedArgument.fromProps({
1952 name: 'name',1952 name: 'name',
1953 description: 'Character name',1953 description: 'Character name - or unique character identifier (avatar key)',
1954 typeList: [ARGUMENT_TYPE.STRING],1954 typeList: [ARGUMENT_TYPE.STRING],
1955 defaultValue: '{{char}}',1955 defaultValue: '{{char}}',
1956 enumProvider: commonEnumProviders.characters(),1956 enumProvider: commonEnumProviders.characters(),
@@ -1993,7 +1993,7 @@ function registerTagsSlashCommands() {
1993 namedArgumentList: [1993 namedArgumentList: [
1994 SlashCommandNamedArgument.fromProps({1994 SlashCommandNamedArgument.fromProps({
1995 name: 'name',1995 name: 'name',
1996 description: 'Character name',1996 description: 'Character name - or unique character identifier (avatar key)',
1997 typeList: [ARGUMENT_TYPE.STRING],1997 typeList: [ARGUMENT_TYPE.STRING],
1998 defaultValue: '{{char}}',1998 defaultValue: '{{char}}',
1999 enumProvider: commonEnumProviders.characters(),1999 enumProvider: commonEnumProviders.characters(),
public/scripts/utils.js+74 -1
@@ -1,10 +1,12 @@
1import { getContext } from './extensions.js';1import { getContext } from './extensions.js';
2import { getRequestHeaders } from '../script.js';2import { characters, getRequestHeaders, this_chid } from '../script.js';
3import { isMobile } from './RossAscends-mods.js';3import { isMobile } from './RossAscends-mods.js';
4import { collapseNewlines } from './power-user.js';4import { collapseNewlines } from './power-user.js';
5import { debounce_timeout } from './constants.js';5import { debounce_timeout } from './constants.js';
6import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';6import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
7import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';7import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
8import { getTagsList } from './tags.js';
9import { groups, selected_group } from './group-chats.js';
810
9/**11/**
10 * Pagination status string template.12 * Pagination status string template.
@@ -2110,3 +2112,74 @@ export async function showFontAwesomePicker(customList = null) {
2110 }2112 }
2111 return null;2113 return null;
2112}2114}
2115
2116/**
2117 * Finds a character by name, with optional filtering and precedence for avatars
2118 * @param {object} [options={}] - The options for the search
2119 * @param {string?} [options.name=null] - The name to search for
2120 * @param {boolean} [options.allowAvatar=true] - Whether to allow searching by avatar
2121 * @param {boolean} [options.insensitive=true] - Whether the search should be case insensitive
2122 * @param {string[]?} [options.filteredByTags=null] - Tags to filter characters by
2123 * @param {boolean} [options.preferCurrentChar=true] - Whether to prefer the current character(s)
2124 * @param {boolean} [options.quiet=false] - Whether to suppress warnings
2125 * @returns {any?} - The found character or null if not found
2126 */
2127export function findChar({ name = null, allowAvatar = true, insensitive = true, filteredByTags = null, preferCurrentChar = true, quiet = false } = {}) {
2128 const matches = (char) => !name || (allowAvatar && char.avatar === name) || (insensitive ? equalsIgnoreCaseAndAccents(char.name, name) : char.name === name);
2129
2130 // Filter characters by tags if provided
2131 let filteredCharacters = characters;
2132 if (filteredByTags) {
2133 filteredCharacters = characters.filter(char => {
2134 const charTags = getTagsList(char.avatar, false);
2135 return filteredByTags.every(tagName => charTags.some(x => x.name == tagName));
2136 });
2137 }
2138
2139 // Get the current character(s)
2140 /** @type {any[]} */
2141 const currentChars = selected_group ? groups.find(group => group.id === selected_group)?.members.map(member => filteredCharacters.find(char => char.avatar === member))
2142 : filteredCharacters.filter(char => characters[this_chid]?.avatar === char.avatar);
2143
2144 // If we have a current char and prefer it, return that if it matches
2145 if (preferCurrentChar) {
2146 const preferredCharSearch = currentChars.filter(matches);
2147 if (preferredCharSearch.length > 1) {
2148 if (!quiet) toastr.warning('Multiple characters found for given conditions.');
2149 else console.warn('Multiple characters found for given conditions. Returning the first match.');
2150 }
2151 if (preferredCharSearch.length) {
2152 return preferredCharSearch[0];
2153 }
2154 }
2155
2156 // If allowAvatar is true, search by avatar first
2157 if (allowAvatar && name) {
2158 const characterByAvatar = filteredCharacters.find(char => char.avatar === name);
2159 if (characterByAvatar) {
2160 return characterByAvatar;
2161 }
2162 }
2163
2164 // Search for matching characters by name
2165 const matchingCharacters = name ? filteredCharacters.filter(matches) : filteredCharacters;
2166 if (matchingCharacters.length > 1) {
2167 if (!quiet) toastr.warning('Multiple characters found for given conditions.');
2168 else console.warn('Multiple characters found for given conditions. Returning the first match.');
2169 }
2170
2171 return matchingCharacters[0] || null;
2172}
2173
2174/**
2175 * Gets the index of a character based on the character object
2176 * @param {object} char - The character object to find the index for
2177 * @throws {Error} If the character is not found
2178 * @returns {number} The index of the character in the characters array
2179 */
2180export function getCharIndex(char) {
2181 if (!char) throw new Error('Character is undefined');
2182 const index = characters.findIndex(c => c.avatar === char.avatar);
2183 if (index === -1) throw new Error(`Character not found: ${char.avatar}`);
2184 return index;
2185}
public/scripts/variables.js+77 -58
@@ -683,8 +683,8 @@ function deleteGlobalVariable(name) {
683}683}
684684
685/**685/**
686 * Parses a series of numeric values from a string.686 * Parses a series of numeric values from a string or a string array.
687 * @param {string} value A space-separated list of numeric values or variable names687 * @param {string|string[]} value A space-separated list of numeric values or variable names
688 * @param {SlashCommandScope} scope Scope688 * @param {SlashCommandScope} scope Scope
689 * @returns {number[]} An array of numeric values689 * @returns {number[]} An array of numeric values
690 */690 */
@@ -693,11 +693,17 @@ function parseNumericSeries(value, scope = null) {
693 return [value];693 return [value];
694 }694 }
695695
696 const array = value696 /** @type {(string|number)[]} */
697 .split(' ')697 let values = Array.isArray(value) ? value : value.split(' ');
698 .map(i => i.trim())698
699 // If a JSON array was provided as the only value, convert it to an array
700 if (values.length === 1 && typeof values[0] === 'string' && values[0].startsWith('[')) {
701 values = convertValueType(values[0], 'array');
702 }
703
704 const array = values.map(i => typeof i === 'string' ? i.trim() : i)
699 .filter(i => i !== '')705 .filter(i => i !== '')
700 .map(i => isNaN(Number(i)) ? Number(resolveVariable(i, scope)) : Number(i))706 .map(i => isNaN(Number(i)) ? Number(resolveVariable(String(i), scope)) : Number(i))
701 .filter(i => !isNaN(i));707 .filter(i => !isNaN(i));
702708
703 return array;709 return array;
@@ -717,7 +723,7 @@ function performOperation(value, operation, singleOperand = false, scope = null)
717723
718 const result = singleOperand ? operation(array[0]) : operation(array);724 const result = singleOperand ? operation(array[0]) : operation(array);
719725
720 if (isNaN(result) || !isFinite(result)) {726 if (isNaN(result)) {
721 return 0;727 return 0;
722 }728 }
723729
@@ -745,7 +751,7 @@ function maxValuesCallback(args, value) {
745}751}
746752
747function subValuesCallback(args, value) {753function subValuesCallback(args, value) {
748 return performOperation(value, (array) => array[0] - array[1], false, args._scope);754 return performOperation(value, (array) => array.reduce((a, b) => a - b, array.shift() ?? 0), false, args._scope);
749}755}
750756
751function divValuesCallback(args, value) {757function divValuesCallback(args, value) {
@@ -1618,36 +1624,15 @@ export function registerVariableCommands() {
1618 }));1624 }));
1619 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1625 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1620 name: 'add',1626 name: 'add',
1621 callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')),1627 callback: (args, value) => addValuesCallback(args, value),
1622 returns: 'sum of the provided values',1628 returns: 'sum of the provided values',
1623 unnamedArgumentList: [1629 unnamedArgumentList: [
1624 SlashCommandArgument.fromProps({1630 SlashCommandArgument.fromProps({
1625 description: 'values to sum',1631 description: 'values to sum',
1626 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1632 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1627 isRequired: true,1633 isRequired: true,
1628 acceptsMultiple: true,1634 acceptsMultiple: true,
1629 enumProvider: (executor, scope) => {1635 enumProvider: commonEnumProviders.numbersAndVariables,
1630 const vars = commonEnumProviders.variables('all')(executor, scope);
1631 vars.push(
1632 new SlashCommandEnumValue(
1633 'any variable name',
1634 null,
1635 enumTypes.variable,
1636 enumIcons.variable,
1637 (input) => /^\w*$/.test(input),
1638 (input) => input,
1639 ),
1640 new SlashCommandEnumValue(
1641 'any number',
1642 null,
1643 enumTypes.number,
1644 enumIcons.number,
1645 (input) => input == '' || !Number.isNaN(Number(input)),
1646 (input) => input,
1647 ),
1648 );
1649 return vars;
1650 },
1651 forceEnum: false,1636 forceEnum: false,
1652 }),1637 }),
1653 ],1638 ],
@@ -1655,7 +1640,9 @@ export function registerVariableCommands() {
1655 helpString: `1640 helpString: `
1656 <div>1641 <div>
1657 Performs an addition of the set of values and passes the result down the pipe.1642 Performs an addition of the set of values and passes the result down the pipe.
1658 Can use variable names.1643 </div>
1644 <div>
1645 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1659 </div>1646 </div>
1660 <div>1647 <div>
1661 <strong>Example:</strong>1648 <strong>Example:</strong>
@@ -1663,6 +1650,9 @@ export function registerVariableCommands() {
1663 <li>1650 <li>
1664 <pre><code class="language-stscript">/add 10 i 30 j</code></pre>1651 <pre><code class="language-stscript">/add 10 i 30 j</code></pre>
1665 </li>1652 </li>
1653 <li>
1654 <pre><code class="language-stscript">/add ["count", 15, 2, "i"]</code></pre>
1655 </li>
1666 </ul>1656 </ul>
1667 </div>1657 </div>
1668 `,1658 `,
@@ -1674,16 +1664,20 @@ export function registerVariableCommands() {
1674 unnamedArgumentList: [1664 unnamedArgumentList: [
1675 SlashCommandArgument.fromProps({1665 SlashCommandArgument.fromProps({
1676 description: 'values to multiply',1666 description: 'values to multiply',
1677 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1667 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1678 isRequired: true,1668 isRequired: true,
1679 acceptsMultiple: true,1669 acceptsMultiple: true,
1680 enumProvider: commonEnumProviders.variables('all'),1670 enumProvider: commonEnumProviders.numbersAndVariables,
1681 forceEnum: false,1671 forceEnum: false,
1682 }),1672 }),
1683 ],1673 ],
1674 splitUnnamedArgument: true,
1684 helpString: `1675 helpString: `
1685 <div>1676 <div>
1686 Performs a multiplication of the set of values and passes the result down the pipe. Can use variable names.1677 Performs a multiplication of the set of values and passes the result down the pipe.
1678 </div>
1679 <div>
1680 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1687 </div>1681 </div>
1688 <div>1682 <div>
1689 <strong>Examples:</strong>1683 <strong>Examples:</strong>
@@ -1691,6 +1685,9 @@ export function registerVariableCommands() {
1691 <li>1685 <li>
1692 <pre><code class="language-stscript">/mul 10 i 30 j</code></pre>1686 <pre><code class="language-stscript">/mul 10 i 30 j</code></pre>
1693 </li>1687 </li>
1688 <li>
1689 <pre><code class="language-stscript">/mul ["count", 15, 2, "i"]</code></pre>
1690 </li>
1694 </ul>1691 </ul>
1695 </div>1692 </div>
1696 `,1693 `,
@@ -1702,16 +1699,20 @@ export function registerVariableCommands() {
1702 unnamedArgumentList: [1699 unnamedArgumentList: [
1703 SlashCommandArgument.fromProps({1700 SlashCommandArgument.fromProps({
1704 description: 'values to find the max',1701 description: 'values to find the max',
1705 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1702 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1706 isRequired: true,1703 isRequired: true,
1707 acceptsMultiple: true,1704 acceptsMultiple: true,
1708 enumProvider: commonEnumProviders.variables('all'),1705 enumProvider: commonEnumProviders.numbersAndVariables,
1709 forceEnum: false,1706 forceEnum: false,
1710 }),1707 }),
1711 ],1708 ],
1709 splitUnnamedArgument: true,
1712 helpString: `1710 helpString: `
1713 <div>1711 <div>
1714 Returns the maximum value of the set of values and passes the result down the pipe. Can use variable names.1712 Returns the maximum value of the set of values and passes the result down the pipe.
1713 </div>
1714 <div>
1715 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1715 </div>1716 </div>
1716 <div>1717 <div>
1717 <strong>Examples:</strong>1718 <strong>Examples:</strong>
@@ -1719,6 +1720,9 @@ export function registerVariableCommands() {
1719 <li>1720 <li>
1720 <pre><code class="language-stscript">/max 10 i 30 j</code></pre>1721 <pre><code class="language-stscript">/max 10 i 30 j</code></pre>
1721 </li>1722 </li>
1723 <li>
1724 <pre><code class="language-stscript">/max ["count", 15, 2, "i"]</code></pre>
1725 </li>
1722 </ul>1726 </ul>
1723 </div>1727 </div>
1724 `,1728 `,
@@ -1730,17 +1734,20 @@ export function registerVariableCommands() {
1730 unnamedArgumentList: [1734 unnamedArgumentList: [
1731 SlashCommandArgument.fromProps({1735 SlashCommandArgument.fromProps({
1732 description: 'values to find the min',1736 description: 'values to find the min',
1733 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1737 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1734 isRequired: true,1738 isRequired: true,
1735 acceptsMultiple: true,1739 acceptsMultiple: true,
1736 enumProvider: commonEnumProviders.variables('all'),1740 enumProvider: commonEnumProviders.numbersAndVariables,
1737 forceEnum: false,1741 forceEnum: false,
1738 }),1742 }),
1739 ],1743 ],
1744 splitUnnamedArgument: true,
1740 helpString: `1745 helpString: `
1741 <div>1746 <div>
1742 Returns the minimum value of the set of values and passes the result down the pipe.1747 Returns the minimum value of the set of values and passes the result down the pipe.
1743 Can use variable names.1748 </div>
1749 <div>
1750 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1744 </div>1751 </div>
1745 <div>1752 <div>
1746 <strong>Example:</strong>1753 <strong>Example:</strong>
@@ -1748,6 +1755,9 @@ export function registerVariableCommands() {
1748 <li>1755 <li>
1749 <pre><code class="language-stscript">/min 10 i 30 j</code></pre>1756 <pre><code class="language-stscript">/min 10 i 30 j</code></pre>
1750 </li>1757 </li>
1758 <li>
1759 <pre><code class="language-stscript">/min ["count", 15, 2, "i"]</code></pre>
1760 </li>
1751 </ul>1761 </ul>
1752 </div>1762 </div>
1753 `,1763 `,
@@ -1758,18 +1768,21 @@ export function registerVariableCommands() {
1758 returns: 'difference of the provided values',1768 returns: 'difference of the provided values',
1759 unnamedArgumentList: [1769 unnamedArgumentList: [
1760 SlashCommandArgument.fromProps({1770 SlashCommandArgument.fromProps({
1761 description: 'values to find the difference',1771 description: 'values to subtract, starting form the first provided value',
1762 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1772 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1763 isRequired: true,1773 isRequired: true,
1764 acceptsMultiple: true,1774 acceptsMultiple: true,
1765 enumProvider: commonEnumProviders.variables('all'),1775 enumProvider: commonEnumProviders.numbersAndVariables,
1766 forceEnum: false,1776 forceEnum: false,
1767 }),1777 }),
1768 ],1778 ],
1779 splitUnnamedArgument: true,
1769 helpString: `1780 helpString: `
1770 <div>1781 <div>
1771 Performs a subtraction of the set of values and passes the result down the pipe.1782 Performs a subtraction of the set of values and passes the result down the pipe.
1772 Can use variable names.1783 </div>
1784 <div>
1785 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1773 </div>1786 </div>
1774 <div>1787 <div>
1775 <strong>Example:</strong>1788 <strong>Example:</strong>
@@ -1777,6 +1790,9 @@ export function registerVariableCommands() {
1777 <li>1790 <li>
1778 <pre><code class="language-stscript">/sub i 5</code></pre>1791 <pre><code class="language-stscript">/sub i 5</code></pre>
1779 </li>1792 </li>
1793 <li>
1794 <pre><code class="language-stscript">/sub ["count", 4, "i"]</code></pre>
1795 </li>
1780 </ul>1796 </ul>
1781 </div>1797 </div>
1782 `,1798 `,
@@ -1790,17 +1806,18 @@ export function registerVariableCommands() {
1790 description: 'dividend',1806 description: 'dividend',
1791 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1807 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1792 isRequired: true,1808 isRequired: true,
1793 enumProvider: commonEnumProviders.variables('all'),1809 enumProvider: commonEnumProviders.numbersAndVariables,
1794 forceEnum: false,1810 forceEnum: false,
1795 }),1811 }),
1796 SlashCommandArgument.fromProps({1812 SlashCommandArgument.fromProps({
1797 description: 'divisor',1813 description: 'divisor',
1798 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1814 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1799 isRequired: true,1815 isRequired: true,
1800 enumProvider: commonEnumProviders.variables('all'),1816 enumProvider: commonEnumProviders.numbersAndVariables,
1801 forceEnum: false,1817 forceEnum: false,
1802 }),1818 }),
1803 ],1819 ],
1820 splitUnnamedArgument: true,
1804 helpString: `1821 helpString: `
1805 <div>1822 <div>
1806 Performs a division of two values and passes the result down the pipe.1823 Performs a division of two values and passes the result down the pipe.
@@ -1825,17 +1842,18 @@ export function registerVariableCommands() {
1825 description: 'dividend',1842 description: 'dividend',
1826 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1843 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1827 isRequired: true,1844 isRequired: true,
1828 enumProvider: commonEnumProviders.variables('all'),1845 enumProvider: commonEnumProviders.numbersAndVariables,
1829 forceEnum: false,1846 forceEnum: false,
1830 }),1847 }),
1831 SlashCommandArgument.fromProps({1848 SlashCommandArgument.fromProps({
1832 description: 'divisor',1849 description: 'divisor',
1833 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1850 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1834 isRequired: true,1851 isRequired: true,
1835 enumProvider: commonEnumProviders.variables('all'),1852 enumProvider: commonEnumProviders.numbersAndVariables,
1836 forceEnum: false,1853 forceEnum: false,
1837 }),1854 }),
1838 ],1855 ],
1856 splitUnnamedArgument: true,
1839 helpString: `1857 helpString: `
1840 <div>1858 <div>
1841 Performs a modulo operation of two values and passes the result down the pipe.1859 Performs a modulo operation of two values and passes the result down the pipe.
@@ -1860,17 +1878,18 @@ export function registerVariableCommands() {
1860 description: 'base',1878 description: 'base',
1861 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1879 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1862 isRequired: true,1880 isRequired: true,
1863 enumProvider: commonEnumProviders.variables('all'),1881 enumProvider: commonEnumProviders.numbersAndVariables,
1864 forceEnum: false,1882 forceEnum: false,
1865 }),1883 }),
1866 SlashCommandArgument.fromProps({1884 SlashCommandArgument.fromProps({
1867 description: 'exponent',1885 description: 'exponent',
1868 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1886 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1869 isRequired: true,1887 isRequired: true,
1870 enumProvider: commonEnumProviders.variables('all'),1888 enumProvider: commonEnumProviders.numbersAndVariables,
1871 forceEnum: false,1889 forceEnum: false,
1872 }),1890 }),
1873 ],1891 ],
1892 splitUnnamedArgument: true,
1874 helpString: `1893 helpString: `
1875 <div>1894 <div>
1876 Performs a power operation of two values and passes the result down the pipe.1895 Performs a power operation of two values and passes the result down the pipe.
@@ -1895,7 +1914,7 @@ export function registerVariableCommands() {
1895 description: 'value',1914 description: 'value',
1896 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1915 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1897 isRequired: true,1916 isRequired: true,
1898 enumProvider: commonEnumProviders.variables('all'),1917 enumProvider: commonEnumProviders.numbersAndVariables,
1899 forceEnum: false,1918 forceEnum: false,
1900 }),1919 }),
1901 ],1920 ],
@@ -1923,7 +1942,7 @@ export function registerVariableCommands() {
1923 description: 'value',1942 description: 'value',
1924 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1943 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1925 isRequired: true,1944 isRequired: true,
1926 enumProvider: commonEnumProviders.variables('all'),1945 enumProvider: commonEnumProviders.numbersAndVariables,
1927 forceEnum: false,1946 forceEnum: false,
1928 }),1947 }),
1929 ],1948 ],
@@ -1952,7 +1971,7 @@ export function registerVariableCommands() {
1952 description: 'value',1971 description: 'value',
1953 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1972 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1954 isRequired: true,1973 isRequired: true,
1955 enumProvider: commonEnumProviders.variables('all'),1974 enumProvider: commonEnumProviders.numbersAndVariables,
1956 forceEnum: false,1975 forceEnum: false,
1957 }),1976 }),
1958 ],1977 ],
@@ -1980,7 +1999,7 @@ export function registerVariableCommands() {
1980 description: 'value',1999 description: 'value',
1981 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],2000 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1982 isRequired: true,2001 isRequired: true,
1983 enumProvider: commonEnumProviders.variables('all'),2002 enumProvider: commonEnumProviders.numbersAndVariables,
1984 forceEnum: false,2003 forceEnum: false,
1985 }),2004 }),
1986 ],2005 ],
@@ -2008,7 +2027,7 @@ export function registerVariableCommands() {
2008 description: 'value',2027 description: 'value',
2009 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],2028 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
2010 isRequired: true,2029 isRequired: true,
2011 enumProvider: commonEnumProviders.variables('all'),2030 enumProvider: commonEnumProviders.numbersAndVariables,
2012 forceEnum: false,2031 forceEnum: false,
2013 }),2032 }),
2014 ],2033 ],
@@ -2036,7 +2055,7 @@ export function registerVariableCommands() {
2036 description: 'value',2055 description: 'value',
2037 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],2056 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
2038 isRequired: true,2057 isRequired: true,
2039 enumProvider: commonEnumProviders.variables('all'),2058 enumProvider: commonEnumProviders.numbersAndVariables,
2040 forceEnum: false,2059 forceEnum: false,
2041 }),2060 }),
2042 ],2061 ],
src/endpoints/backends/chat-completions.js+1 -1
@@ -323,7 +323,7 @@ async function sendMakerSuiteRequest(request, response) {
323 ? (stream ? 'streamGenerateContent' : 'generateContent')323 ? (stream ? 'streamGenerateContent' : 'generateContent')
324 : (isText ? 'generateText' : 'generateMessage');324 : (isText ? 'generateText' : 'generateMessage');
325325
326 const generateResponse = await fetch(`${apiUrl.origin}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {326 const generateResponse = await fetch(`${apiUrl}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {
327 body: JSON.stringify(body),327 body: JSON.stringify(body),
328 method: 'POST',328 method: 'POST',
329 headers: {329 headers: {