Fix: Rewrite updateCharacterField for new multi-line format
- Completely rewrote updateCharacterField function to work with new multi-line Present Characters format - Now parses character blocks by '- Name' lines instead of pipe-separated format - Handles updating Details, Relationship, and Stats lines correctly - Supports all field types: name, emoji, custom fields, relationship, character stats - Creates new character blocks if character doesn't exist - Fixes bug where edits would revert because old format logic couldn't parse new format - Users can now successfully edit all Present Characters fields
This commit is contained in:
+16
-14
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { saveSettingsDebounced, chat_metadata, saveChatDebounced } from '../../../../../../script.js';
|
||||
import { power_user } from '../../../../../power-user.js';
|
||||
import { getContext } from '../../../../../extensions.js';
|
||||
import {
|
||||
extensionSettings,
|
||||
@@ -57,19 +56,17 @@ function validateSettings(settings) {
|
||||
*/
|
||||
export function loadSettings() {
|
||||
try {
|
||||
// Validate power_user structure
|
||||
if (!power_user || typeof power_user !== 'object') {
|
||||
console.warn('[RPG Companion] power_user is not available, using default settings');
|
||||
const context = getContext();
|
||||
const extension_settings = context.extension_settings || context.extensionSettings;
|
||||
|
||||
// Validate extension_settings structure
|
||||
if (!extension_settings || typeof extension_settings !== 'object') {
|
||||
console.warn('[RPG Companion] extension_settings is not available, using default settings');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!power_user.extensions) {
|
||||
power_user.extensions = {};
|
||||
// console.log('[RPG Companion] Created power_user.extensions object');
|
||||
}
|
||||
|
||||
if (power_user.extensions[extensionName]) {
|
||||
const savedSettings = power_user.extensions[extensionName];
|
||||
if (extension_settings[extensionName]) {
|
||||
const savedSettings = extension_settings[extensionName];
|
||||
|
||||
// Validate loaded settings
|
||||
if (!validateSettings(savedSettings)) {
|
||||
@@ -117,10 +114,15 @@ export function loadSettings() {
|
||||
* Saves the extension settings to the global settings object.
|
||||
*/
|
||||
export function saveSettings() {
|
||||
if (!power_user.extensions) {
|
||||
power_user.extensions = {};
|
||||
const context = getContext();
|
||||
const extension_settings = context.extension_settings || context.extensionSettings;
|
||||
|
||||
if (!extension_settings) {
|
||||
console.error('[RPG Companion] extension_settings is not available, cannot save');
|
||||
return;
|
||||
}
|
||||
power_user.extensions[extensionName] = extensionSettings;
|
||||
|
||||
extension_settings[extensionName] = extensionSettings;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
|
||||
@@ -447,132 +447,133 @@ export function renderThoughts() {
|
||||
|
||||
/**
|
||||
* Updates a specific character field in Present Characters data and re-renders.
|
||||
* Handles character creation if character doesn't exist yet.
|
||||
* Works with the new multi-line format.
|
||||
*
|
||||
* @param {string} characterName - Name of the character to update
|
||||
* @param {string} field - Field to update (emoji, name, traits, thoughts, relationship)
|
||||
* @param {string} field - Field to update (emoji, name, custom field name, Relationship, stat name)
|
||||
* @param {string} value - New value for the field
|
||||
*/
|
||||
export function updateCharacterField(characterName, field, value) {
|
||||
// console.log('[RPG Companion] 📝 updateCharacterField called - character:', characterName, 'field:', field, 'value:', value);
|
||||
// console.log('[RPG Companion] 📝 Current lastGeneratedData.characterThoughts:', lastGeneratedData.characterThoughts);
|
||||
|
||||
// Initialize if it doesn't exist
|
||||
if (!lastGeneratedData.characterThoughts) {
|
||||
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) || [];
|
||||
|
||||
let characterFound = false;
|
||||
let inTargetCharacter = false;
|
||||
let characterStartIndex = -1;
|
||||
let characterEndIndex = -1;
|
||||
|
||||
const updatedLines = lines.map(line => {
|
||||
// Case-insensitive character name matching
|
||||
if (line.toLowerCase().includes(characterName.toLowerCase())) {
|
||||
// Find the character block
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
if (line.startsWith('- ')) {
|
||||
const name = line.substring(2).trim();
|
||||
if (name.toLowerCase() === characterName.toLowerCase()) {
|
||||
characterFound = true;
|
||||
const parts = line.split('|').map(p => p.trim());
|
||||
if (parts.length >= 2) {
|
||||
const firstPart = parts[0];
|
||||
const emojiMatch = firstPart.match(/^(.+?):\s*(.+)$/);
|
||||
|
||||
if (emojiMatch) {
|
||||
let emoji = emojiMatch[1].trim();
|
||||
let info = emojiMatch[2].trim();
|
||||
let relationship = parts[1] ? parts[1].trim() : '';
|
||||
let thoughts = parts[2] ? parts[2].trim() : '';
|
||||
|
||||
// Handle 4-part format (with demeanor)
|
||||
if (parts.length >= 4) {
|
||||
relationship = parts[2] ? parts[2].trim() : '';
|
||||
thoughts = parts[3] ? parts[3].trim() : '';
|
||||
}
|
||||
|
||||
const infoParts = info.split(',').map(p => p.trim());
|
||||
let name = infoParts[0];
|
||||
let traits = infoParts.slice(1).join(', ');
|
||||
|
||||
if (field === 'emoji') {
|
||||
emoji = value;
|
||||
} else if (field === 'name') {
|
||||
name = value;
|
||||
} else if (field === 'traits') {
|
||||
traits = value;
|
||||
} else if (field === 'thoughts') {
|
||||
thoughts = value;
|
||||
} else if (field === 'relationship') {
|
||||
const emojiToRelationship = {
|
||||
'⚔️': 'Enemy',
|
||||
'⚖️': 'Neutral',
|
||||
'⭐': 'Friend',
|
||||
'❤️': 'Lover'
|
||||
};
|
||||
relationship = emojiToRelationship[value] || value;
|
||||
}
|
||||
|
||||
const newInfo = traits ? `${name}, ${traits}` : name;
|
||||
return `${emoji}: ${newInfo} | ${relationship} | ${thoughts}`;
|
||||
inTargetCharacter = true;
|
||||
characterStartIndex = i;
|
||||
} else if (inTargetCharacter) {
|
||||
characterEndIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return line;
|
||||
});
|
||||
|
||||
// If character wasn't found, create a new character line
|
||||
if (!characterFound) {
|
||||
// Find the divider line
|
||||
const dividerIndex = updatedLines.findIndex(line => line.includes('---'));
|
||||
if (characterFound && characterEndIndex === -1) {
|
||||
characterEndIndex = lines.length;
|
||||
}
|
||||
|
||||
if (characterFound) {
|
||||
// Update the specific field within the character block
|
||||
for (let i = characterStartIndex; i < characterEndIndex; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
if (field === 'name' && line.startsWith('- ')) {
|
||||
lines[i] = `- ${value}`;
|
||||
}
|
||||
else if (field === 'emoji' && line.startsWith('Details:')) {
|
||||
const parts = line.substring(line.indexOf(':') + 1).split('|').map(p => p.trim());
|
||||
parts[0] = value;
|
||||
lines[i] = `Details: ${parts.join(' | ')}`;
|
||||
}
|
||||
else if (line.startsWith('Details:')) {
|
||||
const fieldIndex = enabledFields.findIndex(f => f.name === field);
|
||||
if (fieldIndex !== -1) {
|
||||
const parts = line.substring(line.indexOf(':') + 1).split('|').map(p => p.trim());
|
||||
if (parts.length > fieldIndex + 1) {
|
||||
parts[fieldIndex + 1] = value;
|
||||
lines[i] = `Details: ${parts.join(' | ')}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (field === 'Relationship' && line.startsWith('Relationship:')) {
|
||||
const emojiToRelationship = { '⚔️': 'Enemy', '⚖️': 'Neutral', '⭐': 'Friend', '❤️': 'Lover' };
|
||||
const relationshipValue = emojiToRelationship[value] || value;
|
||||
lines[i] = `Relationship: ${relationshipValue}`;
|
||||
}
|
||||
else if (line.startsWith('Stats:')) {
|
||||
const statIndex = enabledCharStats.findIndex(s => s.name === field);
|
||||
if (statIndex !== -1) {
|
||||
const statsContent = line.substring(line.indexOf(':') + 1).trim();
|
||||
const statParts = statsContent.split('|').map(p => p.trim());
|
||||
|
||||
for (let j = 0; j < statParts.length; j++) {
|
||||
if (statParts[j].startsWith(field + ':')) {
|
||||
statParts[j] = `${field}: ${value}%`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
lines[i] = `Stats: ${statParts.join(' | ')}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create new character block
|
||||
const dividerIndex = lines.findIndex(line => line.includes('---'));
|
||||
if (dividerIndex >= 0) {
|
||||
// Create initial character line with the edited field
|
||||
let emoji = '😊';
|
||||
let name = characterName;
|
||||
let traits = 'Traits';
|
||||
let relationship = 'Neutral';
|
||||
let thoughts = '';
|
||||
const newCharacterLines = [`- ${characterName}`];
|
||||
|
||||
// Apply the edited field
|
||||
if (field === 'emoji') {
|
||||
emoji = value;
|
||||
} else if (field === 'name') {
|
||||
name = value;
|
||||
} else if (field === 'traits') {
|
||||
traits = value;
|
||||
} else if (field === 'thoughts') {
|
||||
thoughts = value;
|
||||
} else if (field === 'relationship') {
|
||||
const emojiToRelationship = {
|
||||
'⚔️': 'Enemy',
|
||||
'⚖️': 'Neutral',
|
||||
'⭐': 'Friend',
|
||||
'❤️': 'Lover'
|
||||
};
|
||||
relationship = emojiToRelationship[value] || value;
|
||||
let detailsParts = [field === 'emoji' ? value : '😊'];
|
||||
for (let i = 0; i < enabledFields.length; i++) {
|
||||
detailsParts.push(field === enabledFields[i].name ? value : '');
|
||||
}
|
||||
newCharacterLines.push(`Details: ${detailsParts.join(' | ')}`);
|
||||
|
||||
if (presentCharsConfig?.relationshipFields?.length > 0) {
|
||||
const emojiToRelationship = { '⚔️': 'Enemy', '⚖️': 'Neutral', '⭐': 'Friend', '❤️': 'Lover' };
|
||||
const relationshipValue = field === 'Relationship' ? (emojiToRelationship[value] || value) : 'Neutral';
|
||||
newCharacterLines.push(`Relationship: ${relationshipValue}`);
|
||||
}
|
||||
|
||||
const newCharacterLine = `${emoji}: ${name}, ${traits} | ${relationship} | ${thoughts}`;
|
||||
// Insert after the divider
|
||||
updatedLines.splice(dividerIndex + 1, 0, newCharacterLine);
|
||||
if (enabledCharStats.length > 0) {
|
||||
const statsParts = enabledCharStats.map(s => `${s.name}: ${field === s.name ? value : '0'}%`);
|
||||
newCharacterLines.push(`Stats: ${statsParts.join(' | ')}`);
|
||||
}
|
||||
|
||||
lines.splice(dividerIndex + 1, 0, ...newCharacterLines);
|
||||
}
|
||||
}
|
||||
|
||||
lastGeneratedData.characterThoughts = updatedLines.join('\n');
|
||||
// console.log('[RPG Companion] 💾 Updated lastGeneratedData.characterThoughts:', lastGeneratedData.characterThoughts);
|
||||
lastGeneratedData.characterThoughts = lines.join('\n');
|
||||
committedTrackerData.characterThoughts = lines.join('\n');
|
||||
|
||||
// Update BOTH lastGeneratedData AND committedTrackerData
|
||||
// This makes manual edits immediately visible to AI
|
||||
committedTrackerData.characterThoughts = updatedLines.join('\n');
|
||||
|
||||
// Also update the last assistant message's swipe data
|
||||
const chat = getContext().chat;
|
||||
if (chat && chat.length > 0) {
|
||||
// Find the last assistant message
|
||||
for (let i = chat.length - 1; i >= 0; i--) {
|
||||
const message = chat[i];
|
||||
if (!message.is_user) {
|
||||
// Found last assistant message - update its swipe data
|
||||
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 = updatedLines.join('\n');
|
||||
// console.log('[RPG Companion] Updated thoughts in message swipe data');
|
||||
message.extra.rpg_companion_swipes[swipeId].characterThoughts = lines.join('\n');
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -581,18 +582,12 @@ export function updateCharacterField(characterName, field, value) {
|
||||
}
|
||||
|
||||
saveChatData();
|
||||
|
||||
// Always update the sidebar panel
|
||||
renderThoughts();
|
||||
|
||||
// For thoughts edited from the bubble, delay recreation to allow blur event to complete
|
||||
// This ensures the edit is saved first, then the bubble is recreated with correct layout
|
||||
if (field === 'thoughts') {
|
||||
setTimeout(() => {
|
||||
updateChatThoughts();
|
||||
}, 100);
|
||||
const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts';
|
||||
if (field === thoughtsFieldName) {
|
||||
setTimeout(() => updateChatThoughts(), 100);
|
||||
} else {
|
||||
// For other fields, recreate immediately
|
||||
updateChatThoughts();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user