Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea45930102 | ||
|
|
b889f79baf | ||
|
|
1d8fed3534 | ||
|
|
a557788e70 | ||
|
|
05cccaffa2 | ||
|
|
85af9dd68f | ||
|
|
233615ea25 | ||
|
|
8b6aabbfaa | ||
|
|
c72539078c | ||
|
|
95fc642782 | ||
|
|
d88ca5b748 | ||
|
|
e86a484160 | ||
|
|
5d85bfd38f | ||
|
|
77eb9f9805 | ||
|
|
266e9d1024 | ||
|
|
dc7234640c | ||
|
|
4e8417d501 | ||
|
|
62609f9c6a | ||
|
|
438401b8c7 | ||
|
|
e18d980b77 |
@@ -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)"
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
@@ -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)"}
|
||||
+92
-15
@@ -139,7 +139,9 @@ async def async_request_with_retry(
|
||||
logger.error(f"Error response body: {error_body}")
|
||||
|
||||
if attempt == max_retries - 1:
|
||||
logger.error(f"Request {method} : {url} failed after {max_retries} attempts: {e}")
|
||||
logger.error(
|
||||
f"Request {method} : {url} failed after {max_retries} attempts: {e}"
|
||||
)
|
||||
raise
|
||||
|
||||
await asyncio.sleep(retry_delay)
|
||||
@@ -156,6 +158,8 @@ from logging import basicConfig, getLogger
|
||||
# Check for an environment variable to enable/disable Logfire
|
||||
use_logfire = os.environ.get("USE_LOGFIRE", "false").lower() == "true"
|
||||
|
||||
API_KEY_COMFY_ORG = os.environ.get("API_KEY_COMFY_ORG", None)
|
||||
|
||||
if use_logfire:
|
||||
try:
|
||||
import logfire
|
||||
@@ -281,6 +285,9 @@ def post_prompt(json_data):
|
||||
if "extra_data" in json_data:
|
||||
extra_data = json_data["extra_data"]
|
||||
|
||||
if API_KEY_COMFY_ORG is not None:
|
||||
extra_data["api_key_comfy_org"] = API_KEY_COMFY_ORG
|
||||
|
||||
if "client_id" in json_data:
|
||||
extra_data["client_id"] = json_data["client_id"]
|
||||
if valid[0]:
|
||||
@@ -314,7 +321,7 @@ def randomSeed(num_digits=15):
|
||||
return random.randint(range_start, range_end)
|
||||
|
||||
|
||||
def apply_random_seed_to_workflow(workflow_api):
|
||||
def apply_random_seed_to_workflow(workflow_api, workflow):
|
||||
"""
|
||||
Applies a random seed to each element in the workflow_api that has a 'seed' input.
|
||||
|
||||
@@ -327,6 +334,41 @@ 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
|
||||
|
||||
# Check node type in workflow to determine if we should randomize
|
||||
node_id = key
|
||||
should_skip = (
|
||||
False # Add a flag to track if we should skip randomization
|
||||
)
|
||||
|
||||
for node in workflow["nodes"]:
|
||||
if str(node["id"]) == node_id and node["type"] == "KSampler":
|
||||
# Check if this node has widgets_values and if seed setting is not "fixed"
|
||||
if "widgets_values" in node and len(node["widgets_values"]) > 1:
|
||||
seed_mode = node["widgets_values"][1]
|
||||
if seed_mode == "fixed":
|
||||
# Skip randomization for fixed seeds
|
||||
logger.info(
|
||||
f"Skipping random seed for KSampler (node {node_id}) as it's set to fixed"
|
||||
)
|
||||
should_skip = True # Set the flag to skip randomization
|
||||
break # Exit the inner loop
|
||||
|
||||
# Apply random seed for non-fixed seeds (randomize, iter, etc.)
|
||||
workflow_api[key]["inputs"]["seed"] = randomSeed()
|
||||
logger.info(
|
||||
f"Applied random seed {workflow_api[key]['inputs']['seed']} to KSampler (node {node_id})"
|
||||
)
|
||||
should_skip = (
|
||||
True # Set the flag to skip default randomization
|
||||
)
|
||||
break # Exit the inner loop
|
||||
break # This break will skip checking other nodes if widgets_values doesn't exist
|
||||
|
||||
# Skip the rest of the code for this key if we already handled it
|
||||
if should_skip:
|
||||
continue
|
||||
|
||||
# Special case for SONICSampler
|
||||
if workflow_api[key]["class_type"] == "SONICSampler":
|
||||
workflow_api[key]["inputs"]["seed"] = randomSeed("sonic")
|
||||
@@ -413,6 +455,9 @@ def apply_inputs_to_workflow(workflow_api: Any, inputs: Any, sid: str = None):
|
||||
if value["class_type"] == "ComfyUIDeployExternalImageBatch":
|
||||
value["inputs"]["images"] = new_value
|
||||
|
||||
if value["class_type"] == "ComfyUIDeployExternalEnum":
|
||||
value["inputs"]["default_value"] = new_value
|
||||
|
||||
if value["class_type"] == "ComfyUIDeployExternalLora":
|
||||
value["inputs"]["lora_url"] = new_value
|
||||
|
||||
@@ -431,6 +476,12 @@ def apply_inputs_to_workflow(workflow_api: Any, inputs: Any, sid: str = None):
|
||||
if value["class_type"] == "ComfyUIDeployExternalEXR":
|
||||
value["inputs"]["exr_file"] = new_value
|
||||
|
||||
if value["class_type"] == "ComfyUIDeployExternalSeed":
|
||||
logger.info(
|
||||
f"Applied random seed {new_value} to {value['class_type']}"
|
||||
)
|
||||
value["inputs"]["default_value"] = new_value
|
||||
|
||||
|
||||
def send_prompt(sid: str, inputs: StreamingPrompt):
|
||||
# workflow_api = inputs.workflow_api
|
||||
@@ -438,7 +489,7 @@ def send_prompt(sid: str, inputs: StreamingPrompt):
|
||||
workflow = copy.deepcopy(inputs.workflow)
|
||||
|
||||
# Random seed
|
||||
apply_random_seed_to_workflow(workflow_api)
|
||||
apply_random_seed_to_workflow(workflow_api, workflow)
|
||||
|
||||
logger.info("getting inputs", inputs.inputs)
|
||||
|
||||
@@ -524,7 +575,7 @@ async def comfy_deploy_run(request):
|
||||
workflow = data.get("workflow")
|
||||
|
||||
# Now it handles directly in here
|
||||
apply_random_seed_to_workflow(workflow_api)
|
||||
apply_random_seed_to_workflow(workflow_api, workflow)
|
||||
apply_inputs_to_workflow(workflow_api, inputs)
|
||||
|
||||
prompt = {
|
||||
@@ -591,7 +642,7 @@ async def stream_prompt(data, token):
|
||||
gpu_event_id = data.get("gpu_event_id", None)
|
||||
|
||||
# Now it handles directly in here
|
||||
apply_random_seed_to_workflow(workflow_api)
|
||||
apply_random_seed_to_workflow(workflow_api, workflow)
|
||||
apply_inputs_to_workflow(workflow_api, inputs)
|
||||
|
||||
prompt = {
|
||||
@@ -1310,7 +1361,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
|
||||
@@ -1395,9 +1446,12 @@ async def send_json_override(self, event, data, sid=None):
|
||||
# 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)
|
||||
# We will now rely on the UploadQueue worker to set the final SUCCESS status
|
||||
# after all uploads are confirmed complete.
|
||||
|
||||
if not have_pending_upload(prompt_id):
|
||||
await update_run(prompt_id, Status.SUCCESS)
|
||||
if prompt_id in prompt_metadata:
|
||||
# await update_run(prompt_id, Status.SUCCESS) # <-- REMOVE/COMMENT OUT
|
||||
if prompt_id in prompt_metadata: # <-- REMOVE/COMMENT OUT THIS BLOCK
|
||||
current_time = time.perf_counter()
|
||||
if prompt_metadata[prompt_id].start_time is not None:
|
||||
elapsed_time = current_time - prompt_metadata[prompt_id].start_time
|
||||
@@ -1839,8 +1893,13 @@ async def upload_file(
|
||||
|
||||
def have_pending_upload(prompt_id):
|
||||
# Check if there are pending uploads in the queue
|
||||
if prompt_id in upload_queue.pending_uploads and upload_queue.pending_uploads[prompt_id]:
|
||||
logger.info(f"Have pending upload {len(upload_queue.pending_uploads[prompt_id])}")
|
||||
if (
|
||||
prompt_id in upload_queue.pending_uploads
|
||||
and upload_queue.pending_uploads[prompt_id]
|
||||
):
|
||||
logger.info(
|
||||
f"Have pending upload {len(upload_queue.pending_uploads[prompt_id])}"
|
||||
)
|
||||
return True
|
||||
|
||||
logger.info("No pending upload")
|
||||
@@ -1946,7 +2005,7 @@ async def handle_upload(
|
||||
|
||||
for item in items:
|
||||
# Skipping temp files
|
||||
if item.get("type") == "temp":
|
||||
if isinstance(item, dict) and item.get("type") == "temp":
|
||||
continue
|
||||
|
||||
file_type = item.get(content_type_key, default_content_type)
|
||||
@@ -1997,22 +2056,29 @@ async def upload_in_background(
|
||||
("files", "content_type", "image/png"),
|
||||
("gifs", "format", "image/gif"),
|
||||
("model_file", "format", "application/octet-stream"),
|
||||
("result", "format", "application/octet-stream"),
|
||||
("text_file", "format", "text/plain"),
|
||||
]:
|
||||
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 file_type == "model_file" or file_type == "result":
|
||||
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": subfolder,
|
||||
"type": "output",
|
||||
}
|
||||
|
||||
# Skip temp files
|
||||
if item.get("type") == "temp":
|
||||
if isinstance(item, dict) and item.get("type") == "temp":
|
||||
continue
|
||||
|
||||
# Add to the upload queue instead of uploading immediately
|
||||
@@ -2077,6 +2143,8 @@ async def update_run_with_output(
|
||||
or "files" in data
|
||||
or "gifs" in data
|
||||
or "model_file" in data
|
||||
or "result" in data
|
||||
or "text_file" in data
|
||||
)
|
||||
if bypass_upload and have_upload_media:
|
||||
print(
|
||||
@@ -2387,6 +2455,11 @@ class UploadQueue:
|
||||
logger.warning(f"No upload endpoint for prompt ID: {prompt_id}")
|
||||
return
|
||||
|
||||
# Check if file_info is a valid dictionary with a filename
|
||||
if not isinstance(file_info, dict) or "filename" not in file_info:
|
||||
logger.warning(f"Invalid file_info for prompt ID {prompt_id}: {file_info}")
|
||||
return
|
||||
|
||||
filename = file_info.get("filename")
|
||||
subfolder = file_info.get("subfolder")
|
||||
file_type = file_info.get("type", "output")
|
||||
@@ -2537,9 +2610,11 @@ class UploadQueue:
|
||||
# If this was the last file for this prompt, show the stats summary
|
||||
if (
|
||||
prompt_id in self.pending_uploads
|
||||
# We now rely on the worker's finally block for the final SUCCESS update.
|
||||
# Check if the set becomes empty *after* removal in the worker.
|
||||
and len(self.pending_uploads[prompt_id]) == 1
|
||||
):
|
||||
await update_run(prompt_id, Status.SUCCESS)
|
||||
# await update_run(prompt_id, Status.SUCCESS) # <-- REMOVE/COMMENT OUT
|
||||
|
||||
self._log_upload_stats(prompt_id)
|
||||
# Clean up stats
|
||||
@@ -2661,6 +2736,8 @@ class UploadQueue:
|
||||
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:
|
||||
|
||||
+96
-78
@@ -783,15 +783,25 @@ const ext = {
|
||||
},
|
||||
|
||||
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: [] },
|
||||
);
|
||||
// Add a check to see if nativeEnum exists and provide fallback
|
||||
if (node.nativeEnum && typeof node.nativeEnum === "function") {
|
||||
// Original code using nativeEnum
|
||||
return node.nativeEnum(inputName, inputData);
|
||||
} else {
|
||||
// Fallback implementation
|
||||
console.log(
|
||||
"DYNAMIC_ENUM",
|
||||
JSON.parse(JSON.stringify(node)),
|
||||
inputName,
|
||||
inputData
|
||||
);
|
||||
const enumWidget = node.addWidget("combo", inputName, "", {
|
||||
serialize: true,
|
||||
values: [],
|
||||
});
|
||||
|
||||
return { widget: enumWidget };
|
||||
return { widget: enumWidget };
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -1966,80 +1976,88 @@ const client = new ComfyDeploy({
|
||||
serverURL: `${currentOrigin}/comfydeploy/api/`,
|
||||
});
|
||||
|
||||
app.extensionManager.registerSidebarTab({
|
||||
id: "search",
|
||||
icon: "pi pi-cloud-upload",
|
||||
title: "Deploy",
|
||||
tooltip: "Deploy and Configure",
|
||||
type: "custom",
|
||||
render: (el) => {
|
||||
el.innerHTML = `
|
||||
<div style="padding: 20px;">
|
||||
<h3>Comfy Deploy</h3>
|
||||
<div id="deploy-container" style="margin-bottom: 20px;"></div>
|
||||
<div id="workflows-container" style="display: none;">
|
||||
<h4>Your Workflows</h4>
|
||||
<div id="workflows-loading" style="display: flex; justify-content: center; align-items: center; height: 100px;">
|
||||
${loadingIcon}
|
||||
// Check if the current URL hostname starts with localhost or 127.0.0.1
|
||||
const isLocalhost =
|
||||
window.location.hostname === "localhost" ||
|
||||
window.location.hostname === "127.0.0.1";
|
||||
|
||||
// Only register the sidebar tab if we're on localhost
|
||||
if (isLocalhost) {
|
||||
app.extensionManager.registerSidebarTab({
|
||||
id: "search",
|
||||
icon: "pi pi-cloud-upload",
|
||||
title: "Deploy",
|
||||
tooltip: "Deploy and Configure",
|
||||
type: "custom",
|
||||
render: (el) => {
|
||||
el.innerHTML = `
|
||||
<div style="padding: 20px;">
|
||||
<h3>Comfy Deploy</h3>
|
||||
<div id="deploy-container" style="margin-bottom: 20px;"></div>
|
||||
<div id="workflows-container" style="display: none;">
|
||||
<h4>Your Workflows</h4>
|
||||
<div id="workflows-loading" style="display: flex; justify-content: center; align-items: center; height: 100px;">
|
||||
${loadingIcon}
|
||||
</div>
|
||||
<ul id="workflows-list" style="list-style-type: none; padding: 0; display: none;"></ul>
|
||||
</div>
|
||||
<ul id="workflows-list" style="list-style-type: none; padding: 0; display: none;"></ul>
|
||||
<div id="config-container"></div>
|
||||
</div>
|
||||
<div id="config-container"></div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
|
||||
// Add deploy button
|
||||
const deployContainer = el.querySelector("#deploy-container");
|
||||
const deployButton = document.createElement("button");
|
||||
deployButton.id = "sidebar-deploy-button";
|
||||
deployButton.style.display = "flex";
|
||||
deployButton.style.alignItems = "center";
|
||||
deployButton.style.justifyContent = "center";
|
||||
deployButton.style.width = "100%";
|
||||
deployButton.style.marginBottom = "10px";
|
||||
deployButton.style.padding = "10px";
|
||||
deployButton.style.fontSize = "16px";
|
||||
deployButton.style.fontWeight = "bold";
|
||||
deployButton.style.backgroundColor = "#4CAF50";
|
||||
deployButton.style.color = "white";
|
||||
deployButton.style.border = "none";
|
||||
deployButton.style.borderRadius = "5px";
|
||||
deployButton.style.cursor = "pointer";
|
||||
deployButton.innerHTML = `<i class="pi pi-cloud-upload" style="margin-right: 8px;"></i><div id='sidebar-button-title'>Deploy</div>`;
|
||||
deployButton.onclick = async () => {
|
||||
await deployWorkflow();
|
||||
// Refresh the workflows list after deployment
|
||||
refreshWorkflowsList(el);
|
||||
};
|
||||
deployContainer.appendChild(deployButton);
|
||||
|
||||
// Add config button
|
||||
const configContainer = el.querySelector("#config-container");
|
||||
const configButton = document.createElement("button");
|
||||
configButton.style.display = "flex";
|
||||
configButton.style.alignItems = "center";
|
||||
configButton.style.justifyContent = "center";
|
||||
configButton.style.width = "100%";
|
||||
configButton.style.padding = "8px";
|
||||
configButton.style.fontSize = "14px";
|
||||
configButton.style.backgroundColor = "#f0f0f0";
|
||||
configButton.style.color = "#333";
|
||||
configButton.style.border = "1px solid #ccc";
|
||||
configButton.style.borderRadius = "5px";
|
||||
configButton.style.cursor = "pointer";
|
||||
configButton.innerHTML = `<i class="pi pi-cog" style="margin-right: 8px;"></i>Configure`;
|
||||
configButton.onclick = () => {
|
||||
configDialog.show();
|
||||
};
|
||||
deployContainer.appendChild(configButton);
|
||||
|
||||
// Fetch and display workflows
|
||||
const workflowsList = el.querySelector("#workflows-list");
|
||||
const workflowsLoading = el.querySelector("#workflows-loading");
|
||||
|
||||
// Add deploy button
|
||||
const deployContainer = el.querySelector("#deploy-container");
|
||||
const deployButton = document.createElement("button");
|
||||
deployButton.id = "sidebar-deploy-button";
|
||||
deployButton.style.display = "flex";
|
||||
deployButton.style.alignItems = "center";
|
||||
deployButton.style.justifyContent = "center";
|
||||
deployButton.style.width = "100%";
|
||||
deployButton.style.marginBottom = "10px";
|
||||
deployButton.style.padding = "10px";
|
||||
deployButton.style.fontSize = "16px";
|
||||
deployButton.style.fontWeight = "bold";
|
||||
deployButton.style.backgroundColor = "#4CAF50";
|
||||
deployButton.style.color = "white";
|
||||
deployButton.style.border = "none";
|
||||
deployButton.style.borderRadius = "5px";
|
||||
deployButton.style.cursor = "pointer";
|
||||
deployButton.innerHTML = `<i class="pi pi-cloud-upload" style="margin-right: 8px;"></i><div id='sidebar-button-title'>Deploy</div>`;
|
||||
deployButton.onclick = async () => {
|
||||
await deployWorkflow();
|
||||
// Refresh the workflows list after deployment
|
||||
refreshWorkflowsList(el);
|
||||
};
|
||||
deployContainer.appendChild(deployButton);
|
||||
|
||||
// Add config button
|
||||
const configContainer = el.querySelector("#config-container");
|
||||
const configButton = document.createElement("button");
|
||||
configButton.style.display = "flex";
|
||||
configButton.style.alignItems = "center";
|
||||
configButton.style.justifyContent = "center";
|
||||
configButton.style.width = "100%";
|
||||
configButton.style.padding = "8px";
|
||||
configButton.style.fontSize = "14px";
|
||||
configButton.style.backgroundColor = "#f0f0f0";
|
||||
configButton.style.color = "#333";
|
||||
configButton.style.border = "1px solid #ccc";
|
||||
configButton.style.borderRadius = "5px";
|
||||
configButton.style.cursor = "pointer";
|
||||
configButton.innerHTML = `<i class="pi pi-cog" style="margin-right: 8px;"></i>Configure`;
|
||||
configButton.onclick = () => {
|
||||
configDialog.show();
|
||||
};
|
||||
deployContainer.appendChild(configButton);
|
||||
|
||||
// Fetch and display workflows
|
||||
const workflowsList = el.querySelector("#workflows-list");
|
||||
const workflowsLoading = el.querySelector("#workflows-loading");
|
||||
|
||||
refreshWorkflowsList(el);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getTimeAgo(date) {
|
||||
const seconds = Math.floor((new Date() - date) / 1000);
|
||||
|
||||
Reference in New Issue
Block a user