mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-02 12:26:59 +01:00
Add backup/restore for Personas
This commit is contained in:
parent
3143356523
commit
3c3594c52f
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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') {
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user