Compare commits

...
Author SHA1 Message Date
BennyKok 16522b5a7b Use parent folder name as subfolder for model file uploads 2025-04-20 22:49:42 +08:00
BennyKok 987de97e49 fix 3d upload with subfolder support and enhanced logging 2025-04-20 22:16:43 +08:00
bennykok fe2d9b82b5 fix: extra options form enum list for some node 2025-04-16 17:09:51 +08:00
bennykok 22858abb31 feat: add external enum node 2025-04-16 16:18:56 +08:00
bennykok 99a5f71b2c feat: add external enum node 2025-04-16 11:52:20 +08:00
bennykok 1810d4ecdb feat: improvement to convert external inputs system 2025-04-16 01:12:28 +08:00
bennykok 3593f0b37e fix: convert external inputs layout issues 2025-04-16 00:10:39 +08:00
bennykok 858a3bcda4 fix rendering problems 2025-04-14 18:08:42 +08:00
bennykok 484147f5b4 fix convert shortcut for string type 2025-04-14 17:59:57 +08:00
Vivek 61a8f1123e fixed a minor issue related to external inputs (#84) 2025-04-14 17:56:27 +08:00
EmmanuelMr18 46b056290d fix(workflows): correct typo in masks example workflow name 2025-04-07 01:31:17 -06:00
EmmanuelMr18 2cf0497823 chore(workflows): convert example workflow previews to JPG format 2025-04-07 01:27:01 -06:00
EmmanuelMr18 4b5eec4e4c feat(workflows): add example workflows for ComfyUI templates 2025-04-07 01:20:46 -06:00
Emmanuel Morales 17c48c7d4f chore: remove model_list custom node
This node was created just for an experiment in the previous year, but never was a core feature.

I'm removing it because looks that is braking the import in local machines because i'm seeing this error locally:

```
  File "F:\ComfyUI\custom_nodes\comfyui-deploy\comfy-nodes\model_list.py", line 35, in <module>
    allModels = fetch_files("./models")
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "F:\ComfyUI\custom_nodes\comfyui-deploy\comfy-nodes\model_list.py", line 23, in fetch_files
    fs.extend(fetch_files(f"{dirpath}/{dirname}"))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "F:\ComfyUI\custom_nodes\comfyui-deploy\comfy-nodes\model_list.py", line 23, in fetch_files
    fs.extend(fetch_files(f"{dirpath}/{dirname}"))
TypeError: 'NoneType' object is not iterable

Cannot import F:\ComfyUI\custom_nodes\comfyui-deploy module for custom nodes: 'NoneType' object is not iterable
```


I'm not sure why, previously was working but was a long time ago and this node was just an experiment, so i'm deleting it
2025-04-05 21:28:37 -06:00
BennyKok cd3a2ff547 Update custom_routes.py
random seed for XlabsSampler
2025-04-03 16:58:49 +02:00
bennykok 62f8e388bb update version and toml file 2025-04-01 12:56:55 +02:00
Robin Huangandsnomiao f86c08baed chore(publish): update GitHub Actions workflow for node publishing (#83)
- Add permissions to allow issue writing
- Update action version from `main` to `v1`
- Add condition to run job only for 'BennyKok' repository owner

Co-authored-by: snomiao <snomiao+comfy-pr@gmail.com>
2025-04-01 12:55:27 +02:00
bennykok 7f64bcc3ae fix: ensure validate workflow 2025-03-31 11:46:58 +02:00
bennykok 8359d1c783 reenable ws event 2025-03-30 22:28:44 +02:00
bennykok dca27fe2ba fix: random seed for sonic node 2025-03-29 09:22:11 +01:00
BennyKok 0ae70835c4 remove sending ws event, since its not used. 2025-03-27 00:04:34 +08:00
BennyKok 99cda529a9 chore: clearer log when request time out 2025-03-26 23:55:47 +08:00
bennykok 7e640a0da4 bump version 2025-03-24 09:44:00 +08:00
BennyKok f19b80a25d Update README.md
Removed the videos since its outdated
2025-03-16 10:31:00 +08:00
karrix 1ba7640d7f refactor: image output id optional 2025-03-13 21:15:14 +08:00
Nick Kao 6ecf9c782b Merge pull request #81 from Jeremy8776/main
Fix: Organised Node List
2025-03-07 10:26:46 -08:00
Jeremy 1904b4bdbf Fix: Organised Node List
All CD nodes under one group in the node list.

Not sure what subdir are wanted.
2025-03-07 18:12:56 +00:00
karrix 4112e0ec8c feat: example workflows 2025-03-06 20:11:52 +08:00
karrix 171a227856 add: 3d upload support 2025-03-02 03:55:18 +08:00
karrix 051db3c394 add: model_file 2025-03-02 02:40:31 +08:00
bennykok 757868deaf prevent duplicated output / run 2025-03-02 00:30:23 +08:00
bennykok 7b1e7afb62 change to 50ms stagger delay 2025-03-01 22:19:40 +08:00
bennykok 9f65ce72a5 remove log 2025-03-01 22:19:04 +08:00
bennykok a424e391d1 also log Node Execution Timeline as graph 2025-03-01 19:18:11 +08:00
bennykok 6add68599b feat: add upload queue, stagger upload 2025-03-01 19:02:12 +08:00
40 changed files with 5315 additions and 258 deletions
+6 -2
View File
@@ -7,15 +7,19 @@ on:
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'BennyKok' }}
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
uses: Comfy-Org/publish-node-action@v1
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
-4
View File
@@ -96,10 +96,6 @@ Major areas
# Self Hosting with Vercel
[![Video](https://img.mytsi.org/i/nFOG479.png)](https://www.youtube.com/watch?v=hWvsEY1cS2M)
Tutorial Created by [Ross](https://github.com/rossman22590) and [Syn](https://github.com/mortlsyn)
Build command
```
+37 -1
View File
@@ -2,8 +2,9 @@
@author: BennyKok
@title: comfyui-deploy
@nickname: Comfy Deploy
@description:
@description:
"""
import os
import sys
@@ -17,19 +18,23 @@ import requests
import folder_paths
from folder_paths import add_model_folder_path, get_filename_list, get_folder_paths
from tqdm import tqdm
import re
from . import custom_routes
# import routes
ag_path = os.path.join(os.path.dirname(__file__))
def get_python_files(path):
return [f[:-3] for f in os.listdir(path) if f.endswith(".py")]
def append_to_sys_path(path):
if path not in sys.path:
sys.path.append(path)
paths = ["comfy-nodes"]
files = []
@@ -41,14 +46,45 @@ for path in paths:
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
def split_camel_case(name):
# Split on underscores first, then split each part on camelCase
parts = []
for part in name.split("_"):
# Find all camelCase boundaries
words = re.findall("[A-Z][^A-Z]*", part)
if not words: # If no camelCase found, use the whole part
words = [part]
parts.extend(words)
return parts
# Import all the modules and append their mappings
for file in files:
module = importlib.import_module(file)
# Check if the module has explicit mappings
if hasattr(module, "NODE_CLASS_MAPPINGS"):
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS"):
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
# Auto-discover classes with ComfyUI node attributes
for name, obj in inspect.getmembers(module):
# Check if it's a class and has the required ComfyUI node attributes
if (
inspect.isclass(obj)
and hasattr(obj, "INPUT_TYPES")
and hasattr(obj, "RETURN_TYPES")
):
# Use the class name as the key if not already in mappings
if name not in NODE_CLASS_MAPPINGS:
NODE_CLASS_MAPPINGS[name] = obj
# Create a display name by converting camelCase to Title Case with spaces
words = split_camel_case(name.replace("ComfyUIDeploy", ""))
display_name = " ".join(word.capitalize() for word in words)
# print(display_name, name)
NODE_DISPLAY_NAME_MAPPINGS[name] = display_name
WEB_DIRECTORY = "web-plugin"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
+1
View File
@@ -7,6 +7,7 @@ class ComfyUIDeployExternalAudio:
RETURN_TYPES = ("AUDIO",)
RETURN_NAMES = ("audio",)
FUNCTION = "load_audio"
CATEGORY = "🔗ComfyDeploy"
@classmethod
def INPUT_TYPES(cls):
+2 -1
View File
@@ -23,8 +23,9 @@ class ComfyUIDeployExternalBoolean:
RETURN_TYPES = ("BOOLEAN",)
RETURN_NAMES = ("bool_value",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
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}")
+2 -1
View File
@@ -36,10 +36,11 @@ class ComfyUIDeployExternalCheckpoint:
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "deploy"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, display_name=None, description=None):
import requests
+46
View File
@@ -0,0 +1,46 @@
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyUIDeployExternalEnum:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_enum"},
),
},
"optional": {
"default_value": (
"STRING",
{"multiline": False, "default": "", "dynamic_enum": True},
),
"options": (
"STRING",
{"multiline": True, "default": ""},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("text",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, options=None, default_value=None, display_name=None, description=None):
return [default_value]
+1
View File
@@ -10,6 +10,7 @@ class ComfyUIDeployExternalEXR:
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("image", "mask")
FUNCTION = "load_exr"
CATEGORY = "🔗ComfyDeploy"
@classmethod
def INPUT_TYPES(cls):
+1 -3
View File
@@ -48,10 +48,8 @@ class ComfyUIDeployExternalFaceModel:
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "deploy"
CATEGORY = "🔗ComfyDeploy"
def run(
self,
+1 -3
View File
@@ -29,10 +29,8 @@ class ComfyUIDeployExternalImage:
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "run"
CATEGORY = "image"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, display_name=None, description=None, default_value_url=None):
image = default_value
+1 -3
View File
@@ -28,10 +28,8 @@ class ComfyUIDeployExternalImageAlpha:
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "run"
CATEGORY = "image"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, display_name=None, description=None):
image = default_value
+1 -3
View File
@@ -34,10 +34,8 @@ class ComfyUIDeployExternalImageBatch:
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "run"
CATEGORY = "image"
CATEGORY = "🔗ComfyDeploy"
def process_image(self, image):
image = ImageOps.exif_transpose(image)
+1 -3
View File
@@ -46,10 +46,8 @@ class ComfyUIDeployExternalLora:
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "deploy"
CATEGORY = "🔗ComfyDeploy"
def run(
self,
+1 -3
View File
@@ -31,10 +31,8 @@ class ComfyUIDeployExternalNumber:
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
FUNCTION = "run"
CATEGORY = "number"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, display_name=None, description=None):
try:
+1 -3
View File
@@ -31,10 +31,8 @@ class ComfyUIDeployExternalNumberInt:
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("value",)
FUNCTION = "run"
CATEGORY = "number"
CATEGORY = "🔗ComfyDeploy"
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()):
+1 -3
View File
@@ -34,10 +34,8 @@ class ComfyUIDeployExternalNumberSlider:
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
FUNCTION = "run"
CATEGORY = "number"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, min_value=0, max_value=1, display_name=None, description=None):
try:
+1 -1
View File
@@ -18,7 +18,7 @@ class StringFunction:
RETURN_TYPES = ("STRING",)
FUNCTION = "exec"
CATEGORY = "utils"
CATEGORY = "🔗ComfyDeploy"
OUTPUT_NODE = True
def exec(self, action, tidy_tags, text_a="", text_b="", text_c=""):
+1 -1
View File
@@ -34,7 +34,7 @@ class ComfyUIDeployExternalText:
FUNCTION = "run"
CATEGORY = "text"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, display_name=None, description=None):
return [default_value]
+1 -1
View File
@@ -36,7 +36,7 @@ class ComfyUIDeployExternalTextAny:
FUNCTION = "run"
CATEGORY = "text"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, display_name=None, description=None):
return [default_value]
+1
View File
@@ -36,6 +36,7 @@ class ComfyUIDeployExternalVideo:
RETURN_NAMES = ("video")
FUNCTION = "load_video"
CATEGORY = "🔗ComfyDeploy"
def load_video(self, input_id, default_value):
input_dir = folder_paths.get_input_directory()
+1
View File
@@ -791,6 +791,7 @@ class ComfyUIDeployExternalVideo:
)
FUNCTION = "load_video"
CATEGORY = "🔗ComfyDeploy"
def load_video(self, **kwargs):
input_id = kwargs.get("input_id")
+1
View File
@@ -33,6 +33,7 @@ class ComfyDeployWebscoketImageInput:
RETURN_NAMES = ("images",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
@classmethod
def VALIDATE_INPUTS(s, input_id):
-60
View File
@@ -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)"}
+3 -2
View File
@@ -27,6 +27,8 @@ class ComfyDeployOutputImage:
),
"file_type": (["png", "jpg", "webp"], {"default": "webp"}),
"quality": ("INT", {"default": 80, "min": 1, "max": 100, "step": 1}),
},
"optional": {
"output_id": (
"STRING",
{"multiline": False, "default": "output_images"},
@@ -39,8 +41,7 @@ class ComfyDeployOutputImage:
FUNCTION = "run"
OUTPUT_NODE = True
CATEGORY = "output"
CATEGORY = "🔗ComfyDeploy"
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
def run(
+1 -3
View File
@@ -33,10 +33,8 @@ class ComfyDeployWebscoketImageOutput:
RETURN_TYPES = ()
RETURN_NAMES = ("text",)
FUNCTION = "run"
CATEGORY = "output"
CATEGORY = "🔗ComfyDeploy"
@classmethod
def VALIDATE_INPUTS(s, output_id):
+807 -68
View File
@@ -30,6 +30,7 @@ from model_management import get_torch_device
import torch
import psutil
from collections import OrderedDict
import io
# Global session
client_session = None
@@ -43,7 +44,22 @@ client_session = None
async def ensure_client_session():
global client_session
if client_session is None:
client_session = aiohttp.ClientSession()
# Configure TCP connection pooling for better performance
connector = aiohttp.TCPConnector(
limit=30, # Maximum number of connections in the pool
limit_per_host=10, # Maximum number of connections per host
enable_cleanup_closed=True, # Clean up closed connections
force_close=False, # Keep connections alive when possible
ttl_dns_cache=300, # Cache DNS results for 5 minutes
)
# Create the session with the connector
client_session = aiohttp.ClientSession(
connector=connector,
timeout=ClientTimeout(total=None, connect=5, sock_read=60, sock_connect=5),
raise_for_status=False, # We'll handle status manually
)
logger.info("Created global client session with optimized connection pooling")
async def cleanup():
@@ -123,7 +139,7 @@ async def async_request_with_retry(
logger.error(f"Error response body: {error_body}")
if attempt == max_retries - 1:
logger.error(f"Request failed after {max_retries} attempts: {e}")
logger.error(f"Request {method} : {url} failed after {max_retries} attempts: {e}")
raise
await asyncio.sleep(retry_delay)
@@ -131,7 +147,7 @@ async def async_request_with_retry(
total_time = time.time() - start_time
raise Exception(
f"Request failed after {max_retries} attempts and {total_time:.2f} seconds"
f"Request {method} : {url} failed after {max_retries} attempts and {total_time:.2f} seconds"
)
@@ -288,6 +304,11 @@ def post_prompt(json_data):
def randomSeed(num_digits=15):
# Special case for SONICSampler which uses np.int32
if num_digits == "sonic":
return random.randint(0, 2147483647) # np.iinfo(np.int32).max
# Original logic for other cases
range_start = 10 ** (num_digits - 1)
range_end = (10**num_digits) - 1
return random.randint(range_start, range_end)
@@ -306,6 +327,13 @@ def apply_random_seed_to_workflow(workflow_api):
# If seed is a list, it's an input from another node (generally `external number int`)
if isinstance(workflow_api[key]["inputs"]["seed"], list):
continue
# Special case for SONICSampler
if workflow_api[key]["class_type"] == "SONICSampler":
workflow_api[key]["inputs"]["seed"] = randomSeed("sonic")
logger.info(
f"Applied random seed {workflow_api[key]['inputs']['seed']} to SONICSampler"
)
continue
if workflow_api[key]["class_type"] == "PromptExpansion":
workflow_api[key]["inputs"]["seed"] = randomSeed(8)
logger.info(
@@ -339,6 +367,12 @@ def apply_random_seed_to_workflow(workflow_api):
f"Applied random noise_seed {workflow_api[key]['inputs']['noise_seed']} to SamplerCustom"
)
continue
if workflow_api[key]["class_type"] == "XlabsSampler":
workflow_api[key]["inputs"]["noise_seed"] = randomSeed()
logger.info(
f"Applied random noise_seed {workflow_api[key]['inputs']['noise_seed']} to SamplerCustom"
)
continue
def apply_inputs_to_workflow(workflow_api: Any, inputs: Any, sid: str = None):
@@ -469,7 +503,7 @@ async def comfy_deploy_run(request):
},
) as response:
data = await response.json()
print(data)
# print(data)
if "cd_token" in data:
token = data["cd_token"]
@@ -1276,7 +1310,7 @@ send_json = prompt_server.send_json
async def send_json_override(self, event, data, sid=None):
# logger.info("INTERNAL:", event, data, sid)
# logger.info(f"INTERNAL: event={event}, data={data}, sid={sid}")
prompt_id = data.get("prompt_id")
target_sid = sid
@@ -1347,13 +1381,17 @@ async def send_json_override(self, event, data, sid=None):
)
)
print(node_execution_array)
# print(node_execution_array)
# print("\n=== Node Execution Times ===")
logger.info("Printing Node Execution Times")
# logger.info("Printing Node Execution Times")
logger.info(format_table(headers, table_data))
# print("========================\n")
timeline = format_execution_timeline(NODE_EXECUTION_TIMES)
logger.info(f"\nNode Execution Timeline:\n{timeline}")
# Clear the execution times for the next run
# the last executing event is none, then the workflow is finished
if event == "executing" and data.get("node") is None:
mark_prompt_done(prompt_id=prompt_id)
@@ -1622,40 +1660,75 @@ async def file_sender(file_object, chunk_size):
chunk_size = 1024 * 1024 # 1MB chunks, adjust as needed
class ProgressTracker:
def __init__(self, data, callback):
self.data = data
self.callback = callback
self.total = len(data)
self.uploaded = 0
self._cursor = 0
async def read(self, n=-1):
if n == -1:
chunk = self.data[self._cursor :]
self._cursor = len(self.data)
else:
chunk = self.data[self._cursor : self._cursor + n]
self._cursor += len(chunk)
if chunk:
self.uploaded += len(chunk)
if self.callback:
await self.callback(self.uploaded, self.total)
return chunk
async def upload_with_retry(
session, url, headers, data, max_retries=3, initial_delay=1
session,
url,
headers,
data,
max_retries=5,
initial_delay=1,
timeout=300,
progress_callback=None,
):
start_time = time.time() # Start timing here
for attempt in range(max_retries):
"""Upload data with retry logic and progress tracking"""
retries = 0
total_size = len(data)
while True:
try:
async with session.put(url, headers=headers, data=data) as response:
upload_duration = time.time() - start_time
logger.info(
f"Upload attempt {attempt + 1} completed in {upload_duration:.2f} seconds"
)
logger.info(f"Upload response status: {response.status}")
async with session.put(
url,
headers=headers,
data=data,
timeout=aiohttp.ClientTimeout(total=timeout),
) as response:
if progress_callback:
await progress_callback(
total_size, total_size
) # Mark as complete since we can't track progress
response.raise_for_status() # This will raise an exception for 4xx and 5xx status codes
if response.status >= 200 and response.status < 300:
return response
else:
raise aiohttp.ClientError(
f"Upload failed with status {response.status}"
)
response_text = await response.text()
# logger.info(f"Response body: {response_text[:1000]}...")
logger.info("Upload successful")
return response # Successful upload, exit the retry loop
except (ClientError, ClientResponseError) as e:
logger.error(f"Upload attempt {attempt + 1} failed: {str(e)}")
if attempt < max_retries - 1: # If it's not the last attempt
delay = initial_delay * (2**attempt) # Exponential backoff
logger.info(f"Retrying in {delay} seconds...")
await asyncio.sleep(delay)
else:
logger.error("Max retries reached. Upload failed.")
raise # Re-raise the last exception if all retries are exhausted
except Exception as e:
logger.error(f"Unexpected error during upload: {str(e)}")
logger.error(traceback.format_exc())
raise # Re-raise unexpected exceptions immediately
retries += 1
if retries > max_retries:
raise
# Calculate delay with exponential backoff
delay = initial_delay * (2 ** (retries - 1))
logger.warning(
f"Upload attempt {retries} failed: {str(e)}. Retrying in {delay}s..."
)
await asyncio.sleep(delay)
async def upload_file(
@@ -1834,7 +1907,7 @@ async def update_file_status(
else:
prompt_metadata[prompt_id].uploading_nodes.discard(node_id)
logger.info(f"Remaining uploads: {prompt_metadata[prompt_id].uploading_nodes}")
# logger.info(f"Remaining uploads: {prompt_metadata[prompt_id].uploading_nodes}")
# Update the remote status
if have_error:
@@ -1921,42 +1994,71 @@ async def upload_in_background(
prompt_id: str, data, node_id=None, have_upload=True, node_meta=None
):
try:
# await handle_upload(prompt_id, data, 'images', "content_type", "image/png")
# await handle_upload(prompt_id, data, 'files', "content_type", "image/png")
# await handle_upload(prompt_id, data, 'gifs', "format", "image/gif")
# await handle_upload(prompt_id, data, 'mesh', "format", "application/octet-stream")
file_upload_endpoint = prompt_metadata[prompt_id].file_upload_endpoint
if file_upload_endpoint is not None and file_upload_endpoint != "":
upload_tasks = [
handle_upload(prompt_id, data, "images", "content_type", "image/png"),
handle_upload(prompt_id, data, "files", "content_type", "image/png"),
handle_upload(prompt_id, data, "gifs", "format", "image/gif"),
handle_upload(
prompt_id, data, "mesh", "format", "application/octet-stream"
),
]
# Flag to track if we need to update status after uploads
has_uploads = False
await asyncio.gather(*upload_tasks)
# Flatten all file types into a single list of uploads
for file_type, content_type_key, default_content_type in [
("images", "content_type", "image/png"),
("files", "content_type", "image/png"),
("gifs", "format", "image/gif"),
("model_file", "format", "application/octet-stream"),
]:
items = data.get(file_type, [])
for item in items:
# if is model_file, just add it to the data
if file_type == "model_file":
if isinstance(item, str):
filename = os.path.basename(item)
# Extract folder name from the path
folder_path = os.path.dirname(item)
subfolder = os.path.basename(folder_path) if folder_path else ""
item = {
"filename": filename,
"subfolder": subfolder,
"type": "output",
}
# Skip temp files
if item.get("type") == "temp":
continue
# Add to the upload queue instead of uploading immediately
await upload_queue.add_upload(prompt_id, item, node_id)
has_uploads = True
# Mark the prompt as needing uploads but still report data immediately
if has_uploads:
await update_file_status(prompt_id, data, True, node_id=node_id)
else:
print("No file upload endpoint, skipping file upload")
logger.info("No file upload endpoint, skipping file upload")
status_endpoint = prompt_metadata[prompt_id].status_endpoint
token = prompt_metadata[prompt_id].token
gpu_event_id = prompt_metadata[prompt_id].gpu_event_id or None
if have_upload:
if status_endpoint is not None:
body = {
"run_id": prompt_id,
"output_data": data,
"node_meta": node_meta,
"gpu_event_id": gpu_event_id,
}
# pprint(body)
await async_request_with_retry(
"POST", status_endpoint, token=token, json=body
)
# Still update the API with the output data even if we're not uploading files
# status_endpoint = prompt_metadata[prompt_id].status_endpoint
# token = prompt_metadata[prompt_id].token
# gpu_event_id = prompt_metadata[prompt_id].gpu_event_id or None
# if have_upload and status_endpoint is not None:
# body = {
# "run_id": prompt_id,
# "output_data": data,
# "node_meta": node_meta,
# "gpu_event_id": gpu_event_id,
# }
# await async_request_with_retry(
# "POST", status_endpoint, token=token, json=body
# )
# If no uploads are needed, update file status immediately
if (
not have_upload
or file_upload_endpoint is None
or file_upload_endpoint == ""
):
await update_file_status(prompt_id, data, False, node_id=node_id)
except Exception as e:
await handle_error(prompt_id, data, e)
@@ -1983,7 +2085,10 @@ async def update_run_with_output(
have_upload_media = False
if data is not None:
have_upload_media = (
"images" in data or "files" in data or "gifs" in data or "mesh" in data
"images" in data
or "files" in data
or "gifs" in data
or "model_file" in data
)
if bypass_upload and have_upload_media:
print(
@@ -2033,7 +2138,7 @@ async def watch_file_changes(file_path, callback):
global last_read_line
last_modified_time = os.stat(file_path).st_mtime
while True:
time.sleep(1) # sleep for a while to reduce CPU usage
await asyncio.sleep(1) # Use asyncio.sleep instead of time.sleep
modified_time = os.stat(file_path).st_mtime
if modified_time != last_modified_time:
last_modified_time = modified_time
@@ -2073,9 +2178,643 @@ if cd_enable_log:
run_in_new_thread(watch_file_changes(log_file_path, send_logs_to_websocket))
# Initialize the upload queue when the module loads
async def initialize_upload_queue(app=None):
"""Initialize the upload queue and start the worker process"""
logger.info("Initializing upload queue system...")
await upload_queue.ensure_worker_running()
logger.info(
"Upload queue system initialized with max_concurrent=%d",
upload_queue.max_concurrent,
)
# Start the queue monitoring task in the same event loop
asyncio.create_task(monitor_upload_queue())
# Get the server's event loop and initialize there
server.PromptServer.instance.app.on_startup.append(initialize_upload_queue)
async def monitor_upload_queue():
"""Monitor the upload queue and log statistics periodically"""
while True:
try:
queue_size = upload_queue.queue.qsize()
pending_uploads_count = sum(
len(uploads) for uploads in upload_queue.pending_uploads.values()
)
pending_prompts = len(upload_queue.pending_uploads)
if queue_size > 0 or pending_uploads_count > 0:
logger.info(
f"Upload queue status: {queue_size} queued, {pending_uploads_count} pending "
f"uploads across {pending_prompts} prompts"
)
# If queue is getting big, log a warning
if queue_size > 20:
logger.warning(
f"Upload queue is large ({queue_size} items). Check for bottlenecks."
)
# More detailed logging for large queue
if queue_size > 50:
for prompt_id, uploads in upload_queue.pending_uploads.items():
logger.warning(
f"Prompt {prompt_id[:8]}... has {len(uploads)} pending uploads"
)
except Exception as e:
logger.error(f"Error in upload queue monitor: {str(e)}")
# Check every 30 seconds
await asyncio.sleep(30)
# use after calling GET /object_info (it populates the `filename_list_cache` variable)
@server.PromptServer.instance.routes.get("/comfyui-deploy/filename_list_cache")
async def get_filename_list_cache(_):
from folder_paths import filename_list_cache
return web.json_response({"filename_list": filename_list_cache})
@server.PromptServer.instance.routes.get("/comfyui-deploy/upload-queue-status")
async def get_upload_queue_status(request):
"""Get the current status of the upload queue"""
prompt_id = request.rel_url.query.get("prompt_id", None)
queue_size = upload_queue.queue.qsize()
pending_uploads_count = sum(
len(uploads) for uploads in upload_queue.pending_uploads.values()
)
status_data = {
"queue_size": queue_size,
"pending_uploads": pending_uploads_count,
"max_concurrent": upload_queue.max_concurrent,
}
# If prompt_id is provided, add specific data for that prompt
if prompt_id and prompt_id in upload_queue.pending_uploads:
prompt_pending = len(upload_queue.pending_uploads[prompt_id])
status_data["prompt_pending"] = prompt_pending
status_data["prompt_id"] = prompt_id
return web.json_response(status_data)
@server.PromptServer.instance.routes.post("/comfyui-deploy/cancel-uploads")
async def cancel_prompt_uploads(request):
"""Cancel all pending uploads for a specific prompt"""
data = await request.json()
prompt_id = data.get("prompt_id")
if not prompt_id:
return web.json_response({"error": "prompt_id is required"}, status=400)
success = await upload_queue.cancel_uploads_for_prompt(prompt_id)
if success:
# Also update the prompt status
if prompt_id in prompt_metadata:
# Mark as SUCCESS since we're not waiting for uploads anymore
await update_run(prompt_id, Status.SUCCESS)
return web.json_response(
{
"success": True,
"message": f"Cancelled pending uploads for prompt {prompt_id}",
}
)
return web.json_response(
{
"success": False,
"message": f"No pending uploads found for prompt {prompt_id}",
},
status=404,
)
class UploadQueue:
def __init__(self, max_concurrent=3):
self.queue = asyncio.Queue()
self.max_concurrent = max_concurrent
self.active_uploads = 0
self.worker_task = None
self.lock = asyncio.Lock()
self.pending_uploads = {} # prompt_id -> set of pending upload tasks
self.node_uploads = {} # prompt_id -> {node_id -> set of pending upload tasks}
self.node_output_data = {} # prompt_id -> {node_id -> output data}
self.upload_stats = {} # prompt_id -> {filename: {stats}}
self.upload_timeline = {} # prompt_id -> list of upload events with timing
self.last_status_update = 0
self.status_update_interval = 5
self.upload_lock = asyncio.Lock() # Add lock for upload coordination
self.last_upload_time = 0 # Track the last upload start time
self.STAGGER_DELAY = 0.05 # Stagger delay in seconds
def _log_upload_stats(self, prompt_id):
"""Log upload statistics in a formatted table with waterfall timing"""
if prompt_id not in self.upload_stats or not self.upload_stats[prompt_id]:
return
stats = self.upload_stats[prompt_id]
timeline = self.upload_timeline[prompt_id]
# Sort timeline by start time
timeline.sort(key=lambda x: x["start_time"])
first_start = min(event["start_time"] for event in timeline)
headers = [
"Node",
"File",
"Size",
"Start Time",
"Duration",
"Speed",
"Timeline",
]
data = []
# Calculate timeline scale (80 chars wide)
total_duration = max(event["end_time"] for event in timeline) - first_start
scale = 80.0 / total_duration if total_duration > 0 else 1.0
for event in timeline:
filename = event["filename"]
file_stats = stats[filename]
# Calculate timeline bar position and width
start_offset = event["start_time"] - first_start
duration = event["end_time"] - event["start_time"]
bar_start = int(start_offset * scale)
bar_width = max(1, int(duration * scale))
# Create timeline bar
timeline_bar = " " * bar_start + "=" * bar_width
# Format size and speed
size_mb = file_stats["size"] / (1024 * 1024)
speed_mb = file_stats["size"] / (file_stats["upload_time"] * 1024 * 1024)
# Format relative time
start_time = f"+{start_offset:.2f}s"
data.append(
[
event["node_name"] or "-",
filename,
f"{size_mb:.2f}MB",
start_time,
f"{duration:.2f}s",
f"{speed_mb:.2f}MB/s",
timeline_bar,
]
)
logger.info("\nUpload Performance Summary:")
logger.info(format_table(headers, data))
# Calculate and show totals
total_size = sum(s["size"] for s in stats.values())
total_time = total_duration
avg_speed = total_size / (total_time * 1024 * 1024) if total_time > 0 else 0
logger.info(f"\nTotal Stats:")
logger.info(f"Total Size: {total_size / (1024 * 1024):.2f}MB")
logger.info(f"Total Time: {total_time:.2f}s")
logger.info(f"Average Speed: {avg_speed:.2f}MB/s")
async def _process_upload(self, prompt_id, file_info, node_id):
"""Process a single file upload"""
if prompt_id not in prompt_metadata:
logger.warning(f"Cannot upload for unknown prompt ID: {prompt_id}")
return
file_upload_endpoint = prompt_metadata[prompt_id].file_upload_endpoint
token = prompt_metadata[prompt_id].token
if not file_upload_endpoint:
logger.warning(f"No upload endpoint for prompt ID: {prompt_id}")
return
filename = file_info.get("filename")
subfolder = file_info.get("subfolder")
file_type = file_info.get("type", "output")
# Initialize tracking for this prompt if needed
if prompt_id not in self.upload_stats:
self.upload_stats[prompt_id] = {}
self.upload_timeline[prompt_id] = []
# Determine content type based on file extension
file_extension = os.path.splitext(filename)[1]
if file_extension in [".jpg", ".jpeg"]:
content_type = "image/jpeg"
elif file_extension == ".png":
content_type = "image/png"
elif file_extension == ".webp":
content_type = "image/webp"
elif file_extension == ".gif":
content_type = "image/gif"
else:
content_type = file_info.get("content_type", "application/octet-stream")
# Get node name from metadata if available
node_name = None
if node_id and prompt_id in prompt_metadata:
workflow_api = prompt_metadata[prompt_id].workflow_api
node = workflow_api.get(node_id)
if node:
node_name = node.get("class_type", "")
# Get the full file path and validate
filename, output_dir = folder_paths.annotated_filepath(filename)
if filename[0] == "/" or ".." in filename:
logger.warning(f"Insecure filename path: {filename}")
return
if output_dir is None:
output_dir = folder_paths.get_directory_by_type(file_type)
if output_dir is None:
logger.warning(f"{filename} Upload failed: output_dir is None")
return
if subfolder is not None:
full_output_dir = os.path.join(output_dir, subfolder)
if (
os.path.commonpath((os.path.abspath(full_output_dir), output_dir))
!= output_dir
):
logger.warning(f"Insecure subfolder path: {subfolder}")
return
output_dir = full_output_dir
filename_base = os.path.basename(filename)
file_path = os.path.join(output_dir, filename_base)
# Record start time
start_time = time.perf_counter()
try:
# Get the signed upload URL
filename_quoted = quote(filename_base)
prompt_id_quoted = quote(prompt_id)
content_type_quoted = quote(content_type)
target_url = f"{file_upload_endpoint}?file_name={filename_quoted}&run_id={prompt_id_quoted}&type={content_type_quoted}&version=v2"
result = await async_request_with_retry(
"GET", target_url, disable_timeout=True, token=token
)
signed_url_data = await result.json()
# Read and upload the file
async with aiofiles.open(file_path, "rb") as f:
data = await f.read()
size = len(data)
headers = {
"Content-Type": content_type,
"Content-Length": str(size),
}
if signed_url_data.get("include_acl") is True:
headers["x-amz-acl"] = "public-read"
async with aiohttp.ClientSession() as session:
response = await upload_with_retry(
session,
signed_url_data.get("url"),
headers,
data,
)
# Record upload success and timing
end_time = time.perf_counter()
upload_time = end_time - start_time
# Store upload statistics
self.upload_stats[prompt_id][filename_base] = {
"size": size,
"type": content_type,
"upload_time": upload_time,
}
# Store timeline event
self.upload_timeline[prompt_id].append(
{
"filename": filename_base,
"node_id": node_id,
"node_name": node_name,
"start_time": start_time
- prompt_metadata[prompt_id].start_time,
"end_time": end_time
- prompt_metadata[prompt_id].start_time,
}
)
# Update the file_info with download URL and timing
file_info["url"] = signed_url_data.get("download_url")
file_info["upload_duration"] = upload_time
if signed_url_data.get("is_public") is not None:
file_info["is_public"] = signed_url_data.get("is_public")
# Update node output data if this upload is associated with a node
if (
node_id
and prompt_id in self.node_output_data
and node_id in self.node_output_data[prompt_id]
):
node_data = self.node_output_data[prompt_id][node_id]
file_type_key = (
"images" if content_type.startswith("image/") else "files"
)
if file_type_key not in node_data["data"]:
node_data["data"][file_type_key] = []
node_data["data"][file_type_key].append(file_info)
# Send success status to clients
await send(
"upload_success",
{
"prompt_id": prompt_id,
"filename": filename_base,
"url": file_info["url"],
"node_id": node_id,
},
)
# If this was the last file for this prompt, show the stats summary
if (
prompt_id in self.pending_uploads
and len(self.pending_uploads[prompt_id]) == 1
):
self._log_upload_stats(prompt_id)
# Clean up stats
del self.upload_stats[prompt_id]
del self.upload_timeline[prompt_id]
except Exception as e:
logger.error(f"\nUpload failed for {filename_base}: {str(e)}")
await send(
"upload_failed",
{
"prompt_id": prompt_id,
"filename": filename_base,
"error": str(e),
"node_id": node_id,
},
)
raise
async def add_upload(self, prompt_id, file_info, node_id=None):
"""Add a file to the upload queue"""
# Initialize the pending uploads set for this prompt if needed
if prompt_id not in self.pending_uploads:
self.pending_uploads[prompt_id] = set()
self.node_uploads[prompt_id] = {}
self.node_output_data[prompt_id] = {}
# Initialize node tracking if needed
if node_id and node_id not in self.node_uploads[prompt_id]:
self.node_uploads[prompt_id][node_id] = set()
self.node_output_data[prompt_id][node_id] = {"data": {}}
# Add a unique identifier for this upload
upload_id = str(uuid.uuid4())
self.pending_uploads[prompt_id].add(upload_id)
# Track upload for specific node if provided
if node_id:
self.node_uploads[prompt_id][node_id].add(upload_id)
# Add to the queue
await self.queue.put(
{
"prompt_id": prompt_id,
"file_info": file_info,
"node_id": node_id,
"upload_id": upload_id,
}
)
# Send status update to clients
await self.update_queue_status(prompt_id)
# Ensure worker is running
await self.ensure_worker_running()
return upload_id
async def ensure_worker_running(self):
"""Ensure the worker task is running"""
async with self.lock:
if self.worker_task is None or self.worker_task.done():
self.worker_task = asyncio.create_task(self.worker())
async def update_queue_status(self, prompt_id=None):
"""Send queue status updates to clients"""
# Throttle updates to avoid flooding clients
current_time = time.time()
if current_time - self.last_status_update < self.status_update_interval:
return
self.last_status_update = current_time
queue_size = self.queue.qsize()
pending_uploads_count = sum(
len(uploads) for uploads in self.pending_uploads.values()
)
status_data = {
"queue_size": queue_size,
"pending_uploads": pending_uploads_count,
"max_concurrent": self.max_concurrent,
}
# If prompt_id is provided, add specific data for that prompt
if prompt_id and prompt_id in self.pending_uploads:
prompt_pending = len(self.pending_uploads[prompt_id])
status_data["prompt_pending"] = prompt_pending
status_data["prompt_id"] = prompt_id
# Send targeted status update to relevant clients
await send("upload_queue_status", status_data, prompt_id)
else:
# Send global update to all clients
await send("upload_queue_status", status_data)
async def worker(self):
"""Worker process that manages the upload queue"""
# Start multiple worker tasks within concurrency limits
workers = [
asyncio.create_task(self.upload_worker())
for _ in range(self.max_concurrent)
]
# Wait for all workers to complete (should only happen on shutdown)
await asyncio.gather(*workers)
async def upload_worker(self):
"""Individual worker that processes uploads from the queue"""
loop = asyncio.get_event_loop()
while True:
try:
# Get next upload task first
upload_task = await self.queue.get()
prompt_id = upload_task["prompt_id"]
file_info = upload_task["file_info"]
node_id = upload_task["node_id"]
upload_id = upload_task["upload_id"]
print(file_info)
try:
# Coordinate the actual start of the upload
async with self.upload_lock:
current_time = time.time()
time_since_last = current_time - self.last_upload_time
if time_since_last < self.STAGGER_DELAY:
await asyncio.sleep(self.STAGGER_DELAY - time_since_last)
self.last_upload_time = time.time()
# Start the actual upload while holding the lock
# to ensure true staggering
await self._process_upload(prompt_id, file_info, node_id)
except Exception as e:
logger.error(f"Upload failed: {str(e)}")
logger.error(traceback.format_exc())
finally:
# Remove this upload from tracking
if prompt_id in self.pending_uploads:
self.pending_uploads[prompt_id].discard(upload_id)
# Remove from node tracking if applicable
if (
node_id
and prompt_id in self.node_uploads
and node_id in self.node_uploads[prompt_id]
):
self.node_uploads[prompt_id][node_id].discard(upload_id)
# If this was the last upload for this node, clean up node data
if not self.node_uploads[prompt_id][node_id]:
del self.node_uploads[prompt_id][node_id]
if self.node_output_data[prompt_id][node_id]["data"]:
# Send final node data to API before cleanup
if prompt_metadata[prompt_id].status_endpoint:
body = {
"run_id": prompt_id,
"output_data": self.node_output_data[
prompt_id
][node_id]["data"],
"node_meta": {"node_id": node_id},
}
try:
await async_request_with_retry(
"POST",
prompt_metadata[
prompt_id
].status_endpoint,
token=prompt_metadata[prompt_id].token,
json=body,
)
except Exception as e:
logger.error(
f"Failed to send final node data: {str(e)}"
)
del self.node_output_data[prompt_id][node_id]
# Send status update
await self.update_queue_status(prompt_id)
# If no more pending uploads for this prompt and it's done, update status
if not self.pending_uploads[prompt_id] and is_prompt_done(
prompt_id
):
# Clean up all data for this prompt
if prompt_id in self.node_uploads:
del self.node_uploads[prompt_id]
if prompt_id in self.node_output_data:
del self.node_output_data[prompt_id]
del self.pending_uploads[prompt_id]
# Use the same event loop for these tasks
loop.create_task(update_run(prompt_id, Status.SUCCESS))
loop.create_task(send("success", {"prompt_id": prompt_id}))
# Mark task as done
self.queue.task_done()
except Exception as e:
logger.error(f"Error in upload worker: {str(e)}")
logger.error(traceback.format_exc())
# Brief pause to prevent tight loop in case of persistent errors
await asyncio.sleep(0.5)
async def cancel_uploads_for_prompt(self, prompt_id):
"""Cancel all pending uploads for a prompt"""
if prompt_id in self.pending_uploads:
# Remove all pending uploads for this prompt
self.pending_uploads[prompt_id].clear()
self.node_uploads[prompt_id].clear()
self.node_output_data[prompt_id].clear()
# Clean up
del self.pending_uploads[prompt_id]
del self.node_uploads[prompt_id]
del self.node_output_data[prompt_id]
# Send status update
await self.update_queue_status(prompt_id)
# Create a global instance of the upload queue
upload_queue = UploadQueue(max_concurrent=3) # Limit to 3 concurrent uploads
def format_execution_timeline(execution_times):
"""Format node execution times into a table with timeline visualization"""
if not execution_times:
return "No execution data available"
# Calculate total time and start times for each node
sorted_nodes = sorted(execution_times.items(), key=lambda x: x[1]["time"])
total_duration = sum(node["time"] for _, node in execution_times.items())
# Prepare table data
headers = ["Node", "Type", "Duration", "VRAM", "Timeline"]
rows = []
current_time = 0
# Calculate timeline width (e.g., 80 chars)
TIMELINE_WIDTH = 80
for node_id, data in sorted_nodes:
# Calculate the start position and width for the timeline
duration = data["time"]
vram_mb = data["vram_used"] / (1024 * 1024) # Convert to MB
start_pos = int((current_time / total_duration) * TIMELINE_WIDTH)
width = max(1, int((duration / total_duration) * TIMELINE_WIDTH))
# Create the timeline visualization
timeline = (
" " * start_pos + "=" * width + " " * (TIMELINE_WIDTH - start_pos - width)
)
# Add the row
rows.append(
[
f"#{node_id}",
data["class_type"],
f"{duration:.2f}s",
f"{vram_mb:.1f}MB",
timeline,
]
)
current_time += duration
return format_table(headers, rows)
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

+152
View File
@@ -0,0 +1,152 @@
{
"id": "ed93ac94-4f26-4ed3-a57b-73cd8f4d3494",
"revision": 0,
"last_node_id": 5,
"last_link_id": 1,
"nodes": [
{
"id": 2,
"type": "LoraLoader",
"pos": [
736.646728515625,
628.3823852539062
],
"size": [
315,
126
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "clip",
"type": "CLIP",
"link": null
},
{
"name": "lora_name",
"type": "COMBO",
"widget": {
"name": "lora_name"
},
"link": 1
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": null
},
{
"name": "CLIP",
"type": "CLIP",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "LoraLoader"
},
"widgets_values": [
"1-292.safetensors",
1,
1
]
},
{
"id": 1,
"type": "ComfyUIDeployExternalLora",
"pos": [
299.6898498535156,
624.7929077148438
],
"size": [
400,
208
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "path",
"type": "*",
"links": [
1
]
}
],
"properties": {
"cnr_id": "comfyui-deploy",
"ver": "cd3a2ff5471828f9c840e746551592e882c05aa4",
"Node name for S&R": "ComfyUIDeployExternalLora"
},
"widgets_values": [
"input_lora",
"HyperSD\\FLUX.1\\Hyper-FLUX.1-dev-16steps-lora.safetensors",
"",
"",
"",
""
]
},
{
"id": 5,
"type": "Note",
"pos": [
302.09033203125,
401.2951965332031
],
"size": [
479.4894104003906,
161.61924743652344
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"\"External Lora\" node will let you to use different loras from the Comfy Deploy UI or even via API.\n\n- lora_url:\n url that will be used to download your LoRA model in execution time\n\n- lora_save_name:\n when we download your model, this will be saved in your private storage, \n give it a good name :D"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
1,
1,
0,
2,
2,
"COMBO"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.167184107045006,
"offset": [
298.431389807788,
-207.58877445762934
]
},
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

+873
View File
@@ -0,0 +1,873 @@
{
"id": "351f402b-62f2-4f62-8a5e-0b9d3510e8f9",
"revision": 0,
"last_node_id": 29,
"last_link_id": 28,
"nodes": [
{
"id": 11,
"type": "JoinImageWithAlpha",
"pos": [
814.478271484375,
419.3052062988281
],
"size": [
264.5999755859375,
46
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 10
},
{
"name": "alpha",
"type": "MASK",
"link": 12
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
11
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "JoinImageWithAlpha"
},
"widgets_values": []
},
{
"id": 15,
"type": "PreviewImage",
"pos": [
1950,
640
],
"size": [
210,
246
],
"flags": {},
"order": 18,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 16
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 10,
"type": "LoadImage",
"pos": [
467.8168640136719,
422.453857421875
],
"size": [
315,
314
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
10
]
},
{
"name": "MASK",
"type": "MASK",
"links": [
12
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"Bob-Minion-Background-PNG-Image.png",
"image",
""
]
},
{
"id": 18,
"type": "Note",
"pos": [
460.8001708984375,
263.64251708984375
],
"size": [
379.4292297363281,
88
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"Option 1: CREATE THE MASK FROM THE ALPHA CHANNEL (Useful for example to generate the background of an image)\n\nMake sure that you are using \"External Image Alpha\". \n"
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 21,
"type": "LoadImage",
"pos": [
469.57025146484375,
1506.3018798828125
],
"size": [
315,
314
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
18
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"ComfyUI_temp_otmos_00005_.png",
"image",
""
]
},
{
"id": 25,
"type": "MaskToImage",
"pos": [
1283.1668701171875,
1579.603271484375
],
"size": [
176.39999389648438,
26
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "mask",
"type": "MASK",
"link": 28
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
21
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "MaskToImage"
},
"widgets_values": []
},
{
"id": 13,
"type": "SplitImageWithAlpha",
"pos": [
1602.5518798828125,
417.59869384765625
],
"size": [
277.20001220703125,
46
],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 13
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
14,
25
]
},
{
"name": "MASK",
"type": "MASK",
"links": [
15,
26
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "SplitImageWithAlpha"
},
"widgets_values": []
},
{
"id": 29,
"type": "VAEEncodeForInpaint",
"pos": [
2220.89453125,
401.0885009765625
],
"size": [
340.20001220703125,
98
],
"flags": {},
"order": 16,
"mode": 0,
"inputs": [
{
"name": "pixels",
"type": "IMAGE",
"link": 25
},
{
"name": "vae",
"type": "VAE",
"link": null
},
{
"name": "mask",
"type": "MASK",
"link": 26
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "VAEEncodeForInpaint"
},
"widgets_values": [
6
]
},
{
"id": 14,
"type": "PreviewImage",
"pos": [
1950,
350
],
"size": [
210,
246
],
"flags": {},
"order": 14,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 14
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 17,
"type": "MaskToImage",
"pos": [
1752.33203125,
572.061767578125
],
"size": [
176.39999389648438,
26
],
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "mask",
"type": "MASK",
"link": 15
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
16
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "MaskToImage"
},
"widgets_values": []
},
{
"id": 27,
"type": "PreviewImage",
"pos": [
1329.2960205078125,
1132.759765625
],
"size": [
210,
246
],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 22
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 20,
"type": "LoadImage",
"pos": [
468.1963806152344,
1128.9498291015625
],
"size": [
315,
314
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
17
]
},
{
"name": "MASK",
"type": "MASK",
"links": []
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"clipspace/clipspace-mask-1126817.300000012.png [input]",
"image",
""
]
},
{
"id": 24,
"type": "ImageToMask",
"pos": [
1255.560302734375,
1468.347900390625
],
"size": [
315,
58
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 19
}
],
"outputs": [
{
"name": "MASK",
"type": "MASK",
"links": [
24,
28
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "ImageToMask"
},
"widgets_values": [
"red"
]
},
{
"id": 26,
"type": "PreviewImage",
"pos": [
1488.1925048828125,
1579.146728515625
],
"size": [
210,
246
],
"flags": {},
"order": 17,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 21
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 28,
"type": "VAEEncodeForInpaint",
"pos": [
1637.603271484375,
1182.81298828125
],
"size": [
340.20001220703125,
98
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "pixels",
"type": "IMAGE",
"link": 23
},
{
"name": "vae",
"type": "VAE",
"link": null
},
{
"name": "mask",
"type": "MASK",
"link": 24
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "VAEEncodeForInpaint"
},
"widgets_values": [
6
]
},
{
"id": 19,
"type": "Note",
"pos": [
467.4615173339844,
985.1439819335938
],
"size": [
379.4292297363281,
88
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"Option 2: UPLOAD THE MASK AS OTHER IMAGE (useful if you require the image that is below the mask to do inpainting, in this case to generate new glasses)"
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 22,
"type": "ComfyUIDeployExternalImage",
"pos": [
910.8199462890625,
1132.306396484375
],
"size": [
390.5999755859375,
154
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "default_value",
"shape": 7,
"type": "IMAGE",
"link": 17
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
22,
23
]
}
],
"properties": {
"cnr_id": "comfyui-deploy",
"ver": "cd3a2ff5471828f9c840e746551592e882c05aa4",
"Node name for S&R": "ComfyUIDeployExternalImage"
},
"widgets_values": [
"input_image",
"",
"",
"",
""
]
},
{
"id": 23,
"type": "ComfyUIDeployExternalImage",
"pos": [
843.6348876953125,
1467.8876953125
],
"size": [
390.5999755859375,
154
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "default_value",
"shape": 7,
"type": "IMAGE",
"link": 18
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
19
]
}
],
"properties": {
"cnr_id": "comfyui-deploy",
"ver": "cd3a2ff5471828f9c840e746551592e882c05aa4",
"Node name for S&R": "ComfyUIDeployExternalImage"
},
"widgets_values": [
"input_image_mask",
"",
"",
"",
""
]
},
{
"id": 12,
"type": "ComfyUIDeployExternalImageAlpha",
"pos": [
1107.891357421875,
418.2679138183594
],
"size": [
466.1999816894531,
200
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "default_value",
"shape": 7,
"type": "IMAGE",
"link": 11
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
13
]
}
],
"properties": {
"cnr_id": "comfyui-deploy",
"ver": "cd3a2ff5471828f9c840e746551592e882c05aa4",
"Node name for S&R": "ComfyUIDeployExternalImageAlpha"
},
"widgets_values": [
"input_image_alpha",
"",
""
]
}
],
"links": [
[
10,
10,
0,
11,
0,
"IMAGE"
],
[
11,
11,
0,
12,
0,
"IMAGE"
],
[
12,
10,
1,
11,
1,
"MASK"
],
[
13,
12,
0,
13,
0,
"IMAGE"
],
[
14,
13,
0,
14,
0,
"IMAGE"
],
[
15,
13,
1,
17,
0,
"MASK"
],
[
16,
17,
0,
15,
0,
"IMAGE"
],
[
17,
20,
0,
22,
0,
"IMAGE"
],
[
18,
21,
0,
23,
0,
"IMAGE"
],
[
19,
23,
0,
24,
0,
"IMAGE"
],
[
21,
25,
0,
26,
0,
"IMAGE"
],
[
22,
22,
0,
27,
0,
"IMAGE"
],
[
23,
22,
0,
28,
0,
"IMAGE"
],
[
24,
24,
0,
28,
2,
"MASK"
],
[
25,
13,
0,
29,
0,
"IMAGE"
],
[
26,
13,
1,
29,
2,
"MASK"
],
[
28,
24,
0,
25,
0,
"MASK"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.9646149645000013,
"offset": [
48.66905973637718,
-817.5683540167485
]
},
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
+359
View File
@@ -0,0 +1,359 @@
{
"extra": {
"ds": {
"scale": 0.6010518407212623,
"offset": [815.5938895649746, 84.94304700477853]
},
"node_versions": {
"comfy-core": "0.3.19",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[9, 8, 0, 9, 0, "IMAGE"],
[45, 30, 1, 6, 0, "CLIP"],
[46, 30, 2, 8, 1, "VAE"],
[47, 30, 0, 31, 0, "MODEL"],
[51, 27, 0, 31, 3, "LATENT"],
[52, 31, 0, 8, 0, "LATENT"],
[54, 30, 1, 33, 0, "CLIP"],
[55, 33, 0, 31, 2, "CONDITIONING"],
[56, 6, 0, 35, 0, "CONDITIONING"],
[57, 35, 0, 31, 1, "CONDITIONING"],
[58, 38, 0, 6, 1, "STRING"],
[59, 40, 0, 27, 0, "INT"],
[60, 39, 0, 27, 1, "INT"]
],
"nodes": [
{
"id": 6,
"pos": [384, 192],
"mode": 0,
"size": [422.8500061035156, 164.30999755859375],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 7,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 45, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 58,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [56],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"cute anime girl with massive fluffy fennec ears and a big fluffy tail blonde messy long hair blue eyes wearing a maid outfit with a long black gold leaf pattern dress and a white apron mouth open placing a fancy black forest cake with candles on top of a dinner table of an old dark Victorian mansion lit by candlelight with a bright window to the foggy forest and very expensive stuff everywhere there are paintings on the walls"
]
},
{
"id": 8,
"pos": [1151, 195],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 11,
"inputs": [
{ "link": 52, "name": "samples", "type": "LATENT" },
{ "link": 46, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [9], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 9,
"pos": [1375, 194],
"mode": 0,
"size": [985.2999877929688, 1060.3800048828125],
"type": "SaveImage",
"flags": {},
"order": 12,
"inputs": [{ "link": 9, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI"]
},
{
"id": 31,
"pos": [816, 192],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 10,
"inputs": [
{ "link": 47, "name": "model", "type": "MODEL" },
{ "link": 57, "name": "positive", "type": "CONDITIONING" },
{ "link": 55, "name": "negative", "type": "CONDITIONING" },
{ "link": 51, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [52],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
1024035737089801,
"randomize",
20,
1,
"euler",
"simple",
1
]
},
{
"id": 35,
"pos": [576, 96],
"mode": 0,
"size": [211.60000610351562, 58],
"type": "FluxGuidance",
"flags": {},
"order": 9,
"inputs": [
{ "link": 56, "name": "conditioning", "type": "CONDITIONING" }
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [57],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "FluxGuidance" },
"widgets_values": [3.5]
},
{
"id": 37,
"pos": [60, 345],
"mode": 0,
"size": [225, 88],
"type": "MarkdownNote",
"color": "#432",
"flags": {},
"order": 0,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": {},
"widgets_values": [
"🛈 [Learn more about this workflow](https://comfyanonymous.github.io/ComfyUI_examples/flux/#flux-dev-1)"
]
},
{
"id": 34,
"pos": [825, 510],
"mode": 0,
"size": [282.8599853515625, 164.0800018310547],
"type": "Note",
"color": "#432",
"flags": {},
"order": 1,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"Note that Flux dev and schnell do not have any negative prompt so CFG should be set to 1.0. Setting CFG to 1.0 means the negative prompt is ignored."
]
},
{
"id": 30,
"pos": [48, 192],
"mode": 0,
"size": [315, 98],
"type": "CheckpointLoaderSimple",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [47],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [45, 54],
"shape": 3,
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [46],
"shape": 3,
"slot_index": 2
}
],
"properties": { "Node name for S&R": "CheckpointLoaderSimple" },
"widgets_values": ["FLUX1/flux1-dev-fp8.safetensors"]
},
{
"id": 33,
"pos": [390, 400],
"mode": 0,
"size": [422.8500061035156, 164.30999755859375],
"type": "CLIPTextEncode",
"color": "#322",
"flags": { "collapsed": true },
"order": 6,
"title": "CLIP Text Encode (Negative Prompt)",
"inputs": [
{ "link": 54, "name": "clip", "type": "CLIP", "slot_index": 0 }
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [55],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [""]
},
{
"id": 27,
"pos": [461.8629455566406, 460.2491149902344],
"mode": 0,
"size": [315, 126],
"type": "EmptySD3LatentImage",
"color": "#323",
"flags": {},
"order": 8,
"inputs": [
{
"pos": [10, 60],
"link": 59,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
},
{
"pos": [10, 36],
"link": 60,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
}
],
"bgcolor": "#535",
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [51],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "EmptySD3LatentImage" },
"widgets_values": [1024, 1024, 1]
},
{
"id": 38,
"pos": [-497.3238525390625, 306.5517578125],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [58] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"prompt",
"cute anime girl with massive fluffy fennec ears and a big fluffy tail blonde messy long hair blue eyes wearing a maid outfit with a long black gold leaf pattern dress and a white apron mouth open placing a fancy black forest cake with candles on top of a dinner table of an old dark Victorian mansion lit by candlelight with a bright window to the foggy forest and very expensive stuff everywhere there are paintings on the walls",
"Prompt",
"The prompt to generate an image from."
]
},
{
"id": 39,
"pos": [-507.4543762207031, 612.9552001953125],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [60], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 1024, "Width", "The width of the image."]
},
{
"id": 40,
"pos": [-497.8695068359375, 864.68994140625],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [59], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 1024, "Height", "The height of the image."]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-508.010986328125, 186.76593017578125, 453.23040771484375,
334.3218994140625
],
"font_size": 24
},
{
"id": 2,
"color": "#A88",
"flags": {},
"title": "Additional",
"bounding": [
-517.1227416992188, 537.53759765625, 644.4954223632812,
599.2609252929688
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 60,
"last_node_id": 40
}
+423
View File
@@ -0,0 +1,423 @@
{
"extra": {
"ds": {
"scale": 0.6830134553650709,
"offset": [750.2474149300585, 120.48924647647146]
},
"node_versions": {
"comfy-core": "0.3.19",
"comfyui-deploy": "171a227856bd5f31e97828d89f83f3741004d05e"
}
},
"links": [
[9, 8, 0, 9, 0, "IMAGE"],
[10, 11, 0, 6, 0, "CLIP"],
[12, 10, 0, 8, 1, "VAE"],
[19, 16, 0, 13, 2, "SAMPLER"],
[20, 17, 0, 13, 3, "SIGMAS"],
[23, 5, 0, 13, 4, "LATENT"],
[24, 13, 0, 8, 0, "LATENT"],
[30, 22, 0, 13, 1, "GUIDER"],
[37, 25, 0, 13, 0, "NOISE"],
[38, 12, 0, 17, 0, "MODEL"],
[39, 12, 0, 22, 0, "MODEL"],
[40, 6, 0, 22, 1, "CONDITIONING"],
[41, 28, 0, 6, 1, "STRING"],
[44, 29, 0, 5, 0, "INT"],
[45, 30, 0, 5, 1, "INT"]
],
"nodes": [
{
"id": 13,
"pos": [842, 215],
"mode": 0,
"size": [355.20001220703125, 106],
"type": "SamplerCustomAdvanced",
"flags": {},
"order": 14,
"inputs": [
{ "link": 37, "name": "noise", "type": "NOISE", "slot_index": 0 },
{ "link": 30, "name": "guider", "type": "GUIDER", "slot_index": 1 },
{ "link": 19, "name": "sampler", "type": "SAMPLER", "slot_index": 2 },
{ "link": 20, "name": "sigmas", "type": "SIGMAS", "slot_index": 3 },
{
"link": 23,
"name": "latent_image",
"type": "LATENT",
"slot_index": 4
}
],
"outputs": [
{
"name": "output",
"type": "LATENT",
"links": [24],
"shape": 3,
"slot_index": 0
},
{
"name": "denoised_output",
"type": "LATENT",
"links": null,
"shape": 3
}
],
"properties": { "Node name for S&R": "SamplerCustomAdvanced" },
"widgets_values": []
},
{
"id": 8,
"pos": [1248, 192],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 15,
"inputs": [
{ "link": 24, "name": "samples", "type": "LATENT" },
{ "link": 12, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [9], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 22,
"pos": [559, 125],
"mode": 0,
"size": [241.79998779296875, 46],
"type": "BasicGuider",
"flags": {},
"order": 13,
"inputs": [
{ "link": 39, "name": "model", "type": "MODEL", "slot_index": 0 },
{
"link": 40,
"name": "conditioning",
"type": "CONDITIONING",
"slot_index": 1
}
],
"outputs": [
{
"name": "GUIDER",
"type": "GUIDER",
"links": [30],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "BasicGuider" },
"widgets_values": []
},
{
"id": 16,
"pos": [480, 720],
"mode": 0,
"size": [315, 58],
"type": "KSamplerSelect",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "SAMPLER", "type": "SAMPLER", "links": [19], "shape": 3 }
],
"properties": { "Node name for S&R": "KSamplerSelect" },
"widgets_values": ["euler"]
},
{
"id": 17,
"pos": [480, 816],
"mode": 0,
"size": [315, 106],
"type": "BasicScheduler",
"flags": {},
"order": 10,
"inputs": [
{ "link": 38, "name": "model", "type": "MODEL", "slot_index": 0 }
],
"outputs": [
{ "name": "SIGMAS", "type": "SIGMAS", "links": [20], "shape": 3 }
],
"properties": { "Node name for S&R": "BasicScheduler" },
"widgets_values": ["simple", 4, 1]
},
{
"id": 27,
"pos": [480, 960],
"mode": 0,
"size": [311.3529052734375, 131.16229248046875],
"type": "Note",
"color": "#432",
"flags": {},
"order": 1,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"The schnell model is a distilled model that can generate a good image with only 4 steps."
]
},
{
"id": 9,
"pos": [1488, 192],
"mode": 0,
"size": [985.3012084960938, 1060.3828125],
"type": "SaveImage",
"flags": {},
"order": 16,
"inputs": [{ "link": 9, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI"]
},
{
"id": 25,
"pos": [480, 587.7890625],
"mode": 0,
"size": [315, 82],
"type": "RandomNoise",
"color": "#2a363b",
"flags": {},
"order": 2,
"inputs": [],
"bgcolor": "#3f5159",
"outputs": [
{ "name": "NOISE", "type": "NOISE", "links": [37], "shape": 3 }
],
"properties": { "Node name for S&R": "RandomNoise" },
"widgets_values": [393845863554716, "randomize"]
},
{
"id": 5,
"pos": [471.7746276855469, 423.91571044921875],
"mode": 0,
"size": [315, 126],
"type": "EmptyLatentImage",
"color": "#323",
"flags": {},
"order": 12,
"inputs": [
{
"pos": [10, 36],
"link": 44,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"pos": [10, 60],
"link": 45,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"bgcolor": "#535",
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [23], "slot_index": 0 }
],
"properties": { "Node name for S&R": "EmptyLatentImage" },
"widgets_values": [1024, 1024, 1]
},
{
"id": 26,
"pos": [41.60395812988281, 517.059326171875],
"mode": 0,
"size": [336, 288],
"type": "Note",
"color": "#432",
"flags": {},
"order": 3,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"If you get an error in any of the nodes above make sure the files are in the correct directories.\n\nSee the top of the examples page for the links : https://comfyanonymous.github.io/ComfyUI_examples/flux/\n\nflux1-schnell.safetensors goes in: ComfyUI/models/unet/\n\nt5xxl_fp16.safetensors and clip_l.safetensors go in: ComfyUI/models/clip/\n\nae.safetensors goes in: ComfyUI/models/vae/\n\n\nTip: You can set the weight_dtype above to one of the fp8 types if you have memory issues."
]
},
{
"id": 11,
"pos": [45.584651947021484, 229.13525390625],
"mode": 0,
"size": [315, 122],
"type": "DualCLIPLoader",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [10],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "DualCLIPLoader" },
"widgets_values": [
"t5xxl_fp16.safetensors",
"clip_l.safetensors",
"flux",
"default"
]
},
{
"id": 12,
"pos": [45.92215347290039, 95.6536865234375],
"mode": 0,
"size": [315, 82],
"type": "UNETLoader",
"color": "#223",
"flags": { "pinned": true },
"order": 5,
"inputs": [],
"bgcolor": "#335",
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [38, 39],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["flux1-schnell.safetensors", "default"]
},
{
"id": 6,
"pos": [385.87054443359375, 216.75625610351562],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 11,
"inputs": [
{ "link": 10, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 41,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [40],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a bottle with a beautiful rainbow galaxy inside it on top of a wooden table in the middle of a modern kitchen beside a plate of vegetables and mushrooms and a wine glasse that contains a planet earth with a plate with a half eaten apple pie on it"
]
},
{
"id": 10,
"pos": [42.45906066894531, 405.2187805175781],
"mode": 0,
"size": [315, 58],
"type": "VAELoader",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [12],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["ae.sft"]
},
{
"id": 28,
"pos": [-620.3922119140625, 199.8013916015625],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 7,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [41] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"prompt",
"a bottle with a beautiful rainbow galaxy inside it on top of a wooden table in the middle of a modern kitchen beside a plate of vegetables and mushrooms and a wine glasse that contains a planet earth with a plate with a half eaten apple pie on it",
"Prompt",
"The prompt to generate an image from."
]
},
{
"id": 29,
"pos": [-677.4290161132812, 597.3389282226562],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [44], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 1024, "Width", "The width of the image."]
},
{
"id": 30,
"pos": [-670.0703735351562, 848.3707885742188],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 9,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [45], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 1024, "Height", "The height of the image."]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-652.184326171875, 99.11231994628906, 565.7923583984375,
362.8800354003906
],
"font_size": 24
},
{
"id": 2,
"color": "#A88",
"flags": {},
"title": "Additional",
"bounding": [
-720.3274536132812, 508.1206970214844, 703.0422973632812,
576.5584716796875
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 45,
"last_node_id": 30
}
+482
View File
@@ -0,0 +1,482 @@
{
"extra": {
"ds": {
"scale": 0.6830134553650709,
"offset": [1030.9627343190857, 159.73178253107423]
},
"groupNodes": {},
"node_versions": {
"comfy-core": "0.3.12",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[19, 16, 0, 13, 2, "SAMPLER"],
[20, 17, 0, 13, 3, "SIGMAS"],
[30, 22, 0, 13, 1, "GUIDER"],
[37, 25, 0, 13, 0, "NOISE"],
[129, 26, 0, 22, 1, "CONDITIONING"],
[175, 44, 0, 26, 0, "CONDITIONING"],
[180, 45, 0, 13, 4, "LATENT"],
[181, 13, 0, 8, 0, "LATENT"],
[190, 12, 0, 17, 0, "MODEL"],
[195, 67, 0, 22, 0, "MODEL"],
[205, 11, 0, 44, 0, "CLIP"],
[206, 10, 0, 8, 1, "VAE"],
[209, 12, 0, 67, 0, "MODEL"],
[210, 13, 0, 73, 0, "LATENT"],
[211, 10, 0, 73, 1, "VAE"],
[215, 73, 0, 75, 0, "IMAGE"],
[216, 78, 0, 44, 1, "STRING"],
[217, 79, 0, 45, 0, "INT"],
[218, 83, 0, 45, 1, "INT"]
],
"nodes": [
{
"id": 16,
"pos": [484, 751],
"mode": 0,
"size": [315, 58],
"type": "KSamplerSelect",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "SAMPLER", "type": "SAMPLER", "links": [19], "shape": 3 }
],
"properties": { "Node name for S&R": "KSamplerSelect" },
"widgets_values": ["euler"]
},
{
"id": 17,
"pos": [478, 860],
"mode": 0,
"size": [315, 106],
"type": "BasicScheduler",
"flags": {},
"order": 10,
"inputs": [
{ "link": 190, "name": "model", "type": "MODEL", "slot_index": 0 }
],
"outputs": [
{ "name": "SIGMAS", "type": "SIGMAS", "links": [20], "shape": 3 }
],
"properties": { "Node name for S&R": "BasicScheduler" },
"widgets_values": ["simple", 20, 1]
},
{
"id": 26,
"pos": [520, 100],
"mode": 0,
"size": [317.4000244140625, 58],
"type": "FluxGuidance",
"color": "#233",
"flags": {},
"order": 14,
"inputs": [
{ "link": 175, "name": "conditioning", "type": "CONDITIONING" }
],
"bgcolor": "#355",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [129],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "FluxGuidance" },
"widgets_values": [6]
},
{
"id": 45,
"pos": [475.540771484375, 432.673583984375],
"mode": 0,
"size": [315, 130],
"type": "EmptyHunyuanLatentVideo",
"flags": {},
"order": 13,
"inputs": [
{
"link": 217,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"link": 218,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [180], "slot_index": 0 }
],
"properties": { "Node name for S&R": "EmptyHunyuanLatentVideo" },
"widgets_values": [848, 480, 73, 1]
},
{
"id": 22,
"pos": [600, 0],
"mode": 0,
"size": [222.3482666015625, 46],
"type": "BasicGuider",
"flags": {},
"order": 15,
"inputs": [
{ "link": 195, "name": "model", "type": "MODEL", "slot_index": 0 },
{
"link": 129,
"name": "conditioning",
"type": "CONDITIONING",
"slot_index": 1
}
],
"outputs": [
{
"name": "GUIDER",
"type": "GUIDER",
"links": [30],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "BasicGuider" },
"widgets_values": []
},
{
"id": 67,
"pos": [360, 0],
"mode": 0,
"size": [210, 58],
"type": "ModelSamplingSD3",
"flags": {},
"order": 11,
"inputs": [{ "link": 209, "name": "model", "type": "MODEL" }],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [195], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ModelSamplingSD3" },
"widgets_values": [7]
},
{
"id": 73,
"pos": [1150, 200],
"mode": 0,
"size": [210, 150],
"type": "VAEDecodeTiled",
"flags": {},
"order": 18,
"inputs": [
{ "link": 210, "name": "samples", "type": "LATENT" },
{ "link": 211, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [215], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecodeTiled" },
"widgets_values": [256, 64, 64, 8]
},
{
"id": 8,
"pos": [1150, 90],
"mode": 2,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 17,
"inputs": [
{ "link": 181, "name": "samples", "type": "LATENT" },
{ "link": 206, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 77,
"pos": [0, 0],
"mode": 0,
"size": [350, 110],
"type": "Note",
"color": "#432",
"flags": {},
"order": 1,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": {},
"widgets_values": [
"Select a fp8 weight_dtype if you are running out of memory."
]
},
{
"id": 13,
"pos": [860, 200],
"mode": 0,
"size": [272.3617858886719, 124.53733825683594],
"type": "SamplerCustomAdvanced",
"flags": {},
"order": 16,
"inputs": [
{ "link": 37, "name": "noise", "type": "NOISE", "slot_index": 0 },
{ "link": 30, "name": "guider", "type": "GUIDER", "slot_index": 1 },
{ "link": 19, "name": "sampler", "type": "SAMPLER", "slot_index": 2 },
{ "link": 20, "name": "sigmas", "type": "SIGMAS", "slot_index": 3 },
{
"link": 180,
"name": "latent_image",
"type": "LATENT",
"slot_index": 4
}
],
"outputs": [
{
"name": "output",
"type": "LATENT",
"links": [181, 210],
"shape": 3,
"slot_index": 0
},
{
"name": "denoised_output",
"type": "LATENT",
"links": null,
"shape": 3
}
],
"properties": { "Node name for S&R": "SamplerCustomAdvanced" },
"widgets_values": []
},
{
"id": 75,
"pos": [1410, 200],
"mode": 0,
"size": [315, 366],
"type": "SaveAnimatedWEBP",
"flags": {},
"order": 19,
"inputs": [{ "link": 215, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", 24, false, 80, "default"]
},
{
"id": 25,
"pos": [479, 618],
"mode": 0,
"size": [315, 82],
"type": "RandomNoise",
"color": "#2a363b",
"flags": {},
"order": 2,
"inputs": [],
"bgcolor": "#3f5159",
"outputs": [
{ "name": "NOISE", "type": "NOISE", "links": [37], "shape": 3 }
],
"properties": { "Node name for S&R": "RandomNoise" },
"widgets_values": [1, "randomize"]
},
{
"id": 12,
"pos": [0, 150],
"mode": 0,
"size": [350, 82],
"type": "UNETLoader",
"color": "#223",
"flags": {},
"order": 3,
"inputs": [],
"bgcolor": "#335",
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [190, 209],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["hunyuan_video_t2v_720p_bf16.safetensors", "default"]
},
{
"id": 10,
"pos": [0, 420],
"mode": 0,
"size": [350, 60],
"type": "VAELoader",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [206, 211],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["hunyuan_video_vae_bf16.safetensors"]
},
{
"id": 11,
"pos": [0, 270],
"mode": 0,
"size": [350, 106],
"type": "DualCLIPLoader",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [205],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "DualCLIPLoader" },
"widgets_values": [
"clip_l.safetensors",
"llava_llama3_fp8_scaled.safetensors",
"hunyuan_video",
"default"
]
},
{
"id": 44,
"pos": [459.0518798828125, 226.60147094726562],
"mode": 0,
"size": [285.6000061035156, 54],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 12,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 205, "name": "clip", "type": "CLIP" },
{
"link": 216,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [175],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"anime style anime girl with massive fennec ears and one big fluffy tail, she has blonde hair long hair blue eyes wearing a pink sweater and a long blue skirt walking in a beautiful outdoor scenery with snow mountains in the background"
]
},
{
"id": 83,
"pos": [-591.1870727539062, 751.6737670898438],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [218], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 480, "Height", "The height of the video."]
},
{
"id": 74,
"pos": [1151.89599609375, 402.439697265625],
"mode": 0,
"size": [210, 170],
"type": "Note",
"color": "#432",
"flags": {},
"order": 7,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": {},
"widgets_values": [
"Use the tiled decode node by default because most people will need it.\n\nLower the tile_size and overlap if you run out of memory."
]
},
{
"id": 78,
"pos": [-560.058837890625, 155.3986358642578],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [216] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"prompt",
"anime style anime girl with massive fennec ears and one big fluffy tail, she has blonde hair long hair blue eyes wearing a pink sweater and a long blue skirt walking in a beautiful outdoor scenery with snow mountains in the background",
"Prompt",
"The prompt to generate the video from."
]
},
{
"id": 79,
"pos": [-588.2138061523438, 493.3861389160156],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 9,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [217], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 848, "Width", "The width of the video."]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-662.9779052734375, 14.93773078918457, 575.2218627929688,
384.41424560546875
],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-659.7621459960938, 414.3213195800781, 570.7182006835938,
594.72216796875
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 218,
"last_node_id": 83
}
+240
View File
@@ -0,0 +1,240 @@
{
"extra": {
"ds": { "scale": 1, "offset": { "0": 0, "1": 0 } },
"node_versions": {
"comfy-core": "0.3.12",
"comfyui-deploy": "171a227856bd5f31e97828d89f83f3741004d05e"
}
},
"links": [
[1, 4, 0, 3, 0, "MODEL"],
[2, 5, 0, 3, 3, "LATENT"],
[3, 4, 1, 6, 0, "CLIP"],
[4, 6, 0, 3, 1, "CONDITIONING"],
[5, 4, 1, 7, 0, "CLIP"],
[6, 7, 0, 3, 2, "CONDITIONING"],
[7, 3, 0, 8, 0, "LATENT"],
[8, 4, 2, 8, 1, "VAE"],
[9, 8, 0, 9, 0, "IMAGE"],
[10, 12, 0, 6, 1, "STRING"],
[11, 13, 0, 7, 1, "STRING"]
],
"nodes": [
{
"id": 5,
"pos": [473, 609],
"mode": 0,
"size": [315, 106],
"type": "EmptyLatentImage",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [2], "slot_index": 0 }
],
"properties": { "Node name for S&R": "EmptyLatentImage" },
"widgets_values": [512, 512, 1]
},
{
"id": 3,
"pos": [863, 186],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 7,
"inputs": [
{ "link": 1, "name": "model", "type": "MODEL" },
{ "link": 4, "name": "positive", "type": "CONDITIONING" },
{ "link": 6, "name": "negative", "type": "CONDITIONING" },
{ "link": 2, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [7], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
156680208700286,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 8,
"pos": [1209, 188],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 8,
"inputs": [
{ "link": 7, "name": "samples", "type": "LATENT" },
{ "link": 8, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [9], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 9,
"pos": [1451, 189],
"mode": 0,
"size": [210, 58],
"type": "SaveImage",
"flags": {},
"order": 9,
"inputs": [{ "link": 9, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI"]
},
{
"id": 4,
"pos": [26, 474],
"mode": 0,
"size": [315, 98],
"type": "CheckpointLoaderSimple",
"flags": {},
"order": 1,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [1], "slot_index": 0 },
{ "name": "CLIP", "type": "CLIP", "links": [3, 5], "slot_index": 1 },
{ "name": "VAE", "type": "VAE", "links": [8], "slot_index": 2 }
],
"properties": { "Node name for S&R": "CheckpointLoaderSimple" },
"widgets_values": ["v1-5-pruned-emaonly.ckpt"]
},
{
"id": 6,
"pos": [415, 186],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"flags": {},
"order": 6,
"inputs": [
{ "link": 3, "name": "clip", "type": "CLIP" },
{
"link": 10,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [4],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 7,
"pos": [413, 389],
"mode": 0,
"size": [425.27801513671875, 180.6060791015625],
"type": "CLIPTextEncode",
"flags": {},
"order": 5,
"inputs": [
{ "link": 5, "name": "clip", "type": "CLIP" },
{
"link": 11,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [6],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": ["text, watermark"]
},
{
"id": 13,
"pos": [-70, -60],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [11],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": ["negative_prompt", "text, watermark", "", ""]
},
{
"id": 12,
"pos": [-72, 193],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [10],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"positive_prompt",
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
"",
""
]
},
{
"id": 14,
"pos": [419, 23],
"mode": 0,
"size": [210, 96],
"type": "ComfyDeploy",
"color": "#432",
"flags": {},
"order": 4,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "version": "", "workflow_id": "", "workflow_name": "" },
"widgets_values": ["", "", "1"]
}
],
"config": {},
"groups": [],
"version": 0.4,
"last_link_id": 11,
"last_node_id": 14
}
+409
View File
@@ -0,0 +1,409 @@
{
"extra": {
"ds": {
"scale": 0.620921323059155,
"offset": [1535.1422819333457, 285.0502099350181]
},
"node_versions": {
"comfy-core": "0.3.12",
"comfyui-deploy": "171a227856bd5f31e97828d89f83f3741004d05e"
}
},
"links": [
[7, 3, 0, 8, 0, "LATENT"],
[21, 16, 0, 3, 1, "CONDITIONING"],
[51, 8, 0, 9, 0, "IMAGE"],
[53, 4, 2, 8, 1, "VAE"],
[80, 40, 0, 3, 2, "CONDITIONING"],
[99, 4, 0, 3, 0, "MODEL"],
[100, 53, 0, 3, 3, "LATENT"],
[103, 43, 0, 16, 0, "CLIP"],
[104, 43, 0, 40, 0, "CLIP"],
[105, 54, 0, 16, 1, "STRING"],
[106, 55, 0, 40, 1, "STRING"],
[107, 56, 0, 53, 0, "INT"],
[108, 57, 0, 53, 1, "INT"]
],
"nodes": [
{
"id": 8,
"pos": [1200, 96],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 14,
"inputs": [
{ "link": 7, "name": "samples", "type": "LATENT" },
{ "link": 53, "name": "vae", "type": "VAE", "slot_index": 1 }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [51], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 9,
"pos": [1440, 96],
"mode": 0,
"size": [952.5112915039062, 1007.9328002929688],
"type": "SaveImage",
"flags": {},
"order": 15,
"inputs": [
{ "link": 51, "name": "images", "type": "IMAGE", "slot_index": 0 }
],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI"]
},
{
"id": 53,
"pos": [480, 576],
"mode": 0,
"size": [315, 106],
"type": "EmptySD3LatentImage",
"flags": {},
"order": 10,
"inputs": [
{
"link": 107,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"link": 108,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [100],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "EmptySD3LatentImage" },
"widgets_values": [1024, 1024, 1]
},
{
"id": 50,
"pos": [-384, 144],
"mode": 0,
"size": [223.34756469726562, 254.37765502929688],
"type": "Note",
"color": "#432",
"flags": {},
"order": 0,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"SD3 supports different text encoder configurations, you can see how to load them here.\n\n\nMake sure to put these files:\nclip_g.safetensors\nclip_l.safetensors\nt5xxl_fp16.safetensors\n\n\nIn the ComfyUI/models/clip directory"
]
},
{
"id": 41,
"pos": [-96, 0],
"mode": 0,
"size": [315, 82],
"type": "CLIPLoader",
"flags": {},
"order": 1,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPLoader" },
"widgets_values": ["t5xxl_fp16.safetensors", "sd3", "default"]
},
{
"id": 4,
"pos": [-96, 480],
"mode": 0,
"size": [384.75592041015625, 98],
"type": "CheckpointLoaderSimple",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [99], "slot_index": 0 },
{ "name": "CLIP", "type": "CLIP", "links": [], "slot_index": 1 },
{ "name": "VAE", "type": "VAE", "links": [53], "slot_index": 2 }
],
"properties": { "Node name for S&R": "CheckpointLoaderSimple" },
"widgets_values": ["sd3.5_large.safetensors"]
},
{
"id": 3,
"pos": [864, 96],
"mode": 0,
"size": [315, 474],
"type": "KSampler",
"flags": {},
"order": 13,
"inputs": [
{ "link": 99, "name": "model", "type": "MODEL", "slot_index": 0 },
{ "link": 21, "name": "positive", "type": "CONDITIONING" },
{ "link": 80, "name": "negative", "type": "CONDITIONING" },
{ "link": 100, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [7], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
797639488625463,
"randomize",
20,
5.45,
"euler",
"sgm_uniform",
1
]
},
{
"id": 51,
"pos": [-96, 624],
"mode": 0,
"size": [384, 192],
"type": "Note",
"color": "#432",
"flags": {},
"order": 3,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"sd3.5_large.safetensors and sd3.5_medium.safetensors are files that do not contain any CLIP/text encoder weights so you need to load them separately.\n\nThey go in the ComfyUI/models/checkpoints directory."
]
},
{
"id": 43,
"pos": [-96, 288],
"mode": 0,
"size": [315, 106],
"type": "TripleCLIPLoader",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [103, 104],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "TripleCLIPLoader" },
"widgets_values": [
"clip_l.safetensors",
"clip_g.safetensors",
"t5xxl_fp16.safetensors"
]
},
{
"id": 42,
"pos": [-96, 144],
"mode": 0,
"size": [315, 106],
"type": "DualCLIPLoader",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "DualCLIPLoader" },
"widgets_values": [
"clip_l.safetensors",
"clip_g.safetensors",
"sd3",
"default"
]
},
{
"id": 40,
"pos": [399.0046081542969, 337.009765625],
"mode": 0,
"size": [397.45977783203125, 151.2898406982422],
"type": "CLIPTextEncode",
"color": "#322",
"flags": {},
"order": 11,
"title": "Negative Prompt",
"inputs": [
{ "link": 104, "name": "clip", "type": "CLIP" },
{
"link": 106,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [80],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [""]
},
{
"id": 16,
"pos": [398.5880432128906, 108.27188110351562],
"mode": 0,
"size": [397.21484375, 158.671875],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 12,
"title": "Positive Prompt",
"inputs": [
{ "link": 103, "name": "clip", "type": "CLIP" },
{
"link": 105,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [21],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a bottle with a rainbow galaxy inside it on top of a wooden table on a snowy mountain top with the ocean and clouds in the background with a shot glass beside containing darkness beside a snow sculpture in the shape of a fox"
]
},
{
"id": 57,
"pos": [-1015.1683349609375, 980.396240234375],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [108], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 1024, "Height", "The height of the image. "]
},
{
"id": 56,
"pos": [-1014.532470703125, 732.8568725585938],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 7,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [107], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 1024, "Width", "The width of the image. "]
},
{
"id": 55,
"pos": [-1010.8035278320312, 487.404052734375],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [106] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": ["negative_prompt", "", "Negative Prompt", ""]
},
{
"id": 54,
"pos": [-1059.1072998046875, 127.12823486328125],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 9,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [105] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"prompt",
"a bottle with a rainbow galaxy inside it on top of a wooden table on a snowy mountain top with the ocean and clouds in the background with a shot glass beside containing darkness beside a snow sculpture in the shape of a fox",
"Prompt",
"The prompt to generate an image from."
]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Different Text Encoder Configurations",
"bounding": [-144, -96, 480, 528],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-1194.5233154296875, 388.3871154785156, 722.5393676757812,
824.6746215820312
],
"font_size": 24
},
{
"id": 3,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-1148.487060546875, 30.449382781982422, 552.3358764648438,
329.3829040527344
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 108,
"last_node_id": 57
}
+452
View File
@@ -0,0 +1,452 @@
{
"extra": {
"ds": {
"scale": 0.6934334949441662,
"offset": [276.7941771713029, -7.923387380923855]
},
"node_versions": {
"comfy-core": "0.3.18",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[35, 3, 0, 8, 0, "LATENT"],
[56, 8, 0, 28, 0, "IMAGE"],
[74, 38, 0, 6, 0, "CLIP"],
[75, 38, 0, 7, 0, "CLIP"],
[76, 39, 0, 8, 1, "VAE"],
[93, 8, 0, 47, 0, "IMAGE"],
[94, 49, 0, 51, 0, "CLIP_VISION"],
[97, 6, 0, 50, 0, "CONDITIONING"],
[98, 7, 0, 50, 1, "CONDITIONING"],
[99, 39, 0, 50, 2, "VAE"],
[101, 50, 0, 3, 1, "CONDITIONING"],
[102, 50, 1, 3, 2, "CONDITIONING"],
[103, 50, 2, 3, 3, "LATENT"],
[107, 51, 0, 50, 3, "CLIP_VISION_OUTPUT"],
[110, 37, 0, 54, 0, "MODEL"],
[111, 54, 0, 3, 0, "MODEL"],
[112, 55, 0, 6, 1, "STRING"],
[113, 56, 0, 7, 1, "STRING"],
[115, 57, 0, 50, 4, "IMAGE"],
[116, 57, 0, 51, 1, "IMAGE"],
[117, 58, 0, 50, 5, "INT"],
[118, 59, 0, 50, 6, "INT"]
],
"nodes": [
{
"id": 8,
"pos": [1210, 190],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 15,
"inputs": [
{ "link": 35, "name": "samples", "type": "LATENT" },
{ "link": 76, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [56, 93], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 39,
"pos": [866.3932495117188, 499.18597412109375],
"mode": 0,
"size": [306.36004638671875, 58],
"type": "VAELoader",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "VAE", "type": "VAE", "links": [76, 99], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["wan_2.1_vae.safetensors"]
},
{
"id": 28,
"pos": [1460, 190],
"mode": 0,
"size": [870.8511352539062, 643.7430419921875],
"type": "SaveAnimatedWEBP",
"flags": {},
"order": 16,
"inputs": [{ "link": 56, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", 16, false, 90, "default", ""]
},
{
"id": 47,
"pos": [2367.213134765625, 193.6114959716797],
"mode": 4,
"size": [315, 130],
"type": "SaveWEBM",
"flags": {},
"order": 17,
"inputs": [{ "link": 93, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": { "Node name for S&R": "SaveWEBM" },
"widgets_values": ["ComfyUI", "vp9", 24, 32]
},
{
"id": 50,
"pos": [673.0507202148438, 627.272705078125],
"mode": 0,
"size": [342.5999755859375, 250],
"type": "WanImageToVideo",
"flags": {},
"order": 13,
"inputs": [
{ "link": 97, "name": "positive", "type": "CONDITIONING" },
{ "link": 98, "name": "negative", "type": "CONDITIONING" },
{ "link": 99, "name": "vae", "type": "VAE" },
{
"link": 107,
"name": "clip_vision_output",
"type": "CLIP_VISION_OUTPUT",
"shape": 7
},
{ "link": 115, "name": "start_image", "type": "IMAGE", "shape": 7 },
{
"pos": [10, 116],
"link": 117,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"pos": [10, 140],
"link": 118,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{
"name": "positive",
"type": "CONDITIONING",
"links": [101],
"slot_index": 0
},
{
"name": "negative",
"type": "CONDITIONING",
"links": [102],
"slot_index": 1
},
{ "name": "latent", "type": "LATENT", "links": [103], "slot_index": 2 }
],
"properties": { "Node name for S&R": "WanImageToVideo" },
"widgets_values": [512, 512, 33, 1]
},
{
"id": 3,
"pos": [863, 187],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 14,
"inputs": [
{ "link": 111, "name": "model", "type": "MODEL" },
{ "link": 101, "name": "positive", "type": "CONDITIONING" },
{ "link": 102, "name": "negative", "type": "CONDITIONING" },
{ "link": 103, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [35], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
55104841546759,
"randomize",
20,
6,
"uni_pc",
"simple",
1
]
},
{
"id": 51,
"pos": [360, 640],
"mode": 0,
"size": [253.60000610351562, 78],
"type": "CLIPVisionEncode",
"flags": {},
"order": 12,
"inputs": [
{ "link": 94, "name": "clip_vision", "type": "CLIP_VISION" },
{ "link": 116, "name": "image", "type": "IMAGE" }
],
"outputs": [
{
"name": "CLIP_VISION_OUTPUT",
"type": "CLIP_VISION_OUTPUT",
"links": [107],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPVisionEncode" },
"widgets_values": ["none"]
},
{
"id": 54,
"pos": [510, 70],
"mode": 0,
"size": [315, 58],
"type": "ModelSamplingSD3",
"flags": {},
"order": 9,
"inputs": [{ "link": 110, "name": "model", "type": "MODEL" }],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [111], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ModelSamplingSD3" },
"widgets_values": [8]
},
{
"id": 6,
"pos": [415, 186],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 10,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 74, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 112,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [97],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a cute anime girl with massive fennec ears and a big fluffy tail wearing a maid outfit turning around"
]
},
{
"id": 55,
"pos": [-672.6411743164062, 140.167724609375],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 1,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [112] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"positive_prompt",
"a cute anime girl with massive fennec ears and a big fluffy tail wearing a maid outfit turning around",
"Prompt",
"The text prompt to guide video generation."
]
},
{
"id": 57,
"pos": [-664.3828125, 405.5113525390625],
"mode": 0,
"size": [390.5999755859375, 366],
"type": "ComfyUIDeployExternalImage",
"flags": {},
"order": 2,
"inputs": [
{ "link": null, "name": "default_value", "type": "IMAGE", "shape": 7 }
],
"outputs": [{ "name": "image", "type": "IMAGE", "links": [115, 116] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalImage" },
"widgets_values": [
"image_url",
"Image Url",
"URL of the input image.",
"https://comfy-deploy-output.s3.us-east-2.amazonaws.com/assets/img_GZMJYXDnLbYjWybu.png",
""
]
},
{
"id": 56,
"pos": [-674.42333984375, 953.0498046875],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [113] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"negative_prompt",
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
"Negative Prompt",
"The negative prompt to use. Use it to address details that you don't want in the video. This could be colors, objects, scenery and even the small details (e.g. moustache, blurry, low resolution). "
]
},
{
"id": 58,
"pos": [-668.314453125, 1208.204833984375],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [117], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 512, "Width", "The width of the video. "]
},
{
"id": 59,
"pos": [-665.8831787109375, 1461.255126953125],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [118], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 512, "Height", "The Height of the video."]
},
{
"id": 37,
"pos": [-3.3700287342071533, 81.69654846191406],
"mode": 0,
"size": [346.7470703125, 82],
"type": "UNETLoader",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [110], "slot_index": 0 }
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["wan2.1_i2v_480p_14B_bf16.safetensors", "default"]
},
{
"id": 38,
"pos": [-3.7316977977752686, 230.76495361328125],
"mode": 0,
"size": [390, 98],
"type": "CLIPLoader",
"flags": {},
"order": 7,
"inputs": [],
"outputs": [
{ "name": "CLIP", "type": "CLIP", "links": [74, 75], "slot_index": 0 }
],
"properties": { "Node name for S&R": "CLIPLoader" },
"widgets_values": [
"umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"wan",
"default"
]
},
{
"id": 49,
"pos": [16.57183265686035, 640.22314453125],
"mode": 0,
"size": [315, 58],
"type": "CLIPVisionLoader",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [
{
"name": "CLIP_VISION",
"type": "CLIP_VISION",
"links": [94],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPVisionLoader" },
"widgets_values": ["clip_vision_h.safetensors"]
},
{
"id": 7,
"pos": [414.1811828613281, 393.8824768066406],
"mode": 0,
"size": [425.27801513671875, 180.6060791015625],
"type": "CLIPTextEncode",
"color": "#322",
"flags": {},
"order": 11,
"title": "CLIP Text Encode (Negative Prompt)",
"inputs": [
{ "link": 75, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 113,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [98],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-708.2940063476562, 47.549102783203125, 617.7969360351562,
761.303955078125
],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-703.6196899414062, 861.310791015625, 625.998779296875,
845.0675048828125
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 118,
"last_node_id": 59
}
+452
View File
@@ -0,0 +1,452 @@
{
"extra": {
"ds": {
"scale": 0.6303940863128795,
"offset": [-209.31226203277674, -32.62073943929215]
},
"node_versions": {
"comfy-core": "0.3.18",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[1, 6, 0, 1, 0, "LATENT"],
[2, 2, 0, 1, 1, "VAE"],
[3, 1, 0, 3, 0, "IMAGE"],
[4, 1, 0, 4, 0, "IMAGE"],
[5, 9, 0, 5, 0, "CONDITIONING"],
[6, 18, 0, 5, 1, "CONDITIONING"],
[7, 2, 0, 5, 2, "VAE"],
[8, 7, 0, 5, 3, "CLIP_VISION_OUTPUT"],
[9, 11, 0, 5, 4, "IMAGE"],
[10, 13, 0, 5, 5, "INT"],
[11, 14, 0, 5, 6, "INT"],
[12, 8, 0, 6, 0, "MODEL"],
[13, 5, 0, 6, 1, "CONDITIONING"],
[14, 5, 1, 6, 2, "CONDITIONING"],
[15, 5, 2, 6, 3, "LATENT"],
[16, 17, 0, 7, 0, "CLIP_VISION"],
[17, 11, 0, 7, 1, "IMAGE"],
[18, 15, 0, 8, 0, "MODEL"],
[19, 16, 0, 9, 0, "CLIP"],
[20, 10, 0, 9, 1, "STRING"],
[21, 16, 0, 18, 0, "CLIP"],
[22, 12, 0, 18, 1, "STRING"]
],
"nodes": [
{
"id": 1,
"pos": [1823.98876953125, 702.6244506835938],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 15,
"inputs": [
{ "link": 1, "name": "samples", "type": "LATENT" },
{ "link": 2, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [3, 4], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 2,
"pos": [1480.382080078125, 1011.8104248046875],
"mode": 0,
"size": [306.36004638671875, 58],
"type": "VAELoader",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "VAE", "type": "VAE", "links": [2, 7], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["wan_2.1_vae.safetensors"]
},
{
"id": 3,
"pos": [2073.98876953125, 702.6244506835938],
"mode": 0,
"size": [870.8511352539062, 643.7430419921875],
"type": "SaveAnimatedWEBP",
"flags": {},
"order": 16,
"inputs": [{ "link": 3, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", 16, false, 90, "default", ""]
},
{
"id": 4,
"pos": [2981.201904296875, 706.2359008789062],
"mode": 4,
"size": [315, 130],
"type": "SaveWEBM",
"flags": {},
"order": 17,
"inputs": [{ "link": 4, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": { "Node name for S&R": "SaveWEBM" },
"widgets_values": ["ComfyUI", "vp9", 24, 32]
},
{
"id": 5,
"pos": [1287.03955078125, 1139.8970947265625],
"mode": 0,
"size": [342.5999755859375, 250],
"type": "WanImageToVideo",
"flags": {},
"order": 13,
"inputs": [
{ "link": 5, "name": "positive", "type": "CONDITIONING" },
{ "link": 6, "name": "negative", "type": "CONDITIONING" },
{ "link": 7, "name": "vae", "type": "VAE" },
{
"link": 8,
"name": "clip_vision_output",
"type": "CLIP_VISION_OUTPUT",
"shape": 7
},
{ "link": 9, "name": "start_image", "type": "IMAGE", "shape": 7 },
{
"pos": [10, 116],
"link": 10,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"pos": [10, 140],
"link": 11,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{
"name": "positive",
"type": "CONDITIONING",
"links": [13],
"slot_index": 0
},
{
"name": "negative",
"type": "CONDITIONING",
"links": [14],
"slot_index": 1
},
{ "name": "latent", "type": "LATENT", "links": [15], "slot_index": 2 }
],
"properties": { "Node name for S&R": "WanImageToVideo" },
"widgets_values": [512, 512, 33, 1]
},
{
"id": 6,
"pos": [1476.98876953125, 699.6244506835938],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 14,
"inputs": [
{ "link": 12, "name": "model", "type": "MODEL" },
{ "link": 13, "name": "positive", "type": "CONDITIONING" },
{ "link": 14, "name": "negative", "type": "CONDITIONING" },
{ "link": 15, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [1], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
625624384968868,
"randomize",
20,
6,
"uni_pc",
"simple",
1
]
},
{
"id": 7,
"pos": [973.98876953125, 1152.6243896484375],
"mode": 0,
"size": [253.60000610351562, 78],
"type": "CLIPVisionEncode",
"flags": {},
"order": 10,
"inputs": [
{ "link": 16, "name": "clip_vision", "type": "CLIP_VISION" },
{ "link": 17, "name": "image", "type": "IMAGE" }
],
"outputs": [
{
"name": "CLIP_VISION_OUTPUT",
"type": "CLIP_VISION_OUTPUT",
"links": [8],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPVisionEncode" },
"widgets_values": ["none"]
},
{
"id": 8,
"pos": [1123.98876953125, 582.6244506835938],
"mode": 0,
"size": [315, 58],
"type": "ModelSamplingSD3",
"flags": {},
"order": 11,
"inputs": [{ "link": 18, "name": "model", "type": "MODEL" }],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [12], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ModelSamplingSD3" },
"widgets_values": [8]
},
{
"id": 9,
"pos": [1028.98876953125, 698.6244506835938],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 12,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 19, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 20,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [5],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a cute anime girl with massive fennec ears and a big fluffy tail wearing a maid outfit turning around"
]
},
{
"id": 11,
"pos": [-50.3940315246582, 918.1358032226562],
"mode": 0,
"size": [390.5999755859375, 366],
"type": "ComfyUIDeployExternalImage",
"flags": {},
"order": 1,
"inputs": [
{ "link": null, "name": "default_value", "type": "IMAGE", "shape": 7 }
],
"outputs": [{ "name": "image", "type": "IMAGE", "links": [9, 17] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalImage" },
"widgets_values": [
"image_url",
"Image Url",
"URL of the input image.",
"https://comfy-deploy-output.s3.us-east-2.amazonaws.com/assets/img_GZMJYXDnLbYjWybu.png",
""
]
},
{
"id": 12,
"pos": [-60.4345588684082, 1465.6741943359375],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [22] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"negative_prompt",
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
"Negative Prompt",
"The negative prompt to use. Use it to address details that you don't want in the video. This could be colors, objects, scenery and even the small details (e.g. moustache, blurry, low resolution). "
]
},
{
"id": 13,
"pos": [-54.3256721496582, 1720.8292236328125],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [10], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 512, "Width", "The width of the video. "]
},
{
"id": 14,
"pos": [-51.8943977355957, 1973.8795166015625],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [11], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 512, "Height", "The Height of the video."]
},
{
"id": 16,
"pos": [610.257080078125, 743.389404296875],
"mode": 0,
"size": [390, 98],
"type": "CLIPLoader",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{ "name": "CLIP", "type": "CLIP", "links": [19, 21], "slot_index": 0 }
],
"properties": { "Node name for S&R": "CLIPLoader" },
"widgets_values": [
"umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"wan",
"default"
]
},
{
"id": 17,
"pos": [630.5606079101562, 1152.8475341796875],
"mode": 0,
"size": [315, 58],
"type": "CLIPVisionLoader",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{
"name": "CLIP_VISION",
"type": "CLIP_VISION",
"links": [16],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPVisionLoader" },
"widgets_values": ["clip_vision_h.safetensors"]
},
{
"id": 18,
"pos": [1028.169921875, 906.5068969726562],
"mode": 0,
"size": [425.27801513671875, 180.6060791015625],
"type": "CLIPTextEncode",
"color": "#322",
"flags": {},
"order": 9,
"title": "CLIP Text Encode (Negative Prompt)",
"inputs": [
{ "link": 21, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 22,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [6],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
]
},
{
"id": 15,
"pos": [610.6187744140625, 594.3209838867188],
"mode": 0,
"size": [346.7470703125, 82],
"type": "UNETLoader",
"flags": {},
"order": 7,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [18], "slot_index": 0 }
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["wan2.1_i2v_720p_14B_bf16.safetensors", "default"]
},
{
"id": 10,
"pos": [-60.696964263916016, 649.5515747070312],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [20] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"positive_prompt",
"a cute anime girl with massive fennec ears and a big fluffy tail wearing a maid outfit running towards front happily",
"Prompt",
"The text prompt to guide video generation."
]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-94.30522155761719, 560.1735229492188, 617.7969360351562,
761.303955078125
],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-89.63090515136719, 1373.9351806640625, 625.998779296875,
845.0675048828125
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 22,
"last_node_id": 18
}
+359
View File
@@ -0,0 +1,359 @@
{
"extra": {
"ds": {
"scale": 0.8390545288824369,
"offset": [814.3725295729478, -347.90757575249455]
},
"node_versions": {
"comfy-core": "0.3.18",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[35, 3, 0, 8, 0, "LATENT"],
[46, 6, 0, 3, 1, "CONDITIONING"],
[52, 7, 0, 3, 2, "CONDITIONING"],
[56, 8, 0, 28, 0, "IMAGE"],
[74, 38, 0, 6, 0, "CLIP"],
[75, 38, 0, 7, 0, "CLIP"],
[76, 39, 0, 8, 1, "VAE"],
[91, 40, 0, 3, 3, "LATENT"],
[93, 8, 0, 47, 0, "IMAGE"],
[94, 37, 0, 48, 0, "MODEL"],
[95, 48, 0, 3, 0, "MODEL"],
[96, 49, 0, 6, 1, "STRING"],
[97, 50, 0, 7, 1, "STRING"],
[99, 52, 0, 40, 1, "INT"],
[100, 51, 0, 40, 0, "INT"]
],
"nodes": [
{
"id": 8,
"pos": [1210, 190],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 12,
"inputs": [
{ "link": 35, "name": "samples", "type": "LATENT" },
{ "link": 76, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [56, 93], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 39,
"pos": [866.3932495117188, 499.18597412109375],
"mode": 0,
"size": [306.36004638671875, 58],
"type": "VAELoader",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "VAE", "type": "VAE", "links": [76], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["wan_2.1_vae.safetensors"]
},
{
"id": 47,
"pos": [2367.213134765625, 193.6114959716797],
"mode": 4,
"size": [315, 130],
"type": "SaveWEBM",
"flags": {},
"order": 14,
"inputs": [{ "link": 93, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": { "Node name for S&R": "SaveWEBM" },
"widgets_values": ["ComfyUI", "vp9", 24, 32]
},
{
"id": 3,
"pos": [863, 187],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 11,
"inputs": [
{ "link": 95, "name": "model", "type": "MODEL" },
{ "link": 46, "name": "positive", "type": "CONDITIONING" },
{ "link": 52, "name": "negative", "type": "CONDITIONING" },
{ "link": 91, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [35], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
577746309562741,
"randomize",
30,
6,
"uni_pc",
"simple",
1
]
},
{
"id": 48,
"pos": [440, 50],
"mode": 0,
"size": [210, 58],
"type": "ModelSamplingSD3",
"flags": {},
"order": 7,
"inputs": [{ "link": 94, "name": "model", "type": "MODEL" }],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [95], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ModelSamplingSD3" },
"widgets_values": [8]
},
{
"id": 37,
"pos": [20, 40],
"mode": 0,
"size": [346.7470703125, 82],
"type": "UNETLoader",
"flags": {},
"order": 1,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [94], "slot_index": 0 }
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["wan2.1_t2v_1.3B_fp16.safetensors", "default"]
},
{
"id": 6,
"pos": [415, 186],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 8,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 74, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 96,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [46],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a fox moving quickly in a beautiful winter scenery nature trees mountains daytime tracking camera"
]
},
{
"id": 7,
"pos": [413, 389],
"mode": 0,
"size": [425.27801513671875, 180.6060791015625],
"type": "CLIPTextEncode",
"color": "#322",
"flags": {},
"order": 9,
"title": "CLIP Text Encode (Negative Prompt)",
"inputs": [
{ "link": 75, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 97,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [52],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
]
},
{
"id": 38,
"pos": [-10.047812461853027, 187.37384033203125],
"mode": 0,
"size": [390, 98],
"type": "CLIPLoader",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [
{ "name": "CLIP", "type": "CLIP", "links": [74, 75], "slot_index": 0 }
],
"properties": { "Node name for S&R": "CLIPLoader" },
"widgets_values": [
"umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"wan",
"default"
]
},
{
"id": 49,
"pos": [-535.2967529296875, 342.3277587890625],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [96] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"positive_prompt",
"a fox moving quickly in a beautiful winter scenery nature trees mountains daytime tracking camera",
"Prompt",
"The text prompt to guide video generation. "
]
},
{
"id": 50,
"pos": [-526.2716064453125, 703.8343505859375],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [97] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"negative_prompt",
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
"Negative Prompt",
"The negative prompt to use. Use it to address details that you don't want in the image. This could be colors, objects, scenery and even the small details (e.g. moustache, blurry, low resolution). "
]
},
{
"id": 40,
"pos": [516.926513671875, 619.59716796875],
"mode": 0,
"size": [315, 150],
"type": "EmptyHunyuanLatentVideo",
"flags": {},
"order": 10,
"inputs": [
{
"pos": [10, 36],
"link": 100,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"pos": [10, 60],
"link": 99,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [91], "slot_index": 0 }
],
"properties": { "Node name for S&R": "EmptyHunyuanLatentVideo" },
"widgets_values": [832, 480, 33, 1]
},
{
"id": 28,
"pos": [1460, 190],
"mode": 0,
"size": [870.8511352539062, 643.7430419921875],
"type": "SaveAnimatedWEBP",
"flags": {},
"order": 13,
"inputs": [{ "link": 56, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", 16, false, 90, "default"]
},
{
"id": 51,
"pos": [-522.7415161132812, 959.3386840820312],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [100], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 832, "Width", "The Width of the Video. "]
},
{
"id": 52,
"pos": [-518.9917602539062, 1207.9444580078125],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [99], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 480, "Height", "The Height of the Video. "]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Inputs",
"bounding": [
-560.9110717773438, 255.1485595703125, 500.94989013671875,
333.4786682128906
],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-556.5305786132812, 619.87548828125, 761.2673950195312,
811.6837768554688
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 100,
"last_node_id": 52
}
+2 -2
View File
@@ -1,9 +1,9 @@
[project]
name = "comfyui-deploy"
description = "Open source comfyui deployment platform, a vercel for generative workflow infra."
version = "1.1.0"
version = "2.1.0"
license = { file = "LICENSE" }
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg"]
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg", "tabulate", "brotli"]
[project.urls]
Repository = "https://github.com/BennyKok/comfyui-deploy"
+192 -87
View File
@@ -168,12 +168,12 @@ function setSelectedWorkflowInfo(info) {
const VALID_TYPES = [
"STRING",
"combo",
"number",
"toggle",
"BOOLEAN",
"text",
"string",
"combo",
];
function hideWidget(node, widget, suffix = "") {
@@ -181,7 +181,9 @@ function hideWidget(node, widget, suffix = "") {
widget.origType = widget.type;
widget.origComputeSize = widget.computeSize;
widget.origSerializeValue = widget.serializeValue;
widget.computeSize = () => [0, -4];
// console.log(widget.origComputeSize);
// console.log(LiteGraph.NODE_SLOT_HEIGHT);
// widget.computeSize = () => [0, 0];
widget.type = CONVERTED_TYPE + suffix;
widget.serializeValue = () => {
if (!node.inputs) {
@@ -210,10 +212,37 @@ function getWidgetType(config) {
return { type };
}
const GET_CONFIG = Symbol();
async function convertToInput(node, widget, config) {
const { type } = getWidgetType(config);
console.log(node, widget, config);
const result = await app.extensionManager.dialog.prompt(
{
title: "Convert " + widget.name + " to external input",
message: "Input name",
defaultValue: widget.name,
}
);
if (!result) return;
// Check for duplicate input IDs across existing external input nodes
const existingInputIds = Object.values(app.graph.nodes)
.filter(n => n.type.startsWith("ComfyUIDeployExternal"))
.map(n => n.widgets_values?.[0])
.filter(Boolean);
if (existingInputIds.includes(result)) {
app.extensionManager.toast.add({
severity: 'error',
summary: 'Input ID already exists',
detail: 'Please choose a different name.',
life: 3000
});
return;
}
function convertToInput(node, widget, config) {
console.log(node);
if (node.type == "LoadImage") {
var inputNode = LiteGraph.createNode("ComfyUIDeployExternalImage");
console.log(widget);
@@ -235,54 +264,94 @@ function convertToInput(node, widget, config) {
const links = app.graph.links;
console.log(currentOutputsLinks);
// 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,
);
}
if (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])]);
let externalNode = "";
let inputId = result;
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);
if (type === "INT") {
externalNode = "ComfyUIDeployExternalNumberInt";
// inputId = "input_number";
}
if (type === "FLOAT") {
externalNode = "ComfyUIDeployExternalNumberSlider";
// inputId = "input_number";
}
if (type === "STRING") {
externalNode = "ComfyUIDeployExternalText";
// inputId = "input_text";
}
if (type === "COMBO") {
externalNode = "ComfyUIDeployExternalEnum";
// inputId = "input_enum";
}
if (!externalNode || !inputId) return;
node.convertWidgetToInput(widget);
var inputNode = LiteGraph.createNode(externalNode, "External Input: " + inputId);
// if (type === "COMBO") {
// inputNode = LiteGraph.createNode(externalNode, "External Input: " + inputId, {
// dynamic_enum_options: config[0],
// });
// console.log(inputNode);
// const options = config[0];
// console.log(options);
// } else {
// inputNode = LiteGraph.createNode(externalNode, "External Input: " + inputId);
// }
var options;
const index = node.inputs.findIndex((x) => x.name == widget.name);
if (type === "COMBO") {
options = widget.options?.values ?? config[0];
inputNode.configure({
widgets_values: ["input_text", widget.value],
widgets_values: [inputId, widget.value, JSON.stringify(options)],
});
} else
{
inputNode.configure({
widgets_values: [inputId, 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);
}
inputNode.id = ++app.graph.last_node_id;
inputNode.pos = node.pos;
inputNode.pos[0] -= node.size[0] + 160;
return input;
if (type === "COMBO") {
console.log(inputNode);
console.log(options);
inputNode.widgets.find((x) => x.name == "default_value").options.values = options;
}
app.graph.add(inputNode);
inputNode.connect(0, node, index);
app.graph.setDirtyCanvas(true, true);
return node.inputs.find((x) => x.name == widget.name);
}
const CONVERTED_TYPE = "converted-widget";
@@ -295,7 +364,12 @@ function getConfig(widgetName) {
);
}
function isConvertibleWidget(widget, config) {
function isConvertibleWidget(node, widget, config) {
// console.log(config);
if (node.type === "LoadImage" && widget.type === "combo" && widget.name == "image") {
return true;
}
return (
(VALID_TYPES.includes(widget.type) || VALID_TYPES.includes(config[0])) &&
!widget.options?.forceInput
@@ -442,11 +516,11 @@ const ext = {
w.type,
w.options || {},
];
if (isConvertibleWidget(w, config)) {
if (isConvertibleWidget(this, w, config)) {
toInput.push({
content: `Convert ${w.name} to external input`,
callback: /* @__PURE__ */ __name(
() => convertToInput(this, w, config),
async () => convertToInput(this, w, config),
"callback",
),
className: "comfydeploy-menu-item",
@@ -501,6 +575,13 @@ const ext = {
console.log(nodeData.input.optional.default_value_url);
}
if (
nodeData?.input?.optional?.default_value?.[1]?.dynamic_enum === true
) {
nodeData.input.optional.default_value = ["DYNAMIC_ENUM"];
// console.log(nodeData.input.optional.default_value);
}
// const origonNodeCreated = nodeType.prototype.onNodeCreated;
// nodeType.prototype.onNodeCreated = function () {
// const r = origonNodeCreated
@@ -534,6 +615,10 @@ const ext = {
// return r
// };
},
// async nodeCreated(node) {
// },
registerCustomNodes() {
/** @type {LGraphNode}*/
@@ -635,7 +720,7 @@ const ext = {
"string",
inputName,
/* value=*/ "",
() => {},
() => { },
{ serialize: true },
);
@@ -696,9 +781,37 @@ const ext = {
return { widget: urlWidget };
},
DYNAMIC_ENUM(node, inputName, inputData) {
// console.log("DYNAMIC_ENUM", JSON.parse(JSON.stringify(node)), inputName, inputData);
const enumWidget = node.addWidget(
"combo",
inputName,
"",
{ serialize: true, values: [] },
);
return { widget: enumWidget };
},
};
},
async afterConfigureGraph() {
app.graph.nodes.forEach(node => {
if (node.type === "ComfyUIDeployExternalEnum") {
const default_value_index = node.widgets.findIndex(x => x.name === "default_value");
const options_index = node.widgets.findIndex(x => x.name === "options");
var dynamic_enum_options = [node.widgets[default_value_index].value];
if (node.widgets[options_index].value) {
dynamic_enum_options = JSON.parse(node.widgets[options_index].value);
}
// console.log("dynamic_enum_options", dynamic_enum_options);
node.widgets[default_value_index].options.values = dynamic_enum_options;
}
});
},
async setup() {
// const graphCanvas = document.getElementById("graph-canvas");
@@ -716,7 +829,7 @@ const ext = {
try {
await window["app"].ui.settings.setSettingValueAsync(
"Comfy.Validation.Workflows",
false,
true,
);
} catch (error) {
console.warning(
@@ -886,6 +999,7 @@ const ext = {
}
})(app.graph.onAfterChange);
sendEventToCD("cd_plugin_setup");
},
};
@@ -920,10 +1034,10 @@ function createDynamicUIHtml(data) {
<h3 style="font-size: 14px; font-weight: semibold; margin-bottom: 8px;">Missing Nodes</h3>
<p style="font-size: 12px;">These nodes are not found with any matching custom_nodes in the ComfyUI Manager Database</p>
${data.missing_nodes
.map((node) => {
return `<p style="font-size: 14px; color: #d69e2e;">${node}</p>`;
})
.join("")}
.map((node) => {
return `<p style="font-size: 14px; color: #d69e2e;">${node}</p>`;
})
.join("")}
</div>
`;
}
@@ -931,17 +1045,14 @@ function createDynamicUIHtml(data) {
Object.values(data.custom_nodes).forEach((node) => {
html += `
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 16px;">
<a href="${
node.url
}" target="_blank" style="font-size: 18px; font-weight: semibold; color: white; text-decoration: none;">${
node.name
}</a>
<a href="${node.url
}" target="_blank" style="font-size: 18px; font-weight: semibold; color: white; text-decoration: none;">${node.name
}</a>
<p style="font-size: 14px; color: #4b5563;">${node.hash}</p>
${
node.warning
? `<p style="font-size: 14px; color: #d69e2e;">${node.warning}</p>`
: ""
}
${node.warning
? `<p style="font-size: 14px; color: #d69e2e;">${node.warning}</p>`
: ""
}
</div>
`;
});
@@ -955,9 +1066,8 @@ function createDynamicUIHtml(data) {
Object.entries(data.models).forEach(([section, items]) => {
html += `
<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>`;
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${section.charAt(0).toUpperCase() + section.slice(1)
}</h3>`;
items.forEach((item) => {
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
});
@@ -973,9 +1083,8 @@ function createDynamicUIHtml(data) {
Object.entries(data.files).forEach(([section, items]) => {
html += `
<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>`;
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${section.charAt(0).toUpperCase() + section.slice(1)
}</h3>`;
items.forEach((item) => {
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
});
@@ -1076,6 +1185,8 @@ async function deployWorkflow() {
const prompt = await app.graphToPrompt();
let deps = undefined;
console.log(prompt);
if (includeDeps) {
loadingDialog.showLoading("Fetching existing version");
@@ -1493,14 +1604,12 @@ export class LoadingDialog extends ComfyDialog {
showLoading(title, message) {
this.show(`
<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>
${
message
? `<label style="max-width: 100%; white-space: pre-wrap; word-wrap: break-word;">${message}</label>`
: ""
}
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${this.loadingIcon
}</h3>
${message
? `<label style="max-width: 100%; white-space: pre-wrap; word-wrap: break-word;">${message}</label>`
: ""
}
</div>
`);
}
@@ -1766,21 +1875,17 @@ export class ConfigDialog extends ComfyDialog {
</label>
<label style="color: white; width: 100%;">
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>
<div style="color: white;">
API Key: User / Org <button style="font-size: 18px;">${
data.displayName ?? ""
}</button>
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${
data.apiKey
}">
API Key: User / Org <button style="font-size: 18px;">${data.displayName ?? ""
}</button>
<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;">
${
data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
}
${data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
}
</button>
</div>
</div>
@@ -1960,7 +2065,7 @@ async function loadWorkflowApi(versionId) {
console.log("Workflow API loaded:", response);
await window["app"].ui.settings.setSettingValueAsync(
"Comfy.Validation.Workflows",
false,
true,
);
app.loadGraphData(response.workflow);
// You might want to update the UI or trigger some action in ComfyUI here