mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Add button to show connections for current char
This commit is contained in:
@ -5091,6 +5091,7 @@
|
|||||||
<div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions" data-i18n="[title]Advanced Definition"></div>
|
<div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions" data-i18n="[title]Advanced Definition"></div>
|
||||||
<div id="world_button" class="menu_button fa-solid fa-globe" title="Character Lore Click to load Shift-click to open 'Link to World Info' popup" data-i18n="[title]world_button_title"></div>
|
<div id="world_button" class="menu_button fa-solid fa-globe" title="Character Lore Click to load Shift-click to open 'Link to World Info' popup" data-i18n="[title]world_button_title"></div>
|
||||||
<div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore Alt+Click to open the lorebook" data-i18n="[title]Chat Lore Alt+Click to open the lorebook"></div>
|
<div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore Alt+Click to open the lorebook" data-i18n="[title]Chat Lore Alt+Click to open the lorebook"></div>
|
||||||
|
<div id="char_connections_button" class="menu_button fa-solid fa-face-smile" title="Connected Personas" data-i18n="[title]Connected Personas"></div>
|
||||||
<div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div>
|
<div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div>
|
||||||
<!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> -->
|
<!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> -->
|
||||||
<!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World Info / Lorebook"></div> -->
|
<!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World Info / Lorebook"></div> -->
|
||||||
|
@ -234,6 +234,8 @@ import {
|
|||||||
setPersonaDescription,
|
setPersonaDescription,
|
||||||
initUserAvatar,
|
initUserAvatar,
|
||||||
updatePersonaConnectionsAvatarList,
|
updatePersonaConnectionsAvatarList,
|
||||||
|
getConnectedPersonas,
|
||||||
|
askForPersonaSelection,
|
||||||
} from './scripts/personas.js';
|
} from './scripts/personas.js';
|
||||||
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
|
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
|
||||||
import { hideLoader, showLoader } from './scripts/loader.js';
|
import { hideLoader, showLoader } from './scripts/loader.js';
|
||||||
@ -9985,6 +9987,47 @@ jQuery(async function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#char_connections_button').on('click', async () => {
|
||||||
|
let isRemoving = false;
|
||||||
|
|
||||||
|
const connections = getConnectedPersonas();
|
||||||
|
const message = t`The following personas are connected to the current character.\n\nClick on a persona to select it for the current character.\nShift + Click to unlink the persona from the character.`;
|
||||||
|
const selectedPersona = await askForPersonaSelection(t`Persona Connections`, message, connections, {
|
||||||
|
okButton: t`Ok`,
|
||||||
|
shiftClickHandler: (element, ev) => {
|
||||||
|
|
||||||
|
const personaId = $(element).attr('data-pid');
|
||||||
|
|
||||||
|
/** @type {import('./scripts/personas.js').PersonaConnection[]} */
|
||||||
|
const connections = power_user.persona_descriptions[personaId]?.connections;
|
||||||
|
if (connections) {
|
||||||
|
console.log(`Unlocking persona ${personaId} from current character ${name2}`);
|
||||||
|
power_user.persona_descriptions[personaId].connections = connections.filter(c => {
|
||||||
|
if (menu_type == 'group_edit' && c.type == 'group' && c.id == selected_group) return false;
|
||||||
|
else if (c.type == 'character' && c.id == characters[this_chid]?.avatar) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
saveSettingsDebounced();
|
||||||
|
updatePersonaConnectionsAvatarList();
|
||||||
|
if (power_user.persona_show_notifications) {
|
||||||
|
toastr.info(t`User persona ${power_user.personas[personaId]} is now unlocked from the current character ${name2}.`, t`Persona unlocked`);
|
||||||
|
}
|
||||||
|
|
||||||
|
isRemoving = true;
|
||||||
|
$('#char_connections_button').trigger('click');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// One of the persona was selected. So load it.
|
||||||
|
if (!isRemoving && selectedPersona) {
|
||||||
|
setUserAvatar(selectedPersona);
|
||||||
|
if (power_user.persona_show_notifications) {
|
||||||
|
toastr.info(t`Selected persona ${power_user.personas[selectedPersona]} for current chat.`, t`Connected Persona Selected`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('#character_cross').click(function () {
|
$('#character_cross').click(function () {
|
||||||
is_advanced_char_open = false;
|
is_advanced_char_open = false;
|
||||||
$('#character_popup').transition({
|
$('#character_popup').transition({
|
||||||
|
@ -544,32 +544,46 @@ export function updatePersonaConnectionsAvatarList() {
|
|||||||
* @param {string} title - The title to display in the popup
|
* @param {string} title - The title to display in the popup
|
||||||
* @param {string} text - The text 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
|
* @param {string[]} personas - An array of persona ids to display for selection
|
||||||
|
* @param {Object} [options] - Optional settings for the popup
|
||||||
|
* @param {string} [options.okButton='None'] - The label for the OK button
|
||||||
|
* @param {(element: HTMLElement, ev: MouseEvent) => any} [options.shiftClickHandler] - A function to handle shift-click
|
||||||
* @returns {Promise<string?>} - A promise that resolves to the selected persona id or null if no selection was made
|
* @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) {
|
export async function askForPersonaSelection(title, text, personas, { okButton = 'None', shiftClickHandler = undefined } = {}) {
|
||||||
const content = document.createElement('div');
|
const content = document.createElement('div');
|
||||||
const titleElement = document.createElement('h3');
|
const titleElement = document.createElement('h3');
|
||||||
titleElement.textContent = title;
|
titleElement.textContent = title;
|
||||||
content.appendChild(titleElement);
|
content.appendChild(titleElement);
|
||||||
|
|
||||||
const textElement = document.createElement('div');
|
const textElement = document.createElement('div');
|
||||||
textElement.classList.add('m-b-1');
|
textElement.classList.add('multiline', 'm-b-1');
|
||||||
textElement.textContent = text;
|
textElement.textContent = text;
|
||||||
content.appendChild(textElement);
|
content.appendChild(textElement);
|
||||||
|
|
||||||
const personaListBlock = document.createElement('div');
|
const personaListBlock = document.createElement('div');
|
||||||
personaListBlock.classList.add('persona-list', 'avatars_inline', 'avatars_multiline');
|
personaListBlock.classList.add('persona-list', 'avatars_inline', 'avatars_multiline', 'text_muted');
|
||||||
content.appendChild(personaListBlock);
|
content.appendChild(personaListBlock);
|
||||||
|
|
||||||
|
if (personas.length > 0)
|
||||||
buildPersonaAvatarList(personaListBlock, personas, { interactable: true });
|
buildPersonaAvatarList(personaListBlock, personas, { interactable: true });
|
||||||
|
else
|
||||||
|
personaListBlock.textContent = '[No personas]';
|
||||||
|
|
||||||
// Make the persona blocks clickable and close the popup
|
// Make the persona blocks clickable and close the popup
|
||||||
personaListBlock.querySelectorAll('.avatar[data-type="persona"]').forEach(block => {
|
personaListBlock.querySelectorAll('.avatar[data-type="persona"]').forEach(block => {
|
||||||
if (!(block instanceof HTMLElement)) return;
|
if (!(block instanceof HTMLElement)) return;
|
||||||
block.dataset.result = String(100 + personas.indexOf(block.dataset.pid));
|
block.dataset.result = String(100 + personas.indexOf(block.dataset.pid));
|
||||||
|
|
||||||
|
if (shiftClickHandler) {
|
||||||
|
block.addEventListener('click', function (ev) {
|
||||||
|
if (ev.shiftKey) {
|
||||||
|
shiftClickHandler(this, ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const popup = new Popup(content, POPUP_TYPE.TEXT, '', { okButton: 'None' });
|
const popup = new Popup(content, POPUP_TYPE.TEXT, '', { okButton: okButton });
|
||||||
const result = await popup.show();
|
const result = await popup.show();
|
||||||
return Number(result) > 100 ? personas[Number(result) - 100] : null;
|
return Number(result) > 100 ? personas[Number(result) - 100] : null;
|
||||||
}
|
}
|
||||||
@ -730,7 +744,7 @@ function selectCurrentPersona() {
|
|||||||
* @param {PersonaConnection} connection - Connection to check
|
* @param {PersonaConnection} connection - Connection to check
|
||||||
* @returns {boolean} Whether the connection is locked
|
* @returns {boolean} Whether the connection is locked
|
||||||
*/
|
*/
|
||||||
function isPersonaConnectionLocked(connection) {
|
export function isPersonaConnectionLocked(connection) {
|
||||||
return (menu_type === 'character_edit' && connection.type === 'character' && connection.id === characters[this_chid]?.avatar)
|
return (menu_type === 'character_edit' && connection.type === 'character' && connection.id === characters[this_chid]?.avatar)
|
||||||
|| (menu_type === 'group_edit' && connection.type === 'group' && connection.id === selected_group);
|
|| (menu_type === 'group_edit' && connection.type === 'group' && connection.id === selected_group);
|
||||||
}
|
}
|
||||||
@ -1199,10 +1213,7 @@ async function loadPersonaForCurrentChat() {
|
|||||||
|
|
||||||
// Check if we have any persona connected to the current character
|
// Check if we have any persona connected to the current character
|
||||||
if (!chatPersona) {
|
if (!chatPersona) {
|
||||||
const characterKey = menu_type === 'group_edit' ? selected_group : characters[this_chid]?.avatar;
|
const connectedPersonas = getConnectedPersonas();
|
||||||
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 (connectedPersonas.length > 0) {
|
||||||
if (connectedPersonas.length === 1) {
|
if (connectedPersonas.length === 1) {
|
||||||
@ -1212,7 +1223,7 @@ async function loadPersonaForCurrentChat() {
|
|||||||
toastr.warning(t`More than one persona is connected to this character. Using the first available persona for this chat.`, t`Automatic Persona Selection`);
|
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 {
|
} else {
|
||||||
chatPersona = await askForPersonaSelection(t`Select Persona`,
|
chatPersona = await askForPersonaSelection(t`Select Persona`,
|
||||||
t`Multiple personas are connected to this character. Select a persona to use for this chat.`,
|
t`Multiple personas are connected to this character.\nSelect a persona to use for this chat.`,
|
||||||
connectedPersonas);
|
connectedPersonas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1253,6 +1264,20 @@ async function loadPersonaForCurrentChat() {
|
|||||||
updatePersonaLockIcons();
|
updatePersonaLockIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of persona keys that are connected to the given character key.
|
||||||
|
* If the character key is not provided, it defaults to the currently selected group or character.
|
||||||
|
* @param {string} [characterKey] - The character key to query
|
||||||
|
* @returns {string[]} - An array of persona keys that are connected to the given character key
|
||||||
|
*/
|
||||||
|
export function getConnectedPersonas(characterKey = undefined) {
|
||||||
|
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);
|
||||||
|
return connectedPersonas;
|
||||||
|
}
|
||||||
|
|
||||||
function onBackupPersonas() {
|
function onBackupPersonas() {
|
||||||
const timestamp = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
const timestamp = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
||||||
const filename = `personas_${timestamp}.json`;
|
const filename = `personas_${timestamp}.json`;
|
||||||
|
@ -5793,3 +5793,7 @@ body:not(.movingUI) .drawer-content.maximized {
|
|||||||
.mes_text div[data-type="assistant_note"]:has(.assistant_note_export)>div:not(.assistant_note_export) {
|
.mes_text div[data-type="assistant_note"]:has(.assistant_note_export)>div:not(.assistant_note_export) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.multiline {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user