Compare commits

..
Author SHA1 Message Date
KarrixLee 4ee7d03937 refactor: remove unnecessary code 2025-09-10 14:53:07 +08:00
KarrixLee fdf1f08d8d fix: versioning display 2025-09-10 14:37:46 +08:00
KarrixLee 94a5f52b3f add: workflow version control 2025-09-09 23:28:37 +08:00
KarrixLee f91680d435 tweak again 2025-09-09 21:20:14 +08:00
KarrixLee 5e88e7e406 fix native mode 2025-09-09 21:13:26 +08:00
KarrixLee 7421b2ce9e add: real logic to upload model 2025-09-09 17:33:24 +08:00
KarrixLee eaf55eb974 test 2025-09-09 16:05:11 +08:00
KarrixLee 4ef8e7e12b add: model display 2025-09-09 13:36:38 +08:00
KarrixLee 142458994e tweak 2025-09-09 12:45:47 +08:00
KarrixLee 5ac4aa98a3 init 2025-09-09 12:41:28 +08:00
bennykok 31c38ddb9b version bump 2025-08-29 15:48:42 -07:00
bennykok 2f2c63fa11 proxy interrupt request 2025-08-29 15:47:54 -07:00
BennyKok 7675c5ba90 Update README with demo link and recruitment note
Added a link to the latest local demo and a call for team members.
2025-08-29 13:03:39 -07:00
bennykok 7d65d23f84 fix: add null check in findIndex to prevent TypeError in context menu
- Fixed TypeError when accessing 'content' property of null/undefined array elements
- Removed empty options.push() that was adding undefined to the array
- Bump version to 2.3.3
2025-08-26 18:34:20 -07:00
KarrixLee a91edf6371 Fix serverless machine (#106)
* refresh the machine list automatically

* update ui

* sync non local workflow

* Bump CD ver to 2.3.2 in Manager
2025-08-26 17:38:34 +08:00
KarrixLee 967709dd07 Bump CD ver to 2.3.1 in Manager 2025-08-21 19:07:55 +08:00
KarrixLee 07a827d56b Local: Sync serverless machine (#105)
* initt: add machine management functionality

- Implemented a new endpoint in custom_routes.py to retrieve machine details by ID.
- Introduced machine-manager.js to handle machine data fetching, display, and local storage management.
- Updated index.js to initialize the machine manager and display machine information in the UI.
- Adjusted workflow-list.js to modify the layout for better integration with the new machine management features.

These changes enhance the application's capability to manage and display serverless machine information effectively.

* refactor: enhance machine management UI and functionality

- Removed console error logs for improved user experience during initialization.
- Updated the display styles for "No machine found" and error messages to enhance visual clarity.
- Added buttons for adding and updating machines directly from the error and no machine states.
- Implemented a dialog for adding and updating machine IDs, improving user interaction.
- Adjusted the machine addition process to show loading states and ensure proper DOM updates.

These changes significantly improve the usability and functionality of the machine management feature.

* add: machine id when create

* remove unused code

* fetch docker steps in backend

* compare list

* can sync machine

* add: create machine from local

* add error

* add: blacklist node

* refactor: improve snapshot fetching and UI adjustments

- Introduced centralized snapshot fetching utilities in snapshot-utils.js, including a fallback for ComfyUI version retrieval.
- Updated machine-manager.js to utilize the new snapshot fetching methods, enhancing error handling and code clarity.
- Adjusted index.js to streamline snapshot fetching during workflow deployment.
- Made minor UI adjustments in workflow-list.js for better layout consistency.

These changes enhance the application's robustness in handling snapshots and improve overall user experience.

* small tweak

* tweak machine display

* chore: version to 2.3.0
2025-08-21 18:39:39 +08:00
KarrixLee 7f6eea361e tweak: add tutorial complete to true by default (#104) 2025-08-18 00:19:57 +08:00
KarrixLee 14829a2b12 Comfyui 0.3.48 compatibility (#102)
* refactor: support 3 attribute for the validation prompt

* add: comments for clarification
2025-08-04 17:23:12 +08:00
KarrixLee 03c4e2d85d Add audio file upload support 2025-07-26 18:37:09 +08:00
KarrixLee bad8b1104f Fix comfyui 0.3.45 (#100)
* Refactor post_prompt function to include prompt_id handling

- Updated the post_prompt function in custom_routes.py to retrieve and validate a prompt_id from the incoming JSON data, defaulting to a new UUID if not provided.
- Adjusted the validation call to use prompt_id alongside the prompt, ensuring proper identification and processing of prompts.

These changes enhance the functionality of prompt handling within the application.

* Refactor post_prompt and send_prompt functions for async handling

- Updated the post_prompt function to be asynchronous, allowing for non-blocking execution when validating prompts.
- Adjusted calls to post_prompt in send_prompt and comfy_deploy_run to await the asynchronous execution, ensuring proper handling of prompt submissions.
- Commented out unused prompt construction code to streamline the function.

These changes enhance the performance and responsiveness of prompt handling in the application.

* Refactor handle_execute to support asynchronous execution

- Updated the swizzle_execute function to be asynchronous, allowing for non-blocking execution of the origin_execute function.
- Added missing parameters for pending_async_nodes in the swizzle_execute function call.
- Ensured that the result from origin_execute is awaited, improving the handling of asynchronous operations.

These changes enhance the performance and responsiveness of the execution handling in the application.

* Enhance error handling in post_prompt and origin_execute functions

- Added try-except blocks in post_prompt to handle TypeErrors during prompt validation, allowing for fallback to an older signature.
- Implemented similar error handling in the origin_execute function to manage potential TypeErrors, ensuring robust execution flow.
- Improved logging to capture issues with function signatures, aiding in debugging.

These changes improve the resilience of the application when dealing with prompt and execution validation.

* Refactor post_prompt and origin_execute for synchronous handling

- Modified the post_prompt function to call validate_prompt synchronously when a TypeError occurs, improving error handling.
- Updated the origin_execute function to execute synchronously, ensuring consistent behavior during execution.

These changes enhance the robustness of prompt validation and execution processes in the application.

* tweak: optional

* Refactor swizzle_execute for async and sync handling

- Enhanced the swizzle_execute function to differentiate between asynchronous and synchronous execution paths based on the origin_execute function's nature.
- Improved the structure of the swizzle_execute function to ensure proper handling of parameters and execution flow for both async and sync scenarios.
- Maintained existing error handling while ensuring consistent behavior across execution types.

These changes improve the flexibility and robustness of the execution handling in the application.
2025-07-23 16:12:35 -07:00
KarrixLee ac8779dc54 fix 2025-07-19 10:24:04 -07:00
KarrixLee 2a5223f2f0 fix 2025-06-29 15:43:12 +08:00
KarrixLee f02aef4cb3 tweak versoin 2025-06-29 13:37:24 +08:00
KarrixLee 22458a1cd6 Merge branch 'local-flow-2' 2025-06-27 14:10:10 +08:00
Tristan-mc-qandtristan22mc 0a58eba554 feat: Add deployable EXR saver node (#98)
Co-authored-by: tristan22mc <tristan22mc@gmail.com>
2025-06-26 14:07:48 -07:00
KarrixLee def54df9c2 tweak 2025-06-17 18:37:02 +08:00
KarrixLee 32a950afe8 tweak 2025-06-17 18:07:43 +08:00
KarrixLee 8130779d94 Enhance configuration saving and workflow list management
- Updated the save method in ConfigDialog to be asynchronous, allowing for smoother handling of configuration saves.
- Added a new function to refresh the workflow list if the sidebar is open, ensuring the UI reflects the latest data after configuration changes.
- Made workflowsState globally accessible for improved state management across components.
- Adjusted the height of the workflows list for better UI layout.

These changes improve the user experience by ensuring that the workflow list is up-to-date and enhancing the overall responsiveness of the configuration dialog.
2025-06-16 21:25:33 +08:00
KarrixLee 4cbd2a8225 Add workflow retrieval functionality and enhance UI interaction
- Introduced a new endpoint in custom_routes.py for fetching workflows by ID, including authorization checks and error handling.
- Updated workflow-list.js to support fetching and displaying workflow data upon user interaction, including loading indicators and error handling.
- Enhanced the createWorkflowItem function to accept additional parameters for improved data handling and user feedback.

These changes improve the user experience by enabling seamless workflow retrieval and interaction within the application.
2025-06-16 21:01:43 +08:00
KarrixLee d6fb2daeff Add workflow list management and search functionality
- Introduced a new workflow-list.js file to manage workflows, including fetching, displaying, and searching workflows.
- Enhanced the custom_routes.py file with a new endpoint for retrieving workflows, ensuring proper authorization and query parameter handling.
- Updated index.js to initialize the workflows list and integrate search functionality within the UI.

These changes improve the user experience by allowing efficient management and retrieval of workflows in the application.
2025-06-16 18:47:18 +08:00
KarrixLee a91effe3c8 Add workflow management endpoints and enhance deployment logic
- Introduced new endpoints for creating workflows and versions in the custom_routes.py file.
- Updated the deployWorkflow function in index.js to include apiUrl in the request body and handle workflow versioning.
- Improved error handling for API requests and ensured required fields are validated before processing.
- Enhanced user feedback during deployment with updated success messages.

These changes streamline workflow management and improve the overall deployment process within the application.
2025-06-16 16:32:23 +08:00
KarrixLee c015b710fe Enhance authentication flow and improve API integration
- Added a new endpoint for handling authentication responses in the UploadQueue class.
- Updated the deployWorkflow function to include apiUrl in the configuration checks.
- Refactored API calls to use the new auth-response endpoint, ensuring proper request handling.
- Improved logging for better debugging during workflow deployment.

These changes streamline the authentication process and enhance the overall API interaction within the application.
2025-06-15 13:55:59 +08:00
KarrixLee 2738d1913a tweak 2025-06-14 22:04:53 +08:00
KarrixLee 3f4c11e3f1 Refactor event dispatching and improve code readability in index.js
- Standardized formatting for CustomEvent dispatches to enhance consistency.
- Simplified async function prompts and improved filtering logic for existing input IDs.
- Enhanced readability by restructuring multiline statements and ensuring consistent indentation.
- Added error handling for deployment processes and improved dialog display methods.

These changes aim to improve maintainability and clarity of the codebase.
2025-06-14 21:49:53 +08:00
KarrixLee 089bad5560 Revert "Implement enhanced media preview functionality in ComfyUI"
This reverts commit 46010e1dd5.
2025-06-09 16:09:38 +08:00
KarrixLee 46010e1dd5 Implement enhanced media preview functionality in ComfyUI
- Added support for video previews alongside existing image previews.
- Introduced helper functions to detect video URLs and display media accordingly.
- Updated URL widget handling to show the appropriate media type based on the input URL.
- Improved error handling for media loading failures.

This change enhances the user experience by providing a seamless way to preview both images and videos.
2025-06-08 19:31:47 +08:00
KarrixLee 52d876fa67 Enhance ComfyUIDeployExternalVideo to support default video URL input
- Added 'default_value_url' parameter to allow fetching videos from a specified URL if the input_id is not a URL.
- Updated the logic to handle video fetching, ensuring it uses the correct URL based on the input.
- Improved the return structure for optional parameters, including the new 'default_value_url' with image preview support.

This change enhances flexibility in video input handling for the ComfyUI Deploy.
2025-06-08 19:24:13 +08:00
BennyKokandDevin AI f7e7eb19d0 Add ComfyUIDeployExternalNumberSliderInt node (#95)
- Implements integer slider node for ComfyUI Deploy
- Returns INT type instead of FLOAT for proper integer input compatibility
- Uses int(round(float(input_id))) for robust conversion
- Default range 0-10 with step=1 for integer appropriateness
- Includes proper range validation and error handling
- Frontend and API support already exists

Fixes COM-1041

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-06-06 12:38:18 +08:00
ImpactFrames cb03b6718e racing condition delete check if [prompt_id] exist (#94) 2025-06-05 16:42:37 +08:00
BennyKok 7b734c415a fix: import 2025-05-27 15:14:29 +08:00
BennyKok 64d3ec6b45 Revert "fix: import issues"
This reverts commit c47865ec26.
2025-05-27 15:12:33 +08:00
bennykok c47865ec26 fix: import issues 2025-05-21 19:10:20 +08:00
BennyKok b889f79baf Merge branch 'benny/support-comfy-api-key' 2025-05-12 17:06:08 +08:00
KarrixLee 1d8fed3534 feat: only register sidebar tab for Comfy Deploy on localhost 2025-05-12 12:38:23 +08:00
BennyKok a557788e70 fix 2025-05-11 10:57:07 +08:00
BennyKok 05cccaffa2 support for API_KEY_COMFY_ORG 2025-05-11 10:41:10 +08:00
KarrixLee 85af9dd68f Test (#91)
* feat: enhance apply_random_seed_to_workflow function to support KSampler node type and handle fixed seed settings

* refactor: add flag to skip randomization in apply_random_seed_to_workflow for KSampler nodes

* tweak
2025-05-10 13:13:02 +08:00
KarrixLee 233615ea25 Karrix/external seed (#90)
* feat: add ComfyUIDeployExternalSeed node for generating random seeds with configurable limits

* refactor: update ComfyUIDeployExternalSeed to use control options for seed generation

* feat: add default_value input for ComfyUIDeployExternalSeed node to enhance seed configuration

* test

* refactor: rename lower_limit and upper_limit to min_value and max_value in ComfyUIDeployExternalSeed for clarity

* refactor: update default_value handling and control options in ComfyUIDeployExternalSeed for improved seed generation logic

* refactor: enhance seed generation logic in ComfyUIDeployExternalSeed by refining control handling and default_value checks

* refactor: update description for default_value in ComfyUIDeployExternalSeed to clarify usage and behavior
2025-05-10 03:39:27 +08:00
KarrixLee 8b6aabbfaa feat: add support for 'result' file type in upload process 2025-05-07 21:42:13 +08:00
Nick Kao c72539078c Merge pull request #89 from BennyKok/nick/private-lora-download
feat: add bearer token param in external lora
2025-05-03 10:28:30 -07:00
KarrixLee 95fc642782 Karrix/fix preview image upload queue (#88)
* refactor: update upload completion logic to rely on UploadQueue worker for final SUCCESS status

* refactor: clean up commented-out code in upload completion logic
2025-04-28 15:56:58 +08:00
KarrixLee d88ca5b748 refactor: rename 'text' to 'text_file' for consistency in file handling 2025-04-25 20:03:02 +08:00
KarrixLee e86a484160 refactor: improve logging format and add validation for file_info in upload process 2025-04-25 13:40:38 +08:00
KarrixLee 5d85bfd38f fix: ensure temp file check handles non-dictionary items 2025-04-25 13:29:34 +08:00
BennyKok 77eb9f9805 Merge branch 'benny/fix-preview-image-stuck' 2025-04-24 15:29:16 +08:00
KarrixLee 266e9d1024 refactor: enhance audio loading with error handling and import checks 2025-04-23 13:36:01 +08:00
KarrixLee dc7234640c tweak 2025-04-22 18:30:34 +08:00
KarrixLee 4e8417d501 add: output text node 2025-04-22 18:15:44 +08:00
BennyKok 62609f9c6a Benny/fix preview image stuck (#87)
* Fix preview image loading and add support for 3D model uploads

* skip: sending the upload status, make sure, we are also sending the success status after all files is uploaded
2025-04-22 12:59:20 +08:00
BennyKok 438401b8c7 fix: external enum node value replace 2025-04-22 10:27:55 +08:00
BennyKok 9beac36d0f skip: sending the upload status, make sure, we are also sending the success status after all files is uploaded 2025-04-21 14:18:18 +08:00
BennyKok 313ce956fd Fix preview image loading and add support for 3D model uploads 2025-04-21 14:08:56 +08:00
BennyKok e18d980b77 Benny/fix 3d upload (#85)
* fix 3d upload with subfolder support and enhanced logging

* Use parent folder name as subfolder for model file uploads
2025-04-20 22:54:51 +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
24 changed files with 8872 additions and 840 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 }}
+2
View File
@@ -2,6 +2,8 @@
Open source comfyui deployment platform, a `vercel` for generative workflow infra. (serverless hosted gpu with vertical intergation with comfyui)
Check out our latest lcoal demo -> https://github.com/comfy-deploy/comfyui-api-comfydeploy
> [!NOTE]
> Im looking for creative hacker to join ComfyDeploy's core team! DM me on [twitter](https://x.com/BennyKokMusic)
+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"]
+45 -21
View File
@@ -1,14 +1,13 @@
import os
import io
import torchaudio
from folder_paths import get_annotated_filepath
class ComfyUIDeployExternalAudio:
RETURN_TYPES = ("AUDIO",)
RETURN_NAMES = ("audio",)
FUNCTION = "load_audio"
CATEGORY = "🔗ComfyDeploy"
@classmethod
def INPUT_TYPES(cls):
return {
@@ -29,30 +28,55 @@ class ComfyUIDeployExternalAudio:
"STRING",
{"multiline": False, "default": ""},
),
}
},
}
@classmethod
def VALIDATE_INPUTS(s, audio_file, **kwargs):
return True
def load_audio(self, input_id, audio_file, default_value=None, display_name=None, description=None):
if audio_file and audio_file != "":
if audio_file.startswith(('http://', 'https://')):
# Handle URL input
import requests
response = requests.get(audio_file)
audio_data = io.BytesIO(response.content)
waveform, sample_rate = torchaudio.load(audio_data)
def load_audio(
self,
input_id,
audio_file,
default_value=None,
display_name=None,
description=None,
):
try:
import torchaudio
if audio_file and audio_file != "":
if audio_file.startswith(("http://", "https://")):
# Handle URL input
try:
import requests
response = requests.get(audio_file)
audio_data = io.BytesIO(response.content)
waveform, sample_rate = torchaudio.load(audio_data)
except Exception as e:
print(f"Error loading audio from URL: {e}")
return (default_value,)
else:
# Handle local file
try:
audio_path = get_annotated_filepath(audio_file)
waveform, sample_rate = torchaudio.load(audio_path)
except Exception as e:
print(f"Error loading local audio file: {e}")
return (default_value,)
audio = {"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate}
return (audio,)
else:
# Handle local file
audio_path = get_annotated_filepath(audio_file)
waveform, sample_rate = torchaudio.load(audio_path)
audio = {"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate}
return (audio,)
else:
return (default_value,)
except ImportError as e:
print(f"Error: torchaudio not installed or cannot be imported: {e}")
return (default_value,)
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalAudio": ComfyUIDeployExternalAudio}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalAudio": "External Audio (ComfyUI Deploy)"}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalAudio": "External Audio (ComfyUI Deploy)"
}
+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]
+10 -5
View File
@@ -1,8 +1,4 @@
import folder_paths
from PIL import Image, ImageOps
import numpy as np
import torch
import folder_paths
class AnyType(str):
@@ -41,6 +37,10 @@ class ComfyUIDeployExternalLora:
"STRING",
{"multiline": False, "default": ""},
),
"bearer_token": (
"STRING",
{"multiline": False, "default": ""},
),
},
}
@@ -57,6 +57,7 @@ class ComfyUIDeployExternalLora:
display_name=None,
description=None,
lora_url=None,
bearer_token=None,
):
import requests
import os
@@ -84,9 +85,13 @@ class ComfyUIDeployExternalLora:
+ " to "
+ destination_path
)
headers = {"User-Agent": "Mozilla/5.0"}
if bearer_token:
headers["Authorization"] = f"Bearer {bearer_token}"
print("using bearer token")
response = requests.get(
lora_url,
headers={"User-Agent": "Mozilla/5.0"},
headers=headers,
allow_redirects=True,
)
with open(destination_path, "wb") as out_file:
+54
View File
@@ -0,0 +1,54 @@
class ComfyUIDeployExternalNumberSliderInt:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_number_slider_int"},
),
},
"optional": {
"default_value": (
"INT",
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 1, "step": 1},
),
"min_value": (
"INT",
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 0, "step": 1},
),
"max_value": (
"INT",
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 10, "step": 1},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("value",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, min_value=0, max_value=10, display_name=None, description=None):
try:
int_value = int(round(float(input_id)))
if min_value <= int_value <= max_value:
print("my integer", int_value)
return [int_value]
else:
print("Integer out of range. Returning default value:", default_value)
return [default_value]
except (ValueError, TypeError):
print("Invalid input. Returning default value:", default_value)
return [default_value]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalNumberSliderInt": ComfyUIDeployExternalNumberSliderInt}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalNumberSliderInt": "External Number Slider Int (ComfyUI Deploy)"}
+116
View File
@@ -0,0 +1,116 @@
import random
class ComfyUIDeployExternalSeed:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_seed"},
),
"default_value": (
"INT",
{"default": -1},
),
"min_value": (
"INT",
{"default": 1, "min": 1, "max": 999999999999999},
),
"max_value": (
"INT",
{"default": 4294967295, "min": 1, "max": 999999999999999},
),
},
"optional": {
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{
"multiline": True,
"default": 'For default value:\n"-1" (i.e. not in range): Randomize within the min and max value range. \nin range: Fixed, always the same value\n',
},
),
},
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("seed",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
# Limits
_MAX_LIMIT = 999_999_999_999_999 # 15 digits
# Store cached seed when fixed flag is enabled
_cached_seed = None
@classmethod
def IS_CHANGED(
cls,
input_id,
min_value,
max_value,
default_value=None,
**kwargs,
):
"""Inform ComfyUI whether the node output should be considered changed.
If default_value is within range (Fixed mode), we return the inputs tuple
so the cached result is reused until the user changes something.
For Randomize mode, we force re-execution each queue.
"""
# Clamp values to allowed range for check
min_value = max(1, min_value)
max_value = min(cls._MAX_LIMIT, max_value)
# Fixed mode when default_value is within range
if (
default_value is not None
and default_value >= min_value
and default_value <= max_value
):
return (input_id, default_value)
# For Randomize (default_value is -1 or out of range) we force re-execution
import random as _rnd
return _rnd.random()
def run(
self,
input_id,
min_value: int,
max_value: int,
display_name=None,
description=None,
default_value: int = -1,
):
# Clamp values to allowed range
min_value = max(1, min_value)
max_value = min(self._MAX_LIMIT, max_value)
# Ensure limits are in correct order after clamping
if min_value > max_value:
min_value, max_value = max_value, min_value
# Fixed mode: default_value is within range
if default_value >= min_value and default_value <= max_value:
seed = int(default_value)
self._cached_seed = seed
return [seed]
# Randomize mode: default_value is -1 or out of range
seed = random.randint(min_value, max_value)
self._cached_seed = seed
return [seed]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalSeed": ComfyUIDeployExternalSeed}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalSeed": "External Seed (ComfyUI Deploy)"
}
+68 -35
View File
@@ -748,36 +748,64 @@ class ComfyUIDeployExternalVideo:
file_parts = f.split(".")
if len(file_parts) > 1 and (file_parts[-1] in video_extensions):
files.append(f)
return {"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_video"},
),
"force_rate": ("INT", {"default": 0, "min": 0, "max": 60, "step": 1}),
"force_size": (["Disabled", "Custom Height", "Custom Width", "Custom", "256x?", "?x256", "256x256", "512x?", "?x512", "512x512"],),
"custom_width": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
"custom_height": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
"frame_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
"skip_first_frames": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
},
"optional": {
"meta_batch": ("VHS_BatchManager",),
"vae": ("VAE",),
"default_video": (sorted(files),),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
},
"hidden": {
"unique_id": "UNIQUE_ID"
},
}
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_video"},
),
"force_rate": ("INT", {"default": 0, "min": 0, "max": 60, "step": 1}),
"force_size": (
[
"Disabled",
"Custom Height",
"Custom Width",
"Custom",
"256x?",
"?x256",
"256x256",
"512x?",
"?x512",
"512x512",
],
),
"custom_width": (
"INT",
{"default": 512, "min": 0, "max": DIMMAX, "step": 8},
),
"custom_height": (
"INT",
{"default": 512, "min": 0, "max": DIMMAX, "step": 8},
),
"frame_load_cap": (
"INT",
{"default": 0, "min": 0, "max": BIGMAX, "step": 1},
),
"skip_first_frames": (
"INT",
{"default": 0, "min": 0, "max": BIGMAX, "step": 1},
),
"select_every_nth": (
"INT",
{"default": 1, "min": 1, "max": BIGMAX, "step": 1},
),
},
"optional": {
"meta_batch": ("VHS_BatchManager",),
"vae": ("VAE",),
"default_video": (sorted(files),),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
"default_value_url": ("STRING", {"image_preview": True, "default": ""}),
},
"hidden": {"unique_id": "UNIQUE_ID"},
}
CATEGORY = "Video Helper Suite 🎥🅥🅗🅢"
@@ -804,16 +832,21 @@ class ComfyUIDeployExternalVideo:
select_every_nth = kwargs.get("select_every_nth")
meta_batch = kwargs.get("meta_batch")
unique_id = kwargs.get("unique_id")
default_value_url = kwargs.get("default_value_url")
input_dir = folder_paths.get_input_directory()
if input_id.startswith("http"):
if input_id.startswith("http") or (
default_value_url and default_value_url.startswith("http")
):
import requests
print("Fetching video from URL: ", input_id)
response = requests.get(input_id, stream=True)
# Use input_id if it's a URL, otherwise use default_value_url
url = input_id if input_id.startswith("http") else default_value_url
print("Fetching video from URL: ", url)
response = requests.get(url, stream=True)
file_size = int(response.headers.get("Content-Length", 0))
file_extension = input_id.split(".")[-1].split("?")[
file_extension = url.split(".")[-1].split("?")[
0
] # Extract extension and handle URLs with parameters
if file_extension not in video_extensions:
-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 = "🔗ComfyDeploy"
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)"}
+78
View File
@@ -0,0 +1,78 @@
# In file: comfyui-deploy/comfy-nodes/output_exr.py
import os
import numpy as np
import folder_paths
# Try to set up OpenCV for EXR writing.
try:
os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1"
import cv2
OPENCV_AVAILABLE = True
except ImportError:
print("Warning: OpenCV not found for ComfyDeployOutputEXR. Please add opencv-python-headless to requirements.txt")
OPENCV_AVAILABLE = False
# ALIGNED: Renamed class to match project conventions
class ComfyDeployOutputEXR:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE", ),
"filename_prefix": ("STRING", {"default": "ComfyDeploy_EXR"})
},
# ADDED: Optional output_id for consistency with other ComfyDeploy nodes
"optional": {
"output_id": ("STRING", {"multiline": False, "default": "output_exr"}),
},
}
RETURN_TYPES = ()
# ALIGNED: Changed function name to 'run'
FUNCTION = "run"
OUTPUT_NODE = True
# ALIGNED: Matched the category name
CATEGORY = "🔗ComfyDeploy"
DESCRIPTION = "Saves the input images as EXR (HDR) files."
def run(self, images, filename_prefix="ComfyDeploy_EXR", output_id="output_exr"):
if not OPENCV_AVAILABLE:
raise ImportError("OpenCV is required to save EXR files. Please ensure opencv-python-headless is in requirements.txt.")
full_output_folder, filename, counter, subfolder, filename_prefix = (
folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
)
)
results = list()
for image in images:
image_np = image.cpu().numpy()
if image_np.dtype != np.float32:
image_np = image_np.astype(np.float32)
file = f"{filename}_{counter:05}.exr"
file_path = os.path.join(full_output_folder, file)
image_np_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
cv2.imwrite(file_path, image_np_bgr)
results.append({
"filename": file,
"subfolder": subfolder,
"type": self.type,
"output_id": output_id, # ADDED
})
counter += 1
return {"ui": {"images": results}}
# ALIGNED: Mappings are defined at the bottom of the node file in this project
NODE_CLASS_MAPPINGS = {"ComfyDeployOutputEXR": ComfyDeployOutputEXR}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyDeployOutputEXR": "EXR Output (ComfyDeploy)"}
+99
View File
@@ -0,0 +1,99 @@
import os
import json
import folder_paths
class ComfyDeployOutputText:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": (
"STRING",
{
"multiline": True,
"forceInput": True,
"tooltip": "The text to save.",
},
),
"filename_prefix": (
"STRING",
{
"default": "ComfyUI",
"tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% to include values from nodes.",
},
),
"file_type": (["txt", "json", "md"], {"default": "txt"}),
},
"optional": {
"output_id": (
"STRING",
{"multiline": False, "default": "output_text"},
),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
FUNCTION = "run"
OUTPUT_NODE = True
CATEGORY = "🔗ComfyDeploy"
DESCRIPTION = "Saves the input text to your ComfyUI output directory."
def run(
self,
text,
filename_prefix="ComfyUI",
file_type="txt",
output_id="output_text",
prompt=None,
extra_pnginfo=None,
):
filename_prefix += self.prefix_append
# For text, we don't need dimensions, so pass 0, 0
full_output_folder, filename, counter, subfolder, filename_prefix = (
folder_paths.get_save_image_path(filename_prefix, self.output_dir, 0, 0)
)
results = list()
# Create file path
file = f"{filename}_{counter:05}_.{file_type}"
file_path = os.path.join(full_output_folder, file)
# Save the text based on file type
if file_type == "json":
try:
# Try to save as JSON if the text is valid JSON
json_data = json.loads(text) if isinstance(text, str) else text
with open(file_path, "w", encoding="utf-8") as f:
json.dump(json_data, f, indent=2)
except json.JSONDecodeError:
# Fall back to saving as plain text if not valid JSON
with open(file_path, "w", encoding="utf-8") as f:
f.write(text)
else:
# Save as plain text for txt and md
with open(file_path, "w", encoding="utf-8") as f:
f.write(text)
results.append(
{
"filename": file,
"subfolder": subfolder,
"type": self.type,
"output_id": output_id,
}
)
return {"ui": {"text_file": results}}
NODE_CLASS_MAPPINGS = {"ComfyDeployOutputText": ComfyDeployOutputText}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyDeployOutputText": "Text Output (ComfyDeploy)"}
+959 -113
View File
File diff suppressed because it is too large Load Diff
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
}
+1
View File
@@ -18,6 +18,7 @@ class Status(Enum):
SUCCESS = "success"
FAILED = "failed"
UPLOADING = "uploading"
CANCELLED = "cancelled"
class StreamingPrompt(BaseModel):
+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 = "2.0.0"
version = "2.3.4"
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"
+1513 -601
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+82
View File
@@ -0,0 +1,82 @@
// Snapshot Utilities
// Centralized snapshot fetching with ComfyUI version fallback
/**
* Fetches the current snapshot with ComfyUI version fallback
* If the snapshot response has null comfyui field, it will fetch the latest ComfyUI version
* and update the snapshot with the comfyui_hash
*
* @param {Function} getDataFn - Function that returns { apiKey, apiUrl } for ComfyUI version API calls
* @returns {Promise<Object>} - The snapshot data with comfyui field populated
*/
export async function fetchSnapshot(getDataFn = null) {
try {
// Fetch the current snapshot
const response = await fetch("/snapshot/get_current");
if (!response.ok) {
throw new Error(`Snapshot fetch failed: ${response.status}`);
}
const snapshot = await response.json();
// Check if comfyui field is null and we have getDataFn for fallback
if (snapshot.comfyui === null && getDataFn) {
console.log(
"ComfyUI version is null in snapshot, fetching latest version..."
);
try {
const data = getDataFn();
if (data && data.apiKey) {
const comfyuiVersionResponse = await fetch(
`/comfyui-deploy/comfyui-version?api_url=${encodeURIComponent(
data.apiUrl || "https://api.comfydeploy.com"
)}`,
{
headers: {
Authorization: `Bearer ${data.apiKey}`,
},
}
);
if (comfyuiVersionResponse.ok) {
const versionData = await comfyuiVersionResponse.json();
if (versionData.comfyui_hash) {
console.log(
`Using ComfyUI hash from API: ${versionData.comfyui_hash}`
);
snapshot.comfyui = versionData.comfyui_hash;
}
} else {
console.warn(
"Failed to fetch ComfyUI version from API:",
comfyuiVersionResponse.status
);
}
}
} catch (error) {
console.warn("Error fetching ComfyUI version fallback:", error);
// Continue with original snapshot even if fallback fails
}
}
return snapshot;
} catch (error) {
console.error("Error fetching snapshot:", error);
throw error;
}
}
/**
* Simple snapshot fetch without ComfyUI version fallback
* Use this when you don't need the ComfyUI version fallback logic
*
* @returns {Promise<Object>} - The snapshot data as-is
*/
export async function fetchSnapshotSimple() {
const response = await fetch("/snapshot/get_current");
if (!response.ok) {
throw new Error(`Snapshot fetch failed: ${response.status}`);
}
return response.json();
}
File diff suppressed because it is too large Load Diff