Fix RPG state restoration on message delete and swipe
- Add MESSAGE_DELETED event handler to restore state from last assistant message - Fix swipe to use previous message's data for LLM context (prevents time/story advancing) - Update UI to show rolled-back state immediately when triggering new swipe - Handle edge cases: empty chat, first message swipe, no previous RPG data
This commit is contained in:
@@ -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
|
||||
});
|
||||
|
||||
@@ -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,20 +375,66 @@ 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 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
|
||||
// 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 swipe data into lastGeneratedData for display (both modes)
|
||||
// Load swipe data into lastGeneratedData for display
|
||||
lastGeneratedData.userStats = swipeData.userStats || null;
|
||||
lastGeneratedData.infoBox = swipeData.infoBox || null;
|
||||
lastGeneratedData.characterThoughts = swipeData.characterThoughts || null;
|
||||
@@ -401,6 +448,7 @@ export function onMessageSwiped(messageIndex) {
|
||||
} else {
|
||||
// console.log('[RPG Companion] ℹ️ No stored data for swipe:', currentSwipeId);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-render the panels
|
||||
renderUserStats();
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user