Compare commits
126
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef50de41e5 | ||
|
|
ac843527d9 | ||
|
|
f39d216326 | ||
|
|
40ec37e58f | ||
|
|
1d63b21643 | ||
|
|
0e3baf22df | ||
|
|
1837065ed2 | ||
|
|
9a8f4795d1 | ||
|
|
c0c617c5d2 | ||
|
|
1e33435ae5 | ||
|
|
04161071f2 | ||
|
|
32d574475c | ||
|
|
1a017ee6a3 | ||
|
|
603223741a | ||
|
|
2bd8b23c60 | ||
|
|
a82e315d6c | ||
|
|
7fdfba6b6e | ||
|
|
0779136134 | ||
|
|
fe116a4655 | ||
|
|
7dd8a7e67e | ||
|
|
778e6fefe6 | ||
|
|
fd310e8478 | ||
|
|
3a3b93d564 | ||
|
|
82c564228d | ||
|
|
ad0a23434b | ||
|
|
292f77f06b | ||
|
|
a139424b91 | ||
|
|
44a91d2093 | ||
|
|
7cff930861 | ||
|
|
ce464c6ce4 | ||
|
|
1c7998c554 | ||
|
|
66d1e42409 | ||
|
|
8882f4983c | ||
|
|
492b81c340 | ||
|
|
8b05ed26c9 | ||
|
|
ce67604926 | ||
|
|
c115c22a91 | ||
|
|
2f33bcf497 | ||
|
|
bcf466c472 | ||
|
|
f812d9d698 | ||
|
|
101b6cca57 | ||
|
|
ae68aae011 | ||
|
|
07926158f0 | ||
|
|
ce92dd0570 | ||
|
|
e2fcf67aec | ||
|
|
79650f48d0 | ||
|
|
69f63f4869 | ||
|
|
50860cd500 | ||
|
|
2eb02fc92e | ||
|
|
5c6defbe62 | ||
|
|
d1c54b2b6d | ||
|
|
3a6c3b1ae9 | ||
|
|
aea456cba9 | ||
|
|
8c5e5c4277 | ||
|
|
02430ee62d | ||
|
|
764a8fee82 | ||
|
|
61acffd355 | ||
|
|
aa47f3523f | ||
|
|
7ed4284a6f | ||
|
|
a403daa314 | ||
|
|
ba9b187dcc | ||
|
|
1243fa4e58 | ||
|
|
0d1537963c | ||
|
|
0083b38dcc | ||
|
|
b8dded1535 | ||
|
|
4927d81e73 | ||
|
|
0e70db4013 | ||
|
|
06805e310d | ||
|
|
fb6bb2357a | ||
|
|
086d642360 | ||
|
|
212daa838c | ||
|
|
c08b68c41f | ||
|
|
5a78ca97bd | ||
|
|
1f5a88b888 | ||
|
|
946571e32e | ||
|
|
e692beb009 | ||
|
|
3d099f88ea | ||
|
|
65f7576748 | ||
|
|
2d72cd8175 | ||
|
|
5554c95f44 | ||
|
|
c1003f7e31 | ||
|
|
71d60a5dd1 | ||
|
|
e011711600 | ||
|
|
4cd7d7a8f9 | ||
|
|
4df9d38e56 | ||
|
|
9cd626e1f6 | ||
|
|
503dca8fb6 | ||
|
|
73c149b4cb | ||
|
|
65b5b0b8c7 | ||
|
|
9d6ee85402 | ||
|
|
cdaed8a571 | ||
|
|
3129e89cce | ||
|
|
7a693eabc8 | ||
|
|
8f677e520d | ||
|
|
4c8d32c5b0 | ||
|
|
a99d2568e0 | ||
|
|
649b61c580 | ||
|
|
edff5685f9 | ||
|
|
9fc0c2b4a2 | ||
|
|
d34e2e99b1 | ||
|
|
f85043db07 | ||
|
|
894d8e1503 | ||
|
|
08d631d1eb | ||
|
|
a1031487e1 | ||
|
|
ca41207192 | ||
|
|
507d5ef631 | ||
|
|
dd1d9df23f | ||
|
|
3a14e49ca5 | ||
|
|
8147c4bfb7 | ||
|
|
10268825d9 | ||
|
|
f6ea252652 | ||
|
|
98cd5ef79c | ||
|
|
4bce5cadfb | ||
|
|
f362671041 | ||
|
|
0582d1d869 | ||
|
|
ce073a86c7 | ||
|
|
3a85a1edf2 | ||
|
|
369c1456a9 | ||
|
|
01e323b7e2 | ||
|
|
db684d044a | ||
|
|
8e12803ea1 | ||
|
|
7585d5049a | ||
|
|
772bb09240 | ||
|
|
9a7e18e651 | ||
|
|
a02c8d237f | ||
|
|
2ba5a0ff3d |
@@ -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]
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
}
|
||||||
@@ -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'):
|
||||||
|
|||||||
@@ -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'):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import numpy as np
|
|||||||
import torch
|
import torch
|
||||||
import folder_paths
|
import folder_paths
|
||||||
|
|
||||||
|
|
||||||
class AnyType(str):
|
class AnyType(str):
|
||||||
def __ne__(self, __value: object) -> bool:
|
def __ne__(self, __value: object) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
WILDCARD = AnyType("*")
|
WILDCARD = AnyType("*")
|
||||||
|
|
||||||
|
|
||||||
class ComfyUIDeployExternalLora:
|
class ComfyUIDeployExternalLora:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
@@ -22,6 +25,22 @@ class ComfyUIDeployExternalLora:
|
|||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"default_lora_name": (folder_paths.get_filename_list("loras"),),
|
"default_lora_name": (folder_paths.get_filename_list("loras"),),
|
||||||
|
"lora_save_name": ( # if `default_lora_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": ""},
|
||||||
|
),
|
||||||
|
"lora_url": (
|
||||||
|
"STRING",
|
||||||
|
{"multiline": False, "default": ""},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,30 +51,55 @@ class ComfyUIDeployExternalLora:
|
|||||||
|
|
||||||
CATEGORY = "deploy"
|
CATEGORY = "deploy"
|
||||||
|
|
||||||
def run(self, input_id, default_lora_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:
|
||||||
unique_filename = str(uuid.uuid4()) + ".safetensors"
|
if lora_url.startswith("http"):
|
||||||
print(unique_filename)
|
if lora_save_name:
|
||||||
|
existing_loras = folder_paths.get_filename_list("loras")
|
||||||
|
# Check if lora_save_name exists in the list
|
||||||
|
if lora_save_name in existing_loras:
|
||||||
|
print(f"using lora: {lora_save_name}")
|
||||||
|
return (lora_save_name,)
|
||||||
|
else:
|
||||||
|
lora_save_name = str(uuid.uuid4()) + ".safetensors"
|
||||||
|
print(lora_save_name)
|
||||||
print(folder_paths.folder_names_and_paths["loras"][0][0])
|
print(folder_paths.folder_names_and_paths["loras"][0][0])
|
||||||
destination_path = os.path.join(
|
destination_path = os.path.join(
|
||||||
folder_paths.folder_names_and_paths["loras"][0][0], unique_filename
|
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,
|
||||||
)
|
)
|
||||||
with open(destination_path, "wb") as out_file:
|
with open(destination_path, "wb") as out_file:
|
||||||
out_file.write(response.content)
|
out_file.write(response.content)
|
||||||
return (unique_filename,)
|
print(f"Ext Lora loading: {lora_url} to {lora_save_name}")
|
||||||
|
return (lora_save_name,)
|
||||||
else:
|
else:
|
||||||
print(f"using lora: {default_lora_name}")
|
print(f"Ext Lora loading: {lora_url}")
|
||||||
|
return (lora_url,)
|
||||||
|
else:
|
||||||
|
print(f"Ext Lora loading: {default_lora_name}")
|
||||||
return (default_lora_name,)
|
return (default_lora_name,)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class StringFunction:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"action": (["append", "replace"], {}),
|
||||||
|
"tidy_tags": (["yes", "no"], {}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"text_a": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||||
|
"text_b": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||||
|
"text_c": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
FUNCTION = "exec"
|
||||||
|
CATEGORY = "utils"
|
||||||
|
OUTPUT_NODE = True
|
||||||
|
|
||||||
|
def exec(self, action, tidy_tags, text_a="", text_b="", text_c=""):
|
||||||
|
tidy_tags = tidy_tags == "yes"
|
||||||
|
out = ""
|
||||||
|
if action == "append":
|
||||||
|
out = (", " if tidy_tags else "").join(
|
||||||
|
filter(None, [text_a, text_b, text_c])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if text_c is None:
|
||||||
|
text_c = ""
|
||||||
|
if text_b.startswith("/") and text_b.endswith("/"):
|
||||||
|
regex = text_b[1:-1]
|
||||||
|
out = re.sub(regex, text_c, text_a)
|
||||||
|
else:
|
||||||
|
out = text_a.replace(text_b, text_c)
|
||||||
|
if tidy_tags:
|
||||||
|
out = re.sub(r"\s{2,}", " ", out)
|
||||||
|
out = out.replace(" ,", ",")
|
||||||
|
out = re.sub(r",{2,}", ",", out)
|
||||||
|
out = out.strip()
|
||||||
|
return {"ui": {"text": (out,)}, "result": (out,)}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"ComfyUIDeployStringCombine": StringFunction,
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
|
"ComfyUIDeployStringCombine": "String Combine (ComfyUI Deploy)",
|
||||||
|
}
|
||||||
@@ -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]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)"}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)"}
|
||||||
+1136
-337
File diff suppressed because it is too large
Load Diff
+28
-8
@@ -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
@@ -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"]
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -2,4 +2,6 @@ aiofiles
|
|||||||
pydantic
|
pydantic
|
||||||
opencv-python
|
opencv-python
|
||||||
imageio-ffmpeg
|
imageio-ffmpeg
|
||||||
logfire
|
brotli
|
||||||
|
tabulate
|
||||||
|
# logfire
|
||||||
+766
-67
@@ -2,6 +2,40 @@ 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 styles = `
|
||||||
|
.comfydeploy-menu-item {
|
||||||
|
background: linear-gradient(to right, rgba(74, 144, 226, 0.9), rgba(103, 178, 111, 0.9)) !important;
|
||||||
|
color: white !important;
|
||||||
|
position: relative !important;
|
||||||
|
padding-left: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comfydeploy-menu-item:hover {
|
||||||
|
filter: brightness(1.1) !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comfydeploy-menu-item::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background-image: url('https://www.comfydeploy.com/icon.svg');
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add stylesheet to document
|
||||||
|
const styleSheet = document.createElement("style");
|
||||||
|
styleSheet.textContent = styles;
|
||||||
|
document.head.appendChild(styleSheet);
|
||||||
|
|
||||||
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>`;
|
||||||
|
|
||||||
@@ -82,11 +116,192 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VALID_TYPES = [
|
||||||
|
"STRING",
|
||||||
|
"combo",
|
||||||
|
"number",
|
||||||
|
"toggle",
|
||||||
|
"BOOLEAN",
|
||||||
|
"text",
|
||||||
|
"string",
|
||||||
|
];
|
||||||
|
|
||||||
|
function hideWidget(node, widget, suffix = "") {
|
||||||
|
if (widget.type?.startsWith(CONVERTED_TYPE)) return;
|
||||||
|
widget.origType = widget.type;
|
||||||
|
widget.origComputeSize = widget.computeSize;
|
||||||
|
widget.origSerializeValue = widget.serializeValue;
|
||||||
|
widget.computeSize = () => [0, -4];
|
||||||
|
widget.type = CONVERTED_TYPE + suffix;
|
||||||
|
widget.serializeValue = () => {
|
||||||
|
if (!node.inputs) {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
let node_input = node.inputs.find((i) => i.widget?.name === widget.name);
|
||||||
|
if (!node_input || !node_input.link) {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
return widget.origSerializeValue
|
||||||
|
? widget.origSerializeValue()
|
||||||
|
: widget.value;
|
||||||
|
};
|
||||||
|
if (widget.linkedWidgets) {
|
||||||
|
for (const w of widget.linkedWidgets) {
|
||||||
|
hideWidget(node, w, ":" + widget.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWidgetType(config) {
|
||||||
|
let type = config[0];
|
||||||
|
if (type instanceof Array) {
|
||||||
|
type = "COMBO";
|
||||||
|
}
|
||||||
|
return { type };
|
||||||
|
}
|
||||||
|
|
||||||
|
const GET_CONFIG = Symbol();
|
||||||
|
|
||||||
|
function convertToInput(node, widget, config) {
|
||||||
|
console.log(node);
|
||||||
|
if (node.type == "LoadImage") {
|
||||||
|
var inputNode = LiteGraph.createNode("ComfyUIDeployExternalImage");
|
||||||
|
console.log(widget);
|
||||||
|
|
||||||
|
const currentOutputsLinks = node.outputs[0].links;
|
||||||
|
|
||||||
|
// const index = node.inputs.findIndex((x) => x.name == widget.name);
|
||||||
|
// console.log(node.widgets_values, index);
|
||||||
|
// inputNode.configure({
|
||||||
|
// widgets_values: ["input_text", widget.value],
|
||||||
|
// });
|
||||||
|
inputNode.pos = node.pos;
|
||||||
|
inputNode.id = ++app.graph.last_node_id;
|
||||||
|
// inputNode.pos[0] += node.size[0] + 40;
|
||||||
|
node.pos[0] -= inputNode.size[0] + 20;
|
||||||
|
console.log(inputNode);
|
||||||
|
console.log(app.graph);
|
||||||
|
app.graph.add(inputNode);
|
||||||
|
|
||||||
|
const links = app.graph.links;
|
||||||
|
|
||||||
|
console.log(currentOutputsLinks);
|
||||||
|
|
||||||
|
for (let i = 0; i < currentOutputsLinks.length; i++) {
|
||||||
|
const link = currentOutputsLinks[i];
|
||||||
|
const llink = links[link];
|
||||||
|
console.log(links[link]);
|
||||||
|
setTimeout(
|
||||||
|
() => inputNode.connect(0, llink.target_id, llink.target_slot),
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.connect(0, inputNode, 0);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideWidget(node, widget);
|
||||||
|
const { type } = getWidgetType(config);
|
||||||
|
const sz = node.size;
|
||||||
|
const inputIsOptional = !!widget.options?.inputIsOptional;
|
||||||
|
const input = node.addInput(widget.name, type, {
|
||||||
|
widget: { name: widget.name, [GET_CONFIG]: () => config },
|
||||||
|
...(inputIsOptional ? { shape: LiteGraph.SlotShape.HollowCircle } : {}),
|
||||||
|
});
|
||||||
|
for (const widget2 of node.widgets) {
|
||||||
|
widget2.last_y += LiteGraph.NODE_SLOT_HEIGHT;
|
||||||
|
}
|
||||||
|
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]);
|
||||||
|
|
||||||
|
if (type == "STRING") {
|
||||||
|
var inputNode = LiteGraph.createNode("ComfyUIDeployExternalText");
|
||||||
|
console.log(widget);
|
||||||
|
const index = node.inputs.findIndex((x) => x.name == widget.name);
|
||||||
|
console.log(node.widgets_values, index);
|
||||||
|
inputNode.configure({
|
||||||
|
widgets_values: ["input_text", widget.value],
|
||||||
|
});
|
||||||
|
inputNode.id = ++app.graph.last_node_id;
|
||||||
|
inputNode.pos = node.pos;
|
||||||
|
inputNode.pos[0] -= node.size[0] + 40;
|
||||||
|
console.log(inputNode);
|
||||||
|
console.log(app.graph);
|
||||||
|
app.graph.add(inputNode);
|
||||||
|
inputNode.connect(0, node, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONVERTED_TYPE = "converted-widget";
|
||||||
|
|
||||||
|
function getConfig(widgetName) {
|
||||||
|
const { nodeData } = this.constructor;
|
||||||
|
return (
|
||||||
|
nodeData?.input?.required?.[widgetName] ??
|
||||||
|
nodeData?.input?.optional?.[widgetName]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConvertibleWidget(widget, config) {
|
||||||
|
return (
|
||||||
|
(VALID_TYPES.includes(widget.type) || VALID_TYPES.includes(config[0])) &&
|
||||||
|
!widget.options?.forceInput
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __name = (target, value) =>
|
||||||
|
__defProp(target, "name", { value, configurable: true });
|
||||||
|
|
||||||
/** @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();
|
||||||
|
|
||||||
@@ -96,16 +311,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(
|
||||||
@@ -189,13 +405,127 @@ const ext = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async beforeRegisterNodeDef(nodeType, nodeData, app2) {
|
||||||
|
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||||
|
nodeType.prototype.getExtraMenuOptions = function (_, options) {
|
||||||
|
const r = origGetExtraMenuOptions
|
||||||
|
? origGetExtraMenuOptions.apply(this, arguments)
|
||||||
|
: void 0;
|
||||||
|
if (this.widgets) {
|
||||||
|
let toInput = [];
|
||||||
|
let toWidget = [];
|
||||||
|
for (const w of this.widgets) {
|
||||||
|
if (w.options?.forceInput) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (w.type === CONVERTED_TYPE) {
|
||||||
|
toWidget.push({
|
||||||
|
content: `Convert ${w.name} to widget`,
|
||||||
|
callback: /* @__PURE__ */ __name(
|
||||||
|
() => convertToWidget(this, w),
|
||||||
|
"callback",
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const config = getConfig.call(this, w.name) ?? [
|
||||||
|
w.type,
|
||||||
|
w.options || {},
|
||||||
|
];
|
||||||
|
if (isConvertibleWidget(w, config)) {
|
||||||
|
toInput.push({
|
||||||
|
content: `Convert ${w.name} to external input`,
|
||||||
|
callback: /* @__PURE__ */ __name(
|
||||||
|
() => convertToInput(this, w, config),
|
||||||
|
"callback",
|
||||||
|
),
|
||||||
|
className: "comfydeploy-menu-item",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toInput.length) {
|
||||||
|
if (true) {
|
||||||
|
options.push();
|
||||||
|
|
||||||
|
let optionIndex = options.findIndex((o) => o.content === "Outputs");
|
||||||
|
if (optionIndex === -1) optionIndex = options.length;
|
||||||
|
else optionIndex++;
|
||||||
|
options.splice(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
// {
|
||||||
|
// content: "[ComfyDeploy] Convert to External Input",
|
||||||
|
// submenu: {
|
||||||
|
// options: toInput,
|
||||||
|
// },
|
||||||
|
// className: "comfydeploy-menu-item"
|
||||||
|
// },
|
||||||
|
...toInput,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
options.push(...toInput, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (toWidget.length) {
|
||||||
|
// if (useConversionSubmenusSetting.value) {
|
||||||
|
// options.push({
|
||||||
|
// content: "Convert Input to Widget",
|
||||||
|
// submenu: {
|
||||||
|
// options: toWidget,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// options.push(...toWidget, null);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
|
// const origonNodeCreated = nodeType.prototype.onNodeCreated;
|
||||||
|
// nodeType.prototype.onNodeCreated = function () {
|
||||||
|
// const r = origonNodeCreated
|
||||||
|
// ? origonNodeCreated.apply(this, arguments)
|
||||||
|
// : void 0;
|
||||||
|
|
||||||
|
// if (!this.widgets) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// console.log(this.widgets);
|
||||||
|
|
||||||
|
// this.widgets.forEach(element => {
|
||||||
|
// if (element.type != "customtext") return
|
||||||
|
|
||||||
|
// console.log(element.element);
|
||||||
|
|
||||||
|
// const parent = element.element.parentElement
|
||||||
|
|
||||||
|
// console.log(element.element.parentElement)
|
||||||
|
// const btn = document.createElement("button");
|
||||||
|
// // const div = document.createElement("div");
|
||||||
|
// // parent.removeChild(element.element)
|
||||||
|
// // div.appendChild(element.element)
|
||||||
|
// // parent.appendChild(div)
|
||||||
|
// // element.element = div
|
||||||
|
// // console.log(element.element);
|
||||||
|
// // btn.style = element.element.style
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return r
|
||||||
|
// };
|
||||||
|
},
|
||||||
|
|
||||||
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 = "";
|
||||||
@@ -203,50 +533,75 @@ const ext = {
|
|||||||
this.properties.version = "";
|
this.properties.version = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
ComfyWidgets.STRING(
|
this.addWidget(
|
||||||
this,
|
"text",
|
||||||
"workflow_name",
|
"workflow_name",
|
||||||
["", { default: this.properties.workflow_name, multiline: false }],
|
this.properties.workflow_name,
|
||||||
app,
|
(v) => {
|
||||||
|
this.properties.workflow_name = v;
|
||||||
|
},
|
||||||
|
{ multiline: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
ComfyWidgets.STRING(
|
this.addWidget(
|
||||||
this,
|
"text",
|
||||||
"workflow_id",
|
"workflow_id",
|
||||||
["", { default: this.properties.workflow_id, multiline: false }],
|
this.properties.workflow_id,
|
||||||
app,
|
(v) => {
|
||||||
|
this.properties.workflow_id = v;
|
||||||
|
},
|
||||||
|
{ multiline: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
ComfyWidgets.STRING(
|
this.addWidget(
|
||||||
this,
|
"text",
|
||||||
"version",
|
"version",
|
||||||
["", { default: this.properties.version, multiline: false }],
|
this.properties.version,
|
||||||
app,
|
(v) => {
|
||||||
|
this.properties.version = v;
|
||||||
|
},
|
||||||
|
{ 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,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -268,6 +623,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);
|
||||||
}
|
}
|
||||||
@@ -278,7 +644,11 @@ const ext = {
|
|||||||
sendEventToCD("cd_plugin_onDeployChanges", prompt);
|
sendEventToCD("cd_plugin_onDeployChanges", prompt);
|
||||||
} else if (message.type === "queue_prompt") {
|
} else if (message.type === "queue_prompt") {
|
||||||
const prompt = await app.graphToPrompt();
|
const prompt = await app.graphToPrompt();
|
||||||
|
if (typeof api.handlePromptGenerated === "function") {
|
||||||
api.handlePromptGenerated(prompt);
|
api.handlePromptGenerated(prompt);
|
||||||
|
} else {
|
||||||
|
console.warn("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") {
|
||||||
const prompt = await app.graphToPrompt();
|
const prompt = await app.graphToPrompt();
|
||||||
@@ -301,6 +671,58 @@ const ext = {
|
|||||||
|
|
||||||
app.graph.add(node);
|
app.graph.add(node);
|
||||||
app.graph.afterChange();
|
app.graph.afterChange();
|
||||||
|
} else if (message.type === "zoom_to_node") {
|
||||||
|
const nodeId = message.data.nodeId;
|
||||||
|
const position = message.data.position;
|
||||||
|
|
||||||
|
const node = app.graph.getNodeById(nodeId);
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const startScale = canvas.ds.scale;
|
||||||
|
const startOffsetX = canvas.ds.offset[0];
|
||||||
|
const startOffsetY = canvas.ds.offset[1];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
@@ -320,6 +742,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();
|
||||||
@@ -375,11 +816,14 @@ function createDynamicUIHtml(data) {
|
|||||||
Object.values(data.custom_nodes).forEach((node) => {
|
Object.values(data.custom_nodes).forEach((node) => {
|
||||||
html += `
|
html += `
|
||||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 16px;">
|
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 16px;">
|
||||||
<a href="${node.url
|
<a href="${
|
||||||
}" target="_blank" style="font-size: 18px; font-weight: semibold; color: white; text-decoration: none;">${node.name
|
node.url
|
||||||
|
}" target="_blank" style="font-size: 18px; font-weight: semibold; color: white; text-decoration: none;">${
|
||||||
|
node.name
|
||||||
}</a>
|
}</a>
|
||||||
<p style="font-size: 14px; color: #4b5563;">${node.hash}</p>
|
<p style="font-size: 14px; color: #4b5563;">${node.hash}</p>
|
||||||
${node.warning
|
${
|
||||||
|
node.warning
|
||||||
? `<p style="font-size: 14px; color: #d69e2e;">${node.warning}</p>`
|
? `<p style="font-size: 14px; color: #d69e2e;">${node.warning}</p>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
@@ -396,7 +840,8 @@ function createDynamicUIHtml(data) {
|
|||||||
Object.entries(data.models).forEach(([section, items]) => {
|
Object.entries(data.models).forEach(([section, items]) => {
|
||||||
html += `
|
html += `
|
||||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
||||||
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${section.charAt(0).toUpperCase() + section.slice(1)
|
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${
|
||||||
|
section.charAt(0).toUpperCase() + section.slice(1)
|
||||||
}</h3>`;
|
}</h3>`;
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
||||||
@@ -413,7 +858,8 @@ function createDynamicUIHtml(data) {
|
|||||||
Object.entries(data.files).forEach(([section, items]) => {
|
Object.entries(data.files).forEach(([section, items]) => {
|
||||||
html += `
|
html += `
|
||||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
||||||
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${section.charAt(0).toUpperCase() + section.slice(1)
|
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${
|
||||||
|
section.charAt(0).toUpperCase() + section.slice(1)
|
||||||
}</h3>`;
|
}</h3>`;
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
||||||
@@ -426,6 +872,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");
|
||||||
|
|
||||||
@@ -572,30 +1019,30 @@ async function deployWorkflow() {
|
|||||||
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(`/comfyui-deploy/upload-file`, {
|
// const { download_url } = await fetch(`/comfyui-deploy/upload-file`, {
|
||||||
method: "POST",
|
// method: "POST",
|
||||||
body: JSON.stringify({
|
// body: JSON.stringify({
|
||||||
file_path: file,
|
// file_path: file,
|
||||||
token: apiKey,
|
// token: apiKey,
|
||||||
url: endpoint + "/api/upload-url",
|
// url: endpoint + "/api/upload-url",
|
||||||
}),
|
// }),
|
||||||
})
|
// })
|
||||||
.then((x) => x.json())
|
// .then((x) => x.json())
|
||||||
.catch(() => {
|
// .catch(() => {
|
||||||
loadingDialog.close();
|
// loadingDialog.close();
|
||||||
confirmDialog.confirm("Error", "Unable to upload file " + file);
|
// confirmDialog.confirm("Error", "Unable to upload file " + file);
|
||||||
});
|
// });
|
||||||
loadingDialog.showLoading("Uploaded file", file);
|
// loadingDialog.showLoading("Uploaded file", file);
|
||||||
console.log(download_url);
|
// console.log(download_url);
|
||||||
return download_url;
|
// return download_url;
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
return undefined;
|
// return undefined;
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
existingDependencies: existing_workflow.dependencies,
|
existingDependencies: existing_workflow.dependencies,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -620,6 +1067,15 @@ 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;"
|
||||||
@@ -689,6 +1145,14 @@ async function deployWorkflow() {
|
|||||||
`<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";
|
||||||
@@ -706,6 +1170,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");
|
||||||
|
|
||||||
@@ -837,9 +1380,11 @@ export class LoadingDialog extends ComfyDialog {
|
|||||||
showLoading(title, message) {
|
showLoading(title, message) {
|
||||||
this.show(`
|
this.show(`
|
||||||
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||||
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${this.loadingIcon
|
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${
|
||||||
|
this.loadingIcon
|
||||||
}</h3>
|
}</h3>
|
||||||
${message
|
${
|
||||||
|
message
|
||||||
? `<label style="max-width: 100%; white-space: pre-wrap; word-wrap: break-word;">${message}</label>`
|
? `<label style="max-width: 100%; white-space: pre-wrap; word-wrap: break-word;">${message}</label>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
@@ -1108,16 +1653,20 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
</label>
|
</label>
|
||||||
<label style="color: white; width: 100%;">
|
<label style="color: white; width: 100%;">
|
||||||
Endpoint:
|
Endpoint:
|
||||||
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${data.endpoint
|
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${
|
||||||
|
data.endpoint
|
||||||
}">
|
}">
|
||||||
</label>
|
</label>
|
||||||
<div style="color: white;">
|
<div style="color: white;">
|
||||||
API Key: User / Org <button style="font-size: 18px;">${data.displayName ?? ""
|
API Key: User / Org <button style="font-size: 18px;">${
|
||||||
|
data.displayName ?? ""
|
||||||
}</button>
|
}</button>
|
||||||
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${data.apiKey
|
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${
|
||||||
|
data.apiKey
|
||||||
}">
|
}">
|
||||||
<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 ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
|
${
|
||||||
|
data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1192,3 +1741,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);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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)",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ const createRunRoute = createRoute({
|
|||||||
export const registerCreateRunRoute = (app: App) => {
|
export const registerCreateRunRoute = (app: App) => {
|
||||||
app.openapi(createRunRoute, async (c) => {
|
app.openapi(createRunRoute, async (c) => {
|
||||||
const data = c.req.valid("json");
|
const data = c.req.valid("json");
|
||||||
const origin = new URL(c.req.url).origin;
|
const proto = c.req.headers.get('x-forwarded-proto') || "http";
|
||||||
|
const host = c.req.headers.get('x-forwarded-host') || c.req.headers.get('host');
|
||||||
|
const origin = `${proto}://${host}` || new URL(c.req.url).origin;
|
||||||
const apiKeyTokenData = c.get("apiKeyTokenData")!;
|
const apiKeyTokenData = c.get("apiKeyTokenData")!;
|
||||||
|
|
||||||
const { deployment_id, inputs } = data;
|
const { deployment_id, inputs } = data;
|
||||||
|
|||||||
Reference in New Issue
Block a user