Compare commits

..
Author SHA1 Message Date
KarrixLee ea45930102 tweak 2025-05-21 18:43:09 +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
3 changed files with 265 additions and 83 deletions
+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)"
}
+53 -5
View File
@@ -158,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
@@ -283,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]:
@@ -316,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.
@@ -329,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")
@@ -436,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
@@ -443,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)
@@ -529,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 = {
@@ -596,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 = {
@@ -2010,13 +2056,14 @@ 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
@@ -2096,6 +2143,7 @@ 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:
+96 -78
View File
@@ -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);