Spaces:
Runtime error
Runtime error
| import { app } from "../../../scripts/app.js"; | |
| //based on diffus3's SetGet: https://github.com/diffus3/ComfyUI-extensions | |
| // Nodes that allow you to tunnel connections for cleaner graphs | |
| function setColorAndBgColor(type) { | |
| const colorMap = { | |
| "MODEL": LGraphCanvas.node_colors.blue, | |
| "LATENT": LGraphCanvas.node_colors.purple, | |
| "VAE": LGraphCanvas.node_colors.red, | |
| "CONDITIONING": LGraphCanvas.node_colors.brown, | |
| "IMAGE": LGraphCanvas.node_colors.pale_blue, | |
| "CLIP": LGraphCanvas.node_colors.yellow, | |
| "FLOAT": LGraphCanvas.node_colors.green, | |
| "MASK": { color: "#1c5715", bgcolor: "#1f401b"}, | |
| "INT": { color: "#1b4669", bgcolor: "#29699c"}, | |
| "CONTROL_NET": { color: "#156653", bgcolor: "#1c453b"}, | |
| "NOISE": { color: "#2e2e2e", bgcolor: "#242121"}, | |
| "GUIDER": { color: "#3c7878", bgcolor: "#1c453b"}, | |
| "SAMPLER": { color: "#614a4a", bgcolor: "#3b2c2c"}, | |
| "SIGMAS": { color: "#485248", bgcolor: "#272e27"}, | |
| }; | |
| const colors = colorMap[type]; | |
| if (colors) { | |
| this.color = colors.color; | |
| this.bgcolor = colors.bgcolor; | |
| } | |
| } | |
| let isAlertShown = false; | |
| let disablePrefix = app.ui.settings.getSettingValue("KJNodes.disablePrefix") | |
| const LGraphNode = LiteGraph.LGraphNode | |
| function showAlertWithThrottle(message, delay) { | |
| if (!isAlertShown) { | |
| isAlertShown = true; | |
| alert(message); | |
| setTimeout(() => isAlertShown = false, delay); | |
| } | |
| } | |
| app.registerExtension({ | |
| name: "SetNode", | |
| registerCustomNodes() { | |
| class SetNode extends LGraphNode { | |
| defaultVisibility = true; | |
| serialize_widgets = true; | |
| drawConnection = false; | |
| currentGetters = null; | |
| slotColor = "#FFF"; | |
| canvas = app.canvas; | |
| menuEntry = "Show connections"; | |
| constructor(title) { | |
| super(title) | |
| if (!this.properties) { | |
| this.properties = { | |
| "previousName": "" | |
| }; | |
| } | |
| this.properties.showOutputText = SetNode.defaultVisibility; | |
| const node = this; | |
| this.addWidget( | |
| "text", | |
| "Constant", | |
| '', | |
| (s, t, u, v, x) => { | |
| node.validateName(node.graph); | |
| if(this.widgets[0].value !== ''){ | |
| this.title = (!disablePrefix ? "Set_" : "") + this.widgets[0].value; | |
| } | |
| this.update(); | |
| this.properties.previousName = this.widgets[0].value; | |
| }, | |
| {} | |
| ) | |
| this.addInput("*", "*"); | |
| this.addOutput("*", '*'); | |
| this.onConnectionsChange = function( | |
| slotType, //1 = input, 2 = output | |
| slot, | |
| isChangeConnect, | |
| link_info, | |
| output | |
| ) { | |
| //On Disconnect | |
| if (slotType == 1 && !isChangeConnect) { | |
| if(this.inputs[slot].name === ''){ | |
| this.inputs[slot].type = '*'; | |
| this.inputs[slot].name = '*'; | |
| this.title = "Set" | |
| } | |
| } | |
| if (slotType == 2 && !isChangeConnect) { | |
| this.outputs[slot].type = '*'; | |
| this.outputs[slot].name = '*'; | |
| } | |
| //On Connect | |
| if (link_info && node.graph && slotType == 1 && isChangeConnect) { | |
| const fromNode = node.graph._nodes.find((otherNode) => otherNode.id == link_info.origin_id); | |
| if (fromNode && fromNode.outputs && fromNode.outputs[link_info.origin_slot]) { | |
| const type = fromNode.outputs[link_info.origin_slot].type; | |
| if (this.title === "Set"){ | |
| this.title = (!disablePrefix ? "Set_" : "") + type; | |
| } | |
| if (this.widgets[0].value === '*'){ | |
| this.widgets[0].value = type | |
| } | |
| this.validateName(node.graph); | |
| this.inputs[0].type = type; | |
| this.inputs[0].name = type; | |
| if (app.ui.settings.getSettingValue("KJNodes.nodeAutoColor")){ | |
| setColorAndBgColor.call(this, type); | |
| } | |
| } else { | |
| alert("Error: Set node input undefined. Most likely you're missing custom nodes"); | |
| } | |
| } | |
| if (link_info && node.graph && slotType == 2 && isChangeConnect) { | |
| const fromNode = node.graph._nodes.find((otherNode) => otherNode.id == link_info.origin_id); | |
| if (fromNode && fromNode.inputs && fromNode.inputs[link_info.origin_slot]) { | |
| const type = fromNode.inputs[link_info.origin_slot].type; | |
| this.outputs[0].type = type; | |
| this.outputs[0].name = type; | |
| } else { | |
| alert("Error: Get Set node output undefined. Most likely you're missing custom nodes"); | |
| } | |
| } | |
| //Update either way | |
| this.update(); | |
| } | |
| this.validateName = function(graph) { | |
| let widgetValue = node.widgets[0].value; | |
| if (widgetValue !== '') { | |
| let tries = 0; | |
| const existingValues = new Set(); | |
| graph._nodes.forEach(otherNode => { | |
| if (otherNode !== this && otherNode.type === 'SetNode') { | |
| existingValues.add(otherNode.widgets[0].value); | |
| } | |
| }); | |
| while (existingValues.has(widgetValue)) { | |
| widgetValue = node.widgets[0].value + "_" + tries; | |
| tries++; | |
| } | |
| node.widgets[0].value = widgetValue; | |
| this.update(); | |
| } | |
| } | |
| this.clone = function () { | |
| const cloned = SetNode.prototype.clone.apply(this); | |
| cloned.inputs[0].name = '*'; | |
| cloned.inputs[0].type = '*'; | |
| cloned.value = ''; | |
| cloned.properties.previousName = ''; | |
| cloned.size = cloned.computeSize(); | |
| return cloned; | |
| }; | |
| this.onAdded = function(graph) { | |
| this.validateName(graph); | |
| } | |
| this.update = function() { | |
| if (!node.graph) { | |
| return; | |
| } | |
| const getters = this.findGetters(node.graph); | |
| getters.forEach(getter => { | |
| getter.setType(this.inputs[0].type); | |
| }); | |
| if (this.widgets[0].value) { | |
| const gettersWithPreviousName = this.findGetters(node.graph, true); | |
| gettersWithPreviousName.forEach(getter => { | |
| getter.setName(this.widgets[0].value); | |
| }); | |
| } | |
| const allGetters = node.graph._nodes.filter(otherNode => otherNode.type === "GetNode"); | |
| allGetters.forEach(otherNode => { | |
| if (otherNode.setComboValues) { | |
| otherNode.setComboValues(); | |
| } | |
| }); | |
| } | |
| this.findGetters = function(graph, checkForPreviousName) { | |
| const name = checkForPreviousName ? this.properties.previousName : this.widgets[0].value; | |
| return graph._nodes.filter(otherNode => otherNode.type === 'GetNode' && otherNode.widgets[0].value === name && name !== ''); | |
| } | |
| // This node is purely frontend and does not impact the resulting prompt so should not be serialized | |
| this.isVirtualNode = true; | |
| } | |
| onRemoved() { | |
| const allGetters = this.graph._nodes.filter((otherNode) => otherNode.type == "GetNode"); | |
| allGetters.forEach((otherNode) => { | |
| if (otherNode.setComboValues) { | |
| otherNode.setComboValues([this]); | |
| } | |
| }) | |
| } | |
| getExtraMenuOptions(_, options) { | |
| this.menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; | |
| options.unshift( | |
| { | |
| content: this.menuEntry, | |
| callback: () => { | |
| this.currentGetters = this.findGetters(this.graph); | |
| if (this.currentGetters.length == 0) return; | |
| let linkType = (this.currentGetters[0].outputs[0].type); | |
| this.slotColor = this.canvas.default_connection_color_byType[linkType] | |
| this.menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; | |
| this.drawConnection = !this.drawConnection; | |
| this.canvas.setDirty(true, true); | |
| }, | |
| has_submenu: true, | |
| submenu: { | |
| title: "Color", | |
| options: [ | |
| { | |
| content: "Highlight", | |
| callback: () => { | |
| this.slotColor = "orange" | |
| this.canvas.setDirty(true, true); | |
| } | |
| } | |
| ], | |
| }, | |
| }, | |
| { | |
| content: "Hide all connections", | |
| callback: () => { | |
| const allGetters = this.graph._nodes.filter(otherNode => otherNode.type === "GetNode" || otherNode.type === "SetNode"); | |
| allGetters.forEach(otherNode => { | |
| otherNode.drawConnection = false; | |
| console.log(otherNode); | |
| }); | |
| this.menuEntry = "Show connections"; | |
| this.drawConnection = false | |
| this.canvas.setDirty(true, true); | |
| }, | |
| }, | |
| ); | |
| // Dynamically add a submenu for all getters | |
| this.currentGetters = this.findGetters(this.graph); | |
| if (this.currentGetters) { | |
| let gettersSubmenu = this.currentGetters.map(getter => ({ | |
| content: `${getter.title} id: ${getter.id}`, | |
| callback: () => { | |
| this.canvas.centerOnNode(getter); | |
| this.canvas.selectNode(getter, false); | |
| this.canvas.setDirty(true, true); | |
| }, | |
| })); | |
| options.unshift({ | |
| content: "Getters", | |
| has_submenu: true, | |
| submenu: { | |
| title: "GetNodes", | |
| options: gettersSubmenu, | |
| } | |
| }); | |
| } | |
| } | |
| onDrawForeground(ctx, lGraphCanvas) { | |
| if (this.drawConnection) { | |
| this._drawVirtualLinks(lGraphCanvas, ctx); | |
| } | |
| } | |
| // onDrawCollapsed(ctx, lGraphCanvas) { | |
| // if (this.drawConnection) { | |
| // this._drawVirtualLinks(lGraphCanvas, ctx); | |
| // } | |
| // } | |
| _drawVirtualLinks(lGraphCanvas, ctx) { | |
| if (!this.currentGetters?.length) return; | |
| var title = this.getTitle ? this.getTitle() : this.title; | |
| var title_width = ctx.measureText(title).width; | |
| if (!this.flags.collapsed) { | |
| var start_node_slotpos = [ | |
| this.size[0], | |
| LiteGraph.NODE_TITLE_HEIGHT * 0.5, | |
| ]; | |
| } | |
| else { | |
| var start_node_slotpos = [ | |
| title_width + 55, | |
| -15, | |
| ]; | |
| } | |
| for (const getter of this.currentGetters) { | |
| if (!this.flags.collapsed) { | |
| var end_node_slotpos = this.getConnectionPos(false, 0); | |
| end_node_slotpos = [ | |
| getter.pos[0] - end_node_slotpos[0] + this.size[0], | |
| getter.pos[1] - end_node_slotpos[1] | |
| ]; | |
| } | |
| else { | |
| var end_node_slotpos = this.getConnectionPos(false, 0); | |
| end_node_slotpos = [ | |
| getter.pos[0] - end_node_slotpos[0] + title_width + 50, | |
| getter.pos[1] - end_node_slotpos[1] - 30 | |
| ]; | |
| } | |
| lGraphCanvas.renderLink( | |
| ctx, | |
| start_node_slotpos, | |
| end_node_slotpos, | |
| null, | |
| false, | |
| null, | |
| this.slotColor, | |
| LiteGraph.RIGHT, | |
| LiteGraph.LEFT | |
| ); | |
| } | |
| } | |
| } | |
| LiteGraph.registerNodeType( | |
| "SetNode", | |
| Object.assign(SetNode, { | |
| title: "Set", | |
| }) | |
| ); | |
| SetNode.category = "KJNodes"; | |
| }, | |
| }); | |
| app.registerExtension({ | |
| name: "GetNode", | |
| registerCustomNodes() { | |
| class GetNode extends LGraphNode { | |
| defaultVisibility = true; | |
| serialize_widgets = true; | |
| drawConnection = false; | |
| slotColor = "#FFF"; | |
| currentSetter = null; | |
| canvas = app.canvas; | |
| constructor(title) { | |
| super(title) | |
| if (!this.properties) { | |
| this.properties = {}; | |
| } | |
| this.properties.showOutputText = GetNode.defaultVisibility; | |
| const node = this; | |
| this.addWidget( | |
| "combo", | |
| "Constant", | |
| "", | |
| (e) => { | |
| this.onRename(); | |
| }, | |
| { | |
| values: () => { | |
| const setterNodes = node.graph._nodes.filter((otherNode) => otherNode.type == 'SetNode'); | |
| return setterNodes.map((otherNode) => otherNode.widgets[0].value).sort(); | |
| } | |
| } | |
| ) | |
| this.addOutput("*", '*'); | |
| this.onConnectionsChange = function( | |
| slotType, //0 = output, 1 = input | |
| slot, //self-explanatory | |
| isChangeConnect, | |
| link_info, | |
| output | |
| ) { | |
| this.validateLinks(); | |
| } | |
| this.setName = function(name) { | |
| node.widgets[0].value = name; | |
| node.onRename(); | |
| node.serialize(); | |
| } | |
| this.onRename = function() { | |
| const setter = this.findSetter(node.graph); | |
| if (setter) { | |
| let linkType = (setter.inputs[0].type); | |
| this.setType(linkType); | |
| this.title = (!disablePrefix ? "Get_" : "") + setter.widgets[0].value; | |
| if (app.ui.settings.getSettingValue("KJNodes.nodeAutoColor")){ | |
| setColorAndBgColor.call(this, linkType); | |
| } | |
| } else { | |
| this.setType('*'); | |
| } | |
| } | |
| this.clone = function () { | |
| const cloned = GetNode.prototype.clone.apply(this); | |
| cloned.size = cloned.computeSize(); | |
| return cloned; | |
| }; | |
| this.validateLinks = function() { | |
| if (this.outputs[0].type !== '*' && this.outputs[0].links) { | |
| this.outputs[0].links.filter(linkId => { | |
| const link = node.graph.links[linkId]; | |
| return link && (link.type !== this.outputs[0].type && link.type !== '*'); | |
| }).forEach(linkId => { | |
| node.graph.removeLink(linkId); | |
| }); | |
| } | |
| }; | |
| this.setType = function(type) { | |
| this.outputs[0].name = type; | |
| this.outputs[0].type = type; | |
| this.validateLinks(); | |
| } | |
| this.findSetter = function(graph) { | |
| const name = this.widgets[0].value; | |
| const foundNode = graph._nodes.find(otherNode => otherNode.type === 'SetNode' && otherNode.widgets[0].value === name && name !== ''); | |
| return foundNode; | |
| }; | |
| this.goToSetter = function() { | |
| const setter = this.findSetter(this.graph); | |
| this.canvas.centerOnNode(setter); | |
| this.canvas.selectNode(setter, false); | |
| }; | |
| // This node is purely frontend and does not impact the resulting prompt so should not be serialized | |
| this.isVirtualNode = true; | |
| } | |
| getInputLink(slot) { | |
| const setter = this.findSetter(this.graph); | |
| if (setter) { | |
| const slotInfo = setter.inputs[slot]; | |
| const link = this.graph.links[slotInfo.link]; | |
| return link; | |
| } else { | |
| const errorMessage = "No SetNode found for " + this.widgets[0].value + "(" + this.type + ")"; | |
| showAlertWithThrottle(errorMessage, 5000); | |
| //throw new Error(errorMessage); | |
| } | |
| } | |
| onAdded(graph) { | |
| } | |
| getExtraMenuOptions(_, options) { | |
| let menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; | |
| options.unshift( | |
| { | |
| content: "Go to setter", | |
| callback: () => { | |
| this.goToSetter(); | |
| }, | |
| }, | |
| { | |
| content: menuEntry, | |
| callback: () => { | |
| this.currentSetter = this.findSetter(this.graph); | |
| if (this.currentSetter.length == 0) return; | |
| let linkType = (this.currentSetter.inputs[0].type); | |
| this.drawConnection = !this.drawConnection; | |
| this.slotColor = this.canvas.default_connection_color_byType[linkType] | |
| menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; | |
| this.canvas.setDirty(true, true); | |
| }, | |
| }, | |
| ); | |
| } | |
| onDrawForeground(ctx, lGraphCanvas) { | |
| if (this.drawConnection) { | |
| this._drawVirtualLink(lGraphCanvas, ctx); | |
| } | |
| } | |
| // onDrawCollapsed(ctx, lGraphCanvas) { | |
| // if (this.drawConnection) { | |
| // this._drawVirtualLink(lGraphCanvas, ctx); | |
| // } | |
| // } | |
| _drawVirtualLink(lGraphCanvas, ctx) { | |
| if (!this.currentSetter) return; | |
| let start_node_slotpos = this.currentSetter.getConnectionPos(false, 0); | |
| start_node_slotpos = [ | |
| start_node_slotpos[0] - this.pos[0], | |
| start_node_slotpos[1] - this.pos[1], | |
| ]; | |
| let end_node_slotpos = [0, -LiteGraph.NODE_TITLE_HEIGHT * 0.5]; | |
| lGraphCanvas.renderLink( | |
| ctx, | |
| start_node_slotpos, | |
| end_node_slotpos, | |
| null, | |
| false, | |
| null, | |
| this.slotColor | |
| ); | |
| } | |
| } | |
| LiteGraph.registerNodeType( | |
| "GetNode", | |
| Object.assign(GetNode, { | |
| title: "Get", | |
| }) | |
| ); | |
| GetNode.category = "KJNodes"; | |
| }, | |
| }); | |