mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
4 Commits
OptimizedW
...
CollapseSa
Author | SHA1 | Date | |
---|---|---|---|
|
5b270c3333 | ||
|
ffe0e76812 | ||
|
7ccf05d160 | ||
|
b5da8742e8 |
@@ -70,7 +70,7 @@ enableDiscreetLogin: false
|
||||
# https://www.authelia.com/
|
||||
# This will use auto login to an account with the same username
|
||||
# as that used for authlia. (Ensure the username in authlia
|
||||
# is an exact match in lowercase with that in sillytavern)
|
||||
# is an exact match with that in sillytavern)
|
||||
autheliaAuth: false
|
||||
# If `basicAuthMode` and this are enabled then
|
||||
# the username and passwords for basic auth are the same as those
|
||||
|
@@ -25,6 +25,10 @@
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.mes_text img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#extensions_settings,
|
||||
#extensions_settings2 {
|
||||
width: 100% !important;
|
||||
|
@@ -140,7 +140,7 @@
|
||||
<div data-preset-manager-import="novel" class="margin0 menu_button_icon menu_button" title="Import preset" data-i18n="[title]Import preset">
|
||||
<i class="fa-fw fa-solid fa-file-import"></i>
|
||||
</div>
|
||||
<div data-preset-manager-export="novel" class="margin0 menu_button_icon menu_button" title="Export preset" data-i18n="[title]Export preset">
|
||||
<div data-preset-manager-export="novel" class="margin0 menu_button_icon menu_button" title="Export preset" data-i18n="[title]Export preset">
|
||||
<i class="fa-fw fa-solid fa-file-export"></i>
|
||||
</div>
|
||||
<div data-preset-manager-delete="novel" class="margin0 menu_button_icon menu_button" title="Delete the preset" data-i18n="[title]Delete the preset">
|
||||
@@ -663,7 +663,7 @@
|
||||
<div data-source="openrouter" class="range-block">
|
||||
<div class="range-block-title" title="Allow compressing requests by removing messages from the middle of the prompt.">
|
||||
<span data-i18n="Middle-out Transform">Middle-out Transform</span>
|
||||
<a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a>
|
||||
<a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a>
|
||||
</div>
|
||||
<div class="wide100p">
|
||||
<select id="openrouter_middleout" class="text_pole">
|
||||
@@ -3825,7 +3825,7 @@
|
||||
<input id="instruct_bind_to_context" type="checkbox" style="display:none;" />
|
||||
<small><i class="fa-solid fa-link menu_button margin0"></i></small>
|
||||
</label>
|
||||
<label id="instruct_enabled_label"for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
|
||||
<label id="instruct_enabled_label" for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
|
||||
<input id="instruct_enabled" type="checkbox" style="display:none;" />
|
||||
<small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small>
|
||||
</label>
|
||||
@@ -3869,6 +3869,10 @@
|
||||
<input id="instruct_skip_examples" type="checkbox" />
|
||||
<small data-i18n="Skip Example Dialogues Formatting">Skip Example Dialogues Formatting</small>
|
||||
</label>
|
||||
<label for="instruct_collapse_same_entity" class="checkbox_label">
|
||||
<input id="instruct_collapse_same_entity" type="checkbox" />
|
||||
<small data-i18n="Collapse Consecutive Same-Entity Sequences" title="If enabled, all consecutive messages with the same entity will be merged in prompt." data-i18n="[title]If enabled, all consecutive messages with the same entity will be merged in prompt.">Collapse Consecutive Same-Entity Sequences</small>
|
||||
</label>
|
||||
<div>
|
||||
<small data-i18n="Include Names">
|
||||
Include Names
|
||||
@@ -4286,8 +4290,8 @@
|
||||
<span data-i18n="Budget Cap">Budget Cap</span>
|
||||
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title](0 = disabled)" title="(0 = disabled)"></div>
|
||||
</small>
|
||||
<input class="neo-range-slider" type="range" id="world_info_budget_cap" name="world_info_budget_cap" min="0" max="65536" step="1">
|
||||
<input class="neo-range-input" type="number" min="0" max="65536" step="1" data-for="world_info_budget_cap" id="world_info_budget_cap_counter">
|
||||
<input class="neo-range-slider" type="range" id="world_info_budget_cap" name="world_info_budget_cap" min="0" max="8192" step="1">
|
||||
<input class="neo-range-input" type="number" min="0" max="8192" step="1" data-for="world_info_budget_cap" id="world_info_budget_cap_counter">
|
||||
</div>
|
||||
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" title="Scan chronologically until reached min entries or token budget." data-i18n="[title]Scan chronologically until reached min entries or token budget.">
|
||||
@@ -5284,7 +5288,7 @@
|
||||
<div class="flex-container wide100p alignitemscenter spaceBetween flexNoGap">
|
||||
<div class="flex-container alignItemsBaseline wide100p">
|
||||
<div class="flex1 flex-container alignItemsBaseline">
|
||||
<h3 class="margin0" >
|
||||
<h3 class="margin0">
|
||||
<span data-i18n="Persona Management">Persona Management</span>
|
||||
<a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank">
|
||||
<span class="fa-solid fa-circle-question note-link-span"></span>
|
||||
|
174
public/script.js
174
public/script.js
@@ -2439,7 +2439,7 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
|
||||
const image = messageElement.find('.mes_img');
|
||||
const text = messageElement.find('.mes_text');
|
||||
const isInline = !!mes.extra?.inline_image;
|
||||
const doAdjustScroll = () => {
|
||||
image.off('load').on('load', function () {
|
||||
if (!adjustScroll) {
|
||||
return;
|
||||
}
|
||||
@@ -2447,16 +2447,6 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
|
||||
const newChatHeight = $('#chat').prop('scrollHeight');
|
||||
const diff = newChatHeight - chatHeight;
|
||||
$('#chat').scrollTop(scrollPosition + diff);
|
||||
};
|
||||
image.off('load').on('load', function () {
|
||||
image.removeAttr('alt');
|
||||
image.removeClass('error');
|
||||
doAdjustScroll();
|
||||
});
|
||||
image.off('error').on('error', function () {
|
||||
image.attr('alt', '');
|
||||
image.addClass('error');
|
||||
doAdjustScroll();
|
||||
});
|
||||
image.attr('src', mes.extra?.image);
|
||||
image.attr('title', mes.extra?.title || mes.title || '');
|
||||
@@ -3756,6 +3746,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
|
||||
prompt = api == 'novel' ? adjustNovelInstructionPrompt(prompt) : prompt;
|
||||
prompt = isInstruct ? formatInstructModeChat(name1, prompt, false, true, '', name1, name2, false) : prompt;
|
||||
prompt = isInstruct ? (prompt + formatInstructModePrompt(name2, false, '', name1, name2, isQuiet, quietToLoud)) : (prompt + '\n');
|
||||
prompt = isInstruct && power_user.instruct.collapse_same_entity ? (collapseConsecutiveMessagesInPrompt(prompt, power_user.instruct)) : prompt;
|
||||
|
||||
try {
|
||||
if (responseLengthCustomized) {
|
||||
@@ -3955,6 +3946,159 @@ function removeLastMessage() {
|
||||
});
|
||||
}
|
||||
|
||||
function collapseConsecutiveMessagesInPrompt(prompt, instruct) {
|
||||
if (!instruct.enabled) {
|
||||
console.debug('Instruct mode is disabled; returning original prompt');
|
||||
return prompt;
|
||||
}
|
||||
|
||||
if (!instruct.collapse_same_entity) {
|
||||
console.debug('collapse_same_entity is false; returning original prompt');
|
||||
return prompt;
|
||||
}
|
||||
|
||||
console.debug('Collapsing consecutive messages in prompt');
|
||||
|
||||
// Define prefixes and suffixes
|
||||
const prefixes = {
|
||||
system: instruct.system_sequence,
|
||||
system_first: instruct.system_sequence_prefix,
|
||||
user: instruct.input_sequence,
|
||||
assistant: instruct.output_sequence,
|
||||
};
|
||||
const suffixes = {
|
||||
system_first: instruct.system_sequence_suffix,
|
||||
system: instruct.system_suffix,
|
||||
user: instruct.input_suffix,
|
||||
assistant: instruct.output_suffix,
|
||||
};
|
||||
|
||||
// Validate required settings
|
||||
// Fail only if all prefixes and suffixes are empty strings
|
||||
const allPrefixesEmpty = Object.values(prefixes).every(val => !val);
|
||||
const allSuffixesEmpty = Object.values(suffixes).every(val => !val);
|
||||
if (allPrefixesEmpty && allSuffixesEmpty) {
|
||||
console.debug('All prefixes and suffixes are empty; returning original prompt', {
|
||||
system_sequence: prefixes.system,
|
||||
system_sequence_prefix: prefixes.system_first,
|
||||
input_sequence: prefixes.user,
|
||||
output_sequence: prefixes.assistant,
|
||||
system_first_suffix: suffixes.system_first,
|
||||
system_suffix: suffixes.system,
|
||||
input_suffix: suffixes.user,
|
||||
output_suffix: suffixes.assistant,
|
||||
});
|
||||
return prompt;
|
||||
}
|
||||
|
||||
// Escape prefixes and suffixes for regex, handling empty strings
|
||||
const escapeRegex = (str) => str ? str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
|
||||
const prefixRegexStr = Object.values(prefixes)
|
||||
.filter(val => val)
|
||||
.map(escapeRegex)
|
||||
.join('|') || '\\b';
|
||||
const suffixRegexStr = Object.values(suffixes)
|
||||
.filter(val => val)
|
||||
.map(escapeRegex)
|
||||
.join('|') || '\\b';
|
||||
// Use lookahead to match content up to suffix or next prefix
|
||||
const messageRegex = new RegExp(
|
||||
`(${prefixRegexStr})([\\s\\S]*?)(?=(?:${suffixRegexStr}|${prefixRegexStr}|$))((?:${suffixRegexStr})?)`,
|
||||
'g',
|
||||
);
|
||||
|
||||
// Parse the prompt into messages
|
||||
const messages = [];
|
||||
let match;
|
||||
let lastIndex = 0;
|
||||
|
||||
console.debug('Parsing prompt with regex:', messageRegex);
|
||||
while ((match = messageRegex.exec(prompt)) !== null) {
|
||||
const [, prefix, content, suffix = ''] = match;
|
||||
const prefixStart = match.index;
|
||||
const gap = prompt.slice(lastIndex, prefixStart);
|
||||
if (gap) {
|
||||
console.debug(`Found gap between messages: ${JSON.stringify(gap)}`);
|
||||
}
|
||||
messages.push({ prefix, content: content.trim(), suffix, fullMatch: match[0], gap });
|
||||
lastIndex = match.index + match[0].length;
|
||||
console.debug(`Matched message: prefix=${prefix}, content=${content}, suffix=${suffix}`);
|
||||
}
|
||||
|
||||
// Capture any trailing text
|
||||
if (lastIndex < prompt.length) {
|
||||
const trailing = prompt.slice(lastIndex);
|
||||
console.debug(`Found trailing text: ${JSON.stringify(trailing)}`);
|
||||
// Check if trailing text starts with a prefix
|
||||
const trailingMatch = trailing.match(new RegExp(`^(${prefixRegexStr})([\\s\\S]*?)(?=(?:${suffixRegexStr}|${prefixRegexStr}|$))((?:${suffixRegexStr})?)`));
|
||||
if (trailingMatch) {
|
||||
const [, prefix, content, suffix = ''] = trailingMatch;
|
||||
messages.push({ prefix, content: content.trim(), suffix, fullMatch: trailingMatch[0], gap: '' });
|
||||
console.debug(`Matched trailing message: prefix=${prefix}, content=${content}, suffix=${suffix}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.length === 0) {
|
||||
console.debug('No messages parsed; returning original prompt');
|
||||
return prompt;
|
||||
}
|
||||
|
||||
// Collapse consecutive messages with the same effective prefix
|
||||
const collapsedMessages = [];
|
||||
let i = 0;
|
||||
|
||||
while (i < messages.length) {
|
||||
const current = messages[i];
|
||||
const effectivePrefix = (i === 0 && current.prefix === prefixes.system_first) ? prefixes.system : current.prefix;
|
||||
let combinedContent = [current.content];
|
||||
let currentSuffix = current.suffix;
|
||||
let j = i + 1;
|
||||
|
||||
// Avoid collapsing the last complete assistant message if followed by an incomplete assistant message
|
||||
const isLastCompleteAssistant = i === messages.length - 2 &&
|
||||
messages[i].prefix === prefixes.assistant &&
|
||||
messages[i + 1].prefix === prefixes.assistant;
|
||||
|
||||
if (!isLastCompleteAssistant) {
|
||||
// Collect consecutive messages with the same effective prefix
|
||||
while (j < messages.length) {
|
||||
const next = messages[j];
|
||||
const nextEffectivePrefix = (j === 0 && next.prefix === prefixes.system_first) ? prefixes.system : next.prefix;
|
||||
|
||||
if (nextEffectivePrefix !== effectivePrefix) {
|
||||
console.debug(`Stopping collapse at index ${j}: effectivePrefix=${effectivePrefix}, nextEffectivePrefix=${nextEffectivePrefix}`);
|
||||
break;
|
||||
}
|
||||
|
||||
combinedContent.push(next.content);
|
||||
currentSuffix = next.suffix; // Use the last suffix
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
// Join contents with newlines
|
||||
const joinedContent = combinedContent.reduce((acc, curr, idx) => {
|
||||
if (idx === 0) return curr;
|
||||
return acc + '\n' + curr;
|
||||
}, '');
|
||||
|
||||
// Add newline after prefix if wrap is true and prefix doesn't end with newline
|
||||
const prefixNewline = instruct.wrap && !current.prefix.match(/\n$/) ? '\n' : '';
|
||||
|
||||
// Create the collapsed message
|
||||
const combinedMessage = `${current.prefix}${prefixNewline}${joinedContent}${currentSuffix}`;
|
||||
collapsedMessages.push(combinedMessage);
|
||||
console.debug(`Collapsed message at index ${i}: ${combinedMessage}`);
|
||||
i = j; // Skip the combined messages
|
||||
}
|
||||
|
||||
// Reconstruct the prompt without extra newlines
|
||||
const collapsedPrompt = collapsedMessages.join('');
|
||||
console.debug('Before collapsing:', prompt);
|
||||
console.debug('After collapsing:', collapsedPrompt);
|
||||
return collapsedPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* MARK:Generate()
|
||||
* Runs a generation using the current chat context.
|
||||
@@ -4998,6 +5142,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
|
||||
showStopButton();
|
||||
|
||||
if (isInstruct && power_user.instruct.collapse_same_entity) {
|
||||
generate_data.prompt = collapseConsecutiveMessagesInPrompt(generate_data.prompt, power_user.instruct);
|
||||
}
|
||||
|
||||
//set array object for prompt token itemization of this message
|
||||
let currentArrayEntry = Number(thisPromptBits.length - 1);
|
||||
let additionalPromptStuff = {
|
||||
@@ -11094,9 +11242,9 @@ jQuery(async function () {
|
||||
const oldFileName = oldFileNameFull.replace('.jsonl', '');
|
||||
|
||||
const popupText = await renderTemplateAsync('chatRename');
|
||||
const newName = await callGenericPopup(popupText, POPUP_TYPE.INPUT, oldFileName);
|
||||
const newName = await callPopup(popupText, 'input', oldFileName);
|
||||
|
||||
if (!newName || typeof newName !== 'string' || newName == oldFileName) {
|
||||
if (!newName || newName == oldFileName) {
|
||||
console.log('no new name found, aborting');
|
||||
return;
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
import {
|
||||
characterGroupOverlay,
|
||||
callPopup,
|
||||
characters,
|
||||
event_types,
|
||||
eventSource,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
import { favsToHotswap } from './RossAscends-mods.js';
|
||||
import { hideLoader, showLoader } from './loader.js';
|
||||
import { convertCharacterToPersona } from './personas.js';
|
||||
import { callGenericPopup, POPUP_TYPE } from './popup.js';
|
||||
import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js';
|
||||
|
||||
/**
|
||||
@@ -835,13 +835,12 @@ class BulkEditOverlay {
|
||||
*/
|
||||
handleContextMenuDelete = () => {
|
||||
const characterIds = this.selectedCharacters;
|
||||
const popupContent = $(BulkEditOverlay.#getDeletePopupContentHtml(characterIds));
|
||||
const checkbox = popupContent.find('#del_char_checkbox');
|
||||
const promise = callGenericPopup(popupContent, POPUP_TYPE.CONFIRM)
|
||||
const popupContent = BulkEditOverlay.#getDeletePopupContentHtml(characterIds);
|
||||
const promise = callPopup(popupContent, null)
|
||||
.then((accept) => {
|
||||
if (!accept) return;
|
||||
if (true !== accept) return;
|
||||
|
||||
const deleteChats = checkbox.prop('checked') ?? false;
|
||||
const deleteChats = document.getElementById('del_char_checkbox').checked ?? false;
|
||||
|
||||
showLoader();
|
||||
const toast = toastr.info('We\'re deleting your characters, please wait...', 'Working on it');
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Fuse } from '../lib.js';
|
||||
|
||||
import { chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
|
||||
import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
|
||||
import { openThirdPartyExtensionMenu, saveMetadataDebounced } from './extensions.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
@@ -245,7 +245,7 @@ async function getNewBackgroundName(referenceElement) {
|
||||
const fileExtension = oldBg.split('.').pop();
|
||||
const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
|
||||
const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
|
||||
const newBgExtensionless = await Popup.show.input(t`Enter new background name:`, null, oldBgExtensionless);
|
||||
const newBgExtensionless = await callPopup('<h3>' + t`Enter new background name:` + '</h3>', 'input', oldBgExtensionless);
|
||||
|
||||
if (!newBgExtensionless) {
|
||||
console.debug('no new_bg_extensionless');
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { appendMediaToMessage, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
|
||||
import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
|
||||
import { getMessageTimeStamp } from '../../RossAscends-mods.js';
|
||||
import { SECRET_KEYS, secret_state } from '../../secrets.js';
|
||||
import { getMultimodalCaption } from '../shared.js';
|
||||
@@ -9,7 +9,6 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'caption';
|
||||
@@ -99,9 +98,9 @@ async function wrapCaptionTemplate(caption) {
|
||||
let messageText = substituteParamsExtended(template, { caption: caption });
|
||||
|
||||
if (extension_settings.caption.refine_mode) {
|
||||
messageText = await Popup.show.input(
|
||||
'Review and edit the generated caption:',
|
||||
'Press "Cancel" to abort the caption sending.',
|
||||
messageText = await callPopup(
|
||||
'<h3>Review and edit the generated caption:</h3>Press "Cancel" to abort the caption sending.',
|
||||
'input',
|
||||
messageText,
|
||||
{ rows: 5, okButton: 'Send' });
|
||||
|
||||
@@ -279,7 +278,7 @@ async function captionMultimodal(base64Img, externalPrompt) {
|
||||
let prompt = externalPrompt || extension_settings.caption.prompt || PROMPT_DEFAULT;
|
||||
|
||||
if (!externalPrompt && extension_settings.caption.prompt_ask) {
|
||||
const customPrompt = await callGenericPopup('Enter a comment or question:', POPUP_TYPE.INPUT, prompt, { rows: 2 });
|
||||
const customPrompt = await callPopup('<h3>Enter a comment or question:</h3>', 'input', prompt, { rows: 2 });
|
||||
if (!customPrompt) {
|
||||
throw new Error('User aborted the caption sending.');
|
||||
}
|
||||
|
@@ -15,12 +15,10 @@ import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/S
|
||||
import { DragAndDropHandler } from '../../dragdrop.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { t, translate } from '../../i18n.js';
|
||||
import { Popup } from '../../popup.js';
|
||||
|
||||
const extensionName = 'gallery';
|
||||
const extensionFolderPath = `scripts/extensions/${extensionName}/`;
|
||||
let firstTime = true;
|
||||
let deleteModeActive = false;
|
||||
|
||||
// Exposed defaults for future tweaking
|
||||
let thumbnailHeight = 150;
|
||||
@@ -148,29 +146,6 @@ async function getGalleryFolders() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a gallery item based on the provided URL.
|
||||
* @param {string} url - The URL of the image to be deleted.
|
||||
*/
|
||||
async function deleteGalleryItem(url) {
|
||||
try {
|
||||
const response = await fetch('/api/images/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ path: url }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error. Status: ${response.status}`);
|
||||
}
|
||||
|
||||
toastr.success(t`Image deleted successfully.`);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete the image:', error);
|
||||
toastr.error(t`Failed to delete the image. Check the console for details.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sort order for the gallery.
|
||||
* @param {string} order Sort order
|
||||
@@ -285,7 +260,7 @@ async function initGallery(items, url) {
|
||||
*
|
||||
* @returns {Promise<void>} - Promise representing the completion of the gallery display process.
|
||||
*/
|
||||
async function showCharGallery(deleteModeState = false) {
|
||||
async function showCharGallery() {
|
||||
// Load necessary files if it's the first time calling the function
|
||||
if (firstTime) {
|
||||
await loadFileToDocument(
|
||||
@@ -301,7 +276,6 @@ async function showCharGallery(deleteModeState = false) {
|
||||
}
|
||||
|
||||
try {
|
||||
deleteModeActive = deleteModeState;
|
||||
let url = selected_group || this_chid;
|
||||
if (!selected_group && this_chid !== undefined) {
|
||||
url = getGalleryFolder(characters[this_chid]);
|
||||
@@ -455,18 +429,6 @@ async function makeMovable(url) {
|
||||
galleryFolderAccept.title = t`Change gallery folder`;
|
||||
galleryFolderAccept.addEventListener('click', onChangeFolder);
|
||||
|
||||
const galleryDeleteMode = document.createElement('div');
|
||||
galleryDeleteMode.classList.add('right_menu_button', 'fa-solid', 'fa-trash', 'fa-fw');
|
||||
galleryDeleteMode.classList.toggle('warning', deleteModeActive);
|
||||
galleryDeleteMode.title = t`Delete mode`;
|
||||
galleryDeleteMode.addEventListener('click', () => {
|
||||
deleteModeActive = !deleteModeActive;
|
||||
galleryDeleteMode.classList.toggle('warning', deleteModeActive);
|
||||
if (deleteModeActive) {
|
||||
toastr.info(t`Delete mode is ON. Click on images you want to delete.`);
|
||||
}
|
||||
});
|
||||
|
||||
const galleryFolderRestore = document.createElement('div');
|
||||
galleryFolderRestore.classList.add('right_menu_button', 'fa-solid', 'fa-recycle', 'fa-fw');
|
||||
galleryFolderRestore.title = t`Restore gallery folder`;
|
||||
@@ -474,7 +436,6 @@ async function makeMovable(url) {
|
||||
|
||||
topBarElement.appendChild(galleryFolderInput);
|
||||
topBarElement.appendChild(galleryFolderAccept);
|
||||
topBarElement.appendChild(galleryDeleteMode);
|
||||
topBarElement.appendChild(galleryFolderRestore);
|
||||
newElement.append(topBarElement);
|
||||
|
||||
@@ -674,19 +635,9 @@ function sanitizeHTMLId(id) {
|
||||
function viewWithDragbox(items) {
|
||||
if (items && items.length > 0) {
|
||||
const url = items[0].responsiveURL(); // Get the URL of the clicked image/video
|
||||
if (deleteModeActive) {
|
||||
Popup.show.confirm(t`Are you sure you want to delete this image?`, url)
|
||||
.then(async (confirmed) => {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
deleteGalleryItem(url).then(() => showCharGallery(deleteModeActive));
|
||||
});
|
||||
} else {
|
||||
// ID should just be the last part of the URL, removing the extension
|
||||
const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
|
||||
makeDragImg(id, url);
|
||||
}
|
||||
// ID should just be the last part of the URL, removing the extension
|
||||
const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
|
||||
makeDragImg(id, url);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -43,8 +43,3 @@
|
||||
#dragGallery {
|
||||
min-height: 25dvh;
|
||||
}
|
||||
|
||||
#gallery .right_menu_button.warning {
|
||||
opacity: 1;
|
||||
filter: unset;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { characters, eventSource, event_types, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js';
|
||||
import { callPopup, characters, eventSource, event_types, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js';
|
||||
import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
|
||||
import { selected_group } from '../../group-chats.js';
|
||||
import { callGenericPopup, POPUP_TYPE } from '../../popup.js';
|
||||
@@ -276,7 +276,7 @@ async function onRegexEditorOpenClick(existingId, isScoped) {
|
||||
editorHtml.find('input, textarea, select').on('input', updateTestResult);
|
||||
updateInfoBlock(editorHtml);
|
||||
|
||||
const popupResult = await callGenericPopup(editorHtml, POPUP_TYPE.CONFIRM, '', { okButton: t`Save`, cancelButton: t`Cancel`, allowVerticalScrolling: true });
|
||||
const popupResult = await callPopup(editorHtml, 'confirm', undefined, { okButton: t`Save` });
|
||||
if (popupResult) {
|
||||
const newRegexScript = {
|
||||
id: existingId ? String(existingId) : uuidv4(),
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
amount_gen,
|
||||
callPopup,
|
||||
getRequestHeaders,
|
||||
max_context,
|
||||
saveSettingsDebounced,
|
||||
@@ -10,7 +11,6 @@ import { delay } from './utils.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
import { autoSelectInstructPreset } from './instruct-mode.js';
|
||||
import { t } from './i18n.js';
|
||||
import { callGenericPopup, POPUP_TYPE } from './popup.js';
|
||||
|
||||
export {
|
||||
horde_settings,
|
||||
@@ -271,7 +271,7 @@ async function generateHorde(prompt, params, signal, reportProgress) {
|
||||
await delay(CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
await callGenericPopup(t`Horde request timed out. Try again`, POPUP_TYPE.TEXT);
|
||||
callPopup('Horde request timed out. Try again', 'text');
|
||||
throw new Error('Horde timeout');
|
||||
}
|
||||
|
||||
|
@@ -43,6 +43,7 @@ const controls = [
|
||||
{ id: 'instruct_derived', property: 'derived', isCheckbox: true },
|
||||
{ id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
|
||||
{ id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
|
||||
{ id: 'instruct_collapse_same_entity', property: 'collapse_same_entity', isCheckbox: true },
|
||||
{ id: 'instruct_names_behavior', property: 'names_behavior', isCheckbox: false },
|
||||
{ id: 'instruct_system_same_as_user', property: 'system_same_as_user', isCheckbox: true, trigger: true },
|
||||
];
|
||||
|
@@ -7,6 +7,7 @@ import { Fuse, DOMPurify } from '../lib.js';
|
||||
|
||||
import {
|
||||
abortStatusCheck,
|
||||
callPopup,
|
||||
characters,
|
||||
event_types,
|
||||
eventSource,
|
||||
@@ -70,7 +71,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
|
||||
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
|
||||
import { t } from './i18n.js';
|
||||
import { ToolManager } from './tool-calling.js';
|
||||
import { accountStorage } from './util/AccountStorage.js';
|
||||
@@ -4083,7 +4084,7 @@ async function onPresetImportFileChange(e) {
|
||||
}
|
||||
|
||||
if (name in openai_setting_names) {
|
||||
const confirm = await callGenericPopup('Preset name already exists. Overwrite?', POPUP_TYPE.CONFIRM);
|
||||
const confirm = await callPopup('Preset name already exists. Overwrite?', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
@@ -4219,7 +4220,7 @@ function onLogitBiasPresetExportClick() {
|
||||
}
|
||||
|
||||
async function onDeletePresetClick() {
|
||||
const confirm = await callGenericPopup(t`Delete the preset? This action is irreversible and your current settings will be overwritten.`, POPUP_TYPE.CONFIRM);
|
||||
const confirm = await callPopup(t`Delete the preset? This action is irreversible and your current settings will be overwritten.`, 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
@@ -4254,7 +4255,7 @@ async function onDeletePresetClick() {
|
||||
}
|
||||
|
||||
async function onLogitBiasPresetDeleteClick() {
|
||||
const value = await callGenericPopup(t`Delete the preset?`, POPUP_TYPE.CONFIRM);
|
||||
const value = await callPopup(t`Delete the preset?`, 'confirm');
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
@@ -4959,7 +4960,10 @@ async function onOpenrouterModelSortChange() {
|
||||
}
|
||||
|
||||
async function onNewPresetClick() {
|
||||
const name = await Popup.show.input(t`Preset name:`, t`Hint: Use a character/group name to bind preset to a specific chat.`, oai_settings.preset_settings_openai);
|
||||
const popupText = `
|
||||
<h3>` + t`Preset name:` + `</h3>
|
||||
<h4>` + t`Hint: Use a character/group name to bind preset to a specific chat.` + '</h4>';
|
||||
const name = await callPopup(popupText, 'input', oai_settings.preset_settings_openai);
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
@@ -5344,7 +5348,7 @@ async function onCustomizeParametersClick() {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true });
|
||||
callPopup(template, 'text', '', { wide: true, large: true });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
main_api,
|
||||
saveSettingsDebounced,
|
||||
callPopup,
|
||||
} from '../script.js';
|
||||
import { power_user } from './power-user.js';
|
||||
//import { BIAS_CACHE, displayLogitBias, getLogitBiasListResult } from './logit-bias.js';
|
||||
@@ -9,7 +10,6 @@ import { power_user } from './power-user.js';
|
||||
//import { getCfgPrompt } from './cfg-scale.js';
|
||||
import { setting_names } from './textgen-settings.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { Popup, POPUP_TYPE } from './popup.js';
|
||||
|
||||
|
||||
const TGsamplerNames = setting_names;
|
||||
@@ -21,6 +21,8 @@ let userDisabledSamplers, userShownSamplers;
|
||||
|
||||
// Goal 1: show popup with all samplers for active API
|
||||
async function showSamplerSelectPopup() {
|
||||
const popup = $('#dialogue_popup');
|
||||
popup.addClass('large_dialogue_popup');
|
||||
const html = $(document.createElement('div'));
|
||||
html.attr('id', 'sampler_view_list')
|
||||
.addClass('flex-container flexFlowColumn');
|
||||
@@ -31,7 +33,7 @@ async function showSamplerSelectPopup() {
|
||||
listContainer.append(APISamplers);
|
||||
html.append(listContainer);
|
||||
|
||||
const showPromise = new Popup(html, POPUP_TYPE.TEXT, null, { wide: true, large: true, allowVerticalScrolling: true }).show();
|
||||
callPopup(html, 'text', null, { allowVerticalScrolling: true });
|
||||
|
||||
setSamplerListListeners();
|
||||
|
||||
@@ -52,8 +54,6 @@ async function showSamplerSelectPopup() {
|
||||
power_user.selectSamplers.forceHidden = [];
|
||||
await validateDisabledSamplers();
|
||||
});
|
||||
|
||||
await showPromise;
|
||||
}
|
||||
|
||||
function setSamplerListListeners() {
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { DOMPurify } from '../lib.js';
|
||||
import { getRequestHeaders } from '../script.js';
|
||||
import { callPopup, getRequestHeaders } from '../script.js';
|
||||
import { t } from './i18n.js';
|
||||
import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
|
||||
|
||||
export const SECRET_KEYS = {
|
||||
HORDE: 'api_key_horde',
|
||||
@@ -117,7 +116,7 @@ async function viewSecrets() {
|
||||
});
|
||||
|
||||
if (response.status == 403) {
|
||||
await Popup.show.text(t`Forbidden`, t`To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.`);
|
||||
callPopup('<h3>' + t`Forbidden` + '</h3><p>' + t`To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.` + '</p>', 'text');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,7 +134,7 @@ async function viewSecrets() {
|
||||
$(table).append(`<tr><td>${DOMPurify.sanitize(key)}</td><td>${DOMPurify.sanitize(value)}</td></tr>`);
|
||||
}
|
||||
|
||||
await callGenericPopup(table.outerHTML, POPUP_TYPE.TEXT, '', { wide: true, large: true, allowVerticalScrolling: true });
|
||||
callPopup(table.outerHTML, 'text');
|
||||
}
|
||||
|
||||
export let secret_state = {};
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
activateSendButtons,
|
||||
addOneMessage,
|
||||
api_server,
|
||||
callPopup,
|
||||
characters,
|
||||
chat,
|
||||
chat_metadata,
|
||||
@@ -4778,7 +4779,7 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
||||
toastr.error(
|
||||
`${toast}${clickHint}`,
|
||||
'SlashCommandExecutionError',
|
||||
{ escapeHtml: false, timeOut: 10000, onclick: () => callGenericPopup(toast, POPUP_TYPE.TEXT, '', { allowHorizontalScrolling: true, allowVerticalScrolling: true }) },
|
||||
{ escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') },
|
||||
);
|
||||
} else {
|
||||
toastr.error(result.errorMessage);
|
||||
@@ -4835,7 +4836,7 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
||||
toastr.error(
|
||||
`${toast}${clickHint}`,
|
||||
'SlashCommandParserError',
|
||||
{ escapeHtml: false, timeOut: 10000, onclick: () => callGenericPopup(toast, POPUP_TYPE.TEXT, '', { allowHorizontalScrolling: true, allowVerticalScrolling: true }) },
|
||||
{ escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') },
|
||||
);
|
||||
const result = new SlashCommandClosureResult();
|
||||
return result;
|
||||
@@ -4865,7 +4866,7 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
||||
toastr.error(
|
||||
`${toast}${clickHint}`,
|
||||
'SlashCommandExecutionError',
|
||||
{ escapeHtml: false, timeOut: 10000, onclick: () => callGenericPopup(toast, POPUP_TYPE.TEXT, '', { allowHorizontalScrolling: true, allowVerticalScrolling: true }) },
|
||||
{ escapeHtml: false, timeOut: 10000, onclick: () => callPopup(toast, 'text') },
|
||||
);
|
||||
} else {
|
||||
toastr.error(e.message);
|
||||
|
@@ -1,8 +1,7 @@
|
||||
// statsHelper.js
|
||||
import { moment } from '../lib.js';
|
||||
import { getRequestHeaders, characters, this_chid } from '../script.js';
|
||||
import { getRequestHeaders, callPopup, characters, this_chid } from '../script.js';
|
||||
import { humanizeGenTime } from './RossAscends-mods.js';
|
||||
import { callGenericPopup, POPUP_TYPE } from './popup.js';
|
||||
import { registerDebugFunction } from './power-user.js';
|
||||
|
||||
let charStats = {};
|
||||
@@ -124,7 +123,7 @@ function createHtml(statsType, stats) {
|
||||
html += createStatBlock('Character Words', stats.non_user_word_count);
|
||||
html += createStatBlock('Swipes', stats.total_swipe_count);
|
||||
|
||||
return callGenericPopup(html, POPUP_TYPE.TEXT);
|
||||
callPopup(html, 'text');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { DOMPurify } from '../lib.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
import { amount_gen, eventSource, event_types, getRequestHeaders, max_context, online_status, setGenerationParamsFromPreset } from '../script.js';
|
||||
import { amount_gen, callPopup, eventSource, event_types, getRequestHeaders, max_context, online_status, setGenerationParamsFromPreset } from '../script.js';
|
||||
import { textgenerationwebui_settings as textgen_settings, textgen_types } from './textgen-settings.js';
|
||||
import { tokenizers } from './tokenizers.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
@@ -761,7 +761,7 @@ async function downloadOllamaModel() {
|
||||
|
||||
const html = `Enter a model tag, for example <code>llama2:latest</code>.<br>
|
||||
See <a target="_blank" href="https://ollama.ai/library">Library</a> for available models.`;
|
||||
const name = await callGenericPopup(html, POPUP_TYPE.INPUT, '', { okButton: 'Download' });
|
||||
const name = await callPopup(html, 'input', '', { okButton: 'Download' });
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
|
@@ -60,9 +60,6 @@ function assignNestedVariables(scope, arg, prefix) {
|
||||
Object.entries(arg).forEach(([key, value]) => {
|
||||
const newPrefix = `${prefix}.${key}`;
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (Array.isArray(value)) {
|
||||
scope.letVariable(newPrefix, JSON.stringify(value));
|
||||
}
|
||||
assignNestedVariables(scope, value, newPrefix);
|
||||
} else {
|
||||
scope.letVariable(newPrefix, value);
|
||||
@@ -300,19 +297,6 @@ export class ToolManager {
|
||||
console.log(`[ToolManager] Unregistered function tool: ${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tool call parameters -- they're usually JSON, but they can also be empty strings (which are not valid JSON apparently).
|
||||
* @param {object} parameters The parameters for a tool call, usually a string with JSON inside
|
||||
* @returns {object} The parsed parameters
|
||||
*/
|
||||
static #parseParameters(parameters) {
|
||||
return parameters === ''
|
||||
? {}
|
||||
: typeof parameters === 'string'
|
||||
? JSON.parse(parameters)
|
||||
: parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a tool by name. Returns the result of the tool's action function.
|
||||
* @param {string} name The name of the tool to invoke.
|
||||
@@ -325,7 +309,7 @@ export class ToolManager {
|
||||
throw new Error(`No tool with the name "${name}" has been registered.`);
|
||||
}
|
||||
|
||||
const invokeParameters = this.#parseParameters(parameters);
|
||||
const invokeParameters = typeof parameters === 'string' ? JSON.parse(parameters) : parameters;
|
||||
const tool = this.#tools.get(name);
|
||||
const result = await tool.invoke(invokeParameters);
|
||||
return typeof result === 'string' ? result : JSON.stringify(result);
|
||||
@@ -368,7 +352,7 @@ export class ToolManager {
|
||||
|
||||
try {
|
||||
const tool = this.#tools.get(name);
|
||||
const formatParameters = this.#parseParameters(parameters);
|
||||
const formatParameters = typeof parameters === 'string' ? JSON.parse(parameters) : parameters;
|
||||
return await tool.formatMessage(formatParameters);
|
||||
} catch (error) {
|
||||
console.error(`[ToolManager] An error occurred while formatting the tool call message for "${name}":`, error);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Fuse } from '../lib.js';
|
||||
|
||||
import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
|
||||
import { download, debounce, delay, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents } from './utils.js';
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents } from './utils.js';
|
||||
import { extension_settings, getContext } from './extensions.js';
|
||||
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
@@ -85,7 +85,6 @@ const saveSettingsDebounced = debounce(() => {
|
||||
}, debounce_timeout.relaxed);
|
||||
const sortFn = (a, b) => b.order - a.order;
|
||||
let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };
|
||||
let isSaveWorldInfoDisabled = false;
|
||||
|
||||
// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
|
||||
export const worldInfoFilter = new FilterHelper(() => updateEditor());
|
||||
@@ -1755,12 +1754,12 @@ function registerWorldInfoSlashCommands() {
|
||||
*/
|
||||
export async function showWorldEditor(name) {
|
||||
if (!name) {
|
||||
await hideWorldEditor();
|
||||
hideWorldEditor();
|
||||
return;
|
||||
}
|
||||
|
||||
const wiData = await loadWorldInfo(name);
|
||||
await displayWorldEntries(name, wiData);
|
||||
displayWorldEntries(name, wiData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1816,8 +1815,8 @@ export async function updateWorldInfoList() {
|
||||
}
|
||||
}
|
||||
|
||||
async function hideWorldEditor() {
|
||||
await displayWorldEntries(null, null);
|
||||
function hideWorldEditor() {
|
||||
displayWorldEntries(null, null);
|
||||
}
|
||||
|
||||
function getWIElement(name) {
|
||||
@@ -1938,75 +1937,15 @@ function updateWorldEntryKeyOptionsCache(keyOptions, { remove = false, reset = f
|
||||
worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text));
|
||||
}
|
||||
|
||||
function clearEntryList() {
|
||||
console.time('clearEntryList');
|
||||
const $list = $('#world_popup_entries_list');
|
||||
|
||||
if (!$list.children().length) {
|
||||
console.debug('List already empty, skipping cleanup.');
|
||||
console.timeEnd('clearEntryList');
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: Clean all <option> elements within <select>
|
||||
$list.find('option').each(function () {
|
||||
const $option = $(this);
|
||||
$option.off();
|
||||
$.cleanData([$option[0]]);
|
||||
$option.remove();
|
||||
});
|
||||
|
||||
// Step 2: Clean all <select> elements
|
||||
$list.find('select').each(function () {
|
||||
const $select = $(this);
|
||||
// Remove Select2-related data and container if present
|
||||
if ($select.data('select2')) {
|
||||
try {
|
||||
$select.select2('destroy');
|
||||
} catch (e) {
|
||||
console.debug('Select2 destroy failed:', e);
|
||||
}
|
||||
}
|
||||
const $container = $select.parent();
|
||||
if ($container.length) {
|
||||
$container.find('*').off();
|
||||
$.cleanData($container.find('*').get());
|
||||
$container.remove();
|
||||
}
|
||||
|
||||
$select.off();
|
||||
$.cleanData([$select[0]]);
|
||||
});
|
||||
|
||||
// Step 3: Clean <div>, <span>, <input>
|
||||
$list.find('div, span, input').each(function () {
|
||||
const $elem = $(this);
|
||||
$elem.off();
|
||||
$.cleanData([$elem[0]]);
|
||||
$elem.remove();
|
||||
});
|
||||
|
||||
const totalElementsOfAnyKindLeftInList = $list.children().length;
|
||||
|
||||
// Final cleanup
|
||||
if (totalElementsOfAnyKindLeftInList) {
|
||||
console.time('empty');
|
||||
$list.empty();
|
||||
console.timeEnd('empty');
|
||||
}
|
||||
|
||||
console.timeEnd('clearEntryList');
|
||||
}
|
||||
|
||||
async function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
|
||||
updateEditor = async (navigation, flashOnNav = true) => await displayWorldEntries(name, data, navigation, flashOnNav);
|
||||
function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
|
||||
updateEditor = (navigation, flashOnNav = true) => displayWorldEntries(name, data, navigation, flashOnNav);
|
||||
|
||||
const worldEntriesList = $('#world_popup_entries_list');
|
||||
|
||||
worldEntriesList.css({ 'opacity': 0, 'transition': 'opacity 250ms ease-in-out' });
|
||||
await delay(250);
|
||||
clearEntryList();
|
||||
worldEntriesList.show();
|
||||
// We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
|
||||
// This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
|
||||
worldEntriesList.find('*').off();
|
||||
worldEntriesList.empty().show();
|
||||
|
||||
if (!data || !('entries' in data)) {
|
||||
$('#world_popup_new').off('click').on('click', nullWorldInfo);
|
||||
@@ -2099,39 +2038,22 @@ async function displayWorldEntries(name, data, navigation = navigation_option.no
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
showNavigator: true,
|
||||
callback: async function (/** @type {object[]} */ page) {
|
||||
try {
|
||||
// Prevent saveWorldInfo from firing timeouts while rendering the list
|
||||
isSaveWorldInfoDisabled = true;
|
||||
clearEntryList();
|
||||
// We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
|
||||
// This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
|
||||
worldEntriesList.find('*').off();
|
||||
worldEntriesList.empty();
|
||||
|
||||
const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');
|
||||
const blocks = [];
|
||||
|
||||
for (const entry of page) {
|
||||
try {
|
||||
const block = await getWorldEntry(name, data, entry);
|
||||
if (block) {
|
||||
blocks.push(block);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error while processing entry ${entry.uid}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
|
||||
if (!isCustomOrder) {
|
||||
blocks.forEach(block => {
|
||||
block.find('.drag-handle').remove();
|
||||
});
|
||||
}
|
||||
|
||||
worldEntriesList.append(keywordHeaders);
|
||||
worldEntriesList.append(blocks);
|
||||
} catch (error) {
|
||||
console.error('Error while rendering WI entries:', error);
|
||||
} finally {
|
||||
isSaveWorldInfoDisabled = false;
|
||||
const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');
|
||||
const blocksPromises = page.map(async (entry) => await getWorldEntry(name, data, entry)).filter(x => x);
|
||||
const blocks = await Promise.all(blocksPromises);
|
||||
const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
|
||||
if (!isCustomOrder) {
|
||||
blocks.forEach(block => {
|
||||
block.find('.drag-handle').remove();
|
||||
});
|
||||
}
|
||||
worldEntriesList.append(keywordHeaders);
|
||||
worldEntriesList.append(blocks);
|
||||
},
|
||||
afterSizeSelectorChange: function (e) {
|
||||
accountStorage.setItem(storageKey, e.target.value);
|
||||
@@ -2251,7 +2173,7 @@ async function displayWorldEntries(name, data, navigation = navigation_option.no
|
||||
if (selectedIndex !== -1) {
|
||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||
} else {
|
||||
await hideWorldEditor();
|
||||
hideWorldEditor();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -2289,12 +2211,6 @@ async function displayWorldEntries(name, data, navigation = navigation_option.no
|
||||
await saveWorldInfo(name, data);
|
||||
},
|
||||
});
|
||||
|
||||
worldEntriesList.css('opacity', '1');
|
||||
await delay(250);
|
||||
worldEntriesList.removeAttr('style');
|
||||
|
||||
|
||||
//$("#world_popup_entries_list").disableSelection();
|
||||
}
|
||||
|
||||
@@ -3350,7 +3266,7 @@ export async function getWorldEntry(name, data, entry) {
|
||||
container.appendChild(select);
|
||||
|
||||
let selectedWorldIndex = -1;
|
||||
select.addEventListener('change', function () {
|
||||
select.addEventListener('change', function() {
|
||||
selectedWorldIndex = this.value === '' ? -1 : Number(this.value);
|
||||
});
|
||||
|
||||
@@ -3745,11 +3661,6 @@ async function _save(name, data) {
|
||||
* @return {Promise<void>} A promise that resolves when the world info is saved
|
||||
*/
|
||||
export async function saveWorldInfo(name, data, immediately = false) {
|
||||
// Saving is temporarily disabled
|
||||
if (isSaveWorldInfoDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name || !data) {
|
||||
return;
|
||||
}
|
||||
@@ -3908,7 +3819,7 @@ export async function createNewWorldInfo(worldName, { interactive = false } = {}
|
||||
if (selectedIndex !== -1) {
|
||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||
} else {
|
||||
await hideWorldEditor();
|
||||
hideWorldEditor();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -5474,7 +5385,7 @@ export function initWorldInfo() {
|
||||
const selectedIndex = String($('#world_editor_select').find(':selected').val());
|
||||
|
||||
if (selectedIndex === '') {
|
||||
await hideWorldEditor();
|
||||
hideWorldEditor();
|
||||
} else {
|
||||
const worldName = world_names[selectedIndex];
|
||||
showWorldEditor(worldName);
|
||||
@@ -5626,12 +5537,9 @@ export function initWorldInfo() {
|
||||
select2ChoiceClickSubscribe($('#world_info'), target => {
|
||||
const name = $(target).text();
|
||||
const selectedIndex = world_names.indexOf(name);
|
||||
const alreadySelectedInEditor = $('#world_editor_select option:selected').text() === name;
|
||||
if (selectedIndex !== -1 && !alreadySelectedInEditor) {
|
||||
if (selectedIndex !== -1) {
|
||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||
console.log('Quick selection of world', name);
|
||||
} else {
|
||||
console.warn('lets not reload an already loaded list yes?');
|
||||
}
|
||||
}, { buttonStyle: true, closeDrawer: true });
|
||||
}
|
||||
|
@@ -5059,12 +5059,6 @@ a:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mes_img.error {
|
||||
visibility: hidden;
|
||||
min-height: 100px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.mes_img_swipes,
|
||||
.mes_img_controls {
|
||||
position: absolute;
|
||||
@@ -5105,8 +5099,6 @@ a:hover {
|
||||
filter: brightness(150%);
|
||||
}
|
||||
|
||||
.mes_img_container:has(.mes_img.error) .mes_img_swipes,
|
||||
.mes_img_container:has(.mes_img.error) .mes_img_controls,
|
||||
.mes_img_container:hover .mes_img_swipes,
|
||||
.mes_img_container:focus-within .mes_img_swipes,
|
||||
.mes_img_container:hover .mes_img_controls,
|
||||
|
@@ -1342,11 +1342,10 @@ router.post('/generate', function (request, response) {
|
||||
bodyParams['reasoning'] = { effort: request.body.reasoning_effort };
|
||||
}
|
||||
|
||||
const cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number');
|
||||
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number');
|
||||
const isClaude3or4 = /anthropic\/claude-(3|opus-4|sonnet-4)/.test(request.body.model);
|
||||
const cacheTTL = getConfigValue('claude.extendedTTL', false, 'boolean') ? '1h' : '5m';
|
||||
if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && isClaude3or4) {
|
||||
cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth, cacheTTL);
|
||||
cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth);
|
||||
}
|
||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.CUSTOM) {
|
||||
apiUrl = request.body.custom_url;
|
||||
|
@@ -411,7 +411,7 @@ export async function getChatInfo(pathToFile, additionalData = {}, isGroup = fal
|
||||
const jsonData = tryParse(lastLine);
|
||||
if (jsonData && (jsonData.name || jsonData.character_name)) {
|
||||
chatData.chat_items = isGroup ? itemCounter : (itemCounter - 1);
|
||||
chatData.mes = jsonData['mes'] || '[The message is empty]';
|
||||
chatData.mes = jsonData['mes'] || '[The chat is empty]';
|
||||
chatData.last_mes = jsonData['send_date'] || stats.mtimeMs;
|
||||
|
||||
res(chatData);
|
||||
|
@@ -5,7 +5,7 @@ import { Buffer } from 'node:buffer';
|
||||
import express from 'express';
|
||||
import sanitize from 'sanitize-filename';
|
||||
|
||||
import { clientRelativePath, removeFileExtension, getImages, isPathUnderParent } from '../util.js';
|
||||
import { clientRelativePath, removeFileExtension, getImages } from '../util.js';
|
||||
|
||||
/**
|
||||
* Ensure the directory for the provided file path exists.
|
||||
@@ -126,27 +126,3 @@ router.post('/folders', (request, response) => {
|
||||
return response.status(500).send({ error: 'Unable to retrieve folders' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/delete', async (request, response) => {
|
||||
try {
|
||||
if (!request.body.path) {
|
||||
return response.status(400).send('No path specified');
|
||||
}
|
||||
|
||||
const pathToDelete = path.join(request.user.directories.root, request.body.path);
|
||||
if (!isPathUnderParent(request.user.directories.userImages, pathToDelete)) {
|
||||
return response.status(400).send('Invalid path');
|
||||
}
|
||||
|
||||
if (!fs.existsSync(pathToDelete)) {
|
||||
return response.status(404).send('File not found');
|
||||
}
|
||||
|
||||
fs.unlinkSync(pathToDelete);
|
||||
console.info(`Deleted image: ${request.body.path} from ${request.user.profile.handle}`);
|
||||
return response.sendStatus(200);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
@@ -819,6 +819,7 @@ pollinations.post('/generate', async (request, response) => {
|
||||
model: String(request.body.model),
|
||||
negative_prompt: String(request.body.negative_prompt),
|
||||
seed: String(request.body.seed >= 0 ? request.body.seed : Math.floor(Math.random() * 10_000_000)),
|
||||
enhance: String(request.body.enhance ?? false),
|
||||
width: String(request.body.width ?? 1024),
|
||||
height: String(request.body.height ?? 1024),
|
||||
nologo: String(true),
|
||||
@@ -826,9 +827,6 @@ pollinations.post('/generate', async (request, response) => {
|
||||
private: String(true),
|
||||
referrer: 'sillytavern',
|
||||
});
|
||||
if (request.body.enhance) {
|
||||
params.set('enhance', String(true));
|
||||
}
|
||||
promptUrl.search = params.toString();
|
||||
|
||||
console.info('Pollinations request URL:', promptUrl.toString());
|
||||
|
@@ -906,9 +906,8 @@ export function cachingAtDepthForClaude(messages, cachingAtDepth, ttl) {
|
||||
* messages array.
|
||||
* @param {object[]} messages Array of messages
|
||||
* @param {number} cachingAtDepth Depth at which caching is supposed to occur
|
||||
* @param {string} ttl TTL value
|
||||
*/
|
||||
export function cachingAtDepthForOpenRouterClaude(messages, cachingAtDepth, ttl) {
|
||||
export function cachingAtDepthForOpenRouterClaude(messages, cachingAtDepth) {
|
||||
//caching the prefill is a terrible idea in general
|
||||
let passedThePrefill = false;
|
||||
//depth here is the number of message role switches
|
||||
@@ -928,13 +927,12 @@ export function cachingAtDepthForOpenRouterClaude(messages, cachingAtDepth, ttl)
|
||||
messages[i].content = [{
|
||||
type: 'text',
|
||||
text: content,
|
||||
cache_control: { type: 'ephemeral', ttl: ttl },
|
||||
cache_control: { type: 'ephemeral' },
|
||||
}];
|
||||
} else {
|
||||
const contentPartCount = content.length;
|
||||
content[contentPartCount - 1].cache_control = {
|
||||
type: 'ephemeral',
|
||||
ttl: ttl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -760,7 +760,7 @@ async function autheliaUserLogin(request) {
|
||||
|
||||
const userHandles = await getAllUserHandles();
|
||||
for (const userHandle of userHandles) {
|
||||
if (remoteUser.toLowerCase() === userHandle) {
|
||||
if (remoteUser === userHandle) {
|
||||
const user = await storage.getItem(toKey(userHandle));
|
||||
if (user && user.enabled) {
|
||||
request.session.handle = userHandle;
|
||||
|
6
tests/package-lock.json
generated
6
tests/package-lock.json
generated
@@ -5332,9 +5332,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz",
|
||||
"integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==",
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
|
||||
"integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
Reference in New Issue
Block a user