feat: prefer active workflow widgets over model metadata for base model detection
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(' ');
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user