diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js index 1ef272c..25d90bc 100644 --- a/src/systems/rendering/thoughts.js +++ b/src/systems/rendering/thoughts.js @@ -16,6 +16,34 @@ import { import { saveChatData } from '../../core/persistence.js'; import { getSafeThumbnailUrl } from '../../utils/avatars.js'; +/** + * Fuzzy name matching that handles: + * - Exact matches: "Sabrina" === "Sabrina" + * - Parenthetical additions: "Sabrina" matches "Sabrina (Margrokha's Avatar)" + * - Title additions: "Sabrina" matches "Princess Sabrina" + * - Word boundaries: "Sabrina" won't match "Sabrina's Mother" + * + * @param {string} cardName - Name from the character card + * @param {string} aiName - Name generated by the AI + * @returns {boolean} True if names match + */ +function namesMatch(cardName, aiName) { + if (!cardName || !aiName) return false; + + // 1. Exact match (fast path) + if (cardName.toLowerCase() === aiName.toLowerCase()) return true; + + // 2. Strip parentheses and match + const stripParens = (s) => s.replace(/\s*\([^)]*\)/g, '').trim(); + const cardCore = stripParens(cardName).toLowerCase(); + const aiCore = stripParens(aiName).toLowerCase(); + if (cardCore === aiCore) return true; + + // 3. Check if card name appears as complete word in AI name + const wordBoundary = new RegExp(`\\b${cardCore}\\b`); + return wordBoundary.test(aiCore); +} + /** * Renders character thoughts (Present Characters) panel. * Displays character cards with avatars, relationship badges, and traits. @@ -139,7 +167,7 @@ export function renderThoughts() { if (selected_group) { const groupMembers = getGroupMembers(selected_group); const matchingMember = groupMembers.find(member => - member && member.name && member.name.toLowerCase() === char.name.toLowerCase() + member && member.name && namesMatch(member.name, char.name) ); if (matchingMember && matchingMember.avatar && matchingMember.avatar !== 'none') { @@ -153,7 +181,7 @@ export function renderThoughts() { // For regular chats or if not found in group, search all characters if (characterPortrait === FALLBACK_AVATAR_DATA_URI && characters && characters.length > 0) { const matchingCharacter = characters.find(c => - c && c.name && c.name.toLowerCase() === char.name.toLowerCase() + c && c.name && namesMatch(c.name, char.name) ); if (matchingCharacter && matchingCharacter.avatar && matchingCharacter.avatar !== 'none') { @@ -166,7 +194,7 @@ export function renderThoughts() { // If this is the current character in a 1-on-1 chat, use their portrait if (this_chid !== undefined && characters[this_chid] && - characters[this_chid].name && characters[this_chid].name.toLowerCase() === char.name.toLowerCase()) { + characters[this_chid].name && namesMatch(characters[this_chid].name, char.name)) { const thumbnailUrl = getSafeThumbnailUrl('avatar', characters[this_chid].avatar); if (thumbnailUrl) { characterPortrait = thumbnailUrl;