diff --git a/litegraph b/litegraph index cd24bc7..39b040a 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit cd24bc7356572250a5b3ec2320da1b8a73ddf2cc +Subproject commit 39b040a0b148ef0aa248562720d904150ad61859 diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 1808b6f..1979524 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -439,7 +439,7 @@ export default class ComfyApp { const n = workflow.nodes.find((n) => n.id === node_.id); if (!node_.isBackendNode) { - console.debug("Not serializing node: ", node_.type) + // console.debug("Not serializing node: ", node_.type) continue; } @@ -564,8 +564,8 @@ export default class ComfyApp { } } - console.warn({ workflow, output }) - console.warn(promptToGraphVis({ workflow, output })) + // console.debug({ workflow, output }) + // console.debug(promptToGraphVis({ workflow, output })) return { workflow, output }; } diff --git a/src/lib/components/ComfyProperties.svelte b/src/lib/components/ComfyProperties.svelte index 3017b08..8ddb036 100644 --- a/src/lib/components/ComfyProperties.svelte +++ b/src/lib/components/ComfyProperties.svelte @@ -99,11 +99,16 @@ if (spec.canShow) return spec.canShow(widget); - if (widget.type === "widget" && spec.validNodeTypes) { - const node = (widget as WidgetLayout).node - if (!node) + if (spec.validNodeTypes) { + if (widget.type === "widget") { + const node = (widget as WidgetLayout).node + if (!node) + return false; + return spec.validNodeTypes.indexOf(node.type) !== -1; + } + else if (widget.type === "container") { return false; - return spec.validNodeTypes.indexOf(node.type) !== -1; + } } return spec.name in widget.attrs diff --git a/src/lib/nodes/ComfyActionNodes.ts b/src/lib/nodes/ComfyActionNodes.ts index beeda87..39a66b4 100644 --- a/src/lib/nodes/ComfyActionNodes.ts +++ b/src/lib/nodes/ComfyActionNodes.ts @@ -112,7 +112,7 @@ export class ComfyCopyAction extends ComfyGraphNode { { name: "copy", type: BuiltInSlotType.ACTION } ], outputs: [ - { name: "out", type: "*" } + { name: "out", type: BuiltInSlotType.EVENT } ], } @@ -130,13 +130,15 @@ export class ComfyCopyAction extends ComfyGraphNode { } override onExecute() { - this.setProperty("value", this.getInputData(0)) + if (this.getInputLink(0)) + this.setProperty("value", this.getInputData(0)) } override onAction(action: any, param: any) { - this.setProperty("value", this.getInputData(0)) - this.setOutputData(0, this.properties.value) - console.log("setData", this.properties.value) + if (action === "copy") { + this.setProperty("value", this.getInputData(0)) + this.triggerSlot(0, this.properties.value) + } }; } diff --git a/src/lib/nodes/ComfyImageCacheNode.ts b/src/lib/nodes/ComfyImageCacheNode.ts index 728557b..51fee60 100644 --- a/src/lib/nodes/ComfyImageCacheNode.ts +++ b/src/lib/nodes/ComfyImageCacheNode.ts @@ -1,4 +1,4 @@ -import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp } from "@litegraph-ts/core"; +import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp, type PropertyLayout, type IComboWidget } from "@litegraph-ts/core"; import ComfyGraphNode from "./ComfyGraphNode"; import type { GalleryOutput } from "./ComfyWidgetNodes"; @@ -6,7 +6,8 @@ export interface ComfyImageCacheNodeProperties extends Record { images: GalleryOutput | null, index: number, filenames: Record, - genNumber: number + genNumber: number, + updateMode: "replace" | "append" } type ImageCacheState = "none" | "uploading" | "failed" | "cached" @@ -20,7 +21,8 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { images: null, index: 0, filenames: {}, - genNumber: 0 + genNumber: 0, + updateMode: "replace" } static slotLayout: SlotLayout = { @@ -36,11 +38,15 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { ] } + static propertyLayout: PropertyLayout = [ + { name: "updateMode", defaultValue: "replace", type: "enum", options: { values: ["replace", "append"] } } + ] + private _uploadPromise: Promise | null = null; - private _state: ImageCacheState = "none" stateWidget: ITextWidget; filenameWidget: ITextWidget; + modeWidget: IComboWidget; constructor(name?: string) { super(name) @@ -57,6 +63,14 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { "" ); this.filenameWidget.disabled = true; + + this.modeWidget = this.addWidget( + "combo", + "Mode", + this.properties.updateMode, + null, + { property: "updateMode", values: ["replace", "append"] } + ); } override onPropertyChanged(property: string, value: any, prevValue?: any) { @@ -66,13 +80,23 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { else this.properties.index = 0 } + else if (property === "updateMode") { + this.modeWidget.value = value; + } + this.updateWidgets() + } + + private updateWidgets() { if (this.properties.filenames && this.properties.images) { const fileCount = this.properties.images.images.length; const cachedCount = Object.keys(this.properties.filenames).length console.warn(cachedCount, this.properties.filenames) this.filenameWidget.value = `${fileCount} files, ${cachedCount} cached` } + else { + this.filenameWidget.value = `No files cached` + } } override onExecute() { @@ -185,6 +209,7 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { this.setProperty("images", null) this.setProperty("filenames", {}) this.setProperty("index", 0) + this.updateWidgets(); return } @@ -192,11 +217,24 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { if (link.data && "images" in link.data) { this.setProperty("genNumber", this.properties.genNumber + 1) - this.setProperty("images", link.data as GalleryOutput) - this.setProperty("filenames", {}) - console.debug("[ComfyImageCacheNode] Received output!", link.data) + + const output = link.data as GalleryOutput; + + if (this.properties.updateMode === "append" && this.properties.images != null) { + const newImages = this.properties.images.images.concat(output.images) + this.properties.images.images = newImages + this.setProperty("images", this.properties.images) + } + else { + this.setProperty("images", link.data as GalleryOutput) + this.setProperty("filenames", {}) + } + + console.debug("[ComfyImageCacheNode] Received output!", output, this.properties.updateMode, this.properties.images) this.setIndex(0, true) } + + this.updateWidgets(); } } diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index a1aa2dc..095cce6 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -1,10 +1,11 @@ -import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget, type INodeOutputSlot, type SerializedLGraphNode, BuiltInSlotType, type PropertyLayout } from "@litegraph-ts/core"; +import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget, type INodeOutputSlot, type SerializedLGraphNode, BuiltInSlotType, type PropertyLayout, type IComboWidget } from "@litegraph-ts/core"; import ComfyGraphNode from "./ComfyGraphNode"; import ComboWidget from "$lib/widgets/ComboWidget.svelte"; import RangeWidget from "$lib/widgets/RangeWidget.svelte"; import TextWidget from "$lib/widgets/TextWidget.svelte"; import GalleryWidget from "$lib/widgets/GalleryWidget.svelte"; import ButtonWidget from "$lib/widgets/ButtonWidget.svelte"; +import CheckboxWidget from "$lib/widgets/CheckboxWidget.svelte"; import type { SvelteComponentDev } from "svelte/internal"; import { Watch } from "@litegraph-ts/nodes-basic"; import type IComfyInputSlot from "$lib/IComfyInputSlot"; @@ -48,8 +49,11 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { override isBackendNode = false; override serialize_widgets = true; - outputIndex: number | null = 0; + // input slots inputIndex: number = 0; + + // output slots + outputIndex: number | null = 0; changedIndex: number | null = 1; displayWidget: ITextWidget; @@ -94,7 +98,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { if (this.outputIndex !== null && this.outputs.length >= this.outputIndex) { this.setOutputData(this.outputIndex, get(this.value)) } - if (this.changedIndex !== null & this.outputs.length >= this.changedIndex) { + if (this.changedIndex !== null && this.outputs.length >= this.changedIndex) { const changedOutput = this.outputs[this.changedIndex] if (changedOutput.type === BuiltInSlotType.EVENT) this.triggerSlot(this.changedIndex, "changed") @@ -118,10 +122,8 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { if (this.copyFromInputLink) { if (this.inputs.length >= this.inputIndex) { const data = this.getInputData(this.inputIndex) - if (data) { // TODO can "null" be a legitimate value here? + if (data != null) { // TODO can "null" be a legitimate value here? this.setValue(data) - const input = this.getInputLink(this.inputIndex) - input.data = null; } } } @@ -231,7 +233,8 @@ export class ComfySliderNode extends ComfyWidgetNode { static slotLayout: SlotLayout = { inputs: [ - { name: "value", type: "number" } + { name: "value", type: "number" }, + { name: "store", type: BuiltInSlotType.ACTION } ], outputs: [ { name: "value", type: "number" }, @@ -250,6 +253,11 @@ export class ComfySliderNode extends ComfyWidgetNode { super(name, 0) } + override onAction(action: any, param: any) { + if (action === "store" && typeof param === "number") + this.setValue(param) + } + override setValue(value: any) { if (typeof value !== "number") return; @@ -283,7 +291,8 @@ export class ComfyComboNode extends ComfyWidgetNode { static slotLayout: SlotLayout = { inputs: [ - { name: "value", type: "string" } + { name: "value", type: "string" }, + { name: "store", type: BuiltInSlotType.ACTION } ], outputs: [ { name: "value", type: "string" }, @@ -323,6 +332,11 @@ export class ComfyComboNode extends ComfyWidgetNode { return true; } + override onAction(action: any, param: any) { + if (action === "store" && typeof param === "string") + this.setValue(param) + } + override setValue(value: any) { if (typeof value !== "string" || this.properties.values.indexOf(value) === -1) return; @@ -358,7 +372,8 @@ export class ComfyTextNode extends ComfyWidgetNode { static slotLayout: SlotLayout = { inputs: [ - { name: "value", type: "string" } + { name: "value", type: "string" }, + { name: "store", type: BuiltInSlotType.ACTION } ], outputs: [ { name: "value", type: "string" }, @@ -372,6 +387,11 @@ export class ComfyTextNode extends ComfyWidgetNode { super(name, "") } + override onAction(action: any, param: any) { + if (action === "store") + this.setValue(param) + } + override setValue(value: any) { super.setValue(`${value}`) } @@ -428,15 +448,24 @@ export class ComfyGalleryNode extends ComfyWidgetNode { override outputIndex = null; override changedIndex = null; + modeWidget: IComboWidget; + constructor(name?: string) { super(name, []) + this.modeWidget = this.addWidget("combo", "Mode", this.properties.updateMode, null, { property: "updateMode", values: ["replace", "append"] }) + } + + override onPropertyChanged(property: any, value: any) { + if (property === "updateMode") { + this.modeWidget.value = value; + } } override onExecute() { this.setOutputData(0, this.properties.index) } - override onAction(action: any) { + override onAction(action: any, param: any, options: { action_call?: string }) { if (action === "clear") { this.setValue([]) } @@ -539,3 +568,40 @@ LiteGraph.registerNodeType({ desc: "Button that triggers an event when clicked", type: "ui/button" }) + +export interface ComfyCheckboxProperties extends ComfyWidgetProperties { +} + +export class ComfyCheckboxNode extends ComfyWidgetNode { + override properties: ComfyCheckboxProperties = { + defaultValue: false, + } + + static slotLayout: SlotLayout = { + outputs: [ + { name: "value", type: "boolean" }, + { name: "changed", type: BuiltInSlotType.EVENT }, + ] + } + + override svelteComponentType = CheckboxWidget; + + override setValue(value: any) { + value = Boolean(value) + const changed = value != get(this.value); + super.setValue(Boolean(value)) + if (changed) + this.triggerSlot(1) + } + + constructor(name?: string) { + super(name, false) + } +} + +LiteGraph.registerNodeType({ + class: ComfyCheckboxNode, + title: "UI.Checkbox", + desc: "Checkbox that stores a boolean value", + type: "ui/checkbox" +}) diff --git a/src/lib/widgets/CheckboxWidget.svelte b/src/lib/widgets/CheckboxWidget.svelte new file mode 100644 index 0000000..7acb016 --- /dev/null +++ b/src/lib/widgets/CheckboxWidget.svelte @@ -0,0 +1,55 @@ + + +
+
+ {#key $attrsChanged} + {#if node !== null} + + + + {/if} + {/key} +
+
+ + diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte index 3efb7c4..68cb6c4 100644 --- a/src/lib/widgets/ComboWidget.svelte +++ b/src/lib/widgets/ComboWidget.svelte @@ -114,6 +114,11 @@ } } + :global(.svelte-select) { + width: auto; + max-width: 30rem; + } + :global(.svelte-select-list) { z-index: var(--layer-top) !important; } diff --git a/src/lib/widgets/GalleryWidget.svelte b/src/lib/widgets/GalleryWidget.svelte index e5b2c2c..5c06043 100644 --- a/src/lib/widgets/GalleryWidget.svelte +++ b/src/lib/widgets/GalleryWidget.svelte @@ -73,10 +73,13 @@ {/if} -