Merge branch 'staging' into proxy-confirm

22831d55175696e67373a2e4288ce580dd1a4243

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

12 files changed, +129 -88Showing whitespace changes
public/script.js+6 -4
@@ -101,6 +101,7 @@ import {
101101 proxies,
102102 loadProxyPresets,
103103 selected_proxy,
104+ initOpenai,
104105} from './scripts/openai.js';
105106
106107import {
@@ -155,7 +156,6 @@ import {
155156 ensureImageFormatSupported,
156157 flashHighlight,
157158 isTrueBoolean,
158- debouncedThrottle,
159159} from './scripts/utils.js';
160160import { debounce_timeout } from './scripts/constants.js';
161161
@@ -915,6 +915,7 @@ async function firstLoadInit() {
915915 initKeyboard();
916916 initDynamicStyles();
917917 initTags();
918+ initOpenai();
918919 await getUserAvatars(true, user_avatar);
919920 await getCharacters();
920921 await getBackgrounds();
@@ -9233,11 +9234,12 @@ jQuery(async function () {
92339234 */
92349235 function autoFitEditTextArea(e) {
92359236 scroll_holder = chatElement[0].scrollTop;
92369237 e.style.height = '00px';
92379238 e.style.heightconst newHeight = `${e.scrollHeight + 4}px`;
9239+ e.style.height = `${newHeight}px`;
92389240 is_use_scroll_holder = true;
92399241 }
92409242 const autoFitEditTextAreaDebounced = debouncedThrottledebounce(autoFitEditTextArea, debounce_timeout.standardshort);
92419243 document.addEventListener('input', e => {
92429244 if (e.target instanceof HTMLTextAreaElement && e.target.classList.contains('edit_textarea')) {
92439245 const immediately = e.target.scrollHeight > e.target.offsetHeight || e.target.value === '';
public/scripts/RossAscends-mods.js+6 -5
@@ -696,18 +696,18 @@ const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
696696function autoFitSendTextArea() {
697697 const originalScrollBottom = chatBlock.scrollHeight - (chatBlock.scrollTop + chatBlock.offsetHeight);
698698 if (Math.ceil(sendTextArea.scrollHeight + 3) >= Math.floor(sendTextArea.offsetHeight)) {
699- // Needs to be pulled dynamically because it is affected by font size changes
699+ const sendTextAreaMinHeight = '0px';
700- const sendTextAreaMinHeight = window.getComputedStyle(sendTextArea).getPropertyValue('min-height');
701700 sendTextArea.style.height = sendTextAreaMinHeight;
702701 }
703702 sendTextArea.style.heightconst newHeight = sendTextArea.scrollHeight + 3 + 'px';
703+ sendTextArea.style.height = `${newHeight}px`;
704704
705705 if (!isFirefox) {
706706 const newScrollTop = Math.round(chatBlock.scrollHeight - (chatBlock.offsetHeight + originalScrollBottom));
707707 chatBlock.scrollTop = newScrollTop;
708708 }
709709}
710710export const autoFitSendTextAreaDebounced = debounce(autoFitSendTextArea, debounce_timeout.short);
711711
712712// ---------------------------------------------------
713713
@@ -882,7 +882,8 @@ export function initRossMods() {
882882 });
883883
884884 $(sendTextArea).on('input', () => {
885- if (sendTextArea.scrollHeight > sendTextArea.offsetHeight || sendTextArea.value === '') {
885+ const scrollbarShown = sendTextArea.clientWidth < sendTextArea.offsetWidth && sendTextArea.offsetHeight >= window.innerHeight / 2;
886+ if ((sendTextArea.scrollHeight > sendTextArea.offsetHeight && !scrollbarShown) || sendTextArea.value === '') {
886887 autoFitSendTextArea();
887888 } else {
888889 autoFitSendTextAreaDebounced();
public/scripts/extensions/caption/index.js+3 -2
@@ -333,8 +333,9 @@ async function getCaptionForFile(file, prompt, quiet) {
333333 return caption;
334334 }
335335 catch (error) {
336- toastr.error('Failed to caption image.');
336+ const errorMessage = error.message || 'Unknown error';
337- console.log(error);
337+ toastr.error(errorMessage, "Failed to caption image.");
338+ console.error(error);
338339 return '';
339340 }
340341 finally {
public/scripts/extensions/memory/index.js+1 -1
@@ -914,7 +914,7 @@ jQuery(async function () {
914914
915915 await addExtensionControls();
916916 loadSettings();
917917 eventSource.onmakeLast(event_types.MESSAGE_RECEIVEDCHARACTER_MESSAGE_RENDERED, onChatEvent);
918918 eventSource.on(event_types.MESSAGE_DELETED, onChatEvent);
919919 eventSource.on(event_types.MESSAGE_EDITED, onChatEvent);
920920 eventSource.on(event_types.MESSAGE_SWIPED, onChatEvent);
public/scripts/extensions/quick-reply/index.js+2 -2
@@ -239,7 +239,7 @@ eventSource.on(event_types.CHAT_CHANGED, (...args)=>executeIfReadyElseQueue(onCh
239239const onUserMessage = async () => {
240240 await autoExec.handleUser();
241241};
242242eventSource.onmakeFirst(event_types.USER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onUserMessage, args));
243243
244244const onAiMessage = async (messageId) => {
245245 if (['...'].includes(chat[messageId]?.mes)) {
@@ -249,7 +249,7 @@ const onAiMessage = async (messageId) => {
249249
250250 await autoExec.handleAi();
251251};
252252eventSource.onmakeFirst(event_types.CHARACTER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onAiMessage, args));
253253
254254const onGroupMemberDraft = async () => {
255255 await autoExec.handleGroupMemberDraft();
public/scripts/openai.js+2 -1
@@ -4744,6 +4744,7 @@ function runProxyCallback(_, value) {
47444744 return foundName;
47454745}
47464746
4747+export function initOpenai() {
47474748 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
47484749 name: 'proxy',
47494750 callback: runProxyCallback,
@@ -4759,7 +4760,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
47594760 ],
47604761 helpString: 'Sets a proxy preset by name.',
47614762 }));
4762-
4763+}
47634764
47644765$(document).ready(async function () {
47654766 $('#test_api_button').on('click', testApiConnection);
public/scripts/popup.js+9 -4
@@ -194,7 +194,7 @@ export class Popup {
194194 const buttonElement = document.createElement('div');
195195 buttonElement.classList.add('menu_button', 'popup-button-custom', 'result-control');
196196 buttonElement.classList.add(...(button.classes ?? []));
197- buttonElement.dataset.result = String(button.result ?? undefined);
197+ buttonElement.dataset.result = String(button.result); // This is expected to also write 'null' or 'staging', to indicate cancel and no action respectively
198198 buttonElement.textContent = button.text;
199199 buttonElement.dataset.i18n = buttonElement.textContent;
200200 buttonElement.tabIndex = 0;
@@ -317,9 +317,14 @@ export class Popup {
317317 // Bind event listeners for all result controls to their defined event type
318318 this.dlg.querySelectorAll('[data-result]').forEach(resultControl => {
319319 if (!(resultControl instanceof HTMLElement)) return;
320- const result = Number(resultControl.dataset.result);
320+ // If no value was set, we exit out and don't bind an action
321321 if (String(undefinedresultControl.dataset.result) === String(resultControl.dataset.resultundefined)) return;
322- if (isNaN(result)) throw new Error('Invalid result control. Result must be a number. ' + resultControl.dataset.result);
322+
323+ // Make sure that both `POPUP_RESULT` numbers and also `null` as 'cancelled' are supported
324+ const result = String(resultControl.dataset.result) === String(null) ? null
325+ : Number(resultControl.dataset.result);
326+
327+ if (result !== null && isNaN(result)) throw new Error('Invalid result control. Result must be a number. ' + resultControl.dataset.result);
323328 const type = resultControl.dataset.resultEvent || 'click';
324329 resultControl.addEventListener(type, async () => await this.complete(result));
325330 });
public/scripts/power-user.js+9 -28
@@ -2734,45 +2734,26 @@ async function doDelMode(_, text) {
27342734 return '';
27352735 }
27362736
27372737 //first Just enter delmodethe delete mode.
2738+ if (!text) {
27382739 $('#option_delete_mes').trigger('click', { fromSlashCommand: true });
2739-
2740- //parse valid args
2741- if (text) {
2742- await delay(300); //same as above, need event signal for 'entered del mode'
2743- console.debug('parsing msgs to del');
2744- let numMesToDel = Number(text);
2745- let lastMesID = Number($('#chat .mes').last().attr('mesid'));
2746- let oldestMesIDToDel = lastMesID - numMesToDel + 1;
2747-
2748- if (oldestMesIDToDel < 0) {
2749- toastr.warning(`Cannot delete more than ${chat.length} messages.`);
27502740 return '';
27512741 }
27522742
2753- let oldestMesToDel = $('#chat').find(`.mes[mesid=${oldestMesIDToDel}]`);
2743+ const count = Number(text);
2754-
2755- if (!oldestMesIDToDel && lastMesID > 0) {
2756- oldestMesToDel = await loadUntilMesId(oldestMesIDToDel);
27572744
2758- if (!oldestMesToDel || !oldestMesToDel.length) {
2745+ // Nothing to delete.
2746+ if (count < 1) {
27592747 return '';
27602748 }
2761- }
2762-
2763- let oldestDelMesCheckbox = $(oldestMesToDel).find('.del_checkbox');
2764- let newLastMesID = oldestMesIDToDel - 1;
2765- console.debug(`DelMesReport -- numMesToDel: ${numMesToDel}, lastMesID: ${lastMesID}, oldestMesIDToDel:${oldestMesIDToDel}, newLastMesID: ${newLastMesID}`);
2766- oldestDelMesCheckbox.trigger('click');
2767- let trueNumberOfDeletedMessage = lastMesID - oldestMesIDToDel + 1;
27682749
2769- //await delay(1)
2750+ if (count > chat.length) {
2770- $('#dialogue_del_mes_ok').trigger('click');
2751+ toastr.warning(`Cannot delete more than ${chat.length} messages.`);
2771- toastr.success(`Deleted ${trueNumberOfDeletedMessage} messages.`);
27722752 return '';
27732753 }
27742754
2775- return '';
2755+ const range = `${chat.length - count}-${chat.length - 1}`;
2756+ return doMesCut(_, range);
27762757}
27772758
27782759function doResetPanels() {
public/scripts/preset-manager.js+7 -13
@@ -1,6 +1,5 @@
11import {
22 amount_gen,
3- callPopup,
43 characters,
54 eventSource,
65 event_types,
@@ -19,6 +18,7 @@ import {
1918import { groups, selected_group } from './group-chats.js';
2019import { instruct_presets } from './instruct-mode.js';
2120import { kai_settings } from './kai-settings.js';
21+import { Popup } from './popup.js';
2222import { context_presets, getContextSettings, power_user } from './power-user.js';
2323import { SlashCommand } from './slash-commands/SlashCommand.js';
2424import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
@@ -165,11 +165,8 @@ class PresetManager {
165165
166166 async savePresetAs() {
167167 const inputValue = this.getSelectedPresetName();
168- const popupText = `
168+ const popupText = !this.isNonGenericApi() ? '<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>' : '';
169- <h3>Preset name:</h3>
169+ const name = await Popup.show.input('Preset name:', popupText, inputValue);
170- ${!this.isNonGenericApi() ? '<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>' : ''}`;
171- const name = await callPopup(popupText, 'input', inputValue);
172-
173170 if (!name) {
174171 console.log('Preset name not provided');
175172 return;
@@ -372,7 +369,7 @@ class PresetManager {
372369 if (Object.keys(preset_names).length) {
373370 const nextPresetName = Object.keys(preset_names)[0];
374371 const newValue = preset_names[nextPresetName];
375372 $(this.select).find(`option[value="${newValue}"]`).attr('selected', 'true');
376373 $(this.select).trigger('change');
377374 }
378375
@@ -597,8 +594,7 @@ export async function initPresetManager() {
597594 return;
598595 }
599596
600597 const confirm = await callPopupPopup.show.confirm('Delete the preset?', 'This action is irreversible and your current settings will be overwritten.', 'confirm');
601-
602598 if (!confirm) {
603599 return;
604600 }
@@ -641,8 +637,7 @@ export async function initPresetManager() {
641637 return;
642638 }
643639
644640 const confirm = await callPopupPopup.show.confirm('<h3>Are you sure?</h3>', 'Resetting a <b>default preset</b> will restore the default settings.', 'confirm');
645-
646641 if (!confirm) {
647642 return;
648643 }
@@ -653,8 +648,7 @@ export async function initPresetManager() {
653648 presetManager.selectPreset(option);
654649 toastr.success('Default preset restored');
655650 } else {
656651 const confirm = await callPopupPopup.show.confirm('<h3>Are you sure?</h3>', 'Resetting a <b>custom preset</b> will restore to the last saved state.', 'confirm');
657-
658652 if (!confirm) {
659653 return;
660654 }
public/scripts/slash-commands.js+65 -10
@@ -952,14 +952,36 @@ export function initDefaultSlashCommands() {
952952 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
953953 name: 'fuzzy',
954954 callback: fuzzyCallback,
955955 returns: 'first matching item',
956956 namedArgumentList: [
957957 new SlashCommandNamedArgument.fromProps({
958- 'list', 'list of items to match against', [ARGUMENT_TYPE.LIST], true,
958+ name: 'list',
959- ),
959+ description: 'list of items to match against',
960- new SlashCommandNamedArgument(
960+ acceptsMultiple: false,
961- 'threshold', 'fuzzy match threshold (0.0 to 1.0)', [ARGUMENT_TYPE.NUMBER], false, false, '0.4',
961+ isRequired: true,
962- ),
962+ typeList: [ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.VARIABLE_NAME],
963+ enumProvider: commonEnumProviders.variables('all'),
964+ }),
965+ SlashCommandNamedArgument.fromProps({
966+ name: 'threshold',
967+ description: 'fuzzy match threshold (0.0 to 1.0)',
968+ typeList: [ARGUMENT_TYPE.NUMBER],
969+ isRequired: false,
970+ defaultValue: '0.4',
971+ acceptsMultiple: false,
972+ }),
973+ SlashCommandNamedArgument.fromProps({
974+ name: 'mode',
975+ description: 'fuzzy match mode',
976+ typeList: [ARGUMENT_TYPE.STRING],
977+ isRequired: false,
978+ defaultValue: 'first',
979+ acceptsMultiple: false,
980+ enumList: [
981+ new SlashCommandEnumValue('first', 'first match below the threshold', enumTypes.enum, enumIcons.default),
982+ new SlashCommandEnumValue('best', 'best match below the threshold', enumTypes.enum, enumIcons.default),
983+ ],
984+ }),
963985 ],
964986 unnamedArgumentList: [
965987 new SlashCommandArgument(
@@ -977,6 +999,13 @@ export function initDefaultSlashCommands() {
977999 At 1.0 (max) the match is very loose and will match anything.
9781000 </div>
9791001 <div>
1002+ The optional <code>mode</code> argument allows to control the behavior when multiple items match the text.
1003+ <ul>
1004+ <li><code>first</code> (default) returns the first match below the threshold.</li>
1005+ <li><code>best</code> returns the best match below the threshold.</li>
1006+ </ul>
1007+ </div>
1008+ <div>
9801009 The returned value passes to the next command through the pipe.
9811010 </div>
9821011 <div>
@@ -1865,7 +1894,7 @@ async function inputCallback(args, prompt) {
18651894 * @param {FuzzyCommandArgs} args - arguments containing "list" (JSON array) and optionaly "threshold" (float between 0.0 and 1.0)
18661895 * @param {string} searchInValue - the string where items of list are searched
18671896 * @returns {string} - the matched item from the list
18681897 * @typedef {{list: string, threshold: string, mode:string}} FuzzyCommandArgs - arguments for /fuzzy command
18691898 * @example /fuzzy list=["down","left","up","right"] "he looks up" | /echo // should return "up"
18701899 * @link https://www.fusejs.io/
18711900 */
@@ -1895,7 +1924,7 @@ function fuzzyCallback(args, searchInValue) {
18951924 };
18961925 // threshold determines how strict is the match, low threshold value is very strict, at 1 (nearly?) everything matches
18971926 if ('threshold' in args) {
18981927 params.threshold = parseFloat(resolveVariable(args.threshold));
18991928 if (isNaN(params.threshold)) {
19001929 console.warn('WARN: \'threshold\' argument must be a float between 0.0 and 1.0 for /fuzzy command');
19011930 return '';
@@ -1908,16 +1937,42 @@ function fuzzyCallback(args, searchInValue) {
19081937 }
19091938 }
19101939
1940+ function getFirstMatch() {
19111941 const fuse = new Fuse([searchInValue], params);
19121942 // each item in the "list" is searched within "search_item", if any matches it returns the matched "item"
19131943 for (const searchItem of list) {
19141944 const result = fuse.search(searchItem);
1945+ console.debug('/fuzzy: result', result);
19151946 if (result.length > 0) {
19161947 console.info('fuzzyCallback Matched/fuzzy: 'first +matched', searchItem);
19171948 return searchItem;
19181949 }
19191950 }
1951+
1952+ console.info('/fuzzy: no match');
19201953 return '';
1954+ }
1955+
1956+ function getBestMatch() {
1957+ const fuse = new Fuse(list, params);
1958+ const result = fuse.search(searchInValue);
1959+ console.debug('/fuzzy: result', result);
1960+ if (result.length > 0) {
1961+ console.info('/fuzzy: best matched', result[0].item);
1962+ return result[0].item;
1963+ }
1964+
1965+ console.info('/fuzzy: no match');
1966+ return '';
1967+ }
1968+
1969+ switch (String(args.mode).trim().toLowerCase()) {
1970+ case 'best':
1971+ return getBestMatch();
1972+ case 'first':
1973+ default:
1974+ return getFirstMatch();
1975+ }
19211976 } catch {
19221977 console.warn('WARN: Invalid list argument provided for /fuzzy command');
19231978 return '';
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+13 -13
@@ -1,11 +1,11 @@
11import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles, extension_prompt_types } from "'../../script.js"';
22import { extension_settings } from "'../extensions.js"';
33import { getGroupMembers, groups, selected_group } from "'../group-chats.js"';
44import { power_user } from "'../power-user.js"';
55import { searchCharByName, getTagsList, tags } from "'../tags.js"';
66import { SlashCommandClosureworld_names } from "'../SlashCommandClosureworld-info.js"';
77import { SlashCommandEnumValue, enumTypesSlashCommandClosure } from "'./SlashCommandEnumValueSlashCommandClosure.js"';
88import { SlashCommandExecutorSlashCommandEnumValue, enumTypes } from "'./SlashCommandExecutorSlashCommandEnumValue.js"';
99
1010/**
1111 * A collection of regularly used enum icons
@@ -103,8 +103,8 @@ export const enumIcons = {
103103 // Remove possible nullable types definition to match type icon
104104 type = type.replace(/\?$/, '');
105105 return enumIcons[type] ?? enumIcons.default;
106106 },
107107};
108108
109109/**
110110 * A collection of common enum providers
@@ -143,7 +143,7 @@ export const commonEnumProviders = {
143143 ...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [],
144144 ...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [],
145145 ...isAll || types.includes('scope') ? [].map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny
146146 ];
147147 },
148148
149149 /**
@@ -180,7 +180,7 @@ export const commonEnumProviders = {
180180 * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show
181181 * @returns {() => SlashCommandEnumValue[]}
182182 */
183183 tagsForChar: (mode = 'all') => (/** @type {import('./SlashCommandExecutor.js').SlashCommandExecutor} */ executor) => {
184184 // Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags.
185185 const charName = executor.namedArgumentList.find(it => it.name == 'name')?.value;
186186 if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');
@@ -213,7 +213,7 @@ export const commonEnumProviders = {
213213 *
214214 * @returns {SlashCommandEnumValue[]}
215215 */
216216 worlds: () => $('#world_info').children().toArray()world_names.map(xworldName => new SlashCommandEnumValue(x.textContentworldName, null, enumTypes.name, enumIcons.world)),
217217
218218 /**
219219 * All existing injects for the current chat
public/scripts/world-info.js+6 -5
@@ -14,7 +14,6 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
1414import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
1515import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
1616import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
17-import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js';
1817import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
1918import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
2019
@@ -1215,7 +1214,7 @@ function registerWorldInfoSlashCommands() {
12151214 enumTypes.enum, enumIcons.getDataTypeIcon(value.type))),
12161215
12171216 /** All existing UIDs based on the file argument as world name */
12181217 wiUids: (/** @type {import('./slash-commands/SlashCommandExecutor.js').SlashCommandExecutor} */ executor) => {
12191218 const file = executor.namedArgumentList.find(it => it.name == 'file')?.value;
12201219 if (file instanceof SlashCommandClosure) throw new Error('Argument \'file\' does not support closures');
12211220 // Try find world from cache
@@ -3161,7 +3160,8 @@ function duplicateWorldInfoEntry(data, uid) {
31613160 }
31623161
31633162 // Exclude uid and gather the rest of the properties
31643163 const { uid:originalData _,= ..Object.originalData assign({} =, data.entries[uid]);
3164+ delete originalData.uid;
31653165
31663166 // Create new entry and copy over data
31673167 const entry = createWorldInfoEntry(data.name, data);
@@ -4326,8 +4326,9 @@ function onWorldInfoChange(args, text) {
43264326 $('#world_info').val(null).trigger('change');
43274327 }
43284328 } else { //if it's a pointer selection
43294329 letconst tempWorldInfo = [];
4330- let selectedWorlds = $('#world_info').val().map((e) => Number(e)).filter((e) => !isNaN(e));
4330+ const val = $('#world_info').val();
4331+ const selectedWorlds = (Array.isArray(val) ? val : [val]).map((e) => Number(e)).filter((e) => !isNaN(e));
43314332 if (selectedWorlds.length > 0) {
43324333 selectedWorlds.forEach((worldIndex) => {
43334334 const existingWorldName = world_names[worldIndex];