mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-09 00:28:52 +01:00
Add backup/restore for Personas
This commit is contained in:
parent
3143356523
commit
3c3594c52f
@ -3093,8 +3093,33 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="drawer-content closedDrawer">
|
<div class="drawer-content closedDrawer">
|
||||||
<div class="flex-container wide100p alignitemscenter spaceBetween">
|
<div class="flex-container wide100p alignitemscenter spaceBetween">
|
||||||
<h3 class="margin0"><span data-i18n="Persona Management">Persona Management</span></h3>
|
<div class="flex-container alignItemsBaseline wide100p">
|
||||||
<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="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 id="persona-management-block" class="flex-container wide100p">
|
||||||
<div class="flex1">
|
<div class="flex1">
|
||||||
<h4 data-i18n="Name">Name</h4>
|
<h4 data-i18n="Name">Name</h4>
|
||||||
@ -3134,13 +3159,6 @@
|
|||||||
<div class="flex1">
|
<div class="flex1">
|
||||||
<h4 class="title_restorable">
|
<h4 class="title_restorable">
|
||||||
<span data-i18n="Your Persona">Your Persona</span>
|
<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>
|
</h4>
|
||||||
<div id="user_avatar_block">
|
<div id="user_avatar_block">
|
||||||
<div class="avatar_upload">+</div>
|
<div class="avatar_upload">+</div>
|
||||||
|
@ -5085,11 +5085,10 @@ export async function getUserAvatars() {
|
|||||||
$("#user_avatar_block").append('<div class="avatar_upload">+</div>');
|
$("#user_avatar_block").append('<div class="avatar_upload">+</div>');
|
||||||
|
|
||||||
for (var i = 0; i < getData.length; i++) {
|
for (var i = 0; i < getData.length; i++) {
|
||||||
//console.log(1);
|
|
||||||
appendUserAvatar(getData[i]);
|
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 { 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 { persona_description_positions, power_user } from "./power-user.js";
|
||||||
import { getTokenCount } from "./tokenizers.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
|
* Uploads an avatar file to the server
|
||||||
@ -486,6 +486,96 @@ function setChatLockedPersona() {
|
|||||||
updateUserLockIcon();
|
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() {
|
export function initPersonas() {
|
||||||
$(document).on('click', '.bind_user_name', bindUserNameToPersona);
|
$(document).on('click', '.bind_user_name', bindUserNameToPersona);
|
||||||
$(document).on('click', '.set_default_persona', setDefaultPersona);
|
$(document).on('click', '.set_default_persona', setDefaultPersona);
|
||||||
@ -494,6 +584,9 @@ export function initPersonas() {
|
|||||||
$("#create_dummy_persona").on('click', createDummyPersona);
|
$("#create_dummy_persona").on('click', createDummyPersona);
|
||||||
$('#persona_description').on('input', onPersonaDescriptionInput);
|
$('#persona_description').on('input', onPersonaDescriptionInput);
|
||||||
$('#persona_description_position').on('input', onPersonaDescriptionPositionInput);
|
$('#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) => {
|
eventSource.on("charManagementDropdown", (target) => {
|
||||||
if (target === 'convert_to_persona') {
|
if (target === 'convert_to_persona') {
|
||||||
|
@ -524,13 +524,13 @@ async function onTagRestoreFileSelect(e) {
|
|||||||
const data = await parseJsonFile(file);
|
const data = await parseJsonFile(file);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
toastr.warning('Tag restore: Empty file data.');
|
toastr.warning('Empty file data', 'Tag restore');
|
||||||
console.log('Tag restore: File data empty.');
|
console.log('Tag restore: File data empty.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.tags || !data.tag_map || !Array.isArray(data.tags) || typeof data.tag_map !== 'object') {
|
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.');
|
console.log('Tag restore: Invalid file format.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user