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;
|
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")
|
* Temporary storage for pending dice roll (not saved until user clicks "Save Roll")
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import {
|
|||||||
lastActionWasSwipe,
|
lastActionWasSwipe,
|
||||||
setIsGenerating,
|
setIsGenerating,
|
||||||
setLastActionWasSwipe,
|
setLastActionWasSwipe,
|
||||||
$musicPlayerContainer
|
$musicPlayerContainer,
|
||||||
|
getSeparateGenerationId
|
||||||
} from '../../core/state.js';
|
} from '../../core/state.js';
|
||||||
import { saveChatData, mirrorToSwipeInfo } from '../../core/persistence.js';
|
import { saveChatData, mirrorToSwipeInfo } from '../../core/persistence.js';
|
||||||
import {
|
import {
|
||||||
@@ -218,7 +219,7 @@ export async function switchToPreset(presetName) {
|
|||||||
* @param {Function} renderThoughts - UI function to render character thoughts
|
* @param {Function} renderThoughts - UI function to render character thoughts
|
||||||
* @param {Function} renderInventory - UI function to render inventory
|
* @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) {
|
if (isGenerating) {
|
||||||
// console.log('[RPG Companion] Already generating, skipping...');
|
// console.log('[RPG Companion] Already generating, skipping...');
|
||||||
return;
|
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) {
|
if (response) {
|
||||||
// console.log('[RPG Companion] Raw AI response:', response);
|
// console.log('[RPG Companion] Raw AI response:', response);
|
||||||
const parsedData = parseResponse(response);
|
const parsedData = parseResponse(response);
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import {
|
|||||||
setIsAwaitingNewMessage,
|
setIsAwaitingNewMessage,
|
||||||
updateLastGeneratedData,
|
updateLastGeneratedData,
|
||||||
updateCommittedTrackerData,
|
updateCommittedTrackerData,
|
||||||
$musicPlayerContainer
|
$musicPlayerContainer,
|
||||||
|
getSeparateGenerationId,
|
||||||
|
incrementSeparateGenerationId
|
||||||
} from '../../core/state.js';
|
} from '../../core/state.js';
|
||||||
import { saveChatData, loadChatData, autoSwitchPresetForEntity, getSwipeData, commitTrackerDataFromPriorMessage, mirrorToSwipeInfo } from '../../core/persistence.js';
|
import { saveChatData, loadChatData, autoSwitchPresetForEntity, getSwipeData, commitTrackerDataFromPriorMessage, mirrorToSwipeInfo } from '../../core/persistence.js';
|
||||||
import { i18n } from '../../core/i18n.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)
|
// Trigger auto-update if enabled (for both separate and external modes)
|
||||||
// Only trigger if this is a newly generated message, not loading chat history
|
// Only trigger if this is a newly generated message, not loading chat history
|
||||||
if (extensionSettings.autoUpdate && isAwaitingNewMessage) {
|
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 () => {
|
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
|
// Update FAB widgets and strip widgets after separate/external mode update completes
|
||||||
setFabLoadingState(false);
|
setFabLoadingState(false);
|
||||||
updateFabWidgets();
|
updateFabWidgets();
|
||||||
@@ -439,6 +446,10 @@ export function onMessageDeleted() {
|
|||||||
|
|
||||||
// console.log('[RPG Companion] 🗑️ EVENT: 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;
|
const currentChat = getContext().chat;
|
||||||
|
|
||||||
// Walk backward to find the new last assistant message.
|
// Walk backward to find the new last assistant message.
|
||||||
|
|||||||
Reference in New Issue
Block a user