fix: prevent stale tab renders by scoping workflow and loaded models data loading to their respective tokens

This commit is contained in:
Dariusz L
2026-06-07 18:37:00 +02:00
parent 9160b365b0
commit e61c868038
7 changed files with 91 additions and 29 deletions
+2
View File
@@ -66,6 +66,8 @@ export class ResolverManagerDialog extends ComfyDialog {
this._dragging = false;
this._dragStart = null;
this._analysisProgressToken = null;
this._workflowDataLoadToken = null;
this._loadedModelsLoadToken = null;
this._locateAnimationFrame = null;
this._viewportClampFrame = null;
this.activeWorkflowRouteKey = this.getActiveWorkflowRouteKey();
@@ -704,6 +704,8 @@ export const downloadTargetMethods = {
this.downloadSubfolders.clear();
this.downloadTargetSelections?.clear();
this._analysisProgressToken = null;
this._workflowDataLoadToken = null;
this._loadedModelsLoadToken = null;
await this.ensureCapabilitiesLoaded();
await this.ensureBaseModelsLoaded();
+43 -19
View File
@@ -116,6 +116,14 @@ export const lifecycleGraphMethods = {
async loadWorkflowData(workflow = null, { force = false } = {}) {
if (!this.contentElement) return;
const loadToken = `missing-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
this._workflowDataLoadToken = loadToken;
const shouldRenderMissingModels = () => (
this.activeTab === 'missing' &&
this._workflowDataLoadToken === loadToken &&
this.contentElement
);
// Show loading state
try {
// Use provided workflow, or get current workflow from ComfyUI
@@ -124,8 +132,12 @@ export const lifecycleGraphMethods = {
}
if (!workflow) {
this._analysisProgressToken = null;
this.contentElement.innerHTML = '<p>No workflow loaded. Please load a workflow first.</p>';
if (this._workflowDataLoadToken === loadToken) {
this._analysisProgressToken = null;
}
if (shouldRenderMissingModels()) {
this.contentElement.innerHTML = '<p>No workflow loaded. Please load a workflow first.</p>';
}
return;
}
this.syncWorkflowScopedQueue(workflow);
@@ -140,19 +152,23 @@ export const lifecycleGraphMethods = {
this.cachedWorkflowSignature === workflowSignature &&
this.cachedAnalysisData
) {
this.displayMissingModels(this.contentElement, this.cachedAnalysisData);
this.reconnectActiveDownloads();
if (shouldRenderMissingModels()) {
this.displayMissingModels(this.contentElement, this.cachedAnalysisData);
this.reconnectActiveDownloads();
}
return;
}
const analysisId = `an-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
this._analysisProgressToken = analysisId;
this.contentElement.innerHTML = this.renderAnalysisProgress({
status: 'starting',
message: 'Starting analysis...',
current: 0,
total: 0
});
if (shouldRenderMissingModels()) {
this.contentElement.innerHTML = this.renderAnalysisProgress({
status: 'starting',
message: 'Starting analysis...',
current: 0,
total: 0
});
}
// Call analyze endpoint
const progressPromise = this.pollAnalysisProgress(analysisId, analysisId);
@@ -161,7 +177,9 @@ export const lifecycleGraphMethods = {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ workflow, analysis_id: analysisId })
});
this._analysisProgressToken = null;
if (this._analysisProgressToken === analysisId) {
this._analysisProgressToken = null;
}
await progressPromise;
if (!response.ok) {
@@ -169,18 +187,24 @@ export const lifecycleGraphMethods = {
}
const data = await response.json();
this.cachedWorkflowSignature = workflowSignature;
this.cachedAnalysisData = data;
this.saveAnalysisCacheForActiveWorkflow();
this.displayMissingModels(this.contentElement, data);
if (this._workflowDataLoadToken === loadToken) {
this.cachedWorkflowSignature = workflowSignature;
this.cachedAnalysisData = data;
this.saveAnalysisCacheForActiveWorkflow();
}
if (shouldRenderMissingModels()) {
this.displayMissingModels(this.contentElement, data);
// Reconnect any active downloads to their new progress divs
this.reconnectActiveDownloads();
// Reconnect any active downloads to their new progress divs
this.reconnectActiveDownloads();
}
} catch (error) {
this._analysisProgressToken = null;
if (this._workflowDataLoadToken === loadToken) {
this._analysisProgressToken = null;
}
console.error('Model Resolver: Error loading workflow data:', error);
if (this.contentElement) {
if (shouldRenderMissingModels()) {
this.contentElement.innerHTML = `<p class="mr-error-text">Error: ${error.message}</p>`;
}
}
@@ -306,6 +306,9 @@ export const workflowStateMethods = {
this.cachedAnalysisData = null;
this.cachedLoadedModelsSignature = null;
this.cachedLoadedModelsData = null;
this._analysisProgressToken = null;
this._workflowDataLoadToken = null;
this._loadedModelsLoadToken = null;
for (const [missingSearchKey, state] of this.searchResultCache.entries()) {
if (!this.hasBackgroundSearchJob?.(key, missingSearchKey, state.activeSearchRunId)) {
state.activeSearchRunId = null;
+9 -1
View File
@@ -162,10 +162,18 @@ export const renderFormatMethods = {
while (this._analysisProgressToken === token) {
try {
const response = await api.fetchApi(`/model_resolver/analyze-progress/${analysisId}`);
if (response.ok && this.contentElement && this._analysisProgressToken === token) {
if (
response.ok &&
this.contentElement &&
this._analysisProgressToken === token &&
this.activeTab === 'missing'
) {
const progress = await response.json();
this.contentElement.innerHTML = this.renderAnalysisProgress(progress);
if (progress.status === 'completed' || progress.status === 'error') {
if (this._analysisProgressToken === token) {
this._analysisProgressToken = null;
}
return;
}
}
@@ -685,7 +685,6 @@ export const missingBrowserMethods = {
// Skip rendering if active tab is not "missing"
if (this.activeTab !== 'missing') {
container.innerHTML = '';
return;
}
+32 -8
View File
@@ -38,6 +38,12 @@ export const tabsLoadedMethods = {
this.activeTab = nextTab;
this.persistActiveTab(this.activeTab);
if (this.activeTab !== 'missing') {
this._analysisProgressToken = null;
}
if (this.activeTab !== 'loaded') {
this._loadedModelsLoadToken = null;
}
this.hideTooltip();
this.animateTabContentTransition();
this.updateTabButtonStates();
@@ -67,10 +73,20 @@ export const tabsLoadedMethods = {
async loadLoadedModels(workflow = null, { force = false } = {}) {
if (!this.contentElement) return;
const loadToken = `loaded-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
this._loadedModelsLoadToken = loadToken;
const shouldRenderLoadedModels = () => (
this.activeTab === 'loaded' &&
this._loadedModelsLoadToken === loadToken &&
this.contentElement
);
try {
workflow = workflow || this.getCurrentWorkflow();
if (!workflow) {
this.contentElement.innerHTML = '<p>No workflow loaded. Please load a workflow first.</p>';
if (shouldRenderLoadedModels()) {
this.contentElement.innerHTML = '<p>No workflow loaded. Please load a workflow first.</p>';
}
return;
}
this.syncWorkflowScopedQueue(workflow);
@@ -82,11 +98,15 @@ export const tabsLoadedMethods = {
this.cachedLoadedModelsSignature === workflowSignature &&
this.cachedLoadedModelsData
) {
this.displayLoadedModels(this.contentElement, this.cachedLoadedModelsData);
if (shouldRenderLoadedModels()) {
this.displayLoadedModels(this.contentElement, this.cachedLoadedModelsData);
}
return;
}
this.contentElement.innerHTML = '<p>Loading loaded models...</p>';
if (shouldRenderLoadedModels()) {
this.contentElement.innerHTML = '<p>Loading loaded models...</p>';
}
const response = await api.fetchApi('/model_resolver/loaded', {
method: 'POST',
@@ -99,14 +119,18 @@ export const tabsLoadedMethods = {
}
const data = await response.json();
this.cachedLoadedModelsSignature = workflowSignature;
this.cachedLoadedModelsData = data;
this.saveLoadedModelsCacheForActiveWorkflow();
this.displayLoadedModels(this.contentElement, data);
if (this._loadedModelsLoadToken === loadToken) {
this.cachedLoadedModelsSignature = workflowSignature;
this.cachedLoadedModelsData = data;
this.saveLoadedModelsCacheForActiveWorkflow();
}
if (shouldRenderLoadedModels()) {
this.displayLoadedModels(this.contentElement, data);
}
} catch (error) {
console.error('Model Resolver: Error loading loaded models:', error);
if (this.contentElement) {
if (shouldRenderLoadedModels()) {
this.contentElement.innerHTML = `<p class="mr-error-text">Error: ${error.message}</p>`;
}
}