mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into parser-followup-2
This commit is contained in:
@ -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 === '';
|
||||||
|
@ -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);
|
||||||
|
|
||||||
// ---------------------------------------------------
|
// ---------------------------------------------------
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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));
|
||||||
});
|
});
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 '';
|
||||||
|
@ -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
|
||||||
|
@ -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];
|
||||||
|
Reference in New Issue
Block a user