Highlight personas from current chat in popup

2253990898b6772ee58771df694a203b8db8717e

Wolfsblvt <wolfsblvt@gmail.com>

3 files changed, +31 -4Ignore whitespace
public/script.js+1 -0
@@ -9994,6 +9994,7 @@ jQuery(async function () {
9994 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.`;9994 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.`;
9995 const selectedPersona = await askForPersonaSelection(t`Persona Connections`, message, connections, {9995 const selectedPersona = await askForPersonaSelection(t`Persona Connections`, message, connections, {
9996 okButton: t`Ok`,9996 okButton: t`Ok`,
9997 highlightPersonas: true,
9997 shiftClickHandler: (element, ev) => {9998 shiftClickHandler: (element, ev) => {
99989999
9999 const personaId = $(element).attr('data-pid');10000 const personaId = $(element).attr('data-pid');
public/scripts/personas.js+26 -4
@@ -22,7 +22,7 @@ import {
22} from '../script.js';22} from '../script.js';
23import { persona_description_positions, power_user } from './power-user.js';23import { persona_description_positions, power_user } from './power-user.js';
24import { getTokenCountAsync } from './tokenizers.js';24import { getTokenCountAsync } from './tokenizers.js';
25import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, parseJsonFile } from './utils.js';25import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, onlyUnique, parseJsonFile } from './utils.js';
26import { debounce_timeout } from './constants.js';26import { debounce_timeout } from './constants.js';
27import { FILTER_TYPES, FilterHelper } from './filters.js';27import { FILTER_TYPES, FilterHelper } from './filters.js';
28import { groups, selected_group } from './group-chats.js';28import { groups, selected_group } from './group-chats.js';
@@ -40,6 +40,8 @@ import { saveMetadataDebounced } from './extensions.js';
4040
41/** @typedef {'chat' | 'character' | 'default'} PersonaLockType Type of the persona lock */41/** @typedef {'chat' | 'character' | 'default'} PersonaLockType Type of the persona lock */
4242
43const USER_AVATAR_PATH = 'User Avatars/';
44
43let savePersonasPage = 0;45let savePersonasPage = 0;
44const GRID_STORAGE_KEY = 'Personas_GridView';46const GRID_STORAGE_KEY = 'Personas_GridView';
45const DEFAULT_DEPTH = 2;47const DEFAULT_DEPTH = 2;
@@ -58,7 +60,7 @@ function switchPersonaGridView() {
58 * @returns {string} User avatar URL60 * @returns {string} User avatar URL
59 */61 */
60export function getUserAvatar(avatarImg) {62export function getUserAvatar(avatarImg) {
61 return `User Avatars/${avatarImg}`;63 return `${USER_AVATAR_PATH}${avatarImg}`;
62}64}
6365
64export function initUserAvatar(avatar) {66export function initUserAvatar(avatar) {
@@ -486,6 +488,17 @@ export function setPersonaDescription() {
486 updatePersonaConnectionsAvatarList();488 updatePersonaConnectionsAvatarList();
487}489}
488490
491/**
492 * Gets a list of all personas in the current chat.
493 *
494 * @returns {string[]} An array of persona identifiers
495 */
496function getPersonasOfCurrentChat() {
497 const personas = chat.filter(message => String(message.force_avatar).startsWith(USER_AVATAR_PATH))
498 .map(message => message.force_avatar.replace(USER_AVATAR_PATH, ''))
499 .filter(onlyUnique);
500 return personas;
501}
489502
490/**503/**
491 * Builds a list of persona avatars and populates the given block element with them.504 * Builds a list of persona avatars and populates the given block element with them.
@@ -547,9 +560,10 @@ export function updatePersonaConnectionsAvatarList() {
547 * @param {Object} [options] - Optional settings for the popup560 * @param {Object} [options] - Optional settings for the popup
548 * @param {string} [options.okButton='None'] - The label for the OK button561 * @param {string} [options.okButton='None'] - The label for the OK button
549 * @param {(element: HTMLElement, ev: MouseEvent) => any} [options.shiftClickHandler] - A function to handle shift-click562 * @param {(element: HTMLElement, ev: MouseEvent) => any} [options.shiftClickHandler] - A function to handle shift-click
563 * @param {boolean|string[]} [options.highlightPersonas=false] - Whether to highlight personas - either by providing a list of persona keys, or true to highlight all present in current chat
550 * @returns {Promise<string?>} - A promise that resolves to the selected persona id or null if no selection was made564 * @returns {Promise<string?>} - A promise that resolves to the selected persona id or null if no selection was made
551 */565 */
552export async function askForPersonaSelection(title, text, personas, { okButton = 'None', shiftClickHandler = undefined } = {}) {566export async function askForPersonaSelection(title, text, personas, { okButton = 'None', shiftClickHandler = undefined, highlightPersonas = false } = {}) {
553 const content = document.createElement('div');567 const content = document.createElement('div');
554 const titleElement = document.createElement('h3');568 const titleElement = document.createElement('h3');
555 titleElement.textContent = title;569 titleElement.textContent = title;
@@ -569,6 +583,8 @@ export async function askForPersonaSelection(title, text, personas, { okButton =
569 else583 else
570 personaListBlock.textContent = '[No personas]';584 personaListBlock.textContent = '[No personas]';
571585
586 const personasToHighlight = highlightPersonas instanceof Array ? highlightPersonas : (highlightPersonas ? getPersonasOfCurrentChat() : []);
587
572 // Make the persona blocks clickable and close the popup588 // Make the persona blocks clickable and close the popup
573 personaListBlock.querySelectorAll('.avatar[data-type="persona"]').forEach(block => {589 personaListBlock.querySelectorAll('.avatar[data-type="persona"]').forEach(block => {
574 if (!(block instanceof HTMLElement)) return;590 if (!(block instanceof HTMLElement)) return;
@@ -581,6 +597,12 @@ export async function askForPersonaSelection(title, text, personas, { okButton =
581 }597 }
582 });598 });
583 }599 }
600
601 if (personasToHighlight && personasToHighlight.includes(block.dataset.pid)) {
602 block.classList.add('is_active');
603 block.title = block.title + '\n\n' + t`Was used in current chat.`;
604 if (block.classList.contains('is_fav')) block.title = block.title + '\n' + t`Is your default persona.`;
605 }
584 });606 });
585607
586 const popup = new Popup(content, POPUP_TYPE.TEXT, '', { okButton: okButton });608 const popup = new Popup(content, POPUP_TYPE.TEXT, '', { okButton: okButton });
@@ -1225,7 +1247,7 @@ async function loadPersonaForCurrentChat() {
1225 } else {1247 } else {
1226 chatPersona = await askForPersonaSelection(t`Select Persona`,1248 chatPersona = await askForPersonaSelection(t`Select Persona`,
1227 t`Multiple personas are connected to this character.\nSelect a persona to use for this chat.`,1249 t`Multiple personas are connected to this character.\nSelect a persona to use for this chat.`,
1228 connectedPersonas);1250 connectedPersonas, { highlightPersonas: true });
1229 }1251 }
1230 }1252 }
12311253
public/style.css+4 -0
@@ -3151,6 +3151,10 @@ input[type=search]:focus::-webkit-search-cancel-button {
3151 color: var(--golden);3151 color: var(--golden);
3152}3152}
31533153
3154.avatar.is_active {
3155 outline: 2px solid var(--active);
3156}
3157
3154#fav_chara_wrap {3158#fav_chara_wrap {
3155 display: flex;3159 display: flex;
3156 margin: 5px 0px;3160 margin: 5px 0px;