Merge branch 'parser-followup-2' of https://github.com/LenAnderson/SillyTavern into parser-followup-2

This commit is contained in:
LenAnderson
2024-07-30 16:01:16 -04:00
25 changed files with 177 additions and 212 deletions

View File

@@ -101,6 +101,21 @@ const showPopupHelper = {
if (typeof result === 'string' || typeof result === 'boolean') throw new Error(`Invalid popup result. CONFIRM popups only support numbers, or null. Result: ${result}`);
return result;
},
/**
* Asynchronously displays a text popup with the given header and text, returning the clicked result button value.
*
* @param {string?} header - The header text for the popup.
* @param {string?} text - The main text for the popup.
* @param {PopupOptions} [popupOptions={}] - Options for the popup.
* @return {Promise<POPUP_RESULT>} A Promise that resolves with the result of the user's interaction.
*/
text: async (header, text, popupOptions = {}) => {
const content = PopupUtils.BuildTextWithHeader(header, text);
const popup = new Popup(content, POPUP_TYPE.TEXT, null, popupOptions);
const result = await popup.show();
if (typeof result === 'string' || typeof result === 'boolean') throw new Error(`Invalid popup result. TEXT popups only support numbers, or null. Result: ${result}`);
return result;
},
};
export class Popup {
@@ -511,14 +526,14 @@ export class Popup {
return this.#promise;
}
completeAffirmative() {
return this.complete(POPUP_RESULT.AFFIRMATIVE);
async completeAffirmative() {
return await this.complete(POPUP_RESULT.AFFIRMATIVE);
}
completeNegative() {
return this.complete(POPUP_RESULT.NEGATIVE);
async completeNegative() {
return await this.complete(POPUP_RESULT.NEGATIVE);
}
completeCancelled() {
return this.complete(POPUP_RESULT.CANCELLED);
async completeCancelled() {
return await this.complete(POPUP_RESULT.CANCELLED);
}
/**

View File

@@ -242,6 +242,7 @@ let power_user = {
example_separator: defaultExampleSeparator,
use_stop_strings: true,
allow_jailbreak: false,
names_as_stop_strings: true,
},
personas: {},
@@ -348,6 +349,7 @@ const contextControls = [
{ id: 'context_chat_start', property: 'chat_start', isCheckbox: false, isGlobalSetting: false },
{ id: 'context_use_stop_strings', property: 'use_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
{ id: 'context_allow_jailbreak', property: 'allow_jailbreak', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
{ id: 'context_names_as_stop_strings', property: 'names_as_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: true },
// Existing power user settings
{ id: 'always-force-name2-checkbox', property: 'always_force_name2', isCheckbox: true, isGlobalSetting: true, defaultValue: true },

View File

@@ -158,12 +158,12 @@ async function showSamplerSelectPopup() {
<div class="flex-container justifyCenter">
<h3>Sampler Select</h3>
<div class="flex-container alignItemsBaseline">
<div id="resetSelectedSamplers" class="menu_button menu_button_icon tag_view_create" title="Reset custom sampler selection">
<div id="resetSelectedSamplers" class="menu_button menu_button_icon" title="Reset custom sampler selection">
<i class="fa-solid fa-recycle"></i>
</div>
</div>
<!--<div class="flex-container alignItemsBaseline">
<div class="menu_button menu_button_icon tag_view_create" title="Create a new sampler">
<div class="menu_button menu_button_icon" title="Create a new sampler">
<i class="fa-solid fa-plus"></i>
<span data-i18n="Create">Create</span>
</div>

View File

@@ -21,7 +21,7 @@ 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 { isMobile } from './RossAscends-mods.js';
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { debounce_timeout } from './constants.js';
import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js';
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
@@ -1367,27 +1367,8 @@ function makeTagListDraggable(tagContainer) {
const id = $(tagElement).attr('id');
const tag = tags.find(x => x.id === id);
// Fix the defined colors, because if there is no color set, they seem to get automatically set to black
// based on the color picker after drag&drop, even if there was no color chosen. We just set them back.
const color = $(tagElement).find('.tagColorPickerHolder .tag-color').attr('color');
const color2 = $(tagElement).find('.tagColorPicker2Holder .tag-color2').attr('color');
if (color === '' || color === undefined) {
tag.color = '';
fixColor('background-color', tag.color);
}
if (color2 === '' || color2 === undefined) {
tag.color2 = '';
fixColor('color', tag.color2);
}
// Update the sort order
tag.sort_order = i;
function fixColor(property, color) {
$(tagElement).find('.tag_view_name').css(property, color);
$(`.tag[id="${id}"]`).css(property, color);
$(`.bogus_folder_select[tagid="${id}"] .avatar`).css(property, color);
}
});
// If tags were dragged manually, we have to disable auto sorting
@@ -1455,18 +1436,28 @@ async function onTagRestoreFileSelect(e) {
const data = await parseJsonFile(file);
if (!data) {
toastr.warning('Empty file data', 'Tag restore');
toastr.warning('Empty file data', 'Tag Restore');
console.log('Tag restore: File data empty.');
return;
}
if (!data.tags || !data.tag_map || !Array.isArray(data.tags) || typeof data.tag_map !== 'object') {
toastr.warning('Invalid file format', 'Tag restore');
toastr.warning('Invalid file format', 'Tag Restore');
console.log('Tag restore: Invalid file format.');
return;
}
// Prompt user if they want to overwrite existing tags
let overwrite = false;
if (tags.length > 0) {
const result = await Popup.show.confirm('Tag Restore', 'You have existing tags. If the backup contains any of those tags, do you want the backup to overwrite their settings (Name, color, folder state, etc)?',
{ okButton: 'Overwrite', cancelButton: 'Keep Existing' });
overwrite = result === POPUP_RESULT.AFFIRMATIVE;
}
const warnings = [];
/** @type {Map<string, string>} Map import tag ids with existing ids on overwrite */
const idToActualTagIdMap = new Map();
// Import tags
for (const tag of data.tags) {
@@ -1475,10 +1466,28 @@ async function onTagRestoreFileSelect(e) {
continue;
}
if (tags.find(x => x.id === tag.id)) {
warnings.push(`Tag with id ${tag.id} already exists.`);
// Check against both existing id (direct match) and tag with the same name, which is not allowed.
let existingTag = tags.find(x => x.id === tag.id);
if (existingTag && !overwrite) {
warnings.push(`Tag '${tag.name}' with id ${tag.id} already exists.`);
continue;
}
existingTag = getTag(tag.name);
if (existingTag && !overwrite) {
warnings.push(`Tag with name '${tag.name}' already exists.`);
// Remember the tag id, so we can still import the tag map entries for this
idToActualTagIdMap.set(tag.id, existingTag.id);
continue;
}
if (existingTag) {
// On overwrite, we remove and re-add the tag
removeFromArray(tags, existingTag);
// And remember the ID if it was different, so we can update the tag map accordingly
if (existingTag.id !== tag.id) {
idToActualTagIdMap.set(existingTag.id, tag.id);
}
}
tags.push(tag);
}
@@ -1497,30 +1506,39 @@ async function onTagRestoreFileSelect(e) {
const groupExists = groups.some(x => String(x.id) === String(key));
if (!characterExists && !groupExists) {
warnings.push(`Tag map key ${key} does not exist.`);
warnings.push(`Tag map key ${key} does not exist as character or group.`);
continue;
}
// Get existing tag ids for this key or empty array.
const existingTagIds = tag_map[key] || [];
// Merge existing and new tag ids. Remove duplicates.
tag_map[key] = existingTagIds.concat(tagIds).filter(onlyUnique);
// Merge existing and new tag ids. Replace the ones mapped to a new id. Remove duplicates.
const combinedTags = existingTagIds.concat(tagIds)
.map(tagId => (idToActualTagIdMap.has(tagId)) ? idToActualTagIdMap.get(tagId) : tagId)
.filter(onlyUnique);
// Verify that all tags exist. Remove tags that don't exist.
tag_map[key] = tag_map[key].filter(x => tags.some(y => String(y.id) === String(x)));
tag_map[key] = combinedTags.filter(tagId => tags.some(y => String(y.id) === String(tagId)));
}
if (warnings.length) {
toastr.success('Tags restored with warnings. Check console for details.');
toastr.warning('Tags restored with warnings. Check console or click on this message for details.', 'Tag Restore', {
timeOut: toastr.options.timeOut * 2, // Display double the time
onclick: () => Popup.show.text('Tag Restore Warnings', `<samp class="justifyLeft">${DOMPurify.sanitize(warnings.join('\n'))}<samp>`, { allowVerticalScrolling: true }),
});
console.warn(`TAG RESTORE REPORT\n====================\n${warnings.join('\n')}`);
} else {
toastr.success('Tags restored successfully.');
toastr.success('Tags restored successfully.', 'Tag Restore');
}
$('#tag_view_restore_input').val('');
printCharactersDebounced();
saveSettingsDebounced();
await onViewTagsListClick();
// Reprint the tag management popup, without having it to be opened again
const tagContainer = $('#tag_view_list .tag_view_list_tags');
printViewTagList(tagContainer);
}
function onBackupRestoreClick() {
@@ -1577,7 +1595,7 @@ function appendViewTagToList(list, tag, everything) {
const primaryColorPicker = $('<toolcool-color-picker></toolcool-color-picker>')
.addClass('tag-color')
.attr({ id: colorPickerId, color: tag.color || 'rgba(0, 0, 0, 0.3)', 'data-default-color': 'rgba(0, 0, 0, 0.3)' });
.attr({ id: colorPickerId, color: tag.color || 'rgba(0, 0, 0, 0.5)', 'data-default-color': 'rgba(0, 0, 0, 0.5)' });
const secondaryColorPicker = $('<toolcool-color-picker></toolcool-color-picker>')
.addClass('tag-color2')

View File

@@ -3584,7 +3584,7 @@ export async function getSortedEntries() {
// Parse decorators
entries = entries.map((entry) => {
const [decorators, content] = parseDecorators(entry.content);
const [decorators, content] = parseDecorators(entry.content || '');
return { ...entry, decorators, content };
});