Treat model_gguf as diffusion_models and render accepted file formats in missing-model UI
This commit is contained in:
+7
-1
@@ -73,7 +73,13 @@ TEMPLATE_KEY_ALIASES = {
|
||||
"loras": ("loras", "lora"),
|
||||
"checkpoints": ("checkpoints", "checkpoint"),
|
||||
"embeddings": ("embeddings", "embedding"),
|
||||
"diffusion_models": ("diffusion_models", "diffusion_model", "unet", "unet_gguf"),
|
||||
"diffusion_models": (
|
||||
"diffusion_models",
|
||||
"diffusion_model",
|
||||
"unet",
|
||||
"unet_gguf",
|
||||
"model_gguf",
|
||||
),
|
||||
"text_encoders": ("text_encoders", "text_encoder", "clip", "clips", "clip_gguf"),
|
||||
"controlnet": ("controlnet", "control_net"),
|
||||
"vae": ("vae",),
|
||||
|
||||
@@ -253,6 +253,7 @@ CATEGORY_MAP = {
|
||||
"diffusers": "diffusers",
|
||||
"unet": "diffusion_models",
|
||||
"unet_gguf": "diffusion_models",
|
||||
"model_gguf": "diffusion_models",
|
||||
"select_safetensors": "diffusion_models",
|
||||
"diffusion_model": "diffusion_models",
|
||||
"diffusion_models": "diffusion_models",
|
||||
@@ -1130,6 +1131,7 @@ def get_category_folder_keys(category: str) -> list[str]:
|
||||
if folder_key == "diffusion_models":
|
||||
keys.append("unet")
|
||||
keys.append("unet_gguf")
|
||||
keys.append("model_gguf")
|
||||
elif folder_key == "text_encoders":
|
||||
keys.append("clip")
|
||||
return keys
|
||||
|
||||
@@ -22,7 +22,12 @@ except ImportError:
|
||||
log.warning("Model Resolver: folder_paths not available yet - will retry later")
|
||||
|
||||
|
||||
from .type_utils import MODEL_EXTENSIONS, URN_TYPE_MAP, unique_ordered_strings
|
||||
from .type_utils import (
|
||||
MODEL_EXTENSIONS,
|
||||
URN_TYPE_MAP,
|
||||
normalize_download_category,
|
||||
unique_ordered_strings,
|
||||
)
|
||||
|
||||
URN_REGEX = re.compile(r"^urn:air:([^:]+):([^:]+):([^:]+):(\d+)@(\d+)$")
|
||||
|
||||
@@ -1024,7 +1029,7 @@ def get_model_widget_category_hints(
|
||||
hints_list = []
|
||||
for h in dynamic_category_hints:
|
||||
if h:
|
||||
hints_list.append(h)
|
||||
hints_list.append(normalize_download_category(h))
|
||||
|
||||
if dynamic_category_hints:
|
||||
return _ordered_unique_categories(hints_list)
|
||||
|
||||
@@ -34,6 +34,10 @@ const modelInfoMethodsSource = fs.readFileSync(
|
||||
path.join(projectRoot, 'web/resolver/views/model_info_methods.js'),
|
||||
'utf8'
|
||||
);
|
||||
const missingBrowserMethodsSource = fs.readFileSync(
|
||||
path.join(projectRoot, 'web/resolver/views/missing_browser_methods.js'),
|
||||
'utf8'
|
||||
);
|
||||
const renderFormatMethodsSource = fs.readFileSync(
|
||||
path.join(projectRoot, 'web/resolver/utils/render_format_methods.js'),
|
||||
'utf8'
|
||||
@@ -1827,7 +1831,7 @@ test('local match bad folder badge is omitted for supported folder', () => {
|
||||
assert.equal(html, '');
|
||||
});
|
||||
|
||||
test('local match bad folder badge treats unet gguf as diffusion models', () => {
|
||||
test('local match bad folder badge treats gguf folder keys as diffusion models', () => {
|
||||
const getLocalMatchCategory = eval(`(${extractMethod(searchPanelMethodsSource, 'getLocalMatchCategory')})`);
|
||||
const getLocalMatchBadFolderWarning = eval(`(${extractMethod(searchPanelMethodsSource, 'getLocalMatchBadFolderWarning')})`);
|
||||
const renderLocalMatchBadFolderBadge = eval(`(${extractMethod(searchPanelMethodsSource, 'renderLocalMatchBadFolderBadge')})`);
|
||||
@@ -1837,7 +1841,7 @@ test('local match bad folder badge treats unet gguf as diffusion models', () =>
|
||||
normalizeDownloadCategory(value = '') {
|
||||
return {
|
||||
unet_gguf: 'diffusion_models',
|
||||
diffusion_models: 'diffusion_models',
|
||||
model_gguf: 'diffusion_models',
|
||||
}[String(value || '').trim()] || String(value || '').trim();
|
||||
},
|
||||
getMissingSupportedDownloadCategories() {
|
||||
@@ -1851,7 +1855,7 @@ test('local match bad folder badge treats unet gguf as diffusion models', () =>
|
||||
const html = renderLocalMatchBadFolderBadge.call(
|
||||
dialog,
|
||||
{ category: 'diffusion_models' },
|
||||
{ confidence: 100, model: { category: 'unet_gguf' } }
|
||||
{ confidence: 100, model: { category: 'model_gguf' } }
|
||||
);
|
||||
|
||||
assert.equal(html, '');
|
||||
@@ -1925,11 +1929,12 @@ test('local match bad type badge is omitted for a file type accepted by node cat
|
||||
assert.equal(html, '');
|
||||
});
|
||||
|
||||
test('download category normalization maps unet gguf to diffusion models', () => {
|
||||
test('download category normalization maps gguf folder keys to diffusion models', () => {
|
||||
const normalizeDownloadCategory = eval(`(${extractMethod(downloadTargetMethodsSource, 'normalizeDownloadCategory')})`);
|
||||
|
||||
assert.equal(normalizeDownloadCategory('unet_gguf'), 'diffusion_models');
|
||||
assert.equal(normalizeDownloadCategory('UNET GGUF'), 'diffusion_models');
|
||||
assert.equal(normalizeDownloadCategory('model_gguf'), 'diffusion_models');
|
||||
});
|
||||
|
||||
test('download category normalization maps select safetensors alias to diffusion models', () => {
|
||||
@@ -1939,6 +1944,27 @@ test('download category normalization maps select safetensors alias to diffusion
|
||||
assert.equal(normalizeDownloadCategory('SELECT SAFETENSORS'), 'diffusion_models');
|
||||
});
|
||||
|
||||
test('missing model preview shows accepted GGUF format next to its category', () => {
|
||||
const renderMissingModelFormatBadges = eval(`(${extractMethod(missingBrowserMethodsSource, 'renderMissingModelFormatBadges')})`);
|
||||
const dialog = {
|
||||
getMissingAcceptedModelFileTypes() {
|
||||
return [{ extension: 'gguf', label: 'GGUF', display: 'GGUF (.gguf)' }];
|
||||
},
|
||||
escapeHtml(value) {
|
||||
return String(value).replace(/[&<>\"]/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[char]));
|
||||
},
|
||||
};
|
||||
|
||||
const html = renderMissingModelFormatBadges.call(dialog, {
|
||||
category: 'diffusion_models',
|
||||
node_type: 'UnetLoaderGGUF',
|
||||
});
|
||||
|
||||
assert.match(html, /mr-model-format-chip/);
|
||||
assert.match(html, />GGUF<\/span>/);
|
||||
assert.match(html, /Accepted file format for this node: GGUF \(\.gguf\)\./);
|
||||
});
|
||||
|
||||
test('hash match label map numbers distinct matched hashes', () => {
|
||||
const normalizeSearchResultSha256 = eval(`(${extractMethod(searchPanelMethodsSource, 'normalizeSearchResultSha256')})`);
|
||||
const getLocalMatchHash = eval(`(${extractMethod(searchPanelMethodsSource, 'getLocalMatchHash')})`);
|
||||
|
||||
@@ -246,8 +246,15 @@ class TestRefactoringTargets(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
def test_category_folder_keys_mapping(self):
|
||||
from core.type_utils import get_category_folder_keys
|
||||
self.assertEqual(get_category_folder_keys("diffusion_models"), ["diffusion_models", "unet", "unet_gguf"])
|
||||
self.assertEqual(get_category_folder_keys("unet_gguf"), ["diffusion_models", "unet", "unet_gguf"])
|
||||
expected_diffusion_keys = [
|
||||
"diffusion_models",
|
||||
"unet",
|
||||
"unet_gguf",
|
||||
"model_gguf",
|
||||
]
|
||||
self.assertEqual(get_category_folder_keys("diffusion_models"), expected_diffusion_keys)
|
||||
self.assertEqual(get_category_folder_keys("model_gguf"), expected_diffusion_keys)
|
||||
self.assertEqual(get_category_folder_keys("unet_gguf"), expected_diffusion_keys)
|
||||
self.assertEqual(get_category_folder_keys("text_encoders"), ["text_encoders", "clip"])
|
||||
self.assertEqual(get_category_folder_keys("checkpoints"), ["checkpoints"])
|
||||
|
||||
@@ -298,5 +305,3 @@ class TestRefactoringTargets(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
# Require download excludes main.safetensors because it has no download url
|
||||
self.assertEqual(select_primary_model_file(files, require_download=True)["id"], 1)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from core.scanner import scan_directory
|
||||
from core.workflow_analyzer import analyze_workflow_models, identify_missing_models
|
||||
@@ -412,6 +413,40 @@ class WorkflowAnalyzerCategoryHintTests(unittest.TestCase):
|
||||
self.assertEqual(4, len(refs))
|
||||
self.assertEqual({"text_encoders"}, {ref["category"] for ref in refs})
|
||||
|
||||
def test_gguf_unet_loader_keeps_diffusion_badge_and_raw_folder_hint(self):
|
||||
for node_type in (
|
||||
"LoaderGGUF",
|
||||
"LoaderGGUFAdvanced",
|
||||
"UnetLoaderGGUF",
|
||||
"UnetLoaderGGUFAdvanced",
|
||||
):
|
||||
with self.subTest(node_type=node_type):
|
||||
with patch(
|
||||
"core.workflow_analyzer.get_dynamic_widget_category_hints",
|
||||
return_value=["model_gguf"],
|
||||
):
|
||||
refs = analyze_workflow_models(
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": 345,
|
||||
"type": node_type,
|
||||
"widgets": [{"name": "gguf_name"}],
|
||||
"widgets_values": [
|
||||
r"LTXV\LTX-2.3-22B-distilled-1.1-Q4_K_M.gguf"
|
||||
],
|
||||
"outputs": [{"type": "MODEL", "links": [1]}],
|
||||
}
|
||||
]
|
||||
},
|
||||
available_models=[],
|
||||
)
|
||||
|
||||
self.assertEqual(1, len(refs))
|
||||
self.assertEqual("diffusion_models", refs[0]["category"])
|
||||
self.assertEqual(["diffusion_models"], refs[0]["category_hints"])
|
||||
self.assertEqual(["model_gguf"], refs[0]["folder_key_hints"])
|
||||
|
||||
|
||||
class ScannerFolderModelTests(unittest.TestCase):
|
||||
def test_diffusers_folder_models_are_scanned_as_folder_entries(self):
|
||||
|
||||
@@ -1489,6 +1489,12 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mr-model-format-chip {
|
||||
background: rgba(67, 160, 255, 0.12);
|
||||
border-color: rgba(104, 184, 255, 0.3);
|
||||
color: #b9ddff;
|
||||
}
|
||||
|
||||
/* ComfyUI node_slot-inspired type chips. */
|
||||
.mr-type-chip,
|
||||
.mr-info-tag.mr-type-chip,
|
||||
|
||||
@@ -109,6 +109,7 @@ export const downloadTargetMethods = {
|
||||
upscaler: 'upscale_models',
|
||||
unet: 'diffusion_models',
|
||||
unet_gguf: 'diffusion_models',
|
||||
model_gguf: 'diffusion_models',
|
||||
select_safetensors: 'diffusion_models',
|
||||
diffusion_model: 'diffusion_models',
|
||||
diffusion_models: 'diffusion_models',
|
||||
@@ -297,6 +298,10 @@ export const downloadTargetMethods = {
|
||||
CheckpointLoader: 'checkpoints',
|
||||
UNETLoader: 'diffusion_models',
|
||||
UNETLoaderAdvanced: 'diffusion_models',
|
||||
LoaderGGUF: 'diffusion_models',
|
||||
LoaderGGUFAdvanced: 'diffusion_models',
|
||||
UnetLoaderGGUF: 'diffusion_models',
|
||||
UnetLoaderGGUFAdvanced: 'diffusion_models',
|
||||
VAELoader: 'vae',
|
||||
VAELoaderKJ: 'vae',
|
||||
LoraLoader: 'loras',
|
||||
|
||||
@@ -12,6 +12,17 @@ export const missingBrowserMethods = {
|
||||
return this.getFilenameFromPath(missing.original_path) || missing.name || 'Missing model';
|
||||
},
|
||||
|
||||
renderMissingModelFormatBadges(missing = {}) {
|
||||
const acceptedTypes = this.getMissingAcceptedModelFileTypes?.(missing) || [];
|
||||
return acceptedTypes.map(type => {
|
||||
const label = String(type?.label || type?.extension || '').trim();
|
||||
if (!label) return '';
|
||||
const display = String(type?.display || label).trim();
|
||||
const tooltip = `Accepted file format for this node: ${display}.`;
|
||||
return `<span class="mr-category-chip mr-model-format-chip" data-tooltip="${this.escapeHtml(tooltip)}">${this.escapeHtml(label)}</span>`;
|
||||
}).join('');
|
||||
},
|
||||
|
||||
getMissingLocateTarget(missing = {}) {
|
||||
const hasLocateNode = missing.locate_node_id !== undefined
|
||||
&& missing.locate_node_id !== null
|
||||
@@ -2079,6 +2090,7 @@ export const missingBrowserMethods = {
|
||||
if (missing.category) {
|
||||
html += `<span class="mr-category-chip ${this.getModelTypeColorClass(missing.category)}" data-tooltip="${this.escapeHtml(missing.category)}">${this.getCategoryDisplayName(missing.category)}</span>`;
|
||||
}
|
||||
html += this.renderMissingModelFormatBadges(missing);
|
||||
html += `<span id="${locateId}" class="${nodeChipClasses}"${nodeChipTitle ? ` data-tooltip="${this.escapeHtml(nodeChipTitle)}"` : ''}>`;
|
||||
if (nodeDisplay.canLocate) {
|
||||
html += this.getLocateIconHtml();
|
||||
|
||||
Reference in New Issue
Block a user