Compare commits

..

4 Commits

Author SHA1 Message Date
Cohee
5b270c3333 Merge branch 'staging' into CollapseSameEntityInstructMessages 2025-06-01 20:16:06 +03:00
RossAscends
ffe0e76812 console warn --> debug 2025-05-24 12:08:03 +09:00
RossAscends
7ccf05d160 fix doubled "label for=" 2025-05-24 12:02:13 +09:00
RossAscends
b5da8742e8 initial commit 2025-05-24 11:44:14 +09:00
28 changed files with 265 additions and 306 deletions

View File

@@ -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

View File

@@ -25,6 +25,10 @@
font-size: 15px;
}
.mes_text img {
width: 100%;
}
#extensions_settings,
#extensions_settings2 {
width: 100% !important;

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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');

View File

@@ -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');

View File

@@ -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.');
}

View File

@@ -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);
}
}

View File

@@ -43,8 +43,3 @@
#dragGallery {
min-height: 25dvh;
}
#gallery .right_menu_button.warning {
opacity: 1;
filter: unset;
}

View File

@@ -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(),

View File

@@ -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');
}

View File

@@ -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 },
];

View File

@@ -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 });
}
/**

View File

@@ -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() {

View File

@@ -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 = {};

View File

@@ -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);

View File

@@ -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');
}
/**

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 });
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
});

View File

@@ -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());

View File

@@ -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,
};
}
}

View File

@@ -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;

View File

@@ -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": {