diff --git a/index.js b/index.js index 8963fe3..4a6c2ff 100644 --- a/index.js +++ b/index.js @@ -150,6 +150,7 @@ import { onMessageReceived, onCharacterChanged, onMessageSwiped, + onMessageDeleted, updatePersonaAvatar, clearExtensionPrompts, onGenerationEnded, @@ -1175,6 +1176,7 @@ jQuery(async () => { [event_types.GENERATION_ENDED]: onGenerationEnded, [event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar, restoreCheckpointOnLoad, clearSessionAvatarPrompts], [event_types.MESSAGE_SWIPED]: onMessageSwiped, + [event_types.MESSAGE_DELETED]: onMessageDeleted, [event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar, [event_types.SETTINGS_UPDATED]: updatePersonaAvatar }); diff --git a/src/systems/integration/sillytavern.js b/src/systems/integration/sillytavern.js index 57727f3..f585d42 100644 --- a/src/systems/integration/sillytavern.js +++ b/src/systems/integration/sillytavern.js @@ -354,7 +354,8 @@ export function onMessageSwiped(messageIndex) { // console.log('[RPG Companion] đŸ”ĩ EVENT: onMessageSwiped at index:', messageIndex); // Get the message that was swiped - const message = chat[messageIndex]; + const currentChat = getContext().chat; + const message = currentChat[messageIndex]; if (!message || message.is_user) { // console.log('[RPG Companion] đŸ”ĩ Ignoring swipe - message is user or undefined'); return; @@ -374,32 +375,79 @@ export function onMessageSwiped(messageIndex) { setLastActionWasSwipe(true); setIsAwaitingNewMessage(true); // console.log('[RPG Companion] đŸ”ĩ NEW swipe detected - Set lastActionWasSwipe = true'); + + // CRITICAL: For new swipes, commit data from the PREVIOUS assistant message + // This ensures the LLM gets context from BEFORE the message being regenerated, + // not the message itself (which would cause time/story to advance incorrectly) + for (let i = messageIndex - 1; i >= 0; i--) { + const prevMessage = currentChat[i]; + if (!prevMessage.is_user && prevMessage.extra?.rpg_companion_swipes) { + const prevSwipeId = prevMessage.swipe_id || 0; + const prevSwipeData = prevMessage.extra.rpg_companion_swipes[prevSwipeId]; + + if (prevSwipeData) { + // console.log('[RPG Companion] đŸ”ĩ Committing tracker data from PREVIOUS message at index', i); + committedTrackerData.userStats = prevSwipeData.userStats || null; + committedTrackerData.infoBox = prevSwipeData.infoBox || null; + committedTrackerData.characterThoughts = prevSwipeData.characterThoughts || null; + } else { + // Previous message has no swipe data - clear committed data + committedTrackerData.userStats = null; + committedTrackerData.infoBox = null; + committedTrackerData.characterThoughts = null; + } + break; + } + + // If we hit index 0 without finding a previous assistant message, clear committed data + if (i === 0) { + // console.log('[RPG Companion] đŸ”ĩ No previous assistant message found - clearing committed data'); + committedTrackerData.userStats = null; + committedTrackerData.infoBox = null; + committedTrackerData.characterThoughts = null; + } + } + + // Edge case: if messageIndex is 0 (first message being swiped), clear committed data + if (messageIndex === 0) { + // console.log('[RPG Companion] đŸ”ĩ Swiping first message - clearing committed data'); + committedTrackerData.userStats = null; + committedTrackerData.infoBox = null; + committedTrackerData.characterThoughts = null; + } + + // For new swipes, also update lastGeneratedData to match committed data + // This ensures the UI shows the "before" state while waiting for the new response + lastGeneratedData.userStats = committedTrackerData.userStats; + lastGeneratedData.infoBox = committedTrackerData.infoBox; + lastGeneratedData.characterThoughts = committedTrackerData.characterThoughts; + + // Parse user stats for display if available + if (committedTrackerData.userStats) { + parseUserStats(committedTrackerData.userStats); + } } else { // This is navigating to an EXISTING swipe - don't change the flag // console.log('[RPG Companion] đŸ”ĩ EXISTING swipe navigation - lastActionWasSwipe unchanged =', lastActionWasSwipe); - } - // console.log('[RPG Companion] Loading data for swipe', currentSwipeId); + // Load RPG data for this existing swipe for DISPLAY purposes + if (message.extra && message.extra.rpg_companion_swipes && message.extra.rpg_companion_swipes[currentSwipeId]) { + const swipeData = message.extra.rpg_companion_swipes[currentSwipeId]; - // Load RPG data for this swipe - // lastGeneratedData is for DISPLAY, committedTrackerData is for GENERATION - // It's safe to load swipe data into lastGeneratedData - it won't be committed due to !lastActionWasSwipe check - if (message.extra && message.extra.rpg_companion_swipes && message.extra.rpg_companion_swipes[currentSwipeId]) { - const swipeData = message.extra.rpg_companion_swipes[currentSwipeId]; + // Load swipe data into lastGeneratedData for display + lastGeneratedData.userStats = swipeData.userStats || null; + lastGeneratedData.infoBox = swipeData.infoBox || null; + lastGeneratedData.characterThoughts = swipeData.characterThoughts || null; - // Load swipe data into lastGeneratedData for display (both modes) - lastGeneratedData.userStats = swipeData.userStats || null; - lastGeneratedData.infoBox = swipeData.infoBox || null; - lastGeneratedData.characterThoughts = swipeData.characterThoughts || null; + // Parse user stats if available + if (swipeData.userStats) { + parseUserStats(swipeData.userStats); + } - // Parse user stats if available - if (swipeData.userStats) { - parseUserStats(swipeData.userStats); + // console.log('[RPG Companion] 🔄 Loaded swipe data into lastGeneratedData for display:', currentSwipeId); + } else { + // console.log('[RPG Companion] â„šī¸ No stored data for swipe:', currentSwipeId); } - - // console.log('[RPG Companion] 🔄 Loaded swipe data into lastGeneratedData for display:', currentSwipeId); - } else { - // console.log('[RPG Companion] â„šī¸ No stored data for swipe:', currentSwipeId); } // Re-render the panels @@ -414,6 +462,145 @@ export function onMessageSwiped(messageIndex) { updateChatThoughts(); } +/** + * Event handler for when a message is deleted. + * Restores RPG state from the last assistant message with RPG data, + * or clears state if no messages remain. + */ +export function onMessageDeleted(messageIndex) { + if (!extensionSettings.enabled) { + return; + } + + // console.log('[RPG Companion] đŸ—‘ī¸ EVENT: onMessageDeleted at index:', messageIndex); + + const context = getContext(); + const currentChat = context.chat; + + // If chat is empty, clear all RPG state + if (!currentChat || currentChat.length === 0) { + // console.log('[RPG Companion] đŸ—‘ī¸ Chat is empty - clearing RPG state'); + lastGeneratedData.userStats = null; + lastGeneratedData.infoBox = null; + lastGeneratedData.characterThoughts = null; + + committedTrackerData.userStats = null; + committedTrackerData.infoBox = null; + committedTrackerData.characterThoughts = null; + + // Clear parsed stats from extensionSettings + if (extensionSettings.userStats) { + extensionSettings.userStats = null; + } + + // Re-render empty panels + renderUserStats(); + renderInfoBox(); + renderThoughts(); + renderInventory(); + renderQuests(); + renderMusicPlayer($musicPlayerContainer[0]); + + // Update FAB widgets + updateFabWidgets(); + + // Update chat thought overlays (removes any remaining) + updateChatThoughts(); + + // Save the cleared state + saveChatData(); + return; + } + + // Find the last assistant message with RPG data + for (let i = currentChat.length - 1; i >= 0; i--) { + const message = currentChat[i]; + if (!message.is_user && message.extra?.rpg_companion_swipes) { + const swipeId = message.swipe_id || 0; + const swipeData = message.extra.rpg_companion_swipes[swipeId]; + + if (swipeData) { + // Check if this is the same data we already have displayed + const sameUserStats = lastGeneratedData.userStats === swipeData.userStats; + const sameInfoBox = lastGeneratedData.infoBox === swipeData.infoBox; + const sameThoughts = lastGeneratedData.characterThoughts === swipeData.characterThoughts; + + if (sameUserStats && sameInfoBox && sameThoughts) { + // console.log('[RPG Companion] đŸ—‘ī¸ RPG state already matches last message - no restore needed'); + return; + } + + // console.log('[RPG Companion] đŸ—‘ī¸ Restoring RPG state from message index', i, 'swipe', swipeId); + + // Restore state from this message + lastGeneratedData.userStats = swipeData.userStats || null; + lastGeneratedData.infoBox = swipeData.infoBox || null; + lastGeneratedData.characterThoughts = swipeData.characterThoughts || null; + + // Also update committed data so next generation uses correct context + committedTrackerData.userStats = swipeData.userStats || null; + committedTrackerData.infoBox = swipeData.infoBox || null; + committedTrackerData.characterThoughts = swipeData.characterThoughts || null; + + // Parse user stats if available + if (swipeData.userStats) { + parseUserStats(swipeData.userStats); + } + + // Re-render panels with restored data + renderUserStats(); + renderInfoBox(); + renderThoughts(); + renderInventory(); + renderQuests(); + renderMusicPlayer($musicPlayerContainer[0]); + + // Update FAB widgets + updateFabWidgets(); + + // Update chat thought overlays + updateChatThoughts(); + + // Save the restored state + saveChatData(); + return; + } + } + } + + // No assistant message with RPG data found - clear state + // console.log('[RPG Companion] đŸ—‘ī¸ No assistant message with RPG data found - clearing state'); + lastGeneratedData.userStats = null; + lastGeneratedData.infoBox = null; + lastGeneratedData.characterThoughts = null; + + committedTrackerData.userStats = null; + committedTrackerData.infoBox = null; + committedTrackerData.characterThoughts = null; + + // Clear parsed stats + if (extensionSettings.userStats) { + extensionSettings.userStats = null; + } + + // Re-render empty panels + renderUserStats(); + renderInfoBox(); + renderThoughts(); + renderInventory(); + renderQuests(); + renderMusicPlayer($musicPlayerContainer[0]); + + // Update FAB widgets + updateFabWidgets(); + + // Update chat thought overlays + updateChatThoughts(); + + // Save the cleared state + saveChatData(); +} + /** * Update the persona avatar image when user switches personas */