Refactor frontend apply path for auto-resolve and queue selections

This commit is contained in:
Dariusz L
2026-07-04 17:45:17 +02:00
parent 245bf79800
commit 44f539ce49
2 changed files with 79 additions and 95 deletions
+20 -13
View File
@@ -2030,17 +2030,9 @@ export const queueMethods = {
},
/**
* Queue a resolution for later batch apply
*/
queueResolution(missing, resolvedModel) {
if (!resolvedModel) {
this.showNotification('No model selected', 'error');
return;
}
buildResolutionSelection(missing, resolvedModel) {
const missingKey = this.getMissingModelKey(missing);
const resolution = {
return {
missing_key: missingKey,
missing_search_key: this.getMissingSearchKey?.(missing),
node_id: missing.node_id,
@@ -2061,6 +2053,18 @@ export const queueMethods = {
node_type: missing.node_type,
node_label: missing.subgraph_name || missing.node_type
};
},
/**
* Queue a resolution for later batch apply
*/
queueResolution(missing, resolvedModel) {
if (!resolvedModel) {
this.showNotification('No model selected', 'error');
return;
}
const resolution = this.buildResolutionSelection(missing, resolvedModel);
const key = this.getResolutionQueueKey(resolution);
if (this.pendingIndex.has(key)) {
@@ -2116,7 +2120,7 @@ export const queueMethods = {
async applyPendingResolutionList(list, { clearAll = false } = {}) {
if (!list.length) {
this.showNotification('No selections queued', 'error');
return;
return null;
}
try {
@@ -2124,12 +2128,12 @@ export const queueMethods = {
const applyResolutions = this.expandPendingResolutionsForApply(appliedSelections);
if (!applyResolutions.length) {
this.showNotification('No valid selections to apply', 'error');
return;
return null;
}
const workflow = this.getCurrentWorkflow();
if (!workflow) {
this.showNotification('No workflow loaded', 'error');
return;
return null;
}
const data = await this.fetchJson('/model_resolver/resolve', {
@@ -2167,12 +2171,15 @@ export const queueMethods = {
: '';
this.showNotification(`✓ Linked ${selectionCount} selection${selectionCount > 1 ? 's' : ''}${refText}`, 'success');
this.refreshAnalysisInBackground(data.workflow, this.getWorkflowSignature(data.workflow));
return data.workflow || null;
} else {
this.showNotification('Failed to apply selections: ' + (data.error || 'Unknown error'), 'error');
return null;
}
} catch (e) {
console.error('Model Resolver: applyPendingResolutions error', e);
this.showNotification('Error applying selections: ' + e.message, 'error');
return null;
}
},
@@ -84,8 +84,28 @@ export const resolveDownloadMethods = {
}
},
getExactLocalMatchSelections(missingModels = []) {
const selections = [];
const seenKeys = new Set();
for (const missing of Array.isArray(missingModels) ? missingModels : []) {
const match = this.getBestLocalMatch?.(missing, 100)
|| (missing.matches || []).find(item => Number(item.confidence || 0) >= 100);
if (!match?.model) continue;
const selection = this.buildResolutionSelection(missing, match.model);
const key = this.getResolutionQueueKey(selection);
if (!key || seenKeys.has(key)) continue;
seenKeys.add(key);
selections.push(selection);
}
return selections;
},
/**
* Auto-resolve all 100% confidence matches
* Auto-resolve all 100% confidence matches using the same apply path as queued selections.
* @returns {object|null} The updated workflow if successful, null otherwise
*/
async autoResolve100Percent() {
@@ -96,94 +116,51 @@ export const resolveDownloadMethods = {
return null;
}
// Analyze workflow first
const analyzeData = await this.fetchJson('/model_resolver/analyze', {
method: 'POST',
body: JSON.stringify({ workflow })
}, 'Analyze workflow');
const missingModels = analyzeData.missing_models || [];
const workflowSignature = this.getWorkflowSignature?.(workflow);
const canUseCurrentAnalysis = Boolean(
this.isVisible?.()
&& workflowSignature
&& this.activeWorkflowSignature === workflowSignature
&& this.cachedWorkflowSignature === workflowSignature
);
let missingModels = canUseCurrentAnalysis && Array.isArray(this.missingModels)
? this.missingModels
: [];
let selections = this.getExactLocalMatchSelections(missingModels);
// Collect all 100% matches
const resolutions = [];
const seenResolutionKeys = new Set();
for (const missing of missingModels) {
const matches = missing.matches || [];
const perfectMatch = matches.find((m) => m.confidence === 100);
if (perfectMatch && perfectMatch.model) {
const nodeRefs = Array.isArray(missing.all_node_refs) && missing.all_node_refs.length
? missing.all_node_refs
: [missing];
for (const ref of nodeRefs) {
const resolutionKey = [
ref.node_id ?? '',
ref.widget_index ?? '',
ref.subgraph_id || '',
ref.is_top_level !== false ? 'T' : 'F',
ref.nested_key || '',
ref.name || ref.original_path || ''
].join(':');
if (seenResolutionKeys.has(resolutionKey)) continue;
seenResolutionKeys.add(resolutionKey);
resolutions.push({
missing_key: this.getMissingModelKey?.(missing),
missing_search_key: this.getMissingSearchKey?.(missing),
node_id: ref.node_id,
widget_index: ref.widget_index,
resolved_path: perfectMatch.model.path,
category: ref.category || missing.category,
resolved_model: perfectMatch.model,
subgraph_id: ref.subgraph_id, // Include subgraph_id for subgraph nodes
is_top_level: ref.is_top_level, // True for top-level nodes, False for nodes in subgraph definitions
is_lora_v2: ref.is_lora_v2,
original_lora_name: ref.name || ref.original_path,
nested_key: ref.nested_key
});
}
}
if (
canUseCurrentAnalysis
&& !selections.length
&& !missingModels.length
&& Array.isArray(this.cachedAnalysisData?.missing_models)
) {
missingModels = this.cachedAnalysisData.missing_models;
selections = this.getExactLocalMatchSelections(missingModels);
}
if (resolutions.length === 0) {
if (!selections.length && !missingModels.length) {
const analyzeData = await this.fetchJson('/model_resolver/analyze', {
method: 'POST',
body: JSON.stringify({ workflow })
}, 'Analyze workflow');
this.applyResolvedSelectionAliasesToAnalysisData?.(analyzeData);
this.syncWorkflowScopedQueue?.(workflow);
if (workflowSignature) {
this.cachedWorkflowSignature = workflowSignature;
}
this.cachedAnalysisData = this.cloneAnalysisData?.(analyzeData) || analyzeData;
missingModels = Array.isArray(analyzeData.missing_models) ? analyzeData.missing_models : [];
this.missingModels = missingModels;
selections = this.getExactLocalMatchSelections(missingModels);
}
if (!selections.length) {
this.showNotification('No 100% confidence matches found to auto-resolve.', 'error');
return null;
}
// Apply resolutions
const resolveData = await this.fetchJson('/model_resolver/resolve', {
method: 'POST',
body: JSON.stringify({
workflow,
resolutions
})
}, 'Resolve models');
if (resolveData.success) {
await this.refreshComfyModelCatalogAfterApply?.(resolveData.workflow, resolutions);
// Update workflow in ComfyUI
await this.updateWorkflowInComfyUI(resolveData.workflow);
// Show success notification
this.showNotification(
`✓ Successfully linked ${resolutions.length} model${resolutions.length > 1 ? 's' : ''}!`,
'success'
);
this.rememberAppliedResolvedSelections?.(resolutions);
// Reload dialog using the updated workflow from API response (if dialog is visible)
if (this.contentElement) {
this.preserveSearchCacheAcrossNextWorkflowSync = true;
await this.loadWorkflowData(resolveData.workflow, { force: true });
}
// Return the updated workflow for callers who need it
return resolveData.workflow;
} else {
this.showNotification('Failed to resolve models: ' + (resolveData.error || 'Unknown error'), 'error');
return null;
}
this.closeFooterMenus?.();
return await this.applyPendingResolutionList(selections, { clearAll: false });
} catch (error) {
console.error('Model Resolver: Error auto-resolving:', error);
this.showNotification('Error auto-resolving: ' + error.message, 'error');