Add "name" argument to /hide and /unhide. Add default value for unnamed argument
| @@ -130,9 +130,10 @@ function getConverter(type) { | ||
| 130 | 130 | * @param {number} start Starting message ID |
| 131 | 131 | * @param {number} end Ending message ID (inclusive) |
| 132 | 132 | * @param {boolean} unhide If true, unhide the messages instead. |
| 133 | + * @param {string} nameFitler Optional name filter | |
| 133 | 134 | * @returns {Promise<void>} |
| 134 | 135 | */ |
| 135 | 136 | export async function hideChatMessageRange(start, end, unhide, nameFitler = null) { |
| 136 | 137 | if (isNaN(start)) return; |
| 137 | 138 | if (!end) end = start; |
| 138 | 139 | const hide = !unhide; |
| @@ -140,6 +141,7 @@ export async function hideChatMessageRange(start, end, unhide) { | ||
| 140 | 141 | for (let messageId = start; messageId <= end; messageId++) { |
| 141 | 142 | const message = chat[messageId]; |
| 142 | 143 | if (!message) continue; |
| 144 | + if (nameFitler && message.name !== nameFitler) continue; | |
| 143 | 145 | |
| 144 | 146 | message.is_system = hide; |
| 145 | 147 | |
| @@ -667,11 +667,21 @@ export function initDefaultSlashCommands() { | ||
| 667 | 667 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 668 | 668 | name: 'hide', |
| 669 | 669 | callback: hideMessageCallback, |
| 670 | + namedArgumentList: [ | |
| 671 | + SlashCommandNamedArgument.fromProps({ | |
| 672 | + name: 'name', | |
| 673 | + description: 'only hide messages from a certain character or persona', | |
| 674 | + typeList: [ARGUMENT_TYPE.STRING], | |
| 675 | + enumProvider: commonEnumProviders.messageNames, | |
| 676 | + isRequired: false, | |
| 677 | + acceptsMultiple: false, | |
| 678 | + }), | |
| 679 | + ], | |
| 670 | 680 | unnamedArgumentList: [ |
| 671 | 681 | SlashCommandArgument.fromProps({ |
| 672 | 682 | description: 'message index (starts with 0) or range, defaults to the last message index if not provided', |
| 673 | 683 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], |
| 674 | 684 | isRequired: truefalse, |
| 675 | 685 | enumProvider: commonEnumProviders.messages(), |
| 676 | 686 | }), |
| 677 | 687 | ], |
| @@ -680,11 +690,21 @@ export function initDefaultSlashCommands() { | ||
| 680 | 690 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 681 | 691 | name: 'unhide', |
| 682 | 692 | callback: unhideMessageCallback, |
| 693 | + namedArgumentList: [ | |
| 694 | + SlashCommandNamedArgument.fromProps({ | |
| 695 | + name: 'name', | |
| 696 | + description: 'only unhide messages from a certain character or persona', | |
| 697 | + typeList: [ARGUMENT_TYPE.STRING], | |
| 698 | + enumProvider: commonEnumProviders.messageNames, | |
| 699 | + isRequired: false, | |
| 700 | + acceptsMultiple: false, | |
| 701 | + }), | |
| 702 | + ], | |
| 683 | 703 | unnamedArgumentList: [ |
| 684 | 704 | SlashCommandArgument.fromProps({ |
| 685 | 705 | description: 'message index (starts with 0) or range, defaults to the last message index if not provided', |
| 686 | 706 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], |
| 687 | 707 | isRequired: truefalse, |
| 688 | 708 | enumProvider: commonEnumProviders.messages(), |
| 689 | 709 | }), |
| 690 | 710 | ], |
| @@ -3034,37 +3054,37 @@ async function askCharacter(args, text) { | ||
| 3034 | 3054 | return await slashCommandReturnHelper.doReturn(args.return ?? 'pipe', message, { objectToStringFunc: x => x.mes }); |
| 3035 | 3055 | } |
| 3036 | 3056 | |
| 3037 | 3057 | async function hideMessageCallback(_args, argvalue) { |
| 3038 | 3058 | if (!argvalue) { |
| 3039 | 3059 | console.warnlog('WARN: No argumentrange provided. forHiding /hidethe commandlast message.'); |
| 3040 | - return ''; | |
| 3041 | 3060 | } |
| 3042 | 3061 | |
| 3043 | 3062 | const range = value ? stringToRange(argvalue, 0, chat.length - 1) : { start: chat.length - 1, end: chat.length - 1 }; |
| 3044 | 3063 | |
| 3045 | 3064 | if (!range) { |
| 3046 | 3065 | console.warn(`WARN: Invalid range provided for /hide command: ${argvalue}`); |
| 3047 | 3066 | return ''; |
| 3048 | 3067 | } |
| 3049 | 3068 | |
| 3050 | - await hideChatMessageRange(range.start, range.end, false); | |
| 3069 | + const nameFilter = String(args.name ?? '').trim(); | |
| 3070 | + await hideChatMessageRange(range.start, range.end, false, nameFilter); | |
| 3051 | 3071 | return ''; |
| 3052 | 3072 | } |
| 3053 | 3073 | |
| 3054 | 3074 | async function unhideMessageCallback(_args, argvalue) { |
| 3055 | 3075 | if (!argvalue) { |
| 3056 | 3076 | console.warnlog('WARN: No argumentrange provided. forUnhiding /unhidethe commandlast message'); |
| 3057 | - return ''; | |
| 3058 | 3077 | } |
| 3059 | 3078 | |
| 3060 | 3079 | const range = value ? stringToRange(argvalue, 0, chat.length - 1) : { start: chat.length - 1, end: chat.length - 1 }; |
| 3061 | 3080 | |
| 3062 | 3081 | if (!range) { |
| 3063 | 3082 | console.warn(`WARN: Invalid range provided for /unhide command: ${argvalue}`); |
| 3064 | 3083 | return ''; |
| 3065 | 3084 | } |
| 3066 | 3085 | |
| 3067 | - await hideChatMessageRange(range.start, range.end, true); | |
| 3086 | + const nameFilter = String(args.name ?? '').trim(); | |
| 3087 | + await hideChatMessageRange(range.start, range.end, true, nameFilter); | |
| 3068 | 3088 | return ''; |
| 3069 | 3089 | } |
| 3070 | 3090 | |
| @@ -3,6 +3,7 @@ import { extension_settings } from '../extensions.js'; | ||
| 3 | 3 | import { getGroupMembers, groups } from '../group-chats.js'; |
| 4 | 4 | import { power_user } from '../power-user.js'; |
| 5 | 5 | import { searchCharByName, getTagsList, tags, tag_map } from '../tags.js'; |
| 6 | +import { onlyUnique } from '../utils.js'; | |
| 6 | 7 | import { world_names } from '../world-info.js'; |
| 7 | 8 | import { SlashCommandClosure } from './SlashCommandClosure.js'; |
| 8 | 9 | import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js'; |
| @@ -251,15 +252,23 @@ export const commonEnumProviders = { | ||
| 251 | 252 | * @param {boolean} [options.allowVars=false] - Whether to add enum option for variable names |
| 252 | 253 | * @returns {(executor:SlashCommandExecutor, scope:SlashCommandScope) => SlashCommandEnumValue[]} |
| 253 | 254 | */ |
| 254 | 255 | messages: ({ allowIdAfter = false, allowVars = false } = {}) => (_executor, scope) => { |
| 256 | + const nameFilter = executor.namedArgumentList.find(it => it.name == 'name')?.value || ''; | |
| 255 | 257 | return [ |
| 256 | 258 | ...chat.map((message, index) => new SlashCommandEnumValue(String(index), `${message.name}: ${message.mes}`, enumTypes.number, message.is_user ? enumIcons.user : message.is_system ? enumIcons.system : enumIcons.assistant)).filter(value => !nameFilter || value.description.startsWith(`${nameFilter}:`)), |
| 257 | 259 | ...allowIdAfter ? [new SlashCommandEnumValue(String(chat.length), '>> After Last Message >>', enumTypes.enum, '➕')] : [], |
| 258 | 260 | ...allowVars ? commonEnumProviders.variables('all')(_executor, scope) : [], |
| 259 | 261 | ]; |
| 260 | 262 | }, |
| 261 | 263 | |
| 262 | 264 | /** |
| 265 | + * All names used in the current chat. | |
| 266 | + * | |
| 267 | + * @returns {SlashCommandEnumValue[]} | |
| 268 | + */ | |
| 269 | + messageNames: () => chat.map((message) => message.name).filter(onlyUnique).sort(Intl.Collator().compare).map(name => new SlashCommandEnumValue(name)), | |
| 270 | + | |
| 271 | + /** | |
| 263 | 272 | * All existing worlds / lorebooks |
| 264 | 273 | * |
| 265 | 274 | * @returns {SlashCommandEnumValue[]} |
| @@ -6,7 +6,7 @@ import { | ||
| 6 | 6 | } from '../lib.js'; |
| 7 | 7 | |
| 8 | 8 | import { getContext } from './extensions.js'; |
| 9 | 9 | import { characters, getRequestHeaders, this_chid, user_avatar } from '../script.js'; |
| 10 | 10 | import { isMobile } from './RossAscends-mods.js'; |
| 11 | 11 | import { collapseNewlines, power_user } from './power-user.js'; |
| 12 | 12 | import { debounce_timeout } from './constants.js'; |
| @@ -2197,6 +2197,48 @@ export async function showFontAwesomePicker(customList = null) { | ||
| 2197 | 2197 | } |
| 2198 | 2198 | |
| 2199 | 2199 | /** |
| 2200 | + * Finds a persona by name, with optional filtering and precedence for avatars | |
| 2201 | + * @param {object} [options={}] - The options for the search | |
| 2202 | + * @param {string?} [options.name=null] - The name to search for | |
| 2203 | + * @param {boolean} [options.allowAvatar=true] - Whether to allow searching by avatar | |
| 2204 | + * @param {boolean} [options.insensitive=true] - Whether the search should be case insensitive | |
| 2205 | + * @param {boolean} [options.preferCurrentPersona=true] - Whether to prefer the current persona(s) | |
| 2206 | + * @param {boolean} [options.quiet=false] - Whether to suppress warnings | |
| 2207 | + * @returns {PersonaViewModel} The persona object | |
| 2208 | + * @typedef {object} PersonaViewModel | |
| 2209 | + * @property {string} avatar - The avatar of the persona | |
| 2210 | + * @property {string} name - The name of the persona | |
| 2211 | + */ | |
| 2212 | +export function findPersona({ name = null, allowAvatar = true, insensitive = true, preferCurrentPersona = true, quiet = false } = {}) { | |
| 2213 | + /** @type {PersonaViewModel[]} */ | |
| 2214 | + const personas = Object.entries(power_user.personas).map(([avatar, name]) => ({ avatar, name })); | |
| 2215 | + const matches = (/** @type {PersonaViewModel} */ persona) => !name || (allowAvatar && persona.avatar === name) || (insensitive ? equalsIgnoreCaseAndAccents(persona.name, name) : persona.name === name); | |
| 2216 | + | |
| 2217 | + // If we have a current persona and prefer it, return that if it matches | |
| 2218 | + const currentPersona = personas.find(a => a.avatar === user_avatar); | |
| 2219 | + if (preferCurrentPersona && currentPersona && matches(currentPersona)) { | |
| 2220 | + return currentPersona; | |
| 2221 | + } | |
| 2222 | + | |
| 2223 | + // If allowAvatar is true, search by avatar first | |
| 2224 | + if (allowAvatar && name) { | |
| 2225 | + const personaByAvatar = personas.find(a => a.avatar === name); | |
| 2226 | + if (personaByAvatar && matches(personaByAvatar)) { | |
| 2227 | + return personaByAvatar; | |
| 2228 | + } | |
| 2229 | + } | |
| 2230 | + | |
| 2231 | + // Search for matching personas by name | |
| 2232 | + const matchingPersonas = personas.filter(a => matches(a)); | |
| 2233 | + if (matchingPersonas.length > 1) { | |
| 2234 | + if (!quiet) toastr.warning('Multiple personas found for given conditions.'); | |
| 2235 | + else console.warn('Multiple personas found for given conditions. Returning the first match.'); | |
| 2236 | + } | |
| 2237 | + | |
| 2238 | + return matchingPersonas[0] || null; | |
| 2239 | +} | |
| 2240 | + | |
| 2241 | +/** | |
| 2200 | 2242 | * Finds a character by name, with optional filtering and precedence for avatars |
| 2201 | 2243 | * @param {object} [options={}] - The options for the search |
| 2202 | 2244 | * @param {string?} [options.name=null] - The name to search for |