Add support for characterFilter to getEntryField and setEntryField (#4185) * Add support for characterFilter to getEntryField and setEntryField * No more conflicts between lint and autoformat * Fix array type default values hint * Properly capitalize name * Fix search by name --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -2367,7 +2367,7 @@ export function findChar({ name = null, allowAvatar = true, insensitive = true, | ||
| 2367 | 2367 | |
| 2368 | 2368 | // If allowAvatar is true, search by avatar first |
| 2369 | 2369 | if (allowAvatar && name) { |
| 2370 | 2370 | const characterByAvatar = filteredCharacters.find(char => char.avatar === name || (!name.endsWith('.png') && char.avatar === `${name}.png`)); |
| 2371 | 2371 | if (characterByAvatar) { |
| 2372 | 2372 | return characterByAvatar; |
| 2373 | 2373 | } |
| @@ -1124,6 +1124,7 @@ function registerWorldInfoSlashCommands() { | ||
| 1124 | 1124 | async function getEntryFieldCallback(args, uid) { |
| 1125 | 1125 | const file = args.file; |
| 1126 | 1126 | const field = args.field || 'content'; |
| 1127 | + const tags = getContext().tags; | |
| 1127 | 1128 | |
| 1128 | 1129 | const entries = await getEntriesFromFile(file); |
| 1129 | 1130 | |
| @@ -1143,7 +1144,31 @@ function registerWorldInfoSlashCommands() { | ||
| 1143 | 1144 | return ''; |
| 1144 | 1145 | } |
| 1145 | 1146 | |
| 1146 | - const fieldValue = entry[field]; | |
| 1147 | + // handle special cases, otherwise execute default logic | |
| 1148 | + let fieldValue; | |
| 1149 | + switch (field){ | |
| 1150 | + case 'characterFilterNames': | |
| 1151 | + if (entry.characterFilter) { | |
| 1152 | + fieldValue = entry.characterFilter.names; | |
| 1153 | + } | |
| 1154 | + break; | |
| 1155 | + case 'characterFilterTags': | |
| 1156 | + if (entry.characterFilter) { | |
| 1157 | + if (!entry.characterFilter.tags) { | |
| 1158 | + return ''; | |
| 1159 | + } | |
| 1160 | + //Find the tag objects corresponding to each ID in the array, then return the names | |
| 1161 | + fieldValue = tags.filter((tag) => entry.characterFilter.tags.includes(tag.id)).map((tag) => tag.name); | |
| 1162 | + } | |
| 1163 | + break; | |
| 1164 | + case 'characterFilterExclude': | |
| 1165 | + if (entry.characterFilter) { | |
| 1166 | + fieldValue = entry.characterFilter.isExclude; | |
| 1167 | + } | |
| 1168 | + break; | |
| 1169 | + default: | |
| 1170 | + fieldValue = entry[field]; | |
| 1171 | + } | |
| 1147 | 1172 | |
| 1148 | 1173 | if (fieldValue === undefined) { |
| 1149 | 1174 | return ''; |
| @@ -1189,6 +1214,23 @@ function registerWorldInfoSlashCommands() { | ||
| 1189 | 1214 | const file = args.file; |
| 1190 | 1215 | const uid = args.uid; |
| 1191 | 1216 | const field = args.field || 'content'; |
| 1217 | + const tags = getContext().tags; | |
| 1218 | + | |
| 1219 | + // characterFilter is an object with internal fields we need to access, which may also may be null and need to be populated | |
| 1220 | + const createCharacterFilterFieldObjectIfNeeded = (currentEntry) => { | |
| 1221 | + if (!currentEntry.characterFilter) { | |
| 1222 | + Object.assign( | |
| 1223 | + currentEntry, | |
| 1224 | + { | |
| 1225 | + characterFilter: { | |
| 1226 | + isExclude: false, | |
| 1227 | + names: [], | |
| 1228 | + tags: [], | |
| 1229 | + }, | |
| 1230 | + }, | |
| 1231 | + ); | |
| 1232 | + } | |
| 1233 | + }; | |
| 1192 | 1234 | |
| 1193 | 1235 | if (value === undefined) { |
| 1194 | 1236 | toastr.warning('Value is required'); |
| @@ -1216,6 +1258,32 @@ function registerWorldInfoSlashCommands() { | ||
| 1216 | 1258 | return ''; |
| 1217 | 1259 | } |
| 1218 | 1260 | |
| 1261 | + // handle special cases, otherwise execute default logic | |
| 1262 | + let tagNames; | |
| 1263 | + let charNames; | |
| 1264 | + switch (field){ | |
| 1265 | + case 'characterFilterNames': | |
| 1266 | + createCharacterFilterFieldObjectIfNeeded(entry); | |
| 1267 | + charNames = parseStringArray(value); | |
| 1268 | + entry.characterFilter.names = charNames | |
| 1269 | + .map((name) => getCharaFilename(null, { manualAvatarKey: findChar({ name, allowAvatar: true, preferCurrentChar: false, quiet: true })?.avatar })) | |
| 1270 | + .filter(Boolean) | |
| 1271 | + .filter(onlyUnique); | |
| 1272 | + setWIOriginalDataValue(data, uid, 'character_filter', entry.characterFilter); | |
| 1273 | + break; | |
| 1274 | + case 'characterFilterTags': | |
| 1275 | + createCharacterFilterFieldObjectIfNeeded(entry); | |
| 1276 | + tagNames = parseStringArray(value); | |
| 1277 | + //Find the tag objects corresponding to each name in the user array, then return an array of the corresponding IDs | |
| 1278 | + entry.characterFilter.tags = tags.filter((tag) => tagNames.includes(tag.name)).map((tag) => tag.id); | |
| 1279 | + setWIOriginalDataValue(data, uid, 'character_filter', entry.characterFilter); | |
| 1280 | + break; | |
| 1281 | + case 'characterFilterExclude': | |
| 1282 | + createCharacterFilterFieldObjectIfNeeded(entry); | |
| 1283 | + entry.characterFilter.isExclude = isTrueBoolean(value); | |
| 1284 | + setWIOriginalDataValue(data, uid, 'character_filter', entry.characterFilter); | |
| 1285 | + break; | |
| 1286 | + default: | |
| 1219 | 1287 | if (Array.isArray(entry[field])) { |
| 1220 | 1288 | entry[field] = parseStringArray(value); |
| 1221 | 1289 | } else if (typeof entry[field] === 'boolean') { |
| @@ -1229,6 +1297,7 @@ function registerWorldInfoSlashCommands() { | ||
| 1229 | 1297 | if (originalWIDataKeyMap[field]) { |
| 1230 | 1298 | setWIOriginalDataValue(data, uid, originalWIDataKeyMap[field], entry[field]); |
| 1231 | 1299 | } |
| 1300 | + } | |
| 1232 | 1301 | |
| 1233 | 1302 | await saveWorldInfo(file, data); |
| 1234 | 1303 | reloadEditor(file); |
| @@ -1349,7 +1418,7 @@ function registerWorldInfoSlashCommands() { | ||
| 1349 | 1418 | const localEnumProviders = { |
| 1350 | 1419 | /** All possible fields that can be set in a WI entry */ |
| 1351 | 1420 | wiEntryFields: () => Object.entries(newWorldInfoEntryDefinition).map(([key, value]) => |
| 1352 | 1421 | new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : JSON.stringify(value.default))}`, |
| 1353 | 1422 | enumTypes.enum, enumIcons.getDataTypeIcon(value.type))), |
| 1354 | 1423 | |
| 1355 | 1424 | /** All existing UIDs based on the file argument as world name */ |
| @@ -3578,6 +3647,9 @@ export const newWorldInfoEntryDefinition = { | ||
| 3578 | 3647 | sticky: { default: null, type: 'number?' }, |
| 3579 | 3648 | cooldown: { default: null, type: 'number?' }, |
| 3580 | 3649 | delay: { default: null, type: 'number?' }, |
| 3650 | + characterFilterNames: { default: [], type: 'array' }, | |
| 3651 | + characterFilterTags: { default: [], type: 'array' }, | |
| 3652 | + characterFilterExclude: { default: false, type: 'boolean' }, | |
| 3581 | 3653 | }; |
| 3582 | 3654 | |
| 3583 | 3655 | export const newWorldInfoEntryTemplate = Object.fromEntries( |
| @@ -863,8 +863,7 @@ router.post('/search', validateAvatarUrlMiddleware, function (request, response) | ||
| 863 | 863 | |
| 864 | 864 | // Search through title and messages of the chat |
| 865 | 865 | const fragments = query.trim().toLowerCase().split(/\s+/).filter(x => x); |
| 866 | 866 | const text = [path.parse(chatFile.path).name, ...messages.map(message => message?.mes)].join('\n').toLowerCase(); |
| 867 | - ...messages.map(message => message?.mes)].join('\n').toLowerCase(); | |
| 868 | 867 | const hasMatch = fragments.every(fragment => text.includes(fragment)); |
| 869 | 868 | |
| 870 | 869 | if (hasMatch) { |