mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging
This commit is contained in:
@@ -725,8 +725,14 @@ export function initRossMods() {
|
||||
RA_autoconnect();
|
||||
}
|
||||
|
||||
if (getParsedUA()?.os?.name === 'iOS') {
|
||||
document.body.classList.add('ios');
|
||||
const userAgent = getParsedUA();
|
||||
console.debug('User Agent', userAgent);
|
||||
const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||
const isDesktopSafari = userAgent?.browser?.name === 'Safari' && userAgent?.platform?.type === 'desktop';
|
||||
const isIOS = userAgent?.os?.name === 'iOS';
|
||||
|
||||
if (isIOS || isMobileSafari || isDesktopSafari) {
|
||||
document.body.classList.add('safari');
|
||||
}
|
||||
|
||||
$('#main_api').change(function () {
|
||||
|
@@ -1430,7 +1430,7 @@ jQuery(function () {
|
||||
wrapper.classList.add('flexFlowColumn', 'justifyCenter', 'alignitemscenter');
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = String(bro.val());
|
||||
textarea.classList.add('height100p', 'wide100p');
|
||||
textarea.classList.add('height100p', 'wide100p', 'maximized_textarea');
|
||||
bro.hasClass('monospace') && textarea.classList.add('monospace');
|
||||
textarea.addEventListener('input', function () {
|
||||
bro.val(textarea.value).trigger('input');
|
||||
|
@@ -154,7 +154,7 @@ export function initDynamicStyles() {
|
||||
// Process all stylesheets on initial load
|
||||
Array.from(document.styleSheets).forEach(sheet => {
|
||||
try {
|
||||
applyDynamicFocusStyles(sheet, { fromExtension: sheet.href.toLowerCase().includes('scripts/extensions') });
|
||||
applyDynamicFocusStyles(sheet, { fromExtension: sheet.href?.toLowerCase().includes('scripts/extensions') == true });
|
||||
} catch (e) {
|
||||
console.warn('Failed to process stylesheet on initial load:', e);
|
||||
}
|
||||
|
@@ -3017,25 +3017,25 @@ async function generateComfyImage(prompt, negativePrompt) {
|
||||
const text = await workflowResponse.text();
|
||||
toastr.error(`Failed to load workflow.\n\n${text}`);
|
||||
}
|
||||
let workflow = (await workflowResponse.json()).replace('"%prompt%"', JSON.stringify(prompt));
|
||||
workflow = workflow.replace('"%negative_prompt%"', JSON.stringify(negativePrompt));
|
||||
let workflow = (await workflowResponse.json()).replaceAll('"%prompt%"', JSON.stringify(prompt));
|
||||
workflow = workflow.replaceAll('"%negative_prompt%"', JSON.stringify(negativePrompt));
|
||||
|
||||
const seed = extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
workflow = workflow.replaceAll('"%seed%"', JSON.stringify(seed));
|
||||
placeholders.forEach(ph => {
|
||||
workflow = workflow.replace(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
|
||||
workflow = workflow.replaceAll(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
|
||||
});
|
||||
(extension_settings.sd.comfy_placeholders ?? []).forEach(ph => {
|
||||
workflow = workflow.replace(`"%${ph.find}%"`, JSON.stringify(substituteParams(ph.replace)));
|
||||
workflow = workflow.replaceAll(`"%${ph.find}%"`, JSON.stringify(substituteParams(ph.replace)));
|
||||
});
|
||||
if (/%user_avatar%/gi.test(workflow)) {
|
||||
const response = await fetch(getUserAvatarUrl());
|
||||
if (response.ok) {
|
||||
const avatarBlob = await response.blob();
|
||||
const avatarBase64 = await getBase64Async(avatarBlob);
|
||||
workflow = workflow.replace('"%user_avatar%"', JSON.stringify(avatarBase64));
|
||||
workflow = workflow.replaceAll('"%user_avatar%"', JSON.stringify(avatarBase64));
|
||||
} else {
|
||||
workflow = workflow.replace('"%user_avatar%"', JSON.stringify(PNG_PIXEL));
|
||||
workflow = workflow.replaceAll('"%user_avatar%"', JSON.stringify(PNG_PIXEL));
|
||||
}
|
||||
}
|
||||
if (/%char_avatar%/gi.test(workflow)) {
|
||||
@@ -3043,9 +3043,9 @@ async function generateComfyImage(prompt, negativePrompt) {
|
||||
if (response.ok) {
|
||||
const avatarBlob = await response.blob();
|
||||
const avatarBase64 = await getBase64Async(avatarBlob);
|
||||
workflow = workflow.replace('"%char_avatar%"', JSON.stringify(avatarBase64));
|
||||
workflow = workflow.replaceAll('"%char_avatar%"', JSON.stringify(avatarBase64));
|
||||
} else {
|
||||
workflow = workflow.replace('"%char_avatar%"', JSON.stringify(PNG_PIXEL));
|
||||
workflow = workflow.replaceAll('"%char_avatar%"', JSON.stringify(PNG_PIXEL));
|
||||
}
|
||||
}
|
||||
console.log(`{
|
||||
|
@@ -178,8 +178,37 @@ async function loadGroupChat(chatId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function validateGroup(group) {
|
||||
if (!group) return;
|
||||
|
||||
// Validate that all members exist as characters
|
||||
let dirty = false;
|
||||
group.members = group.members.filter(member => {
|
||||
const character = characters.find(x => x.avatar === member || x.name === member);
|
||||
if (!character) {
|
||||
const msg = `Warning: Listed member ${member} does not exist as a character. It will be removed from the group.`;
|
||||
toastr.warning(msg, 'Group Validation');
|
||||
console.warn(msg);
|
||||
dirty = true;
|
||||
}
|
||||
return character;
|
||||
});
|
||||
|
||||
if (dirty) {
|
||||
await editGroup(group.id, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getGroupChat(groupId, reload = false) {
|
||||
const group = groups.find((x) => x.id === groupId);
|
||||
if (!group) {
|
||||
console.warn('Group not found', groupId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Run validation before any loading
|
||||
validateGroup(group);
|
||||
|
||||
const chat_id = group.chat_id;
|
||||
const data = await loadGroupChat(chat_id);
|
||||
let freshChat = false;
|
||||
@@ -197,7 +226,6 @@ export async function getGroupChat(groupId, reload = false) {
|
||||
if (group && Array.isArray(group.members)) {
|
||||
for (let member of group.members) {
|
||||
const character = characters.find(x => x.avatar === member || x.name === member);
|
||||
|
||||
if (!character) {
|
||||
continue;
|
||||
}
|
||||
@@ -219,10 +247,8 @@ export async function getGroupChat(groupId, reload = false) {
|
||||
freshChat = true;
|
||||
}
|
||||
|
||||
if (group) {
|
||||
let metadata = group.chat_metadata ?? {};
|
||||
updateChatMetadata(metadata, true);
|
||||
}
|
||||
let metadata = group.chat_metadata ?? {};
|
||||
updateChatMetadata(metadata, true);
|
||||
|
||||
if (reload) {
|
||||
select_group_chats(groupId, true);
|
||||
|
@@ -1,7 +1,5 @@
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
|
||||
|
||||
const ELEMENT_ID = 'loader';
|
||||
|
||||
/** @type {Popup} */
|
||||
let loaderPopup;
|
||||
|
||||
@@ -31,7 +29,7 @@ export async function hideLoader() {
|
||||
return new Promise((resolve) => {
|
||||
// Spinner blurs/fades out
|
||||
$('#load-spinner').on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () {
|
||||
$(`#${ELEMENT_ID}`).remove();
|
||||
$('#loader').remove();
|
||||
// Yoink preloader entirely; it only exists to cover up unstyled content while loading JS
|
||||
// If it's present, we remove it once and then it's gone.
|
||||
yoinkPreloader();
|
||||
|
@@ -254,7 +254,7 @@ function getCurrentSwipeId() {
|
||||
// For swipe macro, we are accepting using the message that is currently being swiped
|
||||
const mid = getLastMessageId({ exclude_swipe_in_propress: false });
|
||||
const swipeId = chat[mid]?.swipe_id;
|
||||
return swipeId ? swipeId + 1 : null;
|
||||
return swipeId !== null ? swipeId + 1 : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,7 +401,7 @@ function timeDiffReplace(input) {
|
||||
const time2 = moment(matchPart2);
|
||||
|
||||
const timeDifference = moment.duration(time1.diff(time2));
|
||||
return timeDifference.humanize();
|
||||
return timeDifference.humanize(true);
|
||||
});
|
||||
|
||||
return output;
|
||||
|
@@ -689,7 +689,7 @@ function formatWorldInfo(value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!oai_settings.wi_format) {
|
||||
if (!oai_settings.wi_format.trim()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@@ -40,7 +40,7 @@ import { tokenizers } from './tokenizers.js';
|
||||
import { BIAS_CACHE } from './logit-bias.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
|
||||
import { countOccurrences, debounce, delay, download, getFileText, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
|
||||
import { countOccurrences, debounce, delay, download, getFileText, getStringHash, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
|
||||
import { FILTER_TYPES } from './filters.js';
|
||||
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
@@ -335,6 +335,8 @@ const storage_keys = {
|
||||
compact_input_area: 'compact_input_area',
|
||||
auto_connect_legacy: 'AutoConnectEnabled',
|
||||
auto_load_chat_legacy: 'AutoLoadChatEnabled',
|
||||
|
||||
storyStringValidationCache: 'StoryStringValidationCache',
|
||||
};
|
||||
|
||||
const contextControls = [
|
||||
@@ -2105,6 +2107,9 @@ export function fuzzySearchGroups(searchValue) {
|
||||
*/
|
||||
export function renderStoryString(params) {
|
||||
try {
|
||||
// Validate and log possible warnings/errors
|
||||
validateStoryString(power_user.context.story_string, params);
|
||||
|
||||
// compile the story string template into a function, with no HTML escaping
|
||||
const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true });
|
||||
|
||||
@@ -2132,6 +2137,55 @@ export function renderStoryString(params) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the story string for possible warnings or issues
|
||||
*
|
||||
* @param {string} storyString - The story string
|
||||
* @param {Object} params - The story string parameters
|
||||
*/
|
||||
function validateStoryString(storyString, params) {
|
||||
/** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */
|
||||
const cache = JSON.parse(localStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} };
|
||||
|
||||
const hash = getStringHash(storyString);
|
||||
|
||||
// Initialize the cache for the current hash if it doesn't exist
|
||||
if (!cache.hashCache[hash]) {
|
||||
cache.hashCache[hash] = { fieldsWarned: {} };
|
||||
}
|
||||
|
||||
const currentCache = cache.hashCache[hash];
|
||||
const fieldsToWarn = [];
|
||||
|
||||
function validateMissingField(field, fallbackLegacyField = null) {
|
||||
const contains = storyString.includes(`{{${field}}}`) || (!!fallbackLegacyField && storyString.includes(`{{${fallbackLegacyField}}}`));
|
||||
if (!contains && params[field]) {
|
||||
const wasLogged = currentCache.fieldsWarned[field];
|
||||
if (!wasLogged) {
|
||||
fieldsToWarn.push(field);
|
||||
currentCache.fieldsWarned[field] = true;
|
||||
}
|
||||
console.warn(`The story string does not contain {{${field}}}, but it would contain content:\n`, params[field]);
|
||||
}
|
||||
}
|
||||
|
||||
validateMissingField('description');
|
||||
validateMissingField('personality');
|
||||
validateMissingField('persona');
|
||||
validateMissingField('scenario');
|
||||
validateMissingField('system');
|
||||
validateMissingField('wiBefore', 'loreBefore');
|
||||
validateMissingField('wiAfter', 'loreAfter');
|
||||
|
||||
if (fieldsToWarn.length > 0) {
|
||||
const fieldsList = fieldsToWarn.map(field => `{{${field}}}`).join(', ');
|
||||
toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation');
|
||||
}
|
||||
|
||||
localStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache));
|
||||
}
|
||||
|
||||
|
||||
const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);
|
||||
const compareFunc = (first, second) => {
|
||||
const a = first[power_user.sort_field];
|
||||
|
@@ -33,6 +33,7 @@ import {
|
||||
setCharacterName,
|
||||
setExtensionPrompt,
|
||||
setUserName,
|
||||
stopGeneration,
|
||||
substituteParams,
|
||||
system_avatar,
|
||||
system_message_types,
|
||||
@@ -898,6 +899,24 @@ export function initDefaultSlashCommands() {
|
||||
],
|
||||
helpString: 'Adds a swipe to the last chat message.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'stop',
|
||||
callback: () => {
|
||||
const stopped = stopGeneration();
|
||||
return String(stopped);
|
||||
},
|
||||
returns: 'true/false, whether the generation was running and got stopped',
|
||||
helpString: `
|
||||
<div>
|
||||
Stops the generation and any streaming if it is currently running.
|
||||
</div>
|
||||
<div>
|
||||
Note: This command cannot be executed from the chat input, as sending any message or script from there is blocked during generation.
|
||||
But it can be executed via automations or QR scripts/buttons.
|
||||
</div>
|
||||
`,
|
||||
aliases: ['generate-stop'],
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'abort',
|
||||
callback: abortCallback,
|
||||
|
@@ -263,6 +263,8 @@ export const setting_names = [
|
||||
'bypass_status_check',
|
||||
];
|
||||
|
||||
const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba');
|
||||
|
||||
export function validateTextGenUrl() {
|
||||
const selector = SERVER_INPUTS[settings.type];
|
||||
|
||||
@@ -1045,6 +1047,10 @@ export function isJsonSchemaSupported() {
|
||||
return [TABBY, LLAMACPP].includes(settings.type) && main_api === 'textgenerationwebui';
|
||||
}
|
||||
|
||||
function isDynamicTemperatureSupported() {
|
||||
return settings.dynatemp && DYNATEMP_BLOCK?.dataset?.tgType?.includes(settings.type);
|
||||
}
|
||||
|
||||
function getLogprobsNumber() {
|
||||
if (settings.type === VLLM || settings.type === INFERMATICAI) {
|
||||
return 5;
|
||||
@@ -1055,6 +1061,7 @@ function getLogprobsNumber() {
|
||||
|
||||
export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
|
||||
const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet';
|
||||
const dynatemp = isDynamicTemperatureSupported();
|
||||
const { banned_tokens, banned_strings } = getCustomTokenBans();
|
||||
|
||||
let params = {
|
||||
@@ -1063,7 +1070,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
'max_new_tokens': maxTokens,
|
||||
'max_tokens': maxTokens,
|
||||
'logprobs': power_user.request_token_probabilities ? getLogprobsNumber() : undefined,
|
||||
'temperature': settings.dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp,
|
||||
'temperature': dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp,
|
||||
'top_p': settings.top_p,
|
||||
'typical_p': settings.typical_p,
|
||||
'typical': settings.typical_p,
|
||||
@@ -1081,11 +1088,11 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
'length_penalty': settings.length_penalty,
|
||||
'early_stopping': settings.early_stopping,
|
||||
'add_bos_token': settings.add_bos_token,
|
||||
'dynamic_temperature': settings.dynatemp ? true : undefined,
|
||||
'dynatemp_low': settings.dynatemp ? settings.min_temp : undefined,
|
||||
'dynatemp_high': settings.dynatemp ? settings.max_temp : undefined,
|
||||
'dynatemp_range': settings.dynatemp ? (settings.max_temp - settings.min_temp) / 2 : undefined,
|
||||
'dynatemp_exponent': settings.dynatemp ? settings.dynatemp_exponent : undefined,
|
||||
'dynamic_temperature': dynatemp ? true : undefined,
|
||||
'dynatemp_low': dynatemp ? settings.min_temp : undefined,
|
||||
'dynatemp_high': dynatemp ? settings.max_temp : undefined,
|
||||
'dynatemp_range': dynatemp ? (settings.max_temp - settings.min_temp) / 2 : undefined,
|
||||
'dynatemp_exponent': dynatemp ? settings.dynatemp_exponent : undefined,
|
||||
'smoothing_factor': settings.smoothing_factor,
|
||||
'smoothing_curve': settings.smoothing_curve,
|
||||
'dry_allowed_length': settings.dry_allowed_length,
|
||||
|
@@ -803,7 +803,7 @@ export function getImageSizeFromDataURL(dataUrl) {
|
||||
|
||||
export function getCharaFilename(chid) {
|
||||
const context = getContext();
|
||||
const fileName = context.characters[chid ?? context.characterId].avatar;
|
||||
const fileName = context.characters[chid ?? context.characterId]?.avatar;
|
||||
|
||||
if (fileName) {
|
||||
return fileName.replace(/\.[^/.]+$/, '');
|
||||
|
Reference in New Issue
Block a user