From 6f1e6aea344fd4f314437745402915422d2cc9f6 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Mon, 20 Jul 2026 12:46:04 +0200 Subject: [PATCH] Show Bad type badge when local match file type is not accepted by node catalog --- tests/test_downloads_tab_workflow_route.mjs | 68 +++++++++++++++ web/css/resolver-main.css | 6 +- web/resolver/search/search_panel.js | 92 ++++++++++++++++++++- 3 files changed, 163 insertions(+), 3 deletions(-) diff --git a/tests/test_downloads_tab_workflow_route.mjs b/tests/test_downloads_tab_workflow_route.mjs index 8e1ca66..a7a6721 100644 --- a/tests/test_downloads_tab_workflow_route.mjs +++ b/tests/test_downloads_tab_workflow_route.mjs @@ -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 { + 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')})`); diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css index 679ff49..3308afa 100644 --- a/web/css/resolver-main.css +++ b/web/css/resolver-main.css @@ -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; } diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 61eba92..a3b9a3a 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -2493,6 +2493,95 @@ export const searchPanelMethods = { return `Bad folder`; }, + 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 `Bad type`; + }, + getActiveDownloadInfoForLocalMatch(match = {}) { const model = match.model || {}; const normalizePath = (value = '') => normalizePathIdentity(value); @@ -2580,7 +2669,8 @@ export const searchPanelMethods = { }, renderLocalMatchStatusGroup(missing = {}, match = {}, hashLabelMap = null) { - return `${this.renderLocalMatchStatus(match, hashLabelMap)}${this.renderLocalMatchDownloadingBadge(match)}${this.renderLocalMatchBadFolderBadge(missing, match)}`; + const badTypeBadge = this.renderLocalMatchBadTypeBadge?.(missing, match) || ''; + return `${this.renderLocalMatchStatus(match, hashLabelMap)}${this.renderLocalMatchDownloadingBadge(match)}${this.renderLocalMatchBadFolderBadge(missing, match)}${badTypeBadge}`; }, areLocalMatchAlternativesCollapsed() {