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
This commit is contained in:
@@ -7,12 +7,9 @@ An immersive RPG extension for browsers that tracks character stats, scene infor
|
|||||||
|
|
||||||
## 🆕 What's New
|
## 🆕 What's New
|
||||||
|
|
||||||
### v3.7.1
|
### v3.7.2
|
||||||
|
|
||||||
- Improved instructions for the model to generate dynamic weather.
|
- Minor bug fixes
|
||||||
- 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.
|
|
||||||
|
|
||||||
**Special thanks to all the other contributors for this project:**
|
**Special thanks to all the other contributors for this project:**
|
||||||
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, Tomt610, and Jakstein!
|
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, Tomt610, and Jakstein!
|
||||||
|
|||||||
+1
-1
@@ -6,6 +6,6 @@
|
|||||||
"js": "index.js",
|
"js": "index.js",
|
||||||
"css": "style.css",
|
"css": "style.css",
|
||||||
"author": "Marinara",
|
"author": "Marinara",
|
||||||
"version": "3.7.1",
|
"version": "3.7.2",
|
||||||
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
|
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
|
||||||
v3.7.1
|
v3.7.2
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,19 @@ function toSnakeCase(name) {
|
|||||||
.replace(/^_+|_+$/g, '');
|
.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
|
* Builds User Stats JSON format instruction
|
||||||
* @returns {string} JSON format instruction for user stats
|
* @returns {string} JSON format instruction for user stats
|
||||||
@@ -60,12 +73,12 @@ export function buildUserStatsJSONInstruction() {
|
|||||||
if (customFields.length > 0) {
|
if (customFields.length > 0) {
|
||||||
for (let i = 0; i < customFields.length; i++) {
|
for (let i = 0; i < customFields.length; i++) {
|
||||||
const fieldName = customFields[i].toLowerCase();
|
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');
|
const comma = (i === customFields.length - 1 && !userStatsConfig.statusSection.showMoodEmoji) ? '' : (userStatsConfig.statusSection.showMoodEmoji || i < customFields.length - 1 ? ',\n' : '\n');
|
||||||
if (i === 0 && userStatsConfig.statusSection.showMoodEmoji) {
|
if (i === 0 && userStatsConfig.statusSection.showMoodEmoji) {
|
||||||
instruction += ',\n';
|
instruction += ',\n';
|
||||||
}
|
}
|
||||||
instruction += ` "${fieldKey}": "[${fieldName}1, ${fieldName}2]"${comma}`;
|
instruction += ` "${fieldKey}": "[${fieldName}]"${comma}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!userStatsConfig.statusSection.showMoodEmoji && customFields.length > 0) {
|
if (!userStatsConfig.statusSection.showMoodEmoji && customFields.length > 0) {
|
||||||
|
|||||||
@@ -9,6 +9,20 @@ import { saveSettings } from '../../core/persistence.js';
|
|||||||
import { extractInventory } from './inventoryParser.js';
|
import { extractInventory } from './inventoryParser.js';
|
||||||
import { repairJSON } from '../../utils/jsonRepair.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
|
* Helper to separate emoji from text in a string
|
||||||
* Handles cases where there's no comma or space after emoji
|
* Handles cases where there's no comma or space after emoji
|
||||||
@@ -559,10 +573,12 @@ export function parseUserStats(statsText) {
|
|||||||
const trackerConfig = extensionSettings.trackerConfig;
|
const trackerConfig = extensionSettings.trackerConfig;
|
||||||
const customFields = trackerConfig?.userStats?.statusSection?.customFields || [];
|
const customFields = trackerConfig?.userStats?.statusSection?.customFields || [];
|
||||||
for (const fieldName of customFields) {
|
for (const fieldName of customFields) {
|
||||||
const fieldKey = fieldName.toLowerCase();
|
const fieldKey = toFieldKey(fieldName);
|
||||||
if (statsData.status[fieldKey]) {
|
// Try the base key first (e.g., "conditions"), then fall back to full lowercase name
|
||||||
extensionSettings.userStats[fieldKey] = statsData.status[fieldKey];
|
const value = statsData.status[fieldKey] || statsData.status[fieldName.toLowerCase()];
|
||||||
// console.log(`[RPG Parser] ✓ Set ${fieldKey} =`, statsData.status[fieldKey]);
|
if (value) {
|
||||||
|
extensionSettings.userStats[fieldKey] = value;
|
||||||
|
// console.log(`[RPG Parser] ✓ Set ${fieldKey} =`, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,11 +153,20 @@ function namesMatch(cardName, aiName) {
|
|||||||
* Displays character cards with avatars, relationship badges, and traits.
|
* Displays character cards with avatars, relationship badges, and traits.
|
||||||
* Includes event listeners for editable character fields.
|
* Includes event listeners for editable character fields.
|
||||||
*/
|
*/
|
||||||
export function renderThoughts() {
|
export function renderThoughts({ preserveScroll = false } = {}) {
|
||||||
if (!extensionSettings.showCharacterThoughts || !$thoughtsContainer) {
|
if (!extensionSettings.showCharacterThoughts || !$thoughtsContainer) {
|
||||||
return;
|
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)
|
// Don't render if no data exists (e.g., after cache clear)
|
||||||
const thoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts;
|
const thoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts;
|
||||||
if (!thoughtsData) {
|
if (!thoughtsData) {
|
||||||
@@ -714,6 +723,14 @@ export function renderThoughts() {
|
|||||||
setTimeout(() => $thoughtsContainer.removeClass('rpg-content-updating'), 600);
|
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
|
// Update chat overlay if enabled
|
||||||
if (extensionSettings.showThoughtsInChat) {
|
if (extensionSettings.showThoughtsInChat) {
|
||||||
updateChatThoughts();
|
updateChatThoughts();
|
||||||
@@ -1147,8 +1164,8 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
// console.log('[RPG Companion] JSON format updated successfully');
|
// console.log('[RPG Companion] JSON format updated successfully');
|
||||||
// console.log('[RPG Companion] Updated data:', lastGeneratedData.characterThoughts);
|
// console.log('[RPG Companion] Updated data:', lastGeneratedData.characterThoughts);
|
||||||
|
|
||||||
// Re-render the thoughts panel to show updated value
|
// Re-render the thoughts panel to show updated value (preserve scroll position)
|
||||||
renderThoughts();
|
renderThoughts({ preserveScroll: true });
|
||||||
|
|
||||||
// Update chat thought overlays if editing thoughts
|
// Update chat thought overlays if editing thoughts
|
||||||
const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts';
|
const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts';
|
||||||
|
|||||||
@@ -23,6 +23,20 @@ import { isItemLocked, setItemLock } from '../generation/lockManager.js';
|
|||||||
import { updateFabWidgets } from '../ui/mobile.js';
|
import { updateFabWidgets } from '../ui/mobile.js';
|
||||||
import { getStatBarColors } from '../ui/theme.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
|
* Builds the user stats text string using custom stat names
|
||||||
* @returns {string} Formatted stats text for tracker
|
* @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
|
// Then, add any other numeric stats from extensionSettings that aren't in config
|
||||||
// (these could be custom stats the AI added or disabled stats)
|
// (these could be custom stats the AI added or disabled stats)
|
||||||
const customFields = config.statusSection?.customFields || [];
|
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]) => {
|
Object.entries(stats).forEach(([key, value]) => {
|
||||||
if (!processedIds.has(key) && !excludeFields.has(key) && typeof value === 'number') {
|
if (!processedIds.has(key) && !excludeFields.has(key) && typeof value === 'number') {
|
||||||
statsArray.push({
|
statsArray.push({
|
||||||
@@ -127,7 +141,7 @@ function updateUserStatsData() {
|
|||||||
|
|
||||||
// Add all custom status fields
|
// Add all custom status fields
|
||||||
for (const fieldName of customFields) {
|
for (const fieldName of customFields) {
|
||||||
const fieldKey = fieldName.toLowerCase();
|
const fieldKey = toFieldKey(fieldName);
|
||||||
jsonData.status[fieldKey] = stats[fieldKey] || 'None';
|
jsonData.status[fieldKey] = stats[fieldKey] || 'None';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +348,7 @@ export function renderUserStats() {
|
|||||||
// Render custom status fields
|
// Render custom status fields
|
||||||
if (config.statusSection.customFields && config.statusSection.customFields.length > 0) {
|
if (config.statusSection.customFields && config.statusSection.customFields.length > 0) {
|
||||||
for (const fieldName of config.statusSection.customFields) {
|
for (const fieldName of config.statusSection.customFields) {
|
||||||
const fieldKey = fieldName.toLowerCase();
|
const fieldKey = toFieldKey(fieldName);
|
||||||
let fieldValue = stats[fieldKey] || 'None';
|
let fieldValue = stats[fieldKey] || 'None';
|
||||||
// Handle array format (from JSON)
|
// Handle array format (from JSON)
|
||||||
if (Array.isArray(fieldValue)) {
|
if (Array.isArray(fieldValue)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user