import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles, extension_prompt_types } from "../../script.js"; import { extension_settings } from "../extensions.js"; import { getGroupMembers, groups, selected_group } from "../group-chats.js"; import { power_user } from "../power-user.js"; import { searchCharByName, getTagsList, tags } from "../tags.js"; import { SlashCommandClosure } from "./SlashCommandClosure.js"; import { SlashCommandEnumValue, enumTypes } from "./SlashCommandEnumValue.js"; import { SlashCommandExecutor } from "./SlashCommandExecutor.js"; /** * A collection of regularly used enum icons */ export const enumIcons = { default: '◊', // Variables variable: '𝑥', localVariable: 'L', globalVariable: 'G', scopeVariable: 'S', // Common types character: '👤', group: '🧑‍🤝‍🧑', persona: '🧙‍♂️', qr: 'QR', closure: '𝑓', macro: '{{', tag: '🏷️', world: '🌐', preset: '⚙️', file: '📄', message: '💬', voice: '🎤', true: '✔️', false: '❌', // Value types boolean: '🔲', string: '📝', number: '1️⃣', array: '[]', enum: '📚', dictionary: '{}', // Roles system: '⚙️', user: '👤', assistant: '🤖', // WI Icons constant: '🔵', normal: '🟢', disabled: '❌', vectorized: '🔗', /** * Returns the appropriate state icon based on a boolean * * @param {boolean} state - The state to determine the icon for * @returns {string} The corresponding state icon */ getStateIcon: (state) => { return state ? enumIcons.true : enumIcons.false; }, /** * Returns the appropriate WI icon based on the entry * * @param {Object} entry - WI entry * @returns {string} The corresponding WI icon */ getWiStatusIcon: (entry) => { if (entry.constant) return enumIcons.constant; if (entry.disable) return enumIcons.disabled; if (entry.vectorized) return enumIcons.vectorized; return enumIcons.normal; }, /** * Returns the appropriate icon based on the role * * @param {extension_prompt_roles} role - The role to get the icon for * @returns {string} The corresponding icon */ getRoleIcon: (role) => { switch (role) { case extension_prompt_roles.SYSTEM: return enumIcons.system; case extension_prompt_roles.USER: return enumIcons.user; case extension_prompt_roles.ASSISTANT: return enumIcons.assistant; default: return enumIcons.default; } }, /** * A function to get the data type icon * * @param {string} type - The type of the data * @returns {string} The corresponding data type icon */ getDataTypeIcon: (type) => { // Remove possible nullable types definition to match type icon type = type.replace(/\?$/, ''); return enumIcons[type] ?? enumIcons.default; } } /** * A collection of common enum providers * * Can be used on `SlashCommandNamedArgument` and `SlashCommandArgument` and their `enumProvider` property. */ export const commonEnumProviders = { /** * Enum values for booleans. Either using true/false or on/off * Optionally supports "toggle". * * @param {('onOff'|'onOffToggle'|'trueFalse')?} [mode='trueFalse'] - The mode to use. Default is 'trueFalse'. * @returns {() => SlashCommandEnumValue[]} */ boolean: (mode = 'trueFalse') => () => { switch (mode) { case 'onOff': return [new SlashCommandEnumValue('on', null, 'macro', enumIcons.true), new SlashCommandEnumValue('off', null, 'macro', enumIcons.false)]; case 'onOffToggle': return [new SlashCommandEnumValue('on', null, 'macro', enumIcons.true), new SlashCommandEnumValue('off', null, 'macro', enumIcons.false), new SlashCommandEnumValue('toggle', null, 'macro', enumIcons.boolean)]; case 'trueFalse': return [new SlashCommandEnumValue('true', null, 'macro', enumIcons.true), new SlashCommandEnumValue('false', null, 'macro', enumIcons.false)]; default: throw new Error(`Invalid boolean enum provider mode: ${mode}`); } }, /** * All possible variable names * * Can be filtered by `type` to only show global or local variables * * @param {...('global'|'local'|'scope'|'all')} type - The type of variables to include in the array. Can be 'all', 'global', or 'local'. * @returns {() => SlashCommandEnumValue[]} */ variables: (...type) => () => { const types = type.flat(); const isAll = types.includes('all'); return [ ...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [], ...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [], ...isAll || types.includes('scope') ? [].map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny ] }, /** * All possible char entities, like characters and groups. Can be filtered down to just one type. * * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return * @returns {() => SlashCommandEnumValue[]} */ characters: (mode = 'all') => () => { return [ ...['all', 'character'].includes(mode) ? characters.map(char => new SlashCommandEnumValue(char.name, null, enumTypes.name, enumIcons.character)) : [], ...['all', 'group'].includes(mode) ? groups.map(group => new SlashCommandEnumValue(group.name, null, enumTypes.qr, enumIcons.group)) : [], ]; }, /** * All group members of the given group, or default the current active one * * @param {string?} groupId - The id of the group - pass in `undefined` to use the current active group * @returns {() =>SlashCommandEnumValue[]} */ groupMembers: (groupId = undefined) => () => getGroupMembers(groupId).map((character, index) => new SlashCommandEnumValue(String(index), character.name, enumTypes.enum, enumIcons.character)), /** * All possible personas * * @returns {SlashCommandEnumValue[]} */ personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)), /** * All possible tags for a given char/group entity * * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show * @returns {() => SlashCommandEnumValue[]} */ tagsForChar: (mode = 'all') => (/** @type {SlashCommandExecutor} */ executor) => { // Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags. const charName = executor.namedArgumentList.find(it => it.name == 'name')?.value; if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures'); const key = searchCharByName(substituteParams(charName), { suppressLogging: true }); const assigned = key ? getTagsList(key) : []; return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it)) .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag)); }, /** * All messages in the current chat, returning the message id * * Optionally supports variable names, and/or a placeholder for the last/new message id * * @param {object} [options={}] - Optional arguments * @param {boolean} [options.allowIdAfter=false] - Whether to add an enum option for the new message id after the last message * @param {boolean} [options.allowVars=false] - Whether to add enum option for variable names * @returns {() => SlashCommandEnumValue[]} */ messages: ({ allowIdAfter = false, allowVars = false } = {}) => () => { return [ ...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)), ...allowIdAfter ? [new SlashCommandEnumValue(String(chat.length), '>> After Last Message >>', enumTypes.enum, '➕')] : [], ...allowVars ? commonEnumProviders.variables('all')() : [], ]; }, /** * All existing worlds / lorebooks * * @returns {SlashCommandEnumValue[]} */ worlds: () => $('#world_info').children().toArray().map(x => new SlashCommandEnumValue(x.textContent, null, enumTypes.name, enumIcons.world)), /** * All existing injects for the current chat * * @returns {SlashCommandEnumValue[]} */ injects: () => { if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) return []; return Object.entries(chat_metadata.script_injects) .map(([id, inject]) => { const positionName = (Object.entries(extension_prompt_types)).find(([_, value]) => value === inject.position)?.[0] ?? 'unknown'; return new SlashCommandEnumValue(id, `${enumIcons.getRoleIcon(inject.role ?? extension_prompt_roles.SYSTEM)}[Inject](${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}) ${inject.value}`, enumTypes.enum, '💉'); }); }, };