Compare commits

...
Author SHA1 Message Date
EmmanuelMr18 bcd9ab40d3 fix(setup): reload the app after adding the localstorage values
The current problem is that if you do `localsStorage.setItem()` the
value is changed, but comfyUI doesn't detect the change because
`ComfyActionbar` was already mounted.

This is a quick fix where we basically trigger a reload of the app
after docking the menu position.
2024-12-17 02:04:09 -06:00
bennykok ac843527d9 fix: turn perf meta into array 2024-12-09 18:42:15 +08:00
bennykok f39d216326 fix: ordered dict 2024-12-09 18:13:36 +08:00
bennykok 40ec37e58f fix 2024-12-09 16:48:11 +08:00
bennykok 1d63b21643 fix: move update run 2024-12-09 16:31:36 +08:00
bennykok 0e3baf22df fix: also send timing pref 2024-12-09 16:18:04 +08:00
bennykok 1837065ed2 fix: log printing 2024-12-09 09:34:39 +08:00
bennykok 9a8f4795d1 fix log 2024-12-09 00:35:12 +08:00
bennykok c0c617c5d2 Merge branch 'combine-text' into public-main 2024-12-09 00:24:36 +08:00
bennykok 1e33435ae5 feat: add perf counter 2024-12-09 00:11:51 +08:00
karrix 04161071f2 test 2024-12-06 18:54:56 +08:00
bennykok 32d574475c fix: backward comp with old ui 2024-11-13 18:14:36 +09:00
bennykok 1a017ee6a3 make sure link reconnect works 2024-11-13 17:59:54 +09:00
bennykok 603223741a feat: tweak ui styles 2024-11-13 17:24:11 +09:00
bennykok 2bd8b23c60 feat: convert external input 2024-11-13 14:20:24 +08:00
bennykok a82e315d6c fix: when file endpoint is null, skip uploading 2024-10-25 19:56:36 +08:00
nick 7fdfba6b6e external lora 2024-10-24 22:46:31 +08:00
BennyKok 0779136134 Update pyproject.toml 2024-10-22 11:28:59 +08:00
nick fe116a4655 clean logs 2024-10-12 23:59:58 -07:00
nick 7dd8a7e67e gpu eveent 2024-10-12 16:57:37 -07:00
6 changed files with 586 additions and 52 deletions
+34 -24
View File
@@ -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,)
+53
View File
@@ -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)",
}
+208 -24
View File
@@ -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
@@ -296,7 +299,6 @@ def apply_random_seed_to_workflow(workflow_api):
Args: Args:
workflow_api (dict): The workflow API dictionary to modify. workflow_api (dict): The workflow API dictionary to modify.
""" """
print("workflow_api", workflow_api)
for key in workflow_api: for key in workflow_api:
if "inputs" in workflow_api[key]: if "inputs" in workflow_api[key]:
if "seed" in workflow_api[key]["inputs"]: if "seed" in workflow_api[key]["inputs"]:
@@ -468,7 +470,6 @@ async def comfy_deploy_run(request):
if len(parts) == 2 and parts[0].lower() == "bearer": if len(parts) == 2 and parts[0].lower() == "bearer":
token = parts[1] token = parts[1]
print("RECIEVED DATA", data)
# In older version, we use workflow_api, but this has inputs already swapped in nextjs frontend, which is tricky # In older version, we use workflow_api, but this has inputs already swapped in nextjs frontend, which is tricky
workflow_api = data.get("workflow_api_raw") workflow_api = data.get("workflow_api_raw")
# The prompt id generated from comfy deploy, can be None # The prompt id generated from comfy deploy, can be None
@@ -542,6 +543,7 @@ async def stream_prompt(data, token):
prompt_id = data.get("prompt_id") prompt_id = data.get("prompt_id")
inputs = data.get("inputs") inputs = data.get("inputs")
workflow = data.get("workflow") workflow = data.get("workflow")
gpu_event_id = data.get("gpu_event_id", None)
# Now it handles directly in here # Now it handles directly in here
apply_random_seed_to_workflow(workflow_api) apply_random_seed_to_workflow(workflow_api)
@@ -559,6 +561,7 @@ async def stream_prompt(data, token):
file_upload_endpoint=data.get("file_upload_endpoint"), file_upload_endpoint=data.get("file_upload_endpoint"),
workflow_api=workflow_api, workflow_api=workflow_api,
token=token, token=token,
gpu_event_id=gpu_event_id,
) )
# log('info', "Begin prompt", prompt=prompt) # log('info', "Begin prompt", prompt=prompt)
@@ -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,11 +1277,61 @@ 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()
await 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")
await 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:
mark_prompt_done(prompt_id=prompt_id) mark_prompt_done(prompt_id=prompt_id)
@@ -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
@@ -1287,7 +1471,6 @@ async def update_run_ws_event(prompt_id: str, event: str, data: dict):
if prompt_id not in prompt_metadata: if prompt_id not in prompt_metadata:
return return
# print("update_run_ws_event", prompt_id, event, data)
status_endpoint = prompt_metadata[prompt_id].status_endpoint status_endpoint = prompt_metadata[prompt_id].status_endpoint
if status_endpoint is None: if status_endpoint is None:
@@ -1296,11 +1479,6 @@ async def update_run_ws_event(prompt_id: str, event: str, data: dict):
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
print("prompt_metadata", prompt_metadata[prompt_id])
print("gpu_event_id", gpu_event_id)
print("event", event)
print("data", data)
body = { body = {
"run_id": prompt_id, "run_id": prompt_id,
"ws_event": { "ws_event": {
@@ -1719,16 +1897,22 @@ 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"),
handle_upload(prompt_id, data, "files", "content_type", "image/png"),
handle_upload(prompt_id, data, "gifs", "format", "image/gif"),
handle_upload(
prompt_id, data, "mesh", "format", "application/octet-stream"
),
]
await asyncio.gather(*upload_tasks) file_upload_endpoint = prompt_metadata[prompt_id].file_upload_endpoint
if file_upload_endpoint is not None and file_upload_endpoint != "":
upload_tasks = [
handle_upload(prompt_id, data, "images", "content_type", "image/png"),
handle_upload(prompt_id, data, "files", "content_type", "image/png"),
handle_upload(prompt_id, data, "gifs", "format", "image/gif"),
handle_upload(
prompt_id, data, "mesh", "format", "application/octet-stream"
),
]
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
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-deploy" name = "comfyui-deploy"
description = "Open source comfyui deployment platform, a vercel for generative workflow infra." description = "Open source comfyui deployment platform, a vercel for generative workflow infra."
version = "1.0.0" version = "1.1.0"
license = "LICENSE" license = "LICENSE"
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg"] dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg"]
+1
View File
@@ -3,4 +3,5 @@ pydantic
opencv-python opencv-python
imageio-ffmpeg imageio-ffmpeg
brotli brotli
tabulate
# logfire # logfire
+288 -2
View File
@@ -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,13 +748,14 @@ 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");
window.location.reload();
console.log("native mode manmanman"); console.log("native mode manmanman");
} catch (error) { } catch (error) {
console.error("Error setting validation to false", error); console.error("Error setting validation to false", error);