mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Add/expand persona auto selection from char / chat
- Persona selection on chat based on three levels: 1. Chat Locking 2. Char connected personas 3. Default persona - Add popup if multiple personas are connected to char - Add utility function to print persona avatar lists
This commit is contained in:
@ -6383,7 +6383,7 @@ export function buildAvatarList(block, entities, { templateId = 'inline_avatar_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
avatarTemplate.attr('data-type', entity.type);
|
avatarTemplate.attr('data-type', entity.type);
|
||||||
avatarTemplate.attr({ 'chid': id, 'id': `CharID${id}` });
|
avatarTemplate.attr('data-chid', id);
|
||||||
avatarTemplate.find('img').attr('src', this_avatar).attr('alt', entity.item.name);
|
avatarTemplate.find('img').attr('src', this_avatar).attr('alt', entity.item.name);
|
||||||
avatarTemplate.attr('title', `[Character] ${entity.item.name}\nFile: ${entity.item.avatar}`);
|
avatarTemplate.attr('title', `[Character] ${entity.item.name}\nFile: ${entity.item.avatar}`);
|
||||||
if (highlightFavs) {
|
if (highlightFavs) {
|
||||||
@ -6398,8 +6398,14 @@ export function buildAvatarList(block, entities, { templateId = 'inline_avatar_t
|
|||||||
avatarTemplate.addClass(grpTemplate.attr('class'));
|
avatarTemplate.addClass(grpTemplate.attr('class'));
|
||||||
avatarTemplate.empty();
|
avatarTemplate.empty();
|
||||||
avatarTemplate.append(grpTemplate.children());
|
avatarTemplate.append(grpTemplate.children());
|
||||||
|
avatarTemplate.attr({ 'data-grid': id, 'data-chid': null });
|
||||||
avatarTemplate.attr('title', `[Group] ${entity.item.name}`);
|
avatarTemplate.attr('title', `[Group] ${entity.item.name}`);
|
||||||
}
|
}
|
||||||
|
else if (entity.type === 'persona') {
|
||||||
|
avatarTemplate.attr({ 'data-pid': id, 'data-chid': null });
|
||||||
|
avatarTemplate.find('img').attr('src', getUserAvatar(entity.item.avatar));
|
||||||
|
avatarTemplate.attr('title', `[Persona] ${entity.item.name}\nFile: ${entity.item.avatar}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (interactable) {
|
if (interactable) {
|
||||||
avatarTemplate.addClass(INTERACTABLE_CONTROL_CLASS);
|
avatarTemplate.addClass(INTERACTABLE_CONTROL_CLASS);
|
||||||
|
@ -485,6 +485,32 @@ export function setPersonaDescription() {
|
|||||||
updatePersonaConnectionsAvatarList();
|
updatePersonaConnectionsAvatarList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a list of persona avatars and populates the given block element with them.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} block - The HTML element where the avatar list will be rendered
|
||||||
|
* @param {string[]} personas - An array of persona identifiers
|
||||||
|
* @param {Object} [options] - Optional settings for building the avatar list
|
||||||
|
* @param {boolean} [options.empty=true] - Whether to clear the block element before adding avatars
|
||||||
|
* @param {boolean} [options.interactable=false] - Whether the avatars should be interactable
|
||||||
|
* @param {boolean} [options.highlightFavs=true] - Whether to highlight favorite avatars
|
||||||
|
*/
|
||||||
|
export function buildPersonaAvatarList(block, personas, { empty = true, interactable = false, highlightFavs = true } = {}) {
|
||||||
|
const personaEntities = personas.map(avatar => ({
|
||||||
|
type: 'persona',
|
||||||
|
id: avatar,
|
||||||
|
item: {
|
||||||
|
name: power_user.personas[avatar],
|
||||||
|
description: power_user.persona_descriptions[avatar]?.description || '',
|
||||||
|
avatar: avatar,
|
||||||
|
fav: power_user.default_persona === avatar,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
buildAvatarList($(block), personaEntities, { empty: empty, interactable: interactable, highlightFavs: highlightFavs });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays avatar connections for the current persona.
|
* Displays avatar connections for the current persona.
|
||||||
* Converts connections to entities and populates the avatar list. Shows a message if no connections are found.
|
* Converts connections to entities and populates the avatar list. Shows a message if no connections are found.
|
||||||
@ -510,6 +536,42 @@ export function updatePersonaConnectionsAvatarList() {
|
|||||||
$('#persona_connections_list').text('[No connections]');
|
$('#persona_connections_list').text('[No connections]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a popup for persona selection and returns the selected persona.
|
||||||
|
*
|
||||||
|
* @param {string} title - The title to display in the popup
|
||||||
|
* @param {string} text - The text to display in the popup
|
||||||
|
* @param {string[]} personas - An array of persona ids to display for selection
|
||||||
|
* @returns {Promise<string?>} - A promise that resolves to the selected persona id or null if no selection was made
|
||||||
|
*/
|
||||||
|
export async function askForPersonaSelection(title, text, personas) {
|
||||||
|
const content = document.createElement('div');
|
||||||
|
const titleElement = document.createElement('h3');
|
||||||
|
titleElement.textContent = title;
|
||||||
|
content.appendChild(titleElement);
|
||||||
|
|
||||||
|
const textElement = document.createElement('div');
|
||||||
|
textElement.textContent = text;
|
||||||
|
content.appendChild(textElement);
|
||||||
|
|
||||||
|
const personaListBlock = document.createElement('div');
|
||||||
|
personaListBlock.classList.add('persona-list', 'avatars_inline', 'flex-container');
|
||||||
|
content.appendChild(personaListBlock);
|
||||||
|
|
||||||
|
buildPersonaAvatarList(personaListBlock, personas, { interactable: true });
|
||||||
|
|
||||||
|
// Make the persona blocks clickable and close the popup
|
||||||
|
personaListBlock.querySelectorAll('.avatar[data-type="persona"]').forEach(block => {
|
||||||
|
if (!(block instanceof HTMLElement)) return;
|
||||||
|
block.dataset.result = String(100 + personas.indexOf(block.dataset.pid));
|
||||||
|
});
|
||||||
|
|
||||||
|
const popup = new Popup(content, POPUP_TYPE.TEXT, '', {});
|
||||||
|
const result = await popup.show();
|
||||||
|
return Number(result) > 100 ? personas[Number(result) - 100] : null;
|
||||||
|
}
|
||||||
|
|
||||||
export function autoSelectPersona(name) {
|
export function autoSelectPersona(name) {
|
||||||
for (const [key, value] of Object.entries(power_user.personas)) {
|
for (const [key, value] of Object.entries(power_user.personas)) {
|
||||||
if (value === name) {
|
if (value === name) {
|
||||||
@ -1068,34 +1130,59 @@ function updatePersonaLockIcons() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setChatLockedPersona() {
|
async function setChatLockedPersona() {
|
||||||
|
// Cache persona list to check if they exist
|
||||||
|
const userAvatars = await getUserAvatars(false);
|
||||||
|
|
||||||
// Define a persona for this chat
|
// Define a persona for this chat
|
||||||
let chatPersona = '';
|
let chatPersona = '';
|
||||||
|
|
||||||
if (chat_metadata['persona']) {
|
|
||||||
// If persona is locked in chat metadata, select it
|
// If persona is locked in chat metadata, select it
|
||||||
|
if (chat_metadata['persona']) {
|
||||||
console.log(`Using locked persona ${chat_metadata['persona']}`);
|
console.log(`Using locked persona ${chat_metadata['persona']}`);
|
||||||
chatPersona = chat_metadata['persona'];
|
chatPersona = chat_metadata['persona'];
|
||||||
} else if (power_user.default_persona) {
|
|
||||||
// If default persona is set, select it
|
// Verify it exists
|
||||||
|
if (!userAvatars.includes(chatPersona)) {
|
||||||
|
console.warn('Chat-locked persona avatar not found, unlocking persona');
|
||||||
|
delete chat_metadata['persona'];
|
||||||
|
updatePersonaLockIcons();
|
||||||
|
chatPersona = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: TEMP
|
||||||
|
power_user.persona_allow_multi_connections = true;
|
||||||
|
|
||||||
|
// Check if we have any persona connected to the current character
|
||||||
|
if (!chatPersona) {
|
||||||
|
const characterKey = menu_type === 'group_edit' ? selected_group : characters[this_chid]?.avatar;
|
||||||
|
const connectedPersonas = Object.entries(power_user.persona_descriptions)
|
||||||
|
.filter(([_, desc]) => desc.connections?.some(conn => conn.type === 'character' && conn.id === characterKey))
|
||||||
|
.map(([key, _]) => key);
|
||||||
|
|
||||||
|
if (connectedPersonas.length > 0) {
|
||||||
|
if (!power_user.persona_allow_multi_connections || connectedPersonas.length === 1) {
|
||||||
|
chatPersona = connectedPersonas[0];
|
||||||
|
toastr.warning(t`More than one persona is connected to this character. Using the first available persona for this chat.`, t`Automatic persona selection`);
|
||||||
|
} else {
|
||||||
|
chatPersona = await askForPersonaSelection(t`Select Persona`,
|
||||||
|
t`Select one of multiple with this character connected persona to use for this chat`,
|
||||||
|
connectedPersonas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last check if default persona is set, select it
|
||||||
|
if (!chatPersona && power_user.default_persona) {
|
||||||
console.log(`Using default persona ${power_user.default_persona}`);
|
console.log(`Using default persona ${power_user.default_persona}`);
|
||||||
chatPersona = power_user.default_persona;
|
chatPersona = power_user.default_persona;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No persona set: user current settings
|
// Whatever way we selected a persona, if it doesn't exist, unlock this chat
|
||||||
if (!chatPersona) {
|
if (chat_metadata['persona'] && !userAvatars.includes(chat_metadata['persona'])) {
|
||||||
console.debug('No default or locked persona set for this chat');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the avatar file
|
|
||||||
const userAvatars = await getUserAvatars(false);
|
|
||||||
|
|
||||||
// Avatar missing (persona deleted)
|
|
||||||
if (chat_metadata['persona'] && !userAvatars.includes(chatPersona)) {
|
|
||||||
console.warn('Persona avatar not found, unlocking persona');
|
console.warn('Persona avatar not found, unlocking persona');
|
||||||
delete chat_metadata['persona'];
|
delete chat_metadata['persona'];
|
||||||
updatePersonaLockIcons();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default persona missing
|
// Default persona missing
|
||||||
@ -1103,11 +1190,13 @@ async function setChatLockedPersona() {
|
|||||||
console.warn('Default persona avatar not found, clearing default persona');
|
console.warn('Default persona avatar not found, clearing default persona');
|
||||||
power_user.default_persona = null;
|
power_user.default_persona = null;
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persona avatar found, select it
|
// Persona avatar found, select it
|
||||||
|
if (chatPersona) {
|
||||||
setUserAvatar(chatPersona);
|
setUserAvatar(chatPersona);
|
||||||
|
}
|
||||||
|
|
||||||
updatePersonaLockIcons();
|
updatePersonaLockIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user