From 105e20e97a16675a22ebdf03e7cb5c1e19e752f7 Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Fri, 13 Feb 2026 18:34:44 +0100 Subject: [PATCH] v3.7.2: Fix status field key generation for parenthetical names & scroll preservation - Fix: Status fields with parenthetical descriptions (e.g., 'Conditions (up to 5 traits)') now use the base name for the JSON key ('conditions' instead of 'conditions_up_to_5_traits') - Fix: Status field value templates no longer repeat the field name with numbered suffixes - Fix: Editing fields in Present Characters no longer scrolls the panel to the top - Updated jsonPromptHelpers.js, parser.js, and userStats.js to use new toFieldKey() helper - Added scroll position preservation to renderThoughts() when re-rendering after field edits --- README.md | 7 ++---- manifest.json | 2 +- settings.html | 2 +- src/systems/generation/jsonPromptHelpers.js | 17 +++++++++++++-- src/systems/generation/parser.js | 24 +++++++++++++++++---- src/systems/rendering/thoughts.js | 23 +++++++++++++++++--- src/systems/rendering/userStats.js | 20 ++++++++++++++--- 7 files changed, 76 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 6fd067e..b5f7e34 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,9 @@ An immersive RPG extension for browsers that tracks character stats, scene infor ## 🆕 What's New -### v3.7.1 +### v3.7.2 -- Improved instructions for the model to generate dynamic weather. -- Small fixes and updates to descriptions. -- Fixed a scroll/viewport bug that moved everything up after you edited fields. -- Changed the display of avatars for present characters. +- Minor bug fixes **Special thanks to all the other contributors for this project:** Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, Tomt610, and Jakstein! diff --git a/manifest.json b/manifest.json index 26ae263..ff16a2a 100644 --- a/manifest.json +++ b/manifest.json @@ -6,6 +6,6 @@ "js": "index.js", "css": "style.css", "author": "Marinara", - "version": "3.7.1", + "version": "3.7.2", "homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern" } diff --git a/settings.html b/settings.html index 4acebed..6226b02 100644 --- a/settings.html +++ b/settings.html @@ -49,7 +49,7 @@
- v3.7.1 + v3.7.2
diff --git a/src/systems/generation/jsonPromptHelpers.js b/src/systems/generation/jsonPromptHelpers.js index 26478cf..2d66deb 100644 --- a/src/systems/generation/jsonPromptHelpers.js +++ b/src/systems/generation/jsonPromptHelpers.js @@ -21,6 +21,19 @@ function toSnakeCase(name) { .replace(/^_+|_+$/g, ''); } +/** + * Extracts the base name (before parentheses) and converts to snake_case for use as JSON key. + * Parenthetical content is treated as a description/hint, not part of the key. + * Example: "Conditions (up to 5 traits)" -> "conditions" + * Example: "Status Effects" -> "status_effects" + * @param {string} name - Field name, possibly with parenthetical description + * @returns {string} snake_case key from the base name only + */ +function toFieldKey(name) { + const baseName = name.replace(/\s*\(.*\)\s*$/, '').trim(); + return toSnakeCase(baseName); +} + /** * Builds User Stats JSON format instruction * @returns {string} JSON format instruction for user stats @@ -60,12 +73,12 @@ export function buildUserStatsJSONInstruction() { if (customFields.length > 0) { for (let i = 0; i < customFields.length; i++) { const fieldName = customFields[i].toLowerCase(); - const fieldKey = toSnakeCase(fieldName); + const fieldKey = toFieldKey(fieldName); const comma = (i === customFields.length - 1 && !userStatsConfig.statusSection.showMoodEmoji) ? '' : (userStatsConfig.statusSection.showMoodEmoji || i < customFields.length - 1 ? ',\n' : '\n'); if (i === 0 && userStatsConfig.statusSection.showMoodEmoji) { instruction += ',\n'; } - instruction += ` "${fieldKey}": "[${fieldName}1, ${fieldName}2]"${comma}`; + instruction += ` "${fieldKey}": "[${fieldName}]"${comma}`; } } if (!userStatsConfig.statusSection.showMoodEmoji && customFields.length > 0) { diff --git a/src/systems/generation/parser.js b/src/systems/generation/parser.js index 8bb0da0..7a5eaab 100644 --- a/src/systems/generation/parser.js +++ b/src/systems/generation/parser.js @@ -9,6 +9,20 @@ import { saveSettings } from '../../core/persistence.js'; import { extractInventory } from './inventoryParser.js'; import { repairJSON } from '../../utils/jsonRepair.js'; +/** + * Extracts the base name (before parentheses) and converts to snake_case for use as JSON key. + * Example: "Conditions (up to 5 traits)" -> "conditions" + * @param {string} name - Field name, possibly with parenthetical description + * @returns {string} snake_case key from the base name only + */ +function toFieldKey(name) { + const baseName = name.replace(/\s*\(.*\)\s*$/, '').trim(); + return baseName + .toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_+|_+$/g, ''); +} + /** * Helper to separate emoji from text in a string * Handles cases where there's no comma or space after emoji @@ -559,10 +573,12 @@ export function parseUserStats(statsText) { const trackerConfig = extensionSettings.trackerConfig; const customFields = trackerConfig?.userStats?.statusSection?.customFields || []; for (const fieldName of customFields) { - const fieldKey = fieldName.toLowerCase(); - if (statsData.status[fieldKey]) { - extensionSettings.userStats[fieldKey] = statsData.status[fieldKey]; - // console.log(`[RPG Parser] ✓ Set ${fieldKey} =`, statsData.status[fieldKey]); + const fieldKey = toFieldKey(fieldName); + // Try the base key first (e.g., "conditions"), then fall back to full lowercase name + const value = statsData.status[fieldKey] || statsData.status[fieldName.toLowerCase()]; + if (value) { + extensionSettings.userStats[fieldKey] = value; + // console.log(`[RPG Parser] ✓ Set ${fieldKey} =`, value); } } } diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js index 0876c78..5cc4a93 100644 --- a/src/systems/rendering/thoughts.js +++ b/src/systems/rendering/thoughts.js @@ -153,11 +153,20 @@ function namesMatch(cardName, aiName) { * Displays character cards with avatars, relationship badges, and traits. * Includes event listeners for editable character fields. */ -export function renderThoughts() { +export function renderThoughts({ preserveScroll = false } = {}) { if (!extensionSettings.showCharacterThoughts || !$thoughtsContainer) { return; } + // Save scroll position before re-render if requested + let savedContentScroll = 0; + if (preserveScroll) { + const $content = $thoughtsContainer.find('.rpg-thoughts-content'); + if ($content.length) { + savedContentScroll = $content[0].scrollTop; + } + } + // Don't render if no data exists (e.g., after cache clear) const thoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts; if (!thoughtsData) { @@ -714,6 +723,14 @@ export function renderThoughts() { setTimeout(() => $thoughtsContainer.removeClass('rpg-content-updating'), 600); } + // Restore scroll position after re-render + if (preserveScroll) { + const $content = $thoughtsContainer.find('.rpg-thoughts-content'); + if ($content.length) { + $content[0].scrollTop = savedContentScroll; + } + } + // Update chat overlay if enabled if (extensionSettings.showThoughtsInChat) { updateChatThoughts(); @@ -1147,8 +1164,8 @@ export function updateCharacterField(characterName, field, value) { // 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(); + // Re-render the thoughts panel to show updated value (preserve scroll position) + renderThoughts({ preserveScroll: true }); // Update chat thought overlays if editing thoughts const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts'; diff --git a/src/systems/rendering/userStats.js b/src/systems/rendering/userStats.js index fa9ddaa..7706f7a 100644 --- a/src/systems/rendering/userStats.js +++ b/src/systems/rendering/userStats.js @@ -23,6 +23,20 @@ import { isItemLocked, setItemLock } from '../generation/lockManager.js'; import { updateFabWidgets } from '../ui/mobile.js'; import { getStatBarColors } from '../ui/theme.js'; +/** + * Extracts the base name (before parentheses) and converts to snake_case for use as JSON key. + * Example: "Conditions (up to 5 traits)" -> "conditions" + * @param {string} name - Field name, possibly with parenthetical description + * @returns {string} snake_case key from the base name only + */ +function toFieldKey(name) { + const baseName = name.replace(/\s*\(.*\)\s*$/, '').trim(); + return baseName + .toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_+|_+$/g, ''); +} + /** * Builds the user stats text string using custom stat names * @returns {string} Formatted stats text for tracker @@ -107,7 +121,7 @@ function updateUserStatsData() { // Then, add any other numeric stats from extensionSettings that aren't in config // (these could be custom stats the AI added or disabled stats) const customFields = config.statusSection?.customFields || []; - const excludeFields = new Set(['mood', ...customFields.map(f => f.toLowerCase()), 'inventory', 'skills', 'level']); + const excludeFields = new Set(['mood', ...customFields.map(f => toFieldKey(f)), 'inventory', 'skills', 'level']); Object.entries(stats).forEach(([key, value]) => { if (!processedIds.has(key) && !excludeFields.has(key) && typeof value === 'number') { statsArray.push({ @@ -127,7 +141,7 @@ function updateUserStatsData() { // Add all custom status fields for (const fieldName of customFields) { - const fieldKey = fieldName.toLowerCase(); + const fieldKey = toFieldKey(fieldName); jsonData.status[fieldKey] = stats[fieldKey] || 'None'; } @@ -334,7 +348,7 @@ export function renderUserStats() { // Render custom status fields if (config.statusSection.customFields && config.statusSection.customFields.length > 0) { for (const fieldName of config.statusSection.customFields) { - const fieldKey = fieldName.toLowerCase(); + const fieldKey = toFieldKey(fieldName); let fieldValue = stats[fieldKey] || 'None'; // Handle array format (from JSON) if (Array.isArray(fieldValue)) {