@@ -153,6 +168,12 @@
+
+
@@ -164,6 +185,8 @@
+
+
diff --git a/src/lib/ComfyGraph.ts b/src/lib/ComfyGraph.ts
index 1c7d6eb..6e5d82a 100644
--- a/src/lib/ComfyGraph.ts
+++ b/src/lib/ComfyGraph.ts
@@ -7,6 +7,7 @@ import uiState from "./stores/uiState";
import { get } from "svelte/store";
import type ComfyGraphNode from "./nodes/ComfyGraphNode";
import type IComfyInputSlot from "./IComfyInputSlot";
+import type { ComfyBackendNode } from "./nodes/ComfyBackendNode";
type ComfyGraphEvents = {
configured: (graph: LGraph) => void
@@ -16,6 +17,7 @@ type ComfyGraphEvents = {
cleared: () => void
beforeChange: (graph: LGraph, param: any) => void
afterChange: (graph: LGraph, param: any) => void
+ afterExecute: () => void
}
export default class ComfyGraph extends LGraph {
@@ -42,6 +44,10 @@ export default class ComfyGraph extends LGraph {
this.eventBus.emit("afterChange", graph, info);
}
+ override onAfterExecute() {
+ this.eventBus.emit("afterExecute");
+ }
+
override onNodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
layoutState.nodeAdded(node)
this.graphSync.onNodeAdded(node);
@@ -51,7 +57,7 @@ export default class ComfyGraph extends LGraph {
&& !options.addedByDeserialize // ...and we're not trying to deserialize an existing workflow
&& get(uiState).autoAddUI) {
console.debug("[ComfyGraph] AutoAdd UI")
- const comfyNode = node as ComfyGraphNode;
+ const comfyNode = node as ComfyBackendNode;
const widgetNodesAdded = []
for (let index = 0; index < comfyNode.inputs.length; index++) {
const input = comfyNode.inputs[index];
@@ -70,7 +76,7 @@ export default class ComfyGraph extends LGraph {
}
const dragItems = widgetNodesAdded.map(wn => get(layoutState).allItemsByNode[wn.id]?.dragItem).filter(di => di)
console.debug("[ComfyGraph] Group new widgets", dragItems)
- layoutState.groupItems(dragItems, comfyNode.comfyClass)
+ layoutState.groupItems(dragItems, { title: comfyNode.comfyClass })
}
console.debug("Added", node);
diff --git a/src/lib/components/BlockContainer.svelte b/src/lib/components/BlockContainer.svelte
index a6e999c..263ebfb 100644
--- a/src/lib/components/BlockContainer.svelte
+++ b/src/lib/components/BlockContainer.svelte
@@ -143,7 +143,7 @@
gap: var(--layout-gap);
width: var(--size-full);
- .v-pane {
+ > :global(.block > .v-pane) {
flex-direction: row;
}
@@ -157,7 +157,7 @@
&.vertical {
position: relative;
- .v-pane {
+ > :global(.block > .v-pane) {
flex-direction: column;
}
diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte
index 3eb8191..ed7b3ce 100644
--- a/src/lib/components/ComfyApp.svelte
+++ b/src/lib/components/ComfyApp.svelte
@@ -17,13 +17,14 @@
import ComfyQueue from "./ComfyQueue.svelte";
import queueState from "$lib/stores/queueState";
- let app: ComfyApp = undefined;
+ export let app: ComfyApp = undefined;
let imageViewer: ImageViewer;
let queue: ComfyQueue = undefined;
let mainElem: HTMLDivElement;
let uiPane: ComfyUIPane = undefined;
let containerElem: HTMLDivElement;
let resizeTimeout: NodeJS.Timeout | null;
+ let hasShownUIHelpToast: boolean = false;
let debugLayout: boolean = true;
@@ -44,8 +45,8 @@
app.queuePrompt(0, 1);
}
- $: if (app) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
- $: if (app) app.lCanvas.allow_interaction = !$uiState.graphLocked;
+ $: if (app?.lCanvas) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
+ $: if (app?.lCanvas) app.lCanvas.allow_interaction = !$uiState.graphLocked;
$: if ($uiState.uiEditMode)
$layoutState.currentSelection = []
@@ -98,27 +99,26 @@
app.lCanvas.recenter();
}
- onMount(async () => {
- app = new ComfyApp();
+ $: if ($uiState.uiEditMode !== "disabled" && !hasShownUIHelpToast) {
+ hasShownUIHelpToast = true;
+ toast.push("Right-click to open context menu.")
+ }
- if (debugLayout) {
- layoutState.subscribe(s => {
- console.warn("UPDATESTATE", s)
- })
- }
+ if (debugLayout) {
+ layoutState.subscribe(s => {
+ console.warn("UPDATESTATE", s)
+ })
+ }
- app.api.addEventListener("status", (ev: CustomEvent) => {
- queueState.statusUpdated(ev.detail as ComfyAPIStatus);
- });
-
- await app.setup();
- (window as any).app = app;
- (window as any).appPane = uiPane;
-
- refreshView();
+ app.api.addEventListener("status", (ev: CustomEvent) => {
+ queueState.statusUpdated(ev.detail as ComfyAPIStatus);
+ });
+ $: if (app.rootEl && !imageViewer) {
imageViewer = new ImageViewer(app.rootEl);
+ }
+ $: if (containerElem) {
let wrappers = containerElem.querySelectorAll
(".pane-wrapper")
for (const wrapper of wrappers) {
const paneNode = wrapper.parentNode as HTMLElement; // get the node inside the
@@ -126,6 +126,14 @@
app.resizeCanvas()
}
}
+ }
+
+ onMount(async () => {
+ await app.setup();
+ (window as any).app = app;
+ (window as any).appPane = uiPane;
+
+ refreshView();
})
diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts
index 1a57908..69ed594 100644
--- a/src/lib/components/ComfyApp.ts
+++ b/src/lib/components/ComfyApp.ts
@@ -1,4 +1,4 @@
-import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2 } from "@litegraph-ts/core";
+import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType } from "@litegraph-ts/core";
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
import ComfyAPI from "$lib/api"
import { ComfyWidgets } from "$lib/widgets"
@@ -9,7 +9,9 @@ import type TypedEmitter from "typed-emitter";
// Import nodes
import "@litegraph-ts/nodes-basic"
+import "@litegraph-ts/nodes-events"
import * as nodes from "$lib/nodes/index"
+
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
import * as widgets from "$lib/widgets/index"
@@ -20,6 +22,7 @@ import type { SerializedLayoutState } from "$lib/stores/layoutState";
import layoutState from "$lib/stores/layoutState";
import { toast } from '@zerodevx/svelte-toast'
import ComfyGraph from "$lib/ComfyGraph";
+import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
export const COMFYBOX_SERIAL_VERSION = 1;
@@ -61,12 +64,18 @@ export default class ComfyApp {
private queueItems: QueueItem[] = [];
private processingQueue: boolean = false;
+ private alreadySetup = false;
constructor() {
this.api = new ComfyAPI();
}
async setup(): Promise {
+ if (this.alreadySetup) {
+ console.error("Already setup")
+ return;
+ }
+
this.rootEl = document.getElementById("main") as HTMLDivElement;
this.canvasEl = document.getElementById("graph-canvas") as HTMLCanvasElement;
this.lGraph = new ComfyGraph();
@@ -76,11 +85,7 @@ export default class ComfyApp {
LiteGraph.release_link_on_empty_shows_menu = true;
LiteGraph.alt_drag_do_clone_nodes = true;
- this.lGraph.start();
-
// await this.#invokeExtensionsAsync("init");
- this.registerNodeTypeOverrides();
- this.registerWidgetTypeOverrides();
await this.registerNodes();
// Load previous workflow
@@ -109,10 +114,7 @@ export default class ComfyApp {
this.addPasteHandler();
this.addKeyboardHandler();
- // Distinguish frontend/backend connections
- const BACKEND_TYPES = ["CLIP", "CLIP_VISION", "CLIP_VISION_OUTPUT", "CONDITIONING", "CONTROL_NET", "IMAGE", "LATENT", "MASK", "MODEL", "STYLE_MODEL", "VAE"]
- for (const type of BACKEND_TYPES)
- LGraphCanvas.link_type_colors[type] = "orange" // yellow
+ this.setupColorScheme()
// await this.#invokeExtensionsAsync("setup");
@@ -120,6 +122,11 @@ 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();
}
@@ -137,32 +144,14 @@ export default class ComfyApp {
localStorage.setItem("workflow", json)
}
- static node_type_overrides: Record = {}
+ static node_type_overrides: Record = {}
static widget_type_overrides: Record = {}
- private registerNodeTypeOverrides() {
- ComfyApp.node_type_overrides["SaveImage"] = nodes.ComfySaveImageNode;
- ComfyApp.node_type_overrides["PreviewImage"] = nodes.ComfyPreviewImageNode;
- }
-
- private registerWidgetTypeOverrides() {
- ComfyApp.widget_type_overrides["comfy/gallery"] = widgets.ComfyGalleryWidget_Svelte;
- }
-
private async registerNodes() {
const app = this;
// Load node definitions from the backend
const defs = await this.api.getNodeDefs();
- // await this.#invokeExtensionsAsync("addCustomNodeDefs", defs);
-
- // Generate list of known widgets
- const widgets = ComfyWidgets;
- // const widgets = Object.assign(
- // {},
- // ComfyWidgets,
- // ...(await this.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean)
- // );
// Register a node for each definition
for (const nodeId in defs) {
@@ -171,60 +160,11 @@ export default class ComfyApp {
const typeOverride = ComfyApp.node_type_overrides[nodeId]
if (typeOverride)
console.debug("Attaching custom type to received node:", nodeId, typeOverride)
- const baseClass: typeof LGraphNode = typeOverride || LGraphNode;
+ const baseClass: typeof ComfyBackendNode = typeOverride || ComfyBackendNode;
const ctor = class extends baseClass {
constructor(title?: string) {
- super(title);
- this.type = nodeId; // XXX: workaround dependency in LGraphNode.addInput()
- (this as any).comfyClass = nodeId;
- (this as any).isBackendNode = true;
- const color = LGraphCanvas.node_colors["yellow"];
- this.color = color.color
- this.bgColor = color.bgColor
- 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 };
- for (const inputName in inputs) {
- const inputData = inputs[inputName];
- const type = inputData[0];
-
- if (inputData[1]?.forceInput) {
- this.addInput(inputName, type);
- } else {
- if (Array.isArray(type)) {
- // Enums
- Object.assign(config, widgets.COMBO(this, inputName, inputData, app) || {});
- } else if (`${type}:${inputName}` in widgets) {
- // Support custom widgets by Type:Name
- Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData, app) || {});
- } else if (type in widgets) {
- // Standard type widgets
- Object.assign(config, widgets[type](this, inputName, inputData, app) || {});
- } else {
- // Node connection inputs (backend)
- this.addInput(inputName, type, { color_off: "orange", color_on: "orange" });
- }
- }
- }
-
- for (const o in nodeData["output"]) {
- const output = nodeData["output"][o];
- const outputName = nodeData["output_name"][o] || output;
- this.addOutput(outputName, output, { color_off: "orange", color_on: "orange" });
- }
-
- 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);
-
- return this;
+ super(title, nodeId, nodeData);
}
}
@@ -235,15 +175,9 @@ export default class ComfyApp {
desc: `ComfyNode: ${nodeId}`
}
- // this.#addNodeContextMenuHandler(node);
- // this.#addDrawBackgroundHandler(node, app);
-
- // await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData);
LiteGraph.registerNodeType(node);
node.category = nodeData.category;
}
-
- // await this.#invokeExtensionsAsync("registerCustomNodes");
}
private showDropZone() {
@@ -363,6 +297,23 @@ export default class ComfyApp {
});
}
+ private setupColorScheme() {
+ const setColor = (type: any, color: string) => {
+ this.lCanvas.link_type_colors[type] = color
+ this.lCanvas.default_connection_color_byType[type] = color
+ }
+
+ // Distinguish frontend/backend connections
+ const BACKEND_TYPES = ["CLIP", "CLIP_VISION", "CLIP_VISION_OUTPUT", "CONDITIONING", "CONTROL_NET", "IMAGE", "LATENT", "MASK", "MODEL", "STYLE_MODEL", "VAE"]
+ for (const type of BACKEND_TYPES) {
+ setColor(type, "orange")
+ }
+
+ setColor("OUTPUT", "rebeccapurple")
+ setColor(BuiltInSlotType.EVENT, "lightseagreen")
+ setColor(BuiltInSlotType.ACTION, "lightseagreen")
+ }
+
serialize(): SerializedAppState {
const graph = this.lGraph;
diff --git a/src/lib/components/ComfyUIPane.svelte b/src/lib/components/ComfyUIPane.svelte
index 9cee8a5..b9a95b1 100644
--- a/src/lib/components/ComfyUIPane.svelte
+++ b/src/lib/components/ComfyUIPane.svelte
@@ -29,10 +29,10 @@
// TODO
}
- function groupWidgets() {
+ function groupWidgets(horizontal: boolean) {
const items = layoutState.getCurrentSelection()
$layoutState.currentSelection = []
- layoutState.groupItems(items)
+ layoutState.groupItems(items, { direction: horizontal ? "horizontal" : "vertical" })
}
let canUngroup = false;
@@ -95,8 +95,12 @@