feat: prefer active workflow widgets over model metadata for base model detection

This commit is contained in:
Dariusz L
2026-08-25 03:21:42 +02:00
parent 1eb3af32be
commit c5371e7165
2 changed files with 151 additions and 7 deletions
@@ -4211,6 +4211,99 @@ test('auto base model keeps workflow path context when a delayed search result d
assert.match(info.message, /To change: Choose a different option in the Model field/);
});
test('auto base model uses active widgets instead of workflow model metadata', () => {
const getWorkflowModelReferenceText = eval(`(${extractMethod(searchPanelMethodsSource, 'getWorkflowModelReferenceText')})`);
const getDominantWorkflowBaseModel = eval(`(${extractMethod(searchPanelMethodsSource, 'getDominantWorkflowBaseModel')})`);
const getBaseModelIndependentSearchType = eval(`(${extractMethod(searchPanelMethodsSource, 'getBaseModelIndependentSearchType')})`);
const getMissingAutoBaseModelInfo = eval(`(${extractMethod(searchPanelMethodsSource, 'getMissingAutoBaseModelInfo')})`);
const workflow = {
nodes: [
{
id: 761,
type: 'UNETLoader',
widgets_values: [
'KREA2\\Krea2_Turbo_convrot_int8mixed.safetensors',
'default'
],
widgets_values_named: {
unet_name: 'KREA2\\Krea2_Turbo_convrot_int8mixed.safetensors',
weight_dtype: 'default'
},
properties: {
models: [{
name: 'z_image_turbo_bf16.safetensors',
url: 'https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/diffusion_models/z_image_turbo_bf16.safetensors',
directory: 'diffusion_models'
}]
}
}
],
extra: {
z_image_url: 'https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/config.json'
}
};
const dialog = {
getCurrentWorkflow() {
return workflow;
},
getWorkflowSignatureData() {
return {
nodes: [{
bypassed: false,
widgets_values: [{
index: 0,
value: 'KREA2\\Krea2_Turbo_convrot_int8mixed.safetensors'
}]
}],
definitions: {}
};
},
getResolvedWorkflowBaseModelScores() {
return new Map();
},
getBaseModelIndependentSearchType,
getMissingLocalBaseModel() {
return '';
},
resolveBaseModelAlias() {
return '';
},
resolveBaseModelAliasExact() {
return '';
},
resolveBaseModelAliasFromPath() {
return '';
},
getBaseModelAliases() {
return [
{
value: 'Z-Image',
aliases: ['zimage', 'z image', 'z-image', 'z_image', 'zImageTurbo', 'z image turbo']
},
{ value: 'Krea 2', aliases: ['krea', 'krea 2'] }
];
},
normalizeBaseModelToken(value = '') {
return normalizeSearchToken(value);
},
getWorkflowModelReferenceText,
getDominantWorkflowBaseModel,
getMissingAutoBaseModelInfo
};
const referenceText = getWorkflowModelReferenceText.call(dialog);
assert.match(referenceText, /Krea2_Turbo/);
assert.doesNotMatch(referenceText, /z_image/);
assert.equal(getDominantWorkflowBaseModel.call(dialog), 'Krea 2');
const info = getMissingAutoBaseModelInfo.call(dialog, {
name: 'Text Refusal Reduction Krea2.safetensors',
category: 'loras'
});
assert.equal(info.value, 'Krea 2');
assert.equal(info.source, 'workflow fallback');
});
test('base model path mapping ignores conflicting full-path base model', () => {
const {
normalizeBaseModelToken,
+58 -7
View File
@@ -251,26 +251,77 @@ export const searchPanelMethods = {
getWorkflowModelReferenceText() {
const workflow = this.getCurrentWorkflow?.();
if (!workflow) return '';
const values = [];
const visit = (value, key = '') => {
const visitValue = (value) => {
if (typeof value === 'string') {
if (/^urn:/i.test(value.trim())) {
return;
}
if (this.hasModelExtension(value) || /model|checkpoint|unet|diffusion/i.test(key)) {
values.push(value);
}
values.push(value);
return;
}
if (Array.isArray(value)) {
value.forEach(item => visit(item, key));
value.forEach(visitValue);
return;
}
if (value && typeof value === 'object') {
Object.entries(value).forEach(([childKey, childValue]) => visit(childValue, childKey));
Object.values(value).forEach(visitValue);
}
};
visit(workflow);
const collectNormalizedNodes = (nodes = []) => {
if (!Array.isArray(nodes)) return;
nodes.forEach(node => {
if (!node || node.bypassed) return;
const widgetValues = Array.isArray(node.widgets_values)
? node.widgets_values
: [];
widgetValues.forEach(widget => {
if (widget && typeof widget === 'object' && 'value' in widget) {
visitValue(widget.value);
}
});
});
};
const signatureData = this.getWorkflowSignatureData?.(workflow);
if (signatureData && typeof signatureData === 'object') {
collectNormalizedNodes(signatureData.nodes);
Object.values(signatureData.definitions || {}).forEach(definition => {
if (Array.isArray(definition)) {
definition.forEach(item => collectNormalizedNodes(item?.nodes));
} else {
collectNormalizedNodes(definition?.nodes);
}
});
}
// Older workflow snapshots may only expose named widget values. Keep
// this fallback limited to model-like keys so node metadata and URLs
// from properties.models cannot affect the base-model inference.
if (!values.length) {
const modelKeyPattern = /model|lora|checkpoint|unet|vae|clip|encoder|control|upscale|embedding|gligen|sam/i;
const collectNamedValues = (nodes = []) => {
if (!Array.isArray(nodes)) return;
nodes.forEach(node => {
if (!node || node.mode === 4 || !node.widgets_values_named) return;
Object.entries(node.widgets_values_named).forEach(([key, value]) => {
if (modelKeyPattern.test(key)) visitValue(value);
});
});
};
collectNamedValues(workflow.nodes);
const definitions = workflow.definitions;
Object.values(definitions || {}).forEach(definition => {
if (Array.isArray(definition)) {
definition.forEach(item => collectNamedValues(item?.nodes));
} else {
collectNamedValues(definition?.nodes);
}
});
}
return values.join(' ');
},