diff --git a/README.md b/README.md
index 2eab6be..6e88311 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/index.js b/index.js
index 9d6d119..26170ce 100644
--- a/index.js
+++ b/index.js
@@ -64,6 +64,7 @@ import { renderInfoBox, updateInfoBoxField } from './src/systems/rendering/infoB
import {
renderThoughts,
updateCharacterField,
+ removeCharacter,
updateChatThoughts,
createThoughtPanel
} from './src/systems/rendering/thoughts.js';
diff --git a/manifest.json b/manifest.json
index 6cc5b3c..b3f7ce4 100644
--- a/manifest.json
+++ b/manifest.json
@@ -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"
}
diff --git a/settings.html b/settings.html
index 11548d6..12806c0 100644
--- a/settings.html
+++ b/settings.html
@@ -48,7 +48,7 @@
- v3.3.1
+ v3.3.2
diff --git a/src/core/state.js b/src/core/state.js
index 97647fd..7ba229c 100644
--- a/src/core/state.js
+++ b/src/core/state.js
@@ -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;
}
diff --git a/src/systems/integration/sillytavern.js b/src/systems/integration/sillytavern.js
index 33fea8a..c71c3c2 100644
--- a/src/systems/integration/sillytavern.js
+++ b/src/systems/integration/sillytavern.js
@@ -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
diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js
index 4815ef3..5df493d 100644
--- a/src/systems/rendering/thoughts.js
+++ b/src/systems/rendering/thoughts.js
@@ -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 += '';
- html += `
-
-
-

-
⚖️
-
-
-
- `;
-
- // Add custom fields dynamically
- for (const field of enabledFields) {
- const fieldId = field.name.toLowerCase().replace(/\s+/g, '-');
- html += `
-
- `;
- }
-
- html += `
-
-
- `;
- html += '
';
+ debugLog('[RPG Thoughts] ⚠ No characters parsed - showing empty state');
+ html += '';
} else {
html += '';
@@ -540,6 +500,7 @@ export function renderThoughts() {
`;
@@ -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.