diff --git a/public/script.js b/public/script.js index 53e89cc6e..a2557028a 100644 --- a/public/script.js +++ b/public/script.js @@ -143,6 +143,7 @@ import { escapeRegex, resetScrollHeight, onlyUnique, + getBase64Async, } from "./scripts/utils.js"; import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, processExtensionHelpers, registerExtensionHelper, renderExtensionTemplate, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js"; @@ -184,7 +185,7 @@ import { } from "./scripts/instruct-mode.js"; import { applyLocale } from "./scripts/i18n.js"; import { getFriendlyTokenizerName, getTokenCount, getTokenizerModel, initTokenizers, saveTokenCache } from "./scripts/tokenizers.js"; -import { initPersonas, selectCurrentPersona, setPersonaDescription } from "./scripts/personas.js"; +import { createPersona, initPersonas, selectCurrentPersona, setPersonaDescription } from "./scripts/personas.js"; import { getBackgrounds, initBackgrounds } from "./scripts/backgrounds.js"; import { hideLoader, showLoader } from "./scripts/loader.js"; import { CharacterContextMenu, BulkEditOverlay } from "./scripts/BulkEditOverlay.js"; @@ -4785,22 +4786,21 @@ async function read_avatar_load(input) { create_save.avatar = input.files; } - const e = await new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = resolve; - reader.onerror = reject; - reader.readAsDataURL(input.files[0]); - }) + const file = input.files[0]; + const fileData = await getBase64Async(file); - $('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup'); + if (!power_user.never_resize_avatars) { + $('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup'); + const croppedImage = await callPopup(getCropPopup(fileData), 'avatarToCrop'); + if (!croppedImage) { + return; + } - const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop'); - if (!croppedImage) { - return; + $("#avatar_load_preview").attr("src", croppedImage); + } else { + $("#avatar_load_preview").attr("src", fileData); } - $("#avatar_load_preview").attr("src", croppedImage || e.target.result); - if (menu_type == "create") { return; } @@ -5162,24 +5162,19 @@ async function uploadUserAvatar(e) { } 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; - } - + const dataUrl = await getBase64Async(file); let url = "/uploaduseravatar"; - if (crop_data !== undefined) { - url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`; + if (!power_user.never_resize_avatars) { + $('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup'); + const confirmation = await callPopup(getCropPopup(dataUrl), 'avatarToCrop'); + if (!confirmation) { + return; + } + + if (crop_data !== undefined) { + url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`; + } } jQuery.ajax({ @@ -5190,7 +5185,7 @@ async function uploadUserAvatar(e) { cache: false, contentType: false, processData: false, - success: async function () { + success: async function (data) { // If the user uploaded a new avatar, we want to make sure it's not cached const name = formData.get("overwrite_name"); if (name) { @@ -5198,6 +5193,12 @@ async function uploadUserAvatar(e) { reloadUserAvatar(true); } + if (data.path) { + await getUserAvatars(); + await delay(500); + await createPersona(data.path); + } + crop_data = undefined; await getUserAvatars(); }, diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 8a3f0341d..86cbc7826 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -1,7 +1,3 @@ -/** - * This is a placeholder file for all the Persona Management code. Will be refactored into a separate file soon. - */ - import { callPopup, characters, chat_metadata, default_avatar, eventSource, event_types, getRequestHeaders, getThumbnailUrl, getUserAvatars, name1, saveMetadata, saveSettingsDebounced, setUserName, this_chid, user_avatar } from "../script.js"; import { persona_description_positions, power_user } from "./power-user.js"; import { getTokenCount } from "./tokenizers.js"; @@ -38,6 +34,28 @@ async function uploadUserAvatar(url, name) { }); } +/** + * Prompts the user to create a persona for the uploaded avatar. + * @param {string} avatarId User avatar id + * @returns {Promise} Promise that resolves when the persona is set + */ +export async function createPersona(avatarId) { + const personaName = await callPopup('

Enter a name for this persona:

Cancel if you\'re just uploading an avatar.', 'input', ''); + + if (!personaName) { + console.debug('User cancelled creating a persona'); + return; + } + + await delay(500); + const personaDescription = await callPopup('

Enter a description for this persona:

You can always add or change it later.', 'input', '', { rows: 4 }); + + initPersona(avatarId, personaName, personaDescription); + if (power_user.persona_show_notifications) { + toastr.success(`You can now pick ${personaName} as a persona in the Persona Management menu.`, 'Persona Created'); + } +} + async function createDummyPersona() { const personaName = await callPopup('

Enter a name for this persona:

', 'input', ''); @@ -48,18 +66,28 @@ async function createDummyPersona() { // Date + name (only ASCII) to make it unique const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`; + initPersona(avatarId, personaName, ''); + await uploadUserAvatar(default_avatar, avatarId); +} + +/** + * Initializes a persona for the given avatar id. + * @param {string} avatarId User avatar id + * @param {string} personaName Name for the persona + * @param {string} personaDescription Optional description for the persona + * @returns {void} + */ +export function initPersona(avatarId, personaName, personaDescription) { power_user.personas[avatarId] = personaName; power_user.persona_descriptions[avatarId] = { - description: '', + description: personaDescription || '', position: persona_description_positions.IN_PROMPT, }; - await uploadUserAvatar(default_avatar, avatarId); saveSettingsDebounced(); } export async function convertCharacterToPersona(characterId = null) { - if (null === characterId) characterId = this_chid; const avatarUrl = characters[characterId]?.avatar;