From 34049c5ea62cf8a8ebd71a03aefdac6af43446b1 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Mon, 22 Jun 2026 03:40:37 +0200 Subject: [PATCH] Add LTX-2.3 models and resolved selection alias matching --- metadata/model-list.json | 121 ++++++++++++ metadata/model-list.meta.json | 8 +- web/resolver/actions/queue_methods.js | 181 ++++++++++++++++++ web/resolver/views/missing_browser_methods.js | 28 +++ 4 files changed, 334 insertions(+), 4 deletions(-) diff --git a/metadata/model-list.json b/metadata/model-list.json index db288bf..5891162 100644 --- a/metadata/model-list.json +++ b/metadata/model-list.json @@ -5313,6 +5313,127 @@ "url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Static/resolve/main/ltx-2-19b-lora-camera-control-static.safetensors", "size": "2.21GB" }, + { + "name": "LTX-2.3 22B Dev", + "type": "checkpoint", + "base": "LTX-2.3", + "save_path": "checkpoints/LTX-2.3", + "description": "LTX-2.3 22B development model.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3", + "filename": "ltx-2.3-22b-dev.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-dev.safetensors", + "size": "42.98GB" + }, + { + "name": "LTX-2.3 22B Distilled 1.1", + "type": "checkpoint", + "base": "LTX-2.3", + "save_path": "checkpoints/LTX-2.3", + "description": "LTX-2.3 22B distilled model v1.1.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3", + "filename": "ltx-2.3-22b-distilled-1.1.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-1.1.safetensors", + "size": "42.98GB" + }, + { + "name": "LTX-2.3 22B Dev FP8", + "type": "checkpoint", + "base": "LTX-2.3", + "save_path": "checkpoints/LTX-2.3", + "description": "LTX-2.3 22B Dev FP8 model.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3-fp8", + "filename": "ltx-2.3-22b-dev-fp8.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3-fp8/resolve/main/ltx-2.3-22b-dev-fp8.safetensors", + "size": "27.14GB" + }, + { + "name": "LTX-2.3 22B Distilled FP8", + "type": "checkpoint", + "base": "LTX-2.3", + "save_path": "checkpoints/LTX-2.3", + "description": "LTX-2.3 22B Distilled FP8 model.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3-fp8", + "filename": "ltx-2.3-22b-distilled-fp8.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3-fp8/resolve/main/ltx-2.3-22b-distilled-fp8.safetensors", + "size": "27.50GB" + }, + { + "name": "LTX-2.3 22B Dev NVFP4", + "type": "checkpoint", + "base": "LTX-2.3", + "save_path": "checkpoints/LTX-2.3", + "description": "LTX-2.3 22B Dev NVFP4 model.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3-nvfp4", + "filename": "ltx-2.3-22b-dev-nvfp4.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3-nvfp4/resolve/main/ltx-2.3-22b-dev-nvfp4.safetensors", + "size": "20.21GB" + }, + { + "name": "LTX-2.3 22B Distilled LoRA 1.1", + "type": "lora", + "base": "LTX-2.3", + "save_path": "loras/ltxv/ltx2", + "description": "A LoRA adapter that transforms the standard LTX-2.3 22B model into a distilled version when loaded.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3", + "filename": "ltx-2.3-22b-distilled-lora-384-1.1.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-22b-distilled-lora-384-1.1.safetensors", + "size": "7.08GB" + }, + { + "name": "LTX-2.3 22B IC-LoRA Union Control", + "type": "lora", + "base": "LTX-2.3", + "save_path": "loras/ltxv/ltx2", + "description": "In-Context LoRA (IC LoRA) for unified multi-condition control (canny, depth, pose) guided video generation.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3-22b-IC-LoRA-Union-Control", + "filename": "ltx-2.3-22b-ic-lora-union-control-ref0.5.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3-22b-IC-LoRA-Union-Control/resolve/main/ltx-2.3-22b-ic-lora-union-control-ref0.5.safetensors", + "size": "0.61GB" + }, + { + "name": "LTX-2.3 22B IC-LoRA Motion Track Control", + "type": "lora", + "base": "LTX-2.3", + "save_path": "loras/ltxv/ltx2", + "description": "In-Context LoRA (IC LoRA) for motion track guided video generation.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3-22b-IC-LoRA-Motion-Track-Control", + "filename": "ltx-2.3-22b-ic-lora-motion-track-control-ref0.5.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3-22b-IC-LoRA-Motion-Track-Control/resolve/main/ltx-2.3-22b-ic-lora-motion-track-control-ref0.5.safetensors", + "size": "0.30GB" + }, + { + "name": "LTX-2.3 Spatial Upscaler x2 1.1", + "type": "upscale", + "base": "upscale", + "save_path": "latent_upscale_models", + "description": "Spatial upscaler model for LTX-2.3. This model enhances the spatial resolution of generated videos by 2x.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3", + "filename": "ltx-2.3-spatial-upscaler-x2-1.1.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-spatial-upscaler-x2-1.1.safetensors", + "size": "0.93GB" + }, + { + "name": "LTX-2.3 Spatial Upscaler x1.5", + "type": "upscale", + "base": "upscale", + "save_path": "latent_upscale_models", + "description": "Spatial upscaler model for LTX-2.3. This model enhances the spatial resolution of generated videos by 1.5x.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3", + "filename": "ltx-2.3-spatial-upscaler-x1.5-1.0.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-spatial-upscaler-x1.5-1.0.safetensors", + "size": "1.02GB" + }, + { + "name": "LTX-2.3 Temporal Upscaler x2", + "type": "upscale", + "base": "upscale", + "save_path": "latent_upscale_models", + "description": "Temporal upscaler model for LTX-2.3. This model enhances the temporal resolution of generated videos.", + "reference": "https://huggingface.co/Lightricks/LTX-2.3", + "filename": "ltx-2.3-temporal-upscaler-x2-1.0.safetensors", + "url": "https://huggingface.co/Lightricks/LTX-2.3/resolve/main/ltx-2.3-temporal-upscaler-x2-1.0.safetensors", + "size": "0.24GB" + }, { "name": "LTX-Video Spatial Upscaler v0.9.7", "type": "upscale", diff --git a/metadata/model-list.meta.json b/metadata/model-list.meta.json index 42a2649..adf7b29 100644 --- a/metadata/model-list.meta.json +++ b/metadata/model-list.meta.json @@ -2,8 +2,8 @@ "source": "Comfy-Org/ComfyUI-Manager", "source_url": "https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main/model-list.json", "github_url": "https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/model-list.json", - "sha": "5fcb5f0637f9fbdedaf8d37da9a390f76ae0ded8", - "size": 268817, - "model_count": 527, - "updated_at": "2026-06-05T01:04:57+00:00" + "sha": "5d9308f50a08cf3cfb47e6a25cc525425714d3ad", + "size": 274526, + "model_count": 538, + "updated_at": "2026-06-22T01:35:07+00:00" } diff --git a/web/resolver/actions/queue_methods.js b/web/resolver/actions/queue_methods.js index aa9547d..1950508 100644 --- a/web/resolver/actions/queue_methods.js +++ b/web/resolver/actions/queue_methods.js @@ -1815,6 +1815,7 @@ export const queueMethods = { if (data.success) { const optimisticData = this.getOptimisticAnalysisDataAfterApply(appliedSelections); await this.updateWorkflowInComfyUI(data.workflow); + this.rememberAppliedResolvedSelections(appliedSelections); const appliedKeys = new Set(appliedSelections.map(selection => this.getResolutionQueueKey(selection))); const remainingSelections = clearAll @@ -1950,6 +1951,169 @@ export const queueMethods = { return (this.missingModels || []).filter(missing => activeKeys.has(this.getMissingModelKey(missing))); }, + getResolvedSelectionPathCandidates(selection = {}) { + const model = selection.resolved_model || {}; + return [ + model.relative_path, + model.filename, + model.name, + model.path, + model.resolved_path, + selection.resolved_path + ].filter(Boolean); + }, + + normalizeResolvedSelectionToken(value = '', { basename = false, stem = false } = {}) { + let text = String(value || '').trim().replace(/\\/g, '/').toLowerCase(); + if (!text) return ''; + if (basename) { + text = text.split('/').pop() || text; + } + if (stem) { + text = text.replace(/\.(safetensors|ckpt|pt|pth|bin|gguf|onnx)$/i, ''); + } + return text; + }, + + getResolvedSelectionTokenSet(values = []) { + const tokens = new Set(); + for (const value of values) { + const normalized = this.normalizeResolvedSelectionToken(value); + const basename = this.normalizeResolvedSelectionToken(value, { basename: true }); + const stem = this.normalizeResolvedSelectionToken(value, { basename: true, stem: true }); + if (normalized) tokens.add(normalized); + if (basename) tokens.add(basename); + if (stem) tokens.add(stem); + } + return tokens; + }, + + getResolvedSelectionRefs(selection = {}) { + const refs = Array.isArray(selection.node_refs) && selection.node_refs.length + ? selection.node_refs + : [selection]; + return refs.filter(Boolean).map(ref => ({ + node_id: ref.node_id ?? selection.node_id, + widget_index: ref.widget_index ?? selection.widget_index, + subgraph_id: ref.subgraph_id ?? selection.subgraph_id ?? '', + is_top_level: ref.is_top_level ?? selection.is_top_level, + nested_key: ref.nested_key ?? selection.nested_key ?? '' + })); + }, + + rememberAppliedResolvedSelections(appliedSelections = []) { + if (!(this.appliedResolvedSelectionAliases instanceof Map)) { + this.appliedResolvedSelectionAliases = new Map(); + } + + for (const selection of Array.isArray(appliedSelections) ? appliedSelections : []) { + const key = this.getResolutionQueueKey(selection); + if (!key) continue; + this.appliedResolvedSelectionAliases.set(key, { + key, + category: selection.category || '', + refs: this.getResolvedSelectionRefs(selection), + tokens: this.getResolvedSelectionTokenSet(this.getResolvedSelectionPathCandidates(selection)) + }); + } + }, + + doesResolvedModelMatchAlias(model = {}, alias = {}) { + const refs = Array.isArray(alias.refs) ? alias.refs : []; + const modelNodeId = model.node_id ?? ''; + const modelWidgetIndex = model.widget_index ?? ''; + const modelSubgraphId = model.subgraph_id ?? ''; + const modelScope = model.is_top_level !== false; + const modelNestedKey = model.nested_key || ''; + const refMatch = refs.some(ref => ( + String(ref.node_id ?? '') === String(modelNodeId) + && String(ref.widget_index ?? '') === String(modelWidgetIndex) + && String(ref.subgraph_id ?? '') === String(modelSubgraphId) + && (ref.is_top_level !== false) === modelScope + && String(ref.nested_key || '') === String(modelNestedKey) + )); + if (!refMatch) return false; + + const aliasCategory = String(alias.category || '').toLowerCase(); + const modelCategory = String(model.category || '').toLowerCase(); + if (aliasCategory && modelCategory && aliasCategory !== modelCategory) return false; + + const modelTokens = this.getResolvedSelectionTokenSet([ + model.original_path, + model.name, + model.filename, + model.relative_path, + model.full_path, + model.path + ]); + for (const token of alias.tokens || []) { + if (modelTokens.has(token)) return true; + } + return false; + }, + + applyResolvedSelectionAliasesToAnalysisData(data = null) { + if (!data || !(this.appliedResolvedSelectionAliases instanceof Map) || !this.appliedResolvedSelectionAliases.size) { + return data; + } + const resolvedModels = Array.isArray(data.resolved_models) ? data.resolved_models : []; + if (!resolvedModels.length) return data; + + const matchedKeys = new Set(); + for (const model of resolvedModels) { + if (model?.missing_key) continue; + for (const [key, alias] of this.appliedResolvedSelectionAliases.entries()) { + if (matchedKeys.has(key)) continue; + if (!this.doesResolvedModelMatchAlias(model, alias)) continue; + model.missing_key = key; + matchedKeys.add(key); + break; + } + } + + for (const key of matchedKeys) { + this.appliedResolvedSelectionAliases.delete(key); + } + return data; + }, + + buildOptimisticResolvedModel(selection = {}, sourceMissing = null) { + const resolvedModel = selection.resolved_model || {}; + const resolvedRelativePath = resolvedModel.relative_path + || resolvedModel.filename + || selection.resolved_path + || sourceMissing?.original_path + || selection.original_path + || ''; + + return { + ...(sourceMissing || {}), + missing_key: this.getResolutionQueueKey(selection), + __isOptimisticResolved: true, + node_id: selection.node_id ?? sourceMissing?.node_id, + widget_index: selection.widget_index ?? sourceMissing?.widget_index, + category: selection.category || sourceMissing?.category || resolvedModel.category || 'unknown', + original_path: resolvedRelativePath, + name: resolvedRelativePath, + filename: resolvedModel.filename || resolvedRelativePath.split('/').pop()?.split('\\').pop() || resolvedRelativePath, + relative_path: resolvedRelativePath, + full_path: resolvedModel.path || selection.resolved_path || sourceMissing?.full_path || '', + path: resolvedModel.path || selection.resolved_path || '', + matches: [{ + confidence: 100, + match_type: 'exact', + filename: resolvedModel.filename || resolvedRelativePath, + path: resolvedModel.path || selection.resolved_path || '', + model: { + ...resolvedModel, + path: resolvedModel.path || selection.resolved_path || '', + relative_path: resolvedRelativePath, + filename: resolvedModel.filename || resolvedRelativePath + } + }] + }; + }, + getOptimisticAnalysisDataAfterApply(appliedSelections = []) { const missingModels = Array.isArray(this.missingModels) ? this.missingModels : []; const appliedKeys = new Set( @@ -1966,10 +2130,25 @@ export const queueMethods = { ? sourceData.missing_models : missingModels; const nextMissing = sourceMissing.filter(missing => !appliedKeys.has(this.getMissingModelKey(missing))); + const currentResolved = Array.isArray(sourceData.resolved_models) ? sourceData.resolved_models : []; + const optimisticResolved = []; + for (const selection of Array.isArray(appliedSelections) ? appliedSelections : []) { + const key = this.getResolutionQueueKey(selection); + const source = sourceMissing.find(missing => this.getMissingModelKey(missing) === key) + || missingModels.find(missing => this.getMissingModelKey(missing) === key) + || null; + optimisticResolved.push(this.buildOptimisticResolvedModel(selection, source)); + } + const optimisticKeys = new Set(optimisticResolved.map(model => model.missing_key).filter(Boolean)); + const nextResolved = [ + ...currentResolved.filter(model => !optimisticKeys.has(this.getMissingModelKey(model))), + ...optimisticResolved + ]; return { ...sourceData, missing_models: nextMissing, + resolved_models: nextResolved, total_missing: nextMissing.length }; }, @@ -1977,6 +2156,7 @@ export const queueMethods = { applyOptimisticAnalysisData(data, workflow = null) { if (!data) return false; + this.applyResolvedSelectionAliasesToAnalysisData(data); const workflowSignature = this.getWorkflowSignature(workflow || this.getCurrentWorkflow()); if (workflowSignature) { this.cachedWorkflowSignature = workflowSignature; @@ -2010,6 +2190,7 @@ export const queueMethods = { }) .then(data => { if (signature && this.activeWorkflowSignature !== signature) return; + this.applyResolvedSelectionAliasesToAnalysisData(data); this.cachedWorkflowSignature = signature || this.cachedWorkflowSignature; this.cachedAnalysisData = data; this.saveAnalysisCacheForActiveWorkflow?.(); diff --git a/web/resolver/views/missing_browser_methods.js b/web/resolver/views/missing_browser_methods.js index 4e962c1..5cbb474 100644 --- a/web/resolver/views/missing_browser_methods.js +++ b/web/resolver/views/missing_browser_methods.js @@ -288,6 +288,32 @@ export const missingBrowserMethods = { this.displayMissingModels(this.contentElement, this.cachedAnalysisData); }, + getMissingListScrollSnapshot(container) { + const list = container?.querySelector?.('.mr-missing-list'); + if (!(list instanceof HTMLElement)) return null; + return { + top: list.scrollTop, + left: list.scrollLeft + }; + }, + + restoreMissingListScroll(container, snapshot = null) { + if (!snapshot) return; + const list = container?.querySelector?.('.mr-missing-list'); + if (!(list instanceof HTMLElement)) return; + + const applyScroll = () => { + if (!list.isConnected) return; + const maxTop = Math.max(0, list.scrollHeight - list.clientHeight); + const maxLeft = Math.max(0, list.scrollWidth - list.clientWidth); + list.scrollTop = Math.min(Number(snapshot.top) || 0, maxTop); + list.scrollLeft = Math.min(Number(snapshot.left) || 0, maxLeft); + }; + + applyScroll(); + requestAnimationFrame(applyScroll); + }, + renderMissingModelsBrowser(missingModels, selectedKey, totalMissing, activeCount, hasAny100Match, options = {}) { const hiddenResolvedCount = Number(options.hiddenResolvedCount || 0); const resolvedCount = Number(options.resolvedCount || 0); @@ -897,6 +923,7 @@ export const missingBrowserMethods = { * Display missing models in the dialog */ displayMissingModels(container, data) { + const listScrollSnapshot = this.getMissingListScrollSnapshot(container); const missingModels = (data.missing_models || []).map(missing => ( this.restoreDownloadedLocalMatchesForMissing?.(missing) || missing )); @@ -1007,6 +1034,7 @@ export const missingBrowserMethods = { { hiddenResolvedCount, resolvedCount, rawMissingCount, missingCount: unresolvedMissingCount } ); this.wireMissingModelsBrowser(container, data, sortedMissingModels); + this.restoreMissingListScroll(container, listScrollSnapshot); this.scheduleInitialUrnLocalMatchRefresh(sortedMissingModels, container, data); this.reconnectActiveSearchProgress(sortedMissingModels); this.updateBatchFooterButtons();