Merge branch 'dev' of https://github.com/BlipRanger/SillyTavern into feature/random

This commit is contained in:
BlipRanger
2023-06-27 20:18:55 -04:00
26 changed files with 1922 additions and 804 deletions

View File

@ -1,4 +1,4 @@
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp } from "./scripts/RossAscends-mods.js";
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile } from "./scripts/RossAscends-mods.js";
import { encode } from "../scripts/gpt-2-3-tokenizer/mod.js";
import { GPT3BrowserTokenizer } from "../scripts/gpt-3-tokenizer/gpt3-tokenizer.js";
import {
@ -30,6 +30,9 @@ import {
world_names,
world_info_character_strategy,
importEmbeddedWorldInfo,
checkEmbeddedWorld,
setWorldInfoButtonClass,
importWorldInfo,
} from "./scripts/world-info.js";
import {
@ -69,6 +72,8 @@ import {
formatInstructModeChat,
formatInstructStoryString,
formatInstructModePrompt,
persona_description_positions,
loadMovingUIState,
} from "./scripts/power-user.js";
import {
@ -128,6 +133,7 @@ import {
timestampToMoment,
download,
isDataURL,
getCharaFilename,
} from "./scripts/utils.js";
import { extension_settings, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js";
@ -151,7 +157,8 @@ import {
import { EventEmitter } from './scripts/eventemitter.js';
import { context_settings, loadContextTemplatesFromSettings } from "./scripts/context-template.js";
import { markdownExclusionExt } from "./scripts/showdown-exclusion.js";
import { setFloatingPrompt } from "./scripts/extensions/floating-prompt/index.js";
import { NOTE_MODULE_NAME, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/extensions/floating-prompt/index.js";
import { deviceInfo } from "./scripts/RossAscends-mods.js";
//exporting functions and vars for mods
export {
@ -222,6 +229,7 @@ export {
extension_prompt_types,
updateVisibleDivs,
mesForShowdownParse,
printCharacters,
}
// API OBJECT FOR EXTERNAL WIRING
@ -274,7 +282,7 @@ let is_mes_reload_avatar = false;
let optionsPopper = Popper.createPopper(document.getElementById('options_button'), document.getElementById('options'), {
placement: 'top-start'
});
let exportPopper = Popper.createPopper(document.getElementById('char-management-dropdown'), document.getElementById('export_format_popup'), {
let exportPopper = Popper.createPopper(document.getElementById('export_button'), document.getElementById('export_format_popup'), {
placement: 'left'
});
let rawPromptPopper = Popper.createPopper(document.getElementById('dialogue_popup'), document.getElementById('rawPromptPopup'), {
@ -287,13 +295,14 @@ let streamingProcessor = null;
let crop_data = undefined;
let is_delete_mode = false;
let fav_ch_checked = false;
let scrollLock = false;
//initialize global var for future cropped blobs
let currentCroppedAvatar = '';
const durationSaveEdit = 1000;
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
export const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
const getStatusDebounced = debounce(() => getStatus(), 300_000);
const saveChatDebounced = debounce(() => saveChatConditional(), durationSaveEdit);
@ -799,11 +808,14 @@ async function printCharacters() {
template.find('img').attr('src', this_avatar);
template.find('.avatar').attr('title', item.avatar);
template.find('.ch_name').text(item.name);
if (power_user.show_card_avatar_urls) {
template.find('.ch_avatar_url').text(item.avatar);
}
template.find('.ch_fav_icon').css("display", 'none');
template.toggleClass('is_fav', item.fav || item.fav == 'true');
template.find('.ch_fav').val(item.fav);
const description = item.data?.creator_notes || '';
const description = item.data?.creator_notes?.split('\n', 1)[0] || '';
if (description) {
template.find('.ch_description').text(description);
}
@ -1008,6 +1020,10 @@ function clearChat() {
count_view_mes = 0;
extension_prompts = {};
$("#chat").children().remove();
if ($('.zoomed_avatar[forChar]').length) {
console.debug('saw avatars to remove')
$('.zoomed_avatar[forChar]').remove();
} else { console.debug('saw no avatars') }
itemizedPrompts = [];
}
@ -1373,8 +1389,18 @@ function formatGenerationTimer(gen_started, gen_finished) {
function scrollChatToBottom() {
if (power_user.auto_scroll_chat_to_bottom) {
var $textchat = $("#chat");
$textchat.scrollTop(($textchat[0].scrollHeight));
const chatElement = $("#chat");
let position = chatElement[0].scrollHeight;
if (power_user.waifuMode) {
const lastMessage = chatElement.find('.mes').last();
if (lastMessage.length) {
const lastMessagePosition = lastMessage.position().top;
position = chatElement.scrollTop() + lastMessagePosition;
}
}
chatElement.scrollTop(position);
}
}
@ -1555,6 +1581,28 @@ function cleanGroupMessage(getMessage) {
return getMessage;
}
function getPersonaDescription(storyString) {
if (!power_user.persona_description) {
return storyString;
}
switch (power_user.persona_description_position) {
case persona_description_positions.BEFORE_CHAR:
return `${substituteParams(power_user.persona_description)}\n${storyString}`;
case persona_description_positions.AFTER_CHAR:
return `${storyString}\n${substituteParams(power_user.persona_description)}`;
default:
if (shouldWIAddPrompt) {
const originalAN = extension_prompts[NOTE_MODULE_NAME].value
const ANWithDesc = persona_description_positions.TOP_AN
? `${power_user.persona_description}\n${originalAN}`
: `${originalAN}\n${power_user.persona_description}`;
setExtensionPrompt(NOTE_MODULE_NAME, ANWithDesc, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
}
return storyString;
}
}
function getAllExtensionPrompts() {
const value = Object
.values(extension_prompts)
@ -1719,7 +1767,9 @@ class StreamingProcessor {
this.setFirstSwipe(messageId);
}
scrollChatToBottom();
if (!scrollLock) {
scrollChatToBottom();
}
}
onFinishStreaming(messageId, text) {
@ -1813,6 +1863,7 @@ class StreamingProcessor {
if (this.messageId == -1) {
this.messageId = this.onStartStreaming(this.firstMessageText);
await delay(1); // delay for message to be rendered
scrollLock = false;
}
try {
@ -1999,12 +2050,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
console.log(`Core/all messages: ${coreChat.length}/${chat.length}`);
}
if (main_api === 'openai') {
message_already_generated = ''; // OpenAI doesn't have multigen
setOpenAIMessages(coreChat);
setOpenAIMessageExamples(mesExamplesArray);
}
let storyString = "";
if (is_pygmalion) {
@ -2034,7 +2079,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
let chat2 = [];
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
// For OpenAI it's only used in WI
if (main_api == 'openai' && !world_info) {
if (main_api == 'openai' && (!world_info || world_info.length === 0)) {
console.debug('No WI, skipping chat2 for OAI');
break;
}
@ -2063,11 +2108,19 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
setFloatingPrompt();
// Add WI to prompt (and also inject WI to AN value via hijack)
let { worldInfoString, worldInfoBefore, worldInfoAfter } = await getWorldInfoPrompt(chat2, this_max_context);
// Add persona description to prompt
storyString = getPersonaDescription(storyString);
// Call combined AN into Generate
let allAnchors = getAllExtensionPrompts();
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
let zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
if (main_api === 'openai') {
message_already_generated = ''; // OpenAI doesn't have multigen
setOpenAIMessages(coreChat);
setOpenAIMessageExamples(mesExamplesArray);
}
// Moved here to not overflow the Poe context with added prompt bits
if (main_api == 'poe') {
allAnchors = appendPoeAnchors(type, allAnchors);
@ -3955,8 +4008,6 @@ function changeMainAPI() {
////////////////////////////////////////////////////
async function getUserAvatars() {
$("#user_avatar_block").html(""); //RossAscends: necessary to avoid doubling avatars each refresh.
$("#user_avatar_block").append('<div class="avatar_upload">+</div>');
const response = await fetch("/getuseravatars", {
method: "POST",
headers: getRequestHeaders(),
@ -3968,6 +4019,8 @@ async function getUserAvatars() {
const getData = await response.json();
//background = getData;
//console.log(getData.length);
$("#user_avatar_block").html(""); //RossAscends: necessary to avoid doubling avatars each refresh.
$("#user_avatar_block").append('<div class="avatar_upload">+</div>');
for (var i = 0; i < getData.length; i++) {
//console.log(1);
@ -3978,6 +4031,56 @@ async function getUserAvatars() {
}
}
function setPersonaDescription() {
$("#persona_description").val(power_user.persona_description);
$("#persona_description_position")
.val(power_user.persona_description_position)
.find(`option[value='${power_user.persona_description_position}']`)
.attr("selected", true);
}
function onPersonaDescriptionPositionInput() {
power_user.persona_description_position = Number(
$("#persona_description_position").find(":selected").val()
);
if (power_user.personas[user_avatar]) {
let object = power_user.persona_descriptions[user_avatar];
if (!object) {
object = {
description: power_user.persona_description,
position: power_user.persona_description_position,
};
power_user.persona_descriptions[user_avatar] = object;
}
object.position = power_user.persona_description_position;
}
saveSettingsDebounced();
}
function onPersonaDescriptionInput() {
power_user.persona_description = $("#persona_description").val();
if (power_user.personas[user_avatar]) {
let object = power_user.persona_descriptions[user_avatar];
if (!object) {
object = {
description: power_user.persona_description,
position: Number($("#persona_description_position").find(":selected").val()),
};
power_user.persona_descriptions[user_avatar] = object;
}
object.description = power_user.persona_description;
}
saveSettingsDebounced();
}
function highlightSelectedAvatar() {
$("#user_avatar_block").find(".avatar").removeClass("selected");
$("#user_avatar_block")
@ -4000,12 +4103,15 @@ function appendUserAvatar(name) {
highlightSelectedAvatar();
}
function reloadUserAvatar() {
function reloadUserAvatar(force = false) {
$(".mes").each(function () {
const avatarImg = $(this).find(".avatar img");
if (force) {
avatarImg.attr("src", avatarImg.attr("src"));
}
if ($(this).attr("is_user") == 'true' && $(this).attr('force_avatar') == 'false') {
$(this)
.find(".avatar img")
.attr("src", getUserAvatar(user_avatar));
avatarImg.attr("src", getUserAvatar(user_avatar));
}
});
}
@ -4054,9 +4160,20 @@ async function bindUserNameToPersona() {
// If the user clicked ok and entered a name, bind the name to the persona
console.log(`Binding persona ${avatarId} to name ${personaName}`);
power_user.personas[avatarId] = personaName;
const descriptor = power_user.persona_descriptions[avatarId];
const isCurrentPersona = avatarId === user_avatar;
// Create a description object if it doesn't exist
if (!descriptor) {
// If the user is currently using this persona, set the description to the current description
power_user.persona_descriptions[avatarId] = {
description: isCurrentPersona ? power_user.persona_description : '',
position: isCurrentPersona ? power_user.persona_description_position : persona_description_positions.BEFORE_CHAR,
};
}
// If the user is currently using this persona, update the name
if (avatarId === user_avatar) {
if (isCurrentPersona) {
console.log(`Auto-updating user name to ${personaName}`);
setUserName(personaName);
}
@ -4064,16 +4181,39 @@ async function bindUserNameToPersona() {
// If the user clicked ok, but didn't enter a name, delete the persona
console.log(`Unbinding persona ${avatarId}`);
delete power_user.personas[avatarId];
delete power_user.persona_descriptions[avatarId];
}
saveSettingsDebounced();
await getUserAvatars();
setPersonaDescription();
}
async function createDummyPersona() {
const fetchResult = await fetch(default_avatar);
const blob = await fetchResult.blob();
const file = new File([blob], "avatar.png", { type: "image/png" });
const formData = new FormData();
formData.append("avatar", file);
jQuery.ajax({
type: "POST",
url: "/uploaduseravatar",
data: formData,
beforeSend: () => { },
cache: false,
contentType: false,
processData: false,
success: async function (data) {
await getUserAvatars();
},
});
}
function updateUserLockIcon() {
const hasLock = !!chat_metadata['persona'];
$('#lock_user_name').toggleClass('fa-lock', !hasLock);
$('#lock_user_name').toggleClass('fa-unlock', hasLock);
$('#lock_user_name').toggleClass('fa-unlock', !hasLock);
$('#lock_user_name').toggleClass('fa-lock', hasLock);
}
function setUserAvatar() {
@ -4094,12 +4234,75 @@ function setUserAvatar() {
}
setUserName(personaName);
const descriptor = power_user.persona_descriptions[user_avatar];
if (descriptor) {
power_user.persona_description = descriptor.description;
power_user.persona_description_position = descriptor.position;
} else {
power_user.persona_description = '';
power_user.persona_description_position = persona_description_positions.BEFORE_CHAR;
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.BEFORE_CHAR };
}
setPersonaDescription();
}
}
async function setUserInfo() {
// TODO Replace with actual implementation
callPopup('This functionality is under development.<br>Please check back later.', 'text');
async function uploadUserAvatar(e) {
const file = e.target.files[0];
if (!file) {
$("#form_upload_avatar").trigger("reset");
return;
}
const formData = new FormData($("#form_upload_avatar").get(0));
const dataUrl = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = resolve;
reader.onerror = reject;
reader.readAsDataURL(file);
});
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
const confirmation = await callPopup(getCropPopup(dataUrl.target.result), 'avatarToCrop');
if (!confirmation) {
return;
}
let url = "/uploaduseravatar";
if (crop_data !== undefined) {
url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`;
}
jQuery.ajax({
type: "POST",
url: url,
data: formData,
beforeSend: () => { },
cache: false,
contentType: false,
processData: false,
success: async function () {
// If the user uploaded a new avatar, we want to make sure it's not cached
const name = formData.get("overwrite_name");
if (name) {
await fetch(getUserAvatar(name), { cache: "no-cache" });
reloadUserAvatar(true);
}
crop_data = undefined;
await getUserAvatars();
},
error: (jqXHR, exception) => { },
});
// Will allow to select the same file twice in a row
$("#form_upload_avatar").trigger("reset");
}
async function setDefaultPersona() {
@ -4162,7 +4365,7 @@ async function deleteUserAvatar() {
return;
}
const confirm = await callPopup('Are you sure you want to delete this avatar?', 'confirm');
const confirm = await callPopup('<h3>Are you sure you want to delete this avatar?</h3>All information associated with its linked persona will be lost.', 'confirm');
if (!confirm) {
console.debug('User cancelled deleting avatar');
@ -4180,6 +4383,7 @@ async function deleteUserAvatar() {
if (request.ok) {
console.log(`Deleted avatar ${avatarId}`);
delete power_user.personas[avatarId];
delete power_user.persona_descriptions[avatarId];
if (avatarId === power_user.default_persona) {
toastr.warning('The default persona was deleted. You will need to set a new default persona.', 'Default persona deleted');
@ -4216,6 +4420,7 @@ function lockUserNameToChat() {
{ timeOut: 10000, extendedTimeOut: 20000, },
);
power_user.personas[user_avatar] = name1;
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.BEFORE_CHAR };
}
chat_metadata['persona'] = user_avatar;
@ -4413,6 +4618,7 @@ async function getSettings(type) {
user_avatar = settings.user_avatar;
reloadUserAvatar();
highlightSelectedAvatar();
setPersonaDescription();
//Load the API server URL from settings
api_server = settings.api_server;
@ -4875,39 +5081,13 @@ export function select_selected_character(chid) {
$("#renameCharButton").css("display", "");
$('.open_alternate_greetings').data('chid', chid);
$('#set_character_world').data('chid', chid);
const world = characters[chid]?.data?.extensions?.world;
const worldSet = Boolean(world && world_names.includes(world));
$('#set_character_world').toggleClass('world_set', worldSet);
setWorldInfoButtonClass(chid);
checkEmbeddedWorld(chid);
$("#form_create").attr("actiontype", "editcharacter");
saveSettingsDebounced();
}
function checkEmbeddedWorld(chid) {
$('#import_character_info').hide();
if (chid === undefined) {
return;
}
if (characters[chid]?.data?.character_book) {
$('#import_character_info').data('chid', chid).show();
// Only show the alert once per character
const checkKey = `AlertWI_${characters[chid].avatar}`;
const worldName = characters[chid]?.data?.extensions?.world;
if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) {
toastr.info(
'To import and use it, select "Import Embedded World Info" in the Options dropdown menu on the character panel.',
`${characters[chid].name} has an embedded World/Lorebook`,
{ timeOut: 10000, extendedTimeOut: 20000, positionClass: 'toast-top-center' },
);
localStorage.setItem(checkKey, 1);
}
}
}
function select_rm_create() {
menu_type = "create";
@ -4956,7 +5136,7 @@ function select_rm_create() {
$("#name_div").addClass('displayBlock');
$('.open_alternate_greetings').data('chid', undefined);
$('#set_character_world').data('chid', undefined);
$('#set_character_world').toggleClass('world_set', !!create_save.world);
setWorldInfoButtonClass(undefined, !!create_save.world);
updateFavButtonState(false);
checkEmbeddedWorld();
@ -5339,7 +5519,7 @@ function openCharacterWorldPopup() {
}
function onSelectCharacterWorld() {
const value = $(this).find('option:selected').val();
const value = $('.character_world_info_selector').find('option:selected').val();
const worldIndex = value !== '' ? Number(value) : NaN;
const name = !isNaN(worldIndex) ? world_names[worldIndex] : '';
@ -5353,15 +5533,45 @@ function openCharacterWorldPopup() {
createOrEditCharacter();
}
$('#set_character_world').toggleClass('world_set', !!value);
setWorldInfoButtonClass(undefined, !!value);
}
function onExtraWorldInfoChanged() {
const selectedWorlds = $('.character_extra_world_info_selector').val();
let charLore = world_info.charLore ?? [];
// TODO: Maybe make this utility function not use the window context?
const fileName = getCharaFilename(chid);
const tempExtraBooks = selectedWorlds.map((index) => world_names[index]).filter((e) => e !== undefined);
const existingCharLore = charLore.find((e) => e.name === fileName);
if (existingCharLore) {
if (tempExtraBooks.length === 0) {
charLore.splice(existingCharLore, 1);
} else {
existingCharLore.extraBooks = tempExtraBooks;
}
} else {
const newCharLoreEntry = {
name: fileName,
extraBooks: tempExtraBooks
}
charLore.push(newCharLoreEntry);
}
Object.assign(world_info, { charLore: charLore });
saveSettingsDebounced();
}
const name = (menu_type == 'create' ? create_save.name : characters[chid]?.data?.name) || 'Nameless';
const worldId = (menu_type == 'create' ? create_save.world : characters[chid]?.data?.extensions?.world) || '';
const template = $('#character_world_template .character_world').clone();
const select = template.find('.character_world_info_selector');
const extraSelect = template.find('.character_extra_world_info_selector');
const name = (menu_type == 'create' ? create_save.name : characters[chid]?.data?.name) || 'Nameless';
const worldId = (menu_type == 'create' ? create_save.world : characters[chid]?.data?.extensions?.world) || '';
template.find('.character_name').text(name);
// Apped to base dropdown
world_names.forEach((item, i) => {
const option = document.createElement('option');
option.value = i;
@ -5370,7 +5580,47 @@ function openCharacterWorldPopup() {
select.append(option);
});
// Append to extras dropdown
if (world_names.length > 0) {
extraSelect.empty();
}
world_names.forEach((item, i) => {
const option = document.createElement('option');
option.value = i;
option.innerText = item;
const existingCharLore = world_info.charLore?.find((e) => e.name === getCharaFilename());
if (existingCharLore) {
option.selected = existingCharLore.extraBooks.includes(item);
} else {
option.selected = false;
}
extraSelect.append(option);
});
select.on('change', onSelectCharacterWorld);
extraSelect.on('mousedown change', async function (e) {
// If there's no world names, don't do anything
if (world_names.length === 0) {
e.preventDefault();
return;
}
let selectScrollTop = null;
if (deviceInfo && deviceInfo.device.type === 'desktop') {
e.preventDefault();
const option = $(e.target);
const selectElement = $(extraSelect)[0];
selectScrollTop = selectElement.scrollTop;
option.prop('selected', !option.prop('selected'));
await delay(1);
selectElement.scrollTop = selectScrollTop;
}
onExtraWorldInfoChanged();
});
callPopup(template, 'text');
}
@ -6078,7 +6328,13 @@ const isPwaMode = window.navigator.standalone;
if (isPwaMode) { $("body").addClass('PWA') }
$(document).ready(function () {
//////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
if (isMobile() === true) {
console.debug('hiding movingUI and sheldWidth toggles for mobile')
$("#sheldWidthToggleBlock").hide();
$("#movingUIModeCheckBlock").hide();
}
registerSlashCommand('dupe', DupeChar, [], " duplicates the currently selected character", true, true);
registerSlashCommand('api', connectAPISlash, [], " connect to an API", true, true);
@ -6093,11 +6349,11 @@ $(document).ready(function () {
updateVisibleDivs('#rm_print_characters_block', true);
}, 5));
// This does not actually increase performance.
/*$("#chat").on('scroll', debounce(() => {
updateVisibleDivs('#chat', false);
}, 10));*/
$("#chat").on('mousewheel', () => {
scrollLock = true;
});
//////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
let S_TAFocused = false;
let S_TAPreviouslyFocused = false;
$('#send_textarea').on('focusin focus click', () => {
@ -6113,7 +6369,8 @@ $(document).ready(function () {
});
$(document).click(event => {
if ($(':focus').attr('id') !== 'send_textarea') {
if (!$(event.target.id).is("#options_button, #send_but, #send_textarea, #option_regenerate")) {
var validIDs = ["options_button", "send_but", "send_textarea", "option_regenerate"];
if ($(event.target).attr('id') !== validIDs) {
S_TAFocused = false;
S_TAPreviouslyFocused = false;
}
@ -6265,56 +6522,21 @@ $(document).ready(function () {
$(document).on("click", "#user_avatar_block .avatar", setUserAvatar);
$(document).on("click", "#user_avatar_block .avatar_upload", function () {
$("#avatar_upload_file").click();
$("#avatar_upload_overwrite").val("");
$("#avatar_upload_file").trigger('click');
});
$("#avatar_upload_file").on("change", async function (e) {
const file = e.target.files[0];
$(document).on("click", "#user_avatar_block .set_persona_image", function () {
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
if (!file) {
if (!avatarId) {
console.log('no imgfile');
return;
}
const formData = new FormData($("#form_upload_avatar").get(0));
const dataUrl = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = resolve;
reader.onerror = reject;
reader.readAsDataURL(file);
});
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
const confirmation = await callPopup(getCropPopup(dataUrl.target.result), 'avatarToCrop');
if (!confirmation) {
return;
}
let url = "/uploaduseravatar";
if (crop_data !== undefined) {
url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`;
}
jQuery.ajax({
type: "POST",
url: url,
data: formData,
beforeSend: () => { },
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.path) {
appendUserAvatar(data.path);
}
crop_data = undefined;
},
error: (jqXHR, exception) => { },
});
// Will allow to select the same file twice in a row
$("#form_upload_avatar").trigger("reset");
$("#avatar_upload_overwrite").val(avatarId);
$("#avatar_upload_file").trigger('click');
});
$("#avatar_upload_file").on("change", uploadUserAvatar);
$(document).on("click", ".bg_example", async function () {
//when user clicks on a BG thumbnail...
@ -6558,17 +6780,17 @@ $(document).ready(function () {
$("#form_create").submit(createOrEditCharacter);
/* $("#delete_button").click(function () {
popup_type = "del_ch";
callPopup(`
$("#delete_button").on('click', function () {
popup_type = "del_ch";
callPopup(`
<h3>Delete the character?</h3>
<b>THIS IS PERMANENT!<br><br>
THIS WILL ALSO DELETE ALL<br>
OF THE CHARACTER'S CHAT FILES.<br><br></b>`
);
}); */
);
});
$("#rm_info_button").click(function () {
$("#rm_info_button").on('click', function () {
$("#rm_info_avatar").html("");
select_rm_characters();
});
@ -7281,6 +7503,8 @@ $(document).ready(function () {
setUserName($('#your_name').val());
});
$("#create_dummy_persona").on('click', createDummyPersona);
$('#sync_name_button').on('click', async function () {
const confirmation = await callPopup(`<h3>Are you sure?</h3>All user-sent messages in this chat will be attributed to ${name1}.`, 'confirm');
@ -7324,8 +7548,9 @@ $(document).ready(function () {
$(document).on('click', '.bind_user_name', bindUserNameToPersona);
$(document).on('click', '.delete_avatar', deleteUserAvatar);
$(document).on('click', '.set_default_persona', setDefaultPersona);
$(document).on('click', '.set_user_info', setUserInfo);
$('#lock_user_name').on('click', lockUserNameToChat);
$('#persona_description').on('input', onPersonaDescriptionInput);
$('#persona_description_position').on('input', onPersonaDescriptionPositionInput);
//**************************CHARACTER IMPORT EXPORT*************************//
$("#character_import_button").click(function () {
@ -7341,10 +7566,10 @@ $(document).ready(function () {
importCharacter(file);
}
});
/* $("#export_button").click(function (e) {
$('#export_format_popup').toggle();
exportPopper.update();
}); */
$("#export_button").on('click', function (e) {
$('#export_format_popup').toggle();
exportPopper.update();
});
$(document).on('click', '.export_format', async function () {
const format = $(this).data('format');
@ -7426,19 +7651,19 @@ $(document).ready(function () {
select_rm_characters();
});
/* $("#dupe_button").click(async function () {
$("#dupe_button").click(async function () {
const body = { avatar_url: characters[this_chid].avatar };
const response = await fetch('/dupecharacter', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(body),
});
if (response.ok) {
toastr.success("Character Duplicated");
getCharacters();
}
}); */
const body = { avatar_url: characters[this_chid].avatar };
const response = await fetch('/dupecharacter', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(body),
});
if (response.ok) {
toastr.success("Character Duplicated");
getCharacters();
}
});
$(document).on("click", ".select_chat_block, .bookmark_link, .mes_bookmark", async function () {
let file_name = $(this).hasClass('mes_bookmark')
@ -7476,12 +7701,15 @@ $(document).ready(function () {
$('.drawer-toggle').click(function () {
var icon = $(this).find('.drawer-icon');
var drawer = $(this).parent().find('.drawer-content');
if (drawer.hasClass('resizing')) { return }
var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
let targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
if (!drawerWasOpenAlready) {
$('.openDrawer').not('.pinnedOpen').slideToggle(200, "swing");
if (!drawerWasOpenAlready) { //to open the drawer
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, "swing", function () {
$(this).closest('.drawer-content').removeClass('resizing');
});
$('.openIcon').toggleClass('closedIcon openIcon');
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
icon.toggleClass('openIcon closedIcon');
@ -7489,29 +7717,36 @@ $(document).ready(function () {
//console.log(targetDrawerID);
if (targetDrawerID === 'right-nav-panel') {
$(this).closest('.drawer').find('.drawer-content').slideToggle({
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({
duration: 200,
easing: "swing",
start: function () {
jQuery(this).css('display', 'flex');
},
complete: function () {
$(this).closest('.drawer-content').removeClass('resizing');
$("#rm_print_characters_block").trigger("scroll");
}
})
} else {
$(this).closest('.drawer').find('.drawer-content').slideToggle(200, "swing");
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, "swing", function () {
$(this).closest('.drawer-content').removeClass('resizing');
});
}
} else if (drawerWasOpenAlready) {
} else if (drawerWasOpenAlready) { //to close
icon.toggleClass('closedIcon openIcon');
if (pinnedDrawerClicked) {
$(drawer).slideToggle(200, "swing");
$(drawer).addClass('resizing').slideToggle(200, "swing", function () {
$(this).removeClass('resizing');
});
}
else {
$('.openDrawer').not('.pinnedOpen').slideToggle(200, "swing");
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, "swing", function () {
$(this).closest('.drawer-content').removeClass('resizing');
});
}
drawer.toggleClass('closedDrawer openDrawer');
@ -7566,22 +7801,44 @@ $(document).ready(function () {
});
$(document).on('click', '.mes .avatar', function () {
console.log(isMobile());
console.log($('body').hasClass('waifuMode'));
if (isMobile() === true && !$('body').hasClass('waifuMode')) {
console.debug('saw mobile regular mode, returning');
return;
} else { console.debug('saw valid env for zoomed display') }
let thumbURL = $(this).children('img').attr('src');
let charsPath = '/characters/'
let targetAvatarImg = thumbURL.substring(thumbURL.lastIndexOf("=") + 1);
let charname = targetAvatarImg.replace('.png', '');
let avatarSrc = isDataURL(thumbURL) ? thumbURL : charsPath + targetAvatarImg;
console.log(avatarSrc);
if ($(this).parent().parent().attr('is_user') == 'true') { //handle user avatars
$("#zoomed_avatar").attr('src', thumbURL);
} else if ($(this).parent().parent().attr('is_system') == 'true') { //handle system avatars
$("#zoomed_avatar").attr('src', thumbURL);
} else if ($(this).parent().parent().attr('is_user') == 'false') { //handle char avatars
$("#zoomed_avatar").attr('src', avatarSrc);
}
$('#avatar_zoom_popup').toggle();
if ($(`.zoomed_avatar[forChar="${charname}"]`).length) {
console.debug('removing container as it already existed')
$(`.zoomed_avatar[forChar="${charname}"]`).remove();
} else {
console.debug('making new container from template')
const template = $('#zoomed_avatar_template').html();
const newElement = $(template);
newElement.attr('forChar', charname);
newElement.attr('id', `zoomFor_${charname}`);
newElement.find('.drag-grabber').attr('id', `zoomFor_${charname}header`);
//} else { return; }
$('body').append(newElement);
if ($(this).parent().parent().attr('is_user') == 'true') { //handle user avatars
$(`.zoomed_avatar[forChar="${charname}"] img`).attr('src', thumbURL);
} else if ($(this).parent().parent().attr('is_system') == 'true') { //handle system avatars
$(`.zoomed_avatar[forChar="${charname}"] img`).attr('src', thumbURL);
} else if ($(this).parent().parent().attr('is_user') == 'false') { //handle char avatars
$(`.zoomed_avatar[forChar="${charname}"] img`).attr('src', avatarSrc);
}
loadMovingUIState();
$(`.zoomed_avatar[forChar="${charname}"]`).css('display', 'block');
dragElement(newElement)
}
});
$(document).on('click', '#OpenAllWIEntries', function () {
@ -7636,18 +7893,19 @@ $(document).ready(function () {
case 'renameCharButton':
renameCharacter();
break;
case 'dupe_button':
/*case 'dupe_button':
DupeChar();
break;
case 'export_button':
$('#export_format_popup').toggle();
exportPopper.update();
break;
*/
case 'import_character_info':
await importEmbeddedWorldInfo();
saveCharacterDebounced();
break;
case 'delete_button':
/*case 'delete_button':
popup_type = "del_ch";
callPopup(`
<h3>Delete the character?</h3>
@ -7655,7 +7913,7 @@ $(document).ready(function () {
THIS WILL ALSO DELETE ALL<br>
OF THE CHARACTER'S CHAT FILES.<br><br></b>`
);
break;
break;*/
}
$("#char-management-dropdown").prop('selectedIndex', 0);
});
@ -7713,6 +7971,55 @@ $(document).ready(function () {
restoreCaretPosition($(this).get(0), caretPosition);
});
$('#external_import_button').on('click', async () => {
const html = `<h3>Enter the URL of the content to import</h3>
Supported sources:<br>
<ul class="justifyLeft">
<li>Chub characters (direct link or id)<br>Example: <tt>lorebooks/bartleby/example-lorebook</tt></li>
<li>Chub lorebooks (direct link or id)<br>Example: <tt>Anonymous/example-character</tt></li>
<li>More coming soon...</li>
<ul>`
const input = await callPopup(html, 'input');
if (!input) {
console.debug('Custom content import cancelled');
return;
}
const url = input.trim();
console.debug('Custom content import started', url);
const request = await fetch('/import_custom', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ url }),
});
if (!request.ok) {
toastr.info(request.statusText, 'Custom content import failed');
console.error('Custom content import failed', request.status, request.statusText);
return;
}
const data = await request.blob();
const customContentType = request.headers.get('X-Custom-Content-Type');
const fileName = request.headers.get('Content-Disposition').split('filename=')[1].replace(/"/g, '');
const file = new File([data], fileName, { type: data.type });
switch (customContentType) {
case 'character':
processDroppedFiles([file]);
break;
case 'lorebook':
await importWorldInfo(file);
break;
default:
toastr.warn('Unknown content type');
console.error('Unknown content type', customContentType);
break;
}
});
const $dropzone = $(document.body);
$dropzone.on('dragover', (event) => {