Release v3.3.2: Fix auto-update on chat switch & restore character removal
This commit is contained in:
@@ -7,11 +7,10 @@ An immersive RPG extension for browsers that tracks character stats, scene infor
|
||||
|
||||
## 🆕 What's New
|
||||
|
||||
### v3.3.1
|
||||
### v3.3.2
|
||||
|
||||
- Thought bubble can now be collapsed into an icon.
|
||||
- Fixed a bug for Past Events being parsed incorrectly.
|
||||
- Added event emission on when the tracker generation is complete.
|
||||
- Fixed the auto-generation triggering on switching/starting new chats in separate generation mode.
|
||||
- Restored the option to remove generated characters from the panel.
|
||||
|
||||
**Special thanks to all the other contributors for this project:**
|
||||
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, and Tomt610.
|
||||
|
||||
@@ -64,6 +64,7 @@ import { renderInfoBox, updateInfoBoxField } from './src/systems/rendering/infoB
|
||||
import {
|
||||
renderThoughts,
|
||||
updateCharacterField,
|
||||
removeCharacter,
|
||||
updateChatThoughts,
|
||||
createThoughtPanel
|
||||
} from './src/systems/rendering/thoughts.js';
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Marinara",
|
||||
"version": "3.3.1",
|
||||
"version": "3.3.2",
|
||||
"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.3.1
|
||||
v3.3.2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -308,6 +308,12 @@ export let isGenerating = false;
|
||||
*/
|
||||
export let isPlotProgression = false;
|
||||
|
||||
/**
|
||||
* Flag indicating if we're actively expecting a new message from generation
|
||||
* (as opposed to loading chat history)
|
||||
*/
|
||||
export let isAwaitingNewMessage = false;
|
||||
|
||||
/**
|
||||
* Temporary storage for pending dice roll (not saved until user clicks "Save Roll")
|
||||
*/
|
||||
@@ -408,6 +414,10 @@ export function setIsPlotProgression(value) {
|
||||
isPlotProgression = value;
|
||||
}
|
||||
|
||||
export function setIsAwaitingNewMessage(value) {
|
||||
isAwaitingNewMessage = value;
|
||||
}
|
||||
|
||||
export function setPendingDiceRoll(roll) {
|
||||
pendingDiceRoll = roll;
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
committedTrackerData,
|
||||
lastActionWasSwipe,
|
||||
isPlotProgression,
|
||||
isAwaitingNewMessage,
|
||||
setLastActionWasSwipe,
|
||||
setIsPlotProgression,
|
||||
setIsGenerating,
|
||||
setIsAwaitingNewMessage,
|
||||
updateLastGeneratedData,
|
||||
updateCommittedTrackerData,
|
||||
$musicPlayerContainer
|
||||
@@ -105,6 +107,10 @@ export function onMessageSent() {
|
||||
// console.log('[RPG Companion] 🟢 EVENT: onMessageSent (after placeholder check)');
|
||||
// console.log('[RPG Companion] 🟢 NOTE: lastActionWasSwipe will be reset in onMessageReceived after generation completes');
|
||||
|
||||
// Set flag to indicate we're expecting a new message from generation
|
||||
// This allows auto-update to distinguish between new generations and loading chat history
|
||||
setIsAwaitingNewMessage(true);
|
||||
|
||||
// For separate mode with auto-update disabled, commit displayed tracker
|
||||
if (extensionSettings.generationMode === 'separate' && !extensionSettings.autoUpdate) {
|
||||
if (lastGeneratedData.userStats || lastGeneratedData.infoBox || lastGeneratedData.characterThoughts) {
|
||||
@@ -250,13 +256,17 @@ export async function onMessageReceived(data) {
|
||||
}
|
||||
|
||||
// Trigger auto-update if enabled (for both separate and external modes)
|
||||
if (extensionSettings.autoUpdate) {
|
||||
// Only trigger if this is a newly generated message, not loading chat history
|
||||
if (extensionSettings.autoUpdate && isAwaitingNewMessage) {
|
||||
setTimeout(async () => {
|
||||
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the awaiting flag after processing the message
|
||||
setIsAwaitingNewMessage(false);
|
||||
|
||||
// Reset the swipe flag after generation completes
|
||||
// This ensures that if the user swiped → auto-reply generated → flag is now cleared
|
||||
// so the next user message will be treated as a new message (not a swipe)
|
||||
@@ -340,6 +350,7 @@ export function onMessageSwiped(messageIndex) {
|
||||
if (!isExistingSwipe) {
|
||||
// This is a NEW swipe that will trigger generation
|
||||
setLastActionWasSwipe(true);
|
||||
setIsAwaitingNewMessage(true);
|
||||
// console.log('[RPG Companion] 🔵 NEW swipe detected - Set lastActionWasSwipe = true');
|
||||
} else {
|
||||
// This is navigating to an EXISTING swipe - don't change the flag
|
||||
|
||||
@@ -391,50 +391,10 @@ export function renderThoughts() {
|
||||
debugLog('[RPG Thoughts] ==================== BUILDING HTML ====================');
|
||||
debugLog('[RPG Thoughts] Starting HTML generation for', presentCharacters.length + ' characters');
|
||||
|
||||
// If no characters parsed, show a placeholder editable card
|
||||
// If no characters parsed, show empty state (no placeholder)
|
||||
if (presentCharacters.length === 0) {
|
||||
debugLog('[RPG Thoughts] ⚠ No characters parsed - showing placeholder card');
|
||||
// Get default character portrait
|
||||
let defaultPortrait = FALLBACK_AVATAR_DATA_URI;
|
||||
let defaultName = 'Character';
|
||||
|
||||
if (this_chid !== undefined && characters[this_chid]) {
|
||||
if (characters[this_chid].avatar && characters[this_chid].avatar !== 'none') {
|
||||
const thumbnailUrl = getSafeThumbnailUrl('avatar', characters[this_chid].avatar);
|
||||
if (thumbnailUrl) {
|
||||
defaultPortrait = thumbnailUrl;
|
||||
}
|
||||
}
|
||||
defaultName = characters[this_chid].name || 'Character';
|
||||
}
|
||||
|
||||
html += '<div class="rpg-thoughts-content">';
|
||||
html += `
|
||||
<div class="rpg-character-card" data-character-name="${defaultName}">
|
||||
<div class="rpg-character-avatar">
|
||||
<img src="${defaultPortrait}" alt="${defaultName}" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
||||
<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${defaultName}" data-field="relationship" title="Click to edit (use emoji: ⚔️ ⚖️ ⭐ ❤️)">⚖️</div>
|
||||
</div>
|
||||
<div class="rpg-character-info">
|
||||
<div class="rpg-character-header">
|
||||
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${defaultName}" data-field="emoji" title="Click to edit emoji">😊</span>
|
||||
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${defaultName}" data-field="name" title="Click to edit name">${defaultName}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add custom fields dynamically
|
||||
for (const field of enabledFields) {
|
||||
const fieldId = field.name.toLowerCase().replace(/\s+/g, '-');
|
||||
html += `
|
||||
<div class="rpg-character-field rpg-character-${fieldId} rpg-editable" contenteditable="true" data-character="${defaultName}" data-field="${field.name}" title="Click to edit ${field.name}"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
html += '</div>';
|
||||
debugLog('[RPG Thoughts] ⚠ No characters parsed - showing empty state');
|
||||
html += '<div class="rpg-thoughts-content"></div>';
|
||||
} else {
|
||||
html += '<div class="rpg-thoughts-content">';
|
||||
|
||||
@@ -540,6 +500,7 @@ export function renderThoughts() {
|
||||
<div class="rpg-character-header">
|
||||
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${char.name}" data-field="emoji" title="Click to edit emoji">${char.emoji}</span>
|
||||
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="name" title="Click to edit name">${char.name}</span>
|
||||
<button class="rpg-character-remove" data-character="${char.name}" title="Remove character">×</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -650,6 +611,15 @@ export function renderThoughts() {
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
// Add event listener for character remove button
|
||||
$thoughtsContainer.find('.rpg-character-remove').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const characterName = $(this).data('character');
|
||||
removeCharacter(characterName);
|
||||
});
|
||||
|
||||
// Add event listener for avatar upload clicks
|
||||
$thoughtsContainer.find('.rpg-avatar-upload').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
@@ -703,6 +673,121 @@ export function renderThoughts() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a character from Present Characters data and re-renders.
|
||||
*
|
||||
* @param {string} characterName - Name of the character to remove
|
||||
*/
|
||||
export function removeCharacter(characterName) {
|
||||
if (!lastGeneratedData.characterThoughts) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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, treat as text format
|
||||
}
|
||||
|
||||
if (isJSON) {
|
||||
// JSON format - remove character from array
|
||||
let characters = Array.isArray(parsedData) ? parsedData : parsedData.characters;
|
||||
characters = characters.filter(char => char.name !== characterName);
|
||||
|
||||
if (Array.isArray(parsedData)) {
|
||||
parsedData = characters;
|
||||
} else {
|
||||
parsedData.characters = characters;
|
||||
}
|
||||
|
||||
const updatedJSON = JSON.stringify(parsedData, null, 2);
|
||||
lastGeneratedData.characterThoughts = updatedJSON;
|
||||
committedTrackerData.characterThoughts = updatedJSON;
|
||||
} else {
|
||||
// Text format - remove character block
|
||||
const lines = lastGeneratedData.characterThoughts.split('\n');
|
||||
const dividerIndex = lines.findIndex(line => line.includes('---'));
|
||||
|
||||
if (dividerIndex === -1) return;
|
||||
|
||||
// Find the character block to remove
|
||||
let startLineIndex = -1;
|
||||
let endLineIndex = -1;
|
||||
|
||||
for (let i = dividerIndex + 1; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
// Check if this is the start of the character block
|
||||
if (line.startsWith('Name:')) {
|
||||
const nameMatch = line.match(/^Name:\s*(.+)/);
|
||||
if (nameMatch && nameMatch[1].trim() === characterName) {
|
||||
startLineIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found the start, look for the end
|
||||
if (startLineIndex !== -1 && i > startLineIndex) {
|
||||
// End of block is either another "Name:" line or end of content
|
||||
if (line.startsWith('Name:') || i === lines.length - 1) {
|
||||
endLineIndex = line.startsWith('Name:') ? i - 1 : i;
|
||||
|
||||
// Remove empty lines at the end of the block
|
||||
while (endLineIndex > startLineIndex && !lines[endLineIndex].trim()) {
|
||||
endLineIndex--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the character block
|
||||
if (startLineIndex !== -1 && endLineIndex !== -1) {
|
||||
lines.splice(startLineIndex, endLineIndex - startLineIndex + 1);
|
||||
|
||||
// Remove empty lines after removal to keep formatting clean
|
||||
let i = startLineIndex;
|
||||
while (i < lines.length && !lines[i].trim()) {
|
||||
lines.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
lastGeneratedData.characterThoughts = lines.join('\n');
|
||||
committedTrackerData.characterThoughts = lines.join('\n');
|
||||
}
|
||||
|
||||
// Update message swipe data
|
||||
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();
|
||||
|
||||
// Re-render to show updated character list
|
||||
renderThoughts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a specific character field in Present Characters data and re-renders.
|
||||
* Works with the new multi-line format.
|
||||
|
||||
Reference in New Issue
Block a user