Add /getcharlore and /getpersonalore commands Closes #3183
| @@ -2112,7 +2112,7 @@ export async function showFontAwesomePicker(customList = null) { | |||
| 2112 | * @param {string[]?} [options.filteredByTags=null] - Tags to filter characters by | 2112 | * @param {string[]?} [options.filteredByTags=null] - Tags to filter characters by |
| 2113 | * @param {boolean} [options.preferCurrentChar=true] - Whether to prefer the current character(s) | 2113 | * @param {boolean} [options.preferCurrentChar=true] - Whether to prefer the current character(s) |
| 2114 | * @param {boolean} [options.quiet=false] - Whether to suppress warnings | 2114 | * @param {boolean} [options.quiet=false] - Whether to suppress warnings |
| 2115 | * @returns {any?} - The found character or null if not found | 2115 | * @returns {import('./char-data.js').v1CharData?} - The found character or null if not found |
| 2116 | */ | 2116 | */ |
| 2117 | export function findChar({ name = null, allowAvatar = true, insensitive = true, filteredByTags = null, preferCurrentChar = true, quiet = false } = {}) { | 2117 | export function findChar({ name = null, allowAvatar = true, insensitive = true, filteredByTags = null, preferCurrentChar = true, quiet = false } = {}) { |
| 2118 | const matches = (char) => !name || (allowAvatar && char.avatar === name) || (insensitive ? equalsIgnoreCaseAndAccents(char.name, name) : char.name === name); | 2118 | const matches = (char) => !name || (allowAvatar && char.avatar === name) || (insensitive ? equalsIgnoreCaseAndAccents(char.name, name) : char.name === name); |
| @@ -1,7 +1,7 @@ | |||
| 1 | import { Fuse } from '../lib.js'; | 1 | import { Fuse } from '../lib.js'; |
| 2 | 2 | ||
| 3 | import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js'; | 3 | import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js'; |
| 4 | import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce } from './utils.js'; | 4 | import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique } from './utils.js'; |
| 5 | import { extension_settings, getContext } from './extensions.js'; | 5 | import { extension_settings, getContext } from './extensions.js'; |
| 6 | import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; | 6 | import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; |
| 7 | import { isMobile } from './RossAscends-mods.js'; | 7 | import { isMobile } from './RossAscends-mods.js'; |
| @@ -931,9 +931,47 @@ function registerWorldInfoSlashCommands() { | |||
| 931 | } | 931 | } |
| 932 | 932 | ||
| 933 | /** | 933 | /** |
| 934 | * Gets the name of the persona-bound lorebook. | ||
| 935 | * @returns {string} The name of the persona-bound lorebook | ||
| 936 | */ | ||
| 937 | function getPersonaBookCallback() { | ||
| 938 | return power_user.persona_description_lorebook || ''; | ||
| 939 | } | ||
| 940 | |||
| 941 | /** | ||
| 942 | * Gets the name of the character-bound lorebook. | ||
| 943 | * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments | ||
| 944 | * @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} name Character name | ||
| 945 | * @returns {string} The name of the character-bound lorebook, a JSON string of the character's lorebooks, or an empty string | ||
| 946 | */ | ||
| 947 | function getCharBookCallback({ type }, name) { | ||
| 948 | const context = getContext(); | ||
| 949 | if (context.groupId && !name) throw new Error('This command is not available in groups without providing a character name'); | ||
| 950 | type = String(type ?? '').trim().toLowerCase() || 'primary'; | ||
| 951 | name = String(name ?? '') || context.characters[context.characterId]?.avatar || null; | ||
| 952 | const character = findChar({ name }); | ||
| 953 | if (!character) { | ||
| 954 | toastr.error('Character not found.'); | ||
| 955 | return ''; | ||
| 956 | } | ||
| 957 | const books = []; | ||
| 958 | if (type === 'all' || type === 'primary') { | ||
| 959 | books.push(character.data?.extensions?.world); | ||
| 960 | } | ||
| 961 | if (type === 'all' || type === 'additional') { | ||
| 962 | const fileName = getCharaFilename(context.characters.indexOf(character)); | ||
| 963 | const extraCharLore = world_info.charLore?.find((e) => e.name === fileName); | ||
| 964 | if (extraCharLore && Array.isArray(extraCharLore.extraBooks)) { | ||
| 965 | books.push(...extraCharLore.extraBooks); | ||
| 966 | } | ||
| 967 | } | ||
| 968 | return type === 'primary' ? (books[0] ?? '') : JSON.stringify(books.filter(onlyUnique).filter(Boolean)); | ||
| 969 | } | ||
| 970 | |||
| 971 | /** | ||
| 934 | * Gets the name of the chat-bound lorebook. Creates a new one if it doesn't exist. | 972 | * Gets the name of the chat-bound lorebook. Creates a new one if it doesn't exist. |
| 935 | * @param {import('./slash-commands/SlashCommandParser.js').NamedArguments} args Named arguments | 973 | * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments |
| 936 | * @returns | 974 | * @returns {Promise<string>} The name of the chat-bound lorebook |
| 937 | */ | 975 | */ |
| 938 | async function getChatBookCallback(args) { | 976 | async function getChatBookCallback(args) { |
| 939 | const chatId = getCurrentChatId(); | 977 | const chatId = getCurrentChatId(); |
| @@ -1316,6 +1354,37 @@ function registerWorldInfoSlashCommands() { | |||
| 1316 | ], | 1354 | ], |
| 1317 | aliases: ['getchatlore', 'getchatwi'], | 1355 | aliases: ['getchatlore', 'getchatwi'], |
| 1318 | })); | 1356 | })); |
| 1357 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 1358 | name: 'getpersonabook', | ||
| 1359 | callback: getPersonaBookCallback, | ||
| 1360 | returns: 'lorebook name', | ||
| 1361 | helpString: 'Get a name of the current persona-bound lorebook and pass it down the pipe. Returns empty string if persona lorebook is not set.', | ||
| 1362 | aliases: ['getpersonalore', 'getpersonawi'], | ||
| 1363 | })); | ||
| 1364 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 1365 | name: 'getcharbook', | ||
| 1366 | callback: getCharBookCallback, | ||
| 1367 | returns: 'lorebook name or a list of lorebook names', | ||
| 1368 | namedArgumentList: [ | ||
| 1369 | SlashCommandNamedArgument.fromProps({ | ||
| 1370 | name: 'type', | ||
| 1371 | description: 'type of the lorebook to get, returns a list for "all" and "additional"', | ||
| 1372 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1373 | enumList: ['primary', 'additional', 'all'], | ||
| 1374 | defaultValue: 'primary', | ||
| 1375 | }), | ||
| 1376 | ], | ||
| 1377 | unnamedArgumentList: [ | ||
| 1378 | SlashCommandArgument.fromProps({ | ||
| 1379 | description: 'Character name - or unique character identifier (avatar key). If not provided, the current character is used.', | ||
| 1380 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], | ||
| 1381 | isRequired: false, | ||
| 1382 | enumProvider: commonEnumProviders.characters('character'), | ||
| 1383 | }), | ||
| 1384 | ], | ||
| 1385 | helpString: 'Get a name of the character-bound lorebook and pass it down the pipe. Returns empty string if character lorebook is not set. Does not work in group chats without providing a character avatar name.', | ||
| 1386 | aliases: ['getcharlore', 'getcharwi'], | ||
| 1387 | })); | ||
| 1319 | 1388 | ||
| 1320 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 1389 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1321 | name: 'findentry', | 1390 | name: 'findentry', |