Merge branch 'staging' into sd-args

909ac0c622a3f8052605aaec3380046d19477b64

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

12 files changed, +453 -189Ignore whitespace
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';
@@ -2105,14 +2105,20 @@ function migrateSettings() {
2105 }));2105 }));
2106 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2106 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2107 name: 'lastsprite',2107 name: 'lastsprite',
2108 callback: (_, value) => lastExpression[String(value).trim()] ?? '',2108 callback: (_, name) => {
2109 if (typeof name !== 'string') throw new Error('name must be a string');
2110 const char = findChar({ name: name });
2111 const sprite = lastExpression[char?.name ?? name] ?? '';
2112 return sprite;
2113 },
2109 returns: 'the last set sprite / expression for the named character.',2114 returns: 'the last set sprite / expression for the named character.',
2110 unnamedArgumentList: [2115 unnamedArgumentList: [
2111 SlashCommandArgument.fromProps({2116 SlashCommandArgument.fromProps({
2112 description: 'character name',2117 description: 'Character name - or unique character identifier (avatar key)',
2113 typeList: [ARGUMENT_TYPE.STRING],2118 typeList: [ARGUMENT_TYPE.STRING],
2114 isRequired: true,2119 isRequired: true,
2115 enumProvider: commonEnumProviders.characters('character'),2120 enumProvider: commonEnumProviders.characters('character'),
2121 forceEnum: true,
2116 }),2122 }),
2117 ],2123 ],
2118 helpString: 'Returns the last set sprite / expression for the named character.',2124 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+212 -106
@@ -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';
74export {73export {
@@ -174,20 +173,96 @@ export function initDefaultSlashCommands() {
174 `,173 `,
175 }));174 }));
176 SlashCommandParser.addCommandObject(SlashCommand.fromProps({175 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
176 name: 'char-find',
177 aliases: ['findchar'],
178 callback: (args, name) => {
179 if (typeof name !== 'string') throw new Error('name must be a string');
180 if (args.preferCurrent instanceof SlashCommandClosure || Array.isArray(args.preferCurrent)) throw new Error('preferCurrent cannot be a closure or array');
181 if (args.quiet instanceof SlashCommandClosure || Array.isArray(args.quiet)) throw new Error('quiet cannot be a closure or array');
182
183 const char = findChar({ name: name, filteredByTags: validateArrayArgString(args.tag, 'tag'), preferCurrentChar: !isFalseBoolean(args.preferCurrent), quiet: isTrueBoolean(args.quiet) });
184 return char?.avatar ?? '';
185 },
186 returns: 'the avatar key (unique identifier) of the character',
187 namedArgumentList: [
188 SlashCommandNamedArgument.fromProps({
189 name: 'tag',
190 description: 'Supply one or more tags to filter down to the correct character for the provided name, if multiple characters have the same name.',
191 typeList: [ARGUMENT_TYPE.STRING],
192 enumProvider: commonEnumProviders.tags('assigned'),
193 acceptsMultiple: true,
194 }),
195 SlashCommandNamedArgument.fromProps({
196 name: 'preferCurrent',
197 description: 'Prefer current character or characters in a group, if multiple characters match',
198 typeList: [ARGUMENT_TYPE.BOOLEAN],
199 defaultValue: 'true',
200 }),
201 SlashCommandNamedArgument.fromProps({
202 name: 'quiet',
203 description: 'Do not show warning if multiple charactrers are found',
204 typeList: [ARGUMENT_TYPE.BOOLEAN],
205 defaultValue: 'false',
206 enumProvider: commonEnumProviders.boolean('trueFalse'),
207 }),
208 ],
209 unnamedArgumentList: [
210 SlashCommandArgument.fromProps({
211 description: 'Character name - or unique character identifier (avatar key)',
212 typeList: [ARGUMENT_TYPE.STRING],
213 enumProvider: commonEnumProviders.characters('character'),
214 forceEnum: false,
215 }),
216 ],
217 helpString: `
218 <div>
219 Searches for a character and returns its avatar key.
220 </div>
221 <div>
222 This can be used to choose the correct character for something like <code>/sendas</code> or other commands in need of a character name
223 if you have multiple characters with the same name.
224 </div>
225 <div>
226 <strong>Example:</strong>
227 <ul>
228 <li>
229 <pre><code>/char-find name="Chloe"</code></pre>
230 Returns the avatar key for "Chloe".
231 </li>
232 <li>
233 <pre><code>/search name="Chloe" tag="friend"</code></pre>
234 Returns the avatar key for the character "Chloe" that is tagged with "friend".
235 This is useful if you for example have multiple characters named "Chloe", and the others are "foe", "goddess", or anything else,
236 so you can actually select the character you are looking for.
237 </li>
238 </ul>
239 </div>
240 `,
241 }));
242 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
177 name: 'sendas',243 name: 'sendas',
178 callback: sendMessageAs,244 callback: sendMessageAs,
179 namedArgumentList: [245 namedArgumentList: [
180 SlashCommandNamedArgument.fromProps({246 SlashCommandNamedArgument.fromProps({
181 name: 'name',247 name: 'name',
182 description: 'Character name',248 description: 'Character name - or unique character identifier (avatar key)',
183 typeList: [ARGUMENT_TYPE.STRING],249 typeList: [ARGUMENT_TYPE.STRING],
184 isRequired: true,250 isRequired: true,
185 enumProvider: commonEnumProviders.characters('character'),251 enumProvider: commonEnumProviders.characters('character'),
186 forceEnum: false,252 forceEnum: false,
187 }),253 }),
188 new SlashCommandNamedArgument(254 SlashCommandNamedArgument.fromProps({
189 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',255 name: 'avatar',
190 ),256 description: 'Character avatar override (Can be either avatar key or just the character name to pull the avatar from)',
257 typeList: [ARGUMENT_TYPE.STRING],
258 enumProvider: commonEnumProviders.characters('character'),
259 }),
260 SlashCommandNamedArgument.fromProps({
261 name: 'compact',
262 description: 'Use compact layout',
263 typeList: [ARGUMENT_TYPE.BOOLEAN],
264 defaultValue: 'false',
265 }),
191 SlashCommandNamedArgument.fromProps({266 SlashCommandNamedArgument.fromProps({
192 name: 'at',267 name: 'at',
193 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.',268 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.',
@@ -211,6 +286,10 @@ export function initDefaultSlashCommands() {
211 <pre><code>/sendas name="Chloe" Hello, guys!</code></pre>286 <pre><code>/sendas name="Chloe" Hello, guys!</code></pre>
212 will send "Hello, guys!" from "Chloe".287 will send "Hello, guys!" from "Chloe".
213 </li>288 </li>
289 <li>
290 <pre><code>/sendas name="Chloe" avatar="BigBadBoss" Hehehe, I am the big bad evil, fear me.</code></pre>
291 will send a message as the character "Chloe", but utilizing the avatar from a character named "BigBadBoss".
292 </li>
214 </ul>293 </ul>
215 </div>294 </div>
216 <div>295 <div>
@@ -381,12 +460,14 @@ export function initDefaultSlashCommands() {
381 SlashCommandParser.addCommandObject(SlashCommand.fromProps({460 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
382 name: 'go',461 name: 'go',
383 callback: goToCharacterCallback,462 callback: goToCharacterCallback,
463 returns: 'The character/group name',
384 unnamedArgumentList: [464 unnamedArgumentList: [
385 SlashCommandArgument.fromProps({465 SlashCommandArgument.fromProps({
386 description: 'name',466 description: 'Character name - or unique character identifier (avatar key)',
387 typeList: [ARGUMENT_TYPE.STRING],467 typeList: [ARGUMENT_TYPE.STRING],
388 isRequired: true,468 isRequired: true,
389 enumProvider: commonEnumProviders.characters('all'),469 enumProvider: commonEnumProviders.characters('all'),
470 forceEnum: true,
390 }),471 }),
391 ],472 ],
392 helpString: 'Opens up a chat with the character or group by its name',473 helpString: 'Opens up a chat with the character or group by its name',
@@ -432,7 +513,7 @@ export function initDefaultSlashCommands() {
432 namedArgumentList: [513 namedArgumentList: [
433 SlashCommandNamedArgument.fromProps({514 SlashCommandNamedArgument.fromProps({
434 name: 'name',515 name: 'name',
435 description: 'character name',516 description: 'Character name - or unique character identifier (avatar key)',
436 typeList: [ARGUMENT_TYPE.STRING],517 typeList: [ARGUMENT_TYPE.STRING],
437 isRequired: true,518 isRequired: true,
438 enumProvider: commonEnumProviders.characters('character'),519 enumProvider: commonEnumProviders.characters('character'),
@@ -451,7 +532,7 @@ export function initDefaultSlashCommands() {
451 namedArgumentList: [],532 namedArgumentList: [],
452 unnamedArgumentList: [533 unnamedArgumentList: [
453 SlashCommandArgument.fromProps({534 SlashCommandArgument.fromProps({
454 description: 'name',535 description: 'Character name - or unique character identifier (avatar key)',
455 typeList: [ARGUMENT_TYPE.STRING],536 typeList: [ARGUMENT_TYPE.STRING],
456 isRequired: true,537 isRequired: true,
457 enumProvider: commonEnumProviders.characters('character'),538 enumProvider: commonEnumProviders.characters('character'),
@@ -618,7 +699,7 @@ export function initDefaultSlashCommands() {
618 aliases: ['addmember', 'memberadd'],699 aliases: ['addmember', 'memberadd'],
619 unnamedArgumentList: [700 unnamedArgumentList: [
620 SlashCommandArgument.fromProps({701 SlashCommandArgument.fromProps({
621 description: 'character name',702 description: 'Character name - or unique character identifier (avatar key)',
622 typeList: [ARGUMENT_TYPE.STRING],703 typeList: [ARGUMENT_TYPE.STRING],
623 isRequired: true,704 isRequired: true,
624 enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [],705 enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [],
@@ -856,7 +937,7 @@ export function initDefaultSlashCommands() {
856 ),937 ),
857 SlashCommandNamedArgument.fromProps({938 SlashCommandNamedArgument.fromProps({
858 name: 'name',939 name: 'name',
859 description: 'in-prompt name for instruct mode',940 description: 'in-prompt character name for instruct mode (or unique character identifier (avatar key), which will be used as name)',
860 typeList: [ARGUMENT_TYPE.STRING],941 typeList: [ARGUMENT_TYPE.STRING],
861 defaultValue: 'System',942 defaultValue: 'System',
862 enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],943 enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],
@@ -2293,7 +2374,8 @@ async function generateCallback(args, value) {
22932374
2294 setEphemeralStopStrings(resolveVariable(args?.stop));2375 setEphemeralStopStrings(resolveVariable(args?.stop));
2295 const name = args?.name;2376 const name = args?.name;
2296 const result = await generateQuietPrompt(value, quietToLoud, false, '', name, length);2377 const char = findChar({ name: name });
2378 const result = await generateQuietPrompt(value, quietToLoud, false, '', char?.name ?? name, length);
2297 return result;2379 return result;
2298 } catch (err) {2380 } catch (err) {
2299 console.error('Error on /gen generation', err);2381 console.error('Error on /gen generation', err);
@@ -2481,26 +2563,22 @@ async function askCharacter(args, text) {
2481 return '';2563 return '';
2482 }2564 }
24832565
2484 let name = '';2566 if (!args.name) {
24852567 toastr.warning('You must specify a name of the character to ask.');
2486 if (args?.name) {2568 return '';
2487 name = args.name.trim();
2488
2489 if (!name) {
2490 toastr.warning('You must specify a name of the character to ask.');
2491 return '';
2492 }
2493 }2569 }
24942570
2495 const prevChId = this_chid;2571 const prevChId = this_chid;
24962572
2497 // Find the character2573 // Find the character
2498 const chId = characters.findIndex((e) => e.name === name || e.avatar === name);2574 const character = findChar({ name: args?.name });
2499 if (!characters[chId] || chId === -1) {2575 if (!character) {
2500 toastr.error('Character not found.');2576 toastr.error('Character not found.');
2501 return '';2577 return '';
2502 }2578 }
25032579
2580 const chId = getCharIndex(character);
2581
2504 if (text) {2582 if (text) {
2505 const mesText = getRegexedString(text.trim(), regex_placement.SLASH_COMMAND);2583 const mesText = getRegexedString(text.trim(), regex_placement.SLASH_COMMAND);
2506 // Sending a message implicitly saves the chat, so this needs to be done before changing the character2584 // Sending a message implicitly saves the chat, so this needs to be done before changing the character
@@ -2511,19 +2589,9 @@ async function askCharacter(args, text) {
2511 // Override character and send a user message2589 // Override character and send a user message
2512 setCharacterId(String(chId));2590 setCharacterId(String(chId));
25132591
2514 const character = characters[chId];2592 const { name, force_avatar, original_avatar } = getNameAndAvatarForMessage(character, args?.name);
2515 let force_avatar, original_avatar;
2516
2517 if (character && character.avatar !== 'none') {
2518 force_avatar = getThumbnailUrl('avatar', character.avatar);
2519 original_avatar = character.avatar;
2520 }
2521 else {
2522 force_avatar = default_avatar;
2523 original_avatar = default_avatar;
2524 }
25252593
2526 setCharacterName(character.name);2594 setCharacterName(name);
25272595
2528 const restoreCharacter = () => {2596 const restoreCharacter = () => {
2529 if (String(this_chid) !== String(chId)) {2597 if (String(this_chid) !== String(chId)) {
@@ -2541,7 +2609,7 @@ async function askCharacter(args, text) {
2541 // Only force the new avatar if the character name is the same2609 // Only force the new avatar if the character name is the same
2542 // This skips if an error was fired2610 // This skips if an error was fired
2543 const lastMessage = chat[chat.length - 1];2611 const lastMessage = chat[chat.length - 1];
2544 if (lastMessage && lastMessage?.name === character.name) {2612 if (lastMessage && lastMessage?.name === name) {
2545 lastMessage.force_avatar = force_avatar;2613 lastMessage.force_avatar = force_avatar;
2546 lastMessage.original_avatar = original_avatar;2614 lastMessage.original_avatar = original_avatar;
2547 }2615 }
@@ -2552,7 +2620,7 @@ async function askCharacter(args, text) {
2552 // Run generate and restore previous character2620 // Run generate and restore previous character
2553 try {2621 try {
2554 eventSource.once(event_types.MESSAGE_RECEIVED, restoreCharacter);2622 eventSource.once(event_types.MESSAGE_RECEIVED, restoreCharacter);
2555 toastr.info(`Asking ${character.name} something...`);2623 toastr.info(`Asking ${name} something...`);
2556 askResult = await Generate('ask_command');2624 askResult = await Generate('ask_command');
2557 } catch (error) {2625 } catch (error) {
2558 restoreCharacter();2626 restoreCharacter();
@@ -2746,26 +2814,23 @@ async function removeGroupMemberCallback(_, arg) {
2746 return '';2814 return '';
2747}2815}
27482816
2749async function addGroupMemberCallback(_, arg) {2817async function addGroupMemberCallback(_, name) {
2750 if (!selected_group) {2818 if (!selected_group) {
2751 toastr.warning('Cannot run /memberadd command outside of a group chat.');2819 toastr.warning('Cannot run /memberadd command outside of a group chat.');
2752 return '';2820 return '';
2753 }2821 }
27542822
2755 if (!arg) {2823 if (!name) {
2756 console.warn('WARN: No argument provided for /memberadd command');2824 console.warn('WARN: No argument provided for /memberadd command');
2757 return '';2825 return '';
2758 }2826 }
27592827
2760 arg = arg.trim();2828 const character = findChar({ name: name, preferCurrentChar: false });
2761 const chid = findCharacterIndex(arg);2829 if (!character) {
27622830 console.warn(`WARN: No character found for argument ${name}`);
2763 if (chid === -1) {
2764 console.warn(`WARN: No character found for argument ${arg}`);
2765 return '';2831 return '';
2766 }2832 }
27672833
2768 const character = characters[chid];
2769 const group = groups.find(x => x.id === selected_group);2834 const group = groups.find(x => x.id === selected_group);
27702835
2771 if (!group || !Array.isArray(group.members)) {2836 if (!group || !Array.isArray(group.members)) {
@@ -2834,7 +2899,7 @@ function findPersonaByName(name) {
2834 }2899 }
28352900
2836 for (const persona of Object.entries(power_user.personas)) {2901 for (const persona of Object.entries(power_user.personas)) {
2837 if (persona[1].toLowerCase() === name.toLowerCase()) {2902 if (equalsIgnoreCaseAndAccents(persona[1], name)) {
2838 return persona[0];2903 return persona[0];
2839 }2904 }
2840 }2905 }
@@ -2877,7 +2942,9 @@ async function deleteMessagesByNameCallback(_, name) {
2877 return;2942 return;
2878 }2943 }
28792944
2880 name = name.trim();2945 // Search for a matching character to get the real name, or take the name provided
2946 const character = findChar({ name: name });
2947 name = character?.name || name;
28812948
2882 const messagesToDelete = [];2949 const messagesToDelete = [];
2883 chat.forEach((value) => {2950 chat.forEach((value) => {
@@ -2906,60 +2973,34 @@ async function deleteMessagesByNameCallback(_, name) {
2906 return '';2973 return '';
2907}2974}
29082975
2909function findCharacterIndex(name) {
2910 const matchTypes = [
2911 (a, b) => a === b,
2912 (a, b) => a.startsWith(b),
2913 (a, b) => a.includes(b),
2914 ];
2915
2916 const exactAvatarMatch = characters.findIndex(x => x.avatar === name);
2917
2918 if (exactAvatarMatch !== -1) {
2919 return exactAvatarMatch;
2920 }
2921
2922 for (const matchType of matchTypes) {
2923 const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase()));
2924 if (index !== -1) {
2925 return index;
2926 }
2927 }
2928
2929 return -1;
2930}
2931
2932async function goToCharacterCallback(_, name) {2976async function goToCharacterCallback(_, name) {
2933 if (!name) {2977 if (!name) {
2934 console.warn('WARN: No character name provided for /go command');2978 console.warn('WARN: No character name provided for /go command');
2935 return;2979 return;
2936 }2980 }
29372981
2938 name = name.trim();2982 const character = findChar({ name: name });
2939 const characterIndex = findCharacterIndex(name);2983 if (character) {
29402984 const chid = getCharIndex(character);
2941 if (characterIndex !== -1) {2985 await openChat(new String(chid));
2942 await openChat(new String(characterIndex));2986 setActiveCharacter(character.avatar);
2943 setActiveCharacter(characters[characterIndex]?.avatar);
2944 setActiveGroup(null);2987 setActiveGroup(null);
2945 return characters[characterIndex]?.name;2988 return character.name;
2946 } else {2989 }
2947 const group = groups.find(it => it.name.toLowerCase() == name.toLowerCase());2990 const group = groups.find(it => equalsIgnoreCaseAndAccents(it.name, name));
2948 if (group) {2991 if (group) {
2949 await openGroupById(group.id);2992 await openGroupById(group.id);
2950 setActiveCharacter(null);2993 setActiveCharacter(null);
2951 setActiveGroup(group.id);2994 setActiveGroup(group.id);
2952 return group.name;2995 return group.name;
2953 } else {
2954 console.warn(`No matches found for name "${name}"`);
2955 return '';
2956 }
2957 }2996 }
2997 console.warn(`No matches found for name "${name}"`);
2998 return '';
2958}2999}
29593000
2960async function openChat(id) {3001async function openChat(chid) {
2961 resetSelectedGroup();3002 resetSelectedGroup();
2962 setCharacterId(id);3003 setCharacterId(chid);
2963 await delay(1);3004 await delay(1);
2964 await reloadCurrentChat();3005 await reloadCurrentChat();
2965}3006}
@@ -3105,6 +3146,79 @@ async function setNarratorName(_, text) {
3105 return '';3146 return '';
3106}3147}
31073148
3149/**
3150 * Checks if an argument is a string array (or undefined), and if not, throws an error
3151 * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined} arg The named argument to check
3152 * @param {string} name The name of the argument for the error message
3153 * @param {object} [options={}] - The optional arguments
3154 * @param {boolean} [options.allowUndefined=false] - Whether the argument can be undefined
3155 * @throws {Error} If the argument is not an array
3156 * @returns {string[]}
3157 */
3158export function validateArrayArgString(arg, name, { allowUndefined = true } = {}) {
3159 if (arg === undefined) {
3160 if (allowUndefined) return undefined;
3161 throw new Error(`Argument "${name}" is undefined, but must be a string array`);
3162 }
3163 if (!Array.isArray(arg)) throw new Error(`Argument "${name}" must be an array`);
3164 if (!arg.every(x => typeof x === 'string')) throw new Error(`Argument "${name}" must be an array of strings`);
3165 return arg;
3166}
3167
3168/**
3169 * Checks if an argument is a string or closure array (or undefined), and if not, throws an error
3170 * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined} arg The named argument to check
3171 * @param {string} name The name of the argument for the error message
3172 * @param {object} [options={}] - The optional arguments
3173 * @param {boolean} [options.allowUndefined=false] - Whether the argument can be undefined
3174 * @throws {Error} If the argument is not an array of strings or closures
3175 * @returns {(string|SlashCommandClosure)[]}
3176 */
3177export function validateArrayArg(arg, name, { allowUndefined = true } = {}) {
3178 if (arg === undefined) {
3179 if (allowUndefined) return [];
3180 throw new Error(`Argument "${name}" is undefined, but must be an array of strings or closures`);
3181 }
3182 if (!Array.isArray(arg)) throw new Error(`Argument "${name}" must be an array`);
3183 if (!arg.every(x => typeof x === 'string' || x instanceof SlashCommandClosure)) throw new Error(`Argument "${name}" must be an array of strings or closures`);
3184 return arg;
3185}
3186
3187
3188/**
3189 * Retrieves the name and avatar information for a message
3190 *
3191 * 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,
3192 * explicitly implement this in the code using this.
3193 *
3194 * @param {object?} character - The character object to get the avatar data for
3195 * @param {string?} name - The name to get the avatar data for
3196 * @returns {{name: string, force_avatar: string, original_avatar: string}} An object containing the name for the message, forced avatar URL, and original avatar
3197 */
3198export function getNameAndAvatarForMessage(character, name = null) {
3199 const isNeutralCharacter = !character && name2 === neutralCharacterName && name === neutralCharacterName;
3200 const currentChar = characters[this_chid];
3201
3202 let force_avatar, original_avatar;
3203 if (character?.avatar === currentChar?.avatar || isNeutralCharacter) {
3204 // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars
3205 }
3206 else if (character && character.avatar !== 'none') {
3207 force_avatar = getThumbnailUrl('avatar', character.avatar);
3208 original_avatar = character.avatar;
3209 }
3210 else {
3211 force_avatar = default_avatar;
3212 original_avatar = default_avatar;
3213 }
3214
3215 return {
3216 name: character?.name || name,
3217 force_avatar: force_avatar,
3218 original_avatar: original_avatar,
3219 };
3220}
3221
3108export async function sendMessageAs(args, text) {3222export async function sendMessageAs(args, text) {
3109 if (!text) {3223 if (!text) {
3110 return '';3224 return '';
@@ -3143,26 +3257,18 @@ export async function sendMessageAs(args, text) {
3143 const isSystem = bias && !removeMacros(mesText).length;3257 const isSystem = bias && !removeMacros(mesText).length;
3144 const compact = isTrueBoolean(args?.compact);3258 const compact = isTrueBoolean(args?.compact);
31453259
3146 const character = characters.find(x => x.avatar === name) ?? characters.find(x => x.name === name);3260 const character = findChar({ name: name });
3147 let force_avatar, original_avatar;
3148
3149 const chatCharacter = this_chid !== undefined ? characters[this_chid] : null;
3150 const isNeutralCharacter = !chatCharacter && name2 === neutralCharacterName && name === neutralCharacterName;
31513261
3152 if (chatCharacter === character || isNeutralCharacter) {3262 const avatarCharacter = args.avatar ? findChar({ name: args.avatar }) : character;
3153 // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars3263 if (args.avatar && !avatarCharacter) {
3154 }3264 toastr.warning(`Character for avatar ${args.avatar} not found`);
3155 else if (character && character.avatar !== 'none') {3265 return '';
3156 force_avatar = getThumbnailUrl('avatar', character.avatar);
3157 original_avatar = character.avatar;
3158 }
3159 else {
3160 force_avatar = default_avatar;
3161 original_avatar = default_avatar;
3162 }3266 }
31633267
3268 const { name: avatarCharName, force_avatar, original_avatar } = getNameAndAvatarForMessage(avatarCharacter, name);
3269
3164 const message = {3270 const message = {
3165 name: name,3271 name: character?.name || name || avatarCharName,
3166 is_user: false,3272 is_user: false,
3167 is_system: isSystem,3273 is_system: isSystem,
3168 send_date: getMessageTimeStamp(),3274 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';
@@ -153,6 +153,35 @@ export const commonEnumProviders = {
153 },153 },
154154
155 /**155 /**
156 * Enum values for numbers and variable names
157 *
158 * Includes all variable names and the ability to specify any number
159 *
160 * @param {SlashCommandExecutor} executor - The executor of the slash command
161 * @param {SlashCommandScope} scope - The scope of the slash command
162 * @returns {SlashCommandEnumValue[]} The enum values
163 */
164 numbersAndVariables: (executor, scope) => [
165 ...commonEnumProviders.variables('all')(executor, scope),
166 new SlashCommandEnumValue(
167 'any variable name',
168 null,
169 enumTypes.variable,
170 enumIcons.variable,
171 (input) => /^\w*$/.test(input),
172 (input) => input,
173 ),
174 new SlashCommandEnumValue(
175 'any number',
176 null,
177 enumTypes.number,
178 enumIcons.number,
179 (input) => input == '' || !Number.isNaN(Number(input)),
180 (input) => input,
181 ),
182 ],
183
184 /**
156 * All possible char entities, like characters and groups. Can be filtered down to just one type.185 * All possible char entities, like characters and groups. Can be filtered down to just one type.
157 *186 *
158 * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return187 * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return
@@ -182,6 +211,18 @@ export const commonEnumProviders = {
182 personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)),211 personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)),
183212
184 /**213 /**
214 * All possible tags, or only those that have been assigned
215 *
216 * @param {('all' | 'assigned')} [mode='all'] - Which types of tags to show
217 * @returns {() => SlashCommandEnumValue[]}
218 */
219 tags: (mode = 'all') => () => {
220 let assignedTags = mode === 'assigned' ? new Set(Object.values(tag_map).flat()) : new Set();
221 return tags.filter(tag => mode === 'all' || (mode === 'assigned' && assignedTags.has(tag.id)))
222 .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));
223 },
224
225 /**
185 * All possible tags for a given char/group entity226 * All possible tags for a given char/group entity
186 *227 *
187 * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show228 * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show
@@ -193,7 +234,7 @@ export const commonEnumProviders = {
193 if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');234 if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');
194 const key = searchCharByName(substituteParams(charName), { suppressLogging: true });235 const key = searchCharByName(substituteParams(charName), { suppressLogging: true });
195 const assigned = key ? getTagsList(key) : [];236 const assigned = key ? getTagsList(key) : [];
196 return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it))237 return tags.filter(it => mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it))
197 .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));238 .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));
198 },239 },
199240
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
@@ -669,8 +669,8 @@ function deleteGlobalVariable(name) {
669}669}
670670
671/**671/**
672 * Parses a series of numeric values from a string.672 * Parses a series of numeric values from a string or a string array.
673 * @param {string} value A space-separated list of numeric values or variable names673 * @param {string|string[]} value A space-separated list of numeric values or variable names
674 * @param {SlashCommandScope} scope Scope674 * @param {SlashCommandScope} scope Scope
675 * @returns {number[]} An array of numeric values675 * @returns {number[]} An array of numeric values
676 */676 */
@@ -679,11 +679,17 @@ function parseNumericSeries(value, scope = null) {
679 return [value];679 return [value];
680 }680 }
681681
682 const array = value682 /** @type {(string|number)[]} */
683 .split(' ')683 let values = Array.isArray(value) ? value : value.split(' ');
684 .map(i => i.trim())684
685 // If a JSON array was provided as the only value, convert it to an array
686 if (values.length === 1 && typeof values[0] === 'string' && values[0].startsWith('[')) {
687 values = convertValueType(values[0], 'array');
688 }
689
690 const array = values.map(i => typeof i === 'string' ? i.trim() : i)
685 .filter(i => i !== '')691 .filter(i => i !== '')
686 .map(i => isNaN(Number(i)) ? Number(resolveVariable(i, scope)) : Number(i))692 .map(i => isNaN(Number(i)) ? Number(resolveVariable(String(i), scope)) : Number(i))
687 .filter(i => !isNaN(i));693 .filter(i => !isNaN(i));
688694
689 return array;695 return array;
@@ -703,7 +709,7 @@ function performOperation(value, operation, singleOperand = false, scope = null)
703709
704 const result = singleOperand ? operation(array[0]) : operation(array);710 const result = singleOperand ? operation(array[0]) : operation(array);
705711
706 if (isNaN(result) || !isFinite(result)) {712 if (isNaN(result)) {
707 return 0;713 return 0;
708 }714 }
709715
@@ -731,7 +737,7 @@ function maxValuesCallback(args, value) {
731}737}
732738
733function subValuesCallback(args, value) {739function subValuesCallback(args, value) {
734 return performOperation(value, (array) => array[0] - array[1], false, args._scope);740 return performOperation(value, (array) => array.reduce((a, b) => a - b, array.shift() ?? 0), false, args._scope);
735}741}
736742
737function divValuesCallback(args, value) {743function divValuesCallback(args, value) {
@@ -1595,36 +1601,15 @@ export function registerVariableCommands() {
1595 }));1601 }));
1596 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1602 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1597 name: 'add',1603 name: 'add',
1598 callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')),1604 callback: (args, value) => addValuesCallback(args, value),
1599 returns: 'sum of the provided values',1605 returns: 'sum of the provided values',
1600 unnamedArgumentList: [1606 unnamedArgumentList: [
1601 SlashCommandArgument.fromProps({1607 SlashCommandArgument.fromProps({
1602 description: 'values to sum',1608 description: 'values to sum',
1603 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1609 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1604 isRequired: true,1610 isRequired: true,
1605 acceptsMultiple: true,1611 acceptsMultiple: true,
1606 enumProvider: (executor, scope) => {1612 enumProvider: commonEnumProviders.numbersAndVariables,
1607 const vars = commonEnumProviders.variables('all')(executor, scope);
1608 vars.push(
1609 new SlashCommandEnumValue(
1610 'any variable name',
1611 null,
1612 enumTypes.variable,
1613 enumIcons.variable,
1614 (input) => /^\w*$/.test(input),
1615 (input) => input,
1616 ),
1617 new SlashCommandEnumValue(
1618 'any number',
1619 null,
1620 enumTypes.number,
1621 enumIcons.number,
1622 (input) => input == '' || !Number.isNaN(Number(input)),
1623 (input) => input,
1624 ),
1625 );
1626 return vars;
1627 },
1628 forceEnum: false,1613 forceEnum: false,
1629 }),1614 }),
1630 ],1615 ],
@@ -1632,7 +1617,9 @@ export function registerVariableCommands() {
1632 helpString: `1617 helpString: `
1633 <div>1618 <div>
1634 Performs an addition of the set of values and passes the result down the pipe.1619 Performs an addition of the set of values and passes the result down the pipe.
1635 Can use variable names.1620 </div>
1621 <div>
1622 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1636 </div>1623 </div>
1637 <div>1624 <div>
1638 <strong>Example:</strong>1625 <strong>Example:</strong>
@@ -1640,6 +1627,9 @@ export function registerVariableCommands() {
1640 <li>1627 <li>
1641 <pre><code class="language-stscript">/add 10 i 30 j</code></pre>1628 <pre><code class="language-stscript">/add 10 i 30 j</code></pre>
1642 </li>1629 </li>
1630 <li>
1631 <pre><code class="language-stscript">/add ["count", 15, 2, "i"]</code></pre>
1632 </li>
1643 </ul>1633 </ul>
1644 </div>1634 </div>
1645 `,1635 `,
@@ -1651,16 +1641,20 @@ export function registerVariableCommands() {
1651 unnamedArgumentList: [1641 unnamedArgumentList: [
1652 SlashCommandArgument.fromProps({1642 SlashCommandArgument.fromProps({
1653 description: 'values to multiply',1643 description: 'values to multiply',
1654 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1644 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1655 isRequired: true,1645 isRequired: true,
1656 acceptsMultiple: true,1646 acceptsMultiple: true,
1657 enumProvider: commonEnumProviders.variables('all'),1647 enumProvider: commonEnumProviders.numbersAndVariables,
1658 forceEnum: false,1648 forceEnum: false,
1659 }),1649 }),
1660 ],1650 ],
1651 splitUnnamedArgument: true,
1661 helpString: `1652 helpString: `
1662 <div>1653 <div>
1663 Performs a multiplication of the set of values and passes the result down the pipe. Can use variable names.1654 Performs a multiplication of the set of values and passes the result down the pipe.
1655 </div>
1656 <div>
1657 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1664 </div>1658 </div>
1665 <div>1659 <div>
1666 <strong>Examples:</strong>1660 <strong>Examples:</strong>
@@ -1668,6 +1662,9 @@ export function registerVariableCommands() {
1668 <li>1662 <li>
1669 <pre><code class="language-stscript">/mul 10 i 30 j</code></pre>1663 <pre><code class="language-stscript">/mul 10 i 30 j</code></pre>
1670 </li>1664 </li>
1665 <li>
1666 <pre><code class="language-stscript">/mul ["count", 15, 2, "i"]</code></pre>
1667 </li>
1671 </ul>1668 </ul>
1672 </div>1669 </div>
1673 `,1670 `,
@@ -1679,16 +1676,20 @@ export function registerVariableCommands() {
1679 unnamedArgumentList: [1676 unnamedArgumentList: [
1680 SlashCommandArgument.fromProps({1677 SlashCommandArgument.fromProps({
1681 description: 'values to find the max',1678 description: 'values to find the max',
1682 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1679 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1683 isRequired: true,1680 isRequired: true,
1684 acceptsMultiple: true,1681 acceptsMultiple: true,
1685 enumProvider: commonEnumProviders.variables('all'),1682 enumProvider: commonEnumProviders.numbersAndVariables,
1686 forceEnum: false,1683 forceEnum: false,
1687 }),1684 }),
1688 ],1685 ],
1686 splitUnnamedArgument: true,
1689 helpString: `1687 helpString: `
1690 <div>1688 <div>
1691 Returns the maximum value of the set of values and passes the result down the pipe. Can use variable names.1689 Returns the maximum value of the set of values and passes the result down the pipe.
1690 </div>
1691 <div>
1692 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1692 </div>1693 </div>
1693 <div>1694 <div>
1694 <strong>Examples:</strong>1695 <strong>Examples:</strong>
@@ -1696,6 +1697,9 @@ export function registerVariableCommands() {
1696 <li>1697 <li>
1697 <pre><code class="language-stscript">/max 10 i 30 j</code></pre>1698 <pre><code class="language-stscript">/max 10 i 30 j</code></pre>
1698 </li>1699 </li>
1700 <li>
1701 <pre><code class="language-stscript">/max ["count", 15, 2, "i"]</code></pre>
1702 </li>
1699 </ul>1703 </ul>
1700 </div>1704 </div>
1701 `,1705 `,
@@ -1707,17 +1711,20 @@ export function registerVariableCommands() {
1707 unnamedArgumentList: [1711 unnamedArgumentList: [
1708 SlashCommandArgument.fromProps({1712 SlashCommandArgument.fromProps({
1709 description: 'values to find the min',1713 description: 'values to find the min',
1710 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1714 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1711 isRequired: true,1715 isRequired: true,
1712 acceptsMultiple: true,1716 acceptsMultiple: true,
1713 enumProvider: commonEnumProviders.variables('all'),1717 enumProvider: commonEnumProviders.numbersAndVariables,
1714 forceEnum: false,1718 forceEnum: false,
1715 }),1719 }),
1716 ],1720 ],
1721 splitUnnamedArgument: true,
1717 helpString: `1722 helpString: `
1718 <div>1723 <div>
1719 Returns the minimum value of the set of values and passes the result down the pipe.1724 Returns the minimum value of the set of values and passes the result down the pipe.
1720 Can use variable names.1725 </div>
1726 <div>
1727 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1721 </div>1728 </div>
1722 <div>1729 <div>
1723 <strong>Example:</strong>1730 <strong>Example:</strong>
@@ -1725,6 +1732,9 @@ export function registerVariableCommands() {
1725 <li>1732 <li>
1726 <pre><code class="language-stscript">/min 10 i 30 j</code></pre>1733 <pre><code class="language-stscript">/min 10 i 30 j</code></pre>
1727 </li>1734 </li>
1735 <li>
1736 <pre><code class="language-stscript">/min ["count", 15, 2, "i"]</code></pre>
1737 </li>
1728 </ul>1738 </ul>
1729 </div>1739 </div>
1730 `,1740 `,
@@ -1735,18 +1745,21 @@ export function registerVariableCommands() {
1735 returns: 'difference of the provided values',1745 returns: 'difference of the provided values',
1736 unnamedArgumentList: [1746 unnamedArgumentList: [
1737 SlashCommandArgument.fromProps({1747 SlashCommandArgument.fromProps({
1738 description: 'values to find the difference',1748 description: 'values to subtract, starting form the first provided value',
1739 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1749 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1740 isRequired: true,1750 isRequired: true,
1741 acceptsMultiple: true,1751 acceptsMultiple: true,
1742 enumProvider: commonEnumProviders.variables('all'),1752 enumProvider: commonEnumProviders.numbersAndVariables,
1743 forceEnum: false,1753 forceEnum: false,
1744 }),1754 }),
1745 ],1755 ],
1756 splitUnnamedArgument: true,
1746 helpString: `1757 helpString: `
1747 <div>1758 <div>
1748 Performs a subtraction of the set of values and passes the result down the pipe.1759 Performs a subtraction of the set of values and passes the result down the pipe.
1749 Can use variable names.1760 </div>
1761 <div>
1762 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1750 </div>1763 </div>
1751 <div>1764 <div>
1752 <strong>Example:</strong>1765 <strong>Example:</strong>
@@ -1754,6 +1767,9 @@ export function registerVariableCommands() {
1754 <li>1767 <li>
1755 <pre><code class="language-stscript">/sub i 5</code></pre>1768 <pre><code class="language-stscript">/sub i 5</code></pre>
1756 </li>1769 </li>
1770 <li>
1771 <pre><code class="language-stscript">/sub ["count", 4, "i"]</code></pre>
1772 </li>
1757 </ul>1773 </ul>
1758 </div>1774 </div>
1759 `,1775 `,
@@ -1767,17 +1783,18 @@ export function registerVariableCommands() {
1767 description: 'dividend',1783 description: 'dividend',
1768 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1784 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1769 isRequired: true,1785 isRequired: true,
1770 enumProvider: commonEnumProviders.variables('all'),1786 enumProvider: commonEnumProviders.numbersAndVariables,
1771 forceEnum: false,1787 forceEnum: false,
1772 }),1788 }),
1773 SlashCommandArgument.fromProps({1789 SlashCommandArgument.fromProps({
1774 description: 'divisor',1790 description: 'divisor',
1775 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1791 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1776 isRequired: true,1792 isRequired: true,
1777 enumProvider: commonEnumProviders.variables('all'),1793 enumProvider: commonEnumProviders.numbersAndVariables,
1778 forceEnum: false,1794 forceEnum: false,
1779 }),1795 }),
1780 ],1796 ],
1797 splitUnnamedArgument: true,
1781 helpString: `1798 helpString: `
1782 <div>1799 <div>
1783 Performs a division of two values and passes the result down the pipe.1800 Performs a division of two values and passes the result down the pipe.
@@ -1802,17 +1819,18 @@ export function registerVariableCommands() {
1802 description: 'dividend',1819 description: 'dividend',
1803 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1820 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1804 isRequired: true,1821 isRequired: true,
1805 enumProvider: commonEnumProviders.variables('all'),1822 enumProvider: commonEnumProviders.numbersAndVariables,
1806 forceEnum: false,1823 forceEnum: false,
1807 }),1824 }),
1808 SlashCommandArgument.fromProps({1825 SlashCommandArgument.fromProps({
1809 description: 'divisor',1826 description: 'divisor',
1810 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1827 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1811 isRequired: true,1828 isRequired: true,
1812 enumProvider: commonEnumProviders.variables('all'),1829 enumProvider: commonEnumProviders.numbersAndVariables,
1813 forceEnum: false,1830 forceEnum: false,
1814 }),1831 }),
1815 ],1832 ],
1833 splitUnnamedArgument: true,
1816 helpString: `1834 helpString: `
1817 <div>1835 <div>
1818 Performs a modulo operation of two values and passes the result down the pipe.1836 Performs a modulo operation of two values and passes the result down the pipe.
@@ -1837,17 +1855,18 @@ export function registerVariableCommands() {
1837 description: 'base',1855 description: 'base',
1838 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1856 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1839 isRequired: true,1857 isRequired: true,
1840 enumProvider: commonEnumProviders.variables('all'),1858 enumProvider: commonEnumProviders.numbersAndVariables,
1841 forceEnum: false,1859 forceEnum: false,
1842 }),1860 }),
1843 SlashCommandArgument.fromProps({1861 SlashCommandArgument.fromProps({
1844 description: 'exponent',1862 description: 'exponent',
1845 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1863 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1846 isRequired: true,1864 isRequired: true,
1847 enumProvider: commonEnumProviders.variables('all'),1865 enumProvider: commonEnumProviders.numbersAndVariables,
1848 forceEnum: false,1866 forceEnum: false,
1849 }),1867 }),
1850 ],1868 ],
1869 splitUnnamedArgument: true,
1851 helpString: `1870 helpString: `
1852 <div>1871 <div>
1853 Performs a power operation of two values and passes the result down the pipe.1872 Performs a power operation of two values and passes the result down the pipe.
@@ -1872,7 +1891,7 @@ export function registerVariableCommands() {
1872 description: 'value',1891 description: 'value',
1873 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1892 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1874 isRequired: true,1893 isRequired: true,
1875 enumProvider: commonEnumProviders.variables('all'),1894 enumProvider: commonEnumProviders.numbersAndVariables,
1876 forceEnum: false,1895 forceEnum: false,
1877 }),1896 }),
1878 ],1897 ],
@@ -1900,7 +1919,7 @@ export function registerVariableCommands() {
1900 description: 'value',1919 description: 'value',
1901 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1920 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1902 isRequired: true,1921 isRequired: true,
1903 enumProvider: commonEnumProviders.variables('all'),1922 enumProvider: commonEnumProviders.numbersAndVariables,
1904 forceEnum: false,1923 forceEnum: false,
1905 }),1924 }),
1906 ],1925 ],
@@ -1929,7 +1948,7 @@ export function registerVariableCommands() {
1929 description: 'value',1948 description: 'value',
1930 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1949 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1931 isRequired: true,1950 isRequired: true,
1932 enumProvider: commonEnumProviders.variables('all'),1951 enumProvider: commonEnumProviders.numbersAndVariables,
1933 forceEnum: false,1952 forceEnum: false,
1934 }),1953 }),
1935 ],1954 ],
@@ -1957,7 +1976,7 @@ export function registerVariableCommands() {
1957 description: 'value',1976 description: 'value',
1958 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1977 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1959 isRequired: true,1978 isRequired: true,
1960 enumProvider: commonEnumProviders.variables('all'),1979 enumProvider: commonEnumProviders.numbersAndVariables,
1961 forceEnum: false,1980 forceEnum: false,
1962 }),1981 }),
1963 ],1982 ],
@@ -1985,7 +2004,7 @@ export function registerVariableCommands() {
1985 description: 'value',2004 description: 'value',
1986 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],2005 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1987 isRequired: true,2006 isRequired: true,
1988 enumProvider: commonEnumProviders.variables('all'),2007 enumProvider: commonEnumProviders.numbersAndVariables,
1989 forceEnum: false,2008 forceEnum: false,
1990 }),2009 }),
1991 ],2010 ],
@@ -2013,7 +2032,7 @@ export function registerVariableCommands() {
2013 description: 'value',2032 description: 'value',
2014 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],2033 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
2015 isRequired: true,2034 isRequired: true,
2016 enumProvider: commonEnumProviders.variables('all'),2035 enumProvider: commonEnumProviders.numbersAndVariables,
2017 forceEnum: false,2036 forceEnum: false,
2018 }),2037 }),
2019 ],2038 ],
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: {