Normalize MoneyPrinter Comfy workflow templates

This commit is contained in:
boos
2026-06-02 17:11:15 +08:00
parent c98d9a2c6d
commit d0f26118ff
2 changed files with 268 additions and 15 deletions
+67 -1
View File
@@ -42,7 +42,7 @@ function planValue(job: MoneyPrinterJob, key: string) {
}
function workflowPromptFromGraph(graph: any) {
return graph?.prompt || graph?.workflow || graph?.comfyPrompt || graph || null;
return graph?.prompt || graph?.workflow || graph?.comfyPrompt || graphToComfyPrompt(graph) || graph || null;
}
function mappingFromGraph(graph: any) {
@@ -74,6 +74,72 @@ function inferMappingFromPrompt(prompt: any) {
};
}
function normalizeParam(classType: string, field: string, value: any) {
if (classType === 'KSampler' && field === 'cfg') {
return typeof value === 'number' && value >= 0 && value <= 50 ? value : 8;
}
if (classType === 'KSampler' && field === 'denoise') {
const numeric = typeof value === 'number' ? value : Number(value);
if (!Number.isFinite(numeric)) return 1;
return numeric > 1 ? Math.min(1, numeric / 100) : Math.max(0, numeric);
}
if (classType === 'KSampler' && field === 'sampler_name') return typeof value === 'string' ? value : 'euler';
if (classType === 'KSampler' && field === 'scheduler') return typeof value === 'string' ? value : 'normal';
if (classType === 'KSampler' && field === 'seed') return Number.isFinite(Number(value)) ? Math.max(0, Number(value)) : 0;
if (classType === 'EmptyLatentImage' && ['width', 'height'].includes(field)) {
const numeric = Number(value);
return numeric >= 64 && numeric <= 4096 ? numeric : 1024;
}
if (classType === 'EmptyLatentImage' && field === 'batch_size') {
const numeric = Number(value);
return numeric >= 1 && numeric <= 16 ? numeric : 1;
}
return value;
}
function graphToComfyPrompt(graph: any) {
if (!Array.isArray(graph?.nodes)) return null;
const prompt: Record<string, any> = {};
for (const node of graph.nodes) {
const id = String(node?.id || '');
const classType = String(node?.type || node?.class_type || '');
if (!id || !classType) continue;
const params = node?.params && typeof node.params === 'object' ? node.params : {};
const inputs: Record<string, any> = {};
for (const [field, value] of Object.entries(params)) {
inputs[field] = normalizeParam(classType, field, value);
}
prompt[id] = { class_type: classType, inputs };
}
for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
const from = String(edge?.from || '');
const to = String(edge?.to || '');
const [fromId] = from.split('.');
const [toId, toField] = to.split('.');
if (fromId && toId && toField && prompt[toId]?.inputs) {
prompt[toId].inputs[toField] = [fromId, 0];
}
}
const firstByType = (type: string) => Object.entries(prompt).find(([, node]) => node.class_type === type)?.[0] || '';
const allByType = (type: string) => Object.entries(prompt).filter(([, node]) => node.class_type === type).map(([id]) => id);
const checkpoint = firstByType('CheckpointLoaderSimple');
const latent = firstByType('EmptyLatentImage');
const ksampler = firstByType('KSampler');
const textNodes = allByType('CLIPTextEncode');
if (ksampler) {
if (checkpoint) prompt[ksampler].inputs.model = [checkpoint, 0];
if (latent) prompt[ksampler].inputs.latent_image = [latent, 0];
if (textNodes[0]) prompt[ksampler].inputs.positive = [textNodes[0], 0];
if (textNodes[1] || textNodes[0]) prompt[ksampler].inputs.negative = [textNodes[1] || textNodes[0], 0];
}
const vaeDecode = firstByType('VAEDecode');
if (vaeDecode) {
if (ksampler) prompt[vaeDecode].inputs.samples = [ksampler, 0];
if (checkpoint) prompt[vaeDecode].inputs.vae = [checkpoint, 2];
}
return prompt;
}
function templateLabel(workflow: WorkflowSummary) {
if (workflow.isTemplate || workflow.visibility === 'template') return '官方模板';
if (workflow.visibility === 'public') return '共享模板';