diff --git a/README.en.md b/README.en.md index abb9559..4f4b753 100644 --- a/README.en.md +++ b/README.en.md @@ -33,6 +33,7 @@ **v1.1.2 (2024/3/25)** +- Optimized some of the recommended nodes for slots related to EasyUse - Added **Enable ContextMenu Auto Nest Subdirectories** The setting item is enabled by default, and it can be classified into subdirectories, checkpoints and loras previews - Added `easy sv3dLoader` - Added `easy dynamiCrafterLoader` diff --git a/README.md b/README.md index a72b2e2..61a1d72 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ **v1.1.2 (2024/3/25)** +- 改写 EasyUse 相关节点的部分插槽推荐节点 - 增加 **启用上下文菜单自动嵌套子目录** 设置项,默认为启用状态,可分类子目录及checkpoints、loras预览图 - 增加 `easy sv3dLoader` - 增加 `easy dynamiCrafterLoader` diff --git a/web/js/easy/easySuggestion.js b/web/js/easy/easySuggestion.js new file mode 100644 index 0000000..986d1d4 --- /dev/null +++ b/web/js/easy/easySuggestion.js @@ -0,0 +1,467 @@ +import {app} from "/scripts/app.js"; +import {api} from "/scripts/api.js"; +import {$el} from "/scripts/ui.js"; + +const propmts = ["easy wildcards", "easy positive", "easy negative", "easy stylesSelector"] +const loaders = ["easy a1111Loader", "easy comfyLoader", "easy fullLoader", "easy svdLoader", "easy cascadeLoader", "easy sv3dLoader"] +const preSamplingNodes = ["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingNoiseIn", "easy preSamplingDynamicCFG","easy preSamplingSdTurbo", "easy preSamplingLayerDiffusion"] +const kSampler = ["easy kSampler", "easy kSamplerTiled","easy kSamplerInpainting", "easy kSamplerDownscaleUnet", "easy kSamplerSDTurbo"] +const controlNetNodes = ["easy controlnetLoader", "easy controlnetLoaderADV"] +const instantIDNodes = ["easy instantIDApply", "easy instantIDApplyADV"] +const ipadapterNodes = ["easy ipadapterApply", "easy ipadapterApplyADV"] +const pipeNodes = ['easy pipeIn','easy pipeOut', 'easy pipeEdit'] +const xyNodes = ['easy XYPlot', 'easy XYPlotAdvanced'] +const extraNodes = ['easy setNode'] +const modelNormalNodes = [...["Reroute"],...['RescaleCFG','LoraLoaderModelOnly','LoraLoader','FreeU','FreeU_v2'],...ipadapterNodes,...extraNodes] +const suggestions = { + // prompt + "easy seed":{ + "from":{ + "INT": [...["Reroute"],...preSamplingNodes,...['easy fullkSampler']] + } + }, + // sd相关 + "easy fullLoader": { + "from":{ + "PIPE_LINE": [...["Reroute"],...preSamplingNodes,...['easy fullkSampler'],...pipeNodes,...extraNodes], + "MODEL":modelNormalNodes + }, + "to":{ + "STRING": [...["Reroute"],...propmts] + } + }, + "easy a1111Loader": { + "from": { + "PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes], + "MODEL": modelNormalNodes + }, + "to":{ + "STRING": [...["Reroute"],...propmts] + } + }, + "easy comfyLoader": { + "from": { + "PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes], + "MODEL": modelNormalNodes + }, + "to":{ + "STRING": [...["Reroute"],...propmts] + } + }, + "easy svdLoader":{ + "from": { + "PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes], + "MODEL": modelNormalNodes + }, + "to":{ + "STRING": [...["Reroute"],...propmts] + } + }, + "easy zero123Loader":{ + "from": { + "PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes], + "MODEL": modelNormalNodes + }, + "to":{ + "STRING": [...["Reroute"],...propmts] + } + }, + "easy sv3dLoader":{ + "from": { + "PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes], + "MODEL": modelNormalNodes + }, + "to":{ + "STRING": [...["Reroute"],...propmts] + } + }, + "easy preSampling": { + "from": { + "PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes] + }, + }, + "easy preSamplingAdvanced": { + "from": { + "PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes] + } + }, + "easy preSamplingDynamicCFG": { + "from": { + "PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes] + } + }, + "easy preSamplingLayerDiffusion": { + "from": { + "PIPE_LINE": [...["Reroute", "easy kSamplerLayerDiffusion"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes] + } + }, + "easy preSamplingNoiseIn": { + "from": { + "PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes] + } + }, + // ksampler + "easy fullkSampler": { + "from": { + "PIPE_LINE": [...["Reroute"], ...pipeNodes.reverse(), ...['easy preDetailerFix', 'easy preMaskDetailerFix'], ...preSamplingNodes, ...extraNodes] + } + }, + "easy kSampler": { + "from": { + "PIPE_LINE": [...["Reroute"], ...pipeNodes.reverse(), ...['easy preDetailerFix', 'easy preMaskDetailerFix', 'easy hiresFix'], ...preSamplingNodes, ...extraNodes], + } + }, + // cn + "easy controlnetLoader": { + "from": { + "PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes] + } + }, + "easy controlnetLoaderADV":{ + "from": { + "PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes] + } + }, + // instant + "easy instantIDApply": { + "from": { + "PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes], + "MODEL": modelNormalNodes + } + }, + "easy instantIDApplyADV":{ + "from": { + "PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes], + "MODEL": modelNormalNodes + } + }, + // fix + "easy preDetailerFix":{ + "from": { + "PIPE_LINE": [...["Reroute", "easy detailerFix"], ...pipeNodes, ...extraNodes] + }, + "to":{ + "PIPE_LINE": [...["Reroute", "easy ultralyticsDetectorPipe", "easy samLoaderPipe", "easy kSampler", "easy fullkSampler"]] + } + }, + "easy preMaskDetailerFix":{ + "from": { + "PIPE_LINE": [...["Reroute", "easy detailerFix"], ...pipeNodes, ...extraNodes] + } + }, + "easy samLoaderPipe": { + "from":{ + "PIPE_LINE": [...["Reroute", "easy preDetailerFix"], ...pipeNodes, ...extraNodes] + } + }, + "easy ultralyticsDetectorPipe": { + "from":{ + "PIPE_LINE": [...["Reroute", "easy preDetailerFix"], ...pipeNodes, ...extraNodes] + } + }, + // cascade相关 + "easy cascadeLoader":{ + "from": { + "PIPE_LINE": [...["Reroute"], ...["easy fullCascadeKSampler", 'easy preSamplingCascade'], ...controlNetNodes, ...pipeNodes, ...extraNodes], + "MODEL": modelNormalNodes.filter(cate => !ipadapterNodes.includes(cate)) + } + }, + "easy fullCascadeKSampler":{ + "from": { + "PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced"], ...pipeNodes, ...extraNodes] + } + }, + "easy preSamplingCascade":{ + "from": { + "PIPE_LINE": [...["Reroute"], ...["easy cascadeKSampler",], ...pipeNodes, ...extraNodes] + } + }, + "easy cascadeKSampler": { + "from": { + "PIPE_LINE": [...["Reroute"], ...["easy preSampling", "easy preSamplingAdvanced"], ...pipeNodes, ...extraNodes] + } + }, +} + + +app.registerExtension({ + name: "comfy.easyuse.suggestions", + async setup(app) { + LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection + var optPass = optPass || {}; + var opts = Object.assign({ nodeFrom: null // input + ,slotFrom: null // input + ,nodeTo: null // output + ,slotTo: null // output + ,position: [] // pass the event coords + ,nodeType: null // choose a nodetype to add, AUTO to set at first good + ,posAdd:[0,0] // adjust x,y + ,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h + } + ,optPass + ); + var that = this; + + var isFrom = opts.nodeFrom && opts.slotFrom!==null; + var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null; + + if (!isFrom && !isTo){ + console.warn("No data passed to createDefaultNodeForSlot "+opts.nodeFrom+" "+opts.slotFrom+" "+opts.nodeTo+" "+opts.slotTo); + return false; + } + if (!opts.nodeType){ + console.warn("No type to createDefaultNodeForSlot"); + return false; + } + + var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; + var slotX = isFrom ? opts.slotFrom : opts.slotTo; + var nodeType = nodeX.type + + var iSlotConn = false; + switch (typeof slotX){ + case "string": + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "object": + // ok slotX + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); + break; + case "number": + iSlotConn = slotX; + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "undefined": + default: + // bad ? + //iSlotConn = 0; + console.warn("Cant get slot information "+slotX); + return false; + } + + if (slotX===false || iSlotConn===false){ + console.warn("createDefaultNodeForSlot bad slotX "+slotX+" "+iSlotConn); + } + + // check for defaults nodes for this slottype + var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + if(slotTypesDefault && slotTypesDefault[fromSlotType]){ + if (slotX.link !== null) { + // is connected + }else{ + // is not not connected + } + let nodeNewType = false; + const fromOrTo = isFrom ? 'from' : 'to' + if(suggestions[nodeType] && suggestions[nodeType][fromOrTo] && suggestions[nodeType][fromOrTo][fromSlotType]?.length>0){ + for(var typeX in suggestions[nodeType][fromOrTo][fromSlotType]){ + if (opts.nodeType == suggestions[nodeType][fromOrTo][fromSlotType][typeX] || opts.nodeType == "AUTO") { + nodeNewType = suggestions[nodeType][fromOrTo][fromSlotType][typeX]; + break + } + } + } + else if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ + for(var typeX in slotTypesDefault[fromSlotType]){ + if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){ + nodeNewType = slotTypesDefault[fromSlotType][typeX]; + // console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType); + break; // -------- + } + } + }else{ + if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType]; + } + if (nodeNewType) { + var nodeNewOpts = false; + if (typeof nodeNewType == "object" && nodeNewType.node){ + nodeNewOpts = nodeNewType; + nodeNewType = nodeNewType.node; + } + + //that.graph.beforeChange(); + + var newNode = LiteGraph.createNode(nodeNewType); + if(newNode){ + // if is object pass options + if (nodeNewOpts){ + if (nodeNewOpts.properties) { + for (var i in nodeNewOpts.properties) { + newNode.addProperty( i, nodeNewOpts.properties[i] ); + } + } + if (nodeNewOpts.inputs) { + newNode.inputs = []; + for (var i in nodeNewOpts.inputs) { + newNode.addOutput( + nodeNewOpts.inputs[i][0], + nodeNewOpts.inputs[i][1] + ); + } + } + if (nodeNewOpts.outputs) { + newNode.outputs = []; + for (var i in nodeNewOpts.outputs) { + newNode.addOutput( + nodeNewOpts.outputs[i][0], + nodeNewOpts.outputs[i][1] + ); + } + } + if (nodeNewOpts.title) { + newNode.title = nodeNewOpts.title; + } + if (nodeNewOpts.json) { + newNode.configure(nodeNewOpts.json); + } + + } + + // add the node + that.graph.add(newNode); + newNode.pos = [ opts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0) + ,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/ + + //that.graph.afterChange(); + + // connect the two! + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType ); + } + + // if connecting in between + if (isFrom && isTo){ + // TODO + } + + return true; + + }else{ + console.log("failed creating "+nodeNewType); + } + } + } + return false; + } + + LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection + var optPass = optPass || {}; + var opts = Object.assign({ nodeFrom: null // input + ,slotFrom: null // input + ,nodeTo: null // output + ,slotTo: null // output + ,e: null + } + ,optPass + ); + var that = this; + + var isFrom = opts.nodeFrom && opts.slotFrom; + var isTo = !isFrom && opts.nodeTo && opts.slotTo; + + if (!isFrom && !isTo){ + console.warn("No data passed to showConnectionMenu"); + return false; + } + + var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; + var slotX = isFrom ? opts.slotFrom : opts.slotTo; + + var iSlotConn = false; + switch (typeof slotX){ + case "string": + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "object": + // ok slotX + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); + break; + case "number": + iSlotConn = slotX; + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + default: + // bad ? + //iSlotConn = 0; + console.warn("Cant get slot information "+slotX); + return false; + } + + var options = ["Add Node",null]; + + if (that.allow_searchbox){ + options.push("Search"); + options.push(null); + } + + // get defaults nodes for this slottype + var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + var nodeType = nodeX.type + if(slotTypesDefault && slotTypesDefault[fromSlotType]){ + const fromOrTo = isFrom ? 'from' : 'to' + if(suggestions[nodeType] && suggestions[nodeType][fromOrTo] && suggestions[nodeType][fromOrTo][fromSlotType]?.length>0){ + for(var typeX in suggestions[nodeType][fromOrTo][fromSlotType]){ + options.push(suggestions[nodeType][fromOrTo][fromSlotType][typeX]); + } + } + else if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ + for(var typeX in slotTypesDefault[fromSlotType]){ + options.push(slotTypesDefault[fromSlotType][typeX]); + } + }else{ + options.push(slotTypesDefault[fromSlotType]); + } + } + + // build menu + var menu = new LiteGraph.ContextMenu(options, { + event: opts.e, + title: (slotX && slotX.name!="" ? (slotX.name + (fromSlotType?" | ":"")) : "")+(slotX && fromSlotType ? fromSlotType : ""), + callback: inner_clicked + }); + + // callback + function inner_clicked(v,options,e) { + //console.log("Process showConnectionMenu selection"); + switch (v) { + case "Add Node": + LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType ); + } + }); + break; + case "Search": + if(isFrom){ + that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType}); + }else{ + that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType}); + } + break; + default: + // check for defaults nodes for this slottype + var nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY] + ,nodeType: v + })); + if (nodeCreated){ + // new node created + //console.log("node "+v+" created") + }else{ + // failed or v is not in defaults + } + break; + } + } + + return false; + }; + } +}) \ No newline at end of file