mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-02 12:26:59 +01:00
Add char to persona converter. Move persona functions to a separate script file
This commit is contained in:
parent
1bb2778f46
commit
030424d034
@ -82,6 +82,7 @@
|
||||
<script type="module" src="scripts/authors-note.js"></script>
|
||||
<script type="module" src="scripts/preset-manager.js"></script>
|
||||
<script type="module" src="scripts/filters.js"></script>
|
||||
<script type="module" src="scripts/personas.js"></script>
|
||||
|
||||
<title>SillyTavern</title>
|
||||
</head>
|
||||
@ -3197,6 +3198,9 @@
|
||||
<option id="set_chat_scenario" data-i18n="Scenario Override">
|
||||
Scenario Override
|
||||
</option>
|
||||
<option id="convert_to_persona" data-i18n="Convert to Persona">
|
||||
Convert to Persona
|
||||
</option>
|
||||
<option id="renameCharButton" data-i18n="Rename">
|
||||
Rename
|
||||
</option>
|
||||
|
372
public/script.js
372
public/script.js
@ -175,6 +175,7 @@ import {
|
||||
} from "./scripts/instruct-mode.js";
|
||||
import { applyLocale } from "./scripts/i18n.js";
|
||||
import { getTokenCount, getTokenizerModel, saveTokenCache } from "./scripts/tokenizers.js";
|
||||
import { initPersonas, selectCurrentPersona, setPersonaDescription } from "./scripts/personas.js";
|
||||
|
||||
//exporting functions and vars for mods
|
||||
export {
|
||||
@ -290,7 +291,6 @@ export const eventSource = new EventEmitter();
|
||||
setInterval(displayOverrideWarnings, 5000);
|
||||
// ...or when the chat changes
|
||||
eventSource.on(event_types.CHAT_CHANGED, displayOverrideWarnings);
|
||||
eventSource.on(event_types.CHAT_CHANGED, setChatLockedPersona);
|
||||
eventSource.on(event_types.MESSAGE_RECEIVED, processExtensionHelpers);
|
||||
eventSource.on(event_types.MESSAGE_SENT, processExtensionHelpers);
|
||||
|
||||
@ -653,7 +653,7 @@ var settings;
|
||||
export let koboldai_settings;
|
||||
export let koboldai_setting_names;
|
||||
var preset_settings = "gui";
|
||||
var user_avatar = "you.png";
|
||||
export let user_avatar = "you.png";
|
||||
export var amount_gen = 80; //default max length of AI generated responses
|
||||
var max_context = 2048;
|
||||
|
||||
@ -4574,7 +4574,7 @@ function changeMainAPI() {
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
async function getUserAvatars() {
|
||||
export async function getUserAvatars() {
|
||||
const response = await fetch("/getuseravatars", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
@ -4598,70 +4598,8 @@ async function getUserAvatars() {
|
||||
}
|
||||
}
|
||||
|
||||
function setPersonaDescription() {
|
||||
if (power_user.persona_description_position === persona_description_positions.AFTER_CHAR) {
|
||||
power_user.persona_description_position = persona_description_positions.IN_PROMPT;
|
||||
}
|
||||
|
||||
$("#persona_description").val(power_user.persona_description);
|
||||
$("#persona_description_position")
|
||||
.val(power_user.persona_description_position)
|
||||
.find(`option[value='${power_user.persona_description_position}']`)
|
||||
.attr("selected", true);
|
||||
countPersonaDescriptionTokens();
|
||||
}
|
||||
|
||||
function onPersonaDescriptionPositionInput() {
|
||||
power_user.persona_description_position = Number(
|
||||
$("#persona_description_position").find(":selected").val()
|
||||
);
|
||||
|
||||
if (power_user.personas[user_avatar]) {
|
||||
let object = power_user.persona_descriptions[user_avatar];
|
||||
|
||||
if (!object) {
|
||||
object = {
|
||||
description: power_user.persona_description,
|
||||
position: power_user.persona_description_position,
|
||||
};
|
||||
power_user.persona_descriptions[user_avatar] = object;
|
||||
}
|
||||
|
||||
object.position = power_user.persona_description_position;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of tokens in a persona description.
|
||||
*/
|
||||
const countPersonaDescriptionTokens = debounce(() => {
|
||||
const description = String($("#persona_description").val());
|
||||
const count = getTokenCount(description);
|
||||
$("#persona_description_token_count").text(String(count));
|
||||
}, durationSaveEdit);
|
||||
|
||||
function onPersonaDescriptionInput() {
|
||||
power_user.persona_description = String($("#persona_description").val());
|
||||
countPersonaDescriptionTokens();
|
||||
|
||||
if (power_user.personas[user_avatar]) {
|
||||
let object = power_user.persona_descriptions[user_avatar];
|
||||
|
||||
if (!object) {
|
||||
object = {
|
||||
description: power_user.persona_description,
|
||||
position: Number($("#persona_description_position").find(":selected").val()),
|
||||
};
|
||||
power_user.persona_descriptions[user_avatar] = object;
|
||||
}
|
||||
|
||||
object.description = power_user.persona_description;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function highlightSelectedAvatar() {
|
||||
$("#user_avatar_block").find(".avatar").removeClass("selected");
|
||||
@ -4710,124 +4648,13 @@ export function setUserName(value) {
|
||||
saveSettings("change_name");
|
||||
}
|
||||
|
||||
export function autoSelectPersona(name) {
|
||||
for (const [key, value] of Object.entries(power_user.personas)) {
|
||||
if (value === name) {
|
||||
console.log(`Auto-selecting persona ${key} for name ${name}`);
|
||||
$(`.avatar[imgfile="${key}"]`).trigger('click');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bindUserNameToPersona() {
|
||||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||||
|
||||
if (!avatarId) {
|
||||
console.warn('No avatar id found');
|
||||
return;
|
||||
}
|
||||
|
||||
const existingPersona = power_user.personas[avatarId];
|
||||
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>(If empty name is provided, this will unbind the name from this avatar)', 'input', existingPersona || '');
|
||||
|
||||
// If the user clicked cancel, don't do anything
|
||||
if (personaName === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (personaName.length > 0) {
|
||||
// If the user clicked ok and entered a name, bind the name to the persona
|
||||
console.log(`Binding persona ${avatarId} to name ${personaName}`);
|
||||
power_user.personas[avatarId] = personaName;
|
||||
const descriptor = power_user.persona_descriptions[avatarId];
|
||||
const isCurrentPersona = avatarId === user_avatar;
|
||||
|
||||
// Create a description object if it doesn't exist
|
||||
if (!descriptor) {
|
||||
// If the user is currently using this persona, set the description to the current description
|
||||
power_user.persona_descriptions[avatarId] = {
|
||||
description: isCurrentPersona ? power_user.persona_description : '',
|
||||
position: isCurrentPersona ? power_user.persona_description_position : persona_description_positions.IN_PROMPT,
|
||||
};
|
||||
}
|
||||
|
||||
// If the user is currently using this persona, update the name
|
||||
if (isCurrentPersona) {
|
||||
console.log(`Auto-updating user name to ${personaName}`);
|
||||
setUserName(personaName);
|
||||
}
|
||||
} else {
|
||||
// If the user clicked ok, but didn't enter a name, delete the persona
|
||||
console.log(`Unbinding persona ${avatarId}`);
|
||||
delete power_user.personas[avatarId];
|
||||
delete power_user.persona_descriptions[avatarId];
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
await getUserAvatars();
|
||||
setPersonaDescription();
|
||||
}
|
||||
|
||||
async function createDummyPersona() {
|
||||
const fetchResult = await fetch(default_avatar);
|
||||
const blob = await fetchResult.blob();
|
||||
const file = new File([blob], "avatar.png", { type: "image/png" });
|
||||
const formData = new FormData();
|
||||
formData.append("avatar", file);
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/uploaduseravatar",
|
||||
data: formData,
|
||||
beforeSend: () => { },
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (data) {
|
||||
await getUserAvatars();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function updateUserLockIcon() {
|
||||
const hasLock = !!chat_metadata['persona'];
|
||||
$('#lock_user_name').toggleClass('fa-unlock', !hasLock);
|
||||
$('#lock_user_name').toggleClass('fa-lock', hasLock);
|
||||
}
|
||||
|
||||
function setUserAvatar() {
|
||||
user_avatar = $(this).attr("imgfile");
|
||||
reloadUserAvatar();
|
||||
saveSettingsDebounced();
|
||||
highlightSelectedAvatar();
|
||||
|
||||
const personaName = power_user.personas[user_avatar];
|
||||
if (personaName && name1 !== personaName) {
|
||||
const lockedPersona = chat_metadata['persona'];
|
||||
if (lockedPersona && lockedPersona !== user_avatar && power_user.persona_show_notifications) {
|
||||
toastr.info(
|
||||
`To permanently set "${personaName}" as the selected persona, unlock and relock it using the "Lock" button. Otherwise, the selection resets upon reloading the chat.`,
|
||||
`This chat is locked to a different persona (${power_user.personas[lockedPersona]}).`,
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true },
|
||||
);
|
||||
}
|
||||
|
||||
setUserName(personaName);
|
||||
|
||||
const descriptor = power_user.persona_descriptions[user_avatar];
|
||||
|
||||
if (descriptor) {
|
||||
power_user.persona_description = descriptor.description;
|
||||
power_user.persona_description_position = descriptor.position;
|
||||
} else {
|
||||
power_user.persona_description = '';
|
||||
power_user.persona_description_position = persona_description_positions.IN_PROMPT;
|
||||
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT };
|
||||
}
|
||||
|
||||
setPersonaDescription();
|
||||
}
|
||||
selectCurrentPersona();
|
||||
}
|
||||
|
||||
async function uploadUserAvatar(e) {
|
||||
@ -4885,185 +4712,7 @@ async function uploadUserAvatar(e) {
|
||||
$("#form_upload_avatar").trigger("reset");
|
||||
}
|
||||
|
||||
async function setDefaultPersona() {
|
||||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||||
|
||||
if (!avatarId) {
|
||||
console.warn('No avatar id found');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentDefault = power_user.default_persona;
|
||||
|
||||
if (power_user.personas[avatarId] === undefined) {
|
||||
console.warn(`No persona name found for avatar ${avatarId}`);
|
||||
toastr.warning('You must bind a name to this persona before you can set it as the default.', 'Persona name not set');
|
||||
return;
|
||||
}
|
||||
|
||||
const personaName = power_user.personas[avatarId];
|
||||
|
||||
if (avatarId === currentDefault) {
|
||||
const confirm = await callPopup('Are you sure you want to remove the default persona?', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled removing default persona');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Removing default persona ${avatarId}`);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.info('This persona will no longer be used by default when you open a new chat.', `Default persona removed`);
|
||||
}
|
||||
delete power_user.default_persona;
|
||||
} else {
|
||||
const confirm = await callPopup(`<h3>Are you sure you want to set "${personaName}" as the default persona?</h3>
|
||||
This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.`, 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled setting default persona');
|
||||
return;
|
||||
}
|
||||
|
||||
power_user.default_persona = avatarId;
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.success('This persona will be used by default when you open a new chat.', `Default persona set to ${personaName}`);
|
||||
}
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
await getUserAvatars();
|
||||
}
|
||||
|
||||
async function deleteUserAvatar() {
|
||||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||||
|
||||
if (!avatarId) {
|
||||
console.warn('No avatar id found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (avatarId == user_avatar) {
|
||||
console.warn(`User tried to delete their current avatar ${avatarId}`);
|
||||
toastr.warning('You cannot delete the avatar you are currently using', 'Warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await callPopup('<h3>Are you sure you want to delete this avatar?</h3>All information associated with its linked persona will be lost.', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled deleting avatar');
|
||||
return;
|
||||
}
|
||||
|
||||
const request = await fetch("/deleteuseravatar", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
"avatar": avatarId,
|
||||
}),
|
||||
});
|
||||
|
||||
if (request.ok) {
|
||||
console.log(`Deleted avatar ${avatarId}`);
|
||||
delete power_user.personas[avatarId];
|
||||
delete power_user.persona_descriptions[avatarId];
|
||||
|
||||
if (avatarId === power_user.default_persona) {
|
||||
toastr.warning('The default persona was deleted. You will need to set a new default persona.', 'Default persona deleted');
|
||||
power_user.default_persona = null;
|
||||
}
|
||||
|
||||
if (avatarId === chat_metadata['persona']) {
|
||||
toastr.warning('The locked persona was deleted. You will need to set a new persona for this chat.', 'Persona deleted');
|
||||
delete chat_metadata['persona'];
|
||||
await saveMetadata();
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
await getUserAvatars();
|
||||
updateUserLockIcon();
|
||||
}
|
||||
}
|
||||
|
||||
async function lockUserNameToChat() {
|
||||
if (chat_metadata['persona']) {
|
||||
console.log(`Unlocking persona for this chat ${chat_metadata['persona']}`);
|
||||
delete chat_metadata['persona'];
|
||||
await saveMetadata();
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.info('User persona is now unlocked for this chat. Click the "Lock" again to revert.', 'Persona unlocked');
|
||||
}
|
||||
updateUserLockIcon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(user_avatar in power_user.personas)) {
|
||||
console.log(`Creating a new persona ${user_avatar}`);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.info(
|
||||
'Creating a new persona for currently selected user name and avatar...',
|
||||
'Persona not set for this avatar',
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, },
|
||||
);
|
||||
}
|
||||
power_user.personas[user_avatar] = name1;
|
||||
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT };
|
||||
}
|
||||
|
||||
chat_metadata['persona'] = user_avatar;
|
||||
await saveMetadata();
|
||||
saveSettingsDebounced();
|
||||
console.log(`Locking persona for this chat ${user_avatar}`);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.success(`User persona is locked to ${name1} in this chat`);
|
||||
}
|
||||
updateUserLockIcon();
|
||||
}
|
||||
|
||||
function setChatLockedPersona() {
|
||||
// Define a persona for this chat
|
||||
let chatPersona = '';
|
||||
|
||||
if (chat_metadata['persona']) {
|
||||
// If persona is locked in chat metadata, select it
|
||||
console.log(`Using locked persona ${chat_metadata['persona']}`);
|
||||
chatPersona = chat_metadata['persona'];
|
||||
} else if (power_user.default_persona) {
|
||||
// If default persona is set, select it
|
||||
console.log(`Using default persona ${power_user.default_persona}`);
|
||||
chatPersona = power_user.default_persona;
|
||||
}
|
||||
|
||||
// No persona set: user current settings
|
||||
if (!chatPersona) {
|
||||
console.debug('No default or locked persona set for this chat');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the avatar file
|
||||
const personaAvatar = $(`.avatar[imgfile="${chatPersona}"]`).trigger('click');
|
||||
|
||||
// Avatar missing (persona deleted)
|
||||
if (chat_metadata['persona'] && personaAvatar.length == 0) {
|
||||
console.warn('Persona avatar not found, unlocking persona');
|
||||
delete chat_metadata['persona'];
|
||||
updateUserLockIcon();
|
||||
return;
|
||||
}
|
||||
|
||||
// Default persona missing
|
||||
if (power_user.default_persona && personaAvatar.length == 0) {
|
||||
console.warn('Default persona avatar not found, clearing default persona');
|
||||
power_user.default_persona = null;
|
||||
saveSettingsDebounced();
|
||||
return;
|
||||
}
|
||||
|
||||
// Persona avatar found, select it
|
||||
personaAvatar.trigger('click');
|
||||
updateUserLockIcon();
|
||||
}
|
||||
|
||||
async function doOnboarding(avatarId) {
|
||||
const template = $('#onboarding_template .onboarding');
|
||||
@ -6142,7 +5791,7 @@ function hideSwipeButtons() {
|
||||
$("#chat").children().filter(`[mesid="${count_view_mes - 1}"]`).children('.swipe_left').css('display', 'none');
|
||||
}
|
||||
|
||||
async function saveMetadata() {
|
||||
export async function saveMetadata() {
|
||||
if (selected_group) {
|
||||
await editGroup(selected_group, true, false);
|
||||
}
|
||||
@ -8476,7 +8125,6 @@ $(document).ready(function () {
|
||||
setUserName($('#your_name').val());
|
||||
});
|
||||
|
||||
$("#create_dummy_persona").on('click', createDummyPersona);
|
||||
|
||||
$('#sync_name_button').on('click', async function () {
|
||||
const confirmation = await callPopup(`<h3>Are you sure?</h3>All user-sent messages in this chat will be attributed to ${name1}.`, 'confirm');
|
||||
@ -8518,13 +8166,6 @@ $(document).ready(function () {
|
||||
setTimeout(getStatusNovel, 10);
|
||||
});
|
||||
|
||||
$(document).on('click', '.bind_user_name', bindUserNameToPersona);
|
||||
$(document).on('click', '.delete_avatar', deleteUserAvatar);
|
||||
$(document).on('click', '.set_default_persona', setDefaultPersona);
|
||||
$('#lock_user_name').on('click', lockUserNameToChat);
|
||||
$('#persona_description').on('input', onPersonaDescriptionInput);
|
||||
$('#persona_description_position').on('input', onPersonaDescriptionPositionInput);
|
||||
|
||||
//**************************CHARACTER IMPORT EXPORT*************************//
|
||||
$("#character_import_button").click(function () {
|
||||
$("#character_import_file").click();
|
||||
@ -8902,6 +8543,8 @@ $(document).ready(function () {
|
||||
OF THE CHARACTER'S CHAT FILES.<br><br></b>`
|
||||
);
|
||||
break;*/
|
||||
default:
|
||||
eventSource.emit('charManagementDropdown', target);
|
||||
}
|
||||
$("#char-management-dropdown").prop('selectedIndex', 0);
|
||||
});
|
||||
@ -9111,4 +8754,5 @@ $(document).ready(function () {
|
||||
// Added here to prevent execution before script.js is loaded and get rid of quirky timeouts
|
||||
initAuthorsNote();
|
||||
initRossMods();
|
||||
initPersonas();
|
||||
});
|
||||
|
455
public/scripts/personas.js
Normal file
455
public/scripts/personas.js
Normal file
@ -0,0 +1,455 @@
|
||||
/**
|
||||
* 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";
|
||||
import { debounce, delay } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Uploads an avatar file to the server
|
||||
* @param {string} url URL for the avatar file
|
||||
* @param {string} [name] Optional name for the avatar file
|
||||
* @returns {Promise} Promise object representing the AJAX request
|
||||
*/
|
||||
async function uploadUserAvatar(url, name) {
|
||||
const fetchResult = await fetch(url);
|
||||
const blob = await fetchResult.blob();
|
||||
const file = new File([blob], "avatar.png", { type: "image/png" });
|
||||
const formData = new FormData();
|
||||
formData.append("avatar", file);
|
||||
|
||||
if (name) {
|
||||
formData.append("overwrite_name", name);
|
||||
}
|
||||
|
||||
return jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/uploaduseravatar",
|
||||
data: formData,
|
||||
beforeSend: () => { },
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function () {
|
||||
await getUserAvatars();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function createDummyPersona() {
|
||||
await uploadUserAvatar(default_avatar);
|
||||
}
|
||||
|
||||
async function convertCharacterToPersona() {
|
||||
const avatarUrl = characters[this_chid]?.avatar;
|
||||
|
||||
if (!avatarUrl) {
|
||||
console.log("No avatar found for this character");
|
||||
return;
|
||||
}
|
||||
|
||||
const name = characters[this_chid]?.name;
|
||||
let description = characters[this_chid]?.description;
|
||||
const overwriteName = `${name} (Persona).png`;
|
||||
|
||||
if (overwriteName in power_user.personas) {
|
||||
const confirmation = await callPopup("This character exists as a persona already. Are you sure want to overwrite it?", "confirm", "", { okButton: 'Yes' });
|
||||
|
||||
if (confirmation === false) {
|
||||
console.log("User cancelled the overwrite of the persona");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (description.includes('{{char}}') || description.includes('{{user}}')) {
|
||||
await delay(500);
|
||||
const confirmation = await callPopup("This character has a description that uses {{char}} or {{user}} macros. Do you want to swap them in the persona description?", "confirm", "", { okButton: 'Yes' });
|
||||
|
||||
if (confirmation) {
|
||||
description = description.replace(/{{char}}/gi, '{{personaChar}}').replace(/{{user}}/gi, '{{personaUser}}');
|
||||
description = description.replace(/{{personaUser}}/gi, '{{char}}').replace(/{{personaChar}}/gi, '{{user}}');
|
||||
}
|
||||
}
|
||||
|
||||
const thumbnailAvatar = getThumbnailUrl('avatar', avatarUrl);
|
||||
await uploadUserAvatar(thumbnailAvatar, overwriteName);
|
||||
|
||||
power_user.personas[overwriteName] = name;
|
||||
power_user.persona_descriptions[overwriteName] = {
|
||||
description: description,
|
||||
position: persona_description_positions.IN_PROMPT,
|
||||
};
|
||||
|
||||
// If the user is currently using this persona, update the description
|
||||
if (user_avatar === overwriteName) {
|
||||
power_user.persona_description = description;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
|
||||
console.log("Persona for character created");
|
||||
toastr.success(`You can now select ${name} as a persona in the Persona Management menu.`, 'Persona Created');
|
||||
|
||||
// Refresh the persona selector
|
||||
await getUserAvatars();
|
||||
// Reload the persona description
|
||||
setPersonaDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of tokens in a persona description.
|
||||
*/
|
||||
const countPersonaDescriptionTokens = debounce(() => {
|
||||
const description = String($("#persona_description").val());
|
||||
const count = getTokenCount(description);
|
||||
$("#persona_description_token_count").text(String(count));
|
||||
}, 1000);
|
||||
|
||||
export function setPersonaDescription() {
|
||||
if (power_user.persona_description_position === persona_description_positions.AFTER_CHAR) {
|
||||
power_user.persona_description_position = persona_description_positions.IN_PROMPT;
|
||||
}
|
||||
|
||||
$("#persona_description").val(power_user.persona_description);
|
||||
$("#persona_description_position")
|
||||
.val(power_user.persona_description_position)
|
||||
.find(`option[value='${power_user.persona_description_position}']`)
|
||||
.attr("selected", String(true));
|
||||
countPersonaDescriptionTokens();
|
||||
}
|
||||
|
||||
export function autoSelectPersona(name) {
|
||||
for (const [key, value] of Object.entries(power_user.personas)) {
|
||||
if (value === name) {
|
||||
console.log(`Auto-selecting persona ${key} for name ${name}`);
|
||||
$(`.avatar[imgfile="${key}"]`).trigger('click');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bindUserNameToPersona() {
|
||||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||||
|
||||
if (!avatarId) {
|
||||
console.warn('No avatar id found');
|
||||
return;
|
||||
}
|
||||
|
||||
const existingPersona = power_user.personas[avatarId];
|
||||
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>(If empty name is provided, this will unbind the name from this avatar)', 'input', existingPersona || '');
|
||||
|
||||
// If the user clicked cancel, don't do anything
|
||||
if (personaName === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (personaName.length > 0) {
|
||||
// If the user clicked ok and entered a name, bind the name to the persona
|
||||
console.log(`Binding persona ${avatarId} to name ${personaName}`);
|
||||
power_user.personas[avatarId] = personaName;
|
||||
const descriptor = power_user.persona_descriptions[avatarId];
|
||||
const isCurrentPersona = avatarId === user_avatar;
|
||||
|
||||
// Create a description object if it doesn't exist
|
||||
if (!descriptor) {
|
||||
// If the user is currently using this persona, set the description to the current description
|
||||
power_user.persona_descriptions[avatarId] = {
|
||||
description: isCurrentPersona ? power_user.persona_description : '',
|
||||
position: isCurrentPersona ? power_user.persona_description_position : persona_description_positions.IN_PROMPT,
|
||||
};
|
||||
}
|
||||
|
||||
// If the user is currently using this persona, update the name
|
||||
if (isCurrentPersona) {
|
||||
console.log(`Auto-updating user name to ${personaName}`);
|
||||
setUserName(personaName);
|
||||
}
|
||||
} else {
|
||||
// If the user clicked ok, but didn't enter a name, delete the persona
|
||||
console.log(`Unbinding persona ${avatarId}`);
|
||||
delete power_user.personas[avatarId];
|
||||
delete power_user.persona_descriptions[avatarId];
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
await getUserAvatars();
|
||||
setPersonaDescription();
|
||||
}
|
||||
|
||||
export function selectCurrentPersona() {
|
||||
const personaName = power_user.personas[user_avatar];
|
||||
if (personaName && name1 !== personaName) {
|
||||
const lockedPersona = chat_metadata['persona'];
|
||||
if (lockedPersona && lockedPersona !== user_avatar && power_user.persona_show_notifications) {
|
||||
toastr.info(
|
||||
`To permanently set "${personaName}" as the selected persona, unlock and relock it using the "Lock" button. Otherwise, the selection resets upon reloading the chat.`,
|
||||
`This chat is locked to a different persona (${power_user.personas[lockedPersona]}).`,
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true }
|
||||
);
|
||||
}
|
||||
|
||||
setUserName(personaName);
|
||||
|
||||
const descriptor = power_user.persona_descriptions[user_avatar];
|
||||
|
||||
if (descriptor) {
|
||||
power_user.persona_description = descriptor.description;
|
||||
power_user.persona_description_position = descriptor.position;
|
||||
} else {
|
||||
power_user.persona_description = '';
|
||||
power_user.persona_description_position = persona_description_positions.IN_PROMPT;
|
||||
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT };
|
||||
}
|
||||
|
||||
setPersonaDescription();
|
||||
}
|
||||
}
|
||||
|
||||
async function lockUserNameToChat() {
|
||||
if (chat_metadata['persona']) {
|
||||
console.log(`Unlocking persona for this chat ${chat_metadata['persona']}`);
|
||||
delete chat_metadata['persona'];
|
||||
await saveMetadata();
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.info('User persona is now unlocked for this chat. Click the "Lock" again to revert.', 'Persona unlocked');
|
||||
}
|
||||
updateUserLockIcon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(user_avatar in power_user.personas)) {
|
||||
console.log(`Creating a new persona ${user_avatar}`);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.info(
|
||||
'Creating a new persona for currently selected user name and avatar...',
|
||||
'Persona not set for this avatar',
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, },
|
||||
);
|
||||
}
|
||||
power_user.personas[user_avatar] = name1;
|
||||
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT };
|
||||
}
|
||||
|
||||
chat_metadata['persona'] = user_avatar;
|
||||
await saveMetadata();
|
||||
saveSettingsDebounced();
|
||||
console.log(`Locking persona for this chat ${user_avatar}`);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.success(`User persona is locked to ${name1} in this chat`);
|
||||
}
|
||||
updateUserLockIcon();
|
||||
}
|
||||
|
||||
async function deleteUserAvatar() {
|
||||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||||
|
||||
if (!avatarId) {
|
||||
console.warn('No avatar id found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (avatarId == user_avatar) {
|
||||
console.warn(`User tried to delete their current avatar ${avatarId}`);
|
||||
toastr.warning('You cannot delete the avatar you are currently using', 'Warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await callPopup('<h3>Are you sure you want to delete this avatar?</h3>All information associated with its linked persona will be lost.', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled deleting avatar');
|
||||
return;
|
||||
}
|
||||
|
||||
const request = await fetch("/deleteuseravatar", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
"avatar": avatarId,
|
||||
}),
|
||||
});
|
||||
|
||||
if (request.ok) {
|
||||
console.log(`Deleted avatar ${avatarId}`);
|
||||
delete power_user.personas[avatarId];
|
||||
delete power_user.persona_descriptions[avatarId];
|
||||
|
||||
if (avatarId === power_user.default_persona) {
|
||||
toastr.warning('The default persona was deleted. You will need to set a new default persona.', 'Default persona deleted');
|
||||
power_user.default_persona = null;
|
||||
}
|
||||
|
||||
if (avatarId === chat_metadata['persona']) {
|
||||
toastr.warning('The locked persona was deleted. You will need to set a new persona for this chat.', 'Persona deleted');
|
||||
delete chat_metadata['persona'];
|
||||
await saveMetadata();
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
await getUserAvatars();
|
||||
updateUserLockIcon();
|
||||
}
|
||||
}
|
||||
|
||||
function onPersonaDescriptionInput() {
|
||||
power_user.persona_description = String($("#persona_description").val());
|
||||
countPersonaDescriptionTokens();
|
||||
|
||||
if (power_user.personas[user_avatar]) {
|
||||
let object = power_user.persona_descriptions[user_avatar];
|
||||
|
||||
if (!object) {
|
||||
object = {
|
||||
description: power_user.persona_description,
|
||||
position: Number($("#persona_description_position").find(":selected").val()),
|
||||
};
|
||||
power_user.persona_descriptions[user_avatar] = object;
|
||||
}
|
||||
|
||||
object.description = power_user.persona_description;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onPersonaDescriptionPositionInput() {
|
||||
power_user.persona_description_position = Number(
|
||||
$("#persona_description_position").find(":selected").val()
|
||||
);
|
||||
|
||||
if (power_user.personas[user_avatar]) {
|
||||
let object = power_user.persona_descriptions[user_avatar];
|
||||
|
||||
if (!object) {
|
||||
object = {
|
||||
description: power_user.persona_description,
|
||||
position: power_user.persona_description_position,
|
||||
};
|
||||
power_user.persona_descriptions[user_avatar] = object;
|
||||
}
|
||||
|
||||
object.position = power_user.persona_description_position;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function setDefaultPersona() {
|
||||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||||
|
||||
if (!avatarId) {
|
||||
console.warn('No avatar id found');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentDefault = power_user.default_persona;
|
||||
|
||||
if (power_user.personas[avatarId] === undefined) {
|
||||
console.warn(`No persona name found for avatar ${avatarId}`);
|
||||
toastr.warning('You must bind a name to this persona before you can set it as the default.', 'Persona name not set');
|
||||
return;
|
||||
}
|
||||
|
||||
const personaName = power_user.personas[avatarId];
|
||||
|
||||
if (avatarId === currentDefault) {
|
||||
const confirm = await callPopup('Are you sure you want to remove the default persona?', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled removing default persona');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Removing default persona ${avatarId}`);
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.info('This persona will no longer be used by default when you open a new chat.', `Default persona removed`);
|
||||
}
|
||||
delete power_user.default_persona;
|
||||
} else {
|
||||
const confirm = await callPopup(`<h3>Are you sure you want to set "${personaName}" as the default persona?</h3>
|
||||
This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.`, 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled setting default persona');
|
||||
return;
|
||||
}
|
||||
|
||||
power_user.default_persona = avatarId;
|
||||
if (power_user.persona_show_notifications) {
|
||||
toastr.success('This persona will be used by default when you open a new chat.', `Default persona set to ${personaName}`);
|
||||
}
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
await getUserAvatars();
|
||||
}
|
||||
|
||||
function updateUserLockIcon() {
|
||||
const hasLock = !!chat_metadata['persona'];
|
||||
$('#lock_user_name').toggleClass('fa-unlock', !hasLock);
|
||||
$('#lock_user_name').toggleClass('fa-lock', hasLock);
|
||||
}
|
||||
|
||||
function setChatLockedPersona() {
|
||||
// Define a persona for this chat
|
||||
let chatPersona = '';
|
||||
|
||||
if (chat_metadata['persona']) {
|
||||
// If persona is locked in chat metadata, select it
|
||||
console.log(`Using locked persona ${chat_metadata['persona']}`);
|
||||
chatPersona = chat_metadata['persona'];
|
||||
} else if (power_user.default_persona) {
|
||||
// If default persona is set, select it
|
||||
console.log(`Using default persona ${power_user.default_persona}`);
|
||||
chatPersona = power_user.default_persona;
|
||||
}
|
||||
|
||||
// No persona set: user current settings
|
||||
if (!chatPersona) {
|
||||
console.debug('No default or locked persona set for this chat');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the avatar file
|
||||
const personaAvatar = $(`.avatar[imgfile="${chatPersona}"]`).trigger('click');
|
||||
|
||||
// Avatar missing (persona deleted)
|
||||
if (chat_metadata['persona'] && personaAvatar.length == 0) {
|
||||
console.warn('Persona avatar not found, unlocking persona');
|
||||
delete chat_metadata['persona'];
|
||||
updateUserLockIcon();
|
||||
return;
|
||||
}
|
||||
|
||||
// Default persona missing
|
||||
if (power_user.default_persona && personaAvatar.length == 0) {
|
||||
console.warn('Default persona avatar not found, clearing default persona');
|
||||
power_user.default_persona = null;
|
||||
saveSettingsDebounced();
|
||||
return;
|
||||
}
|
||||
|
||||
// Persona avatar found, select it
|
||||
personaAvatar.trigger('click');
|
||||
updateUserLockIcon();
|
||||
}
|
||||
|
||||
export function initPersonas() {
|
||||
$(document).on('click', '.bind_user_name', bindUserNameToPersona);
|
||||
$(document).on('click', '.set_default_persona', setDefaultPersona);
|
||||
$(document).on('click', '.delete_avatar', deleteUserAvatar);
|
||||
$('#lock_user_name').on('click', lockUserNameToChat);
|
||||
$("#create_dummy_persona").on('click', createDummyPersona);
|
||||
$('#persona_description').on('input', onPersonaDescriptionInput);
|
||||
$('#persona_description_position').on('input', onPersonaDescriptionPositionInput);
|
||||
|
||||
eventSource.on("charManagementDropdown", (target) => {
|
||||
if (target === 'convert_to_persona') {
|
||||
convertCharacterToPersona();
|
||||
}
|
||||
});
|
||||
eventSource.on(event_types.CHAT_CHANGED, setChatLockedPersona);
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
addOneMessage,
|
||||
autoSelectPersona,
|
||||
characters,
|
||||
chat,
|
||||
chat_metadata,
|
||||
@ -26,6 +25,7 @@ import { getMessageTimeStamp } from "./RossAscends-mods.js";
|
||||
import { resetSelectedGroup } from "./group-chats.js";
|
||||
import { getRegexedString, regex_placement } from "./extensions/regex/engine.js";
|
||||
import { chat_styles, power_user } from "./power-user.js";
|
||||
import { autoSelectPersona } from "./personas.js";
|
||||
export {
|
||||
executeSlashCommands,
|
||||
registerSlashCommand,
|
||||
|
Loading…
x
Reference in New Issue
Block a user