Merge branch 'staging' into timed-wi
This commit is contained in:
commit
6c9f3a868d
|
@ -3923,13 +3923,22 @@
|
|||
<h4 data-i18n="Character Handling">
|
||||
Character Handling
|
||||
</h4>
|
||||
<div title="If set in the advanced character definitions, this field will be displayed in the characters list." data-i18n="[title]If set in the advanced character definitions, this field will be displayed in the characters list.">
|
||||
<div class="flex-container alignitemscenter" title="If set in the advanced character definitions, this field will be displayed in the characters list." data-i18n="[title]If set in the advanced character definitions, this field will be displayed in the characters list.">
|
||||
<label for="aux_field"><small data-i18n="Char List Subheader">Char List Subheader</small></label>
|
||||
<select id="aux_field">
|
||||
<select id="aux_field" class="widthNatural flex1 margin0">
|
||||
<option data-i18n="Character Version" value="character_version">Character Version</option>
|
||||
<option data-i18n="Created by" value="creator">Created by</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-newbie-hidden class="flex-container alignitemscenter" title="Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog." data-i18n="[title]Defines on importing cards which action should be chosen for importing its listed tags. 'Ask' will always display the dialog.">
|
||||
<label for="tag_import_setting"><small data-i18n="Import Card Tags">Import Card Tags</small></label>
|
||||
<select id="tag_import_setting" class="widthNatural flex1 margin0">
|
||||
<option data-i18n="Ask" value="1">Ask</option>
|
||||
<option data-i18n="None" value="2">None</option>
|
||||
<option data-i18n="All" value="3">All</option>
|
||||
<option data-i18n="Existing" value="4">Existing</option>
|
||||
</select>
|
||||
</div>
|
||||
<label data-newbie-hidden class="checkbox_label" for="fuzzy_search_checkbox" title="Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring." data-i18n="[title]Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring">
|
||||
<input id="fuzzy_search_checkbox" type="checkbox" />
|
||||
<small data-i18n="Advanced Character Search">Advanced Character Search</small>
|
||||
|
@ -3950,10 +3959,6 @@
|
|||
<input id="show_card_avatar_urls" type="checkbox" />
|
||||
<small data-i18n="Show avatar filenames">Show avatar filenames</small>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="import_card_tags" title="Prompt to import embedded card tags on character import. Otherwise embedded tags are ignored." data-i18n="[title]Prompt to import embedded card tags on character import. Otherwise embedded tags are ignored">
|
||||
<input id="import_card_tags" type="checkbox" />
|
||||
<small data-i18n="Import Card Tags">Import Card Tags</small>
|
||||
</label>
|
||||
<label data-newbie-hidden class="checkbox_label" for="spoiler_free_mode" title="Hide character definitions from the editor panel behind a spoiler button." data-i18n="[title]Hide character definitions from the editor panel behind a spoiler button">
|
||||
<input id="spoiler_free_mode" type="checkbox" />
|
||||
<small data-i18n="Spoiler Free Mode">Spoiler Free Mode</small>
|
||||
|
@ -5493,7 +5498,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="character_template" class="template_element">
|
||||
<div class="character_select flex-container wide100p alignitemsflexstart" chid="" id="">
|
||||
<div class="character_select entity_block flex-container wide100p alignitemsflexstart" chid="" id="">
|
||||
<div class="avatar" title="">
|
||||
<img src="">
|
||||
</div>
|
||||
|
@ -5794,7 +5799,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="group_list_template" class="template_element">
|
||||
<div class="group_select flex-container wide100p alignitemsflexstart">
|
||||
<div class="group_select entity_block flex-container wide100p alignitemsflexstart">
|
||||
<div class="avatar">
|
||||
<img src="">
|
||||
</div>
|
||||
|
@ -5812,7 +5817,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="bogus_folder_template" class="template_element">
|
||||
<div class="bogus_folder_select flex-container wide100p alignitemsflexstart">
|
||||
<div class="bogus_folder_select entity_block flex-container wide100p alignitemsflexstart">
|
||||
<div class="avatar flex alignitemscenter textAlignCenter">
|
||||
<i class="bogus_folder_icon fa-solid fa-xl"></i>
|
||||
</div>
|
||||
|
|
|
@ -175,11 +175,12 @@ import {
|
|||
createTagMapFromList,
|
||||
renameTagKey,
|
||||
importTags,
|
||||
tag_filter_types,
|
||||
tag_filter_type,
|
||||
compareTagsForSort,
|
||||
initTags,
|
||||
applyTagsOnCharacterSelect,
|
||||
applyTagsOnGroupSelect,
|
||||
tag_import_setting,
|
||||
} from './scripts/tags.js';
|
||||
import {
|
||||
SECRET_KEYS,
|
||||
|
@ -1346,8 +1347,8 @@ export async function printCharacters(fullRefresh = false) {
|
|||
verifyCharactersSearchSortRule();
|
||||
|
||||
// We are actually always reprinting filters, as it "doesn't hurt", and this way they are always up to date
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
printTagFilters(tag_filter_type.character);
|
||||
printTagFilters(tag_filter_type.group_member);
|
||||
|
||||
// We are also always reprinting the lists on character/group edit window, as these ones doesn't get updated otherwise
|
||||
applyTagsOnCharacterSelect();
|
||||
|
@ -1368,7 +1369,7 @@ export async function printCharacters(fullRefresh = false) {
|
|||
nextText: '>',
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
showNavigator: true,
|
||||
callback: function (data) {
|
||||
callback: function (/** @type {Entity[]} */ data) {
|
||||
$(listId).empty();
|
||||
if (power_user.bogus_folders && isBogusFolderOpen()) {
|
||||
$(listId).append(getBackBlock());
|
||||
|
@ -1388,7 +1389,7 @@ export async function printCharacters(fullRefresh = false) {
|
|||
displayCount++;
|
||||
break;
|
||||
case 'tag':
|
||||
$(listId).append(getTagBlock(i.item, i.entities, i.hidden));
|
||||
$(listId).append(getTagBlock(i.item, i.entities, i.hidden, i.isUseless));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1442,8 +1443,9 @@ function verifyCharactersSearchSortRule() {
|
|||
* @property {Character|Group|import('./scripts/tags.js').Tag|*} item - The item
|
||||
* @property {string|number} id - The id
|
||||
* @property {'character'|'group'|'tag'} type - The type of this entity (character, group, tag)
|
||||
* @property {Entity[]} [entities] - An optional list of entities relevant for this item
|
||||
* @property {number} [hidden] - An optional number representing how many hidden entities this entity contains
|
||||
* @property {Entity[]?} [entities=null] - An optional list of entities relevant for this item
|
||||
* @property {number?} [hidden=null] - An optional number representing how many hidden entities this entity contains
|
||||
* @property {boolean?} [isUseless=null] - Specifies if the entity is useless (not relevant, but should still be displayed for consistency) and should be displayed greyed out
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1538,6 +1540,15 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
// Final step, updating some properties after the last filter run
|
||||
const nonTagEntitiesCount = entities.filter(entity => entity.type !== 'tag').length;
|
||||
for (const entity of entities) {
|
||||
if (entity.type === 'tag') {
|
||||
if (entity.entities?.length == nonTagEntitiesCount) entity.isUseless = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort before returning if requested
|
||||
if (doSort) {
|
||||
sortEntitiesList(entities);
|
||||
}
|
||||
|
@ -8464,7 +8475,7 @@ async function importCharacter(file, preserveFileName = false) {
|
|||
|
||||
await getCharacters();
|
||||
select_rm_info('char_import', data.file_name, oldSelectedChar);
|
||||
if (power_user.import_card_tags) {
|
||||
if (power_user.tag_import_setting !== tag_import_setting.NONE) {
|
||||
let currentContext = getContext();
|
||||
let avatarFileName = `${data.file_name}.png`;
|
||||
let importedCharacter = currentContext.characters.find(character => character.avatar === avatarFileName);
|
||||
|
@ -10599,7 +10610,7 @@ jQuery(async function () {
|
|||
}
|
||||
} break;
|
||||
case 'import_tags': {
|
||||
await importTags(characters[this_chid]);
|
||||
await importTags(characters[this_chid], { forceShow: true });
|
||||
} break;
|
||||
/*case 'delete_button':
|
||||
popup_type = "del_ch";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
|
||||
import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { callPopup, getRequestHeaders, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
|
||||
import { getMessageTimeStamp } from '../../RossAscends-mods.js';
|
||||
|
@ -272,7 +272,7 @@ async function getCaptionForFile(file, prompt, quiet) {
|
|||
try {
|
||||
setSpinnerIcon();
|
||||
const context = getContext();
|
||||
const fileData = await getBase64Async(file);
|
||||
const fileData = await getBase64Async(await ensureImageFormatSupported(file));
|
||||
const base64Format = fileData.split(',')[0].split(';')[0].split('/')[1];
|
||||
const base64Data = fileData.split(',')[1];
|
||||
const { caption } = await doCaptionRequest(base64Data, fileData, prompt);
|
||||
|
|
|
@ -28,6 +28,8 @@ export const POPUP_RESULT = {
|
|||
* @property {boolean?} [allowVerticalScrolling] - Whether to allow vertical scrolling in the popup
|
||||
* @property {POPUP_RESULT|number?} [defaultResult] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`.
|
||||
* @property {CustomPopupButton[]|string[]?} [customButtons] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward.
|
||||
* @property {(popup: Popup) => boolean?} [onClosing] - Handler called before the popup closes, return `false` to cancel the close
|
||||
* @property {(popup: Popup) => void?} [onClose] - Handler called after the popup closes, but before the DOM is cleaned up
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -78,6 +80,9 @@ export class Popup {
|
|||
/** @type {POPUP_RESULT|number?} */ defaultResult;
|
||||
/** @type {CustomPopupButton[]|string[]?} */ customButtons;
|
||||
|
||||
/** @type {(popup: Popup) => boolean?} */ onClosing;
|
||||
/** @type {(popup: Popup) => void?} */ onClose;
|
||||
|
||||
/** @type {POPUP_RESULT|number} */ result;
|
||||
/** @type {any} */ value;
|
||||
|
||||
|
@ -94,13 +99,17 @@ export class Popup {
|
|||
* @param {string} [inputValue=''] - The initial value of the input field
|
||||
* @param {PopupOptions} [options={}] - Additional options for the popup
|
||||
*/
|
||||
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null } = {}) {
|
||||
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null } = {}) {
|
||||
Popup.util.popups.push(this);
|
||||
|
||||
// Make this popup uniquely identifiable
|
||||
this.id = uuidv4();
|
||||
this.type = type;
|
||||
|
||||
// Utilize event handlers being passed in
|
||||
this.onClosing = onClosing;
|
||||
this.onClose = onClose;
|
||||
|
||||
/**@type {HTMLTemplateElement}*/
|
||||
const template = document.querySelector('#popup_template');
|
||||
// @ts-ignore
|
||||
|
@ -317,6 +326,12 @@ export class Popup {
|
|||
|
||||
this.value = value;
|
||||
this.result = result;
|
||||
|
||||
if (this.onClosing) {
|
||||
const shouldClose = this.onClosing(this);
|
||||
if (!shouldClose) return;
|
||||
}
|
||||
|
||||
Popup.util.lastResult = { value, result };
|
||||
this.hide();
|
||||
}
|
||||
|
@ -337,6 +352,11 @@ export class Popup {
|
|||
// Call the close on the dialog
|
||||
this.dlg.close();
|
||||
|
||||
// Run a possible custom handler right before DOM removal
|
||||
if (this.onClose) {
|
||||
this.onClose(this);
|
||||
}
|
||||
|
||||
// Remove it from the dom
|
||||
this.dlg.remove();
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ import {
|
|||
selectInstructPreset,
|
||||
} from './instruct-mode.js';
|
||||
|
||||
import { getTagsList, tag_map, tags } from './tags.js';
|
||||
import { getTagsList, tag_import_setting, tag_map, tags } from './tags.js';
|
||||
import { tokenizers } from './tokenizers.js';
|
||||
import { BIAS_CACHE } from './logit-bias.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
|
@ -198,6 +198,7 @@ let power_user = {
|
|||
trim_spaces: true,
|
||||
relaxed_api_urls: false,
|
||||
world_import_dialog: true,
|
||||
tag_import_setting: tag_import_setting.ASK,
|
||||
disable_group_trimming: false,
|
||||
single_line: false,
|
||||
|
||||
|
@ -1562,6 +1563,12 @@ function loadPowerUserSettings(settings, data) {
|
|||
power_user.tokenizer = tokenizers.GPT2;
|
||||
}
|
||||
|
||||
// Clean up old/legacy settings
|
||||
if (power_user.import_card_tags !== undefined) {
|
||||
power_user.tag_import_setting = power_user.import_card_tags ? tag_import_setting.ASK : tag_import_setting.NONE;
|
||||
delete power_user.import_card_tags;
|
||||
}
|
||||
|
||||
$('#single_line').prop('checked', power_user.single_line);
|
||||
$('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls);
|
||||
$('#world_import_dialog').prop('checked', power_user.world_import_dialog);
|
||||
|
@ -1590,7 +1597,6 @@ function loadPowerUserSettings(settings, data) {
|
|||
$('#zoomed_avatar_magnification').prop('checked', power_user.zoomed_avatar_magnification);
|
||||
$(`#tokenizer option[value="${power_user.tokenizer}"]`).attr('selected', true);
|
||||
$(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr('selected', true);
|
||||
$('#import_card_tags').prop('checked', power_user.import_card_tags);
|
||||
$('#confirm_message_delete').prop('checked', power_user.confirm_message_delete !== undefined ? !!power_user.confirm_message_delete : true);
|
||||
$('#spoiler_free_mode').prop('checked', power_user.spoiler_free_mode);
|
||||
$('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines);
|
||||
|
@ -1632,6 +1638,7 @@ function loadPowerUserSettings(settings, data) {
|
|||
$('#chat_width_slider').val(power_user.chat_width);
|
||||
$('#token_padding').val(power_user.token_padding);
|
||||
$('#aux_field').val(power_user.aux_field);
|
||||
$('#tag_import_setting').val(power_user.tag_import_setting);
|
||||
|
||||
$('#stscript_autocomplete_autoHide').prop('checked', power_user.stscript.autocomplete.autoHide ?? false).trigger('input');
|
||||
$('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy');
|
||||
|
@ -3516,11 +3523,6 @@ $(document).ready(() => {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#import_card_tags').on('input', function () {
|
||||
power_user.import_card_tags = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#confirm_message_delete').on('input', function () {
|
||||
power_user.confirm_message_delete = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
|
@ -3759,6 +3761,12 @@ $(document).ready(() => {
|
|||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#tag_import_setting').on('change', function () {
|
||||
const value = $(this).find(':selected').val();
|
||||
power_user.tag_import_setting = Number(value);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#stscript_autocomplete_autoHide').on('input', function () {
|
||||
power_user.stscript.autocomplete.autoHide = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
|
|
|
@ -2,7 +2,6 @@ import {
|
|||
characters,
|
||||
saveSettingsDebounced,
|
||||
this_chid,
|
||||
callPopup,
|
||||
menu_type,
|
||||
entitiesFilter,
|
||||
printCharactersDebounced,
|
||||
|
@ -21,11 +20,12 @@ 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 { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
|
||||
export {
|
||||
TAG_FOLDER_TYPES,
|
||||
|
@ -62,11 +62,20 @@ function getFilterHelper(listSelector) {
|
|||
return $(listSelector).is(GROUP_FILTER_SELECTOR) ? groupCandidatesFilter : entitiesFilter;
|
||||
}
|
||||
|
||||
export const tag_filter_types = {
|
||||
/** @enum {number} */
|
||||
export const tag_filter_type = {
|
||||
character: 0,
|
||||
group_member: 1,
|
||||
};
|
||||
|
||||
/** @enum {number} */
|
||||
export const tag_import_setting = {
|
||||
ASK: 1,
|
||||
NONE: 2,
|
||||
ALL: 3,
|
||||
ONLY_EXISTING: 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {{ FAV: Tag, GROUP: Tag, FOLDER: Tag, VIEW: Tag, HINT: Tag, UNFILTER: Tag }}
|
||||
* A collection of global actional tags for the filter panel
|
||||
|
@ -242,16 +251,23 @@ function isBogusFolder(tag) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a user is currently in a bogus folder.
|
||||
* Retrieves all currently open bogus folders
|
||||
*
|
||||
* @return {Tag[]} An array of open bogus folders
|
||||
*/
|
||||
function getOpenBogusFolders() {
|
||||
return entitiesFilter.getFilterData(FILTER_TYPES.TAG)?.selected
|
||||
.map(tagId => tags.find(x => x.id === tagId))
|
||||
.filter(isBogusFolder) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a user is currently in a bogus folder
|
||||
*
|
||||
* @returns {boolean} If currently viewing a folder
|
||||
*/
|
||||
function isBogusFolderOpen() {
|
||||
const anyIsFolder = entitiesFilter.getFilterData(FILTER_TYPES.TAG)?.selected
|
||||
.map(tagId => tags.find(x => x.id === tagId))
|
||||
.some(isBogusFolder);
|
||||
|
||||
return !!anyIsFolder;
|
||||
return getOpenBogusFolders().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -283,11 +299,12 @@ function chooseBogusFolder(source, tagId, remove = false) {
|
|||
* Builds the tag block for the specified item.
|
||||
*
|
||||
* @param {Tag} tag The tag item
|
||||
* @param {*} entities The list ob sub items for this tag
|
||||
* @param {*} hidden A count of how many sub items are hidden
|
||||
* @param {any[]} entities The list ob sub items for this tag
|
||||
* @param {number} hidden A count of how many sub items are hidden
|
||||
* @param {boolean} isUseless Whether the tag is useless (should be displayed greyed out)
|
||||
* @returns The html for the tag block
|
||||
*/
|
||||
function getTagBlock(tag, entities, hidden = 0) {
|
||||
function getTagBlock(tag, entities, hidden = 0, isUseless = false) {
|
||||
let count = entities.length;
|
||||
|
||||
const tagFolder = TAG_FOLDER_TYPES[tag.folder_type];
|
||||
|
@ -300,6 +317,7 @@ function getTagBlock(tag, entities, hidden = 0) {
|
|||
template.find('.bogus_folder_hidden_counter').text(hidden > 0 ? `${hidden} hidden` : '');
|
||||
template.find('.bogus_folder_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);
|
||||
template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon);
|
||||
if (isUseless) template.addClass('useless');
|
||||
|
||||
// Fill inline character images
|
||||
buildAvatarList(template.find('.bogus_folder_avatars_block'), entities);
|
||||
|
@ -660,15 +678,6 @@ function getExistingTags(newTags) {
|
|||
return existingTags;
|
||||
}
|
||||
|
||||
const tagImportSettings = {
|
||||
ALWAYS_IMPORT_ALL: 1,
|
||||
ONLY_IMPORT_EXISTING: 2,
|
||||
IMPORT_NONE: 3,
|
||||
ASK: 4,
|
||||
};
|
||||
|
||||
let globalTagImportSetting = tagImportSettings.ASK; // Default setting
|
||||
|
||||
const IMPORT_EXLCUDED_TAGS = ['ROOT', 'TAVERN'];
|
||||
const ANTI_TROLL_MAX_TAGS = 15;
|
||||
|
||||
|
@ -676,11 +685,13 @@ const ANTI_TROLL_MAX_TAGS = 15;
|
|||
* Imports tags for a given character
|
||||
*
|
||||
* @param {Character} character - The character
|
||||
* @param {object} [options] - Options
|
||||
* @param {boolean} [options.forceShow=false] - Whether to force showing the import dialog
|
||||
* @returns {Promise<boolean>} Boolean indicating whether any tag was imported
|
||||
*/
|
||||
async function importTags(character) {
|
||||
async function importTags(character, { forceShow = false } = {}) {
|
||||
// Gather the tags to import based on the selected setting
|
||||
const tagNamesToImport = await handleTagImport(character);
|
||||
const tagNamesToImport = await handleTagImport(character, { forceShow });
|
||||
if (!tagNamesToImport?.length) {
|
||||
toastr.info('No tags imported', 'Importing Tags');
|
||||
return;
|
||||
|
@ -698,9 +709,11 @@ async function importTags(character) {
|
|||
* Handles the import of tags for a given character and returns the resulting list of tags to add
|
||||
*
|
||||
* @param {Character} character - The character
|
||||
* @param {object} [options] - Options
|
||||
* @param {boolean} [options.forceShow=false] - Whether to force showing the import dialog
|
||||
* @returns {Promise<string[]>} Array of strings representing the tags to import
|
||||
*/
|
||||
async function handleTagImport(character) {
|
||||
async function handleTagImport(character, { forceShow = false } = {}) {
|
||||
/** @type {string[]} */
|
||||
const importTags = character.tags.map(t => t.trim()).filter(t => t)
|
||||
.filter(t => !IMPORT_EXLCUDED_TAGS.includes(t))
|
||||
|
@ -708,17 +721,22 @@ async function handleTagImport(character) {
|
|||
const existingTags = getExistingTags(importTags);
|
||||
const newTags = importTags.filter(t => !existingTags.some(existingTag => existingTag.name.toLowerCase() === t.toLowerCase()))
|
||||
.map(newTag);
|
||||
const folderTags = getOpenBogusFolders();
|
||||
|
||||
switch (globalTagImportSetting) {
|
||||
case tagImportSettings.ALWAYS_IMPORT_ALL:
|
||||
return existingTags.concat(newTags).map(t => t.name);
|
||||
case tagImportSettings.ONLY_IMPORT_EXISTING:
|
||||
return existingTags.map(t => t.name);
|
||||
case tagImportSettings.ASK:
|
||||
return await showTagImportPopup(character, existingTags, newTags);
|
||||
case tagImportSettings.IMPORT_NONE:
|
||||
default:
|
||||
// Choose the setting for this dialog. If from settings, verify the setting really exists, otherwise take "ASK".
|
||||
const setting = forceShow ? tag_import_setting.ASK
|
||||
: Object.values(tag_import_setting).find(setting => setting === power_user.tag_import_setting) ?? tag_import_setting.ASK;
|
||||
|
||||
switch (setting) {
|
||||
case tag_import_setting.ALL:
|
||||
return [...existingTags, ...newTags, ...folderTags].map(t => t.name);
|
||||
case tag_import_setting.ONLY_EXISTING:
|
||||
return [...existingTags, ...folderTags].map(t => t.name);
|
||||
case tag_import_setting.ASK:
|
||||
return await showTagImportPopup(character, existingTags, newTags, folderTags);
|
||||
case tag_import_setting.NONE:
|
||||
return [];
|
||||
default: throw new Error(`Invalid tag import setting: ${setting}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -728,63 +746,55 @@ async function handleTagImport(character) {
|
|||
* @param {Character} character - The character
|
||||
* @param {Tag[]} existingTags - List of existing tags
|
||||
* @param {Tag[]} newTags - List of new tags
|
||||
* @param {Tag[]} folderTags - List of tags in the current folder
|
||||
* @returns {Promise<string[]>} Array of strings representing the tags to import
|
||||
*/
|
||||
async function showTagImportPopup(character, existingTags, newTags) {
|
||||
async function showTagImportPopup(character, existingTags, newTags, folderTags) {
|
||||
/** @type {{[key: string]: import('./popup.js').CustomPopupButton}} */
|
||||
const importButtons = {
|
||||
EXISTING: { result: 2, text: 'Import Existing' },
|
||||
NONE: { result: 2, text: 'Import None', },
|
||||
ALL: { result: 3, text: 'Import All' },
|
||||
NONE: { result: 4, text: 'Import None' },
|
||||
EXISTING: { result: 4, text: 'Import Existing' },
|
||||
};
|
||||
const buttonSettingsMap = {
|
||||
[POPUP_RESULT.AFFIRMATIVE]: tag_import_setting.ASK,
|
||||
[importButtons.NONE.result]: tag_import_setting.NONE,
|
||||
[importButtons.ALL.result]: tag_import_setting.ALL,
|
||||
[importButtons.EXISTING.result]: tag_import_setting.ONLY_EXISTING,
|
||||
};
|
||||
|
||||
const customButtonsCaptions = Object.values(importButtons).map(button => `"${button.text}"`);
|
||||
const customButtonsString = customButtonsCaptions.slice(0, -1).join(', ') + ' or ' + customButtonsCaptions.slice(-1);
|
||||
|
||||
const popupContent = $(`
|
||||
<h3>Import Tags For ${character.name}</h3>
|
||||
<div class="import_avatar_placeholder"></div>
|
||||
<div class="import_tags_content justifyLeft">
|
||||
<small>
|
||||
Click remove on any tag to remove it from this import.<br />
|
||||
Select one of the import options to finish importing the tags.
|
||||
</small>
|
||||
|
||||
<h4 class="m-t-1">Existing Tags</h4>
|
||||
<div id="import_existing_tags_list" class="tags"></div>
|
||||
|
||||
<h4 class="m-t-1">New Tags</h4>
|
||||
<div id="import_new_tags_list" class="tags"></div>
|
||||
|
||||
<small>
|
||||
<label class="checkbox flex-container alignitemscenter flexNoGap m-t-3" for="import_remember_option">
|
||||
<input type="checkbox" id="import_remember_option" name="import_remember_option" />
|
||||
<span data-i18n="Remember my choice">
|
||||
Remember my choice
|
||||
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Remember the chosen import option\nIf ${customButtonsString} is selected, this dialog will not show up anymore.\nTo change this, go to the settings and modify "Tag Import Option".\n\nIf the "Import" option is chosen, the global setting will stay on "Ask"."
|
||||
title="Remember the chosen import option\nIf ${customButtonsString} is selected, this dialog will not show up anymore.\nTo change this, go to the settings and modify "Tag Import Option".\n\nIf the "Import" option is chosen, the global setting will stay on "Ask".">
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
</small>
|
||||
</div>`);
|
||||
const popupContent = $(await renderTemplateAsync('charTagImport', { charName: character.name }));
|
||||
|
||||
// Print tags after popup is shown, so that events can be added
|
||||
printTagList(popupContent.find('#import_existing_tags_list'), { tags: existingTags, tagOptions: { removable: true, removeAction: tag => removeFromArray(existingTags, tag) } });
|
||||
printTagList(popupContent.find('#import_new_tags_list'), { tags: newTags, tagOptions: { removable: true, removeAction: tag => removeFromArray(newTags, tag) } });
|
||||
printTagList(popupContent.find('#import_folder_tags_list'), { tags: folderTags, tagOptions: { removable: true, removeAction: tag => removeFromArray(folderTags, tag) } });
|
||||
|
||||
const result = await callGenericPopup(popupContent, POPUP_TYPE.TEXT, null, { wider: true, okButton: 'Import', cancelButton: true, customButtons: Object.values(importButtons) });
|
||||
if (folderTags.length === 0) popupContent.find('#folder_tags_block').hide();
|
||||
|
||||
function onCloseRemember(/** @type {Popup} */ popup) {
|
||||
const rememberCheckbox = document.getElementById('import_remember_option');
|
||||
if (rememberCheckbox instanceof HTMLInputElement && rememberCheckbox.checked) {
|
||||
const setting = buttonSettingsMap[popup.result];
|
||||
if (!setting) return;
|
||||
power_user.tag_import_setting = setting;
|
||||
$('#tag_import_setting').val(power_user.tag_import_setting);
|
||||
saveSettingsDebounced();
|
||||
console.log('Remembered tag import setting:', Object.entries(tag_import_setting).find(x => x[1] === setting)[0], setting);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await callGenericPopup(popupContent, POPUP_TYPE.TEXT, null, { wider: true, okButton: 'Import', cancelButton: true, customButtons: Object.values(importButtons), onClose: onCloseRemember });
|
||||
if (!result) {
|
||||
return [];
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case 1:
|
||||
case true:
|
||||
case importButtons.ALL.result: // Default 'Import' option where it imports all selected
|
||||
return existingTags.concat(newTags).map(t => t.name);
|
||||
case POPUP_RESULT.AFFIRMATIVE: // Default 'Import' option where it imports all selected
|
||||
case importButtons.ALL.result:
|
||||
return [...existingTags, ...newTags, ...folderTags].map(t => t.name);
|
||||
case importButtons.EXISTING.result:
|
||||
return existingTags.map(t => t.name);
|
||||
return [...existingTags, ...folderTags].map(t => t.name);
|
||||
case importButtons.NONE.result:
|
||||
default:
|
||||
return [];
|
||||
|
@ -1113,8 +1123,8 @@ function runTagFilters(listElement) {
|
|||
filterHelper.setFilterData(FILTER_TYPES.TAG, { excluded: excludedTagIds, selected: tagIds });
|
||||
}
|
||||
|
||||
function printTagFilters(type = tag_filter_types.character) {
|
||||
const FILTER_SELECTOR = type === tag_filter_types.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR;
|
||||
function printTagFilters(type = tag_filter_type.character) {
|
||||
const FILTER_SELECTOR = type === tag_filter_type.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR;
|
||||
$(FILTER_SELECTOR).empty();
|
||||
|
||||
// Print all action tags. (Rework 'Folder' button to some kind of onboarding if no folders are enabled yet)
|
||||
|
@ -1133,9 +1143,7 @@ function printTagFilters(type = tag_filter_types.character) {
|
|||
const bogusDrilldown = $(FILTER_SELECTOR).siblings('.rm_tag_bogus_drilldown');
|
||||
bogusDrilldown.empty();
|
||||
if (power_user.bogus_folders && bogusDrilldown.length > 0) {
|
||||
const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
|
||||
const navigatedTags = filterData.selected.map(x => tags.find(t => t.id == x)).filter(x => isBogusFolder(x));
|
||||
|
||||
const navigatedTags = getOpenBogusFolders();
|
||||
printTagList(bogusDrilldown, { tags: navigatedTags, tagOptions: { removable: true } });
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<h3>Import Tags For {{charName}}</h3>
|
||||
<div class="import_avatar_placeholder"></div>
|
||||
<div class="import_tags_content justifyLeft">
|
||||
<small data-i18n="Click remove on any tag to remove it from this import.<br />Select one of the import options to finish importing the tags.">
|
||||
Click remove on any tag to remove it from this import.<br />
|
||||
Select one of the import options to finish importing the tags.
|
||||
</small>
|
||||
|
||||
<h4 class="m-t-1" data-i18n="Existing Tags">Existing Tags</h4>
|
||||
<div id="import_existing_tags_list" class="tags" style="min-height: 20px;"></div>
|
||||
|
||||
<h4 class="m-t-1" data-i18n="New Tags">New Tags</h4>
|
||||
<div id="import_new_tags_list" class="tags" style="min-height: 20px;"></div>
|
||||
|
||||
<div id="folder_tags_block" class="m-t-1">
|
||||
<h4 data-i18n="Folder Tags">Folder Tags</h4>
|
||||
<small data-i18n="The following tags will be auto-imported based on the currently selected folders">
|
||||
The following tags will be auto-imported based on the currently selected folders
|
||||
</small>
|
||||
<div id="import_folder_tags_list" class="tags" style="margin-top: 5px;"></div>
|
||||
</div>
|
||||
|
||||
<small>
|
||||
<label class="checkbox flex-container alignitemscenter flexNoGap m-t-3" for="import_remember_option">
|
||||
<input type="checkbox" id="import_remember_option" name="import_remember_option" />
|
||||
<span data-i18n="Remember my choice">
|
||||
Remember my choice
|
||||
<div class="fa-solid fa-circle-info opacity50p"
|
||||
data-i18n="[title]Remember the chosen import option
If anything besides 'Cancel' is selected, this dialog will not show up anymore.
To change this, go to the settings and modify "Tag Import Option".

If the "Import" option is chosen, the global setting will stay on "Ask"."
|
||||
title="Remember the chosen import option
If anything besides 'Cancel' is selected, this dialog will not show up anymore.
To change this, go to the settings and modify "Tag Import Option".

If the "Import" option is chosen, the global setting will stay on "Ask".">
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
</small>
|
||||
</div>
|
|
@ -39,6 +39,8 @@ const OPENROUTER_PROVIDERS = [
|
|||
'Novita',
|
||||
'Lynn',
|
||||
'Lynn 2',
|
||||
'DeepSeek',
|
||||
'Infermatic',
|
||||
];
|
||||
|
||||
export async function loadOllamaModels(data) {
|
||||
|
|
|
@ -367,7 +367,12 @@ input[type='checkbox']:focus-visible {
|
|||
}
|
||||
|
||||
.img_enlarged_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.img_enlarged_container pre code,
|
||||
|
@ -2363,6 +2368,10 @@ input[type="file"] {
|
|||
padding: 1px;
|
||||
}
|
||||
|
||||
#rm_print_characters_block .entity_block.useless {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
#rm_ch_create_block {
|
||||
display: none;
|
||||
overflow-y: auto;
|
||||
|
@ -4479,7 +4488,7 @@ a {
|
|||
max-height: 100%;
|
||||
border-radius: 2px;
|
||||
border: 1px solid transparent;
|
||||
outline: 1px solid var(--SmartThemeBorderColor);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.cropper-container {
|
||||
|
|
|
@ -6,6 +6,7 @@ const fs = require('fs');
|
|||
const { jsonParser, urlencodedParser } = require('../express-common');
|
||||
const { getConfigValue, mergeObjectWithYaml, excludeKeysByYaml, trimV1 } = require('../util');
|
||||
const { setAdditionalHeaders } = require('../additional-headers');
|
||||
const { OPENROUTER_HEADERS } = require('../constants');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
@ -80,7 +81,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
|
|||
|
||||
if (request.body.api === 'openrouter') {
|
||||
apiUrl = 'https://openrouter.ai/api/v1/chat/completions';
|
||||
headers['HTTP-Referer'] = request.headers.referer;
|
||||
Object.assign(headers, OPENROUTER_HEADERS);
|
||||
}
|
||||
|
||||
if (request.body.api === 'openai') {
|
||||
|
|
|
@ -147,25 +147,36 @@ router.post('/searxng', jsonParser, async (request, response) => {
|
|||
|
||||
console.log('SearXNG query', baseUrl, query);
|
||||
|
||||
const url = new URL(baseUrl);
|
||||
const params = new URLSearchParams();
|
||||
params.append('q', query);
|
||||
params.append('format', 'html');
|
||||
url.pathname = '/search';
|
||||
url.search = params.toString();
|
||||
const mainPageUrl = new URL(baseUrl);
|
||||
const mainPageRequest = await fetch(mainPageUrl, { headers: visitHeaders });
|
||||
|
||||
const result = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: visitHeaders,
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
const text = await result.text();
|
||||
console.log('SearXNG request failed', result.statusText, text);
|
||||
if (!mainPageRequest.ok) {
|
||||
console.log('SearXNG request failed', mainPageRequest.statusText);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
const data = await result.text();
|
||||
const mainPageText = await mainPageRequest.text();
|
||||
const clientHref = mainPageText.match(/href="(\/client.+\.css)"/)?.[1];
|
||||
|
||||
if (clientHref) {
|
||||
const clientUrl = new URL(clientHref, baseUrl);
|
||||
await fetch(clientUrl, { headers: visitHeaders });
|
||||
}
|
||||
|
||||
const searchUrl = new URL('/search', baseUrl);
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append('q', query);
|
||||
searchUrl.search = searchParams.toString();
|
||||
|
||||
const searchResult = await fetch(searchUrl, { headers: visitHeaders });
|
||||
|
||||
if (!searchResult.ok) {
|
||||
const text = await searchResult.text();
|
||||
console.log('SearXNG request failed', searchResult.statusText, text);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
|
||||
const data = await searchResult.text();
|
||||
return response.send(data);
|
||||
} catch (error) {
|
||||
console.log('SearXNG request failed', error);
|
||||
|
|
Loading…
Reference in New Issue