v3.2.4: Fix Present Characters field editing - relationship badges, custom fields, and avatar upload

This commit is contained in:
Spicy_Marinara
2026-01-08 18:13:12 +01:00
parent 055c19951c
commit e5bd1e0411
7 changed files with 190 additions and 33 deletions
+2 -2
View File
@@ -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.
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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"
}
+1 -1
View File
@@ -48,7 +48,7 @@
</div>
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
v3.2.3
v3.2.4
</div>
</div>
</div>
+1 -1
View File
@@ -363,7 +363,7 @@ export function renderInfoBox() {
<div class="rpg-dashboard-widget rpg-calendar-widget">
${dateLockIconHtml}
<div class="rpg-calendar-top rpg-editable" contenteditable="true" data-field="month" data-full-value="${data.month || ''}" title="Click to edit">${monthDisplay}</div>
<div class="rpg-calendar-day rpg-editable" contenteditable="true" data-field="weekday" data-full-value="${data.weekday || ''}" title="Click to edit">${weekdayDisplay}</div>
<div class="rpg-calendar-day" title="Click to edit"><span class="rpg-calendar-day-text rpg-editable" contenteditable="true" data-field="weekday" data-full-value="${data.weekday || ''}">${weekdayDisplay}</span></div>
<div class="rpg-calendar-year rpg-editable" contenteditable="true" data-field="year" data-full-value="${data.year || ''}" title="Click to edit">${yearDisplay}</div>
</div>
`);
+169 -16
View File
@@ -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) {
// 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) {
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
}
/**
+8 -4
View File
@@ -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);