diff --git a/web/resolver/actions/queue_methods.js b/web/resolver/actions/queue_methods.js index 835ecc0..97a56bc 100644 --- a/web/resolver/actions/queue_methods.js +++ b/web/resolver/actions/queue_methods.js @@ -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; } }, diff --git a/web/resolver/actions/resolve_download_methods.js b/web/resolver/actions/resolve_download_methods.js index 65c5eb9..d891de0 100644 --- a/web/resolver/actions/resolve_download_methods.js +++ b/web/resolver/actions/resolve_download_methods.js @@ -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');