diff --git a/core/resolver.py b/core/resolver.py index 6345031..0648830 100644 --- a/core/resolver.py +++ b/core/resolver.py @@ -428,6 +428,11 @@ def analyze_and_find_matches( # Identify missing models missing_models = identify_missing_models(all_model_refs, available_models) + resolved_models = [ + {**model_ref, "matches": []} + for model_ref in all_model_refs + if model_ref.get("exists", False) + ] # Enrich missing models with workflow URLs for missing in missing_models: @@ -557,6 +562,8 @@ def analyze_and_find_matches( result = { "missing_models": missing_with_matches, + "resolved_models": resolved_models, + "total_resolved": len(resolved_models), "total_missing": len(missing_with_matches), "total_models_analyzed": len(all_model_refs), } diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css index 507f05d..0042d5f 100644 --- a/web/css/resolver-main.css +++ b/web/css/resolver-main.css @@ -558,6 +558,48 @@ gap: 8px; flex-wrap: wrap; } +.mr-missing-resolved-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + min-height: 26px; + padding: 0 8px; + color: var(--mr-text-muted); + background: var(--mr-missing-control-bg, rgba(255,255,255,0.035)); + border: 1px solid var(--mr-missing-control-border, rgba(255,255,255,0.105)); + border-radius: var(--mr-missing-control-radius, 7px); + cursor: pointer; + font-size: 11px; + font-weight: 700; + line-height: 1; + white-space: nowrap; + transition: var(--mr-missing-control-transition, background 0.15s ease, border-color 0.15s ease, color 0.15s ease); +} +.mr-missing-resolved-toggle:hover { + color: var(--mr-text); + background: var(--mr-missing-control-bg-hover, rgba(255,255,255,0.06)); + border-color: var(--mr-missing-control-border-hover, rgba(255,255,255,0.17)); +} +.mr-missing-resolved-toggle input { + width: 13px; + height: 13px; + margin: 0; + accent-color: #72d38a; +} +.mr-missing-resolved-toggle em { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 16px; + height: 16px; + padding: 0 4px; + border-radius: 999px; + color: #c9f6d0; + background: rgba(114,211,138,0.12); + font-style: normal; + font-size: 10px; +} .mr-missing-refresh-btn, #model-resolver-modal .mr-missing-refresh-btn { min-height: 26px; @@ -648,6 +690,9 @@ background: linear-gradient(90deg, rgba(114,211,138,0.12) 0%, rgba(114,211,138,0.055) 42%, rgba(114,211,138,0.018) 100%); box-shadow: inset 3px 0 0 rgba(114,211,138,0.92), inset 0 0 0 1px rgba(114,211,138,0.12); } +.mr-missing-list-row.is-resolved { + opacity: 0.72; +} .mr-missing-list-row.is-batch-selected { background: rgba(78,161,255,0.055); } @@ -715,6 +760,20 @@ font-size: 13px; font-weight: 700; } +.mr-missing-row-resolved-pill { + display: inline-flex; + align-items: center; + justify-self: start; + min-height: 17px; + padding: 1px 6px; + border-radius: 999px; + color: #c9f6d0; + background: rgba(114,211,138,0.10); + border: 1px solid rgba(114,211,138,0.20); + font-size: 9px; + font-weight: 750; + line-height: 1; +} .mr-missing-row-node { grid-column: 1; color: var(--mr-text-muted); diff --git a/web/resolver/actions/queue_methods.js b/web/resolver/actions/queue_methods.js index 37dd1d6..75fdfd2 100644 --- a/web/resolver/actions/queue_methods.js +++ b/web/resolver/actions/queue_methods.js @@ -1284,6 +1284,7 @@ export const queueMethods = { this.updateSelectedBarForMissing?.(m); this.updateApplyPendingButton?.(); this.updateQueuePanel(); + this.refreshMissingModelsBrowserFromCache?.(); }, // Clear all queued selections @@ -1293,6 +1294,7 @@ export const queueMethods = { this.savePendingQueueForActiveWorkflow(); this.updateApplyPendingButton?.(); this.updateQueuePanel(); + this.refreshMissingModelsBrowserFromCache?.(); try { document.querySelectorAll('.model-resolver-selected').forEach(el => { el.style.display = 'none'; el.innerHTML = ''; }); } catch (e) { /* ignore */ } @@ -1405,6 +1407,7 @@ export const queueMethods = { this.updateSelectedBarForMissing(m); this.updateApplyPendingButton?.(); this.updateQueuePanel(); + this.refreshMissingModelsBrowserFromCache?.(); } }, @@ -1748,6 +1751,7 @@ export const queueMethods = { this.updateSelectedBarForMissing?.(missing); this.updateQueuePanel(); this.updateApplyPendingButton(); + this.refreshMissingModelsBrowserFromCache?.(); }, /** diff --git a/web/resolver/resolver_dialog.js b/web/resolver/resolver_dialog.js index aac7916..afcc5be 100644 --- a/web/resolver/resolver_dialog.js +++ b/web/resolver/resolver_dialog.js @@ -68,6 +68,12 @@ export class ResolverManagerDialog extends ComfyDialog { this.sidebarTabId = "comfyui-model-resolver"; this.sidebarOpenModeStorageKey = "model_resolver_sidebar_open_mode"; this.missingBrowserSplitStorageKey = "model_resolver_missing_browser_detail_w"; + this.showResolvedModelsStorageKey = "model_resolver_show_resolved_models"; + try { + this.showResolvedModels = localStorage.getItem(this.showResolvedModelsStorageKey) === '1'; + } catch (e) { + this.showResolvedModels = false; + } this.dockButton = null; this.undockButton = null; this._floatingRectBeforeDock = null; diff --git a/web/resolver/views/missing_browser_methods.js b/web/resolver/views/missing_browser_methods.js index bfac52e..ba8ca42 100644 --- a/web/resolver/views/missing_browser_methods.js +++ b/web/resolver/views/missing_browser_methods.js @@ -223,12 +223,76 @@ export const missingBrowserMethods = { }; }, - renderMissingModelsBrowser(missingModels, selectedKey, totalMissing, activeCount, hasAny100Match) { + isMissingModelResolved(missing = {}) { + if (!missing) return false; + if (missing.__isExistingResolved) return true; + const key = this.getMissingModelKey(missing); + return Boolean(key && this.pendingIndex?.has?.(key)); + }, + + getResolvedWorkflowModels(data = {}) { + return (data.resolved_models || []).map(model => this.normalizeResolvedWorkflowModel(model)); + }, + + normalizeResolvedWorkflowModel(model = {}) { + const originalPath = model.original_path || model.name || model.filename || ''; + const filename = originalPath.split('/').pop()?.split('\\').pop() || originalPath || 'Resolved model'; + const fullPath = model.full_path || model.path || ''; + const relativePath = model.relative_path || originalPath || filename; + const category = model.category || 'unknown'; + const resolvedModel = { + path: fullPath, + relative_path: relativePath, + filename, + category, + resolved_path: fullPath + }; + + return { + ...model, + __isExistingResolved: true, + name: model.name || filename, + original_path: originalPath || filename, + category, + matches: [{ + confidence: 100, + match_type: 'exact', + filename, + path: fullPath, + model: resolvedModel + }] + }; + }, + + getVisibleMissingModels(missingModels = []) { + if (this.showResolvedModels) return missingModels; + return missingModels.filter(missing => !this.isMissingModelResolved(missing)); + }, + + getResolvedMissingCount(missingModels = []) { + return missingModels.reduce((count, missing) => ( + count + (this.isMissingModelResolved(missing) ? 1 : 0) + ), 0); + }, + + refreshMissingModelsBrowserFromCache() { + if (this.activeTab !== 'missing' || !this.contentElement || !this.cachedAnalysisData) return; + this.displayMissingModels(this.contentElement, this.cachedAnalysisData); + }, + + renderMissingModelsBrowser(missingModels, selectedKey, totalMissing, activeCount, hasAny100Match, options = {}) { + const hiddenResolvedCount = Number(options.hiddenResolvedCount || 0); + const resolvedCount = Number(options.resolvedCount || 0); + const rawMissingCount = Number(options.rawMissingCount ?? totalMissing); + const missingCount = Number(options.missingCount ?? rawMissingCount); + const resolvedToggleCount = this.showResolvedModels ? resolvedCount : hiddenResolvedCount; const stats = this.getMissingModelSummaryStats(missingModels); const detailIndex = missingModels.findIndex(missing => this.getMissingModelKey(missing) === selectedKey); const detailMissing = detailIndex >= 0 ? missingModels[detailIndex] : null; const activeHint = activeCount > 0 ? `${activeCount} downloading` + : hiddenResolvedCount > 0 + ? `${hiddenResolvedCount} resolved hidden` : (hasAny100Match ? 'Auto-link ready for exact matches' : 'Review matches or search online'); const listLayout = this.getMissingModelsListLayout(missingModels); let savedDetailWidth = null; @@ -239,13 +303,16 @@ export const missingBrowserMethods = { ? `--mr-missing-detail-track:${savedDetailWidth}px;` : ''; const listStyle = `--mr-missing-model-col:${listLayout.modelPx}px;--mr-missing-type-col:${listLayout.typePx}px;`; + const titleText = this.showResolvedModels && resolvedCount > 0 + ? `${totalMissing} shown (${missingCount} missing / ${resolvedCount} resolved)` + : `${totalMissing} missing model${totalMissing === 1 ? '' : 's'}`; let html = `