Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12ddad3cfb |
@@ -1,46 +0,0 @@
|
|||||||
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)"
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
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)"
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
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)"
|
|
||||||
}
|
|
||||||
@@ -39,34 +39,14 @@ class ComfyUIDeployExternalImageBatch:
|
|||||||
|
|
||||||
CATEGORY = "image"
|
CATEGORY = "image"
|
||||||
|
|
||||||
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):
|
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') and img_input.endswith('.zip'):
|
if img_input.startswith('http'):
|
||||||
print("Fetching zip file from url: ", img_input)
|
import requests
|
||||||
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)
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
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)"}
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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": "[]"},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"display_name": (
|
||||||
|
"STRING",
|
||||||
|
{"multiline": False, "default": ""},
|
||||||
|
),
|
||||||
|
"description": (
|
||||||
|
"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, display_name=None, description=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)"}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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)"}
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import folder_paths
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
import folder_paths
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
class AnyType(str):
|
||||||
|
def __ne__(self, __value: object) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
WILDCARD = AnyType("*")
|
||||||
|
|
||||||
|
|
||||||
|
class OuterPortLoadModel:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"ckpt_name": (folder_paths.get_filename_list("checkpoints"), {"tooltip": "The name of the checkpoint (model) to load."}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RETURN_TYPES = ("MODEL", "CLIP", "VAE")
|
||||||
|
OUTPUT_TOOLTIPS = ("The model used for denoising latents.",
|
||||||
|
"The CLIP model used for encoding text prompts.",
|
||||||
|
"The VAE model used for encoding and decoding images to and from latent space.")
|
||||||
|
FUNCTION = "load_checkpoint"
|
||||||
|
|
||||||
|
CATEGORY = "loaders"
|
||||||
|
DESCRIPTION = "Loads a diffusion model checkpoint, diffusion models are used to denoise latents."
|
||||||
|
|
||||||
|
def load_checkpoint(self, ckpt_name):
|
||||||
|
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
|
||||||
|
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
|
||||||
|
return out[:3]
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {"OuterPortLoadModel": OuterPortLoadModel}
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS = {"OuterPortLoadModel": "Outer Port Load Model"}
|
||||||
+332
-735
File diff suppressed because it is too large
Load Diff
+8
-28
@@ -6,12 +6,10 @@ 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"
|
||||||
@@ -19,7 +17,6 @@ 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
|
||||||
@@ -27,52 +24,42 @@ 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
|
||||||
@@ -101,18 +88,12 @@ 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 (
|
except (aiohttp.ClientError, aiohttp.ClientPayloadError, ConnectionResetError) as err:
|
||||||
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}")
|
||||||
@@ -122,7 +103,6 @@ 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.1.0"
|
version = "1.0.0"
|
||||||
license = "LICENSE"
|
license = "LICENSE"
|
||||||
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg"]
|
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg"]
|
||||||
|
|
||||||
|
|||||||
+42
-166
@@ -83,52 +83,11 @@ 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();
|
||||||
|
|
||||||
@@ -138,17 +97,16 @@ 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(
|
||||||
@@ -234,13 +192,11 @@ const ext = {
|
|||||||
|
|
||||||
registerCustomNodes() {
|
registerCustomNodes() {
|
||||||
/** @type {LGraphNode}*/
|
/** @type {LGraphNode}*/
|
||||||
class ComfyDeploy extends LGraphNode {
|
class ComfyDeploy {
|
||||||
|
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 = "";
|
||||||
@@ -248,75 +204,62 @@ const ext = {
|
|||||||
this.properties.version = "";
|
this.properties.version = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addWidget(
|
ComfyWidgets.STRING(
|
||||||
"text",
|
this,
|
||||||
"workflow_name",
|
"workflow_name",
|
||||||
this.properties.workflow_name,
|
[
|
||||||
(v) => {
|
"",
|
||||||
this.properties.workflow_name = v;
|
{
|
||||||
|
default: this.properties.workflow_name,
|
||||||
|
multiline: false,
|
||||||
},
|
},
|
||||||
{ multiline: false },
|
],
|
||||||
|
app,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addWidget(
|
ComfyWidgets.STRING(
|
||||||
"text",
|
this,
|
||||||
"workflow_id",
|
"workflow_id",
|
||||||
this.properties.workflow_id,
|
[
|
||||||
(v) => {
|
"",
|
||||||
this.properties.workflow_id = v;
|
{
|
||||||
|
default: this.properties.workflow_id,
|
||||||
|
multiline: false,
|
||||||
},
|
},
|
||||||
{ multiline: false },
|
],
|
||||||
|
app,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addWidget(
|
ComfyWidgets.STRING(
|
||||||
"text",
|
this,
|
||||||
"version",
|
"version",
|
||||||
this.properties.version,
|
["", { default: this.properties.version, multiline: false }],
|
||||||
(v) => {
|
app,
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSerialize(o) {
|
// Load default visibility
|
||||||
// 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: "Comfy Deploy",
|
|
||||||
title_mode: LiteGraph.NORMAL_TITLE,
|
title_mode: LiteGraph.NORMAL_TITLE,
|
||||||
|
title: "Comfy Deploy",
|
||||||
collapsable: true,
|
collapsable: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -338,17 +281,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -436,8 +368,6 @@ 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");
|
||||||
@@ -457,25 +387,6 @@ 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();
|
||||||
@@ -1571,38 +1482,3 @@ async function loadWorkflowApi(versionId) {
|
|||||||
// Show an error message to the user
|
// 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,5 +6,4 @@ 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)",
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user