import {app} from "../../../../scripts/app.js"; import {$t} from '../common/i18n.js' import {CheckpointInfoDialog, LoraInfoDialog} from "../common/model.js"; const loaders = ['easy fullLoader', 'easy a1111Loader', 'easy comfyLoader', 'easy kolorsLoader', 'easy hunyuanDiTLoader', 'easy pixArtLoader'] const preSampling = ['easy preSampling', 'easy preSamplingAdvanced', 'easy preSamplingDynamicCFG', 'easy preSamplingNoiseIn', 'easy preSamplingCustom', 'easy preSamplingLayerDiffusion', 'easy fullkSampler'] const kSampler = ['easy kSampler', 'easy kSamplerTiled', 'easy kSamplerInpainting', 'easy kSamplerDownscaleUnet', 'easy kSamplerLayerDiffusion'] const controlnet = ['easy controlnetLoader', 'easy controlnetLoaderADV', 'easy controlnetLoader++', 'easy instantIDApply', 'easy instantIDApplyADV'] const ipadapter = ['easy ipadapterApply', 'easy ipadapterApplyADV', 'easy ipadapterStyleComposition', 'easy ipadapterApplyFromParams', 'easy pulIDApply', 'easy pulIDApplyADV'] const positive_prompt = ['easy positive', 'easy wildcards'] const imageNode = ['easy loadImageBase64', 'LoadImage', 'LoadImageMask'] const inpaint = ['easy applyBrushNet', 'easy applyPowerPaint', 'easy applyInpaint'] const widgetMapping = { "positive_prompt":{ "text": "positive", "positive": "text" }, "loaders":{ "ckpt_name": "ckpt_name", "vae_name": "vae_name", "clip_skip": "clip_skip", "lora_name": "lora_name", "resolution": "resolution", "empty_latent_width": "empty_latent_width", "empty_latent_height": "empty_latent_height", "positive": "positive", "negative": "negative", "batch_size": "batch_size", "a1111_prompt_style": "a1111_prompt_style" }, "preSampling":{ "steps": "steps", "cfg": "cfg", "cfg_scale_min": "cfg", "sampler_name": "sampler_name", "scheduler": "scheduler", "denoise": "denoise", "seed_num": "seed_num", "seed": "seed" }, "kSampler":{ "image_output": "image_output", "save_prefix": "save_prefix", "link_id": "link_id" }, "controlnet":{ "control_net_name":"control_net_name", "strength": ["strength", "cn_strength"], "scale_soft_weights": ["scale_soft_weights","cn_soft_weights"], "cn_strength": ["strength", "cn_strength"], "cn_soft_weights": ["scale_soft_weights","cn_soft_weights"], }, "ipadapter":{ "preset":"preset", "lora_strength": "lora_strength", "provider": "provider", "weight":"weight", "weight_faceidv2": "weight_faceidv2", "start_at": "start_at", "end_at": "end_at", "cache_mode": "cache_mode", "use_tiled": "use_tiled", "insightface": "insightface", "pulid_file": "pulid_file" }, "load_image":{ "image":"image", "base64_data":"base64_data", "channel": "channel" }, "inpaint":{ "dtype": "dtype", "fitting": "fitting", "function": "function", "scale": "scale", "start_at": "start_at", "end_at": "end_at" } } const inputMapping = { "loaders":{ "optional_lora_stack": "optional_lora_stack", "positive": "positive", "negative": "negative" }, "preSampling":{ "pipe": "pipe", "image_to_latent": "image_to_latent", "latent": "latent" }, "kSampler":{ "pipe": "pipe", "model": "model" }, "controlnet":{ "pipe": "pipe", "image": "image", "image_kps": "image_kps", "control_net": "control_net", "positive": "positive", "negative": "negative", "mask": "mask" }, "positive_prompt":{ }, "ipadapter":{ "model":"model", "image":"image", "image_style": "image", "attn_mask":"attn_mask", "optional_ipadapter":"optional_ipadapter" }, "inpaint":{ "pipe": "pipe", "image": "image", "mask": "mask" } }; const outputMapping = { "loaders":{ "pipe": "pipe", "model": "model", "vae": "vae", "clip": null, "positive": null, "negative": null, "latent": null, }, "preSampling":{ "pipe":"pipe" }, "kSampler":{ "pipe": "pipe", "image": "image" }, "controlnet":{ "pipe": "pipe", "positive": "positive", "negative": "negative" }, "positive_prompt":{ "text": "positive", "positive": "text" }, "load_image":{ "IMAGE":"IMAGE", "MASK": "MASK" }, "ipadapter":{ "model":"model", "tiles":"tiles", "masks":"masks", "ipadapter":"ipadapter" }, "inpaint":{ "pipe": "pipe", } }; // 替换节点 function replaceNode(oldNode, newNodeName, type) { const newNode = LiteGraph.createNode(newNodeName); if (!newNode) { return; } app.graph.add(newNode); newNode.pos = oldNode.pos.slice(); newNode.size = oldNode.size.slice(); oldNode.widgets.forEach(widget => { if(widgetMapping[type][widget.name]){ const newName = widgetMapping[type][widget.name]; if (newName) { const newWidget = findWidgetByName(newNode, newName); if (newWidget) { newWidget.value = widget.value; if(widget.name == 'seed_num'){ newWidget.linkedWidgets[0].value = widget.linkedWidgets[0].value } if(widget.type == 'converted-widget'){ convertToInput(newNode, newWidget, widget); } } } } }); if(oldNode.inputs){ oldNode.inputs.forEach((input, index) => { if (input && input.link && inputMapping[type][input.name]) { const newInputName = inputMapping[type][input.name]; // If the new node does not have this output, skip if (newInputName === null) { return; } const newInputIndex = newNode.findInputSlot(newInputName); if (newInputIndex !== -1) { const originLinkInfo = oldNode.graph.links[input.link]; if (originLinkInfo) { const originNode = oldNode.graph.getNodeById(originLinkInfo.origin_id); if (originNode) { originNode.connect(originLinkInfo.origin_slot, newNode, newInputIndex); } } } } }); } if(oldNode.outputs){ oldNode.outputs.forEach((output, index) => { if (output && output.links && outputMapping[type] && outputMapping[type][output.name]) { const newOutputName = outputMapping[type][output.name]; // If the new node does not have this output, skip if (newOutputName === null) { return; } const newOutputIndex = newNode.findOutputSlot(newOutputName); if (newOutputIndex !== -1) { output.links.forEach(link => { const targetLinkInfo = oldNode.graph.links[link]; if (targetLinkInfo) { const targetNode = oldNode.graph.getNodeById(targetLinkInfo.target_id); if (targetNode) { newNode.connect(newOutputIndex, targetNode, targetLinkInfo.target_slot); } } }); } } }); } // Remove old node app.graph.remove(oldNode); // Remove others if(newNode.type == 'easy fullkSampler'){ const link_output_id = newNode.outputs[0].links if(link_output_id && link_output_id[0]){ const nodes = app.graph._nodes const node = nodes.find(cate=> cate.inputs && cate.inputs[0] && cate.inputs[0]['link'] == link_output_id[0]) if(node){ app.graph.remove(node); } } }else if(preSampling.includes(newNode.type)){ const link_output_id = newNode.outputs[0].links if(!link_output_id || !link_output_id[0]){ const ksampler = LiteGraph.createNode('easy kSampler'); app.graph.add(ksampler); ksampler.pos = newNode.pos.slice(); ksampler.pos[0] = ksampler.pos[0] + newNode.size[0] + 20; const newInputIndex = newNode.findInputSlot('pipe'); if (newInputIndex !== -1) { if (newNode) { newNode.connect(0, ksampler, newInputIndex); } } } } // autoHeight newNode.setSize([newNode.size[0], newNode.computeSize()[1]]); } export function findWidgetByName(node, widgetName) { return node.widgets.find(widget => typeof widgetName == 'object' ? widgetName.includes(widget.name) : widget.name === widgetName); } function replaceNodeMenuCallback(currentNode, targetNodeName, type) { return function() { replaceNode(currentNode, targetNodeName, type); }; } const addMenuHandler = (nodeType, cb)=> { const getOpts = nodeType.prototype.getExtraMenuOptions; nodeType.prototype.getExtraMenuOptions = function () { const r = getOpts.apply(this, arguments); cb.apply(this, arguments); return r; }; } const addMenu = (content, type, nodes_include, nodeType, has_submenu=true) => { addMenuHandler(nodeType, function (_, options) { options.unshift({ content: content, has_submenu: has_submenu, callback: (value, options, e, menu, node) => showSwapMenu(value, options, e, menu, node, type, nodes_include) }) if(type == 'loaders') { options.unshift({ content: $t("💎 View Lora Info..."), callback: (value, options, e, menu, node) => { const widget = node.widgets.find(cate => cate.name == 'lora_name') let name = widget.value; if (!name || name == 'None') return new LoraInfoDialog(name).show('loras', name); } }) options.unshift({ content: $t("💎 View Checkpoint Info..."), callback: (value, options, e, menu, node) => { let name = node.widgets[0].value; if (!name || name == 'None') return new CheckpointInfoDialog(name).show('checkpoints', name); } }) } }) } const showSwapMenu = (value, options, e, menu, node, type, nodes_include) => { const swapOptions = []; nodes_include.map(cate=>{ if (node.type !== cate) { swapOptions.push({ content: `${cate}`, callback: replaceNodeMenuCallback(node, cate, type) }); } }) new LiteGraph.ContextMenu(swapOptions, { event: e, callback: null, parentMenu: menu, node: node }); return false; } // 重载节点 const CONVERTED_TYPE = "converted-widget"; const GET_CONFIG = Symbol(); function hideWidget(node, widget, suffix = "") { widget.origType = widget.type; widget.origComputeSize = widget.computeSize; widget.origSerializeValue = widget.serializeValue; widget.computeSize = () => [0, -4]; // -4 is due to the gap litegraph adds between widgets automatically widget.type = CONVERTED_TYPE + suffix; widget.serializeValue = () => { // Prevent serializing the widget if we have no input linked if (!node.inputs) { return undefined; } let node_input = node.inputs.find((i) => i.widget?.name === widget.name); if (!node_input || !node_input.link) { return undefined; } return widget.origSerializeValue ? widget.origSerializeValue() : widget.value; }; // Hide any linked widgets, e.g. seed+seedControl if (widget.linkedWidgets) { for (const w of widget.linkedWidgets) { hideWidget(node, w, ":" + widget.name); } } } function convertToInput(node, widget, config) { console.log('config:', config) hideWidget(node, widget); const { type } = getWidgetType(config); // Add input and store widget config for creating on primitive node const sz = node.size; if(!widget.options || !widget.options.forceInput){ node.addInput(widget.name, type, { widget: { name: widget.name, [GET_CONFIG]: () => config }, }); } for (const widget of node.widgets) { widget.last_y += LiteGraph.NODE_SLOT_HEIGHT; } // Restore original size but grow if needed node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]); } function getWidgetType(config) { // Special handling for COMBO so we restrict links based on the entries let type = config[0]; if (type instanceof Array) { type = "COMBO"; } return { type }; } const reloadNode = function (node) { const nodeType = node.constructor.type; const origVals = node.properties.origVals || {}; const nodeTitle = origVals.title || node.title; const nodeColor = origVals.color || node.color; const bgColor = origVals.bgcolor || node.bgcolor; const oldNode = node const options = { 'size': [...node.size], 'color': nodeColor, 'bgcolor': bgColor, 'pos': [...node.pos] } let inputLinks = [] let outputLinks = [] if(node.inputs){ for (const input of node.inputs) { if (input.link) { const input_name = input.name const input_slot = node.findInputSlot(input_name) const input_node = node.getInputNode(input_slot) const input_link = node.getInputLink(input_slot) inputLinks.push([input_link.origin_slot, input_node, input_name]) } } } if(node.outputs) { for (const output of node.outputs) { if (output.links) { const output_name = output.name for (const linkID of output.links) { const output_link = graph.links[linkID] const output_node = graph._nodes_by_id[output_link.target_id] outputLinks.push([output_name, output_node, output_link.target_slot]) } } } } app.graph.remove(node) const newNode = app.graph.add(LiteGraph.createNode(nodeType, nodeTitle, options)); function handleLinks() { // re-convert inputs if(oldNode.widgets) { for (let w of oldNode.widgets) { if (w.type === 'converted-widget') { const WidgetToConvert = newNode.widgets.find((nw) => nw.name === w.name); for (let i of oldNode.inputs) { if (i.name === w.name) { convertToInput(newNode, WidgetToConvert, i.widget); } } } } } // replace input and output links for (let input of inputLinks) { const [output_slot, output_node, input_name] = input; output_node.connect(output_slot, newNode.id, input_name) } for (let output of outputLinks) { const [output_name, input_node, input_slot] = output; newNode.connect(output_name, input_node, input_slot) } } // fix widget values let values = oldNode.widgets_values; if (!values && newNode.widgets?.length>0) { newNode.widgets.forEach((newWidget, index) => { const oldWidget = oldNode.widgets[index]; if (newWidget.name === oldWidget.name && newWidget.type === oldWidget.type) { newWidget.value = oldWidget.value; } }); handleLinks(); return; } let pass = false const isIterateForwards = values?.length <= newNode.widgets?.length; let vi = isIterateForwards ? 0 : values.length - 1; function evalWidgetValues(testValue, newWidg) { if (testValue === true || testValue === false) { if (newWidg.options?.on && newWidg.options?.off) { return { value: testValue, pass: true }; } } else if (typeof testValue === "number") { if (newWidg.options?.min <= testValue && testValue <= newWidg.options?.max) { return { value: testValue, pass: true }; } } else if (newWidg.options?.values?.includes(testValue)) { return { value: testValue, pass: true }; } else if (newWidg.inputEl && typeof testValue === "string") { return { value: testValue, pass: true }; } return { value: newWidg.value, pass: false }; } const updateValue = (wi) => { const oldWidget = oldNode.widgets[wi]; let newWidget = newNode.widgets[wi]; if (newWidget.name === oldWidget.name && newWidget.type === oldWidget.type) { while ((isIterateForwards ? vi < values.length : vi >= 0) && !pass) { let { value, pass } = evalWidgetValues(values[vi], newWidget); if (pass && value !== null) { newWidget.value = value; break; } vi += isIterateForwards ? 1 : -1; } vi++ if (!isIterateForwards) { vi = values.length - (newNode.widgets?.length - 1 - wi); } } }; if (isIterateForwards && newNode.widgets?.length>0) { for (let wi = 0; wi < newNode.widgets.length; wi++) { updateValue(wi); } } else if(newNode.widgets?.length>0){ for (let wi = newNode.widgets.length - 1; wi >= 0; wi--) { updateValue(wi); } } handleLinks(); }; app.registerExtension({ name: "comfy.easyUse.extraMenu", async beforeRegisterNodeDef(nodeType, nodeData, app) { // 刷新节点 addMenuHandler(nodeType, function (_, options) { options.unshift({ content: $t("🔃 Reload Node"), callback: (value, options, e, menu, node) => { let graphcanvas = LGraphCanvas.active_canvas; if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) { reloadNode(node); } else { for (let i in graphcanvas.selected_nodes) { reloadNode(graphcanvas.selected_nodes[i]); } } } }) // ckptNames if(nodeData.name == 'easy ckptNames'){ options.unshift({ content: $t("💎 View Checkpoint Info..."), callback: (value, options, e, menu, node) => { let name = node.widgets[0].value; if (!name || name == 'None') return new CheckpointInfoDialog(name).show('checkpoints', name); } }) } }) // Swap提示词 if (positive_prompt.includes(nodeData.name)) { addMenu("↪️ Swap EasyPrompt", 'positive_prompt', positive_prompt, nodeType) } // Swap加载器 if (loaders.includes(nodeData.name)) { addMenu("↪️ Swap EasyLoader", 'loaders', loaders, nodeType) } // Swap预采样器 if (preSampling.includes(nodeData.name)) { addMenu("↪️ Swap EasyPreSampling", 'preSampling', preSampling, nodeType) } // Swap kSampler if (kSampler.includes(nodeData.name)) { addMenu("↪️ Swap EasyKSampler", 'preSampling', kSampler, nodeType) } // Swap ControlNet if (controlnet.includes(nodeData.name)) { addMenu("↪️ Swap EasyControlnet", 'controlnet', controlnet, nodeType) } // Swap IPAdapater if (ipadapter.includes(nodeData.name)) { addMenu("↪️ Swap EasyAdapater", 'ipadapter', ipadapter, nodeType) } // Swap Image if (imageNode.includes(nodeData.name)) { addMenu("↪️ Swap LoadImage", 'load_image', imageNode, nodeType) } // Swap inpaint if (inpaint.includes(nodeData.name)) { addMenu("↪️ Swap InpaintNode", 'inpaint', inpaint, nodeType) } } });