From e5bd1e0411cb18d20b40e981ec1a2038993f6867 Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Thu, 8 Jan 2026 18:13:12 +0100 Subject: [PATCH] v3.2.4: Fix Present Characters field editing - relationship badges, custom fields, and avatar upload --- README.md | 4 +- index.js | 4 +- manifest.json | 2 +- settings.html | 2 +- src/systems/rendering/infoBox.js | 2 +- src/systems/rendering/thoughts.js | 197 ++++++++++++++++++++++++++---- style.css | 12 +- 7 files changed, 190 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 82efc30..a0e2e0d 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ An immersive RPG extension for browsers that tracks character stats, scene infor ## 🆕 What's New -### v3.2.3 +### v3.2.4 -- Uploading avatars for generated characters is back. +- Minor bug fixes. **Special thanks to all the other contributors for this project:** Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, and Amauragis. diff --git a/index.js b/index.js index f267039..e9ce379 100644 --- a/index.js +++ b/index.js @@ -1004,7 +1004,7 @@ jQuery(async () => { // This cleans historical messages when displayed // Note: We also clean directly in message handler for redundancy try { - console.log('[RPG Companion] Checking JSON cleaning regex. Generation mode:', extensionSettings.generationMode); + // console.log('[RPG Companion] Checking JSON cleaning regex. Generation mode:', extensionSettings.generationMode); if (extensionSettings.generationMode === 'together') { await ensureJsonCleaningRegex(st_extension_settings, saveSettingsDebounced); } else { @@ -1073,7 +1073,7 @@ jQuery(async () => { // Non-critical - continue without it } - // console.log('[RPG Companion] ✅ Extension loaded successfully'); + console.log('[RPG Companion] ✅ Extension loaded successfully.'); } catch (error) { console.error('[RPG Companion] ❌ Critical initialization failure:', error); console.error('[RPG Companion] Error details:', error.message, error.stack); diff --git a/manifest.json b/manifest.json index 98d4183..a811032 100644 --- a/manifest.json +++ b/manifest.json @@ -6,6 +6,6 @@ "js": "index.js", "css": "style.css", "author": "Marinara", - "version": "3.2.3", + "version": "3.2.4", "homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern" } diff --git a/settings.html b/settings.html index 29eaf4d..1520da7 100644 --- a/settings.html +++ b/settings.html @@ -48,7 +48,7 @@
- v3.2.3 + v3.2.4
diff --git a/src/systems/rendering/infoBox.js b/src/systems/rendering/infoBox.js index 0538068..89f39de 100644 --- a/src/systems/rendering/infoBox.js +++ b/src/systems/rendering/infoBox.js @@ -363,7 +363,7 @@ export function renderInfoBox() {
${dateLockIconHtml}
${monthDisplay}
-
${weekdayDisplay}
+
${weekdayDisplay}
${yearDisplay}
`); diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js index 4a16521..db5c5f3 100644 --- a/src/systems/rendering/thoughts.js +++ b/src/systems/rendering/thoughts.js @@ -184,6 +184,9 @@ export function renderThoughts() { // Use committedTrackerData as fallback if lastGeneratedData is empty (e.g., after page refresh) const characterThoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts || ''; + // console.log('[RPG Companion] renderThoughts - Reading from lastGeneratedData:', JSON.stringify(lastGeneratedData.characterThoughts)); + // console.log('[RPG Companion] renderThoughts - Reading from committedTrackerData:', JSON.stringify(committedTrackerData.characterThoughts)); + debugLog('[RPG Thoughts] Raw characterThoughts data:', characterThoughtsData); debugLog('[RPG Thoughts] Data length:', characterThoughtsData.length + ' chars'); debugLog('[RPG Thoughts] Enabled custom fields:', enabledFields.map(f => f.name)); @@ -210,25 +213,37 @@ export function renderThoughts() { // Extract details (appearance, demeanor, etc.) if (char.details) { - // Map details object to custom fields by snake_case name + // Map details object to custom fields for (const field of enabledFields) { - const fieldKey = toSnakeCase(field.name); - if (char.details[fieldKey] !== undefined) { - character[field.name] = stripBrackets(char.details[fieldKey]); + // First try exact field name (for manually edited values) + if (char.details[field.name] !== undefined) { + character[field.name] = stripBrackets(char.details[field.name]); + } else { + // Fall back to snake_case for AI-generated values + const fieldKey = toSnakeCase(field.name); + if (char.details[fieldKey] !== undefined) { + character[field.name] = stripBrackets(char.details[fieldKey]); + } } } } // Also check for fields at root level (for backward compatibility) + // Only use if not already set from details for (const field of enabledFields) { - const fieldKey = toSnakeCase(field.name); - if (char[fieldKey] !== undefined) { - character[field.name] = stripBrackets(char[fieldKey]); + if (character[field.name] === undefined) { + const fieldKey = toSnakeCase(field.name); + if (char[fieldKey] !== undefined) { + character[field.name] = stripBrackets(char[fieldKey]); + } } } // Extract relationship - if (char.relationship) { + // Prefer the new flat format (char.Relationship) over the old nested format (char.relationship.status) + if (char.Relationship) { + character.Relationship = stripBrackets(char.Relationship); + } else if (char.relationship) { character.Relationship = stripBrackets(char.relationship.status || char.relationship); } @@ -498,8 +513,11 @@ export function renderThoughts() { if (hasRelationshipEnabled) { // In the new format, relationship is always stored in char.Relationship if (char.Relationship) { + // console.log(`[RPG Companion] Rendering ${char.name} - char.Relationship:`, char.Relationship); + // console.log('[RPG Companion] relationshipEmojis mapping:', relationshipEmojis); // Try to map text to emoji relationshipBadge = relationshipEmojis[char.Relationship] || char.Relationship; + // console.log('[RPG Companion] Final relationshipBadge:', relationshipBadge); } } @@ -596,6 +614,11 @@ export function renderThoughts() { updateCharacterField(character, field, value); }); + // Prevent click events on editable elements from bubbling to avatar upload handler + $thoughtsContainer.find('.rpg-editable').on('click mousedown', function(e) { + e.stopPropagation(); + }); + // Add event listener for section lock icon clicks (support both click and touch) $thoughtsContainer.find('.rpg-section-lock-icon').on('click touchend', function(e) { e.preventDefault(); @@ -688,12 +711,139 @@ export function updateCharacterField(characterName, field, value) { lastGeneratedData.characterThoughts = 'Present Characters\n---\n'; } - const lines = lastGeneratedData.characterThoughts.split('\n'); const presentCharsConfig = extensionSettings.trackerConfig?.presentCharacters; const enabledFields = presentCharsConfig?.customFields?.filter(f => f && f.enabled && f.name) || []; const characterStats = presentCharsConfig?.characterStats; const enabledCharStats = characterStats?.enabled && characterStats?.customStats?.filter(s => s && s.enabled && s.name) || []; + // Get relationship emoji mappings from config + const relationshipEmojis = presentCharsConfig?.relationshipEmojis || { + 'Enemy': '⚔️', + 'Neutral': '⚖️', + 'Friend': '⭐', + 'Lover': '❤️' + }; + // Create reverse mapping (emoji → name) + const emojiToRelationship = {}; + for (const [name, emoji] of Object.entries(relationshipEmojis)) { + emojiToRelationship[emoji] = name; + } + + // Check if data is in JSON format + let isJSON = false; + let parsedData = null; + + try { + parsedData = typeof lastGeneratedData.characterThoughts === 'string' + ? JSON.parse(lastGeneratedData.characterThoughts) + : lastGeneratedData.characterThoughts; + + if (Array.isArray(parsedData) || (parsedData && parsedData.characters)) { + isJSON = true; + } + } catch (e) { + // Not JSON, continue with text format + } + + // Handle JSON format + if (isJSON) { + const charactersArray = Array.isArray(parsedData) ? parsedData : (parsedData.characters || []); + const charIndex = charactersArray.findIndex(c => + c.name && c.name.toLowerCase() === characterName.toLowerCase() + ); + + if (charIndex !== -1) { + const char = charactersArray[charIndex]; + + // console.log('[RPG Companion] Updating character:', characterName, 'field:', field, 'value:', value); + // console.log('[RPG Companion] Before update - char.Relationship:', char.Relationship); + + // Update the appropriate field + if (field === 'name') { + char.name = value; + } else if (field === 'emoji') { + char.emoji = value; + } else if (field === 'Relationship') { + // Store relationship as text, converting emoji if needed + // First check if it's an emoji → convert to text + if (emojiToRelationship[value]) { + char.Relationship = emojiToRelationship[value]; + } else { + // It's text - find matching relationship name (case-insensitive) + const matchingRelationship = Object.keys(relationshipEmojis).find( + name => name.toLowerCase() === value.toLowerCase() + ); + char.Relationship = matchingRelationship || value; + } + // console.log('[RPG Companion] After update - char.Relationship:', char.Relationship); + // console.log('[RPG Companion] relationshipEmojis:', relationshipEmojis); + // console.log('[RPG Companion] emojiToRelationship:', emojiToRelationship); + } else if (field.toLowerCase() === 'thoughts' || field === (presentCharsConfig?.thoughts?.name || 'Thoughts')) { + if (!char.thoughts) char.thoughts = {}; + char.thoughts.content = value; + } else { + // Check if it's a character stat + const isStatField = enabledCharStats.findIndex(s => s.name === field) !== -1; + if (isStatField) { + if (!char.stats) char.stats = {}; + let numValue = parseInt(value.replace('%', '').trim()); + if (isNaN(numValue)) numValue = 0; + numValue = Math.max(0, Math.min(100, numValue)); + char.stats[field] = numValue; + } else { + // It's a custom detail field + if (!char.details) char.details = {}; + char.details[field] = value; + } + } + } + + // Save back to lastGeneratedData + lastGeneratedData.characterThoughts = Array.isArray(parsedData) ? charactersArray : { ...parsedData, characters: charactersArray }; + committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts; + + // console.log('[RPG Companion] Saved to lastGeneratedData.characterThoughts:', JSON.stringify(lastGeneratedData.characterThoughts)); + // console.log('[RPG Companion] Saved to committedTrackerData.characterThoughts:', JSON.stringify(committedTrackerData.characterThoughts)); + + // Update in chat metadata + const chat = getContext().chat; + if (chat && chat.length > 0) { + for (let i = chat.length - 1; i >= 0; i--) { + const message = chat[i]; + if (!message.is_user) { + if (message.extra && message.extra.rpg_companion_swipes) { + const swipeId = message.swipe_id || 0; + if (message.extra.rpg_companion_swipes[swipeId]) { + message.extra.rpg_companion_swipes[swipeId].characterThoughts = lastGeneratedData.characterThoughts; + } + } + break; + } + } + } + + saveChatData(); + + // console.log('[RPG Companion] JSON format updated successfully'); + // console.log('[RPG Companion] Updated data:', lastGeneratedData.characterThoughts); + + // Re-render the thoughts panel to show updated value + renderThoughts(); + + // Update chat thought overlays if editing thoughts + const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts'; + const isEditingThoughts = field === thoughtsFieldName || field === 'thoughts'; + + if (isEditingThoughts && extensionSettings.showThoughtsInChat) { + updateChatThoughts(); + } + + return; // Exit early for JSON format + } + + // Continue with text format handling below + const lines = lastGeneratedData.characterThoughts.split('\n'); + let characterFound = false; let inTargetCharacter = false; let characterStartIndex = -1; @@ -893,23 +1043,26 @@ export function updateCharacterField(characterName, field, value) { saveChatData(); - // Re-render the sidebar thoughts panel, but skip updating chat bubbles if editing thoughts + // Don't re-render to avoid overwriting user edits while they're still editing + // The changes are already saved to lastGeneratedData and committedTrackerData + // Re-rendering would cause the display to reset and lose focus + + // console.log('[RPG Companion] updateCharacterField called:', { characterName, field, value }); + // console.log('[RPG Companion] Before update - lastGeneratedData.characterThoughts:', lastGeneratedData.characterThoughts); + + // Only update chat thought overlays if editing thoughts field const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts'; const isEditingThoughts = field === thoughtsFieldName || field === 'thoughts'; - if (!isEditingThoughts) { - renderThoughts(); - } else { - // Only update sidebar, don't trigger chat bubble refresh - $thoughtsContainer.find('.rpg-editable').off('blur'); - renderThoughtsSidebarOnly(); - $thoughtsContainer.find('.rpg-editable').on('blur', function() { - const char = $(this).data('character'); - const fld = $(this).data('field'); - const val = $(this).text().trim(); - updateCharacterField(char, fld, val); - }); + // console.log('[RPG Companion] Is editing thoughts?', isEditingThoughts, 'Field:', field, 'Thoughts field name:', thoughtsFieldName); + // console.log('[RPG Companion] After update - lastGeneratedData.characterThoughts:', lastGeneratedData.characterThoughts); + + if (isEditingThoughts && extensionSettings.showThoughtsInChat) { + // console.log('[RPG Companion] Updating chat thought bubbles'); + // Update chat thought bubbles when thoughts are edited + updateChatThoughts(); } + // Note: Don't call renderThoughts() here - it would overwrite the user's edits } /** diff --git a/style.css b/style.css index 2aee58f..751d8a5 100644 --- a/style.css +++ b/style.css @@ -1390,15 +1390,19 @@ body:has(.rpg-panel.rpg-position-left) #sheld { display: flex; align-items: center; justify-content: center; - word-wrap: break-word; - overflow-wrap: break-word; - white-space: normal; max-width: 100%; - line-height: 1.2; + line-height: 1.1; min-width: 0; overflow: hidden; } +.rpg-calendar-day-text { + max-width: 100%; + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; +} + .rpg-calendar-year { font-size: clamp(0.5rem, 3.5cqh, 0.625rem); color: var(--rpg-text);