diff --git a/ts/base_any_input_connected_node.ts b/ts/base_any_input_connected_node.ts index 219e478..357da75 100644 --- a/ts/base_any_input_connected_node.ts +++ b/ts/base_any_input_connected_node.ts @@ -2,7 +2,8 @@ // @ts-ignore import {app} from "../../scripts/app.js"; import { RgthreeBaseNode } from "./base_node.js"; -import type {Vector2, LLink, INodeInputSlot, INodeOutputSlot, LGraphNode as TLGraphNode, LiteGraph as TLiteGraph, IWidget} from './typings/litegraph.js'; +import {rgthree} from "./rgthree.js" +import type {Vector2, LLink, INodeInputSlot, INodeOutputSlot, LGraphNode as TLGraphNode, LiteGraph as TLiteGraph, IWidget, SerializedLGraphNode} from './typings/litegraph.js'; import { PassThroughFollowing, addConnectionLayoutSupport, addMenuItem, filterOutPassthroughNodes, getConnectedInputNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodes, getConnectedOutputNodesAndFilterPassThroughs} from "./utils.js"; declare const LiteGraph: typeof TLiteGraph; @@ -46,11 +47,17 @@ export class BaseAnyInputConnectedNode extends RgthreeBaseNode { override clone() { const cloned = super.clone(); - while (cloned.inputs.length > 1) { - cloned.removeInput(cloned.inputs.length - 1); - } - if (cloned.inputs[0]) { - cloned.inputs[0].label = ''; + // Copying to clipboard (and also, creating node templates) work by cloning nodes and, for some + // reason, it manually manipulates the cloned data. So, we want to keep the present input slots + // so if it's pasted/templatized the data is correct. Otherwise, clear the inputs and so the new + // node is ready to go, fresh. + if (!rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes) { + while (cloned.inputs.length > 1) { + cloned.removeInput(cloned.inputs.length - 1); + } + if (cloned.inputs[0]) { + cloned.inputs[0].label = ''; + } } return cloned; } diff --git a/ts/rgthree.ts b/ts/rgthree.ts index 3531dec..8f40a60 100644 --- a/ts/rgthree.ts +++ b/ts/rgthree.ts @@ -1,7 +1,14 @@ -import { type LGraphCanvas as TLGraphCanvas, type LGraphNode, type SerializedLGraphNode, type serializedLGraph } from "litegraph.js"; +import { + type LGraphCanvas as TLGraphCanvas, + type LGraphNode, + type SerializedLGraphNode, + type serializedLGraph, +} from "litegraph.js"; // @ts-ignore import { app } from "../../scripts/app.js"; // @ts-ignore +import { api } from "../../scripts/api.js"; +// @ts-ignore import { rgthreeConfig } from "./rgthree_config.js"; import { fixBadLinks } from "./link_fixer.js"; import { wait } from "./shared_utils.js"; @@ -94,9 +101,12 @@ export type RgthreeUiMessage = { * A global class as 'rgthree'; exposed on wiindow. Lots can go in here. */ class Rgthree extends EventTarget { - + /** The users' config. */ readonly config = rgthreeConfig; + /** Exposes the ComfyUI api instance on rgthree. */ + readonly api = api; + /** Are any functional keys pressed in this given moment? */ ctrlKey = false; altKey = false; @@ -111,13 +121,227 @@ class Rgthree extends EventTarget { processingQueue = false; + // Comfy/LiteGraph states so nodes and tell what the hell is going on. + canvasCurrentlyCopyingToClipboard = false; + canvasCurrentlyCopyingToClipboardWithMultipleNodes = false; initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff: any = null; - async clearAllMessages() { - let container = document.querySelector(".rgthree-top-messages-container"); - container && (container.innerHTML = ""); + constructor() { + super(); + window.addEventListener("keydown", (e) => { + this.handleKeydown(e); + }); + + window.addEventListener("keyup", (e) => { + this.handleKeyup(e); + }); + + this.initializeGraphAndCanvasHooks(); + this.initializeComfyUIHooks(); + + wait(100).then(() => { + this.injectRgthreeCss(); + }); } + /** + * Initialize a bunch of hooks into LiteGraph itself so we can either keep state or context on + * what's happening so nodes can respond appropriately. This is usually to fix broken assumptions + * in the unowned code [🤮], but sometimes to add features or enhancements too [⭐]. + */ + private async initializeGraphAndCanvasHooks() { + const rgthree = this; + const [canvas, graph] = await Promise.all([waitForCanvas(), waitForGraph()]); + + // [🤮] To mitigate changes from https://github.com/rgthree/rgthree-comfy/issues/69 + // and https://github.com/comfyanonymous/ComfyUI/issues/2193 we can try to store the workflow + // node so our nodes can find the seralized node. Works with method + // `getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff` to find a node + // while serializing. What a way to work around... + const onSerialize = (graph as any).onSerialize; + (graph as any).onSerialize = (data: any) => { + this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = data; + onSerialize?.apply(graph, data); + }; + + // [🤮] Copying to clipboard clones nodes and then manipulats the linking data manually which + // does not allow a node to handle connections. This harms nodes that manually handle inputs, + // like our any-input nodes that may start with one input, and manually add new ones when one is + // attached. + const copyToClipboard = LGraphCanvas.prototype.copyToClipboard; + LGraphCanvas.prototype.copyToClipboard = function (nodes: LGraphNode[]) { + rgthree.canvasCurrentlyCopyingToClipboard = true; + rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes = + Object.values(nodes || this.selected_nodes || []).length > 1; + copyToClipboard.apply(canvas, [...arguments] as any); + rgthree.canvasCurrentlyCopyingToClipboard = false; + rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false; + }; + + // [⭐] Make it so when we add a group, we get to name it immediately. + const onGroupAdd = LGraphCanvas.onGroupAdd; + LGraphCanvas.onGroupAdd = function (...args: any[]) { + onGroupAdd.apply(canvas, [...args] as any); + LGraphCanvas.onShowPropertyEditor( + {}, + null, + null, + null, + graph._groups[graph._groups.length - 1], + ); + }; + } + + /** + * Initialize a bunch of hooks into ComfyUI and/or LiteGraph itself so we can either keep state or + * context on what's happening so nodes can respond appropriately. This is usually to fix broken + * assumptions in the unowned code [🤮], but sometimes to add features or enhancements too [⭐]. + */ + private initializeComfyUIHooks() { + const rgthree = this; + + // Keep state for when the app is queuing the prompt. For instance, this is used for seed to + // understand if we're serializing because we're queueing (and return the random seed to use) or + // for saving the workflow (and keep -1, etc.). + const queuePrompt = app.queuePrompt as Function; + app.queuePrompt = async function () { + rgthree.dispatchEvent(new CustomEvent("queue")); + rgthree.processingQueue = true; + try { + await queuePrompt.apply(app, [...arguments]); + } finally { + rgthree.processingQueue = false; + rgthree.dispatchEvent(new CustomEvent("queue-end")); + } + }; + + // Keep state for when the app is serizalizing the graph to prompt. + const graphToPrompt = app.graphToPrompt as Function; + app.graphToPrompt = async function () { + rgthree.dispatchEvent(new CustomEvent("graph-to-prompt")); + let promise = graphToPrompt.apply(app, [...arguments]); + await promise; + rgthree.dispatchEvent(new CustomEvent("graph-to-prompt-end")); + return promise; + }; + + // Hook into a clean call; allow us to clear and rgthree messages. + const clean = app.clean; + app.clean = function () { + rgthree.clearAllMessages(); + clean && clean.call(app, ...arguments); + }; + + // Hook into a data load, like from an image or JSON drop-in. This is (currently) used to + // monitor for bad linking data. + const loadGraphData = app.loadGraphData; + app.loadGraphData = function (graph: serializedLGraph) { + if (rgthree.monitorLinkTimeout) { + clearTimeout(rgthree.monitorLinkTimeout); + rgthree.monitorLinkTimeout = null; + } + rgthree.clearAllMessages(); + // Try to make a copy to use, because ComfyUI's loadGraphData will modify it. + let graphCopy: serializedLGraph | null; + try { + graphCopy = JSON.parse(JSON.stringify(graph)); + } catch (e) { + graphCopy = null; + } + setTimeout(() => { + const wasLoadingAborted = document + .querySelector(".comfy-modal-content") + ?.textContent?.includes("Loading aborted due"); + const graphToUse = wasLoadingAborted ? graphCopy || graph : app.graph; + const fixBadLinksResult = fixBadLinks(graphToUse); + if (fixBadLinksResult.hasBadLinks) { + rgthree.log( + LogLevel.WARN, + `The workflow you've loaded has corrupt linking data. Open ${ + new URL(location.href).origin + }/extensions/rgthree-comfy/html/links.html to try to fix.`, + ); + if (rgthreeConfig["show_alerts_for_corrupt_workflows"]) { + rgthree.showMessage({ + id: "bad-links", + type: "warn", + message: + "The workflow you've loaded has corrupt linking data that may be able to be fixed.", + actions: [ + { + label: "Open fixer", + href: "/extensions/rgthree-comfy/html/links.html", + }, + { + label: "Fix in place", + href: "/extensions/rgthree-comfy/html/links.html", + callback: (event) => { + event.stopPropagation(); + event.preventDefault(); + if ( + confirm( + "This will attempt to fix in place. Please make sure to have a saved copy of your workflow.", + ) + ) { + try { + const fixBadLinksResult = fixBadLinks(graphToUse, true); + if (!fixBadLinksResult.hasBadLinks) { + rgthree.hideMessage("bad-links"); + alert( + "Success! It's possible some valid links may have been affected. Please check and verify your workflow.", + ); + wasLoadingAborted && app.loadGraphData(fixBadLinksResult.graph); + if ( + rgthreeConfig["monitor_for_corrupt_links"] || + rgthreeConfig["monitor_bad_links"] + ) { + rgthree.monitorLinkTimeout = setTimeout(() => { + rgthree.monitorBadLinks(); + }, 5000); + } + } + } catch (e) { + console.error(e); + alert("Unsuccessful at fixing corrupt data. :("); + rgthree.hideMessage("bad-links"); + } + } + }, + }, + ], + }); + } + } else if ( + rgthreeConfig["monitor_for_corrupt_links"] || + rgthreeConfig["monitor_bad_links"] + ) { + rgthree.monitorLinkTimeout = setTimeout(() => { + rgthree.monitorBadLinks(); + }, 5000); + } + }, 100); + loadGraphData && loadGraphData.call(app, ...arguments); + }; + } + + /** + * [🤮] Finds a node in the currently serializing workflow from the hook setup above. This is to + * mitigate breakages from https://github.com/comfyanonymous/ComfyUI/issues/2193 we can try to + * store the workflow node so our nodes can find the seralized node. + */ + getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff( + node: LGraphNode, + ): SerializedLGraphNode | null { + return ( + this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff?.nodes?.find( + (n: SerializedLGraphNode) => n.id === node.id, + ) ?? null + ); + } + + /** + * Shows a message in the UI. + */ async showMessage(data: RgthreeUiMessage) { let container = document.querySelector(".rgthree-top-messages-container"); if (!container) { @@ -174,6 +398,9 @@ class Rgthree extends EventTarget { } } + /** + * Hides a message in the UI. + */ async hideMessage(id: string) { const msg = document.querySelector(`.rgthree-top-messages-container > [msg-id="${id}"]`); if (msg?.classList.contains("-show")) { @@ -183,7 +410,17 @@ class Rgthree extends EventTarget { msg && msg.remove(); } - /** Handle keydown. Pulled out because sometimes a node will get a keydown before rgthree. */ + /** + * Clears all messages in the UI. + */ + async clearAllMessages() { + let container = document.querySelector(".rgthree-top-messages-container"); + container && (container.innerHTML = ""); + } + + /** + * Handle keydown. Pulled out because sometimes a node will get a keydown before rgthree. + */ handleKeydown(e: KeyboardEvent) { this.ctrlKey = !!e.ctrlKey; this.altKey = !!e.altKey; @@ -193,7 +430,9 @@ class Rgthree extends EventTarget { this.downKeys["^" + e.key.toLocaleUpperCase()] = true; } - /** Handle keyup. Pulled out because sometimes a node will get a keyup before rgthree. */ + /** + * Handle keyup. Pulled out because sometimes a node will get a keyup before rgthree. + */ handleKeyup(e: KeyboardEvent) { this.ctrlKey = !!e.ctrlKey; this.altKey = !!e.altKey; @@ -203,6 +442,9 @@ class Rgthree extends EventTarget { this.downKeys["^" + e.key.toLocaleUpperCase()] = false; } + /** + * Checks if all keys passed in are down. + */ areAllKeysDown(keys: string[], caseSensitive = false) { return keys.every((k) => { if (caseSensitive) { @@ -212,172 +454,9 @@ class Rgthree extends EventTarget { }); } - // This sucks.. but to mitigate changes from https://github.com/rgthree/rgthree-comfy/issues/69 - // and https://github.com/comfyanonymous/ComfyUI/issues/2193 we can try to store the workflow - // node so out nodes can find the seralized node. What a way to work around... - getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node: LGraphNode) : SerializedLGraphNode|null { - return this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff?.nodes?.find( - (n: SerializedLGraphNode) => n.id === node.id, - ) ?? null; - } - - - private async initializeGraphAndCanvasHooks() { - const [canvas, graph] = await Promise.all([waitForCanvas(), waitForGraph()]); - - const onSerialize = (graph as any).onSerialize; - (graph as any).onSerialize = (data: any) => { - // This sucks.. but to mitigate changes from https://github.com/rgthree/rgthree-comfy/issues/69 - // and https://github.com/comfyanonymous/ComfyUI/issues/2193 we can try to store the workflow - // node so out nodes can find the seralized node. What a way to work around... - this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = data; - onSerialize?.apply(graph, data); - }; - - // Make it so when we add a group, we get to name it immediately. - const onGroupAdd = LGraphCanvas.onGroupAdd; - LGraphCanvas.onGroupAdd = function(...args: any[]) { - onGroupAdd.apply(canvas, [...args] as any); - LGraphCanvas.onShowPropertyEditor({}, null, null, null, graph._groups[graph._groups.length - 1]); - } - } - - constructor() { - super(); - window.addEventListener("keydown", (e) => { - this.handleKeydown(e); - }); - - window.addEventListener("keyup", (e) => { - this.handleKeyup(e); - }); - - this.initializeGraphAndCanvasHooks(); - - // Override the loadGraphData so we can check for bad links and ask the user to fix them. - const that = this; - - const queuePrompt = app.queuePrompt as Function; - app.queuePrompt = async function () { - that.dispatchEvent(new CustomEvent("queue")); - that.processingQueue = true; - try { - await queuePrompt.apply(app, [...arguments]); - } finally { - that.processingQueue = false; - that.dispatchEvent(new CustomEvent("queue-end")); - } - }; - - const graphToPrompt = app.graphToPrompt as Function; - app.graphToPrompt = async function () { - that.dispatchEvent(new CustomEvent("graph-to-prompt")); - let promise = graphToPrompt.apply(app, [...arguments]); - await promise; - that.dispatchEvent(new CustomEvent("graph-to-prompt-end")); - return promise; - }; - - const clean = app.clean; - app.clean = function () { - that.clearAllMessages(); - clean && clean.call(app, ...arguments); - }; - - const loadGraphData = app.loadGraphData; - app.loadGraphData = function (graph: serializedLGraph) { - if (that.monitorLinkTimeout) { - clearTimeout(that.monitorLinkTimeout); - that.monitorLinkTimeout = null; - } - that.clearAllMessages(); - // Try to make a copy to use, because ComfyUI's loadGraphData will modify it. - let graphCopy: serializedLGraph | null; - try { - graphCopy = JSON.parse(JSON.stringify(graph)); - } catch (e) { - graphCopy = null; - } - setTimeout(() => { - const wasLoadingAborted = document - .querySelector(".comfy-modal-content") - ?.textContent?.includes("Loading aborted due"); - const graphToUse = wasLoadingAborted ? graphCopy || graph : app.graph; - const fixBadLinksResult = fixBadLinks(graphToUse); - if (fixBadLinksResult.hasBadLinks) { - that.log( - LogLevel.WARN, - `The workflow you've loaded has corrupt linking data. Open ${ - new URL(location.href).origin - }/extensions/rgthree-comfy/html/links.html to try to fix.`, - ); - if (rgthreeConfig["show_alerts_for_corrupt_workflows"]) { - that.showMessage({ - id: "bad-links", - type: "warn", - message: - "The workflow you've loaded has corrupt linking data that may be able to be fixed.", - actions: [ - { - label: "Open fixer", - href: "/extensions/rgthree-comfy/html/links.html", - }, - { - label: "Fix in place", - href: "/extensions/rgthree-comfy/html/links.html", - callback: (event) => { - event.stopPropagation(); - event.preventDefault(); - if ( - confirm( - "This will attempt to fix in place. Please make sure to have a saved copy of your workflow.", - ) - ) { - try { - const fixBadLinksResult = fixBadLinks(graphToUse, true); - if (!fixBadLinksResult.hasBadLinks) { - that.hideMessage("bad-links"); - alert( - "Success! It's possible some valid links may have been affected. Please check and verify your workflow.", - ); - wasLoadingAborted && app.loadGraphData(fixBadLinksResult.graph); - if ( - rgthreeConfig["monitor_for_corrupt_links"] || - rgthreeConfig["monitor_bad_links"] - ) { - that.monitorLinkTimeout = setTimeout(() => { - that.monitorBadLinks(); - }, 5000); - } - } - } catch (e) { - console.error(e); - alert("Unsuccessful at fixing corrupt data. :("); - that.hideMessage("bad-links"); - } - } - }, - }, - ], - }); - } - } else if ( - rgthreeConfig["monitor_for_corrupt_links"] || - rgthreeConfig["monitor_bad_links"] - ) { - that.monitorLinkTimeout = setTimeout(() => { - that.monitorBadLinks(); - }, 5000); - } - }, 100); - loadGraphData && loadGraphData.call(app, ...arguments); - }; - - wait(100).then(() => { - this.injectRgthreeCss(); - }); - } - + /** + * Injects the rgthree.css file into the app. + */ private injectRgthreeCss() { let link = document.createElement("link"); link.rel = "stylesheet"; @@ -403,9 +482,9 @@ class Rgthree extends EventTarget { if (badLinksFound.hasBadLinks && !this.monitorBadLinksAlerted) { this.monitorBadLinksAlerted = true; alert( - `Problematic links just found in live data. Can you save your workflow and file a bug with ` - + `the last few steps you took to trigger this at ` - + `https://github.com/rgthree/rgthree-comfy/issues. Thank you!`, + `Problematic links just found in live data. Can you save your workflow and file a bug with ` + + `the last few steps you took to trigger this at ` + + `https://github.com/rgthree/rgthree-comfy/issues. Thank you!`, ); } else if (!badLinksFound.hasBadLinks) { // Clear the alert once fixed so we can alert again. diff --git a/ts/typings/litegraph.d.ts b/ts/typings/litegraph.d.ts index 7b61e4b..99fe9d9 100644 --- a/ts/typings/litegraph.d.ts +++ b/ts/typings/litegraph.d.ts @@ -1462,7 +1462,8 @@ export declare class LGraphCanvas { /** process a key event */ processKey(e: KeyboardEvent): boolean | undefined; - copyToClipboard(): void; + // @rgthree - added param + copyToClipboard(nodes: LGraphNode[]|{[key:number]:LGraphNode}): void; pasteFromClipboard(): void; processDrop(e: DragEvent): void; checkDropItem(e: DragEvent): void; diff --git a/web/base_any_input_connected_node.js b/web/base_any_input_connected_node.js index 28d3323..d21a77d 100644 --- a/web/base_any_input_connected_node.js +++ b/web/base_any_input_connected_node.js @@ -1,5 +1,6 @@ import { app } from "../../scripts/app.js"; import { RgthreeBaseNode } from "./base_node.js"; +import { rgthree } from "./rgthree.js"; import { PassThroughFollowing, addConnectionLayoutSupport, addMenuItem, getConnectedInputNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedOutputNodes, getConnectedOutputNodesAndFilterPassThroughs } from "./utils.js"; export class BaseAnyInputConnectedNode extends RgthreeBaseNode { constructor(title = BaseAnyInputConnectedNode.title) { @@ -24,11 +25,13 @@ export class BaseAnyInputConnectedNode extends RgthreeBaseNode { } clone() { const cloned = super.clone(); - while (cloned.inputs.length > 1) { - cloned.removeInput(cloned.inputs.length - 1); - } - if (cloned.inputs[0]) { - cloned.inputs[0].label = ''; + if (!rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes) { + while (cloned.inputs.length > 1) { + cloned.removeInput(cloned.inputs.length - 1); + } + if (cloned.inputs[0]) { + cloned.inputs[0].label = ''; + } } return cloned; } diff --git a/web/rgthree.js b/web/rgthree.js index cb72e2a..ff2465c 100644 --- a/web/rgthree.js +++ b/web/rgthree.js @@ -1,4 +1,5 @@ import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; import { rgthreeConfig } from "./rgthree_config.js"; import { fixBadLinks } from "./link_fixer.js"; import { wait } from "./shared_utils.js"; @@ -60,9 +61,162 @@ class LogSession { } } class Rgthree extends EventTarget { - async clearAllMessages() { - let container = document.querySelector(".rgthree-top-messages-container"); - container && (container.innerHTML = ""); + constructor() { + super(); + this.config = rgthreeConfig; + this.api = api; + this.ctrlKey = false; + this.altKey = false; + this.metaKey = false; + this.shiftKey = false; + this.downKeys = {}; + this.logger = new LogSession("[rgthree]"); + this.monitorBadLinksAlerted = false; + this.monitorLinkTimeout = null; + this.processingQueue = false; + this.canvasCurrentlyCopyingToClipboard = false; + this.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false; + this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = null; + window.addEventListener("keydown", (e) => { + this.handleKeydown(e); + }); + window.addEventListener("keyup", (e) => { + this.handleKeyup(e); + }); + this.initializeGraphAndCanvasHooks(); + this.initializeComfyUIHooks(); + wait(100).then(() => { + this.injectRgthreeCss(); + }); + } + async initializeGraphAndCanvasHooks() { + const rgthree = this; + const [canvas, graph] = await Promise.all([waitForCanvas(), waitForGraph()]); + const onSerialize = graph.onSerialize; + graph.onSerialize = (data) => { + this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = data; + onSerialize === null || onSerialize === void 0 ? void 0 : onSerialize.apply(graph, data); + }; + const copyToClipboard = LGraphCanvas.prototype.copyToClipboard; + LGraphCanvas.prototype.copyToClipboard = function (nodes) { + rgthree.canvasCurrentlyCopyingToClipboard = true; + rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes = + Object.values(nodes || this.selected_nodes || []).length > 1; + copyToClipboard.apply(canvas, [...arguments]); + rgthree.canvasCurrentlyCopyingToClipboard = false; + rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false; + }; + const onGroupAdd = LGraphCanvas.onGroupAdd; + LGraphCanvas.onGroupAdd = function (...args) { + onGroupAdd.apply(canvas, [...args]); + LGraphCanvas.onShowPropertyEditor({}, null, null, null, graph._groups[graph._groups.length - 1]); + }; + } + initializeComfyUIHooks() { + const rgthree = this; + const queuePrompt = app.queuePrompt; + app.queuePrompt = async function () { + rgthree.dispatchEvent(new CustomEvent("queue")); + rgthree.processingQueue = true; + try { + await queuePrompt.apply(app, [...arguments]); + } + finally { + rgthree.processingQueue = false; + rgthree.dispatchEvent(new CustomEvent("queue-end")); + } + }; + const graphToPrompt = app.graphToPrompt; + app.graphToPrompt = async function () { + rgthree.dispatchEvent(new CustomEvent("graph-to-prompt")); + let promise = graphToPrompt.apply(app, [...arguments]); + await promise; + rgthree.dispatchEvent(new CustomEvent("graph-to-prompt-end")); + return promise; + }; + const clean = app.clean; + app.clean = function () { + rgthree.clearAllMessages(); + clean && clean.call(app, ...arguments); + }; + const loadGraphData = app.loadGraphData; + app.loadGraphData = function (graph) { + if (rgthree.monitorLinkTimeout) { + clearTimeout(rgthree.monitorLinkTimeout); + rgthree.monitorLinkTimeout = null; + } + rgthree.clearAllMessages(); + let graphCopy; + try { + graphCopy = JSON.parse(JSON.stringify(graph)); + } + catch (e) { + graphCopy = null; + } + setTimeout(() => { + var _a, _b; + const wasLoadingAborted = (_b = (_a = document + .querySelector(".comfy-modal-content")) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.includes("Loading aborted due"); + const graphToUse = wasLoadingAborted ? graphCopy || graph : app.graph; + const fixBadLinksResult = fixBadLinks(graphToUse); + if (fixBadLinksResult.hasBadLinks) { + rgthree.log(LogLevel.WARN, `The workflow you've loaded has corrupt linking data. Open ${new URL(location.href).origin}/extensions/rgthree-comfy/html/links.html to try to fix.`); + if (rgthreeConfig["show_alerts_for_corrupt_workflows"]) { + rgthree.showMessage({ + id: "bad-links", + type: "warn", + message: "The workflow you've loaded has corrupt linking data that may be able to be fixed.", + actions: [ + { + label: "Open fixer", + href: "/extensions/rgthree-comfy/html/links.html", + }, + { + label: "Fix in place", + href: "/extensions/rgthree-comfy/html/links.html", + callback: (event) => { + event.stopPropagation(); + event.preventDefault(); + if (confirm("This will attempt to fix in place. Please make sure to have a saved copy of your workflow.")) { + try { + const fixBadLinksResult = fixBadLinks(graphToUse, true); + if (!fixBadLinksResult.hasBadLinks) { + rgthree.hideMessage("bad-links"); + alert("Success! It's possible some valid links may have been affected. Please check and verify your workflow."); + wasLoadingAborted && app.loadGraphData(fixBadLinksResult.graph); + if (rgthreeConfig["monitor_for_corrupt_links"] || + rgthreeConfig["monitor_bad_links"]) { + rgthree.monitorLinkTimeout = setTimeout(() => { + rgthree.monitorBadLinks(); + }, 5000); + } + } + } + catch (e) { + console.error(e); + alert("Unsuccessful at fixing corrupt data. :("); + rgthree.hideMessage("bad-links"); + } + } + }, + }, + ], + }); + } + } + else if (rgthreeConfig["monitor_for_corrupt_links"] || + rgthreeConfig["monitor_bad_links"]) { + rgthree.monitorLinkTimeout = setTimeout(() => { + rgthree.monitorBadLinks(); + }, 5000); + } + }, 100); + loadGraphData && loadGraphData.call(app, ...arguments); + }; + } + getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node) { + var _a, _b, _c; + return ((_c = (_b = (_a = this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff) === null || _a === void 0 ? void 0 : _a.nodes) === null || _b === void 0 ? void 0 : _b.find((n) => n.id === node.id)) !== null && _c !== void 0 ? _c : null); } async showMessage(data) { let container = document.querySelector(".rgthree-top-messages-container"); @@ -118,6 +272,10 @@ class Rgthree extends EventTarget { } msg && msg.remove(); } + async clearAllMessages() { + let container = document.querySelector(".rgthree-top-messages-container"); + container && (container.innerHTML = ""); + } handleKeydown(e) { this.ctrlKey = !!e.ctrlKey; this.altKey = !!e.altKey; @@ -142,147 +300,6 @@ class Rgthree extends EventTarget { return rgthree.downKeys[k.trim().toUpperCase()]; }); } - getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node) { - var _a, _b, _c; - return (_c = (_b = (_a = this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff) === null || _a === void 0 ? void 0 : _a.nodes) === null || _b === void 0 ? void 0 : _b.find((n) => n.id === node.id)) !== null && _c !== void 0 ? _c : null; - } - async initializeGraphAndCanvasHooks() { - const [canvas, graph] = await Promise.all([waitForCanvas(), waitForGraph()]); - const onSerialize = graph.onSerialize; - graph.onSerialize = (data) => { - this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = data; - onSerialize === null || onSerialize === void 0 ? void 0 : onSerialize.apply(graph, data); - }; - const onGroupAdd = LGraphCanvas.onGroupAdd; - LGraphCanvas.onGroupAdd = function (...args) { - onGroupAdd.apply(canvas, [...args]); - LGraphCanvas.onShowPropertyEditor({}, null, null, null, graph._groups[graph._groups.length - 1]); - }; - } - constructor() { - super(); - this.config = rgthreeConfig; - this.ctrlKey = false; - this.altKey = false; - this.metaKey = false; - this.shiftKey = false; - this.downKeys = {}; - this.logger = new LogSession("[rgthree]"); - this.monitorBadLinksAlerted = false; - this.monitorLinkTimeout = null; - this.processingQueue = false; - this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = null; - window.addEventListener("keydown", (e) => { - this.handleKeydown(e); - }); - window.addEventListener("keyup", (e) => { - this.handleKeyup(e); - }); - this.initializeGraphAndCanvasHooks(); - const that = this; - const queuePrompt = app.queuePrompt; - app.queuePrompt = async function () { - that.dispatchEvent(new CustomEvent("queue")); - that.processingQueue = true; - try { - await queuePrompt.apply(app, [...arguments]); - } - finally { - that.processingQueue = false; - that.dispatchEvent(new CustomEvent("queue-end")); - } - }; - const graphToPrompt = app.graphToPrompt; - app.graphToPrompt = async function () { - that.dispatchEvent(new CustomEvent("graph-to-prompt")); - let promise = graphToPrompt.apply(app, [...arguments]); - await promise; - that.dispatchEvent(new CustomEvent("graph-to-prompt-end")); - return promise; - }; - const clean = app.clean; - app.clean = function () { - that.clearAllMessages(); - clean && clean.call(app, ...arguments); - }; - const loadGraphData = app.loadGraphData; - app.loadGraphData = function (graph) { - if (that.monitorLinkTimeout) { - clearTimeout(that.monitorLinkTimeout); - that.monitorLinkTimeout = null; - } - that.clearAllMessages(); - let graphCopy; - try { - graphCopy = JSON.parse(JSON.stringify(graph)); - } - catch (e) { - graphCopy = null; - } - setTimeout(() => { - var _a, _b; - const wasLoadingAborted = (_b = (_a = document - .querySelector(".comfy-modal-content")) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.includes("Loading aborted due"); - const graphToUse = wasLoadingAborted ? graphCopy || graph : app.graph; - const fixBadLinksResult = fixBadLinks(graphToUse); - if (fixBadLinksResult.hasBadLinks) { - that.log(LogLevel.WARN, `The workflow you've loaded has corrupt linking data. Open ${new URL(location.href).origin}/extensions/rgthree-comfy/html/links.html to try to fix.`); - if (rgthreeConfig["show_alerts_for_corrupt_workflows"]) { - that.showMessage({ - id: "bad-links", - type: "warn", - message: "The workflow you've loaded has corrupt linking data that may be able to be fixed.", - actions: [ - { - label: "Open fixer", - href: "/extensions/rgthree-comfy/html/links.html", - }, - { - label: "Fix in place", - href: "/extensions/rgthree-comfy/html/links.html", - callback: (event) => { - event.stopPropagation(); - event.preventDefault(); - if (confirm("This will attempt to fix in place. Please make sure to have a saved copy of your workflow.")) { - try { - const fixBadLinksResult = fixBadLinks(graphToUse, true); - if (!fixBadLinksResult.hasBadLinks) { - that.hideMessage("bad-links"); - alert("Success! It's possible some valid links may have been affected. Please check and verify your workflow."); - wasLoadingAborted && app.loadGraphData(fixBadLinksResult.graph); - if (rgthreeConfig["monitor_for_corrupt_links"] || - rgthreeConfig["monitor_bad_links"]) { - that.monitorLinkTimeout = setTimeout(() => { - that.monitorBadLinks(); - }, 5000); - } - } - } - catch (e) { - console.error(e); - alert("Unsuccessful at fixing corrupt data. :("); - that.hideMessage("bad-links"); - } - } - }, - }, - ], - }); - } - } - else if (rgthreeConfig["monitor_for_corrupt_links"] || - rgthreeConfig["monitor_bad_links"]) { - that.monitorLinkTimeout = setTimeout(() => { - that.monitorBadLinks(); - }, 5000); - } - }, 100); - loadGraphData && loadGraphData.call(app, ...arguments); - }; - wait(100).then(() => { - this.injectRgthreeCss(); - }); - } injectRgthreeCss() { let link = document.createElement("link"); link.rel = "stylesheet"; @@ -303,9 +320,9 @@ class Rgthree extends EventTarget { const badLinksFound = fixBadLinks(app.graph); if (badLinksFound.hasBadLinks && !this.monitorBadLinksAlerted) { this.monitorBadLinksAlerted = true; - alert(`Problematic links just found in live data. Can you save your workflow and file a bug with ` - + `the last few steps you took to trigger this at ` - + `https://github.com/rgthree/rgthree-comfy/issues. Thank you!`); + alert(`Problematic links just found in live data. Can you save your workflow and file a bug with ` + + `the last few steps you took to trigger this at ` + + `https://github.com/rgthree/rgthree-comfy/issues. Thank you!`); } else if (!badLinksFound.hasBadLinks) { this.monitorBadLinksAlerted = false;