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
|
## 🆕 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:**
|
**Special thanks to all the other contributors for this project:**
|
||||||
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, and Amauragis.
|
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, and Amauragis.
|
||||||
|
|||||||
@@ -1004,7 +1004,7 @@ jQuery(async () => {
|
|||||||
// This cleans historical messages when displayed
|
// This cleans historical messages when displayed
|
||||||
// Note: We also clean directly in message handler for redundancy
|
// Note: We also clean directly in message handler for redundancy
|
||||||
try {
|
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') {
|
if (extensionSettings.generationMode === 'together') {
|
||||||
await ensureJsonCleaningRegex(st_extension_settings, saveSettingsDebounced);
|
await ensureJsonCleaningRegex(st_extension_settings, saveSettingsDebounced);
|
||||||
} else {
|
} else {
|
||||||
@@ -1073,7 +1073,7 @@ jQuery(async () => {
|
|||||||
// Non-critical - continue without it
|
// Non-critical - continue without it
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('[RPG Companion] ✅ Extension loaded successfully');
|
console.log('[RPG Companion] ✅ Extension loaded successfully.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] ❌ Critical initialization failure:', error);
|
console.error('[RPG Companion] ❌ Critical initialization failure:', error);
|
||||||
console.error('[RPG Companion] Error details:', error.message, error.stack);
|
console.error('[RPG Companion] Error details:', error.message, error.stack);
|
||||||
|
|||||||
+1
-1
@@ -6,6 +6,6 @@
|
|||||||
"js": "index.js",
|
"js": "index.js",
|
||||||
"css": "style.css",
|
"css": "style.css",
|
||||||
"author": "Marinara",
|
"author": "Marinara",
|
||||||
"version": "3.2.3",
|
"version": "3.2.4",
|
||||||
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,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.2.3
|
v3.2.4
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ export function renderInfoBox() {
|
|||||||
<div class="rpg-dashboard-widget rpg-calendar-widget">
|
<div class="rpg-dashboard-widget rpg-calendar-widget">
|
||||||
${dateLockIconHtml}
|
${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-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 class="rpg-calendar-year rpg-editable" contenteditable="true" data-field="year" data-full-value="${data.year || ''}" title="Click to edit">${yearDisplay}</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ export function renderThoughts() {
|
|||||||
// Use committedTrackerData as fallback if lastGeneratedData is empty (e.g., after page refresh)
|
// Use committedTrackerData as fallback if lastGeneratedData is empty (e.g., after page refresh)
|
||||||
const characterThoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts || '';
|
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] Raw characterThoughts data:', characterThoughtsData);
|
||||||
debugLog('[RPG Thoughts] Data length:', characterThoughtsData.length + ' chars');
|
debugLog('[RPG Thoughts] Data length:', characterThoughtsData.length + ' chars');
|
||||||
debugLog('[RPG Thoughts] Enabled custom fields:', enabledFields.map(f => f.name));
|
debugLog('[RPG Thoughts] Enabled custom fields:', enabledFields.map(f => f.name));
|
||||||
@@ -210,25 +213,37 @@ export function renderThoughts() {
|
|||||||
|
|
||||||
// Extract details (appearance, demeanor, etc.)
|
// Extract details (appearance, demeanor, etc.)
|
||||||
if (char.details) {
|
if (char.details) {
|
||||||
// Map details object to custom fields by snake_case name
|
// Map details object to custom fields
|
||||||
for (const field of enabledFields) {
|
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);
|
const fieldKey = toSnakeCase(field.name);
|
||||||
if (char.details[fieldKey] !== undefined) {
|
if (char.details[fieldKey] !== undefined) {
|
||||||
character[field.name] = stripBrackets(char.details[fieldKey]);
|
character[field.name] = stripBrackets(char.details[fieldKey]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Also check for fields at root level (for backward compatibility)
|
// Also check for fields at root level (for backward compatibility)
|
||||||
|
// Only use if not already set from details
|
||||||
for (const field of enabledFields) {
|
for (const field of enabledFields) {
|
||||||
|
if (character[field.name] === undefined) {
|
||||||
const fieldKey = toSnakeCase(field.name);
|
const fieldKey = toSnakeCase(field.name);
|
||||||
if (char[fieldKey] !== undefined) {
|
if (char[fieldKey] !== undefined) {
|
||||||
character[field.name] = stripBrackets(char[fieldKey]);
|
character[field.name] = stripBrackets(char[fieldKey]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Extract relationship
|
// 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);
|
character.Relationship = stripBrackets(char.relationship.status || char.relationship);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,8 +513,11 @@ export function renderThoughts() {
|
|||||||
if (hasRelationshipEnabled) {
|
if (hasRelationshipEnabled) {
|
||||||
// In the new format, relationship is always stored in char.Relationship
|
// In the new format, relationship is always stored in char.Relationship
|
||||||
if (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
|
// Try to map text to emoji
|
||||||
relationshipBadge = relationshipEmojis[char.Relationship] || char.Relationship;
|
relationshipBadge = relationshipEmojis[char.Relationship] || char.Relationship;
|
||||||
|
// console.log('[RPG Companion] Final relationshipBadge:', relationshipBadge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,6 +614,11 @@ export function renderThoughts() {
|
|||||||
updateCharacterField(character, field, value);
|
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)
|
// Add event listener for section lock icon clicks (support both click and touch)
|
||||||
$thoughtsContainer.find('.rpg-section-lock-icon').on('click touchend', function(e) {
|
$thoughtsContainer.find('.rpg-section-lock-icon').on('click touchend', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -688,12 +711,139 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
lastGeneratedData.characterThoughts = 'Present Characters\n---\n';
|
lastGeneratedData.characterThoughts = 'Present Characters\n---\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = lastGeneratedData.characterThoughts.split('\n');
|
|
||||||
const presentCharsConfig = extensionSettings.trackerConfig?.presentCharacters;
|
const presentCharsConfig = extensionSettings.trackerConfig?.presentCharacters;
|
||||||
const enabledFields = presentCharsConfig?.customFields?.filter(f => f && f.enabled && f.name) || [];
|
const enabledFields = presentCharsConfig?.customFields?.filter(f => f && f.enabled && f.name) || [];
|
||||||
const characterStats = presentCharsConfig?.characterStats;
|
const characterStats = presentCharsConfig?.characterStats;
|
||||||
const enabledCharStats = characterStats?.enabled && characterStats?.customStats?.filter(s => s && s.enabled && s.name) || [];
|
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 characterFound = false;
|
||||||
let inTargetCharacter = false;
|
let inTargetCharacter = false;
|
||||||
let characterStartIndex = -1;
|
let characterStartIndex = -1;
|
||||||
@@ -893,23 +1043,26 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
|
|
||||||
saveChatData();
|
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 thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts';
|
||||||
const isEditingThoughts = field === thoughtsFieldName || field === 'thoughts';
|
const isEditingThoughts = field === thoughtsFieldName || field === 'thoughts';
|
||||||
|
|
||||||
if (!isEditingThoughts) {
|
// console.log('[RPG Companion] Is editing thoughts?', isEditingThoughts, 'Field:', field, 'Thoughts field name:', thoughtsFieldName);
|
||||||
renderThoughts();
|
// console.log('[RPG Companion] After update - lastGeneratedData.characterThoughts:', lastGeneratedData.characterThoughts);
|
||||||
} else {
|
|
||||||
// Only update sidebar, don't trigger chat bubble refresh
|
if (isEditingThoughts && extensionSettings.showThoughtsInChat) {
|
||||||
$thoughtsContainer.find('.rpg-editable').off('blur');
|
// console.log('[RPG Companion] Updating chat thought bubbles');
|
||||||
renderThoughtsSidebarOnly();
|
// Update chat thought bubbles when thoughts are edited
|
||||||
$thoughtsContainer.find('.rpg-editable').on('blur', function() {
|
updateChatThoughts();
|
||||||
const char = $(this).data('character');
|
|
||||||
const fld = $(this).data('field');
|
|
||||||
const val = $(this).text().trim();
|
|
||||||
updateCharacterField(char, fld, val);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
// 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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
white-space: normal;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
line-height: 1.2;
|
line-height: 1.1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rpg-calendar-day-text {
|
||||||
|
max-width: 100%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
hyphens: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.rpg-calendar-year {
|
.rpg-calendar-year {
|
||||||
font-size: clamp(0.5rem, 3.5cqh, 0.625rem);
|
font-size: clamp(0.5rem, 3.5cqh, 0.625rem);
|
||||||
color: var(--rpg-text);
|
color: var(--rpg-text);
|
||||||
|
|||||||
Reference in New Issue
Block a user