v3.2.4: Fix Present Characters field editing - relationship badges, custom fields, and avatar upload
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
`);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user