Implement separate generation ID to ensure that messages deleted during separate tracker generation do not attempt to apply the received data to a now non-existent message
This commit is contained in:
@@ -381,6 +381,32 @@ export let isPlotProgression = false;
|
||||
*/
|
||||
export let isAwaitingNewMessage = false;
|
||||
|
||||
/**
|
||||
* Monotonically-increasing counter used to detect stale separate-mode tracker
|
||||
* generation results. Incremented each time a new automated generation is
|
||||
* triggered or a message deletion occurs so any in-flight (or pending) call
|
||||
* from a previous generation can recognise that its result is no longer valid.
|
||||
*/
|
||||
let separateGenerationId = 0;
|
||||
|
||||
/**
|
||||
* Returns the current separate generation ID.
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getSeparateGenerationId() {
|
||||
return separateGenerationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments and returns the new separate generation ID.
|
||||
* Call this when starting a new generation or when a deletion
|
||||
* invalidates any pending/in-flight generation.
|
||||
* @returns {number} The new ID
|
||||
*/
|
||||
export function incrementSeparateGenerationId() {
|
||||
return ++separateGenerationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary storage for pending dice roll (not saved until user clicks "Save Roll")
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
lastActionWasSwipe,
|
||||
setIsGenerating,
|
||||
setLastActionWasSwipe,
|
||||
$musicPlayerContainer
|
||||
$musicPlayerContainer,
|
||||
getSeparateGenerationId
|
||||
} from '../../core/state.js';
|
||||
import { saveChatData, mirrorToSwipeInfo } from '../../core/persistence.js';
|
||||
import {
|
||||
@@ -218,7 +219,7 @@ export async function switchToPreset(presetName) {
|
||||
* @param {Function} renderThoughts - UI function to render character thoughts
|
||||
* @param {Function} renderInventory - UI function to render inventory
|
||||
*/
|
||||
export async function updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory) {
|
||||
export async function updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory, generationId = null) {
|
||||
if (isGenerating) {
|
||||
// console.log('[RPG Companion] Already generating, skipping...');
|
||||
return;
|
||||
@@ -262,6 +263,14 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
||||
});
|
||||
}
|
||||
|
||||
// If a generationId was provided and the counter has since been incremented
|
||||
// (by a deletion or a newer generation), discard this result entirely.
|
||||
// The finally block still runs to restore button state.
|
||||
if (generationId !== null && getSeparateGenerationId() !== generationId) {
|
||||
// console.log('[RPG Companion] ⚠️ Separate generation result discarded — superseded (genId', generationId, '!= current', getSeparateGenerationId(), ')');
|
||||
return;
|
||||
}
|
||||
|
||||
if (response) {
|
||||
// console.log('[RPG Companion] Raw AI response:', response);
|
||||
const parsedData = parseResponse(response);
|
||||
|
||||
@@ -20,7 +20,9 @@ import {
|
||||
setIsAwaitingNewMessage,
|
||||
updateLastGeneratedData,
|
||||
updateCommittedTrackerData,
|
||||
$musicPlayerContainer
|
||||
$musicPlayerContainer,
|
||||
getSeparateGenerationId,
|
||||
incrementSeparateGenerationId
|
||||
} from '../../core/state.js';
|
||||
import { saveChatData, loadChatData, autoSwitchPresetForEntity, getSwipeData, commitTrackerDataFromPriorMessage, mirrorToSwipeInfo } from '../../core/persistence.js';
|
||||
import { i18n } from '../../core/i18n.js';
|
||||
@@ -266,8 +268,13 @@ export async function onMessageReceived(data) {
|
||||
// Trigger auto-update if enabled (for both separate and external modes)
|
||||
// Only trigger if this is a newly generated message, not loading chat history
|
||||
if (extensionSettings.autoUpdate && isAwaitingNewMessage) {
|
||||
// Capture the current generation ID before the async gap so that any
|
||||
// message deletion (or a newer generation) that increments the counter
|
||||
// while the 500ms timer or the API call is in-flight will cause
|
||||
// updateRPGData to discard its result rather than stomping the UI.
|
||||
const genId = incrementSeparateGenerationId();
|
||||
setTimeout(async () => {
|
||||
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
|
||||
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory, genId);
|
||||
// Update FAB widgets and strip widgets after separate/external mode update completes
|
||||
setFabLoadingState(false);
|
||||
updateFabWidgets();
|
||||
@@ -439,6 +446,10 @@ export function onMessageDeleted() {
|
||||
|
||||
// console.log('[RPG Companion] 🗑️ EVENT: onMessageDeleted');
|
||||
|
||||
// Invalidate any pending or in-flight separate-mode generation so
|
||||
// its result is not applied to the (now-changed) chat tail.
|
||||
incrementSeparateGenerationId();
|
||||
|
||||
const currentChat = getContext().chat;
|
||||
|
||||
// Walk backward to find the new last assistant message.
|
||||
|
||||
Reference in New Issue
Block a user