diff --git a/public/index.html b/public/index.html index 49b2f9dd9..8f3689f9c 100644 --- a/public/index.html +++ b/public/index.html @@ -3093,8 +3093,33 @@
-

Persona Management

- How do I use this? +
+
+

Persona Management

+ + + +
+
+ + + + + +
+

Name

@@ -3134,13 +3159,6 @@

Your Persona - -

+
diff --git a/public/script.js b/public/script.js index cfe059837..030635bb4 100644 --- a/public/script.js +++ b/public/script.js @@ -5085,11 +5085,10 @@ export async function getUserAvatars() { $("#user_avatar_block").append('
+
'); for (var i = 0; i < getData.length; i++) { - //console.log(1); appendUserAvatar(getData[i]); } - //var aa = JSON.parse(getData[0]); - //const load_ch_coint = Object.getOwnPropertyNames(getData); + + return getData; } } diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 86cbc7826..cad220aaa 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -1,7 +1,7 @@ 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"; -import { debounce, delay } from "./utils.js"; +import { debounce, delay, download, parseJsonFile } from "./utils.js"; /** * Uploads an avatar file to the server @@ -486,6 +486,96 @@ function setChatLockedPersona() { updateUserLockIcon(); } +function onBackupPersonas() { + const timestamp = new Date().toISOString().split('T')[0].replace(/-/g, ''); + const filename = `personas_${timestamp}.json`; + const data = JSON.stringify({ + "personas": power_user.personas, + "persona_descriptions": power_user.persona_descriptions, + "default_persona": power_user.default_persona, + }, null, 2); + + const blob = new Blob([data], { type: 'application/json' }); + download(blob, filename, 'application/json'); +} + +async function onPersonasRestoreInput(e) { + const file = e.target.files[0]; + + if (!file) { + console.debug('No file selected'); + return; + } + + const data = await parseJsonFile(file); + + if (!data) { + toastr.warning('Invalid file selected', 'Persona Management'); + console.debug('Invalid file selected'); + return; + } + + if (!data.personas || !data.persona_descriptions || typeof data.personas !== 'object' || typeof data.persona_descriptions !== 'object') { + toastr.warning('Invalid file format', 'Persona Management'); + console.debug('Invalid file selected'); + return; + } + + const avatarsList = await getUserAvatars(); + const warnings = []; + + // Merge personas with existing ones + for (const [key, value] of Object.entries(data.personas)) { + if (key in power_user.personas) { + warnings.push(`Persona "${key}" (${value}) already exists, skipping`); + continue; + } + + power_user.personas[key] = value; + + // If the avatar is missing, upload it + if (!avatarsList.includes(key)) { + warnings.push(`Persona image "${key}" (${value}) is missing, uploading default avatar`); + await uploadUserAvatar(default_avatar, key); + } + } + + // Merge persona descriptions with existing ones + for (const [key, value] of Object.entries(data.persona_descriptions)) { + if (key in power_user.persona_descriptions) { + warnings.push(`Persona description for "${key}" (${power_user.personas[key]}) already exists, skipping`); + continue; + } + + if (!power_user.personas[key]) { + warnings.push(`Persona for "${key}" does not exist, skipping`); + continue; + } + + power_user.persona_descriptions[key] = value; + } + + if (data.default_persona) { + if (data.default_persona in power_user.personas) { + power_user.default_persona = data.default_persona; + } else { + warnings.push(`Default persona "${data.default_persona}" does not exist, skipping`); + } + } + + if (warnings.length) { + toastr.success('Personas restored with warnings. Check console for details.'); + console.warn(`PERSONA RESTORE REPORT\n====================\n${warnings.join('\n')}`); + } else { + toastr.success('Personas restored successfully.'); + } + + await getUserAvatars(); + setPersonaDescription(); + saveSettingsDebounced(); + $('#personas_restore_input').val(''); +} + export function initPersonas() { $(document).on('click', '.bind_user_name', bindUserNameToPersona); $(document).on('click', '.set_default_persona', setDefaultPersona); @@ -494,6 +584,9 @@ export function initPersonas() { $("#create_dummy_persona").on('click', createDummyPersona); $('#persona_description').on('input', onPersonaDescriptionInput); $('#persona_description_position').on('input', onPersonaDescriptionPositionInput); + $('#personas_backup').on('click', onBackupPersonas); + $('#personas_restore').on('click', () => $('#personas_restore_input').trigger('click')); + $('#personas_restore_input').on('change', onPersonasRestoreInput); eventSource.on("charManagementDropdown", (target) => { if (target === 'convert_to_persona') { diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 2cf83e20e..d989313b3 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -524,13 +524,13 @@ async function onTagRestoreFileSelect(e) { const data = await parseJsonFile(file); if (!data) { - toastr.warning('Tag restore: Empty file data.'); + toastr.warning('Empty file data', 'Tag restore'); console.log('Tag restore: File data empty.'); return; } if (!data.tags || !data.tag_map || !Array.isArray(data.tags) || typeof data.tag_map !== 'object') { - toastr.warning('Tag restore: Invalid file format.'); + toastr.warning('Invalid file format', 'Tag restore'); console.log('Tag restore: Invalid file format.'); return; }