mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'dev' of https://github.com/BlipRanger/SillyTavern into feature/random
This commit is contained in:
613
public/script.js
613
public/script.js
@ -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) => {
|
||||
|
Reference in New Issue
Block a user