diff --git a/custom_routes.py b/custom_routes.py
index 40a5213..4416533 100644
--- a/custom_routes.py
+++ b/custom_routes.py
@@ -22,6 +22,8 @@ from logging.handlers import RotatingFileHandler
from enum import Enum
from urllib.parse import quote
import threading
+import hashlib
+import aiohttp
api = None
api_task = None
@@ -143,6 +145,138 @@ async def comfy_deploy_run(request):
sockets = dict()
+def get_comfyui_path_from_file_path(file_path):
+ file_path_parts = file_path.split("\\")
+
+ if file_path_parts[0] == "input":
+ print("matching input")
+ file_path = os.path.join(folder_paths.get_directory_by_type("input"), *file_path_parts[1:])
+ elif file_path_parts[0] == "models":
+ print("matching models")
+ file_path = folder_paths.get_full_path(file_path_parts[1], os.path.join(*file_path_parts[2:]))
+
+ print(file_path)
+
+ return file_path
+
+# Form ComfyUI Manager
+def compute_sha256_checksum(filepath):
+ filepath = get_comfyui_path_from_file_path(filepath)
+ """Compute the SHA256 checksum of a file, in chunks"""
+ sha256 = hashlib.sha256()
+ with open(filepath, 'rb') as f:
+ for chunk in iter(lambda: f.read(4096), b''):
+ sha256.update(chunk)
+ return sha256.hexdigest()
+
+# This is start uploading the files to Comfy Deploy
+@server.PromptServer.instance.routes.post('/comfyui-deploy/upload-file')
+async def upload_file(request):
+ data = await request.json()
+
+ file_path = data.get("file_path")
+
+ print("Original file path", file_path)
+
+ file_path = get_comfyui_path_from_file_path(file_path)
+
+ # return web.json_response({
+ # "error": f"File not uploaded"
+ # }, status=500)
+
+ token = data.get("token")
+ get_url = data.get("url")
+
+ try:
+ base = folder_paths.base_path
+ file_path = os.path.join(base, file_path)
+
+ if os.path.exists(file_path):
+ file_size = os.path.getsize(file_path)
+ file_extension = os.path.splitext(file_path)[1]
+
+ if file_extension in ['.jpg', '.jpeg']:
+ file_type = 'image/jpeg'
+ elif file_extension == '.png':
+ file_type = 'image/png'
+ elif file_extension == '.webp':
+ file_type = 'image/webp'
+ else:
+ file_type = 'application/octet-stream' # Default to binary file type if unknown
+ else:
+ return web.json_response({
+ "error": f"File not found: {file_path}"
+ }, status=404)
+
+ except Exception as e:
+ return web.json_response({
+ "error": str(e)
+ }, status=500)
+
+ if get_url:
+ try:
+ async with aiohttp.ClientSession() as session:
+ headers = {'Authorization': f'Bearer {token}'}
+ params = {'file_size': file_size, 'type': file_type}
+ async with session.get(get_url, params=params, headers=headers) as response:
+ if response.status == 200:
+ content = await response.json()
+ upload_url = content["upload_url"]
+
+ with open(file_path, 'rb') as f:
+ headers = {
+ "Content-Type": file_type,
+ "x-amz-acl": "public-read",
+ "Content-Length": str(file_size)
+ }
+ async with session.put(upload_url, data=f, headers=headers) as upload_response:
+ if upload_response.status == 200:
+ return web.json_response({
+ "message": "File uploaded successfully",
+ "download_url": content["download_url"]
+ })
+ else:
+ return web.json_response({
+ "error": f"Failed to upload file to {upload_url}. Status code: {upload_response.status}"
+ }, status=upload_response.status)
+ else:
+ return web.json_response({
+ "error": f"Failed to fetch data from {get_url}. Status code: {response.status}"
+ }, status=response.status)
+ except Exception as e:
+ return web.json_response({
+ "error": f"An error occurred while fetching data from {get_url}: {str(e)}"
+ }, status=500)
+
+ return web.json_response({
+ "error": f"File not uploaded"
+ }, status=500)
+
+
+@server.PromptServer.instance.routes.get('/comfyui-deploy/get-file-hash')
+async def get_file_hash(request):
+ file_path = request.rel_url.query.get('file_path', '')
+
+ if file_path is None:
+ return web.json_response({
+ "error": "file_path is required"
+ }, status=400)
+
+ try:
+ base = folder_paths.base_path
+ file_path = os.path.join(base, file_path)
+ # print("file_path", file_path)
+ file_hash = compute_sha256_checksum(
+ file_path
+ )
+ return web.json_response({
+ "file_hash": file_hash
+ })
+ except Exception as e:
+ return web.json_response({
+ "error": str(e)
+ }, status=500)
+
@server.PromptServer.instance.routes.get('/comfyui-deploy/ws')
async def websocket_handler(request):
ws = web.WebSocketResponse()
diff --git a/web-plugin/index.js b/web-plugin/index.js
index f3bef7f..fc7dcb4 100644
--- a/web-plugin/index.js
+++ b/web-plugin/index.js
@@ -1,6 +1,7 @@
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.7";
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
/** @type {ComfyExtension} */
@@ -178,6 +179,71 @@ function showError(title, message) {
);
}
+function createDynamicUIHtml(data) {
+ let html =
+ '
';
+ const bgcolor = "var(--comfy-input-bg)";
+ const textColor = "var(--input-text)";
+
+ // Custom Nodes
+ html += `
`;
+ html +=
+ '
Custom Nodes
';
+ Object.values(data.custom_nodes).forEach((node) => {
+ html += `
+
+
${
+ node.name
+ }
+
${node.hash}
+ ${
+ node.warning
+ ? `
${node.warning}
`
+ : ""
+ }
+
+ `;
+ });
+ html += "
";
+
+ // Models
+ html += `
`;
+ html +=
+ '
Models
';
+ const modelSections = ["checkpoints", "ipadapter", "clip_vision"];
+ modelSections.forEach((section) => {
+ html += `
+
+
${
+ section.charAt(0).toUpperCase() + section.slice(1)
+ }
+ `;
+ data.models[section].forEach((item) => {
+ html += `
${item.name}
`;
+ });
+ html += "
";
+ });
+ html += "
";
+
+ // Files
+ html += `
`;
+ html +=
+ '
Files
';
+ html += `
+
+
Images
+ `;
+ data.files.images.forEach((image) => {
+ html += `
${image.name}
`;
+ });
+ html += "
";
+
+ html += "
";
+ return html;
+}
+
function addButton() {
const menu = document.querySelector(".comfy-menu");
@@ -189,8 +255,29 @@ function addButton() {
/** @type {LGraph} */
const graph = app.graph;
+ let { endpoint, apiKey, displayName } = getData();
+
+ if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
+ configDialog.show();
+ return;
+ }
+
+ const ok = await confirmDialog.confirm(
+ "Confirm deployment -> " + displayName,
+ `A new version will be deployed, are you conform?
Include dependence`,
+ );
+ if (!ok) return;
+
+ const includeDeps = document.getElementById("include-deps").checked;
+
+ if (endpoint.endsWith("/")) {
+ endpoint = endpoint.slice(0, -1);
+ }
+ loadingDialog.showLoading("Generating snapshot", "Please wait...");
+
const snapshot = await fetch("/snapshot/get_current").then((x) => x.json());
// console.log(snapshot);
+ loadingDialog.close();
if (!snapshot) {
showError(
@@ -224,32 +311,94 @@ function addButton() {
const deployMetaNode = deployMeta[0];
- // console.log(deployMetaNode);
-
const workflow_name = deployMetaNode.widgets[0].value;
const workflow_id = deployMetaNode.widgets[1].value;
- console.log(workflow_name, workflow_id);
-
const prompt = await app.graphToPrompt();
- console.log(graph);
- console.log(prompt);
+ let deps = undefined;
- // const endpoint = localStorage.getItem("endpoint") ?? "";
- // const apiKey = localStorage.getItem("apiKey");
+ if (includeDeps) {
+ loadingDialog.showLoading("Fetching existing version", "Please wait...");
- const { endpoint, apiKey, displayName } = getData();
+ 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 {};
+ });
- if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
- configDialog.show();
- return;
+ loadingDialog.close();
+
+ loadingDialog.showLoading(
+ "Generating dependency graph",
+ "Please wait...",
+ );
+ deps = await generateDependencyGraph(
+ prompt.output,
+ snapshot,
+ async (file) => {
+ 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;
+ },
+ 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;
+ }
+ },
+ existing_workflow.dependencies,
+ );
+
+ loadingDialog.close();
+
+ const depsOk = await confirmDialog.confirm(
+ "Check dependencies",
+ // JSON.stringify(deps, null, 2),
+ createDynamicUIHtml(deps),
+ );
+ if (!depsOk) return;
+
+ console.log(deps);
}
- const ok = await confirmDialog.confirm(
- "Confirm deployment -> " + displayName,
- "A new version will be deployed, are you conform?",
- );
- if (!ok) return;
+ loadingDialog.showLoading("Deploying...");
title.innerText = "Deploying...";
title.style.color = "orange";
@@ -261,6 +410,8 @@ function addButton() {
endpoint = endpoint.slice(0, -1);
}
+ // console.log(prompt.workflow);
+
const apiRoute = endpoint + "/api/workflow";
// const userId = apiKey
try {
@@ -270,6 +421,7 @@ function addButton() {
workflow: prompt.workflow,
workflow_api: prompt.output,
snapshot: snapshot,
+ dependencies: deps,
};
console.log(body);
let data = await fetch(apiRoute, {
@@ -289,6 +441,8 @@ function addButton() {
data = await data.json();
}
+ loadingDialog.close();
+
title.textContent = "Done";
title.style.color = "green";
@@ -305,6 +459,7 @@ function addButton() {
title.style.color = "white";
}, 1000);
} catch (e) {
+ loadingDialog.close();
app.ui.dialog.show(e);
console.error(e);
title.textContent = "Error";
@@ -381,7 +536,7 @@ export class InfoDialog extends ComfyDialog {
showMessage(title, message) {
this.show(`
-
+
${title}