Merge branch 'staging' into parser-followup-2

This commit is contained in:
LenAnderson
2024-07-12 08:33:28 -04:00
12 changed files with 162 additions and 116 deletions

View File

@ -101,6 +101,7 @@ import {
proxies, proxies,
loadProxyPresets, loadProxyPresets,
selected_proxy, selected_proxy,
initOpenai,
} from './scripts/openai.js'; } from './scripts/openai.js';
import { import {
@ -915,6 +916,7 @@ async function firstLoadInit() {
initKeyboard(); initKeyboard();
initDynamicStyles(); initDynamicStyles();
initTags(); initTags();
initOpenai();
await getUserAvatars(true, user_avatar); await getUserAvatars(true, user_avatar);
await getCharacters(); await getCharacters();
await getBackgrounds(); await getBackgrounds();
@ -9232,12 +9234,15 @@ jQuery(async function () {
* @param {HTMLTextAreaElement} e Textarea element to auto-fit * @param {HTMLTextAreaElement} e Textarea element to auto-fit
*/ */
function autoFitEditTextArea(e) { function autoFitEditTextArea(e) {
const computedStyle = window.getComputedStyle(e);
const maxHeight = parseInt(computedStyle.maxHeight, 10);
scroll_holder = chatElement[0].scrollTop; scroll_holder = chatElement[0].scrollTop;
e.style.height = '0'; e.style.height = computedStyle.minHeight;
e.style.height = `${e.scrollHeight + 4}px`; const newHeight = e.scrollHeight + 4;
e.style.height = (newHeight < maxHeight) ? `${newHeight}px` : `${maxHeight}px`;
is_use_scroll_holder = true; is_use_scroll_holder = true;
} }
const autoFitEditTextAreaDebounced = debouncedThrottle(autoFitEditTextArea, debounce_timeout.standard); const autoFitEditTextAreaDebounced = debouncedThrottle(autoFitEditTextArea, debounce_timeout.short);
document.addEventListener('input', e => { document.addEventListener('input', e => {
if (e.target instanceof HTMLTextAreaElement && e.target.classList.contains('edit_textarea')) { if (e.target instanceof HTMLTextAreaElement && e.target.classList.contains('edit_textarea')) {
const immediately = e.target.scrollHeight > e.target.offsetHeight || e.target.value === ''; const immediately = e.target.scrollHeight > e.target.offsetHeight || e.target.value === '';

View File

@ -31,7 +31,7 @@ import {
SECRET_KEYS, SECRET_KEYS,
secret_state, secret_state,
} from './secrets.js'; } from './secrets.js';
import { debounce, getStringHash, isValidUrl } from './utils.js'; import { debounce, debouncedThrottle, getStringHash, isValidUrl } from './utils.js';
import { chat_completion_sources, oai_settings } from './openai.js'; import { chat_completion_sources, oai_settings } from './openai.js';
import { getTokenCountAsync } from './tokenizers.js'; import { getTokenCountAsync } from './tokenizers.js';
import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer } from './textgen-settings.js'; import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer } from './textgen-settings.js';
@ -694,20 +694,23 @@ const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
* this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height) * this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
*/ */
function autoFitSendTextArea() { function autoFitSendTextArea() {
// Needs to be pulled dynamically because it is affected by font size changes
const computedStyle = window.getComputedStyle(sendTextArea);
const originalScrollBottom = chatBlock.scrollHeight - (chatBlock.scrollTop + chatBlock.offsetHeight); const originalScrollBottom = chatBlock.scrollHeight - (chatBlock.scrollTop + chatBlock.offsetHeight);
if (Math.ceil(sendTextArea.scrollHeight + 3) >= Math.floor(sendTextArea.offsetHeight)) { if (Math.ceil(sendTextArea.scrollHeight + 3) >= Math.floor(sendTextArea.offsetHeight)) {
// Needs to be pulled dynamically because it is affected by font size changes const sendTextAreaMinHeight = computedStyle.minHeight;
const sendTextAreaMinHeight = window.getComputedStyle(sendTextArea).getPropertyValue('min-height');
sendTextArea.style.height = sendTextAreaMinHeight; sendTextArea.style.height = sendTextAreaMinHeight;
} }
sendTextArea.style.height = sendTextArea.scrollHeight + 3 + 'px'; const maxHeight = parseInt(computedStyle.maxHeight, 10);
const newHeight = sendTextArea.scrollHeight + 3;
sendTextArea.style.height = (newHeight < maxHeight) ? `${newHeight}px` : `${maxHeight}px`;
if (!isFirefox) { if (!isFirefox) {
const newScrollTop = Math.round(chatBlock.scrollHeight - (chatBlock.offsetHeight + originalScrollBottom)); const newScrollTop = Math.round(chatBlock.scrollHeight - (chatBlock.offsetHeight + originalScrollBottom));
chatBlock.scrollTop = newScrollTop; chatBlock.scrollTop = newScrollTop;
} }
} }
export const autoFitSendTextAreaDebounced = debounce(autoFitSendTextArea); export const autoFitSendTextAreaDebounced = debouncedThrottle(autoFitSendTextArea, debounce_timeout.short);
// --------------------------------------------------- // ---------------------------------------------------

View File

@ -333,8 +333,9 @@ async function getCaptionForFile(file, prompt, quiet) {
return caption; return caption;
} }
catch (error) { catch (error) {
toastr.error('Failed to caption image.'); const errorMessage = error.message || 'Unknown error';
console.log(error); toastr.error(errorMessage, "Failed to caption image.");
console.error(error);
return ''; return '';
} }
finally { finally {

View File

@ -914,7 +914,7 @@ jQuery(async function () {
await addExtensionControls(); await addExtensionControls();
loadSettings(); loadSettings();
eventSource.on(event_types.MESSAGE_RECEIVED, onChatEvent); eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onChatEvent);
eventSource.on(event_types.MESSAGE_DELETED, onChatEvent); eventSource.on(event_types.MESSAGE_DELETED, onChatEvent);
eventSource.on(event_types.MESSAGE_EDITED, onChatEvent); eventSource.on(event_types.MESSAGE_EDITED, onChatEvent);
eventSource.on(event_types.MESSAGE_SWIPED, onChatEvent); eventSource.on(event_types.MESSAGE_SWIPED, onChatEvent);

View File

@ -239,7 +239,7 @@ eventSource.on(event_types.CHAT_CHANGED, (...args)=>executeIfReadyElseQueue(onCh
const onUserMessage = async () => { const onUserMessage = async () => {
await autoExec.handleUser(); await autoExec.handleUser();
}; };
eventSource.on(event_types.USER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onUserMessage, args)); eventSource.makeFirst(event_types.USER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onUserMessage, args));
const onAiMessage = async (messageId) => { const onAiMessage = async (messageId) => {
if (['...'].includes(chat[messageId]?.mes)) { if (['...'].includes(chat[messageId]?.mes)) {
@ -249,7 +249,7 @@ const onAiMessage = async (messageId) => {
await autoExec.handleAi(); await autoExec.handleAi();
}; };
eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onAiMessage, args)); eventSource.makeFirst(event_types.CHARACTER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onAiMessage, args));
const onGroupMemberDraft = async () => { const onGroupMemberDraft = async () => {
await autoExec.handleGroupMemberDraft(); await autoExec.handleGroupMemberDraft();

View File

@ -4679,7 +4679,8 @@ function runProxyCallback(_, value) {
return foundName; return foundName;
} }
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ export function initOpenai() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'proxy', name: 'proxy',
callback: runProxyCallback, callback: runProxyCallback,
returns: 'current proxy', returns: 'current proxy',
@ -4693,8 +4694,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
}), }),
], ],
helpString: 'Sets a proxy preset by name.', helpString: 'Sets a proxy preset by name.',
})); }));
}
$(document).ready(async function () { $(document).ready(async function () {
$('#test_api_button').on('click', testApiConnection); $('#test_api_button').on('click', testApiConnection);

View File

@ -194,7 +194,7 @@ export class Popup {
const buttonElement = document.createElement('div'); const buttonElement = document.createElement('div');
buttonElement.classList.add('menu_button', 'popup-button-custom', 'result-control'); buttonElement.classList.add('menu_button', 'popup-button-custom', 'result-control');
buttonElement.classList.add(...(button.classes ?? [])); buttonElement.classList.add(...(button.classes ?? []));
buttonElement.dataset.result = String(button.result ?? undefined); buttonElement.dataset.result = String(button.result); // This is expected to also write 'null' or 'staging', to indicate cancel and no action respectively
buttonElement.textContent = button.text; buttonElement.textContent = button.text;
buttonElement.dataset.i18n = buttonElement.textContent; buttonElement.dataset.i18n = buttonElement.textContent;
buttonElement.tabIndex = 0; buttonElement.tabIndex = 0;
@ -317,9 +317,14 @@ export class Popup {
// Bind event listeners for all result controls to their defined event type // Bind event listeners for all result controls to their defined event type
this.dlg.querySelectorAll('[data-result]').forEach(resultControl => { this.dlg.querySelectorAll('[data-result]').forEach(resultControl => {
if (!(resultControl instanceof HTMLElement)) return; if (!(resultControl instanceof HTMLElement)) return;
const result = Number(resultControl.dataset.result); // If no value was set, we exit out and don't bind an action
if (String(undefined) === String(resultControl.dataset.result)) return; if (String(resultControl.dataset.result) === String(undefined)) return;
if (isNaN(result)) throw new Error('Invalid result control. Result must be a number. ' + resultControl.dataset.result);
// Make sure that both `POPUP_RESULT` numbers and also `null` as 'cancelled' are supported
const result = String(resultControl.dataset.result) === String(null) ? null
: Number(resultControl.dataset.result);
if (result !== null && isNaN(result)) throw new Error('Invalid result control. Result must be a number. ' + resultControl.dataset.result);
const type = resultControl.dataset.resultEvent || 'click'; const type = resultControl.dataset.resultEvent || 'click';
resultControl.addEventListener(type, async () => await this.complete(result)); resultControl.addEventListener(type, async () => await this.complete(result));
}); });

View File

@ -2734,45 +2734,26 @@ async function doDelMode(_, text) {
return ''; return '';
} }
//first enter delmode // Just enter the delete mode.
if (!text) {
$('#option_delete_mes').trigger('click', { fromSlashCommand: true }); $('#option_delete_mes').trigger('click', { fromSlashCommand: true });
return '';
}
//parse valid args const count = Number(text);
if (text) {
await delay(300); //same as above, need event signal for 'entered del mode'
console.debug('parsing msgs to del');
let numMesToDel = Number(text);
let lastMesID = Number($('#chat .mes').last().attr('mesid'));
let oldestMesIDToDel = lastMesID - numMesToDel + 1;
if (oldestMesIDToDel < 0) { // Nothing to delete.
if (count < 1) {
return '';
}
if (count > chat.length) {
toastr.warning(`Cannot delete more than ${chat.length} messages.`); toastr.warning(`Cannot delete more than ${chat.length} messages.`);
return ''; return '';
} }
let oldestMesToDel = $('#chat').find(`.mes[mesid=${oldestMesIDToDel}]`); const range = `${chat.length - count}-${chat.length - 1}`;
return doMesCut(_, range);
if (!oldestMesIDToDel && lastMesID > 0) {
oldestMesToDel = await loadUntilMesId(oldestMesIDToDel);
if (!oldestMesToDel || !oldestMesToDel.length) {
return '';
}
}
let oldestDelMesCheckbox = $(oldestMesToDel).find('.del_checkbox');
let newLastMesID = oldestMesIDToDel - 1;
console.debug(`DelMesReport -- numMesToDel: ${numMesToDel}, lastMesID: ${lastMesID}, oldestMesIDToDel:${oldestMesIDToDel}, newLastMesID: ${newLastMesID}`);
oldestDelMesCheckbox.trigger('click');
let trueNumberOfDeletedMessage = lastMesID - oldestMesIDToDel + 1;
//await delay(1)
$('#dialogue_del_mes_ok').trigger('click');
toastr.success(`Deleted ${trueNumberOfDeletedMessage} messages.`);
return '';
}
return '';
} }
function doResetPanels() { function doResetPanels() {

View File

@ -1,6 +1,5 @@
import { import {
amount_gen, amount_gen,
callPopup,
characters, characters,
eventSource, eventSource,
event_types, event_types,
@ -19,6 +18,7 @@ import {
import { groups, selected_group } from './group-chats.js'; import { groups, selected_group } from './group-chats.js';
import { instruct_presets } from './instruct-mode.js'; import { instruct_presets } from './instruct-mode.js';
import { kai_settings } from './kai-settings.js'; import { kai_settings } from './kai-settings.js';
import { Popup } from './popup.js';
import { context_presets, getContextSettings, power_user } from './power-user.js'; import { context_presets, getContextSettings, power_user } from './power-user.js';
import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
@ -165,11 +165,8 @@ class PresetManager {
async savePresetAs() { async savePresetAs() {
const inputValue = this.getSelectedPresetName(); const inputValue = this.getSelectedPresetName();
const popupText = ` const popupText = !this.isNonGenericApi() ? '<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>' : '';
<h3>Preset name:</h3> const name = await Popup.show.input('Preset name:', popupText, inputValue);
${!this.isNonGenericApi() ? '<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>' : ''}`;
const name = await callPopup(popupText, 'input', inputValue);
if (!name) { if (!name) {
console.log('Preset name not provided'); console.log('Preset name not provided');
return; return;
@ -372,7 +369,7 @@ class PresetManager {
if (Object.keys(preset_names).length) { if (Object.keys(preset_names).length) {
const nextPresetName = Object.keys(preset_names)[0]; const nextPresetName = Object.keys(preset_names)[0];
const newValue = preset_names[nextPresetName]; const newValue = preset_names[nextPresetName];
$(this.select).find(`option[value="${newValue}"]`).attr('selected', true); $(this.select).find(`option[value="${newValue}"]`).attr('selected', 'true');
$(this.select).trigger('change'); $(this.select).trigger('change');
} }
@ -597,8 +594,7 @@ export async function initPresetManager() {
return; return;
} }
const confirm = await callPopup('Delete the preset? This action is irreversible and your current settings will be overwritten.', 'confirm'); const confirm = await Popup.show.confirm('Delete the preset?', 'This action is irreversible and your current settings will be overwritten.');
if (!confirm) { if (!confirm) {
return; return;
} }
@ -641,8 +637,7 @@ export async function initPresetManager() {
return; return;
} }
const confirm = await callPopup('<h3>Are you sure?</h3>Resetting a <b>default preset</b> will restore the default settings.', 'confirm'); const confirm = await Popup.show.confirm('Are you sure?', 'Resetting a <b>default preset</b> will restore the default settings.');
if (!confirm) { if (!confirm) {
return; return;
} }
@ -653,8 +648,7 @@ export async function initPresetManager() {
presetManager.selectPreset(option); presetManager.selectPreset(option);
toastr.success('Default preset restored'); toastr.success('Default preset restored');
} else { } else {
const confirm = await callPopup('<h3>Are you sure?</h3>Resetting a <b>custom preset</b> will restore to the last saved state.', 'confirm'); const confirm = await Popup.show.confirm('Are you sure?', 'Resetting a <b>custom preset</b> will restore to the last saved state.');
if (!confirm) { if (!confirm) {
return; return;
} }

View File

@ -955,14 +955,36 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'fuzzy', name: 'fuzzy',
callback: fuzzyCallback, callback: fuzzyCallback,
returns: 'first matching item', returns: 'matching item',
namedArgumentList: [ namedArgumentList: [
new SlashCommandNamedArgument( SlashCommandNamedArgument.fromProps({
'list', 'list of items to match against', [ARGUMENT_TYPE.LIST], true, name: 'list',
), description: 'list of items to match against',
new SlashCommandNamedArgument( acceptsMultiple: false,
'threshold', 'fuzzy match threshold (0.0 to 1.0)', [ARGUMENT_TYPE.NUMBER], false, false, '0.4', isRequired: true,
), typeList: [ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.VARIABLE_NAME],
enumProvider: commonEnumProviders.variables('all'),
}),
SlashCommandNamedArgument.fromProps({
name: 'threshold',
description: 'fuzzy match threshold (0.0 to 1.0)',
typeList: [ARGUMENT_TYPE.NUMBER],
isRequired: false,
defaultValue: '0.4',
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'mode',
description: 'fuzzy match mode',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: false,
defaultValue: 'first',
acceptsMultiple: false,
enumList: [
new SlashCommandEnumValue('first', 'first match below the threshold', enumTypes.enum, enumIcons.default),
new SlashCommandEnumValue('best', 'best match below the threshold', enumTypes.enum, enumIcons.default),
],
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -979,6 +1001,13 @@ export function initDefaultSlashCommands() {
A low value (min 0.0) means the match is very strict. A low value (min 0.0) means the match is very strict.
At 1.0 (max) the match is very loose and will match anything. At 1.0 (max) the match is very loose and will match anything.
</div> </div>
<div>
The optional <code>mode</code> argument allows to control the behavior when multiple items match the text.
<ul>
<li><code>first</code> (default) returns the first match below the threshold.</li>
<li><code>best</code> returns the best match below the threshold.</li>
</ul>
</div>
<div> <div>
The returned value passes to the next command through the pipe. The returned value passes to the next command through the pipe.
</div> </div>
@ -1882,7 +1911,7 @@ async function inputCallback(args, prompt) {
* @param {FuzzyCommandArgs} args - arguments containing "list" (JSON array) and optionaly "threshold" (float between 0.0 and 1.0) * @param {FuzzyCommandArgs} args - arguments containing "list" (JSON array) and optionaly "threshold" (float between 0.0 and 1.0)
* @param {string} searchInValue - the string where items of list are searched * @param {string} searchInValue - the string where items of list are searched
* @returns {string} - the matched item from the list * @returns {string} - the matched item from the list
* @typedef {{list: string, threshold: string}} FuzzyCommandArgs - arguments for /fuzzy command * @typedef {{list: string, threshold: string, mode:string}} FuzzyCommandArgs - arguments for /fuzzy command
* @example /fuzzy list=["down","left","up","right"] "he looks up" | /echo // should return "up" * @example /fuzzy list=["down","left","up","right"] "he looks up" | /echo // should return "up"
* @link https://www.fusejs.io/ * @link https://www.fusejs.io/
*/ */
@ -1912,7 +1941,7 @@ function fuzzyCallback(args, searchInValue) {
}; };
// threshold determines how strict is the match, low threshold value is very strict, at 1 (nearly?) everything matches // threshold determines how strict is the match, low threshold value is very strict, at 1 (nearly?) everything matches
if ('threshold' in args) { if ('threshold' in args) {
params.threshold = parseFloat(resolveVariable(args.threshold)); params.threshold = parseFloat(args.threshold);
if (isNaN(params.threshold)) { if (isNaN(params.threshold)) {
console.warn('WARN: \'threshold\' argument must be a float between 0.0 and 1.0 for /fuzzy command'); console.warn('WARN: \'threshold\' argument must be a float between 0.0 and 1.0 for /fuzzy command');
return ''; return '';
@ -1925,16 +1954,42 @@ function fuzzyCallback(args, searchInValue) {
} }
} }
function getFirstMatch() {
const fuse = new Fuse([searchInValue], params); const fuse = new Fuse([searchInValue], params);
// each item in the "list" is searched within "search_item", if any matches it returns the matched "item" // each item in the "list" is searched within "search_item", if any matches it returns the matched "item"
for (const searchItem of list) { for (const searchItem of list) {
const result = fuse.search(searchItem); const result = fuse.search(searchItem);
console.debug('/fuzzy: result', result);
if (result.length > 0) { if (result.length > 0) {
console.info('fuzzyCallback Matched: ' + searchItem); console.info('/fuzzy: first matched', searchItem);
return searchItem; return searchItem;
} }
} }
console.info('/fuzzy: no match');
return ''; return '';
}
function getBestMatch() {
const fuse = new Fuse(list, params);
const result = fuse.search(searchInValue);
console.debug('/fuzzy: result', result);
if (result.length > 0) {
console.info('/fuzzy: best matched', result[0].item);
return result[0].item;
}
console.info('/fuzzy: no match');
return '';
}
switch (String(args.mode).trim().toLowerCase()) {
case 'best':
return getBestMatch();
case 'first':
default:
return getFirstMatch();
}
} catch { } catch {
console.warn('WARN: Invalid list argument provided for /fuzzy command'); console.warn('WARN: Invalid list argument provided for /fuzzy command');
return ''; return '';

View File

@ -1,11 +1,11 @@
import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles, extension_prompt_types } from "../../script.js"; import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles, extension_prompt_types } from '../../script.js';
import { extension_settings } from "../extensions.js"; import { extension_settings } from '../extensions.js';
import { getGroupMembers, groups, selected_group } from "../group-chats.js"; import { getGroupMembers, groups } from '../group-chats.js';
import { power_user } from "../power-user.js"; import { power_user } from '../power-user.js';
import { searchCharByName, getTagsList, tags } from "../tags.js"; import { searchCharByName, getTagsList, tags } from '../tags.js';
import { SlashCommandClosure } from "./SlashCommandClosure.js"; import { world_names } from '../world-info.js';
import { SlashCommandEnumValue, enumTypes } from "./SlashCommandEnumValue.js"; import { SlashCommandClosure } from './SlashCommandClosure.js';
import { SlashCommandExecutor } from "./SlashCommandExecutor.js"; import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js';
import { SlashCommandScope } from "./SlashCommandScope.js"; import { SlashCommandScope } from "./SlashCommandScope.js";
/** /**
@ -104,8 +104,8 @@ export const enumIcons = {
// Remove possible nullable types definition to match type icon // Remove possible nullable types definition to match type icon
type = type.replace(/\?$/, ''); type = type.replace(/\?$/, '');
return enumIcons[type] ?? enumIcons.default; return enumIcons[type] ?? enumIcons.default;
} },
} };
/** /**
* A collection of common enum providers * A collection of common enum providers
@ -181,7 +181,7 @@ export const commonEnumProviders = {
* @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show
* @returns {() => SlashCommandEnumValue[]} * @returns {() => SlashCommandEnumValue[]}
*/ */
tagsForChar: (mode = 'all') => (/** @type {SlashCommandExecutor} */ executor) => { tagsForChar: (mode = 'all') => (/** @type {import('./SlashCommandExecutor.js').SlashCommandExecutor} */ executor) => {
// Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags. // Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags.
const charName = executor.namedArgumentList.find(it => it.name == 'name')?.value; const charName = executor.namedArgumentList.find(it => it.name == 'name')?.value;
if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures'); if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');
@ -214,7 +214,7 @@ export const commonEnumProviders = {
* *
* @returns {SlashCommandEnumValue[]} * @returns {SlashCommandEnumValue[]}
*/ */
worlds: () => $('#world_info').children().toArray().map(x => new SlashCommandEnumValue(x.textContent, null, enumTypes.name, enumIcons.world)), worlds: () => world_names.map(worldName => new SlashCommandEnumValue(worldName, null, enumTypes.name, enumIcons.world)),
/** /**
* All existing injects for the current chat * All existing injects for the current chat

View File

@ -14,7 +14,6 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
@ -1215,7 +1214,7 @@ function registerWorldInfoSlashCommands() {
enumTypes.enum, enumIcons.getDataTypeIcon(value.type))), enumTypes.enum, enumIcons.getDataTypeIcon(value.type))),
/** All existing UIDs based on the file argument as world name */ /** All existing UIDs based on the file argument as world name */
wiUids: (/** @type {SlashCommandExecutor} */ executor) => { wiUids: (/** @type {import('./slash-commands/SlashCommandExecutor.js').SlashCommandExecutor} */ executor) => {
const file = executor.namedArgumentList.find(it => it.name == 'file')?.value; const file = executor.namedArgumentList.find(it => it.name == 'file')?.value;
if (file instanceof SlashCommandClosure) throw new Error('Argument \'file\' does not support closures'); if (file instanceof SlashCommandClosure) throw new Error('Argument \'file\' does not support closures');
// Try find world from cache // Try find world from cache
@ -3161,7 +3160,8 @@ function duplicateWorldInfoEntry(data, uid) {
} }
// Exclude uid and gather the rest of the properties // Exclude uid and gather the rest of the properties
const { uid: _, ...originalData } = data.entries[uid]; const originalData = Object.assign({}, data.entries[uid]);
delete originalData.uid;
// Create new entry and copy over data // Create new entry and copy over data
const entry = createWorldInfoEntry(data.name, data); const entry = createWorldInfoEntry(data.name, data);
@ -4326,8 +4326,9 @@ function onWorldInfoChange(args, text) {
$('#world_info').val(null).trigger('change'); $('#world_info').val(null).trigger('change');
} }
} else { //if it's a pointer selection } else { //if it's a pointer selection
let tempWorldInfo = []; const tempWorldInfo = [];
let selectedWorlds = $('#world_info').val().map((e) => Number(e)).filter((e) => !isNaN(e)); const val = $('#world_info').val();
const selectedWorlds = (Array.isArray(val) ? val : [val]).map((e) => Number(e)).filter((e) => !isNaN(e));
if (selectedWorlds.length > 0) { if (selectedWorlds.length > 0) {
selectedWorlds.forEach((worldIndex) => { selectedWorlds.forEach((worldIndex) => {
const existingWorldName = world_names[worldIndex]; const existingWorldName = world_names[worldIndex];