Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef50de41e5 | ||
|
|
ac843527d9 | ||
|
|
f39d216326 | ||
|
|
40ec37e58f | ||
|
|
1d63b21643 | ||
|
|
0e3baf22df | ||
|
|
1837065ed2 | ||
|
|
9a8f4795d1 | ||
|
|
c0c617c5d2 | ||
|
|
1e33435ae5 | ||
|
|
04161071f2 | ||
|
|
32d574475c | ||
|
|
1a017ee6a3 | ||
|
|
603223741a | ||
|
|
2bd8b23c60 | ||
|
|
a82e315d6c | ||
|
|
7fdfba6b6e |
@@ -64,32 +64,42 @@ class ComfyUIDeployExternalLora:
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
if lora_url and lora_url.startswith("http"):
|
if lora_url:
|
||||||
if lora_save_name:
|
if lora_url.startswith("http"):
|
||||||
existing_loras = folder_paths.get_filename_list("loras")
|
if lora_save_name:
|
||||||
# Check if lora_save_name exists in the list
|
existing_loras = folder_paths.get_filename_list("loras")
|
||||||
if lora_save_name in existing_loras:
|
# Check if lora_save_name exists in the list
|
||||||
print(f"using lora: {lora_save_name}")
|
if lora_save_name in existing_loras:
|
||||||
return (lora_save_name,)
|
print(f"using lora: {lora_save_name}")
|
||||||
|
return (lora_save_name,)
|
||||||
|
else:
|
||||||
|
lora_save_name = str(uuid.uuid4()) + ".safetensors"
|
||||||
|
print(lora_save_name)
|
||||||
|
print(folder_paths.folder_names_and_paths["loras"][0][0])
|
||||||
|
destination_path = os.path.join(
|
||||||
|
folder_paths.folder_names_and_paths["loras"][0][0], lora_save_name
|
||||||
|
)
|
||||||
|
print(destination_path)
|
||||||
|
print(
|
||||||
|
"Downloading external lora - "
|
||||||
|
+ lora_url
|
||||||
|
+ " to "
|
||||||
|
+ destination_path
|
||||||
|
)
|
||||||
|
response = requests.get(
|
||||||
|
lora_url,
|
||||||
|
headers={"User-Agent": "Mozilla/5.0"},
|
||||||
|
allow_redirects=True,
|
||||||
|
)
|
||||||
|
with open(destination_path, "wb") as out_file:
|
||||||
|
out_file.write(response.content)
|
||||||
|
print(f"Ext Lora loading: {lora_url} to {lora_save_name}")
|
||||||
|
return (lora_save_name,)
|
||||||
else:
|
else:
|
||||||
lora_save_name = str(uuid.uuid4()) + ".safetensors"
|
print(f"Ext Lora loading: {lora_url}")
|
||||||
print(lora_save_name)
|
return (lora_url,)
|
||||||
print(folder_paths.folder_names_and_paths["loras"][0][0])
|
|
||||||
destination_path = os.path.join(
|
|
||||||
folder_paths.folder_names_and_paths["loras"][0][0], lora_save_name
|
|
||||||
)
|
|
||||||
print(destination_path)
|
|
||||||
print("Downloading external lora - " + lora_url + " to " + destination_path)
|
|
||||||
response = requests.get(
|
|
||||||
lora_url,
|
|
||||||
headers={"User-Agent": "Mozilla/5.0"},
|
|
||||||
allow_redirects=True,
|
|
||||||
)
|
|
||||||
with open(destination_path, "wb") as out_file:
|
|
||||||
out_file.write(response.content)
|
|
||||||
return (lora_save_name,)
|
|
||||||
else:
|
else:
|
||||||
print(f"using lora: {default_lora_name}")
|
print(f"Ext Lora loading: {default_lora_name}")
|
||||||
return (default_lora_name,)
|
return (default_lora_name,)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class StringFunction:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"action": (["append", "replace"], {}),
|
||||||
|
"tidy_tags": (["yes", "no"], {}),
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"text_a": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||||
|
"text_b": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||||
|
"text_c": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("STRING",)
|
||||||
|
FUNCTION = "exec"
|
||||||
|
CATEGORY = "utils"
|
||||||
|
OUTPUT_NODE = True
|
||||||
|
|
||||||
|
def exec(self, action, tidy_tags, text_a="", text_b="", text_c=""):
|
||||||
|
tidy_tags = tidy_tags == "yes"
|
||||||
|
out = ""
|
||||||
|
if action == "append":
|
||||||
|
out = (", " if tidy_tags else "").join(
|
||||||
|
filter(None, [text_a, text_b, text_c])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if text_c is None:
|
||||||
|
text_c = ""
|
||||||
|
if text_b.startswith("/") and text_b.endswith("/"):
|
||||||
|
regex = text_b[1:-1]
|
||||||
|
out = re.sub(regex, text_c, text_a)
|
||||||
|
else:
|
||||||
|
out = text_a.replace(text_b, text_c)
|
||||||
|
if tidy_tags:
|
||||||
|
out = re.sub(r"\s{2,}", " ", out)
|
||||||
|
out = out.replace(" ,", ",")
|
||||||
|
out = re.sub(r",{2,}", ",", out)
|
||||||
|
out = out.strip()
|
||||||
|
return {"ui": {"text": (out,)}, "result": (out,)}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"ComfyUIDeployStringCombine": StringFunction,
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
|
"ComfyUIDeployStringCombine": "String Combine (ComfyUI Deploy)",
|
||||||
|
}
|
||||||
+213
-23
@@ -26,7 +26,10 @@ import copy
|
|||||||
import struct
|
import struct
|
||||||
from aiohttp import web, ClientSession, ClientError, ClientTimeout, ClientResponseError
|
from aiohttp import web, ClientSession, ClientError, ClientTimeout, ClientResponseError
|
||||||
import atexit
|
import atexit
|
||||||
|
from model_management import get_torch_device
|
||||||
|
import torch
|
||||||
|
import psutil
|
||||||
|
from collections import OrderedDict
|
||||||
# Global session
|
# Global session
|
||||||
client_session = None
|
client_session = None
|
||||||
|
|
||||||
@@ -1120,9 +1123,138 @@ async def proxy_to_comfydeploy(request):
|
|||||||
|
|
||||||
|
|
||||||
prompt_server = server.PromptServer.instance
|
prompt_server = server.PromptServer.instance
|
||||||
send_json = prompt_server.send_json
|
|
||||||
|
|
||||||
|
|
||||||
|
NODE_EXECUTION_TIMES = {} # New dictionary to store node execution times
|
||||||
|
CURRENT_START_EXECUTION_DATA = None
|
||||||
|
|
||||||
|
def get_peak_memory():
|
||||||
|
device = get_torch_device()
|
||||||
|
if device.type == 'cuda':
|
||||||
|
return torch.cuda.max_memory_allocated(device)
|
||||||
|
elif device.type == 'mps':
|
||||||
|
# Return system memory usage for MPS devices
|
||||||
|
return psutil.Process().memory_info().rss
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def reset_peak_memory_record():
|
||||||
|
device = get_torch_device()
|
||||||
|
if device.type == 'cuda':
|
||||||
|
torch.cuda.reset_max_memory_allocated(device)
|
||||||
|
# MPS doesn't need reset as we're not tracking its memory
|
||||||
|
|
||||||
|
|
||||||
|
def handle_execute(class_type, last_node_id, prompt_id, server, unique_id):
|
||||||
|
if not CURRENT_START_EXECUTION_DATA:
|
||||||
|
return
|
||||||
|
start_time = CURRENT_START_EXECUTION_DATA["nodes_start_perf_time"].get(unique_id)
|
||||||
|
start_vram = CURRENT_START_EXECUTION_DATA["nodes_start_vram"].get(unique_id)
|
||||||
|
if start_time:
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
execution_time = end_time - start_time
|
||||||
|
|
||||||
|
end_vram = get_peak_memory()
|
||||||
|
vram_used = end_vram - start_vram
|
||||||
|
global NODE_EXECUTION_TIMES
|
||||||
|
# print(f"end_vram - start_vram: {end_vram} - {start_vram} = {vram_used}")
|
||||||
|
NODE_EXECUTION_TIMES[unique_id] = {
|
||||||
|
"time": execution_time,
|
||||||
|
"class_type": class_type,
|
||||||
|
"vram_used": vram_used
|
||||||
|
}
|
||||||
|
# print(f"#{unique_id} [{class_type}]: {execution_time:.2f}s - vram {vram_used}b")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
origin_execute = execution.execute
|
||||||
|
|
||||||
|
def swizzle_execute(
|
||||||
|
server,
|
||||||
|
dynprompt,
|
||||||
|
caches,
|
||||||
|
current_item,
|
||||||
|
extra_data,
|
||||||
|
executed,
|
||||||
|
prompt_id,
|
||||||
|
execution_list,
|
||||||
|
pending_subgraph_results,
|
||||||
|
):
|
||||||
|
unique_id = current_item
|
||||||
|
class_type = dynprompt.get_node(unique_id)["class_type"]
|
||||||
|
last_node_id = server.last_node_id
|
||||||
|
result = origin_execute(
|
||||||
|
server,
|
||||||
|
dynprompt,
|
||||||
|
caches,
|
||||||
|
current_item,
|
||||||
|
extra_data,
|
||||||
|
executed,
|
||||||
|
prompt_id,
|
||||||
|
execution_list,
|
||||||
|
pending_subgraph_results,
|
||||||
|
)
|
||||||
|
handle_execute(class_type, last_node_id, prompt_id, server, unique_id)
|
||||||
|
return result
|
||||||
|
|
||||||
|
execution.execute = swizzle_execute
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def format_table(headers, data):
|
||||||
|
# Calculate column widths
|
||||||
|
widths = [len(h) for h in headers]
|
||||||
|
for row in data:
|
||||||
|
for i, cell in enumerate(row):
|
||||||
|
widths[i] = max(widths[i], len(str(cell)))
|
||||||
|
|
||||||
|
# Create separator line
|
||||||
|
separator = '+' + '+'.join('-' * (w + 2) for w in widths) + '+'
|
||||||
|
|
||||||
|
# Format header
|
||||||
|
result = [separator]
|
||||||
|
header_row = '|' + '|'.join(f' {h:<{w}} ' for w, h in zip(widths, headers)) + '|'
|
||||||
|
result.append(header_row)
|
||||||
|
result.append(separator)
|
||||||
|
|
||||||
|
# Format data rows
|
||||||
|
for row in data:
|
||||||
|
data_row = '|' + '|'.join(f' {str(cell):<{w}} ' for w, cell in zip(widths, row)) + '|'
|
||||||
|
result.append(data_row)
|
||||||
|
|
||||||
|
result.append(separator)
|
||||||
|
return '\n'.join(result)
|
||||||
|
|
||||||
|
origin_func = server.PromptServer.send_sync
|
||||||
|
def swizzle_send_sync(self, event, data, sid=None):
|
||||||
|
# print(f"swizzle_send_sync, event: {event}, data: {data}")
|
||||||
|
global CURRENT_START_EXECUTION_DATA
|
||||||
|
if event == "execution_start":
|
||||||
|
global NODE_EXECUTION_TIMES
|
||||||
|
NODE_EXECUTION_TIMES = {} # Reset execution times at start
|
||||||
|
CURRENT_START_EXECUTION_DATA = dict(
|
||||||
|
start_perf_time=time.perf_counter(),
|
||||||
|
nodes_start_perf_time={},
|
||||||
|
nodes_start_vram={},
|
||||||
|
)
|
||||||
|
|
||||||
|
origin_func(self, event=event, data=data, sid=sid)
|
||||||
|
|
||||||
|
if event == "executing" and data and CURRENT_START_EXECUTION_DATA:
|
||||||
|
if data.get("node") is not None:
|
||||||
|
node_id = data.get("node")
|
||||||
|
CURRENT_START_EXECUTION_DATA["nodes_start_perf_time"][node_id] = (
|
||||||
|
time.perf_counter()
|
||||||
|
)
|
||||||
|
reset_peak_memory_record()
|
||||||
|
CURRENT_START_EXECUTION_DATA["nodes_start_vram"][node_id] = (
|
||||||
|
get_peak_memory()
|
||||||
|
)
|
||||||
|
|
||||||
|
server.PromptServer.send_sync = swizzle_send_sync
|
||||||
|
|
||||||
|
send_json = prompt_server.send_json
|
||||||
|
|
||||||
async def send_json_override(self, event, data, sid=None):
|
async def send_json_override(self, event, data, sid=None):
|
||||||
# logger.info("INTERNAL:", event, data, sid)
|
# logger.info("INTERNAL:", event, data, sid)
|
||||||
prompt_id = data.get("prompt_id")
|
prompt_id = data.get("prompt_id")
|
||||||
@@ -1145,10 +1277,60 @@ async def send_json_override(self, event, data, sid=None):
|
|||||||
asyncio.create_task(update_run_ws_event(prompt_id, event, data))
|
asyncio.create_task(update_run_ws_event(prompt_id, event, data))
|
||||||
|
|
||||||
if event == "execution_start":
|
if event == "execution_start":
|
||||||
await update_run(prompt_id, Status.RUNNING)
|
|
||||||
|
|
||||||
if prompt_id in prompt_metadata:
|
if prompt_id in prompt_metadata:
|
||||||
prompt_metadata[prompt_id].start_time = time.perf_counter()
|
prompt_metadata[prompt_id].start_time = time.perf_counter()
|
||||||
|
|
||||||
|
asyncio.create_task(update_run(prompt_id, Status.RUNNING))
|
||||||
|
|
||||||
|
|
||||||
|
if event == "executing" and data and CURRENT_START_EXECUTION_DATA:
|
||||||
|
if data.get("node") is None:
|
||||||
|
start_perf_time = CURRENT_START_EXECUTION_DATA.get("start_perf_time")
|
||||||
|
new_data = data.copy()
|
||||||
|
if start_perf_time is not None:
|
||||||
|
execution_time = time.perf_counter() - start_perf_time
|
||||||
|
new_data["execution_time"] = int(execution_time * 1000)
|
||||||
|
|
||||||
|
# Replace the print statements with tabulate
|
||||||
|
headers = ["Node ID", "Type", "Time (s)", "VRAM (GB)"]
|
||||||
|
table_data = []
|
||||||
|
node_execution_array = [] # New array to store execution data
|
||||||
|
|
||||||
|
for node_id, node_data in NODE_EXECUTION_TIMES.items():
|
||||||
|
vram_gb = node_data['vram_used'] / (1024**3) # Convert bytes to GB
|
||||||
|
table_data.append([
|
||||||
|
f"#{node_id}",
|
||||||
|
node_data['class_type'],
|
||||||
|
f"{node_data['time']:.2f}",
|
||||||
|
f"{vram_gb:.2f}"
|
||||||
|
])
|
||||||
|
|
||||||
|
# Add to our new array format
|
||||||
|
node_execution_array.append({
|
||||||
|
"id": node_id,
|
||||||
|
**node_data,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add total execution time as the last row
|
||||||
|
table_data.append([
|
||||||
|
"TOTAL",
|
||||||
|
"-",
|
||||||
|
f"{execution_time:.2f}",
|
||||||
|
"-"
|
||||||
|
])
|
||||||
|
|
||||||
|
prompt_id = data.get("prompt_id")
|
||||||
|
asyncio.create_task(update_run_with_output(
|
||||||
|
prompt_id,
|
||||||
|
node_execution_array, # Send the array instead of the OrderedDict
|
||||||
|
))
|
||||||
|
|
||||||
|
print(node_execution_array)
|
||||||
|
|
||||||
|
# print("\n=== Node Execution Times ===")
|
||||||
|
logger.info("Printing Node Execution Times")
|
||||||
|
logger.info(format_table(headers, table_data))
|
||||||
|
# print("========================\n")
|
||||||
|
|
||||||
# the last executing event is none, then the workflow is finished
|
# the last executing event is none, then the workflow is finished
|
||||||
if event == "executing" and data.get("node") is None:
|
if event == "executing" and data.get("node") is None:
|
||||||
@@ -1160,11 +1342,11 @@ async def send_json_override(self, event, data, sid=None):
|
|||||||
if prompt_metadata[prompt_id].start_time is not None:
|
if prompt_metadata[prompt_id].start_time is not None:
|
||||||
elapsed_time = current_time - prompt_metadata[prompt_id].start_time
|
elapsed_time = current_time - prompt_metadata[prompt_id].start_time
|
||||||
logger.info(f"Elapsed time: {elapsed_time} seconds")
|
logger.info(f"Elapsed time: {elapsed_time} seconds")
|
||||||
await send(
|
asyncio.create_task(send(
|
||||||
"elapsed_time",
|
"elapsed_time",
|
||||||
{"prompt_id": prompt_id, "elapsed_time": elapsed_time},
|
{"prompt_id": prompt_id, "elapsed_time": elapsed_time},
|
||||||
sid=sid,
|
sid=sid,
|
||||||
)
|
))
|
||||||
|
|
||||||
if event == "executing" and data.get("node") is not None:
|
if event == "executing" and data.get("node") is not None:
|
||||||
node = data.get("node")
|
node = data.get("node")
|
||||||
@@ -1188,7 +1370,7 @@ async def send_json_override(self, event, data, sid=None):
|
|||||||
prompt_metadata[prompt_id].last_updated_node = node
|
prompt_metadata[prompt_id].last_updated_node = node
|
||||||
class_type = prompt_metadata[prompt_id].workflow_api[node]["class_type"]
|
class_type = prompt_metadata[prompt_id].workflow_api[node]["class_type"]
|
||||||
logger.info(f"At: {round(calculated_progress * 100)}% - {class_type}")
|
logger.info(f"At: {round(calculated_progress * 100)}% - {class_type}")
|
||||||
await send(
|
asyncio.create_task(send(
|
||||||
"live_status",
|
"live_status",
|
||||||
{
|
{
|
||||||
"prompt_id": prompt_id,
|
"prompt_id": prompt_id,
|
||||||
@@ -1196,10 +1378,10 @@ async def send_json_override(self, event, data, sid=None):
|
|||||||
"progress": calculated_progress,
|
"progress": calculated_progress,
|
||||||
},
|
},
|
||||||
sid=sid,
|
sid=sid,
|
||||||
)
|
))
|
||||||
await update_run_live_status(
|
asyncio.create_task(update_run_live_status(
|
||||||
prompt_id, "Executing " + class_type, calculated_progress
|
prompt_id, "Executing " + class_type, calculated_progress
|
||||||
)
|
))
|
||||||
|
|
||||||
if event == "execution_cached" and data.get("nodes") is not None:
|
if event == "execution_cached" and data.get("nodes") is not None:
|
||||||
if prompt_id in prompt_metadata:
|
if prompt_id in prompt_metadata:
|
||||||
@@ -1227,7 +1409,8 @@ async def send_json_override(self, event, data, sid=None):
|
|||||||
"node_class": class_type,
|
"node_class": class_type,
|
||||||
}
|
}
|
||||||
if class_type == "PreviewImage":
|
if class_type == "PreviewImage":
|
||||||
logger.info("Skipping preview image")
|
pass
|
||||||
|
# logger.info("Skipping preview image")
|
||||||
else:
|
else:
|
||||||
await update_run_with_output(
|
await update_run_with_output(
|
||||||
prompt_id,
|
prompt_id,
|
||||||
@@ -1239,9 +1422,10 @@ async def send_json_override(self, event, data, sid=None):
|
|||||||
comfy_message_queues[prompt_id].put_nowait(
|
comfy_message_queues[prompt_id].put_nowait(
|
||||||
{"event": "output_ready", "data": data}
|
{"event": "output_ready", "data": data}
|
||||||
)
|
)
|
||||||
logger.info(f"Executed {class_type} {data}")
|
# logger.info(f"Executed {class_type} {data}")
|
||||||
else:
|
else:
|
||||||
logger.info(f"Executed {data}")
|
pass
|
||||||
|
# logger.info(f"Executed {data}")
|
||||||
|
|
||||||
|
|
||||||
# Global variable to keep track of the last read line number
|
# Global variable to keep track of the last read line number
|
||||||
@@ -1713,17 +1897,23 @@ async def upload_in_background(
|
|||||||
# await handle_upload(prompt_id, data, 'files', "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, 'gifs', "format", "image/gif")
|
||||||
# await handle_upload(prompt_id, data, 'mesh', "format", "application/octet-stream")
|
# await handle_upload(prompt_id, data, 'mesh', "format", "application/octet-stream")
|
||||||
upload_tasks = [
|
|
||||||
handle_upload(prompt_id, data, "images", "content_type", "image/png"),
|
file_upload_endpoint = prompt_metadata[prompt_id].file_upload_endpoint
|
||||||
handle_upload(prompt_id, data, "files", "content_type", "image/png"),
|
|
||||||
handle_upload(prompt_id, data, "gifs", "format", "image/gif"),
|
if file_upload_endpoint is not None and file_upload_endpoint != "":
|
||||||
handle_upload(
|
upload_tasks = [
|
||||||
prompt_id, data, "mesh", "format", "application/octet-stream"
|
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(
|
||||||
await asyncio.gather(*upload_tasks)
|
prompt_id, data, "mesh", "format", "application/octet-stream"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
await asyncio.gather(*upload_tasks)
|
||||||
|
else:
|
||||||
|
print("No file upload endpoint, skipping file upload")
|
||||||
|
|
||||||
status_endpoint = prompt_metadata[prompt_id].status_endpoint
|
status_endpoint = prompt_metadata[prompt_id].status_endpoint
|
||||||
token = prompt_metadata[prompt_id].token
|
token = prompt_metadata[prompt_id].token
|
||||||
gpu_event_id = prompt_metadata[prompt_id].gpu_event_id or None
|
gpu_event_id = prompt_metadata[prompt_id].gpu_event_id or None
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ pydantic
|
|||||||
opencv-python
|
opencv-python
|
||||||
imageio-ffmpeg
|
imageio-ffmpeg
|
||||||
brotli
|
brotli
|
||||||
|
tabulate
|
||||||
# logfire
|
# logfire
|
||||||
+287
-2
@@ -4,6 +4,39 @@ import { ComfyWidgets, LGraphNode } from "./widgets.js";
|
|||||||
import { generateDependencyGraph } from "https://esm.sh/[email protected]";
|
import { generateDependencyGraph } from "https://esm.sh/[email protected]";
|
||||||
import { ComfyDeploy } from "https://esm.sh/[email protected]";
|
import { ComfyDeploy } from "https://esm.sh/[email protected]";
|
||||||
|
|
||||||
|
const styles = `
|
||||||
|
.comfydeploy-menu-item {
|
||||||
|
background: linear-gradient(to right, rgba(74, 144, 226, 0.9), rgba(103, 178, 111, 0.9)) !important;
|
||||||
|
color: white !important;
|
||||||
|
position: relative !important;
|
||||||
|
padding-left: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comfydeploy-menu-item:hover {
|
||||||
|
filter: brightness(1.1) !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comfydeploy-menu-item::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background-image: url('https://www.comfydeploy.com/icon.svg');
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add stylesheet to document
|
||||||
|
const styleSheet = document.createElement("style");
|
||||||
|
styleSheet.textContent = styles;
|
||||||
|
document.head.appendChild(styleSheet);
|
||||||
|
|
||||||
const loadingIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg>`;
|
const loadingIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg>`;
|
||||||
|
|
||||||
function sendEventToCD(event, data) {
|
function sendEventToCD(event, data) {
|
||||||
@@ -122,6 +155,146 @@ function setSelectedWorkflowInfo(info) {
|
|||||||
context.selectedWorkflowInfo = info;
|
context.selectedWorkflowInfo = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VALID_TYPES = [
|
||||||
|
"STRING",
|
||||||
|
"combo",
|
||||||
|
"number",
|
||||||
|
"toggle",
|
||||||
|
"BOOLEAN",
|
||||||
|
"text",
|
||||||
|
"string",
|
||||||
|
];
|
||||||
|
|
||||||
|
function hideWidget(node, widget, suffix = "") {
|
||||||
|
if (widget.type?.startsWith(CONVERTED_TYPE)) return;
|
||||||
|
widget.origType = widget.type;
|
||||||
|
widget.origComputeSize = widget.computeSize;
|
||||||
|
widget.origSerializeValue = widget.serializeValue;
|
||||||
|
widget.computeSize = () => [0, -4];
|
||||||
|
widget.type = CONVERTED_TYPE + suffix;
|
||||||
|
widget.serializeValue = () => {
|
||||||
|
if (!node.inputs) {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
let node_input = node.inputs.find((i) => i.widget?.name === widget.name);
|
||||||
|
if (!node_input || !node_input.link) {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
return widget.origSerializeValue
|
||||||
|
? widget.origSerializeValue()
|
||||||
|
: widget.value;
|
||||||
|
};
|
||||||
|
if (widget.linkedWidgets) {
|
||||||
|
for (const w of widget.linkedWidgets) {
|
||||||
|
hideWidget(node, w, ":" + widget.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWidgetType(config) {
|
||||||
|
let type = config[0];
|
||||||
|
if (type instanceof Array) {
|
||||||
|
type = "COMBO";
|
||||||
|
}
|
||||||
|
return { type };
|
||||||
|
}
|
||||||
|
|
||||||
|
const GET_CONFIG = Symbol();
|
||||||
|
|
||||||
|
function convertToInput(node, widget, config) {
|
||||||
|
console.log(node);
|
||||||
|
if (node.type == "LoadImage") {
|
||||||
|
var inputNode = LiteGraph.createNode("ComfyUIDeployExternalImage");
|
||||||
|
console.log(widget);
|
||||||
|
|
||||||
|
const currentOutputsLinks = node.outputs[0].links;
|
||||||
|
|
||||||
|
// const index = node.inputs.findIndex((x) => x.name == widget.name);
|
||||||
|
// console.log(node.widgets_values, index);
|
||||||
|
// inputNode.configure({
|
||||||
|
// widgets_values: ["input_text", widget.value],
|
||||||
|
// });
|
||||||
|
inputNode.pos = node.pos;
|
||||||
|
inputNode.id = ++app.graph.last_node_id;
|
||||||
|
// inputNode.pos[0] += node.size[0] + 40;
|
||||||
|
node.pos[0] -= inputNode.size[0] + 20;
|
||||||
|
console.log(inputNode);
|
||||||
|
console.log(app.graph);
|
||||||
|
app.graph.add(inputNode);
|
||||||
|
|
||||||
|
const links = app.graph.links;
|
||||||
|
|
||||||
|
console.log(currentOutputsLinks);
|
||||||
|
|
||||||
|
for (let i = 0; i < currentOutputsLinks.length; i++) {
|
||||||
|
const link = currentOutputsLinks[i];
|
||||||
|
const llink = links[link];
|
||||||
|
console.log(links[link]);
|
||||||
|
setTimeout(
|
||||||
|
() => inputNode.connect(0, llink.target_id, llink.target_slot),
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.connect(0, inputNode, 0);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideWidget(node, widget);
|
||||||
|
const { type } = getWidgetType(config);
|
||||||
|
const sz = node.size;
|
||||||
|
const inputIsOptional = !!widget.options?.inputIsOptional;
|
||||||
|
const input = node.addInput(widget.name, type, {
|
||||||
|
widget: { name: widget.name, [GET_CONFIG]: () => config },
|
||||||
|
...(inputIsOptional ? { shape: LiteGraph.SlotShape.HollowCircle } : {}),
|
||||||
|
});
|
||||||
|
for (const widget2 of node.widgets) {
|
||||||
|
widget2.last_y += LiteGraph.NODE_SLOT_HEIGHT;
|
||||||
|
}
|
||||||
|
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]);
|
||||||
|
|
||||||
|
if (type == "STRING") {
|
||||||
|
var inputNode = LiteGraph.createNode("ComfyUIDeployExternalText");
|
||||||
|
console.log(widget);
|
||||||
|
const index = node.inputs.findIndex((x) => x.name == widget.name);
|
||||||
|
console.log(node.widgets_values, index);
|
||||||
|
inputNode.configure({
|
||||||
|
widgets_values: ["input_text", widget.value],
|
||||||
|
});
|
||||||
|
inputNode.id = ++app.graph.last_node_id;
|
||||||
|
inputNode.pos = node.pos;
|
||||||
|
inputNode.pos[0] -= node.size[0] + 40;
|
||||||
|
console.log(inputNode);
|
||||||
|
console.log(app.graph);
|
||||||
|
app.graph.add(inputNode);
|
||||||
|
inputNode.connect(0, node, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONVERTED_TYPE = "converted-widget";
|
||||||
|
|
||||||
|
function getConfig(widgetName) {
|
||||||
|
const { nodeData } = this.constructor;
|
||||||
|
return (
|
||||||
|
nodeData?.input?.required?.[widgetName] ??
|
||||||
|
nodeData?.input?.optional?.[widgetName]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConvertibleWidget(widget, config) {
|
||||||
|
return (
|
||||||
|
(VALID_TYPES.includes(widget.type) || VALID_TYPES.includes(config[0])) &&
|
||||||
|
!widget.options?.forceInput
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __name = (target, value) =>
|
||||||
|
__defProp(target, "name", { value, configurable: true });
|
||||||
|
|
||||||
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
|
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
|
||||||
/** @type {ComfyExtension} */
|
/** @type {ComfyExtension} */
|
||||||
const ext = {
|
const ext = {
|
||||||
@@ -232,6 +405,118 @@ const ext = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async beforeRegisterNodeDef(nodeType, nodeData, app2) {
|
||||||
|
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||||
|
nodeType.prototype.getExtraMenuOptions = function (_, options) {
|
||||||
|
const r = origGetExtraMenuOptions
|
||||||
|
? origGetExtraMenuOptions.apply(this, arguments)
|
||||||
|
: void 0;
|
||||||
|
if (this.widgets) {
|
||||||
|
let toInput = [];
|
||||||
|
let toWidget = [];
|
||||||
|
for (const w of this.widgets) {
|
||||||
|
if (w.options?.forceInput) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (w.type === CONVERTED_TYPE) {
|
||||||
|
toWidget.push({
|
||||||
|
content: `Convert ${w.name} to widget`,
|
||||||
|
callback: /* @__PURE__ */ __name(
|
||||||
|
() => convertToWidget(this, w),
|
||||||
|
"callback",
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const config = getConfig.call(this, w.name) ?? [
|
||||||
|
w.type,
|
||||||
|
w.options || {},
|
||||||
|
];
|
||||||
|
if (isConvertibleWidget(w, config)) {
|
||||||
|
toInput.push({
|
||||||
|
content: `Convert ${w.name} to external input`,
|
||||||
|
callback: /* @__PURE__ */ __name(
|
||||||
|
() => convertToInput(this, w, config),
|
||||||
|
"callback",
|
||||||
|
),
|
||||||
|
className: "comfydeploy-menu-item",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toInput.length) {
|
||||||
|
if (true) {
|
||||||
|
options.push();
|
||||||
|
|
||||||
|
let optionIndex = options.findIndex((o) => o.content === "Outputs");
|
||||||
|
if (optionIndex === -1) optionIndex = options.length;
|
||||||
|
else optionIndex++;
|
||||||
|
options.splice(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
// {
|
||||||
|
// content: "[ComfyDeploy] Convert to External Input",
|
||||||
|
// submenu: {
|
||||||
|
// options: toInput,
|
||||||
|
// },
|
||||||
|
// className: "comfydeploy-menu-item"
|
||||||
|
// },
|
||||||
|
...toInput,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
options.push(...toInput, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (toWidget.length) {
|
||||||
|
// if (useConversionSubmenusSetting.value) {
|
||||||
|
// options.push({
|
||||||
|
// content: "Convert Input to Widget",
|
||||||
|
// submenu: {
|
||||||
|
// options: toWidget,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// options.push(...toWidget, null);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
|
// const origonNodeCreated = nodeType.prototype.onNodeCreated;
|
||||||
|
// nodeType.prototype.onNodeCreated = function () {
|
||||||
|
// const r = origonNodeCreated
|
||||||
|
// ? origonNodeCreated.apply(this, arguments)
|
||||||
|
// : void 0;
|
||||||
|
|
||||||
|
// if (!this.widgets) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// console.log(this.widgets);
|
||||||
|
|
||||||
|
// this.widgets.forEach(element => {
|
||||||
|
// if (element.type != "customtext") return
|
||||||
|
|
||||||
|
// console.log(element.element);
|
||||||
|
|
||||||
|
// const parent = element.element.parentElement
|
||||||
|
|
||||||
|
// console.log(element.element.parentElement)
|
||||||
|
// const btn = document.createElement("button");
|
||||||
|
// // const div = document.createElement("div");
|
||||||
|
// // parent.removeChild(element.element)
|
||||||
|
// // div.appendChild(element.element)
|
||||||
|
// // parent.appendChild(div)
|
||||||
|
// // element.element = div
|
||||||
|
// // console.log(element.element);
|
||||||
|
// // btn.style = element.element.style
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return r
|
||||||
|
// };
|
||||||
|
},
|
||||||
|
|
||||||
registerCustomNodes() {
|
registerCustomNodes() {
|
||||||
/** @type {LGraphNode}*/
|
/** @type {LGraphNode}*/
|
||||||
class ComfyDeploy extends LGraphNode {
|
class ComfyDeploy extends LGraphNode {
|
||||||
@@ -463,11 +748,11 @@ const ext = {
|
|||||||
await app.ui.settings.setSettingValueAsync("Comfy.UseNewMenu", "Top");
|
await app.ui.settings.setSettingValueAsync("Comfy.UseNewMenu", "Top");
|
||||||
await app.ui.settings.setSettingValueAsync(
|
await app.ui.settings.setSettingValueAsync(
|
||||||
"Comfy.Sidebar.Size",
|
"Comfy.Sidebar.Size",
|
||||||
"small"
|
"small",
|
||||||
);
|
);
|
||||||
await app.ui.settings.setSettingValueAsync(
|
await app.ui.settings.setSettingValueAsync(
|
||||||
"Comfy.Sidebar.Location",
|
"Comfy.Sidebar.Location",
|
||||||
"right"
|
"right",
|
||||||
);
|
);
|
||||||
localStorage.setItem("Comfy.MenuPosition.Docked", "true");
|
localStorage.setItem("Comfy.MenuPosition.Docked", "true");
|
||||||
console.log("native mode manmanman");
|
console.log("native mode manmanman");
|
||||||
|
|||||||
Reference in New Issue
Block a user