From 5f51ed4bd7838a778d5f2279e42c12d2671f0cb2 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 1 Jun 2023 08:51:32 -0500 Subject: [PATCH 01/12] Check for existence of Notification safely --- src/lib/components/ComfyApp.ts | 8 +++++--- src/lib/notify.ts | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 2c77775..e4c7625 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -728,9 +728,11 @@ export default class ComfyApp { } private requestPermissions() { - if (Notification.permission === "default") { - Notification.requestPermission() - .then((result) => console.log("Notification status:", result)); + if (window.Notification != null) { + if (window.Notification.permission === "default") { + window.Notification.requestPermission() + .then((result) => console.log("Notification status:", result)); + } } } diff --git a/src/lib/notify.ts b/src/lib/notify.ts index 80a0f2c..90caee9 100644 --- a/src/lib/notify.ts +++ b/src/lib/notify.ts @@ -92,6 +92,11 @@ function notifyToast(text: string, options: NotifyOptions) { } function notifyNative(text: string, options: NotifyOptions) { + if (window.Notification == null) { + console.warn("[notify] No Notification available on window") + return + } + if (document.hasFocus()) return; From 634d16a18244b9b23995df5ca1647932601754f3 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:38:35 -0500 Subject: [PATCH 02/12] Show an X if PickFirst fails to find a good input --- src/lib/nodes/ComfyPickFirstNode.ts | 33 ++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/lib/nodes/ComfyPickFirstNode.ts b/src/lib/nodes/ComfyPickFirstNode.ts index 32f2566..1669dc8 100644 --- a/src/lib/nodes/ComfyPickFirstNode.ts +++ b/src/lib/nodes/ComfyPickFirstNode.ts @@ -40,17 +40,34 @@ export default class ComfyPickFirstNode extends ComfyGraphNode { } override onDrawBackground(ctx: CanvasRenderingContext2D) { - if (this.flags.collapsed || this.selected === -1) { + if (this.flags.collapsed) { return; } - ctx.fillStyle = "#AFB"; - var y = (this.selected) * LiteGraph.NODE_SLOT_HEIGHT + 6; - ctx.beginPath(); - ctx.moveTo(50, y); - ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT); - ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); - ctx.fill(); + if (this.selected === -1) { + // Draw an X + const y = LiteGraph.NODE_SLOT_HEIGHT + 6; + ctx.lineWidth = 5; + ctx.strokeStyle = "red"; + ctx.beginPath(); + + ctx.moveTo(50 - 15, y - 15); + ctx.lineTo(50 + 15, y + 15); + ctx.stroke(); + + ctx.moveTo(50 + 15, y - 15); + ctx.lineTo(50 - 15, y + 15); + ctx.stroke(); + } + else { + ctx.fillStyle = "#AFB"; + const y = (this.selected) * LiteGraph.NODE_SLOT_HEIGHT + 6; + ctx.beginPath(); + ctx.moveTo(50, y); + ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT); + ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); + ctx.fill(); + } }; override onConnectionsChange( From afd3c05d0b51adfe751a52193cf4c14f3d22987d Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:54:27 -0500 Subject: [PATCH 03/12] Count disconnected frontend nodes in upstream check Closes #103 --- src/lib/components/ComfyGraphErrorList.svelte | 11 +- src/lib/components/ComfyPromptSerializer.ts | 118 +++++++++--------- src/lib/nodes/ComfyPickFirstNode.ts | 11 +- 3 files changed, 72 insertions(+), 68 deletions(-) diff --git a/src/lib/components/ComfyGraphErrorList.svelte b/src/lib/components/ComfyGraphErrorList.svelte index 7680169..f8a669b 100644 --- a/src/lib/components/ComfyGraphErrorList.svelte +++ b/src/lib/components/ComfyGraphErrorList.svelte @@ -4,8 +4,8 @@ import Accordion from "./gradio/app/Accordion.svelte"; import uiState from '$lib/stores/uiState'; import type { ComfyNodeDefInputType } from "$lib/ComfyNodeDef"; - import type { INodeInputSlot, LGraphNode, Subgraph } from "@litegraph-ts/core"; - import { UpstreamNodeLocator } from "./ComfyPromptSerializer"; + import type { INodeInputSlot, LGraphNode, LLink, Subgraph } from "@litegraph-ts/core"; + import { UpstreamNodeLocator, getUpstreamLink } from "./ComfyPromptSerializer"; import JsonView from "./JsonView.svelte"; export let app: ComfyApp; @@ -35,7 +35,7 @@ const node = app.lCanvas.graph.getNodeByIdRecursive(error.nodeID); - const inputIndex =node.findInputSlotIndexByName(error.input.name); + const inputIndex = node.findInputSlotIndexByName(error.input.name); if (inputIndex === -1) { return } @@ -43,7 +43,10 @@ // TODO multiple tags? const tag: string | null = error.queueEntry.extraData.extra_pnginfo.comfyBoxPrompt.subgraphs[0]; - const test = (node: LGraphNode) => (node as any).isBackendNode + const test = (node: LGraphNode, currentLink: LLink) => { + const [nextGraph, nextLink, nextInputSlot, nextNode] = getUpstreamLink(node, currentLink) + return nextLink == null; + }; const nodeLocator = new UpstreamNodeLocator(test) const [_, foundLink, foundInputSlot, foundPrevNode] = nodeLocator.locateUpstream(node, inputIndex, tag); diff --git a/src/lib/components/ComfyPromptSerializer.ts b/src/lib/components/ComfyPromptSerializer.ts index 51808d4..cfb5786 100644 --- a/src/lib/components/ComfyPromptSerializer.ts +++ b/src/lib/components/ComfyPromptSerializer.ts @@ -71,62 +71,62 @@ export function isActiveBackendNode(node: LGraphNode, tag: string | null = null) return true; } -type UpstreamResult = [LGraph | null, LLink | null, number | null, LGraphNode | null]; +export type UpstreamResult = [LGraph | null, LLink | null, number | null, LGraphNode | null]; + +function followSubgraph(subgraph: Subgraph, link: LLink): UpstreamResult { + if (link.origin_id != subgraph.id) + throw new Error("Invalid link and graph output!") + + const innerGraphOutput = subgraph.getInnerGraphOutputByIndex(link.origin_slot) + if (innerGraphOutput == null) + throw new Error("No inner graph input!") + + const nextLink = innerGraphOutput.getInputLink(0) + return [innerGraphOutput.graph, nextLink, 0, innerGraphOutput]; +} + +function followGraphInput(graphInput: GraphInput, link: LLink): UpstreamResult { + if (link.origin_id != graphInput.id) + throw new Error("Invalid link and graph input!") + + const outerSubgraph = graphInput.getParentSubgraph(); + if (outerSubgraph == null) + throw new Error("No outer subgraph!") + + const outerInputIndex = outerSubgraph.inputs.findIndex(i => i.name === graphInput.nameInGraph) + if (outerInputIndex == null) + throw new Error("No outer input slot!") + + const nextLink = outerSubgraph.getInputLink(outerInputIndex) + return [outerSubgraph.graph, nextLink, outerInputIndex, outerSubgraph]; +} + +export function getUpstreamLink(parent: LGraphNode, currentLink: LLink): UpstreamResult { + if (parent.is(Subgraph)) { + console.debug("FollowSubgraph") + return followSubgraph(parent, currentLink); + } + else if (parent.is(GraphInput)) { + console.debug("FollowGraphInput") + return followGraphInput(parent, currentLink); + } + else if ("getUpstreamLink" in parent) { + const link = (parent as ComfyGraphNode).getUpstreamLink(); + return [parent.graph, link, link?.target_slot, parent]; + } + else if (parent.inputs.length === 1) { + // Only one input, so assume we can follow it backwards. + const link = parent.getInputLink(0); + if (link) { + return [parent.graph, link, 0, parent] + } + } + console.warn("[graphToPrompt] Frontend node does not support getUpstreamLink", parent.type) + return [null, null, null, null]; +} export class UpstreamNodeLocator { - constructor(private isTheTargetNode: (node: LGraphNode) => boolean) { - } - - private followSubgraph(subgraph: Subgraph, link: LLink): UpstreamResult { - if (link.origin_id != subgraph.id) - throw new Error("Invalid link and graph output!") - - const innerGraphOutput = subgraph.getInnerGraphOutputByIndex(link.origin_slot) - if (innerGraphOutput == null) - throw new Error("No inner graph input!") - - const nextLink = innerGraphOutput.getInputLink(0) - return [innerGraphOutput.graph, nextLink, 0, innerGraphOutput]; - } - - private followGraphInput(graphInput: GraphInput, link: LLink): UpstreamResult { - if (link.origin_id != graphInput.id) - throw new Error("Invalid link and graph input!") - - const outerSubgraph = graphInput.getParentSubgraph(); - if (outerSubgraph == null) - throw new Error("No outer subgraph!") - - const outerInputIndex = outerSubgraph.inputs.findIndex(i => i.name === graphInput.nameInGraph) - if (outerInputIndex == null) - throw new Error("No outer input slot!") - - const nextLink = outerSubgraph.getInputLink(outerInputIndex) - return [outerSubgraph.graph, nextLink, outerInputIndex, outerSubgraph]; - } - - private getUpstreamLink(parent: LGraphNode, currentLink: LLink): UpstreamResult { - if (parent.is(Subgraph)) { - console.debug("FollowSubgraph") - return this.followSubgraph(parent, currentLink); - } - else if (parent.is(GraphInput)) { - console.debug("FollowGraphInput") - return this.followGraphInput(parent, currentLink); - } - else if ("getUpstreamLink" in parent) { - const link = (parent as ComfyGraphNode).getUpstreamLink(); - return [parent.graph, link, link?.target_slot, parent]; - } - else if (parent.inputs.length === 1) { - // Only one input, so assume we can follow it backwards. - const link = parent.getInputLink(0); - if (link) { - return [parent.graph, link, 0, parent] - } - } - console.warn("[graphToPrompt] Frontend node does not support getUpstreamLink", parent.type) - return [null, null, null, null]; + constructor(private isTheTargetNode: (node: LGraphNode, currentLink: LLink) => boolean) { } /* @@ -146,8 +146,8 @@ export class UpstreamNodeLocator { let currentInputSlot = inputIndex; let currentNode = fromNode; - const shouldFollowParent = (parent: LGraphNode) => { - return isActiveNode(parent, tag) && !this.isTheTargetNode(parent); + const shouldFollowParent = (parent: LGraphNode, currentLink: LLink) => { + return isActiveNode(parent, tag) && !this.isTheTargetNode(parent, currentLink); } // If there are non-target nodes between us and another @@ -156,8 +156,8 @@ export class UpstreamNodeLocator { // will simply follow their single input, while branching // nodes have conditional logic that determines which link // to follow backwards. - while (shouldFollowParent(parent)) { - const [nextGraph, nextLink, nextInputSlot, nextNode] = this.getUpstreamLink(parent, currentLink); + while (shouldFollowParent(parent, currentLink)) { + const [nextGraph, nextLink, nextInputSlot, nextNode] = getUpstreamLink(parent, currentLink); currentInputSlot = nextInputSlot; currentNode = nextNode; @@ -183,7 +183,7 @@ export class UpstreamNodeLocator { } } - if (!isActiveNode(parent, tag) || !this.isTheTargetNode(parent) || currentLink == null) + if (!isActiveNode(parent, tag) || !this.isTheTargetNode(parent, currentLink) || currentLink == null) return [null, currentLink, currentInputSlot, currentNode]; return [parent, currentLink, currentInputSlot, currentNode] diff --git a/src/lib/nodes/ComfyPickFirstNode.ts b/src/lib/nodes/ComfyPickFirstNode.ts index 1669dc8..50d5875 100644 --- a/src/lib/nodes/ComfyPickFirstNode.ts +++ b/src/lib/nodes/ComfyPickFirstNode.ts @@ -3,7 +3,7 @@ import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode" import { Watch } from "@litegraph-ts/nodes-basic"; import { nextLetter } from "$lib/utils"; -export type PickFirstMode = "anyActiveLink" | "truthy" | "dataNonNull" +export type PickFirstMode = "anyActiveLink" | "dataTruthy" | "dataNonNull" export interface ComfyPickFirstNodeProperties extends ComfyGraphNodeProperties { mode: PickFirstMode @@ -12,7 +12,7 @@ export interface ComfyPickFirstNodeProperties extends ComfyGraphNodeProperties { export default class ComfyPickFirstNode extends ComfyGraphNode { override properties: ComfyPickFirstNodeProperties = { tags: [], - mode: "dataNonNull" + mode: "anyActiveLink" } static slotLayout: SlotLayout = { @@ -36,7 +36,7 @@ export default class ComfyPickFirstNode extends ComfyGraphNode { super(title); this.displayWidget = this.addWidget("text", "Value", "") this.displayWidget.disabled = true; - this.modeWidget = this.addWidget("combo", "Mode", this.properties.mode, null, { property: "mode", values: ["anyActiveLink", "truthy", "dataNonNull"] }) + this.modeWidget = this.addWidget("combo", "Mode", this.properties.mode, null, { property: "mode", values: ["anyActiveLink", "dataTruthy", "dataNonNull"] }) } override onDrawBackground(ctx: CanvasRenderingContext2D) { @@ -45,7 +45,7 @@ export default class ComfyPickFirstNode extends ComfyGraphNode { } if (this.selected === -1) { - // Draw an X + // Draw an X indicating nothing matched the selection criteria const y = LiteGraph.NODE_SLOT_HEIGHT + 6; ctx.lineWidth = 5; ctx.strokeStyle = "red"; @@ -60,6 +60,7 @@ export default class ComfyPickFirstNode extends ComfyGraphNode { ctx.stroke(); } else { + // Draw an arrow pointing to the selected input ctx.fillStyle = "#AFB"; const y = (this.selected) * LiteGraph.NODE_SLOT_HEIGHT + 6; ctx.beginPath(); @@ -130,7 +131,7 @@ export default class ComfyPickFirstNode extends ComfyGraphNode { else { if (this.properties.mode === "dataNonNull") return link.data != null; - else if (this.properties.mode === "truthy") + else if (this.properties.mode === "dataTruthy") return Boolean(link.data) else // anyActiveLink return true; From e8539add51f84a88bfa40a2f08bb5c34c171c77f Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:24:17 -0500 Subject: [PATCH 04/12] Fix missing index check --- src/lib/components/ComfyPromptSerializer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/ComfyPromptSerializer.ts b/src/lib/components/ComfyPromptSerializer.ts index cfb5786..ec21339 100644 --- a/src/lib/components/ComfyPromptSerializer.ts +++ b/src/lib/components/ComfyPromptSerializer.ts @@ -94,7 +94,7 @@ function followGraphInput(graphInput: GraphInput, link: LLink): UpstreamResult { throw new Error("No outer subgraph!") const outerInputIndex = outerSubgraph.inputs.findIndex(i => i.name === graphInput.nameInGraph) - if (outerInputIndex == null) + if (outerInputIndex === -1) throw new Error("No outer input slot!") const nextLink = outerSubgraph.getInputLink(outerInputIndex) From b1dd8a624223a154f62e444b4b356eb731121970 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 1 Jun 2023 18:43:45 -0500 Subject: [PATCH 05/12] Default workflow subgraph attribute --- src/lib/components/ComfyApp.ts | 6 +++++- src/lib/stores/layoutStates.ts | 7 +++++++ src/lib/stores/workflowState.ts | 6 ++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index e4c7625..7078f29 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -941,7 +941,11 @@ export default class ComfyApp { if (workflow.attrs.queuePromptButtonRunWorkflow) { // Hold control to queue at the front const num = this.ctrlDown ? -1 : 0; - this.queuePrompt(workflow, num, 1); + let tag = null; + if (workflow.attrs.queuePromptButtonDefaultWorkflow) { + tag = workflow.attrs.queuePromptButtonDefaultWorkflow + } + this.queuePrompt(workflow, num, 1, tag); } } diff --git a/src/lib/stores/layoutStates.ts b/src/lib/stores/layoutStates.ts index 0dcd911..bae1f34 100644 --- a/src/lib/stores/layoutStates.ts +++ b/src/lib/stores/layoutStates.ts @@ -681,6 +681,13 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ editable: true, defaultValue: true }, + { + name: "queuePromptButtonDefaultWorkflow", + type: "string", + location: "workflow", + editable: true, + defaultValue: "" + }, { name: "showDefaultNotifications", type: "boolean", diff --git a/src/lib/stores/workflowState.ts b/src/lib/stores/workflowState.ts index c7e0707..559a969 100644 --- a/src/lib/stores/workflowState.ts +++ b/src/lib/stores/workflowState.ts @@ -57,6 +57,12 @@ export type WorkflowAttributes = { */ queuePromptButtonRunWorkflow: boolean, + /* + * Default subgraph to run if `queuePromptButtonRunWorkflow` is `true`. Set + * to blank to run the default subgraph (tagless). + */ + queuePromptButtonDefaultWorkflow: string, + /* * If true, notifications will be shown when a prompt is queued and * completed. Set to false if you need more detailed control over the From 4923a78d7cf1d53f082ee9cab9a7fe0f60e2394c Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 1 Jun 2023 18:44:02 -0500 Subject: [PATCH 06/12] Improvement for finding nodes with missing tags --- src/lib/ComfyGraphCanvas.ts | 21 +++-- src/lib/components/ComfyGraphErrorList.svelte | 80 +++++++++++++++---- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/src/lib/ComfyGraphCanvas.ts b/src/lib/ComfyGraphCanvas.ts index 220d85e..ac04293 100644 --- a/src/lib/ComfyGraphCanvas.ts +++ b/src/lib/ComfyGraphCanvas.ts @@ -25,7 +25,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { activeErrors?: ComfyGraphErrors = null; blinkError: ComfyGraphErrorLocation | null = null; blinkErrorTime: number = 0; - highlightNodeAndInput: [LGraphNode, number] | null = null; + highlightNodeAndInput: [LGraphNode, number | null] | null = null; get comfyGraph(): ComfyGraph | null { return this.graph as ComfyGraph; @@ -133,6 +133,15 @@ export default class ComfyGraphCanvas extends LGraphCanvas { else if (isHighlightedNode) { color = "cyan"; thickness = 2 + + // Blink node if no input highlighted + if (this.highlightNodeAndInput[1] == null) { + if (this.blinkErrorTime > 0) { + if ((Math.floor(this.blinkErrorTime / 2)) % 2 === 0) { + color = null; + } + } + } } else if (ss.currentHoveredNodes.has(node.id)) { color = "lightblue"; @@ -172,9 +181,11 @@ export default class ComfyGraphCanvas extends LGraphCanvas { } if (draw) { const [node, inputSlot] = this.highlightNodeAndInput; - ctx.lineWidth = 2; - ctx.strokeStyle = color; - this.highlightNodeInput(node, inputSlot, ctx); + if (inputSlot != null) { + ctx.lineWidth = 2; + ctx.strokeStyle = color; + this.highlightNodeInput(node, inputSlot, ctx); + } } } } @@ -733,7 +744,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { this.selectNode(node); } - jumpToNodeAndInput(node: LGraphNode, slotIndex: number) { + jumpToNodeAndInput(node: LGraphNode, slotIndex: number | null) { this.jumpToNode(node); this.highlightNodeAndInput = [node, slotIndex]; this.blinkErrorTime = 20; diff --git a/src/lib/components/ComfyGraphErrorList.svelte b/src/lib/components/ComfyGraphErrorList.svelte index f8a669b..3822ec7 100644 --- a/src/lib/components/ComfyGraphErrorList.svelte +++ b/src/lib/components/ComfyGraphErrorList.svelte @@ -5,15 +5,35 @@ import uiState from '$lib/stores/uiState'; import type { ComfyNodeDefInputType } from "$lib/ComfyNodeDef"; import type { INodeInputSlot, LGraphNode, LLink, Subgraph } from "@litegraph-ts/core"; - import { UpstreamNodeLocator, getUpstreamLink } from "./ComfyPromptSerializer"; + import { UpstreamNodeLocator, getUpstreamLink, nodeHasTag } from "./ComfyPromptSerializer"; import JsonView from "./JsonView.svelte"; export let app: ComfyApp; export let errors: ComfyGraphErrors; + let missingTag = null; + let nodeToJumpTo = null; + let inputSlotToHighlight = null; + let _errors = null + + $: if (_errors != errors) { + _errors = errors; + if (errors.errors[0]) { + jumpToError(errors.errors[0]) + } + } + function closeList() { app.lCanvas.clearErrors(); $uiState.activeError = null; + clearState() + } + + function clearState() { + _errors = null; + missingTag = null; + nodeToJumpTo = null; + inputSlotToHighlight = null; } function getParentNode(error: ComfyGraphErrorLocation): Subgraph | null { @@ -24,11 +44,19 @@ return node.graph._subgraph_node } - function canJumpToDisconnectedInput(error: ComfyGraphErrorLocation): boolean { - return error.errorType === ComfyNodeErrorType.RequiredInputMissing && error.input != null; + function jumpToFoundNode() { + if (nodeToJumpTo == null) { + return + } + + app.lCanvas.jumpToNodeAndInput(nodeToJumpTo, inputSlotToHighlight); } - function jumpToDisconnectedInput(error: ComfyGraphErrorLocation) { + function detectDisconnected(error: ComfyGraphErrorLocation) { + missingTag = null; + nodeToJumpTo = null; + inputSlotToHighlight = null; + if (error.errorType !== ComfyNodeErrorType.RequiredInputMissing || error.input == null) { return } @@ -44,19 +72,32 @@ const tag: string | null = error.queueEntry.extraData.extra_pnginfo.comfyBoxPrompt.subgraphs[0]; const test = (node: LGraphNode, currentLink: LLink) => { + if (!nodeHasTag(node, tag, true)) + return true; + const [nextGraph, nextLink, nextInputSlot, nextNode] = getUpstreamLink(node, currentLink) return nextLink == null; }; const nodeLocator = new UpstreamNodeLocator(test) - const [_, foundLink, foundInputSlot, foundPrevNode] = nodeLocator.locateUpstream(node, inputIndex, tag); + const [foundNode, foundLink, foundInputSlot, foundPrevNode] = nodeLocator.locateUpstream(node, inputIndex, null); if (foundInputSlot != null && foundPrevNode != null) { - app.lCanvas.jumpToNodeAndInput(foundPrevNode, foundInputSlot); + if (!nodeHasTag(foundNode, tag, true)) { + nodeToJumpTo = foundNode + missingTag = tag; + inputSlotToHighlight = null; + } + else { + nodeToJumpTo = foundPrevNode; + inputSlotToHighlight = foundInputSlot; + } } } function jumpToError(error: ComfyGraphErrorLocation) { app.lCanvas.jumpToError(error); + + detectDisconnected(error); } function getInputTypeName(type: ComfyNodeDefInputType) { @@ -91,26 +132,37 @@
- {error.message} + {#if missingTag && nodeToJumpTo} +
+
Node "{nodeToJumpTo.title}" was missing tag used in workflow:{missingTag}
+
Tags on node: [{(nodeToJumpTo?.attrs?.tags || []).join(", ")}]
+
+ {:else} + {error.message} + {/if} {#if error.exceptionType} ({error.exceptionType}) {/if} {#if error.exceptionMessage && !isExecutionError}
{error.exceptionMessage}
{/if} - {#if error.input} + {#if nodeToJumpTo != null} +
+ + {#if missingTag} + Jump to node: {nodeToJumpTo.title} + {:else} + Find disconnected input + {/if} +
+ {/if} + {#if error.input && !missingTag}
Input: {error.input.name} {#if error.input.config} ({getInputTypeName(error.input.config[0])}) {/if}
- {#if canJumpToDisconnectedInput(error)} -
- - Find disconnected input -
- {/if} {#if error.input.receivedValue}
From d07d1e74780bbc23d7a877e838b2af487b6c974c Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 1 Jun 2023 19:41:18 -0500 Subject: [PATCH 07/12] Fix gallery thumbnails bar not scrolling on click Closes #104 --- src/lib/components/ComfyGraphErrorList.svelte | 2 +- src/lib/components/gradio/gallery/Gallery.svelte | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/components/ComfyGraphErrorList.svelte b/src/lib/components/ComfyGraphErrorList.svelte index 3822ec7..8028093 100644 --- a/src/lib/components/ComfyGraphErrorList.svelte +++ b/src/lib/components/ComfyGraphErrorList.svelte @@ -135,7 +135,7 @@ {#if missingTag && nodeToJumpTo}
Node "{nodeToJumpTo.title}" was missing tag used in workflow:{missingTag}
-
Tags on node: [{(nodeToJumpTo?.attrs?.tags || []).join(", ")}]
+
Tags on node: {(nodeToJumpTo?.properties?.tags || []).join(", ")}
{:else} {error.message} diff --git a/src/lib/components/gradio/gallery/Gallery.svelte b/src/lib/components/gradio/gallery/Gallery.svelte index f6a8108..06a92bc 100644 --- a/src/lib/components/gradio/gallery/Gallery.svelte +++ b/src/lib/components/gradio/gallery/Gallery.svelte @@ -15,7 +15,7 @@ export let label: string; export let root: string = ""; export let root_url: null | string = null; - export let scrollOnUpdate = false; + export let focusOnScroll = false; export let value: Array | Array | null = null; export let style: Styles = { grid_cols: [2], @@ -121,11 +121,11 @@ let container: HTMLDivElement; async function scroll_to_img(index: number | null) { - if (!scrollOnUpdate) return; if (typeof index !== "number") return; await tick(); - el[index].focus(); + if (focusOnScroll) + el[index].focus(); const { left: container_left, width: container_width } = container.getBoundingClientRect(); From 03a70c60cff6e9b0f700b917b97f5efcd46c3a32 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 1 Jun 2023 19:52:19 -0500 Subject: [PATCH 08/12] Show executing status/progress on subgraphs --- src/lib/ComfyGraphCanvas.ts | 6 +++--- src/lib/stores/queueState.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/lib/ComfyGraphCanvas.ts b/src/lib/ComfyGraphCanvas.ts index ac04293..c6ae720 100644 --- a/src/lib/ComfyGraphCanvas.ts +++ b/src/lib/ComfyGraphCanvas.ts @@ -104,7 +104,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { let state = get(queueState); let ss = get(selectionState); - const isRunningNode = node.id == state.runningNodeID + const isExecuting = state.executingNodes.has(node.id); const nodeErrors = this.activeErrors?.errorsByID[node.id]; const isHighlightedNode = this.highlightNodeAndInput && this.highlightNodeAndInput[0].id === node.id; @@ -146,7 +146,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { else if (ss.currentHoveredNodes.has(node.id)) { color = "lightblue"; } - else if (isRunningNode) { + else if (isExecuting) { color = "#0f0"; } @@ -162,7 +162,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { this.drawNodeOutline(node, ctx, size, mouseOver, fgColor, bgColor, color, thickness) } - if (isRunningNode && state.progress) { + if (isExecuting && state.progress) { ctx.fillStyle = "green"; ctx.fillRect(0, 0, size[0] * (state.progress.value / state.progress.max), 6); ctx.fillStyle = bgColor; diff --git a/src/lib/stores/queueState.ts b/src/lib/stores/queueState.ts index f50048a..9e45774 100644 --- a/src/lib/stores/queueState.ts +++ b/src/lib/stores/queueState.ts @@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid"; import workflowState, { type WorkflowError, type WorkflowExecutionError, type WorkflowInstID, type WorkflowValidationError } from "./workflowState"; import configState from "./configState"; import uiQueueState from "./uiQueueState"; +import type { NodeID } from "@litegraph-ts/core"; export type QueueEntryStatus = "success" | "validation_failed" | "error" | "interrupted" | "all_cached" | "unknown"; @@ -81,7 +82,21 @@ export type QueueState = { queuePending: Writable, queueCompleted: Writable, queueRemaining: number | "X" | null; + + /* + * Currently executing node if any + */ runningNodeID: ComfyNodeID | null; + + /* + * Nodes which should be rendered as "executing" in the frontend (green border). + * This includes the running node and all its parent subgraphs + */ + executingNodes: Set; + + /* + * Progress for the current node reported by the frontend + */ progress: Progress | null, /** * If true, user pressed the "Interrupt" button in the frontend. Disable the @@ -98,6 +113,7 @@ const store: Writable = writable({ queueCompleted: writable([]), queueRemaining: null, runningNodeID: null, + executingNodes: new Set(), progress: null, isInterrupting: false }) @@ -272,6 +288,7 @@ function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null) store.update((s) => { s.progress = null; + s.executingNodes.clear(); const [index, entry, queue] = findEntryInPending(promptID); if (runningNodeID != null) { @@ -279,6 +296,17 @@ function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null) entry.nodesRan.add(runningNodeID) } s.runningNodeID = runningNodeID; + + if (entry?.extraData?.workflowID) { + const workflow = workflowState.getWorkflow(entry.extraData.workflowID); + if (workflow != null) { + let node = workflow.graph.getNodeByIdRecursive(s.runningNodeID); + while (node != null) { + s.executingNodes.add(node.id); + node = node.graph?._subgraph_node; + } + } + } } else { // Prompt finished executing. @@ -310,6 +338,7 @@ function executingUpdated(promptID: PromptID, runningNodeID: ComfyNodeID | null) } s.progress = null; s.runningNodeID = null; + s.executingNodes.clear(); } entry_ = entry; return s @@ -334,6 +363,7 @@ function executionCached(promptID: PromptID, nodes: ComfyNodeID[]) { s.isInterrupting = false; // TODO move to start s.progress = null; s.runningNodeID = null; + s.executingNodes.clear(); return s }) } @@ -351,6 +381,7 @@ function executionError(error: ComfyExecutionError): CompletedQueueEntry | null } s.progress = null; s.runningNodeID = null; + s.executingNodes.clear(); return s }) return entry_; @@ -384,6 +415,8 @@ function executionStart(promptID: PromptID) { moveToRunning(index, queue) } s.isInterrupting = false; + s.runningNodeID = null; + s.executingNodes.clear(); return s }) } @@ -448,6 +481,7 @@ function queueCleared(type: QueueItemType) { s.queueRemaining = 0; s.runningNodeID = null; s.progress = null; + s.executingNodes.clear(); } else { s.queueCompleted.set([]) From 32e39c20d6590ab5e762bd18f1c1757e3e59511a Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Thu, 1 Jun 2023 21:32:48 -0500 Subject: [PATCH 09/12] Open combo box at last selected item --- src/lib/widgets/ComboWidget.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte index 672f926..5a64a58 100644 --- a/src/lib/widgets/ComboWidget.svelte +++ b/src/lib/widgets/ComboWidget.svelte @@ -8,7 +8,7 @@ import { type WidgetLayout } from "$lib/stores/layoutStates"; import { get, writable, type Writable } from "svelte/store"; import { isDisabled } from "./utils" - import { getSafetensorsMetadata } from '$lib/utils'; + import { clamp, getSafetensorsMetadata } from '$lib/utils'; export let widget: WidgetLayout | null = null; export let isMobile: boolean = false; let node: ComfyComboNode | null = null; @@ -174,7 +174,7 @@ itemCount={filteredItems.length} {itemSize} overscanCount={5} - scrollToIndex={hoverItemIndex}> + scrollToIndex={activeIndex != null ? clamp(activeIndex + itemsToShow - 1, 0, filteredItems.length-1) : hoverItemIndex}>
Date: Fri, 2 Jun 2023 10:33:44 -0500 Subject: [PATCH 10/12] Fix Vite HMR bug --- src/lib/components/ComfyBoxWorkflowsView.svelte | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/components/ComfyBoxWorkflowsView.svelte b/src/lib/components/ComfyBoxWorkflowsView.svelte index d0eff8d..4fd5430 100644 --- a/src/lib/components/ComfyBoxWorkflowsView.svelte +++ b/src/lib/components/ComfyBoxWorkflowsView.svelte @@ -1,5 +1,11 @@