From 10268825d96146c5a235bc8e412418b6996be299 Mon Sep 17 00:00:00 2001 From: bennykok Date: Wed, 14 Aug 2024 11:09:58 -0700 Subject: [PATCH] feat: support new frontend! --- custom_routes.py | 47 +- requirements.txt | 1 + web-plugin/index.js | 2196 +++++++++++++++++++++++-------------------- 3 files changed, 1203 insertions(+), 1041 deletions(-) diff --git a/custom_routes.py b/custom_routes.py index 362b159..e397ee7 100644 --- a/custom_routes.py +++ b/custom_routes.py @@ -17,12 +17,13 @@ from urllib.parse import quote import threading import hashlib import aiohttp +from aiohttp import ClientSession, web import aiofiles from typing import Dict, List, Union, Any, Optional from PIL import Image import copy import struct -from aiohttp import ClientError +from aiohttp import web, ClientSession, ClientError import atexit # Global session @@ -835,7 +836,51 @@ async def send(event, data, sid=None): except Exception as e: logger.info(f"Exception: {e}") traceback.print_exc() + +@server.PromptServer.instance.routes.get('/comfydeploy/{tail:.*}') +@server.PromptServer.instance.routes.post('/comfydeploy/{tail:.*}') +async def proxy_to_comfydeploy(request): + # Get the base URL + base_url = f'https://www.comfydeploy.com/{request.match_info["tail"]}' + + # Get all query parameters + query_params = request.query_string + + # Construct the full target URL with query parameters + target_url = f"{base_url}?{query_params}" if query_params else base_url + + print(f"Proxying request to: {target_url}") + try: + # Create a new ClientSession for each request + async with ClientSession() as client_session: + # Forward the request + client_req = await client_session.request( + method=request.method, + url=target_url, + headers={k: v for k, v in request.headers.items() if k.lower() not in ('host', 'content-length')}, + data=await request.read(), + allow_redirects=False, + ) + + # Read the entire response content + content = await client_req.read() + + # Try to decode the content as JSON + try: + json_data = json.loads(content) + # If successful, return a JSON response + return web.json_response(json_data, status=client_req.status) + except json.JSONDecodeError: + # If it's not valid JSON, return the content as-is + return web.Response(body=content, status=client_req.status, headers=client_req.headers) + + except ClientError as e: + print(f"Client error occurred while proxying request: {str(e)}") + return web.Response(status=502, text=f"Bad Gateway: {str(e)}") + except Exception as e: + print(f"Error occurred while proxying request: {str(e)}") + return web.Response(status=500, text=f"Internal Server Error: {str(e)}") prompt_server = server.PromptServer.instance diff --git a/requirements.txt b/requirements.txt index b5a5491..f1d3a61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ aiofiles pydantic opencv-python imageio-ffmpeg +brotli # logfire \ No newline at end of file diff --git a/web-plugin/index.js b/web-plugin/index.js index 3ab28ab..4d2c82c 100644 --- a/web-plugin/index.js +++ b/web-plugin/index.js @@ -2,437 +2,403 @@ import { app } from "./app.js"; import { api } from "./api.js"; import { ComfyWidgets, LGraphNode } from "./widgets.js"; import { generateDependencyGraph } from "https://esm.sh/comfyui-json@0.1.25"; +import { ComfyDeploy } from "https://esm.sh/comfydeploy@0.0.19-beta.30"; const loadingIcon = ``; function sendEventToCD(event, data) { - const message = { - type: event, - data: data, - }; - window.parent.postMessage(JSON.stringify(message), "*"); + const message = { + type: event, + data: data, + }; + window.parent.postMessage(JSON.stringify(message), "*"); } function dispatchAPIEventData(data) { - const msg = JSON.parse(data); + const msg = JSON.parse(data); - // Custom parse error - if (msg.error) { - let message = msg.error.message; - if (msg.error.details) message += ": " + msg.error.details; - for (const [nodeID, nodeError] of Object.entries(msg.node_errors)) { - message += "\n" + nodeError.class_type + ":"; - for (const errorReason of nodeError.errors) { - message += - "\n - " + - errorReason.message + - ": " + - errorReason.details; - } - } - - app.ui.dialog.show(message); - if (msg.node_errors) { - app.lastNodeErrors = msg.node_errors; - app.canvas.draw(true, true); - } + // Custom parse error + if (msg.error) { + let message = msg.error.message; + if (msg.error.details) message += ": " + msg.error.details; + for (const [nodeID, nodeError] of Object.entries(msg.node_errors)) { + message += "\n" + nodeError.class_type + ":"; + for (const errorReason of nodeError.errors) { + message += + "\n - " + errorReason.message + ": " + errorReason.details; + } } - switch (msg.event) { - case "error": - break; - case "status": - if (msg.data.sid) { - // this.clientId = msg.data.sid; - // window.name = this.clientId; // use window name so it isnt reused when duplicating tabs - // sessionStorage.setItem("clientId", this.clientId); // store in session storage so duplicate tab can load correct workflow - } - api.dispatchEvent( - new CustomEvent("status", { detail: msg.data.status }) - ); - break; - case "progress": - api.dispatchEvent( - new CustomEvent("progress", { detail: msg.data }) - ); - break; - case "executing": - api.dispatchEvent( - new CustomEvent("executing", { detail: msg.data.node }) - ); - break; - case "executed": - api.dispatchEvent( - new CustomEvent("executed", { detail: msg.data }) - ); - break; - case "execution_start": - api.dispatchEvent( - new CustomEvent("execution_start", { detail: msg.data }) - ); - break; - case "execution_error": - api.dispatchEvent( - new CustomEvent("execution_error", { detail: msg.data }) - ); - break; - case "execution_cached": - api.dispatchEvent( - new CustomEvent("execution_cached", { detail: msg.data }) - ); - break; - default: - api.dispatchEvent(new CustomEvent(msg.type, { detail: msg.data })); - // default: - // if (this.#registered.has(msg.type)) { - // } else { - // throw new Error(`Unknown message type ${msg.type}`); - // } + app.ui.dialog.show(message); + if (msg.node_errors) { + app.lastNodeErrors = msg.node_errors; + app.canvas.draw(true, true); } + } + + switch (msg.event) { + case "error": + break; + case "status": + if (msg.data.sid) { + // this.clientId = msg.data.sid; + // window.name = this.clientId; // use window name so it isnt reused when duplicating tabs + // sessionStorage.setItem("clientId", this.clientId); // store in session storage so duplicate tab can load correct workflow + } + api.dispatchEvent(new CustomEvent("status", { detail: msg.data.status })); + break; + case "progress": + api.dispatchEvent(new CustomEvent("progress", { detail: msg.data })); + break; + case "executing": + api.dispatchEvent( + new CustomEvent("executing", { detail: msg.data.node }), + ); + break; + case "executed": + api.dispatchEvent(new CustomEvent("executed", { detail: msg.data })); + break; + case "execution_start": + api.dispatchEvent( + new CustomEvent("execution_start", { detail: msg.data }), + ); + break; + case "execution_error": + api.dispatchEvent( + new CustomEvent("execution_error", { detail: msg.data }), + ); + break; + case "execution_cached": + api.dispatchEvent( + new CustomEvent("execution_cached", { detail: msg.data }), + ); + break; + default: + api.dispatchEvent(new CustomEvent(msg.type, { detail: msg.data })); + // default: + // if (this.#registered.has(msg.type)) { + // } else { + // throw new Error(`Unknown message type ${msg.type}`); + // } + } } /** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/ /** @type {ComfyExtension} */ const ext = { - name: "BennyKok.ComfyUIDeploy", + name: "BennyKok.ComfyUIDeploy", - init(app) { - addButton(); + init(app) { + addButton(); - const queryParams = new URLSearchParams(window.location.search); - const workflow_version_id = queryParams.get("workflow_version_id"); - const auth_token = queryParams.get("auth_token"); - const org_display = queryParams.get("org_display"); - const origin = queryParams.get("origin"); - const workspace_mode = queryParams.get("workspace_mode"); + const queryParams = new URLSearchParams(window.location.search); + const workflow_version_id = queryParams.get("workflow_version_id"); + const auth_token = queryParams.get("auth_token"); + const org_display = queryParams.get("org_display"); + const origin = queryParams.get("origin"); + const workspace_mode = queryParams.get("workspace_mode"); - if (workspace_mode) { - document.querySelector(".comfy-menu").style.display = "none"; + if (workspace_mode) { + document.querySelector(".comfy-menu").style.display = "none"; - sendEventToCD("cd_plugin_onInit"); + sendEventToCD("cd_plugin_onInit"); - app.queuePrompt = ((originalFunction) => async () => { - // const prompt = await app.graphToPrompt(); - sendEventToCD("cd_plugin_onQueuePromptTrigger"); - })(app.queuePrompt); + app.queuePrompt = ((originalFunction) => async () => { + // const prompt = await app.graphToPrompt(); + sendEventToCD("cd_plugin_onQueuePromptTrigger"); + })(app.queuePrompt); - // // Intercept the onkeydown event - // window.addEventListener( - // "keydown", - // (event) => { - // // Check for specific keys if necessary - // console.log("hi"); - // if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { - // event.preventDefault(); - // event.stopImmediatePropagation(); - // event.stopPropagation(); - // sendEventToCD("cd_plugin_onQueuePrompt", prompt); - // } - // }, - // true, - // ); - } + // // Intercept the onkeydown event + // window.addEventListener( + // "keydown", + // (event) => { + // // Check for specific keys if necessary + // console.log("hi"); + // if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { + // event.preventDefault(); + // event.stopImmediatePropagation(); + // event.stopPropagation(); + // sendEventToCD("cd_plugin_onQueuePrompt", prompt); + // } + // }, + // true, + // ); + } - const data = getData(); - let endpoint = data.endpoint; - let apiKey = data.apiKey; + const data = getData(); + let endpoint = data.endpoint; + let apiKey = data.apiKey; - // If there is auth token override it - if (auth_token) { - apiKey = auth_token; - endpoint = origin; - saveData({ - displayName: org_display, - endpoint: origin, - apiKey: auth_token, - displayName: org_display, - environment: "cloud", - }); - localStorage.setItem("comfy_deploy_env", "cloud"); - } + // If there is auth token override it + if (auth_token) { + apiKey = auth_token; + endpoint = origin; + saveData({ + displayName: org_display, + endpoint: origin, + apiKey: auth_token, + displayName: org_display, + environment: "cloud", + }); + localStorage.setItem("comfy_deploy_env", "cloud"); + } - if (!workflow_version_id) { - console.error( - "No workflow_version_id provided in query parameters." - ); - } else { - loadingDialog.showLoading( - "Loading workflow from " + org_display, - "Please wait..." - ); - fetch(endpoint + "/api/workflow-version/" + workflow_version_id, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + apiKey, - }, - }) - .then(async (res) => { - const data = await res.json(); - const { workflow, workflow_id, error } = data; - if (error) { - infoDialog.showMessage( - "Unable to load this workflow", - error - ); - return; - } + if (!workflow_version_id) { + console.error("No workflow_version_id provided in query parameters."); + } else { + loadingDialog.showLoading( + "Loading workflow from " + org_display, + "Please wait...", + ); + fetch(endpoint + "/api/workflow-version/" + workflow_version_id, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + apiKey, + }, + }) + .then(async (res) => { + const data = await res.json(); + const { workflow, workflow_id, error } = data; + if (error) { + infoDialog.showMessage("Unable to load this workflow", error); + return; + } - // // Adding a delay to wait for the intial graph to load - // await new Promise((resolve) => setTimeout(resolve, 2000)); + // // Adding a delay to wait for the intial graph to load + // await new Promise((resolve) => setTimeout(resolve, 2000)); - workflow?.nodes.forEach((x) => { - if (x?.type === "ComfyDeploy") { - x.widgets_values[1] = workflow_id; - // x.widgets_values[2] = workflow_version.version; - } - }); - - /** @type {LGraph} */ - app.loadGraphData(workflow); - }) - .catch((e) => infoDialog.showMessage("Error", e.message)) - .finally(() => { - loadingDialog.close(); - window.history.replaceState( - {}, - document.title, - window.location.pathname - ); - }); - } - }, - - registerCustomNodes() { - /** @type {LGraphNode}*/ - class ComfyDeploy { - color = LGraphCanvas.node_colors.yellow.color; - bgcolor = LGraphCanvas.node_colors.yellow.bgcolor; - groupcolor = LGraphCanvas.node_colors.yellow.groupcolor; - constructor() { - if (!this.properties) { - this.properties = {}; - this.properties.workflow_name = ""; - this.properties.workflow_id = ""; - this.properties.version = ""; - } - - ComfyWidgets.STRING( - this, - "workflow_name", - [ - "", - { - default: this.properties.workflow_name, - multiline: false, - }, - ], - app - ); - - ComfyWidgets.STRING( - this, - "workflow_id", - [ - "", - { - default: this.properties.workflow_id, - multiline: false, - }, - ], - app - ); - - ComfyWidgets.STRING( - this, - "version", - [ - "", - { default: this.properties.version, multiline: false }, - ], - app - ); - - // this.widgets.forEach((w) => { - // // w.computeSize = () => [200,10] - // w.computedHeight = 2; - // }) - - this.widgets_start_y = 10; - this.setSize(this.computeSize()); - - // const config = { }; - - // console.log(this); - this.serialize_widgets = true; - this.isVirtualNode = true; + workflow?.nodes.forEach((x) => { + if (x?.type === "ComfyDeploy") { + x.widgets_values[1] = workflow_id; + // x.widgets_values[2] = workflow_version.version; } + }); + + /** @type {LGraph} */ + app.loadGraphData(workflow); + }) + .catch((e) => infoDialog.showMessage("Error", e.message)) + .finally(() => { + loadingDialog.close(); + window.history.replaceState( + {}, + document.title, + window.location.pathname, + ); + }); + } + }, + + registerCustomNodes() { + /** @type {LGraphNode}*/ + class ComfyDeploy { + color = LGraphCanvas.node_colors.yellow.color; + bgcolor = LGraphCanvas.node_colors.yellow.bgcolor; + groupcolor = LGraphCanvas.node_colors.yellow.groupcolor; + constructor() { + if (!this.properties) { + this.properties = {}; + this.properties.workflow_name = ""; + this.properties.workflow_id = ""; + this.properties.version = ""; } - // Load default visibility - - LiteGraph.registerNodeType( - "ComfyDeploy", - Object.assign(ComfyDeploy, { - title_mode: LiteGraph.NORMAL_TITLE, - title: "Comfy Deploy", - collapsable: true, - }) + ComfyWidgets.STRING( + this, + "workflow_name", + [ + "", + { + default: this.properties.workflow_name, + multiline: false, + }, + ], + app, ); - ComfyDeploy.category = "deploy"; - }, + ComfyWidgets.STRING( + this, + "workflow_id", + [ + "", + { + default: this.properties.workflow_id, + multiline: false, + }, + ], + app, + ); - async setup() { - // const graphCanvas = document.getElementById("graph-canvas"); + ComfyWidgets.STRING( + this, + "version", + ["", { default: this.properties.version, multiline: false }], + app, + ); - window.addEventListener("message", async (event) => { - // console.log("message", event); - try { - const message = JSON.parse(event.data); - if (message.type === "graph_load") { - const comfyUIWorkflow = message.data; - // console.log("recieved: ", comfyUIWorkflow); - // Assuming there's a method to load the workflow data into the ComfyUI - // This part of the code would depend on how the ComfyUI expects to receive and process the workflow data - // For demonstration, let's assume there's a loadWorkflow method in the ComfyUI API - if (comfyUIWorkflow && app && app.loadGraphData) { - console.log("loadGraphData"); - app.loadGraphData(comfyUIWorkflow); - } - } else if (message.type === "deploy") { - // deployWorkflow(); - const prompt = await app.graphToPrompt(); - // api.handlePromptGenerated(prompt); - sendEventToCD("cd_plugin_onDeployChanges", prompt); - } else if (message.type === "queue_prompt") { - const prompt = await app.graphToPrompt(); - if (typeof api.handlePromptGenerated === "function") { - api.handlePromptGenerated(prompt); - } else { - console.warn( - "api.handlePromptGenerated is not a function" - ); - } - sendEventToCD("cd_plugin_onQueuePrompt", prompt); - } else if (message.type === "get_prompt") { - const prompt = await app.graphToPrompt(); - sendEventToCD("cd_plugin_onGetPrompt", prompt); - } else if (message.type === "event") { - dispatchAPIEventData(message.data); - } else if (message.type === "add_node") { - console.log("add node", message.data); - app.graph.beforeChange(); - var node = LiteGraph.createNode(message.data.type); - node.configure({ - widgets_values: message.data.widgets_values, - }); + // this.widgets.forEach((w) => { + // // w.computeSize = () => [200,10] + // w.computedHeight = 2; + // }) - console.log("node", node); + this.widgets_start_y = 10; + this.setSize(this.computeSize()); - const graphMouse = app.canvas.graph_mouse; + // const config = { }; - node.pos = [graphMouse[0], graphMouse[1]]; + // console.log(this); + this.serialize_widgets = true; + this.isVirtualNode = true; + } + } - app.graph.add(node); - app.graph.afterChange(); - } else if (message.type === "zoom_to_node") { - const nodeId = message.data.nodeId; - const position = message.data.position; + // Load default visibility - const node = app.graph.getNodeById(nodeId); - if (!node) return; + LiteGraph.registerNodeType( + "ComfyDeploy", + Object.assign(ComfyDeploy, { + title_mode: LiteGraph.NORMAL_TITLE, + title: "Comfy Deploy", + collapsable: true, + }), + ); - const canvas = app.canvas; - const targetScale = 1; - const targetOffsetX = - canvas.canvas.width / 4 - - position[0] - - node.size[0] / 2; - const targetOffsetY = - canvas.canvas.height / 4 - - position[1] - - node.size[1] / 2; + ComfyDeploy.category = "deploy"; + }, - const startScale = canvas.ds.scale; - const startOffsetX = canvas.ds.offset[0]; - const startOffsetY = canvas.ds.offset[1]; + async setup() { + // const graphCanvas = document.getElementById("graph-canvas"); - const duration = 400; // Animation duration in milliseconds - const startTime = Date.now(); + window.addEventListener("message", async (event) => { + // console.log("message", event); + try { + const message = JSON.parse(event.data); + if (message.type === "graph_load") { + const comfyUIWorkflow = message.data; + // console.log("recieved: ", comfyUIWorkflow); + // Assuming there's a method to load the workflow data into the ComfyUI + // This part of the code would depend on how the ComfyUI expects to receive and process the workflow data + // For demonstration, let's assume there's a loadWorkflow method in the ComfyUI API + if (comfyUIWorkflow && app && app.loadGraphData) { + console.log("loadGraphData"); + app.loadGraphData(comfyUIWorkflow); + } + } else if (message.type === "deploy") { + // deployWorkflow(); + const prompt = await app.graphToPrompt(); + // api.handlePromptGenerated(prompt); + sendEventToCD("cd_plugin_onDeployChanges", prompt); + } else if (message.type === "queue_prompt") { + const prompt = await app.graphToPrompt(); + if (typeof api.handlePromptGenerated === "function") { + api.handlePromptGenerated(prompt); + } else { + console.warn("api.handlePromptGenerated is not a function"); + } + sendEventToCD("cd_plugin_onQueuePrompt", prompt); + } else if (message.type === "get_prompt") { + const prompt = await app.graphToPrompt(); + sendEventToCD("cd_plugin_onGetPrompt", prompt); + } else if (message.type === "event") { + dispatchAPIEventData(message.data); + } else if (message.type === "add_node") { + console.log("add node", message.data); + app.graph.beforeChange(); + var node = LiteGraph.createNode(message.data.type); + node.configure({ + widgets_values: message.data.widgets_values, + }); - function easeOutCubic(t) { - return 1 - Math.pow(1 - t, 3); - } + console.log("node", node); - function lerp(start, end, t) { - return start * (1 - t) + end * t; - } + const graphMouse = app.canvas.graph_mouse; - function animate() { - const currentTime = Date.now(); - const elapsedTime = currentTime - startTime; - const t = Math.min(elapsedTime / duration, 1); + node.pos = [graphMouse[0], graphMouse[1]]; - const easedT = easeOutCubic(t); + app.graph.add(node); + app.graph.afterChange(); + } else if (message.type === "zoom_to_node") { + const nodeId = message.data.nodeId; + const position = message.data.position; - const currentScale = lerp( - startScale, - targetScale, - easedT - ); - const currentOffsetX = lerp( - startOffsetX, - targetOffsetX, - easedT - ); - const currentOffsetY = lerp( - startOffsetY, - targetOffsetY, - easedT - ); + const node = app.graph.getNodeById(nodeId); + if (!node) return; - canvas.setZoom(currentScale); - canvas.ds.offset = [currentOffsetX, currentOffsetY]; - canvas.draw(true, true); + const canvas = app.canvas; + const targetScale = 1; + const targetOffsetX = + canvas.canvas.width / 4 - position[0] - node.size[0] / 2; + const targetOffsetY = + canvas.canvas.height / 4 - position[1] - node.size[1] / 2; - if (t < 1) { - requestAnimationFrame(animate); - } - } + const startScale = canvas.ds.scale; + const startOffsetX = canvas.ds.offset[0]; + const startOffsetY = canvas.ds.offset[1]; - animate(); - } - // else if (message.type === "refresh") { - // sendEventToCD("cd_plugin_onRefresh"); - // } - } catch (error) { - // console.error("Error processing message:", error); + const duration = 400; // Animation duration in milliseconds + const startTime = Date.now(); + + function easeOutCubic(t) { + return 1 - Math.pow(1 - t, 3); + } + + function lerp(start, end, t) { + return start * (1 - t) + end * t; + } + + function animate() { + const currentTime = Date.now(); + const elapsedTime = currentTime - startTime; + const t = Math.min(elapsedTime / duration, 1); + + const easedT = easeOutCubic(t); + + const currentScale = lerp(startScale, targetScale, easedT); + const currentOffsetX = lerp(startOffsetX, targetOffsetX, easedT); + const currentOffsetY = lerp(startOffsetY, targetOffsetY, easedT); + + canvas.setZoom(currentScale); + canvas.ds.offset = [currentOffsetX, currentOffsetY]; + canvas.draw(true, true); + + if (t < 1) { + requestAnimationFrame(animate); } - }); + } - api.addEventListener("executed", (evt) => { - const images = evt.detail?.output.images; - // if (images?.length > 0 && images[0].type === "output") { - // generatedImages[evt.detail.node] = images[0].filename; - // } - // if (evt.detail?.output.gltfFilename) { + animate(); + } + // else if (message.type === "refresh") { + // sendEventToCD("cd_plugin_onRefresh"); + // } + } catch (error) { + // console.error("Error processing message:", error); + } + }); - // } - }); + api.addEventListener("executed", (evt) => { + const images = evt.detail?.output.images; + // if (images?.length > 0 && images[0].type === "output") { + // generatedImages[evt.detail.node] = images[0].filename; + // } + // if (evt.detail?.output.gltfFilename) { - app.graph.onAfterChange = ((originalFunction) => - async function () { - const prompt = await app.graphToPrompt(); - sendEventToCD("cd_plugin_onAfterChange", prompt); + // } + }); - if (typeof originalFunction === "function") { - originalFunction.apply(this, arguments); - } - })(app.graph.onAfterChange); + app.graph.onAfterChange = ((originalFunction) => + async function () { + const prompt = await app.graphToPrompt(); + sendEventToCD("cd_plugin_onAfterChange", prompt); - sendEventToCD("cd_plugin_setup"); - }, + if (typeof originalFunction === "function") { + originalFunction.apply(this, arguments); + } + })(app.graph.onAfterChange); + + sendEventToCD("cd_plugin_setup"); + }, }; /** @@ -441,138 +407,138 @@ const ext = { */ function showError(title, message) { - infoDialog.show( - `

${title}


${message} ` - ); + infoDialog.show( + `

${title}


${message} `, + ); } function createDynamicUIHtml(data) { - console.log(data); - let html = - '
'; - const bgcolor = "var(--comfy-input-bg)"; - const evenBg = "var(--border-color)"; - const textColor = "var(--input-text)"; + console.log(data); + let html = + '
'; + const bgcolor = "var(--comfy-input-bg)"; + const evenBg = "var(--border-color)"; + const textColor = "var(--input-text)"; - // Custom Nodes - html += `
`; - html += - '

Custom Nodes

'; + // Custom Nodes + html += `
`; + html += + '

Custom Nodes

'; - if (data.missing_nodes?.length > 0) { - html += ` + if (data.missing_nodes?.length > 0) { + html += `

Missing Nodes

These nodes are not found with any matching custom_nodes in the ComfyUI Manager Database

${data.missing_nodes - .map((node) => { - return `

${node}

`; - }) - .join("")} + .map((node) => { + return `

${node}

`; + }) + .join("")}
`; - } + } - Object.values(data.custom_nodes).forEach((node) => { - html += ` + Object.values(data.custom_nodes).forEach((node) => { + html += `
${ - node.name - } + node.name + }

${node.hash}

${ - node.warning - ? `

${node.warning}

` - : "" + node.warning + ? `

${node.warning}

` + : "" }
`; - }); - html += "
"; + }); + html += "
"; - // Models - html += `
`; - html += - '

Models

'; + // Models + html += `
`; + html += + '

Models

'; - Object.entries(data.models).forEach(([section, items]) => { - html += ` + Object.entries(data.models).forEach(([section, items]) => { + html += `

${ - section.charAt(0).toUpperCase() + section.slice(1) + section.charAt(0).toUpperCase() + section.slice(1) }

`; - items.forEach((item) => { - html += `

${item.name}

`; - }); - html += "
"; + items.forEach((item) => { + html += `

${item.name}

`; }); html += "
"; + }); + html += "
"; - // Models - html += `
`; - html += - '

Files

'; + // Models + html += `
`; + html += + '

Files

'; - Object.entries(data.files).forEach(([section, items]) => { - html += ` + Object.entries(data.files).forEach(([section, items]) => { + html += `

${ - section.charAt(0).toUpperCase() + section.slice(1) + section.charAt(0).toUpperCase() + section.slice(1) }

`; - items.forEach((item) => { - html += `

${item.name}

`; - }); - html += "
"; + items.forEach((item) => { + html += `

${item.name}

`; }); html += "
"; + }); + html += "
"; - html += "
"; - return html; + html += "
"; + return html; } async function deployWorkflow() { - const deploy = document.getElementById("deploy-button"); + const deploy = document.getElementById("deploy-button"); - /** @type {LGraph} */ - const graph = app.graph; + /** @type {LGraph} */ + const graph = app.graph; - let { endpoint, apiKey, displayName } = getData(); + let { endpoint, apiKey, displayName } = getData(); - if (!endpoint || !apiKey || apiKey === "" || endpoint === "") { - configDialog.show(); - return; - } + if (!endpoint || !apiKey || apiKey === "" || endpoint === "") { + configDialog.show(); + return; + } - let deployMeta = graph.findNodesByType("ComfyDeploy"); + let deployMeta = graph.findNodesByType("ComfyDeploy"); - if (deployMeta.length == 0) { - const text = await inputDialog.input( - "Create your deployment", - "Workflow name" - ); - if (!text) return; - console.log(text); - app.graph.beforeChange(); - var node = LiteGraph.createNode("ComfyDeploy"); - node.configure({ - widgets_values: [text], - }); - node.pos = [0, 0]; - app.graph.add(node); - app.graph.afterChange(); - deployMeta = [node]; - } + if (deployMeta.length == 0) { + const text = await inputDialog.input( + "Create your deployment", + "Workflow name", + ); + if (!text) return; + console.log(text); + app.graph.beforeChange(); + var node = LiteGraph.createNode("ComfyDeploy"); + node.configure({ + widgets_values: [text], + }); + node.pos = [0, 0]; + app.graph.add(node); + app.graph.afterChange(); + deployMeta = [node]; + } - const deployMetaNode = deployMeta[0]; + const deployMetaNode = deployMeta[0]; - const workflow_name = deployMetaNode.widgets[0].value; - const workflow_id = deployMetaNode.widgets[1].value; + const workflow_name = deployMetaNode.widgets[0].value; + const workflow_id = deployMetaNode.widgets[1].value; - const ok = await confirmDialog.confirm( - `Confirm deployment`, - ` + const ok = await confirmDialog.confirm( + `Confirm deployment`, + `
A new version of will be deployed, do you confirm? @@ -591,269 +557,270 @@ async function deployWorkflow() { Reuse hash from last version
- ` + `, + ); + if (!ok) return; + + const includeDeps = document.getElementById("include-deps").checked; + const reuseHash = document.getElementById("reuse-hash").checked; + + if (endpoint.endsWith("/")) { + endpoint = endpoint.slice(0, -1); + } + loadingDialog.showLoading("Generating snapshot"); + + const snapshot = await fetch("/snapshot/get_current").then((x) => x.json()); + // console.log(snapshot); + loadingDialog.close(); + + if (!snapshot) { + showError( + "Error when deploying", + "Unable to generate snapshot, please install ComfyUI Manager", ); - if (!ok) return; + return; + } - const includeDeps = document.getElementById("include-deps").checked; - const reuseHash = document.getElementById("reuse-hash").checked; + const title = deploy.querySelector("#button-title"); - if (endpoint.endsWith("/")) { - endpoint = endpoint.slice(0, -1); - } - loadingDialog.showLoading("Generating snapshot"); + const prompt = await app.graphToPrompt(); + let deps = undefined; + + if (includeDeps) { + loadingDialog.showLoading("Fetching existing version"); + + const existing_workflow = await fetch( + endpoint + "/api/workflow/" + workflow_id, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + apiKey, + }, + }, + ) + .then((x) => x.json()) + .catch(() => { + return {}; + }); - const snapshot = await fetch("/snapshot/get_current").then((x) => x.json()); - // console.log(snapshot); loadingDialog.close(); - if (!snapshot) { - showError( - "Error when deploying", - "Unable to generate snapshot, please install ComfyUI Manager" - ); - return; - } + loadingDialog.showLoading("Generating dependency graph"); + deps = await generateDependencyGraph({ + workflow_api: prompt.output, + snapshot: snapshot, + computeFileHash: async (file) => { + console.log(existing_workflow?.dependencies?.models); - const title = deploy.querySelector("#button-title"); + // Match previous hash for models + if (reuseHash && existing_workflow?.dependencies?.models) { + const previousModelHash = Object.entries( + existing_workflow?.dependencies?.models, + ).flatMap(([key, value]) => { + return Object.values(value).map((x) => ({ + ...x, + name: "models/" + key + "/" + x.name, + })); + }); + console.log(previousModelHash); - const prompt = await app.graphToPrompt(); - let deps = undefined; + const match = previousModelHash.find((x) => { + console.log(file, x.name); + return file == x.name; + }); + console.log(match); + if (match && match.hash) { + console.log("cached hash used"); + return match.hash; + } + } + console.log(file); + loadingDialog.showLoading("Generating hash", file); + const hash = await fetch( + `/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent(file)}`, + ).then((x) => x.json()); + loadingDialog.showLoading("Generating hash", file); + console.log(hash); + return hash.file_hash; + }, + // handleFileUpload: async (file, hash, prevhash) => { + // console.log("Uploading ", file); + // loadingDialog.showLoading("Uploading file", file); + // try { + // const { download_url } = await fetch(`/comfyui-deploy/upload-file`, { + // method: "POST", + // body: JSON.stringify({ + // file_path: file, + // token: apiKey, + // url: endpoint + "/api/upload-url", + // }), + // }) + // .then((x) => x.json()) + // .catch(() => { + // loadingDialog.close(); + // confirmDialog.confirm("Error", "Unable to upload file " + file); + // }); + // loadingDialog.showLoading("Uploaded file", file); + // console.log(download_url); + // return download_url; + // } catch (error) { + // return undefined; + // } + // }, + existingDependencies: existing_workflow.dependencies, + }); - if (includeDeps) { - loadingDialog.showLoading("Fetching existing version"); + // Need to find a way to include this if this is not included in comfyui-json level + if ( + !deps.custom_nodes["https://github.com/BennyKok/comfyui-deploy"] && + !deps.custom_nodes["https://github.com/BennyKok/comfyui-deploy.git"] + ) + deps.custom_nodes["https://github.com/BennyKok/comfyui-deploy"] = { + url: "https://github.com/BennyKok/comfyui-deploy", + install_type: "git-clone", + hash: + snapshot?.git_custom_nodes?.[ + "https://github.com/BennyKok/comfyui-deploy" + ]?.hash ?? "HEAD", + name: "ComfyUI Deploy", + }; - const existing_workflow = await fetch( - endpoint + "/api/workflow/" + workflow_id, - { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + apiKey, - }, - } - ) - .then((x) => x.json()) - .catch(() => { - return {}; - }); + loadingDialog.close(); - loadingDialog.close(); - - loadingDialog.showLoading("Generating dependency graph"); - deps = await generateDependencyGraph({ - workflow_api: prompt.output, - snapshot: snapshot, - computeFileHash: async (file) => { - console.log(existing_workflow?.dependencies?.models); - - // Match previous hash for models - if (reuseHash && existing_workflow?.dependencies?.models) { - const previousModelHash = Object.entries( - existing_workflow?.dependencies?.models - ).flatMap(([key, value]) => { - return Object.values(value).map((x) => ({ - ...x, - name: "models/" + key + "/" + x.name, - })); - }); - console.log(previousModelHash); - - const match = previousModelHash.find((x) => { - console.log(file, x.name); - return file == x.name; - }); - console.log(match); - if (match && match.hash) { - console.log("cached hash used"); - return match.hash; - } - } - console.log(file); - loadingDialog.showLoading("Generating hash", file); - const hash = await fetch( - `/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent( - file - )}` - ).then((x) => x.json()); - loadingDialog.showLoading("Generating hash", file); - console.log(hash); - return hash.file_hash; - }, - handleFileUpload: async (file, hash, prevhash) => { - console.log("Uploading ", file); - loadingDialog.showLoading("Uploading file", file); - try { - const { download_url } = await fetch( - `/comfyui-deploy/upload-file`, - { - method: "POST", - body: JSON.stringify({ - file_path: file, - token: apiKey, - url: endpoint + "/api/upload-url", - }), - } - ) - .then((x) => x.json()) - .catch(() => { - loadingDialog.close(); - confirmDialog.confirm( - "Error", - "Unable to upload file " + file - ); - }); - loadingDialog.showLoading("Uploaded file", file); - console.log(download_url); - return download_url; - } catch (error) { - return undefined; - } - }, - existingDependencies: existing_workflow.dependencies, - }); - - // Need to find a way to include this if this is not included in comfyui-json level - if ( - !deps.custom_nodes["https://github.com/BennyKok/comfyui-deploy"] && - !deps.custom_nodes["https://github.com/BennyKok/comfyui-deploy.git"] - ) - deps.custom_nodes["https://github.com/BennyKok/comfyui-deploy"] = { - url: "https://github.com/BennyKok/comfyui-deploy", - install_type: "git-clone", - hash: - snapshot?.git_custom_nodes?.[ - "https://github.com/BennyKok/comfyui-deploy" - ]?.hash ?? "HEAD", - name: "ComfyUI Deploy", - }; - - loadingDialog.close(); - - const depsOk = await confirmDialog.confirm( - "Check dependencies", - // JSON.stringify(deps, null, 2), - ` + const depsOk = await confirmDialog.confirm( + "Check dependencies", + // JSON.stringify(deps, null, 2), + ` +
+ You will need to create a cloud machine with the following configuration on ComfyDeploy +
    +
  1. Review the dependencies listed in the graph below
  2. +
  3. Create a new cloud machine with the required configuration
  4. +
  5. Install missing models and check missing files
  6. +
  7. Deploy your workflow to the newly created machine
  8. +
+
${loadingIcon}