From 8e27a2611ce6871ba78d88b5d05ba1e6e0af768b Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 26 May 2023 12:59:38 -0500 Subject: [PATCH] Switch node --- litegraph | 2 +- public/config.json | 2 +- src/lib/components/ComfyApp.ts | 4 +- src/lib/nodes/ComfyGraphNode.ts | 12 ++- src/lib/nodes/ComfyPickFirstNode.ts | 12 +-- src/lib/nodes/ComfySwitch.ts | 147 ++++++++++++++++++++++++++++ src/lib/nodes/index.ts | 1 + src/lib/utils.ts | 12 +++ 8 files changed, 176 insertions(+), 16 deletions(-) create mode 100644 src/lib/nodes/ComfySwitch.ts diff --git a/litegraph b/litegraph index d335948..5a2b601 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit d335948703b8705e7bc2b3a042142442a5d45853 +Subproject commit 5a2b60167dcf49107e93bba1d7b4599011ea71bd diff --git a/public/config.json b/public/config.json index 744ff2d..f1267ea 100644 --- a/public/config.json +++ b/public/config.json @@ -3,7 +3,7 @@ "comfyUIPort": 8188, "alwaysStripUserState": false, "promptForWorkflowName": false, - "confirmWhenUnloadingUnsavedChanges": true, + "confirmWhenUnloadingUnsavedChanges": false, "builtInTemplates": ["ControlNet", "LoRA x5", "Model Loader", "Positive_Negative", "Seed Randomizer"], "cacheBuiltInResources": true } diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 6f0e579..de4ec6d 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -1020,7 +1020,7 @@ export default class ComfyApp { try { const response = await this.api.queuePrompt(request); if (response.error != null) { - error = response.error; + error = response; } else { queueState.afterQueued(workflow.id, response.promptID, num, p.output, extraData) @@ -1030,7 +1030,7 @@ export default class ComfyApp { } if (error != null) { - const mes: string = error; + const mes: any = error; notify(`Error queuing prompt: \n${mes} `, { type: "error" }) console.error(graphToGraphVis(workflow.graph)) console.error(promptToGraphVis(p)) diff --git a/src/lib/nodes/ComfyGraphNode.ts b/src/lib/nodes/ComfyGraphNode.ts index 5911b9c..e03204a 100644 --- a/src/lib/nodes/ComfyGraphNode.ts +++ b/src/lib/nodes/ComfyGraphNode.ts @@ -103,6 +103,16 @@ export default class ComfyGraphNode extends LGraphNode { return null; } + /* + * Traverses this node backwards in the graph in order to determine the type + * for slot type inheritance. This is used if there isn't a valid upstream + * link but the output type can be inferred otherwise (for example from + * properties or other connected inputs) + */ + getUpstreamLinkForInheritedType(): LLink | null { + return this.getUpstreamLink(); + } + get layoutState(): WritableLayoutStateStore | null { return layoutStates.getLayoutByNode(this); } @@ -151,7 +161,7 @@ export default class ComfyGraphNode extends LGraphNode { while (currentNode) { updateNodes.unshift(currentNode); - const link = currentNode.getUpstreamLink(); + const link = currentNode.getUpstreamLinkForInheritedType(); if (link !== null) { const node = this.graph.getNodeById(link.origin_id) as ComfyGraphNode; if (node.canInheritSlotTypes) { diff --git a/src/lib/nodes/ComfyPickFirstNode.ts b/src/lib/nodes/ComfyPickFirstNode.ts index ef31b7a..32f2566 100644 --- a/src/lib/nodes/ComfyPickFirstNode.ts +++ b/src/lib/nodes/ComfyPickFirstNode.ts @@ -1,6 +1,7 @@ import { BuiltInSlotType, LiteGraph, NodeMode, type INodeInputSlot, type SlotLayout, type INodeOutputSlot, LLink, LConnectionKind, type ITextWidget, type SerializedLGraphNode, type IComboWidget } from "@litegraph-ts/core"; import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; import { Watch } from "@litegraph-ts/nodes-basic"; +import { nextLetter } from "$lib/utils"; export type PickFirstMode = "anyActiveLink" | "truthy" | "dataNonNull" @@ -8,17 +9,6 @@ export interface ComfyPickFirstNodeProperties extends ComfyGraphNodeProperties { mode: PickFirstMode } -function nextLetter(s: string): string { - return s.replace(/([a-zA-Z])[^a-zA-Z]*$/, function(a) { - var c = a.charCodeAt(0); - switch (c) { - case 90: return 'A'; - case 122: return 'a'; - default: return String.fromCharCode(++c); - } - }); -} - export default class ComfyPickFirstNode extends ComfyGraphNode { override properties: ComfyPickFirstNodeProperties = { tags: [], diff --git a/src/lib/nodes/ComfySwitch.ts b/src/lib/nodes/ComfySwitch.ts new file mode 100644 index 0000000..f1f32f5 --- /dev/null +++ b/src/lib/nodes/ComfySwitch.ts @@ -0,0 +1,147 @@ +import { nextLetter } from "$lib/utils"; +import { LConnectionKind, LLink, LiteGraph, type INodeInputSlot, type INodeOutputSlot, type SlotLayout } from "@litegraph-ts/core"; +import ComfyGraphNode from "./ComfyGraphNode"; + +export default class ComfySwitch extends ComfyGraphNode { + static slotLayout: SlotLayout = { + inputs: [ + { name: "A_value", type: "*" }, + { name: "A_cond", type: "boolean" }, + ], + outputs: [ + { name: "out", type: "*" } + ], + } + + override canInheritSlotTypes = true; + + private _selected: number | null = null; + + constructor(title?: string) { + super(title); + } + + override getUpstreamLinkForInheritedType(): LLink | null { + for (let index = 0; index < this.inputs.length / 2; index++) { + const link = this.getInputLink(index * 2); + if (link != null) + return link + } + return null; + } + + override getUpstreamLink(): LLink | null { + const selected = this.getSelected(); + if (selected == null) + return null; + + return this.getInputLink(selected * 2); + } + + getSelected(): number | null { + for (let i = 0; i < this.inputs.length / 2; i++) { + if (this.getInputData(i * 2 + 1) == true) + return i + } + return null; + } + + override onDrawBackground(ctx: CanvasRenderingContext2D) { + if (this.flags.collapsed || this._selected == null) { + return; + } + ctx.fillStyle = "#AFB"; + var y = this._selected * 2 * LiteGraph.NODE_SLOT_HEIGHT + 6; + ctx.beginPath(); + ctx.moveTo(30 + 50, y); + ctx.lineTo(30 + 50, y + LiteGraph.NODE_SLOT_HEIGHT); + ctx.lineTo(30 + 34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); + ctx.fill(); + }; + + override onExecute() { + this._selected = this.getSelected(); + var sel = this._selected + + if (sel == null || sel.constructor !== Number) { + this.setOutputData(0, null) + return + } + + var v = this.getInputData(sel * 2); + if (v !== undefined) { + this.setOutputData(0, v); + } + } + + private hasActiveSlots(pairIndex: number): boolean { + const slotValue = this.inputs[pairIndex * 2] + const slotCond = this.inputs[pairIndex * 2 + 1]; + return slotValue && slotCond && (slotValue.link != null || slotCond.link != null); + } + + override onConnectionsChange( + type: LConnectionKind, + slotIndex: number, + isConnected: boolean, + link: LLink, + ioSlot: (INodeInputSlot | INodeOutputSlot) + ) { + super.onConnectionsChange(type, slotIndex, isConnected, link, ioSlot); + + if (type !== LConnectionKind.INPUT) + return; + + const lastPairIdx = Math.floor((this.inputs.length / 2) - 1); + + let newlyConnected = false; + if (isConnected) { + newlyConnected = this.hasActiveSlots(lastPairIdx) + } + let newlyDisconnected = false; + if (!isConnected) { + newlyDisconnected = !this.hasActiveSlots(lastPairIdx) && !this.hasActiveSlots(lastPairIdx - 1) + } + + console.error("CONNCHANGE", lastPairIdx, this.hasActiveSlots(lastPairIdx), isConnected, slotIndex, this.inputs.length, newlyConnected, newlyDisconnected); + + if (newlyConnected) { + if (link != null) { + // Add new inputs + const lastInputName = this.inputs[this.inputs.length - 1].name + const inputName = nextLetter(lastInputName.split("_")[0]); + this.addInput(`${inputName}_value`, this.inputs[0].type) + this.addInput(`${inputName}_cond`, "boolean") + } + } + else if (newlyDisconnected) { + // Remove empty inputs + for (let i = this.inputs.length / 2; i > 0; i -= 1) { + if (i <= 0) + break; + + if (!this.hasActiveSlots(i - 1)) { + this.removeInput(i * 2) + this.removeInput(i * 2) + } + else { + break; + } + } + + let name = "A" + for (let i = 0; i < this.inputs.length; i += 2) { + this.inputs[i].name = `${name}_value`; + this.inputs[i + 1].name = `${name}_cond` + name = nextLetter(name); + } + } + } +} + +LiteGraph.registerNodeType({ + class: ComfySwitch, + title: "Comfy.Switch", + desc: "Selects an output if its condition is true, if none match returns null", + type: "utils/switch" +}) diff --git a/src/lib/nodes/index.ts b/src/lib/nodes/index.ts index 0de3fc8..0c058b4 100644 --- a/src/lib/nodes/index.ts +++ b/src/lib/nodes/index.ts @@ -2,6 +2,7 @@ export { default as ComfyReroute } from "./ComfyReroute" export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode" export { default as ComfyValueControl } from "./ComfyValueControl" export { default as ComfySelector } from "./ComfySelector" +export { default as ComfySwitch } from "./ComfySwitch" export { default as ComfyTriggerNewEventNode } from "./ComfyTriggerNewEventNode" export { default as ComfyConfigureQueuePromptButton } from "./ComfyConfigureQueuePromptButton" export { default as ComfyPickImageNode } from "./ComfyPickImageNode" diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e222e36..e6ce5c1 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -610,3 +610,15 @@ export async function readFileToText(file: File): Promise { reader.readAsText(file); }) } + + +export function nextLetter(s: string): string { + return s.replace(/([a-zA-Z])[^a-zA-Z]*$/, function(a) { + var c = a.charCodeAt(0); + switch (c) { + case 90: return 'A'; + case 122: return 'a'; + default: return String.fromCharCode(++c); + } + }); +}