diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte index eb86c0e..ce9bd40 100644 --- a/src/lib/components/ComfyApp.svelte +++ b/src/lib/components/ComfyApp.svelte @@ -102,7 +102,7 @@ if (!app?.lGraph) return; - const promptFilename = false; // TODO + const promptFilename = true; // TODO let filename = "workflow.json"; if (promptFilename) { @@ -133,6 +133,7 @@ function loadWorkflow(): void { app.handleFile(fileInput.files[0]); + fileInput.files = null; } function doSaveLocal(): void { diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 640b82a..69cdcde 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -27,7 +27,7 @@ import ComfyGraph from "$lib/ComfyGraph"; import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode"; import { get } from "svelte/store"; import uiState from "$lib/stores/uiState"; -import { promptToGraphVis } from "$lib/utils"; +import { promptToGraphVis, workflowToGraphVis } from "$lib/utils"; export const COMFYBOX_SERIAL_VERSION = 1; @@ -145,9 +145,6 @@ export default class ComfyApp { this.resizeCanvas(); window.addEventListener("resize", this.resizeCanvas.bind(this)); - this.lGraph.start(); - this.lGraph.eventBus.on("afterExecute", () => this.lCanvas.draw(true)) - this.alreadySetup = true; return Promise.resolve(); @@ -374,6 +371,9 @@ export default class ComfyApp { this.lCanvas.deserialize(data.canvas) await this.refreshComboInNodes(); + + this.lGraph.start(); + this.lGraph.eventBus.on("afterExecute", () => this.lCanvas.draw(true)) } async initDefaultGraph() { @@ -606,6 +606,7 @@ export default class ComfyApp { '--toastBackground': 'var(--color-red-500)', } }) + console.error(promptToGraphVis(p)) console.error("Error queuing prompt", mes, num, p) break; } diff --git a/src/lib/nodes/ComfyActionNodes.ts b/src/lib/nodes/ComfyActionNodes.ts index 39a66b4..854afde 100644 --- a/src/lib/nodes/ComfyActionNodes.ts +++ b/src/lib/nodes/ComfyActionNodes.ts @@ -55,24 +55,21 @@ LiteGraph.registerNodeType({ type: "actions/queue_events" }) -export interface ComfyOnExecutedEventProperties extends Record { - images: GalleryOutput | null, - filename: string | null +export interface ComfyStoreImagesActionProperties extends Record { + images: GalleryOutput | null } -export class ComfyOnExecutedEvent extends ComfyGraphNode { - override properties: ComfyOnExecutedEventProperties = { - images: null, - filename: null +export class ComfyStoreImagesAction extends ComfyGraphNode { + override properties: ComfyStoreImagesActionProperties = { + images: null } static slotLayout: SlotLayout = { inputs: [ - { name: "images", type: "IMAGE" } + { name: "output", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } } ], outputs: [ { name: "images", type: "OUTPUT" }, - { name: "onExecuted", type: BuiltInSlotType.EVENT }, ], } @@ -81,20 +78,20 @@ export class ComfyOnExecutedEvent extends ComfyGraphNode { this.setOutputData(0, this.properties.images) } - override receiveOutput(output: any) { - if (output && "images" in output) { - this.setProperty("images", output as GalleryOutput) - this.setOutputData(0, this.properties.images) - this.triggerSlot(1, "bang") - } + override onAction(action: any, param: any) { + if (action !== "store" || !param || !("images" in param)) + return; + + this.setProperty("images", param as GalleryOutput) + this.setOutputData(0, this.properties.images) } } LiteGraph.registerNodeType({ - class: ComfyOnExecutedEvent, - title: "Comfy.OnExecutedEvent", - desc: "Triggers a 'bang' event when a prompt output is received.", - type: "actions/on_executed" + class: ComfyStoreImagesAction, + title: "Comfy.StoreImagesAction", + desc: "Stores images from an onExecuted callback", + type: "actions/store_images" }) export interface ComfyCopyActionProperties extends Record { diff --git a/src/lib/nodes/ComfyBackendNode.ts b/src/lib/nodes/ComfyBackendNode.ts index 9949df6..e5a6206 100644 --- a/src/lib/nodes/ComfyBackendNode.ts +++ b/src/lib/nodes/ComfyBackendNode.ts @@ -2,7 +2,9 @@ import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas"; import ComfyGraphNode from "./ComfyGraphNode"; import ComfyWidgets from "$lib/widgets" import type { ComfyWidgetNode } from "./ComfyWidgetNodes"; -import type { SerializedLGraphNode } from "@litegraph-ts/core"; +import { BuiltInSlotType, type SerializedLGraphNode } from "@litegraph-ts/core"; +import type IComfyInputSlot from "$lib/IComfyInputSlot"; +import type { ComfyInputConfig } from "$lib/IComfyInputSlot"; /* * Base class for any node with configuration sent by the backend. @@ -26,7 +28,7 @@ export class ComfyBackendNode extends ComfyGraphNode { // It just returns a hash like { "ui": { "images": results } } internally. // So this will need to be hardcoded for now. if (["PreviewImage", "SaveImage"].indexOf(comfyClass) !== -1) { - this.addOutput("output", "IMAGE"); + this.addOutput("onExecuted", BuiltInSlotType.EVENT, { color_off: "rebeccapurple", color_on: "rebeccapurple" }); } } @@ -36,14 +38,19 @@ export class ComfyBackendNode extends ComfyGraphNode { */ tags: string[] = [] + private static defaultInputConfigs: Record> = {} + private setup(nodeData: any) { var inputs = nodeData["input"]["required"]; if (nodeData["input"]["optional"] != undefined) { inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"]) } - const config = { minWidth: 1, minHeight: 1 }; + ComfyBackendNode.defaultInputConfigs[this.type] = {} + for (const inputName in inputs) { + const config = { minWidth: 1, minHeight: 1 }; + const inputData = inputs[inputName]; const type = inputData[0]; @@ -64,6 +71,9 @@ export class ComfyBackendNode extends ComfyGraphNode { this.addInput(inputName, type); } } + + if ("widgetNodeType" in config) + ComfyBackendNode.defaultInputConfigs[this.type][config.name] = config.config } for (const o in nodeData["output"]) { @@ -72,39 +82,44 @@ export class ComfyBackendNode extends ComfyGraphNode { this.addOutput(outputName, output); } - const s = this.computeSize(); - s[0] = Math.max(config.minWidth, s[0] * 1.5); - s[1] = Math.max(config.minHeight, s[1]); - this.size = s; this.serialize_widgets = false; - // app.#invokeExtensionsAsync("nodeCreated", this); } override onSerialize(o: SerializedLGraphNode) { super.onSerialize(o); (o as any).tags = this.tags + for (const input of o.inputs) { + // strip user-identifying data, it will be reinstantiated later + if ((input as any).config != null) { + (input as any).config = {}; + } + } } override onConfigure(o: SerializedLGraphNode) { super.onConfigure(o); + this.tags = (o as any).tags || [] + + const configs = ComfyBackendNode.defaultInputConfigs[o.type] + for (let index = 0; index < this.inputs.length; index++) { + const input = this.inputs[index] as IComfyInputSlot + const config = configs[input.name] + if (config != null && index >= 0 && index < this.inputs.length) { + if (input.config == null || Object.keys(input.config).length !== Object.keys(config).length) { + console.error("SET", input, config) + input.config = config + } + } + else { + input.config = {} + } + } } override onExecuted(outputData: any) { console.warn("onExecuted outputs", outputData) - for (let index = 0; index < this.outputs.length; index++) { - const output = this.outputs[index] - if (output.type === "IMAGE") { - this.setOutputData(index, outputData) - for (const node of this.getOutputNodes(index)) { - console.warn(node) - if ("receiveOutput" in node) { - const widgetNode = node as ComfyGraphNode; - widgetNode.receiveOutput(outputData); - } - } - } - } + this.triggerSlot(0, outputData) } } diff --git a/src/lib/nodes/ComfyGraphNode.ts b/src/lib/nodes/ComfyGraphNode.ts index ba2b3c6..47acecc 100644 --- a/src/lib/nodes/ComfyGraphNode.ts +++ b/src/lib/nodes/ComfyGraphNode.ts @@ -26,8 +26,17 @@ export default class ComfyGraphNode extends LGraphNode { defaultWidgets?: DefaultWidgetLayout - /** Called when a backend node sends a ComfyUI output over a link */ - receiveOutput(output: any) { + /* + * If false, don't serialize user-set properties into the workflow. + * Useful for removing personal information from shared workflows. + */ + saveUserState: boolean = true; + + /* + * Called to remove user-set properties from this node. + */ + stripUserState(o: SerializedLGraphNode) { + o.widgets_values = [] } override onResize(size: Vector2) { @@ -56,6 +65,11 @@ export default class ComfyGraphNode extends LGraphNode { (serInput as any).defaultWidgetNode = null } } + (o as any).saveUserState = this.saveUserState + if (!this.saveUserState) { + this.stripUserState(o) + console.warn("[ComfyGraphNode] stripUserState", this, o) + } } override onConfigure(o: SerializedLGraphNode) { @@ -71,5 +85,8 @@ export default class ComfyGraphNode extends LGraphNode { comfyInput.defaultWidgetNode = widgetNode.class as any } } + this.saveUserState = (o as any).saveUserState; + if (this.saveUserState == null) + this.saveUserState = true } } diff --git a/src/lib/nodes/ComfyImageCacheNode.ts b/src/lib/nodes/ComfyImageCacheNode.ts index 51fee60..b1dcc71 100644 --- a/src/lib/nodes/ComfyImageCacheNode.ts +++ b/src/lib/nodes/ComfyImageCacheNode.ts @@ -29,7 +29,7 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { inputs: [ { name: "images", type: "OUTPUT" }, { name: "index", type: "number" }, - { name: "store", type: BuiltInSlotType.ACTION }, + { name: "store", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } }, { name: "clear", type: BuiltInSlotType.ACTION } ], outputs: [ @@ -204,7 +204,7 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { } } - override onAction(action: any) { + override onAction(action: any, param: any) { if (action === "clear") { this.setProperty("images", null) this.setProperty("filenames", {}) @@ -213,12 +213,10 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { return } - const link = this.getInputLink(0) - - if (link.data && "images" in link.data) { + if (param && "images" in param) { this.setProperty("genNumber", this.properties.genNumber + 1) - const output = link.data as GalleryOutput; + const output = param as GalleryOutput; if (this.properties.updateMode === "append" && this.properties.images != null) { const newImages = this.properties.images.images.concat(output.images) @@ -226,7 +224,7 @@ export default class ComfyImageCacheNode extends ComfyGraphNode { this.setProperty("images", this.properties.images) } else { - this.setProperty("images", link.data as GalleryOutput) + this.setProperty("images", param as GalleryOutput) this.setProperty("filenames", {}) } diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index 38626a6..ad64b03 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -39,6 +39,8 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { copyFromInputLink: boolean = true; + abstract defaultValue: T; + /** Names of properties to add as inputs */ // shownInputProperties: string[] = [] @@ -204,15 +206,21 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { clampOneConfig(input: IComfyInputSlot) { } override onSerialize(o: SerializedLGraphNode) { - super.onSerialize(o); (o as any).comfyValue = get(this.value); (o as any).shownOutputProperties = this.shownOutputProperties + super.onSerialize(o); } override onConfigure(o: SerializedLGraphNode) { this.value.set((o as any).comfyValue); this.shownOutputProperties = (o as any).shownOutputProperties; } + + override stripUserState(o: SerializedLGraphNode) { + super.stripUserState(o); + (o as any).comfyValue = this.defaultValue; + o.properties.defaultValue = null; + } } export interface ComfySliderProperties extends ComfyWidgetProperties { @@ -232,6 +240,7 @@ export class ComfySliderNode extends ComfyWidgetNode { } override svelteComponentType = RangeWidget + override defaultValue = 0; static slotLayout: SlotLayout = { inputs: [ @@ -303,6 +312,7 @@ export class ComfyComboNode extends ComfyWidgetNode { } override svelteComponentType = ComboWidget + override defaultValue = "A"; constructor(name?: string) { super(name, "A") @@ -346,13 +356,20 @@ export class ComfyComboNode extends ComfyWidgetNode { } override clampOneConfig(input: IComfyInputSlot) { - if (input.config.values.indexOf(this.properties.value) === -1) { + if (!input.config.values) + this.setValue("") + else if (input.config.values.indexOf(this.properties.value) === -1) { if (input.config.values.length === 0) this.setValue("") else this.setValue(input.config.defaultValue || input.config.values[0]) } } + + override stripUserState(o: SerializedLGraphNode) { + super.stripUserState(o); + o.properties.values = [] + } } LiteGraph.registerNodeType({ @@ -384,6 +401,7 @@ export class ComfyTextNode extends ComfyWidgetNode { } override svelteComponentType = TextWidget + override defaultValue = ""; constructor(name?: string) { super(name, "") @@ -433,7 +451,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode { static slotLayout: SlotLayout = { inputs: [ { name: "images", type: "OUTPUT" }, - { name: "store", type: BuiltInSlotType.ACTION }, + { name: "store", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } }, { name: "clear", type: BuiltInSlotType.ACTION } ], outputs: [ @@ -446,6 +464,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode { ] override svelteComponentType = GalleryWidget + override defaultValue = [] override copyFromInputLink = false; override outputIndex = null; override changedIndex = null; @@ -472,12 +491,11 @@ export class ComfyGalleryNode extends ComfyWidgetNode { this.setValue([]) } else if (action === "store") { - const link = this.getInputLink(0) - if (link.data && "images" in link.data) { - const data = link.data as GalleryOutput + if (param && "images" in param) { + const data = param as GalleryOutput console.debug("[ComfyGalleryNode] Received output!", data) - const galleryItems: GradioFileData[] = this.convertItems(link.data) + const galleryItems: GradioFileData[] = this.convertItems(data) if (this.properties.updateMode === "append") { const currentValue = get(this.value) @@ -546,8 +564,13 @@ export class ComfyButtonNode extends ComfyWidgetNode { ] } - override outputIndex = 1; override svelteComponentType = ButtonWidget; + override defaultValue = false; + override outputIndex = 1; + + constructor(name?: string) { + super(name, false) + } override setValue(value: any) { super.setValue(Boolean(value)) @@ -558,10 +581,6 @@ export class ComfyButtonNode extends ComfyWidgetNode { this.triggerSlot(0, this.properties.param); this.setValue(false) // TODO onRelease } - - constructor(name?: string) { - super(name, false) - } } LiteGraph.registerNodeType({ @@ -587,6 +606,7 @@ export class ComfyCheckboxNode extends ComfyWidgetNode { } override svelteComponentType = CheckboxWidget; + override defaultValue = false; override setValue(value: any) { value = Boolean(value) diff --git a/src/lib/nodes/index.ts b/src/lib/nodes/index.ts index e07776e..c86ce49 100644 --- a/src/lib/nodes/index.ts +++ b/src/lib/nodes/index.ts @@ -1,6 +1,6 @@ export { default as ComfyReroute } from "./ComfyReroute" export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes" -export { ComfyQueueEvents, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyOnExecutedEvent, ComfyExecuteSubgraphAction } from "./ComfyActionNodes" +export { ComfyQueueEvents, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyStoreImagesAction, ComfyExecuteSubgraphAction } from "./ComfyActionNodes" export { default as ComfyValueControl } from "./ComfyValueControl" export { default as ComfySelector } from "./ComfySelector" export { default as ComfyImageCacheNode } from "./ComfyImageCacheNode" diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index d088d97..4572c05 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -381,6 +381,13 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ serialize: serializeStringArray, deserialize: deserializeStringArray }, + { + name: "saveUserState", + type: "boolean", + location: "nodeVars", + editable: true, + defaultValue: true, + }, // Range {