From ddc02d9bbcd76ea043291e28763d92d8caea1c45 Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Fri, 9 Jan 2026 10:04:29 +0100 Subject: [PATCH] Release v3.3.2: Fix auto-update on chat switch & restore character removal --- README.md | 7 +- index.js | 1 + manifest.json | 2 +- settings.html | 2 +- src/core/state.js | 10 ++ src/systems/integration/sillytavern.js | 13 +- src/systems/rendering/thoughts.js | 171 ++++++++++++++++++------- 7 files changed, 156 insertions(+), 50 deletions(-) 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 += ` -
-
- ${defaultName} -
⚖️
-
-
-
- 😊 - ${defaultName} -
- `; - - // 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() {
${char.emoji} ${char.name} +
`; @@ -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.