Compare commits

..
Author SHA1 Message Date
karrix 02f499a4dc add: name 2024-10-23 19:07:42 +08:00
karrix 67d27bd4aa add: any output 2024-10-23 18:47:58 +08:00
karrix 8ee2f88e72 tweak 2024-10-22 17:51:04 +08:00
karrix c17a029173 tweak: back to support all 2024-10-22 17:34:33 +08:00
karrix c82f0c2bf0 tweak 2024-10-22 14:56:06 +08:00
karrix fdbf24207f tweak 2024-10-22 14:04:37 +08:00
karrix 0a9d0d3e3e tweak 2024-10-22 13:57:19 +08:00
karrix d41c4de352 init 2024-10-22 12:54:33 +08:00
BennyKok 0779136134 Update pyproject.toml 2024-10-22 11:28:59 +08:00
nick fe116a4655 clean logs 2024-10-12 23:59:58 -07:00
nick 7dd8a7e67e gpu eveent 2024-10-12 16:57:37 -07:00
nick 778e6fefe6 Merge branch 'main' into nickkao/gpu_event 2024-10-12 12:56:11 -07:00
nick fd310e8478 globals 2024-10-11 21:46:48 -07:00
karrix 3a3b93d564 tweak: modify the local storage of the dock 2024-10-11 17:15:28 +08:00
nick 82c564228d None gpu event 2024-10-10 17:58:11 -07:00
nick ad0a23434b merge 2024-10-10 17:23:19 -07:00
karrix 292f77f06b fix: default queue button position to dock 2024-10-11 02:21:32 +08:00
bennykok a139424b91 fix: output node status 2024-10-10 11:20:12 -07:00
bennykok 44a91d2093 fix: default new ui for comfyui 2024-10-09 17:20:08 -07:00
bennykok 7cff930861 fix: token will be fetched everytime to make sure it is the latest 2024-10-09 16:54:34 -07:00
nick ce464c6ce4 Merge branch 'main' into nickkao/gpu_event 2024-10-07 15:50:47 -07:00
bennykok 1c7998c554 feat: attach gpu event 2024-10-07 15:48:14 -07:00
nick 66d1e42409 lopgs 2024-10-07 14:16:55 -07:00
nick 8882f4983c fix: pydantic type simpleprompt 2024-10-04 19:14:37 -07:00
nick 492b81c340 print 2024-10-04 19:02:25 -07:00
nick 8b05ed26c9 merge 2024-10-04 18:49:30 -07:00
nick ce67604926 stuff 2024-10-04 17:34:50 -07:00
bennykok c115c22a91 fix: send ws after cd logic 2024-10-04 16:16:19 -07:00
bennykok 2f33bcf497 chore: return item on upload 2024-10-04 15:27:31 -07:00
nick bcf466c472 merge 2024-10-04 12:10:01 -07:00
bennykok f812d9d698 Merge branch 'workspace-v3' into public-main 2024-10-02 16:38:55 -07:00
nick 101b6cca57 merge 2024-09-29 12:02:46 -07:00
EdwinWong ae68aae011 fix: add workflow data to extra data 2024-09-27 18:48:51 -07:00
EmmanuelMr18 07926158f0 feat: model_list node to display all the models available 2024-09-27 18:19:56 -07:00
EmmanuelMr18 ce92dd0570 refactor: remove ExternalTextList node, was for lora traning 2024-09-27 15:13:27 -07:00
bennykok e2fcf67aec fix: graph load 2024-09-25 12:59:00 -07:00
nick 79650f48d0 merge 2024-09-24 23:16:49 -07:00
bennykok 69f63f4869 Merge branch 'jeff/fix-workflow-in-extra-data' into workspace-v3 2024-09-24 19:58:16 -07:00
bennykok 50860cd500 test 2024-09-24 19:45:53 -07:00
bennykok 2eb02fc92e fi 2024-09-24 19:36:57 -07:00
EdwinWong 5c6defbe62 fix: add workflow data to extra data 2024-09-24 15:35:48 -07:00
bennykok d1c54b2b6d fix: state 2024-09-23 19:01:47 -07:00
bennykok 3a6c3b1ae9 feat: add native run proxy 2024-09-23 15:31:13 -07:00
bennykok aea456cba9 fix face loader extenal load 2024-09-21 10:51:51 -07:00
bennykok 8c5e5c4277 feat: add ComfyUIDeployExternalTextAny 2024-09-21 10:39:34 -07:00
bennykok 02430ee62d remove some logs 2024-09-20 18:10:04 -07:00
Fawaz Kadem 764a8fee82 Add new external deploy node for face models (#66) 2024-09-18 17:00:51 -07:00
bennykok 61acffd355 fix 2024-09-18 08:20:35 -07:00
bennykok aa47f3523f fix 2024-09-17 23:36:24 -07:00
bennykok 7ed4284a6f fix 2024-09-17 23:25:19 -07:00
bennykok a403daa314 fix 2024-09-17 23:09:42 -07:00
bennykok ba9b187dcc fix 2024-09-17 22:59:27 -07:00
bennykok 1243fa4e58 fix 2024-09-17 22:55:08 -07:00
bennykok 0d1537963c fix 2024-09-17 21:48:42 -07:00
bennykok 0083b38dcc chore: log image size 2024-09-17 20:44:44 -07:00
bennykok b8dded1535 Revert "fix: roll back to unique session per request"
This reverts commit 5a78ca97bd.
2024-09-17 20:26:39 -07:00
bennykok 4927d81e73 chore: accept cd_token 2024-09-17 18:57:15 -07:00
nick 0e70db4013 merge 2024-09-17 16:37:39 -07:00
nick 06805e310d merge 2024-09-17 14:32:52 -07:00
bennykok fb6bb2357a Reapply "fix: back to sequential file upload"
This reverts commit 1f5a88b888.
2024-09-17 14:28:56 -07:00
bennykok 086d642360 Merge branch 'benny/log-sync' into public-main 2024-09-17 14:27:59 -07:00
bennykok 212daa838c Revert "feat: experiment with await + asyncio.gather for multi file in same node"
This reverts commit c08b68c41f.
2024-09-17 14:25:13 -07:00
bennykok c08b68c41f feat: experiment with await + asyncio.gather for multi file in same node 2024-09-17 12:56:42 -07:00
bennykok 5a78ca97bd fix: roll back to unique session per request 2024-09-16 23:57:40 -07:00
bennykok 1f5a88b888 Revert "fix: back to sequential file upload"
This reverts commit 3d099f88ea.
2024-09-16 23:55:16 -07:00
bennykok 946571e32e fix: await 2024-09-16 18:54:05 -07:00
bennykok e692beb009 feat: realtime log sync 2024-09-16 15:34:20 -07:00
bennykok 3d099f88ea fix: back to sequential file upload 2024-09-16 13:55:02 -07:00
karrix 65f7576748 fix: non type error when upload output 2024-09-16 12:45:53 -07:00
bennykok 2d72cd8175 fix: batch zip image input 2024-09-14 21:49:17 -07:00
bennykok 5554c95f44 Merge branch 'benny/auth_token' into public-main 2024-09-12 14:14:16 -07:00
bennykok c1003f7e31 Merge branch 'benny/zip-batch-image' into public-main 2024-09-12 14:14:08 -07:00
EdwinWong 71d60a5dd1 fix: comfydeploy node backward compatible in every comfyui 2024-09-10 01:03:50 -07:00
bennykok e011711600 feat: zip batch image support 2024-09-09 17:49:39 -07:00
nick 4cd7d7a8f9 gpu event 2024-09-08 09:55:47 -07:00
bennykok 4df9d38e56 feat: embed file public status into image output 2024-09-03 23:07:48 -07:00
bennykok 9cd626e1f6 feat: send token for cd update api 2024-09-03 21:58:39 -07:00
bennykok 503dca8fb6 chore: add log 2024-08-30 12:16:41 -07:00
bennykok 73c149b4cb fix node meta 2024-08-30 12:16:41 -07:00
bennykok 65b5b0b8c7 fix: remove content length 2024-08-30 12:16:41 -07:00
bennykok 9d6ee85402 fix: upload file acl 2024-08-30 12:16:41 -07:00
bennykok cdaed8a571 fix: include upload time 2024-08-30 12:16:41 -07:00
bennykok 3129e89cce fix: log file error log 2024-08-30 12:16:41 -07:00
bennykok 7a693eabc8 fix: size 2024-08-30 12:16:41 -07:00
bennykok 8f677e520d chore: log more test for upload file debug 2024-08-30 12:16:41 -07:00
bennykok 4c8d32c5b0 fix 2024-08-30 12:16:41 -07:00
nick a99d2568e0 video and lora node fix 2024-08-28 13:08:15 -07:00
nick 649b61c580 default vid 2024-08-26 13:46:01 -07:00
nick edff5685f9 fix: random seed 2024-08-22 17:39:03 -07:00
bennykok 9fc0c2b4a2 chore: upload node data 2024-08-21 16:34:25 -07:00
bennykok d34e2e99b1 fix: external lora for new comfyui 2024-08-21 09:46:13 -07:00
bennykok f85043db07 fix: remove default value 2024-08-20 19:14:43 -07:00
bennykok 894d8e1503 Merge branch 'benny/async-upload-file' into public-main 2024-08-20 18:02:57 -07:00
bennykok 08d631d1eb feat: async file upload for the same node 2024-08-20 17:07:50 -07:00
karrix a1031487e1 add: all node support name and description 2024-08-20 20:15:29 +08:00
bennykok ca41207192 feat: max min int for all number inputs to enable negative number input 2024-08-19 13:27:46 -07:00
bennykok 507d5ef631 feat: add a init timeout of 10 seconds for retry logic 2024-08-18 17:31:48 -07:00
bennykok dd1d9df23f fix: resolve false possible error 2024-08-18 15:38:16 -07:00
bennykok 3a14e49ca5 fix: refresh workflows list 2024-08-17 16:04:14 -07:00
nick 8147c4bfb7 video node' 2024-08-15 12:50:29 -07:00
bennykok 10268825d9 feat: support new frontend! 2024-08-14 11:09:58 -07:00
bennykok f6ea252652 fix: log when random seed is applied 2024-08-10 10:35:48 -07:00
bennykok 98cd5ef79c fix: randomize noise RandomNoise, KSamplerAdvanced, SamplerCustom 2024-08-10 10:02:01 -07:00
Emmanuel Morales 4bce5cadfb fix(text): return correctly the text in external_text_list node 2024-08-10 09:44:37 -06:00
Nick Kao f362671041 Merge pull request #61 from BennyKok/node-error-no-throw
block on bad prompt
2024-08-08 10:01:33 -07:00
23 changed files with 2754 additions and 1482 deletions
+46
View File
@@ -0,0 +1,46 @@
import json
class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
def __ne__(self, __value: object) -> bool:
return False
any = AnyType("*")
class ComfyDeployStdOutputAny:
@classmethod
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
return {
"required": {
"name": ("STRING", {"default": "ComfyUI"}),
"source": (any, {}), # Use "*" to accept any input type
},
}
CATEGORY = "output"
RETURN_TYPES = ()
FUNCTION = "run"
OUTPUT_NODE = True
def run(self, name, source=None):
value = "None"
if source is not None:
try:
value = json.dumps(source)
except Exception:
try:
value = str(source)
except Exception:
value = "source exists, but could not be serialized."
return {"ui": {name: (value,)}}
NODE_CLASS_MAPPINGS = {"ComfyDeployStdOutputAny": ComfyDeployStdOutputAny}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyDeployStdOutputAny": "Standard Any Output (ComfyDeploy)"
}
+92
View File
@@ -0,0 +1,92 @@
import os
import json
import numpy as np
from PIL import Image
from PIL.PngImagePlugin import PngInfo
import folder_paths
class ComfyDeployStdOutputImage:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
self.compress_level = 4
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE", {"tooltip": "The images to save."}),
"filename_prefix": (
"STRING",
{
"default": "ComfyUI",
"tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes.",
},
),
"file_type": (["png", "jpg", "webp"], {"default": "webp"}),
"quality": ("INT", {"default": 80, "min": 1, "max": 100, "step": 1}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
FUNCTION = "run"
OUTPUT_NODE = True
CATEGORY = "output"
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
def run(
self,
images,
filename_prefix="ComfyUI",
file_type="png",
quality=80,
prompt=None,
extra_pnginfo=None,
):
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = (
folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
)
)
results = list()
for batch_number, image in enumerate(images):
i = 255.0 * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
file = f"{filename_with_batch_num}_{counter:05}_.{file_type}"
file_path = os.path.join(full_output_folder, file)
if file_type == "png":
img.save(
file_path, pnginfo=metadata, compress_level=self.compress_level
)
elif file_type == "jpg":
img.save(file_path, quality=quality, optimize=True)
elif file_type == "webp":
img.save(file_path, quality=quality)
results.append(
{"filename": file, "subfolder": subfolder, "type": self.type}
)
counter += 1
return {"ui": {"images": results}}
NODE_CLASS_MAPPINGS = {"ComfyDeployStdOutputImage": ComfyDeployStdOutputImage}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyDeployStdOutputImage": "Standard Image Output (ComfyDeploy)"
}
+11 -1
View File
@@ -8,6 +8,16 @@ class ComfyUIDeployExternalBoolean:
{"multiline": False, "default": "input_bool"}, {"multiline": False, "default": "input_bool"},
), ),
"default_value": ("BOOLEAN", {"default": False}) "default_value": ("BOOLEAN", {"default": False})
},
"optional": {
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
} }
} }
@@ -16,7 +26,7 @@ class ComfyUIDeployExternalBoolean:
FUNCTION = "run" FUNCTION = "run"
def run(self, input_id, default_value=None): def run(self, input_id, default_value=None, display_name=None, description=None):
print(f"Node '{input_id}' processing with switch set to {default_value}") print(f"Node '{input_id}' processing with switch set to {default_value}")
return [default_value] return [default_value]
+9 -1
View File
@@ -23,6 +23,14 @@ class ComfyUIDeployExternalCheckpoint:
}, },
"optional": { "optional": {
"default_value": (folder_paths.get_filename_list("checkpoints"), ), "default_value": (folder_paths.get_filename_list("checkpoints"), ),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
} }
} }
@@ -33,7 +41,7 @@ class ComfyUIDeployExternalCheckpoint:
CATEGORY = "deploy" CATEGORY = "deploy"
def run(self, input_id, default_value=None): def run(self, input_id, default_value=None, display_name=None, description=None):
import requests import requests
import os import os
import uuid import uuid
+108
View File
@@ -0,0 +1,108 @@
from PIL import Image, ImageOps
import numpy as np
import torch
import folder_paths
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyUIDeployExternalFaceModel:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_reactor_face_model"},
),
},
"optional": {
"default_face_model_name": (
"STRING",
{"multiline": False, "default": ""},
),
"face_model_save_name": ( # if `default_face_model_name` is a link to download a file, we will attempt to save it with this name
"STRING",
{"multiline": False, "default": ""},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
"face_model_url": (
"STRING",
{"multiline": False, "default": ""},
),
},
}
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "deploy"
def run(
self,
input_id,
default_face_model_name=None,
face_model_save_name=None,
display_name=None,
description=None,
face_model_url=None,
):
import requests
import os
import uuid
if face_model_url and face_model_url.startswith("http"):
if face_model_save_name:
existing_face_models = folder_paths.get_filename_list("reactor/faces")
# Check if face_model_save_name exists in the list
if face_model_save_name in existing_face_models:
print(f"using face model: {face_model_save_name}")
return (face_model_save_name,)
else:
face_model_save_name = str(uuid.uuid4()) + ".safetensors"
print(face_model_save_name)
print(folder_paths.folder_names_and_paths["reactor/faces"][0][0])
destination_path = os.path.join(
folder_paths.folder_names_and_paths["reactor/faces"][0][0],
face_model_save_name,
)
print(destination_path)
print(
"Downloading external face model - "
+ face_model_url
+ " to "
+ destination_path
)
response = requests.get(
face_model_url,
headers={"User-Agent": "Mozilla/5.0"},
allow_redirects=True,
)
with open(destination_path, "wb") as out_file:
out_file.write(response.content)
return (face_model_save_name,)
else:
print(f"using face model: {default_face_model_name}")
return (default_face_model_name,)
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalFaceModel": ComfyUIDeployExternalFaceModel}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalFaceModel": "External Face Model (ComfyUI Deploy)"
}
+9 -1
View File
@@ -15,6 +15,14 @@ class ComfyUIDeployExternalImage:
}, },
"optional": { "optional": {
"default_value": ("IMAGE",), "default_value": ("IMAGE",),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
} }
} }
@@ -25,7 +33,7 @@ class ComfyUIDeployExternalImage:
CATEGORY = "image" CATEGORY = "image"
def run(self, input_id, default_value=None): def run(self, input_id, default_value=None, display_name=None, description=None):
image = default_value image = default_value
try: try:
if input_id.startswith('http'): if input_id.startswith('http'):
+9 -1
View File
@@ -15,6 +15,14 @@ class ComfyUIDeployExternalImageAlpha:
}, },
"optional": { "optional": {
"default_value": ("IMAGE",), "default_value": ("IMAGE",),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
} }
} }
@@ -25,7 +33,7 @@ class ComfyUIDeployExternalImageAlpha:
CATEGORY = "image" CATEGORY = "image"
def run(self, input_id, default_value=None): def run(self, input_id, default_value=None, display_name=None, description=None):
image = default_value image = default_value
try: try:
if input_id.startswith('http'): if input_id.startswith('http'):
+31 -3
View File
@@ -21,6 +21,14 @@ class ComfyUIDeployExternalImageBatch:
}, },
"optional": { "optional": {
"default_value": ("IMAGE",), "default_value": ("IMAGE",),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
} }
} }
@@ -31,14 +39,34 @@ class ComfyUIDeployExternalImageBatch:
CATEGORY = "image" CATEGORY = "image"
def run(self, input_id, images=None, default_value=None): def process_image(self, image):
image = ImageOps.exif_transpose(image)
image = image.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image_tensor = torch.from_numpy(image)[None,]
return image_tensor
def run(self, input_id, images=None, default_value=None, display_name=None, description=None):
import requests
import zipfile
import io
processed_images = [] processed_images = []
try: try:
images_list = json.loads(images) # Assuming images is a JSON array string images_list = json.loads(images) # Assuming images is a JSON array string
print(images_list) print(images_list)
for img_input in images_list: for img_input in images_list:
if img_input.startswith('http'): if img_input.startswith('http') and img_input.endswith('.zip'):
import requests print("Fetching zip file from url: ", img_input)
response = requests.get(img_input)
zip_file = zipfile.ZipFile(io.BytesIO(response.content))
for file_name in zip_file.namelist():
if file_name.lower().endswith(('.png', '.jpg', '.jpeg')):
with zip_file.open(file_name) as file:
image = Image.open(file)
image = self.process_image(image)
processed_images.append(image)
elif img_input.startswith('http'):
from io import BytesIO from io import BytesIO
print("Fetching image from url: ", img_input) print("Fetching image from url: ", img_input)
response = requests.get(img_input) response = requests.get(img_input)
+24 -4
View File
@@ -29,6 +29,18 @@ class ComfyUIDeployExternalLora:
"STRING", "STRING",
{"multiline": False, "default": ""}, {"multiline": False, "default": ""},
), ),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
"lora_url": (
"STRING",
{"multiline": False, "default": ""},
),
}, },
} }
@@ -39,12 +51,20 @@ class ComfyUIDeployExternalLora:
CATEGORY = "deploy" CATEGORY = "deploy"
def run(self, input_id, default_lora_name=None, lora_save_name=None): def run(
self,
input_id,
default_lora_name=None,
lora_save_name=None,
display_name=None,
description=None,
lora_url=None,
):
import requests import requests
import os import os
import uuid import uuid
if default_lora_name.startswith("http"): if lora_url and lora_url.startswith("http"):
if lora_save_name: if lora_save_name:
existing_loras = folder_paths.get_filename_list("loras") existing_loras = folder_paths.get_filename_list("loras")
# Check if lora_save_name exists in the list # Check if lora_save_name exists in the list
@@ -59,9 +79,9 @@ class ComfyUIDeployExternalLora:
folder_paths.folder_names_and_paths["loras"][0][0], lora_save_name folder_paths.folder_names_and_paths["loras"][0][0], lora_save_name
) )
print(destination_path) print(destination_path)
print("Downloading external lora - " + input_id + " to " + destination_path) print("Downloading external lora - " + lora_url + " to " + destination_path)
response = requests.get( response = requests.get(
input_id, lora_url,
headers={"User-Agent": "Mozilla/5.0"}, headers={"User-Agent": "Mozilla/5.0"},
allow_redirects=True, allow_redirects=True,
) )
+10 -2
View File
@@ -16,7 +16,15 @@ class ComfyUIDeployExternalNumber:
"optional": { "optional": {
"default_value": ( "default_value": (
"FLOAT", "FLOAT",
{"multiline": True, "display": "number", "default": 0, "step": 0.01}, {"multiline": True, "display": "number", "default": 0, "min": -2147483647, "max": 2147483647, "step": 0.01},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
), ),
} }
} }
@@ -28,7 +36,7 @@ class ComfyUIDeployExternalNumber:
CATEGORY = "number" CATEGORY = "number"
def run(self, input_id, default_value=None): def run(self, input_id, default_value=None, display_name=None, description=None):
try: try:
float_value = float(input_id) float_value = float(input_id)
print("my number", float_value) print("my number", float_value)
+10 -2
View File
@@ -16,7 +16,15 @@ class ComfyUIDeployExternalNumberInt:
"optional": { "optional": {
"default_value": ( "default_value": (
"INT", "INT",
{"multiline": True, "display": "number", "default": 0}, {"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 0},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
), ),
} }
} }
@@ -28,7 +36,7 @@ class ComfyUIDeployExternalNumberInt:
CATEGORY = "number" CATEGORY = "number"
def run(self, input_id, default_value=None): def run(self, input_id, default_value=None, display_name=None, description=None):
if not input_id or (isinstance(input_id, str) and not input_id.strip().isdigit()): if not input_id or (isinstance(input_id, str) and not input_id.strip().isdigit()):
return [default_value] return [default_value]
return [int(input_id)] return [int(input_id)]
+12 -4
View File
@@ -11,15 +11,23 @@ class ComfyUIDeployExternalNumberSlider:
"optional": { "optional": {
"default_value": ( "default_value": (
"FLOAT", "FLOAT",
{"multiline": True, "display": "number", "default": 0.5, "step": 0.01}, {"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 0.5, "step": 0.01},
), ),
"min_value": ( "min_value": (
"FLOAT", "FLOAT",
{"multiline": True, "display": "number", "default": 0, "step": 0.01}, {"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 0, "step": 0.01},
), ),
"max_value": ( "max_value": (
"FLOAT", "FLOAT",
{"multiline": True, "display": "number", "default": 1, "step": 0.01}, {"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 1, "step": 0.01},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
), ),
} }
} }
@@ -31,7 +39,7 @@ class ComfyUIDeployExternalNumberSlider:
CATEGORY = "number" CATEGORY = "number"
def run(self, input_id, default_value=None, min_value=0, max_value=1): def run(self, input_id, default_value=None, min_value=0, max_value=1, display_name=None, description=None):
try: try:
float_value = float(input_id) float_value = float(input_id)
if min_value <= float_value <= max_value: if min_value <= float_value <= max_value:
+9 -1
View File
@@ -18,6 +18,14 @@ class ComfyUIDeployExternalText:
"STRING", "STRING",
{"multiline": True, "default": ""}, {"multiline": True, "default": ""},
), ),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
} }
} }
@@ -28,7 +36,7 @@ class ComfyUIDeployExternalText:
CATEGORY = "text" CATEGORY = "text"
def run(self, input_id, default_value=None): def run(self, input_id, default_value=None, display_name=None, description=None):
return [default_value] return [default_value]
+46
View File
@@ -0,0 +1,46 @@
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyUIDeployExternalTextAny:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_text"},
),
},
"optional": {
"default_value": (
"STRING",
{"multiline": True, "default": ""},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("text",)
FUNCTION = "run"
CATEGORY = "text"
def run(self, input_id, default_value=None, display_name=None, description=None):
return [default_value]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalTextAny": ComfyUIDeployExternalTextAny}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalTextAny": "External Text Any (ComfyUI Deploy)"}
-43
View File
@@ -1,43 +0,0 @@
import folder_paths
from PIL import Image, ImageOps
import numpy as np
import torch
import json
class ComfyUIDeployExternalTextList:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": 'input_text_list'},
),
"text": (
"STRING",
{"multiline": True, "default": "[]"},
),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
OUTPUT_IS_LIST = (True,)
FUNCTION = "run"
CATEGORY = "text"
def run(self, input_id, text=None):
text_list = []
try:
text_list = json.loads(text) # Assuming text is a JSON array string
except Exception as e:
print(f"Error processing images: {e}")
pass
return [text_list]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalTextList": ComfyUIDeployExternalTextList}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalTextList": "External Text List (ComfyUI Deploy)"}
+14 -5
View File
@@ -764,7 +764,15 @@ class ComfyUIDeployExternalVideo:
"optional": { "optional": {
"meta_batch": ("VHS_BatchManager",), "meta_batch": ("VHS_BatchManager",),
"vae": ("VAE",), "vae": ("VAE",),
"default_value": (sorted(files),), "default_video": (sorted(files),),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}, },
"hidden": { "hidden": {
"unique_id": "UNIQUE_ID" "unique_id": "UNIQUE_ID"
@@ -796,8 +804,6 @@ class ComfyUIDeployExternalVideo:
meta_batch = kwargs.get("meta_batch") meta_batch = kwargs.get("meta_batch")
unique_id = kwargs.get("unique_id") unique_id = kwargs.get("unique_id")
video = kwargs.get("default_value")
video_path = folder_paths.get_annotated_filepath(video.strip('"'))
input_dir = folder_paths.get_input_directory() input_dir = folder_paths.get_input_directory()
if input_id.startswith("http"): if input_id.startswith("http"):
@@ -827,8 +833,11 @@ class ComfyUIDeployExternalVideo:
leave=True, leave=True,
): ):
out_file.write(chunk) out_file.write(chunk)
else:
print("video path: ", video_path) video = kwargs.get("default_video", None)
if video is None:
raise "No default video given and no external video provided"
video_path = folder_paths.get_annotated_filepath(video.strip('"'))
return load_video_cv( return load_video_cv(
video=video_path, video=video_path,
+60
View File
@@ -0,0 +1,60 @@
import folder_paths
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
from os import walk
WILDCARD = AnyType("*")
MODEL_EXTENSIONS = {
"safetensors": "SafeTensors file format",
"ckpt": "Checkpoint file",
"pth": "PyTorch serialized file",
"pkl": "Pickle file",
"onnx": "ONNX file",
}
def fetch_files(path):
for (dirpath, dirnames, filenames) in walk(path):
fs = []
if len(dirnames) > 0:
for dirname in dirnames:
fs.extend(fetch_files(f"{dirpath}/{dirname}"))
for filename in filenames:
# Remove "./models/" from the beginning of dirpath
relative_dirpath = dirpath.replace("./models/", "", 1)
file_path = f"{relative_dirpath}/{filename}"
# Only add files that are known model extensions
file_extension = filename.split('.')[-1].lower()
if file_extension in MODEL_EXTENSIONS:
fs.append(file_path)
return fs
allModels = fetch_files("./models")
class ComfyUIDeployModalList:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": (allModels, ),
}
}
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("model",)
FUNCTION = "run"
CATEGORY = "model"
def run(self, model=""):
# Split the model path by '/' and select the last item
model_name = model.split('/')[-1]
return [model_name]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployModelList": ComfyUIDeployModalList}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployModelList": "Model List (ComfyUI Deploy)"}
+856 -317
View File
File diff suppressed because it is too large Load Diff
+28 -8
View File
@@ -6,10 +6,12 @@ from PIL import Image, ImageOps
from io import BytesIO from io import BytesIO
from pydantic import BaseModel as PydanticBaseModel from pydantic import BaseModel as PydanticBaseModel
class BaseModel(PydanticBaseModel): class BaseModel(PydanticBaseModel):
class Config: class Config:
arbitrary_types_allowed = True arbitrary_types_allowed = True
class Status(Enum): class Status(Enum):
NOT_STARTED = "not-started" NOT_STARTED = "not-started"
RUNNING = "running" RUNNING = "running"
@@ -17,6 +19,7 @@ class Status(Enum):
FAILED = "failed" FAILED = "failed"
UPLOADING = "uploading" UPLOADING = "uploading"
class StreamingPrompt(BaseModel): class StreamingPrompt(BaseModel):
workflow_api: Any workflow_api: Any
auth_token: str auth_token: str
@@ -24,42 +27,52 @@ class StreamingPrompt(BaseModel):
running_prompt_ids: set[str] = set() running_prompt_ids: set[str] = set()
status_endpoint: Optional[str] status_endpoint: Optional[str]
file_upload_endpoint: Optional[str] file_upload_endpoint: Optional[str]
workflow: Any
gpu_event_id: Optional[str] = None
class SimplePrompt(BaseModel): class SimplePrompt(BaseModel):
status_endpoint: Optional[str] status_endpoint: Optional[str]
file_upload_endpoint: Optional[str] file_upload_endpoint: Optional[str]
token: Optional[str]
workflow_api: dict workflow_api: dict
status: Status = Status.NOT_STARTED status: Status = Status.NOT_STARTED
progress: set = set() progress: set = set()
last_updated_node: Optional[str] = None, last_updated_node: Optional[str] = None
uploading_nodes: set = set() uploading_nodes: set = set()
done: bool = False done: bool = False
is_realtime: bool = False, is_realtime: bool = False
start_time: Optional[float] = None, start_time: Optional[float] = None
gpu_event_id: Optional[str] = None
sockets = dict() sockets = dict()
prompt_metadata: dict[str, SimplePrompt] = {} prompt_metadata: dict[str, SimplePrompt] = {}
streaming_prompt_metadata: dict[str, StreamingPrompt] = {} streaming_prompt_metadata: dict[str, StreamingPrompt] = {}
class BinaryEventTypes: class BinaryEventTypes:
PREVIEW_IMAGE = 1 PREVIEW_IMAGE = 1
UNENCODED_PREVIEW_IMAGE = 2 UNENCODED_PREVIEW_IMAGE = 2
max_output_id_length = 24 max_output_id_length = 24
async def send_image(image_data, sid=None, output_id:str = None):
async def send_image(image_data, sid=None, output_id: str = None):
max_length = max_output_id_length max_length = max_output_id_length
output_id = output_id[:max_length] output_id = output_id[:max_length]
padded_output_id = output_id.ljust(max_length, '\x00') padded_output_id = output_id.ljust(max_length, "\x00")
encoded_output_id = padded_output_id.encode('ascii', 'replace') encoded_output_id = padded_output_id.encode("ascii", "replace")
image_type = image_data[0] image_type = image_data[0]
image = image_data[1] image = image_data[1]
max_size = image_data[2] max_size = image_data[2]
quality = image_data[3] quality = image_data[3]
if max_size is not None: if max_size is not None:
if hasattr(Image, 'Resampling'): if hasattr(Image, "Resampling"):
resampling = Image.Resampling.BILINEAR resampling = Image.Resampling.BILINEAR
else: else:
resampling = Image.ANTIALIAS resampling = Image.ANTIALIAS
@@ -88,12 +101,18 @@ async def send_image(image_data, sid=None, output_id:str = None):
preview_bytes = bytesIO.getvalue() preview_bytes = bytesIO.getvalue()
await send_bytes(BinaryEventTypes.PREVIEW_IMAGE, preview_bytes, sid=sid) await send_bytes(BinaryEventTypes.PREVIEW_IMAGE, preview_bytes, sid=sid)
async def send_socket_catch_exception(function, message): async def send_socket_catch_exception(function, message):
try: try:
await function(message) await function(message)
except (aiohttp.ClientError, aiohttp.ClientPayloadError, ConnectionResetError) as err: except (
aiohttp.ClientError,
aiohttp.ClientPayloadError,
ConnectionResetError,
) as err:
print("send error:", err) print("send error:", err)
def encode_bytes(event, data): def encode_bytes(event, data):
if not isinstance(event, int): if not isinstance(event, int):
raise RuntimeError(f"Binary event types must be integers, got {event}") raise RuntimeError(f"Binary event types must be integers, got {event}")
@@ -103,6 +122,7 @@ def encode_bytes(event, data):
message.extend(data) message.extend(data)
return message return message
async def send_bytes(event, data, sid=None): async def send_bytes(event, data, sid=None):
message = encode_bytes(event, data) message = encode_bytes(event, data)
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-deploy" name = "comfyui-deploy"
description = "Open source comfyui deployment platform, a vercel for generative workflow infra." description = "Open source comfyui deployment platform, a vercel for generative workflow infra."
version = "1.0.0" version = "1.1.0"
license = "LICENSE" license = "LICENSE"
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg"] dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg"]
+1
View File
@@ -2,4 +2,5 @@ aiofiles
pydantic pydantic
opencv-python opencv-python
imageio-ffmpeg imageio-ffmpeg
brotli
# logfire # logfire
+447 -168
View File
@@ -2,6 +2,7 @@ import { app } from "./app.js";
import { api } from "./api.js"; import { api } from "./api.js";
import { ComfyWidgets, LGraphNode } from "./widgets.js"; import { ComfyWidgets, LGraphNode } from "./widgets.js";
import { generateDependencyGraph } from "https://esm.sh/[email protected]"; import { generateDependencyGraph } from "https://esm.sh/[email protected]";
import { ComfyDeploy } from "https://esm.sh/[email protected]";
const loadingIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg>`; const loadingIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg>`;
@@ -24,10 +25,7 @@ function dispatchAPIEventData(data) {
message += "\n" + nodeError.class_type + ":"; message += "\n" + nodeError.class_type + ":";
for (const errorReason of nodeError.errors) { for (const errorReason of nodeError.errors) {
message += message +=
"\n - " + "\n - " + errorReason.message + ": " + errorReason.details;
errorReason.message +
": " +
errorReason.details;
} }
} }
@@ -47,38 +45,32 @@ function dispatchAPIEventData(data) {
// window.name = this.clientId; // use window name so it isnt reused when duplicating tabs // 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 // sessionStorage.setItem("clientId", this.clientId); // store in session storage so duplicate tab can load correct workflow
} }
api.dispatchEvent( api.dispatchEvent(new CustomEvent("status", { detail: msg.data.status }));
new CustomEvent("status", { detail: msg.data.status })
);
break; break;
case "progress": case "progress":
api.dispatchEvent( api.dispatchEvent(new CustomEvent("progress", { detail: msg.data }));
new CustomEvent("progress", { detail: msg.data })
);
break; break;
case "executing": case "executing":
api.dispatchEvent( api.dispatchEvent(
new CustomEvent("executing", { detail: msg.data.node }) new CustomEvent("executing", { detail: msg.data.node }),
); );
break; break;
case "executed": case "executed":
api.dispatchEvent( api.dispatchEvent(new CustomEvent("executed", { detail: msg.data }));
new CustomEvent("executed", { detail: msg.data })
);
break; break;
case "execution_start": case "execution_start":
api.dispatchEvent( api.dispatchEvent(
new CustomEvent("execution_start", { detail: msg.data }) new CustomEvent("execution_start", { detail: msg.data }),
); );
break; break;
case "execution_error": case "execution_error":
api.dispatchEvent( api.dispatchEvent(
new CustomEvent("execution_error", { detail: msg.data }) new CustomEvent("execution_error", { detail: msg.data }),
); );
break; break;
case "execution_cached": case "execution_cached":
api.dispatchEvent( api.dispatchEvent(
new CustomEvent("execution_cached", { detail: msg.data }) new CustomEvent("execution_cached", { detail: msg.data }),
); );
break; break;
default: default:
@@ -91,11 +83,52 @@ function dispatchAPIEventData(data) {
} }
} }
const context = {
selectedWorkflowInfo: null,
};
// let selectedWorkflowInfo = {
// workflow_id: "05da8f2b-63af-4c0c-86dd-08d01ec512b7",
// machine_id: "45ac5f85-b7b6-436f-8d97-2383b25485f3",
// native_run_api_endpoint: "http://localhost:3011/api/run",
// };
async function getSelectedWorkflowInfo() {
const workflow_info_promise = new Promise((resolve) => {
try {
const handleMessage = (event) => {
try {
const message = JSON.parse(event.data);
if (message.type === "workflow_info") {
resolve(message.data);
window.removeEventListener("message", handleMessage);
}
} catch (error) {
console.error(error);
resolve(undefined);
}
};
window.addEventListener("message", handleMessage);
sendEventToCD("workflow_info");
} catch (error) {
console.error(error);
resolve(undefined);
}
});
return workflow_info_promise;
}
function setSelectedWorkflowInfo(info) {
context.selectedWorkflowInfo = info;
}
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/ /** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
/** @type {ComfyExtension} */ /** @type {ComfyExtension} */
const ext = { const ext = {
name: "BennyKok.ComfyUIDeploy", name: "BennyKok.ComfyUIDeploy",
native_mode: false,
init(app) { init(app) {
addButton(); addButton();
@@ -105,16 +138,17 @@ const ext = {
const org_display = queryParams.get("org_display"); const org_display = queryParams.get("org_display");
const origin = queryParams.get("origin"); const origin = queryParams.get("origin");
const workspace_mode = queryParams.get("workspace_mode"); const workspace_mode = queryParams.get("workspace_mode");
this.native_mode = queryParams.get("native_mode") === "true";
if (workspace_mode) { if (workspace_mode) {
document.querySelector(".comfy-menu").style.display = "none"; document.querySelector(".comfy-menu").style.display = "none";
sendEventToCD("cd_plugin_onInit"); sendEventToCD("cd_plugin_onInit");
app.queuePrompt = ((originalFunction) => async () => { // app.queuePrompt = ((originalFunction) => async () => {
// const prompt = await app.graphToPrompt(); // // const prompt = await app.graphToPrompt();
sendEventToCD("cd_plugin_onQueuePromptTrigger"); // sendEventToCD("cd_plugin_onQueuePromptTrigger");
})(app.queuePrompt); // })(app.queuePrompt);
// // Intercept the onkeydown event // // Intercept the onkeydown event
// window.addEventListener( // window.addEventListener(
@@ -152,13 +186,11 @@ const ext = {
} }
if (!workflow_version_id) { if (!workflow_version_id) {
console.error( console.error("No workflow_version_id provided in query parameters.");
"No workflow_version_id provided in query parameters."
);
} else { } else {
loadingDialog.showLoading( loadingDialog.showLoading(
"Loading workflow from " + org_display, "Loading workflow from " + org_display,
"Please wait..." "Please wait...",
); );
fetch(endpoint + "/api/workflow-version/" + workflow_version_id, { fetch(endpoint + "/api/workflow-version/" + workflow_version_id, {
method: "GET", method: "GET",
@@ -171,10 +203,7 @@ const ext = {
const data = await res.json(); const data = await res.json();
const { workflow, workflow_id, error } = data; const { workflow, workflow_id, error } = data;
if (error) { if (error) {
infoDialog.showMessage( infoDialog.showMessage("Unable to load this workflow", error);
"Unable to load this workflow",
error
);
return; return;
} }
@@ -197,7 +226,7 @@ const ext = {
window.history.replaceState( window.history.replaceState(
{}, {},
document.title, document.title,
window.location.pathname window.location.pathname,
); );
}); });
} }
@@ -205,11 +234,13 @@ const ext = {
registerCustomNodes() { registerCustomNodes() {
/** @type {LGraphNode}*/ /** @type {LGraphNode}*/
class ComfyDeploy { class ComfyDeploy extends LGraphNode {
color = LGraphCanvas.node_colors.yellow.color;
bgcolor = LGraphCanvas.node_colors.yellow.bgcolor;
groupcolor = LGraphCanvas.node_colors.yellow.groupcolor;
constructor() { constructor() {
super();
this.color = LGraphCanvas.node_colors.yellow.color;
this.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor;
this.groupcolor = LGraphCanvas.node_colors.yellow.groupcolor;
if (!this.properties) { if (!this.properties) {
this.properties = {}; this.properties = {};
this.properties.workflow_name = ""; this.properties.workflow_name = "";
@@ -217,67 +248,77 @@ const ext = {
this.properties.version = ""; this.properties.version = "";
} }
ComfyWidgets.STRING( this.addWidget(
this, "text",
"workflow_name", "workflow_name",
[ this.properties.workflow_name,
"", (v) => {
{ this.properties.workflow_name = v;
default: this.properties.workflow_name,
multiline: false,
}, },
], { multiline: false },
app
); );
ComfyWidgets.STRING( this.addWidget(
this, "text",
"workflow_id", "workflow_id",
[ this.properties.workflow_id,
"", (v) => {
{ this.properties.workflow_id = v;
default: this.properties.workflow_id,
multiline: false,
}, },
], { multiline: false },
app
); );
ComfyWidgets.STRING( this.addWidget(
this, "text",
"version", "version",
[ this.properties.version,
"", (v) => {
{ default: this.properties.version, multiline: false }, this.properties.version = v;
], },
app { multiline: false },
); );
// this.widgets.forEach((w) => {
// // w.computeSize = () => [200,10]
// w.computedHeight = 2;
// })
this.widgets_start_y = 10; this.widgets_start_y = 10;
this.setSize(this.computeSize());
// const config = { };
// console.log(this);
this.serialize_widgets = true; this.serialize_widgets = true;
this.isVirtualNode = true; this.isVirtualNode = true;
} }
onExecute() {
// This method is called when the node is executed
// You can add any necessary logic here
} }
// Load default visibility onSerialize(o) {
// This method is called when the node is being serialized
// Ensure all necessary data is saved
if (!o.properties) {
o.properties = {};
}
o.properties.workflow_name = this.properties.workflow_name;
o.properties.workflow_id = this.properties.workflow_id;
o.properties.version = this.properties.version;
}
onConfigure(o) {
// This method is called when the node is being configured (e.g., when loading a saved graph)
// Ensure all necessary data is restored
if (o.properties) {
this.properties = { ...this.properties, ...o.properties };
this.widgets[0].value = this.properties.workflow_name || "";
this.widgets[1].value = this.properties.workflow_id || "";
this.widgets[2].value = this.properties.version || "1";
}
}
}
// Register the node type
LiteGraph.registerNodeType( LiteGraph.registerNodeType(
"ComfyDeploy", "ComfyDeploy",
Object.assign(ComfyDeploy, { Object.assign(ComfyDeploy, {
title_mode: LiteGraph.NORMAL_TITLE,
title: "Comfy Deploy", title: "Comfy Deploy",
title_mode: LiteGraph.NORMAL_TITLE,
collapsable: true, collapsable: true,
}) }),
); );
ComfyDeploy.category = "deploy"; ComfyDeploy.category = "deploy";
@@ -297,6 +338,17 @@ const ext = {
// This part of the code would depend on how the ComfyUI expects to receive and process the workflow data // 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 // For demonstration, let's assume there's a loadWorkflow method in the ComfyUI API
if (comfyUIWorkflow && app && app.loadGraphData) { if (comfyUIWorkflow && app && app.loadGraphData) {
try {
await window["app"].ui.settings.setSettingValueAsync(
"Comfy.Validation.Workflows",
false,
);
} catch (error) {
console.warning(
"Error setting validation to false, is fine to ignore this",
error,
);
}
console.log("loadGraphData"); console.log("loadGraphData");
app.loadGraphData(comfyUIWorkflow); app.loadGraphData(comfyUIWorkflow);
} }
@@ -310,9 +362,7 @@ const ext = {
if (typeof api.handlePromptGenerated === "function") { if (typeof api.handlePromptGenerated === "function") {
api.handlePromptGenerated(prompt); api.handlePromptGenerated(prompt);
} else { } else {
console.warn( console.warn("api.handlePromptGenerated is not a function");
"api.handlePromptGenerated is not a function"
);
} }
sendEventToCD("cd_plugin_onQueuePrompt", prompt); sendEventToCD("cd_plugin_onQueuePrompt", prompt);
} else if (message.type === "get_prompt") { } else if (message.type === "get_prompt") {
@@ -346,13 +396,9 @@ const ext = {
const canvas = app.canvas; const canvas = app.canvas;
const targetScale = 1; const targetScale = 1;
const targetOffsetX = const targetOffsetX =
canvas.canvas.width / 4 - canvas.canvas.width / 4 - position[0] - node.size[0] / 2;
position[0] -
node.size[0] / 2;
const targetOffsetY = const targetOffsetY =
canvas.canvas.height / 4 - canvas.canvas.height / 4 - position[1] - node.size[1] / 2;
position[1] -
node.size[1] / 2;
const startScale = canvas.ds.scale; const startScale = canvas.ds.scale;
const startOffsetX = canvas.ds.offset[0]; const startOffsetX = canvas.ds.offset[0];
@@ -376,21 +422,9 @@ const ext = {
const easedT = easeOutCubic(t); const easedT = easeOutCubic(t);
const currentScale = lerp( const currentScale = lerp(startScale, targetScale, easedT);
startScale, const currentOffsetX = lerp(startOffsetX, targetOffsetX, easedT);
targetScale, const currentOffsetY = lerp(startOffsetY, targetOffsetY, easedT);
easedT
);
const currentOffsetX = lerp(
startOffsetX,
targetOffsetX,
easedT
);
const currentOffsetY = lerp(
startOffsetY,
targetOffsetY,
easedT
);
canvas.setZoom(currentScale); canvas.setZoom(currentScale);
canvas.ds.offset = [currentOffsetX, currentOffsetY]; canvas.ds.offset = [currentOffsetX, currentOffsetY];
@@ -402,6 +436,8 @@ const ext = {
} }
animate(); animate();
} else if (message.type === "workflow_info") {
setSelectedWorkflowInfo(message.data);
} }
// else if (message.type === "refresh") { // else if (message.type === "refresh") {
// sendEventToCD("cd_plugin_onRefresh"); // sendEventToCD("cd_plugin_onRefresh");
@@ -421,6 +457,25 @@ const ext = {
// } // }
}); });
if (this.native_mode) {
// console.log("native mode", window, window.app);
try {
await app.ui.settings.setSettingValueAsync("Comfy.UseNewMenu", "Top");
await app.ui.settings.setSettingValueAsync(
"Comfy.Sidebar.Size",
"small"
);
await app.ui.settings.setSettingValueAsync(
"Comfy.Sidebar.Location",
"right"
);
localStorage.setItem("Comfy.MenuPosition.Docked", "true");
console.log("native mode manmanman");
} catch (error) {
console.error("Error setting validation to false", error);
}
}
app.graph.onAfterChange = ((originalFunction) => app.graph.onAfterChange = ((originalFunction) =>
async function () { async function () {
const prompt = await app.graphToPrompt(); const prompt = await app.graphToPrompt();
@@ -442,7 +497,7 @@ const ext = {
function showError(title, message) { function showError(title, message) {
infoDialog.show( infoDialog.show(
`<h3 style="margin: 0px; color: red;">${title}</h3><br><span>${message}</span> ` `<h3 style="margin: 0px; color: red;">${title}</h3><br><span>${message}</span> `,
); );
} }
@@ -532,6 +587,7 @@ function createDynamicUIHtml(data) {
return html; return html;
} }
// Modify the existing deployWorkflow function
async function deployWorkflow() { async function deployWorkflow() {
const deploy = document.getElementById("deploy-button"); const deploy = document.getElementById("deploy-button");
@@ -550,7 +606,7 @@ async function deployWorkflow() {
if (deployMeta.length == 0) { if (deployMeta.length == 0) {
const text = await inputDialog.input( const text = await inputDialog.input(
"Create your deployment", "Create your deployment",
"Workflow name" "Workflow name",
); );
if (!text) return; if (!text) return;
console.log(text); console.log(text);
@@ -591,7 +647,7 @@ async function deployWorkflow() {
<input id="reuse-hash" type="checkbox" checked>Reuse hash from last version</input> <input id="reuse-hash" type="checkbox" checked>Reuse hash from last version</input>
</label> </label>
</div> </div>
` `,
); );
if (!ok) return; if (!ok) return;
@@ -610,7 +666,7 @@ async function deployWorkflow() {
if (!snapshot) { if (!snapshot) {
showError( showError(
"Error when deploying", "Error when deploying",
"Unable to generate snapshot, please install ComfyUI Manager" "Unable to generate snapshot, please install ComfyUI Manager",
); );
return; return;
} }
@@ -631,7 +687,7 @@ async function deployWorkflow() {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Bearer " + apiKey, Authorization: "Bearer " + apiKey,
}, },
} },
) )
.then((x) => x.json()) .then((x) => x.json())
.catch(() => { .catch(() => {
@@ -650,7 +706,7 @@ async function deployWorkflow() {
// Match previous hash for models // Match previous hash for models
if (reuseHash && existing_workflow?.dependencies?.models) { if (reuseHash && existing_workflow?.dependencies?.models) {
const previousModelHash = Object.entries( const previousModelHash = Object.entries(
existing_workflow?.dependencies?.models existing_workflow?.dependencies?.models,
).flatMap(([key, value]) => { ).flatMap(([key, value]) => {
return Object.values(value).map((x) => ({ return Object.values(value).map((x) => ({
...x, ...x,
@@ -672,44 +728,36 @@ async function deployWorkflow() {
console.log(file); console.log(file);
loadingDialog.showLoading("Generating hash", file); loadingDialog.showLoading("Generating hash", file);
const hash = await fetch( const hash = await fetch(
`/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent( `/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent(file)}`,
file
)}`
).then((x) => x.json()); ).then((x) => x.json());
loadingDialog.showLoading("Generating hash", file); loadingDialog.showLoading("Generating hash", file);
console.log(hash); console.log(hash);
return hash.file_hash; return hash.file_hash;
}, },
handleFileUpload: async (file, hash, prevhash) => { // handleFileUpload: async (file, hash, prevhash) => {
console.log("Uploading ", file); // console.log("Uploading ", file);
loadingDialog.showLoading("Uploading file", file); // loadingDialog.showLoading("Uploading file", file);
try { // try {
const { download_url } = await fetch( // const { download_url } = await fetch(`/comfyui-deploy/upload-file`, {
`/comfyui-deploy/upload-file`, // method: "POST",
{ // body: JSON.stringify({
method: "POST", // file_path: file,
body: JSON.stringify({ // token: apiKey,
file_path: file, // url: endpoint + "/api/upload-url",
token: apiKey, // }),
url: endpoint + "/api/upload-url", // })
}), // .then((x) => x.json())
} // .catch(() => {
) // loadingDialog.close();
.then((x) => x.json()) // confirmDialog.confirm("Error", "Unable to upload file " + file);
.catch(() => { // });
loadingDialog.close(); // loadingDialog.showLoading("Uploaded file", file);
confirmDialog.confirm( // console.log(download_url);
"Error", // return download_url;
"Unable to upload file " + file // } catch (error) {
); // return undefined;
}); // }
loadingDialog.showLoading("Uploaded file", file); // },
console.log(download_url);
return download_url;
} catch (error) {
return undefined;
}
},
existingDependencies: existing_workflow.dependencies, existingDependencies: existing_workflow.dependencies,
}); });
@@ -734,12 +782,21 @@ async function deployWorkflow() {
"Check dependencies", "Check dependencies",
// JSON.stringify(deps, null, 2), // JSON.stringify(deps, null, 2),
` `
<div>
You will need to create a cloud machine with the following configuration on ComfyDeploy
<ol style="text-align: left; margin-top: 10px;">
<li>Review the dependencies listed in the graph below</li>
<li>Create a new cloud machine with the required configuration</li>
<li>Install missing models and check missing files</li>
<li>Deploy your workflow to the newly created machine</li>
</ol>
</div>
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">${loadingIcon}</div> <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">${loadingIcon}</div>
<iframe <iframe
style="z-index: 10; min-width: 600px; max-width: 1024px; min-height: 600px; border: none; background-color: transparent;" style="z-index: 10; min-width: 600px; max-width: 1024px; min-height: 600px; border: none; background-color: transparent;"
src="https://www.comfydeploy.com/dependency-graph?deps=${encodeURIComponent( src="https://www.comfydeploy.com/dependency-graph?deps=${encodeURIComponent(
JSON.stringify(deps) JSON.stringify(deps),
)}" />` )}" />`,
// createDynamicUIHtml(deps), // createDynamicUIHtml(deps),
); );
if (!depsOk) return; if (!depsOk) return;
@@ -800,9 +857,17 @@ async function deployWorkflow() {
graph.change(); graph.change();
infoDialog.show( infoDialog.show(
`<span style="color:green;">Deployed successfully!</span> <a style="color:white;" target="_blank" href=${endpoint}/workflows/${data.workflow_id}>-> View here</a> <br/> <br/> Workflow ID: ${data.workflow_id} <br/> Workflow Name: ${workflow_name} <br/> Workflow Version: ${data.version} <br/>` `<span style="color:green;">Deployed successfully!</span> <a style="color:white;" target="_blank" href=${endpoint}/workflows/${data.workflow_id}>-> View here</a> <br/> <br/> Workflow ID: ${data.workflow_id} <br/> Workflow Name: ${workflow_name} <br/> Workflow Version: ${data.version} <br/>`,
); );
// // Refresh the workflows list in the sidebar
// const sidebarEl = document.querySelector(
// '.comfy-sidebar-tab[data-id="search"]',
// );
// if (sidebarEl) {
// refreshWorkflowsList(sidebarEl);
// }
setTimeout(() => { setTimeout(() => {
title.textContent = "Deploy"; title.textContent = "Deploy";
title.style.color = "white"; title.style.color = "white";
@@ -820,6 +885,85 @@ async function deployWorkflow() {
} }
} }
// Add this function to refresh the workflows list
function refreshWorkflowsList(el) {
const workflowsList = el.querySelector("#workflows-list");
const workflowsLoading = el.querySelector("#workflows-loading");
workflowsLoading.style.display = "flex";
workflowsList.style.display = "none";
workflowsList.innerHTML = "";
client.workflows
.getAll({
page: "1",
pageSize: "10",
})
.then((result) => {
workflowsLoading.style.display = "none";
workflowsList.style.display = "block";
if (result.length === 0) {
workflowsList.innerHTML =
"<li style='color: #bdbdbd;'>No workflows found</li>";
return;
}
result.forEach((workflow) => {
const li = document.createElement("li");
li.style.marginBottom = "15px";
li.style.padding = "15px";
li.style.backgroundColor = "#2a2a2a";
li.style.borderRadius = "8px";
li.style.boxShadow = "0 2px 4px rgba(0,0,0,0.1)";
const lastRun = workflow.runs[0];
const lastRunStatus = lastRun ? lastRun.status : "No runs";
const statusColor =
lastRunStatus === "success"
? "#4CAF50"
: lastRunStatus === "error"
? "#F44336"
: "#FFC107";
const timeAgo = getTimeAgo(new Date(workflow.updatedAt));
li.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<strong style="font-size: 18px; color: #e0e0e0;">${workflow.name}</strong>
</div>
<span style="font-size: 12px; color: ${statusColor}; margin-left: 10px;">Last run: ${lastRunStatus}</span>
</div>
<div style="font-size: 14px; color: #bdbdbd; margin-bottom: 10px;">Last updated ${timeAgo}</div>
<div style="display: flex; gap: 10px;">
<button class="open-cloud-btn" style="padding: 5px 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">Open in Cloud</button>
<button class="load-api-btn" style="padding: 5px 10px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;">Load Workflow</button>
</div>
`;
const openCloudBtn = li.querySelector(".open-cloud-btn");
openCloudBtn.onclick = () =>
window.open(
`${getData().endpoint}/workflows/${workflow.id}?workspace=true`,
"_blank",
);
const loadApiBtn = li.querySelector(".load-api-btn");
loadApiBtn.onclick = () => loadWorkflowApi(workflow.versions[0].id);
workflowsList.appendChild(li);
});
})
.catch((error) => {
console.error("Error fetching workflows:", error);
workflowsLoading.style.display = "none";
workflowsList.style.display = "block";
workflowsList.innerHTML =
"<li style='color: #F44336;'>Error fetching workflows</li>";
});
}
function addButton() { function addButton() {
const menu = document.querySelector(".comfy-menu"); const menu = document.querySelector(".comfy-menu");
@@ -997,22 +1141,17 @@ export class InputDialog extends InfoDialog {
type: "button", type: "button",
textContent: "Save", textContent: "Save",
onclick: () => { onclick: () => {
const input = const input = this.textElement.querySelector("#input").value;
this.textElement.querySelector("#input").value;
if (input.trim() === "") { if (input.trim() === "") {
showError( showError("Input validation", "Input cannot be empty");
"Input validation",
"Input cannot be empty"
);
} else { } else {
this.callback?.(input); this.callback?.(input);
this.close(); this.close();
this.textElement.querySelector("#input").value = this.textElement.querySelector("#input").value = "";
"";
} }
}, },
}), }),
] ],
), ),
]; ];
} }
@@ -1073,7 +1212,7 @@ export class ConfirmDialog extends InfoDialog {
this.close(); this.close();
}, },
}), }),
] ],
), ),
]; ];
} }
@@ -1130,7 +1269,7 @@ function getData(environment) {
function saveData(data) { function saveData(data) {
localStorage.setItem( localStorage.setItem(
"comfy_deploy_env_data_" + data.environment, "comfy_deploy_env_data_" + data.environment,
JSON.stringify(data) JSON.stringify(data),
); );
} }
@@ -1145,9 +1284,7 @@ export class ConfigDialog extends ComfyDialog {
this.element.style.paddingBottom = "20px"; this.element.style.paddingBottom = "20px";
this.container = document.createElement("div"); this.container = document.createElement("div");
this.element this.element.querySelector(".comfy-modal-content").prepend(this.container);
.querySelector(".comfy-modal-content")
.prepend(this.container);
} }
createButtons() { createButtons() {
@@ -1181,7 +1318,7 @@ export class ConfigDialog extends ComfyDialog {
this.close(); this.close();
}, },
}), }),
] ],
), ),
]; ];
} }
@@ -1193,8 +1330,7 @@ export class ConfigDialog extends ComfyDialog {
} }
save(api_key, displayName) { save(api_key, displayName) {
const deployOption = const deployOption = this.container.querySelector("#deployOption").value;
this.container.querySelector("#deployOption").value;
localStorage.setItem("comfy_deploy_env", deployOption); localStorage.setItem("comfy_deploy_env", deployOption);
const endpoint = this.container.querySelector("#endpoint").value; const endpoint = this.container.querySelector("#endpoint").value;
@@ -1226,12 +1362,8 @@ export class ConfigDialog extends ComfyDialog {
<h3 style="margin: 0px;">Comfy Deploy Config</h3> <h3 style="margin: 0px;">Comfy Deploy Config</h3>
<label style="color: white; width: 100%;"> <label style="color: white; width: 100%;">
<select id="deployOption" style="margin: 8px 0px; width: 100%; height:30px; box-sizing: border-box;" > <select id="deployOption" style="margin: 8px 0px; width: 100%; height:30px; box-sizing: border-box;" >
<option value="cloud" ${ <option value="cloud" ${data.environment === "cloud" ? "selected" : ""}>Cloud</option>
data.environment === "cloud" ? "selected" : "" <option value="local" ${data.environment === "local" ? "selected" : ""}>Local</option>
}>Cloud</option>
<option value="local" ${
data.environment === "local" ? "selected" : ""
}>Local</option>
</select> </select>
</label> </label>
<label style="color: white; width: 100%;"> <label style="color: white; width: 100%;">
@@ -1249,9 +1381,7 @@ export class ConfigDialog extends ComfyDialog {
}"> }">
<button id="loginButton" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;"> <button id="loginButton" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;">
${ ${
data.apiKey data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
? "Re-login with ComfyDeploy"
: "Login with ComfyDeploy"
} }
</button> </button>
</div> </div>
@@ -1272,7 +1402,7 @@ export class ConfigDialog extends ComfyDialog {
clearInterval(poll); clearInterval(poll);
infoDialog.showMessage( infoDialog.showMessage(
"Timeout", "Timeout",
"Wait too long for the response, please try re-login" "Wait too long for the response, please try re-login",
); );
}, 30000); // Stop polling after 30 seconds }, 30000); // Stop polling after 30 seconds
@@ -1283,15 +1413,14 @@ export class ConfigDialog extends ComfyDialog {
if (json.api_key) { if (json.api_key) {
this.save(json.api_key, json.name); this.save(json.api_key, json.name);
this.close(); this.close();
this.container.querySelector("#apiKey").value = this.container.querySelector("#apiKey").value = json.api_key;
json.api_key;
// infoDialog.show(); // infoDialog.show();
clearInterval(this.poll); clearInterval(this.poll);
clearTimeout(this.timeout); clearTimeout(this.timeout);
// Refresh dialog // Refresh dialog
const a = await confirmDialog.confirm( const a = await confirmDialog.confirm(
"Authenticated", "Authenticated",
`<div>You will be able to upload workflow to <button style="font-size: 18px; width: fit;">${json.name}</button></div>` `<div>You will be able to upload workflow to <button style="font-size: 18px; width: fit;">${json.name}</button></div>`,
); );
configDialog.show(); configDialog.show();
} }
@@ -1327,3 +1456,153 @@ export class ConfigDialog extends ComfyDialog {
} }
export const configDialog = new ConfigDialog(); export const configDialog = new ConfigDialog();
const currentOrigin = window.location.origin;
const client = new ComfyDeploy({
bearerAuth: getData().apiKey,
serverURL: `${currentOrigin}/comfydeploy/api/`,
});
app.extensionManager.registerSidebarTab({
id: "search",
icon: "pi pi-cloud-upload",
title: "Deploy",
tooltip: "Deploy and Configure",
type: "custom",
render: (el) => {
el.innerHTML = `
<div style="padding: 20px;">
<h3>Comfy Deploy</h3>
<div id="deploy-container" style="margin-bottom: 20px;"></div>
<div id="workflows-container">
<h4>Your Workflows</h4>
<div id="workflows-loading" style="display: flex; justify-content: center; align-items: center; height: 100px;">
${loadingIcon}
</div>
<ul id="workflows-list" style="list-style-type: none; padding: 0; display: none;"></ul>
</div>
<div id="config-container"></div>
</div>
`;
// Add deploy button
const deployContainer = el.querySelector("#deploy-container");
const deployButton = document.createElement("button");
deployButton.id = "sidebar-deploy-button";
deployButton.style.display = "flex";
deployButton.style.alignItems = "center";
deployButton.style.justifyContent = "center";
deployButton.style.width = "100%";
deployButton.style.marginBottom = "10px";
deployButton.style.padding = "10px";
deployButton.style.fontSize = "16px";
deployButton.style.fontWeight = "bold";
deployButton.style.backgroundColor = "#4CAF50";
deployButton.style.color = "white";
deployButton.style.border = "none";
deployButton.style.borderRadius = "5px";
deployButton.style.cursor = "pointer";
deployButton.innerHTML = `<i class="pi pi-cloud-upload" style="margin-right: 8px;"></i><div id='sidebar-button-title'>Deploy</div>`;
deployButton.onclick = async () => {
await deployWorkflow();
// Refresh the workflows list after deployment
refreshWorkflowsList(el);
};
deployContainer.appendChild(deployButton);
// Add config button
const configContainer = el.querySelector("#config-container");
const configButton = document.createElement("button");
configButton.style.display = "flex";
configButton.style.alignItems = "center";
configButton.style.justifyContent = "center";
configButton.style.width = "100%";
configButton.style.padding = "8px";
configButton.style.fontSize = "14px";
configButton.style.backgroundColor = "#f0f0f0";
configButton.style.color = "#333";
configButton.style.border = "1px solid #ccc";
configButton.style.borderRadius = "5px";
configButton.style.cursor = "pointer";
configButton.innerHTML = `<i class="pi pi-cog" style="margin-right: 8px;"></i>Configure`;
configButton.onclick = () => {
configDialog.show();
};
deployContainer.appendChild(configButton);
// Fetch and display workflows
const workflowsList = el.querySelector("#workflows-list");
const workflowsLoading = el.querySelector("#workflows-loading");
refreshWorkflowsList(el);
},
});
function getTimeAgo(date) {
const seconds = Math.floor((new Date() - date) / 1000);
let interval = seconds / 31536000;
if (interval > 1) return Math.floor(interval) + " years ago";
interval = seconds / 2592000;
if (interval > 1) return Math.floor(interval) + " months ago";
interval = seconds / 86400;
if (interval > 1) return Math.floor(interval) + " days ago";
interval = seconds / 3600;
if (interval > 1) return Math.floor(interval) + " hours ago";
interval = seconds / 60;
if (interval > 1) return Math.floor(interval) + " minutes ago";
return Math.floor(seconds) + " seconds ago";
}
async function loadWorkflowApi(versionId) {
try {
const response = await client.comfyui.getWorkflowVersionVersionId({
versionId: versionId,
});
// Implement the logic to load the workflow API into the ComfyUI interface
console.log("Workflow API loaded:", response);
await window["app"].ui.settings.setSettingValueAsync(
"Comfy.Validation.Workflows",
false,
);
app.loadGraphData(response.workflow);
// You might want to update the UI or trigger some action in ComfyUI here
} catch (error) {
console.error("Error loading workflow API:", error);
// Show an error message to the user
}
}
const orginal_fetch_api = api.fetchApi;
api.fetchApi = async (route, options) => {
console.log("Fetch API called with args:", route, options, ext.native_mode);
if (route.startsWith("/prompt") && ext.native_mode) {
const info = await getSelectedWorkflowInfo();
console.log("info", info);
if (info) {
const body = JSON.parse(options.body);
const data = {
client_id: body.client_id,
workflow_api_json: body.prompt,
workflow: body?.extra_data?.extra_pnginfo?.workflow,
is_native_run: true,
machine_id: info.machine_id,
workflow_id: info.workflow_id,
native_run_api_endpoint: info.native_run_api_endpoint,
gpu_event_id: info.gpu_event_id,
};
return await fetch("/comfyui-deploy/run", {
method: "POST",
headers: {
Authorization: `Bearer ${info.cd_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
}
}
return await orginal_fetch_api.call(api, route, options);
};
+1
View File
@@ -6,4 +6,5 @@ export const customInputNodes: Record<string, string> = {
ComfyUIDeployExternalNumberInt: "integer", ComfyUIDeployExternalNumberInt: "integer",
ComfyUIDeployExternalLora: "string - (public lora download url)", ComfyUIDeployExternalLora: "string - (public lora download url)",
ComfyUIDeployExternalCheckpoint: "string - (public checkpoints download url)", ComfyUIDeployExternalCheckpoint: "string - (public checkpoints download url)",
ComfyUIDeployExternalFaceModel: "string - (public face model download url)",
}; };