From d658e337f6634663c7d9b6e659b35da69c11a6bc Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Thu, 13 Nov 2025 16:18:35 +0100 Subject: [PATCH] Fix: Support multiple character variants in Present Characters panel Fixed issues when AI generates multiple character variants (e.g., storyteller mode with 'Dottore (Prime)', 'Dottore (Beta)', etc.): 1. Escape quotes in character names to prevent HTML attribute breakage - Added escapeHtmlAttr() helper function - Prevents names like 'Marianna "Mari"' from breaking HTML 2. Restore avatar lookup for character variants - namesMatch() now strips parentheses and quotes from both sides - Allows 'Dottore (Prime)' to find 'Dottore' character card avatar - Each variant still gets its own card with separate attributes 3. Multiple characters now display correctly in panel - Each variant creates its own character object - Attributes (Details, Relationship, Stats, Thoughts) don't mix - All characters appear in the panel, not just the last one --- src/systems/generation/promptBuilder.js | 2 +- src/systems/rendering/thoughts.js | 52 ++++++++++++++++--------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index 62be66c..456acde 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -256,7 +256,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // Relationship line (only if relationships are enabled) if (relationshipPlaceholders) { - instructions += `Relationship: [${relationshipPlaceholders}]\n`; + instructions += `Relationship: [(choose one: ${relationshipPlaceholders})]\n`; } // Stats line (if enabled) diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js index df8029b..5f8a3cd 100644 --- a/src/systems/rendering/thoughts.js +++ b/src/systems/rendering/thoughts.js @@ -27,6 +27,14 @@ function debugLog(message, data = null) { } } +/** + * Escapes HTML attribute values to prevent quotes from breaking HTML + */ +function escapeHtmlAttr(str) { + if (!str) return ''; + return String(str).replace(/"/g, '"').replace(/'/g, '''); +} + /** * Interpolates color based on percentage value between low and high colors * @param {number} percentage - Value from 0-100 @@ -78,10 +86,12 @@ function namesMatch(cardName, aiName) { // 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(); + // 2. Strip parentheses and quotes from both names and match + // This allows "Dottore (Prime)" to match "Dottore" card for avatar lookup + // and "Marianna "Mari"" to match "Marianna" or "Mari" cards + const stripParensAndQuotes = (s) => s.replace(/\s*\([^)]*\)/g, '').replace(/["']/g, '').trim(); + const cardCore = stripParensAndQuotes(cardName).toLowerCase(); + const aiCore = stripParensAndQuotes(aiName).toLowerCase(); if (cardCore === aiCore) return true; // 3. Check if card name appears as complete word in AI name @@ -249,17 +259,19 @@ export function renderThoughts() { defaultName = characters[this_chid].name || 'Character'; } + const escapedDefaultName = escapeHtmlAttr(defaultName); + html += '
'; html += ` -
+
- ${defaultName} -
⚖️
+ ${escapedDefaultName} +
⚖️
- 😊 - ${defaultName} + 😊 + ${defaultName}
`; @@ -267,7 +279,7 @@ export function renderThoughts() { for (const field of enabledFields) { const fieldId = field.name.toLowerCase().replace(/\s+/g, '-'); html += ` -
+
`; } @@ -361,17 +373,20 @@ export function renderThoughts() { debugLog(`[RPG Thoughts] Building HTML card for ${char.name}...`); + // Escape character name for use in HTML attributes + const escapedName = escapeHtmlAttr(char.name); + html += ` -
+
- ${char.name} - ${hasRelationshipEnabled ? `
${relationshipBadge}
` : ''} + ${escapedName} + ${hasRelationshipEnabled ? `
${relationshipBadge}
` : ''}
- ${char.emoji} - ${char.name} + ${char.emoji} + ${char.name}
`; @@ -380,7 +395,7 @@ export function renderThoughts() { const fieldValue = char[field.name] || ''; const fieldId = field.name.toLowerCase().replace(/\s+/g, '-'); html += ` -
${fieldValue}
+
${fieldValue}
`; } @@ -396,7 +411,7 @@ export function renderThoughts() { const statColor = getStatColor(statValue, extensionSettings.statBarColorLow, extensionSettings.statBarColorHigh); html += `
- ${stat.name}: ${statValue}% + ${stat.name}: ${statValue}%
`; } @@ -817,12 +832,13 @@ export function createThoughtPanel($message, thoughtsArray) { // Build thought bubbles HTML let thoughtsHtml = ''; thoughtsArray.forEach((thought, index) => { + const escapedThoughtName = escapeHtmlAttr(thought.name); thoughtsHtml += `
${thought.emoji}
-
+
${thought.thought}