Add backup/restore for Personas

This commit is contained in:
Cohee 2023-11-15 02:09:40 +02:00
parent 3143356523
commit 3c3594c52f
4 changed files with 125 additions and 15 deletions

View File

@ -3093,8 +3093,33 @@
</div>
<div class="drawer-content closedDrawer">
<div class="flex-container wide100p alignitemscenter spaceBetween">
<h3 class="margin0"><span data-i18n="Persona Management">Persona Management</span></h3>
<a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank" data-i18n="How do I use this?">How do I use this?</a>
<div class="flex-container alignItemsBaseline wide100p">
<div class="flex1 flex-container alignItemsBaseline">
<h3 class="margin0" data-i18n="Persona Management">Persona Management</h3>
<a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank" data-i18n="How do I use this?">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</div>
<div class="flex-container">
<div class="menu_button menu_button_icon user_stats_button" title="Click for stats!">
<i class="fa-solid fa-ranking-star"></i>
<span data-i18n="Usage Stats">Usage Stats</span>
</div>
<div id="personas_backup" class="menu_button menu_button_icon" title="Backup your personas to a file">
<i class="fa-solid fa-file-export"></i>
<span data-i18n="Backup">Backup</span>
</div>
<div id="personas_restore" class="menu_button menu_button_icon" title="Restore your personas from a file">
<i class="fa-solid fa-file-import"></i>
<span data-i18n="Restore">Restore</span>
</div>
<div id="create_dummy_persona" class="menu_button menu_button_icon" title="Create a dummy persona" data-i18n="[title]Create a dummy persona">
<i class="fa-solid fa-person-circle-question fa-fw"></i>
<span data-i18n="Create">Create</span>
</div>
<input id="personas_restore_input" type="file" accept=".json" hidden>
</div>
</div>
<div id="persona-management-block" class="flex-container wide100p">
<div class="flex1">
<h4 data-i18n="Name">Name</h4>
@ -3134,13 +3159,6 @@
<div class="flex1">
<h4 class="title_restorable">
<span data-i18n="Your Persona">Your Persona</span>
<div class="menu_button menu_button_icon user_stats_button" title="Click for stats!">
<i class="fa-solid fa-ranking-star"></i><span data-i18n="Usage Stats">Usage Stats</span>
</div>
<div id="create_dummy_persona" class="menu_button menu_button_icon" title="Create a dummy persona" data-i18n="[title]Create a dummy persona">
<i class="fa-solid fa-person-circle-question fa-fw"></i>
<span data-i18n="Create">Create</span>
</div>
</h4>
<div id="user_avatar_block">
<div class="avatar_upload">+</div>

View File

@ -5085,11 +5085,10 @@ export async function getUserAvatars() {
$("#user_avatar_block").append('<div class="avatar_upload">+</div>');
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;
}
}

View File

@ -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') {

View File

@ -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;
}