Show Bad type badge when local match file type is not accepted by node catalog

This commit is contained in:
Dariusz L
2026-07-20 12:46:04 +02:00
parent 2f7a2c5869
commit 6f1e6aea34
3 changed files with 163 additions and 3 deletions
@@ -1642,6 +1642,74 @@ test('local match bad folder badge treats unet gguf as diffusion models', () =>
assert.equal(html, '');
});
test('local match bad type badge warns when node catalog does not accept gguf', () => {
const getModelFileTypeInfo = eval(`(${extractMethod(searchPanelMethodsSource, 'getModelFileTypeInfo')})`);
const getMissingAcceptedModelFileTypes = eval(`(${extractMethod(searchPanelMethodsSource, 'getMissingAcceptedModelFileTypes')})`);
const getLocalMatchBadTypeWarning = eval(`(${extractMethod(searchPanelMethodsSource, 'getLocalMatchBadTypeWarning')})`);
const renderLocalMatchBadTypeBadge = eval(`(${extractMethod(searchPanelMethodsSource, 'renderLocalMatchBadTypeBadge')})`);
const dialog = {
getModelFileTypeInfo,
getMissingAcceptedModelFileTypes,
getLocalMatchBadTypeWarning,
getCurrentComfyCatalogValues(nodeType, widgetName) {
assert.equal(nodeType, 'CLIPLoader');
assert.equal(widgetName, 'clip_name');
return [
'QWEN/qwen3vl_4b_fp8_scaled.safetensors',
'FLUX/t5xxl_fp16.safetensors',
];
},
escapeHtml(value) {
return String(value).replace(/"/g, '"');
},
};
const html = renderLocalMatchBadTypeBadge.call(
dialog,
{
node_type: 'CLIPLoader',
node_title: 'Load CLIP',
widget_name: 'clip_name',
widget_index: 0,
},
{
confidence: 76.7,
model: { relative_path: 'QWEN/Qwen3-4B-abliterated-iq4_nl.gguf' },
}
);
assert.match(html, /mr-match-status-bad-type[^>]*>Bad type</);
assert.match(html, /This file is GGUF \(.gguf\)/);
assert.match(html, /Load CLIP only lists Safetensors \(.safetensors\) files/);
assert.match(html, /use a loader made for GGUF models/);
});
test('local match bad type badge is omitted for a file type accepted by node catalog', () => {
const getModelFileTypeInfo = eval(`(${extractMethod(searchPanelMethodsSource, 'getModelFileTypeInfo')})`);
const getMissingAcceptedModelFileTypes = eval(`(${extractMethod(searchPanelMethodsSource, 'getMissingAcceptedModelFileTypes')})`);
const getLocalMatchBadTypeWarning = eval(`(${extractMethod(searchPanelMethodsSource, 'getLocalMatchBadTypeWarning')})`);
const renderLocalMatchBadTypeBadge = eval(`(${extractMethod(searchPanelMethodsSource, 'renderLocalMatchBadTypeBadge')})`);
const dialog = {
getModelFileTypeInfo,
getMissingAcceptedModelFileTypes,
getLocalMatchBadTypeWarning,
getCurrentComfyCatalogValues() {
return ['QWEN/qwen3vl_4b_fp8_scaled.safetensors'];
},
escapeHtml(value) {
return String(value);
},
};
const html = renderLocalMatchBadTypeBadge.call(
dialog,
{ node_type: 'CLIPLoader', widget_name: 'clip_name', widget_index: 0 },
{ model: { relative_path: 'QWEN/another_text_encoder.safetensors' } }
);
assert.equal(html, '');
});
test('download category normalization maps unet gguf to diffusion models', () => {
const normalizeDownloadCategory = eval(`(${extractMethod(downloadTargetMethodsSource, 'normalizeDownloadCategory')})`);
+4 -2
View File
@@ -1916,7 +1916,8 @@
background: rgba(241, 207, 100, 0.10);
}
.mr-match-status-bad-folder {
.mr-match-status-bad-folder,
.mr-match-status-bad-type {
color: #fca5a5;
background: rgba(248, 113, 113, 0.12);
box-shadow: inset 0 0 0 1px rgba(248, 113, 113, 0.22);
@@ -1930,7 +1931,8 @@
cursor: help;
}
.mr-match-status-bad-folder:focus-visible {
.mr-match-status-bad-folder:focus-visible,
.mr-match-status-bad-type:focus-visible {
outline: 2px solid rgba(248, 113, 113, 0.58);
outline-offset: 2px;
}
+91 -1
View File
@@ -2493,6 +2493,95 @@ export const searchPanelMethods = {
return `<span class="mr-match-status mr-match-status-bad-folder" tabindex="0" data-tooltip="${this.escapeHtml(warning.tooltip)}">Bad folder</span>`;
},
getModelFileTypeInfo(value = '') {
const match = String(value || '').trim().match(/\.([a-z0-9]+)(?:$|[?#])/i);
if (!match) return null;
const extension = match[1].toLowerCase();
const labels = {
safetensors: 'Safetensors',
gguf: 'GGUF',
ckpt: 'Checkpoint',
pt: 'PyTorch',
pt2: 'PyTorch',
pth: 'PyTorch',
bin: 'Binary model',
onnx: 'ONNX',
pkl: 'Pickle',
sft: 'SFT'
};
if (!labels[extension]) return null;
return {
extension,
label: labels[extension],
display: `${labels[extension]} (.${extension})`
};
},
getMissingAcceptedModelFileTypes(missing = {}) {
const explicitExtensions = missing.accepted_extensions
|| missing.allowed_extensions
|| missing.accepted_model_extensions;
let acceptedValues = Array.isArray(explicitExtensions) ? explicitExtensions : [];
if (!acceptedValues.length) {
const widgetName = missing.widget_name
|| this.getComfyWidgetNameByIndex?.(missing.node_type || '', Number(missing.widget_index))
|| '';
acceptedValues = this.getCurrentComfyCatalogValues?.(
missing.node_type || '',
widgetName
) || [];
}
const byExtension = new Map();
for (const value of acceptedValues) {
const typeInfo = this.getModelFileTypeInfo?.(value);
if (typeInfo && !byExtension.has(typeInfo.extension)) {
byExtension.set(typeInfo.extension, typeInfo);
}
}
return Array.from(byExtension.values());
},
getLocalMatchBadTypeWarning(missing = {}, match = {}) {
const model = match.model && typeof match.model === 'object' ? match.model : {};
const matchValue = model.relative_path
|| model.path
|| match.relative_path
|| match.path
|| model.filename
|| match.filename
|| model.name
|| match.name
|| '';
const matchType = this.getModelFileTypeInfo?.(matchValue);
if (!matchType) return null;
const acceptedTypes = this.getMissingAcceptedModelFileTypes?.(missing) || [];
if (!acceptedTypes.length || acceptedTypes.some(type => type.extension === matchType.extension)) {
return null;
}
const acceptedText = acceptedTypes.length === 1
? acceptedTypes[0].display
: acceptedTypes.map(type => type.display).join(', ');
const nodeLabel = String(missing.node_title || missing.node_type || 'this node').trim();
return {
matchType,
acceptedTypes,
tooltip: `This file is ${matchType.display}, but ${nodeLabel} only lists ${acceptedText} files.\n\nLinking it may not work. Choose a supported file type or use a loader made for ${matchType.label} models.`
};
},
renderLocalMatchBadTypeBadge(missing = {}, match = {}) {
const warning = this.getLocalMatchBadTypeWarning?.(missing, match);
if (!warning) return '';
return `<span class="mr-match-status mr-match-status-bad-type" tabindex="0" data-tooltip="${this.escapeHtml(warning.tooltip)}">Bad type</span>`;
},
getActiveDownloadInfoForLocalMatch(match = {}) {
const model = match.model || {};
const normalizePath = (value = '') => normalizePathIdentity(value);
@@ -2580,7 +2669,8 @@ export const searchPanelMethods = {
},
renderLocalMatchStatusGroup(missing = {}, match = {}, hashLabelMap = null) {
return `<span class="mr-match-status-group">${this.renderLocalMatchStatus(match, hashLabelMap)}${this.renderLocalMatchDownloadingBadge(match)}${this.renderLocalMatchBadFolderBadge(missing, match)}</span>`;
const badTypeBadge = this.renderLocalMatchBadTypeBadge?.(missing, match) || '';
return `<span class="mr-match-status-group">${this.renderLocalMatchStatus(match, hashLabelMap)}${this.renderLocalMatchDownloadingBadge(match)}${this.renderLocalMatchBadFolderBadge(missing, match)}${badTypeBadge}</span>`;
},
areLocalMatchAlternativesCollapsed() {