Add /getcharlore and /getpersonalore commands Closes #3183

713c05f80898db9dee09b6c2e2f9e94967d2068d

Cohee <18619528+Cohee1207@users.noreply.github.com>

2 files changed, +73 -4Ignore whitespace
public/scripts/utils.js+1 -1
@@ -2112,7 +2112,7 @@ export async function showFontAwesomePicker(customList = null) {
2112 * @param {string[]?} [options.filteredByTags=null] - Tags to filter characters by2112 * @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 warnings2114 * @param {boolean} [options.quiet=false] - Whether to suppress warnings
2115 * @returns {any?} - The found character or null if not found2115 * @returns {import('./char-data.js').v1CharData?} - The found character or null if not found
2116 */2116 */
2117export function findChar({ name = null, allowAvatar = true, insensitive = true, filteredByTags = null, preferCurrentChar = true, quiet = false } = {}) {2117export 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);
public/scripts/world-info.js+72 -3
@@ -1,7 +1,7 @@
1import { Fuse } from '../lib.js';1import { Fuse } from '../lib.js';
22
3import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';3import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
4import { 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';4import { 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';
5import { extension_settings, getContext } from './extensions.js';5import { extension_settings, getContext } from './extensions.js';
6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
7import { isMobile } from './RossAscends-mods.js';7import { isMobile } from './RossAscends-mods.js';
@@ -931,9 +931,47 @@ function registerWorldInfoSlashCommands() {
931 }931 }
932932
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 arguments973 * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments
936 * @returns974 * @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 }));
13191388
1320 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1389 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1321 name: 'findentry',1390 name: 'findentry',