Add LTX-2.3 models and resolved selection alias matching

This commit is contained in:
Dariusz L
2026-06-22 03:40:37 +02:00
parent b4cd98e1ca
commit 34049c5ea6
4 changed files with 334 additions and 4 deletions
+121
View File
@@ -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",
+4 -4
View File
@@ -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"
}
+181
View File
@@ -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?.();
@@ -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();