Compare commits

..
Author SHA1 Message Date
KarrixLee 0e1f1cd6ba tweak 2025-06-28 00:33:49 +08:00
KarrixLee 57dd0d9167 tweal 2025-06-28 00:32:34 +08:00
KarrixLee b2de923440 hi 2025-06-28 00:30:09 +08:00
KarrixLee 6d2b918ef1 tweak 2025-06-28 00:27:00 +08:00
KarrixLee d8b1e88c03 hi 2025-06-28 00:23:25 +08:00
4 changed files with 142 additions and 170 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ class ComfyDeployOutputText:
}
)
return {"ui": {"text_file": results}}
return {"ui": {"text_file": results, "text": [text]}}
NODE_CLASS_MAPPINGS = {"ComfyDeployOutputText": ComfyDeployOutputText}
+86 -162
View File
@@ -265,7 +265,7 @@ def clear_current_prompt(sid):
streaming_prompt_metadata[sid].running_prompt_ids.clear()
async def post_prompt(json_data):
def post_prompt(json_data):
prompt_server = server.PromptServer.instance
json_data = prompt_server.trigger_on_prompt(json_data)
@@ -281,48 +281,7 @@ async def post_prompt(json_data):
if "prompt" in json_data:
prompt = json_data["prompt"]
prompt_id = json_data.get("prompt_id") or str(uuid.uuid4())
partial_execution_targets = None
if "partial_execution_targets" in json_data:
partial_execution_targets = json_data["partial_execution_targets"]
# Handle different validate_prompt signatures (newest to oldest)
valid = None
last_error = None
# v0.3.48 (3 args)
try:
valid = await execution.validate_prompt(
prompt_id, prompt, partial_execution_targets
)
except TypeError as e:
last_error = e
logger.debug(
f"validate_prompt with 3 params not supported, trying with 2. Debug: {last_error}"
)
# v0.3.45 - 0.3.47 (2 args)
if valid is None:
try:
valid = await execution.validate_prompt(prompt_id, prompt)
except TypeError as e:
last_error = e
logger.debug(
f"validate_prompt with 2 params not supported, trying legacy signature. Debug: {last_error}"
)
# v0.3.44 or older (1 arg)
if valid is None:
try:
valid = execution.validate_prompt(prompt)
except TypeError as e:
last_error = e
logger.error(
f"validate_prompt failed with all signatures. Last error: {last_error}"
)
raise
valid = execution.validate_prompt(prompt)
extra_data = {}
if "extra_data" in json_data:
extra_data = json_data["extra_data"]
@@ -333,6 +292,8 @@ async def post_prompt(json_data):
if "client_id" in json_data:
extra_data["client_id"] = json_data["client_id"]
if valid[0]:
# if the prompt id is provided
prompt_id = json_data.get("prompt_id") or str(uuid.uuid4())
outputs_to_execute = valid[2]
prompt_server.prompt_queue.put(
(number, prompt_id, prompt, extra_data, outputs_to_execute)
@@ -539,15 +500,15 @@ def send_prompt(sid: str, inputs: StreamingPrompt):
prompt_id = str(uuid.uuid4())
# prompt = {
# "prompt": workflow_api,
# "client_id": sid, # "comfy_deploy_instance", #api.client_id
# "prompt_id": prompt_id,
# "extra_data": {"extra_pnginfo": {"workflow": workflow}},
# }
prompt = {
"prompt": workflow_api,
"client_id": sid, # "comfy_deploy_instance", #api.client_id
"prompt_id": prompt_id,
"extra_data": {"extra_pnginfo": {"workflow": workflow}},
}
try:
# res = post_prompt(prompt)
res = post_prompt(prompt)
inputs.running_prompt_ids.add(prompt_id)
prompt_metadata[prompt_id] = SimplePrompt(
status_endpoint=inputs.status_endpoint,
@@ -558,7 +519,7 @@ def send_prompt(sid: str, inputs: StreamingPrompt):
except Exception as e:
error_type = type(e).__name__
stack_trace_short = traceback.format_exc().strip().split("\n")[-2]
# stack_trace = traceback.format_exc().strip()
stack_trace = traceback.format_exc().strip()
logger.info(f"error: {error_type}, {e}")
logger.info(f"stack trace: {stack_trace_short}")
@@ -634,7 +595,7 @@ async def comfy_deploy_run(request):
)
try:
res = await post_prompt(prompt)
res = post_prompt(prompt)
except Exception as e:
error_type = type(e).__name__
stack_trace_short = traceback.format_exc().strip().split("\n")[-2]
@@ -703,7 +664,7 @@ async def stream_prompt(data, token):
# log('info', "Begin prompt", prompt=prompt)
try:
res = await post_prompt(prompt)
res = post_prompt(prompt)
except Exception as e:
error_type = type(e).__name__
stack_trace_short = traceback.format_exc().strip().split("\n")[-2]
@@ -1305,11 +1266,22 @@ def handle_execute(class_type, last_node_id, prompt_id, server, unique_id):
try:
origin_execute = execution.execute
is_async = asyncio.iscoroutinefunction(origin_execute)
if is_async:
async def swizzle_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,
@@ -1319,61 +1291,12 @@ try:
prompt_id,
execution_list,
pending_subgraph_results,
pending_async_nodes,
):
unique_id = current_item
class_type = dynprompt.get_node(unique_id)["class_type"]
last_node_id = server.last_node_id
result = await origin_execute(
server,
dynprompt,
caches,
current_item,
extra_data,
executed,
prompt_id,
execution_list,
pending_subgraph_results,
pending_async_nodes,
)
handle_execute(class_type, last_node_id, prompt_id, server, unique_id)
return result
else:
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
)
handle_execute(class_type, last_node_id, prompt_id, server, unique_id)
return result
execution.execute = swizzle_execute
except Exception:
except Exception as e:
pass
@@ -1517,6 +1440,10 @@ async def send_json_override(self, event, data, sid=None):
logger.info(format_table(headers, table_data))
# print("========================\n")
timeline = format_execution_timeline(NODE_EXECUTION_TIMES)
logger.info(f"\nNode Execution Timeline:\n{timeline}")
# Clear the execution times for the next run
# 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)
@@ -2132,7 +2059,6 @@ async def upload_in_background(
("model_file", "format", "application/octet-stream"),
("result", "format", "application/octet-stream"),
("text_file", "format", "text/plain"),
("audio", "format", "audio/mpeg"),
]:
items = data.get(file_type, [])
@@ -2220,7 +2146,6 @@ async def update_run_with_output(
or "model_file" in data
or "result" in data
or "text_file" in data
or "audio" in data
)
if bypass_upload and have_upload_media:
print(
@@ -2829,57 +2754,59 @@ class UploadQueue:
logger.error(f"Upload failed: {str(e)}")
logger.error(traceback.format_exc())
finally:
async with self.lock: # Acquire lock to protect shared dict access
if prompt_id in self.pending_uploads:
self.pending_uploads[prompt_id].discard(upload_id)
# Remove this upload from tracking
if prompt_id in self.pending_uploads:
self.pending_uploads[prompt_id].discard(upload_id)
# Remove from node tracking if applicable
if (
node_id
and prompt_id in self.node_uploads
and node_id in self.node_uploads[prompt_id]
):
self.node_uploads[prompt_id][node_id].discard(upload_id)
if (
node_id
and prompt_id in self.node_uploads
and node_id in self.node_uploads[prompt_id]
):
self.node_uploads[prompt_id][node_id].discard(upload_id)
if not self.node_uploads[prompt_id][node_id]:
del self.node_uploads[prompt_id][node_id]
if (
prompt_id in self.node_output_data
and node_id in self.node_output_data[prompt_id]
):
node_data = self.node_output_data[prompt_id][
node_id
]
if node_data["data"]:
body = {
"run_id": prompt_id,
"output_data": node_data["data"],
"node_meta": {"node_id": node_id},
}
try:
await async_request_with_retry(
"POST",
prompt_metadata[
# If this was the last upload for this node, clean up node data
if not self.node_uploads[prompt_id][node_id]:
del self.node_uploads[prompt_id][node_id]
if prompt_id in self.node_output_data:
if node_id in self.node_output_data[prompt_id]:
if self.node_output_data[prompt_id][node_id][
"data"
]:
# Send final node data to API before cleanup
if prompt_metadata[
prompt_id
].status_endpoint:
body = {
"run_id": prompt_id,
"output_data": self.node_output_data[
prompt_id
].status_endpoint,
token=prompt_metadata[
prompt_id
].token,
json=body,
)
except Exception as e:
logger.error(
f"Failed to send final node data: {str(e)}"
)
# Safe to delete now (re-check not strictly needed with lock, but harmless)
][node_id]["data"],
"node_meta": {"node_id": node_id},
}
try:
await async_request_with_retry(
"POST",
prompt_metadata[
prompt_id
].status_endpoint,
token=prompt_metadata[
prompt_id
].token,
json=body,
)
except Exception as e:
logger.error(
f"Failed to send final node data: {str(e)}"
)
del self.node_output_data[prompt_id][node_id]
# Send status update
await self.update_queue_status(prompt_id)
# If no more pending uploads for this prompt and it's done, update status
if (
prompt_id in self.pending_uploads
and not self.pending_uploads[prompt_id]
and is_prompt_done(prompt_id)
if not self.pending_uploads[prompt_id] and is_prompt_done(
prompt_id
):
# Clean up all data for this prompt
if prompt_id in self.node_uploads:
@@ -2892,12 +2819,9 @@ class UploadQueue:
loop.create_task(update_run(prompt_id, Status.SUCCESS))
loop.create_task(send("success", {"prompt_id": prompt_id}))
# Mark task as done (outside lock to avoid holding it unnecessarily)
# Mark task as done
self.queue.task_done()
# Send status update (also outside lock)
await self.update_queue_status(prompt_id)
except Exception as e:
logger.error(f"Error in upload worker: {str(e)}")
logger.error(traceback.format_exc())
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-deploy"
description = "Open source comfyui deployment platform, a vercel for generative workflow infra."
version = "2.2.1"
version = "2.1.0"
license = { file = "LICENSE" }
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg", "tabulate", "brotli"]
+54 -6
View File
@@ -497,6 +497,54 @@ const ext = {
},
async beforeRegisterNodeDef(nodeType, nodeData, app2) {
// Add text display functionality for ComfyDeployOutputText
if (nodeData.name === "ComfyDeployOutputText") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
// Wait a bit to ensure all other widgets are created first
setTimeout(() => {
// Import ComfyWidgets if not available globally
const ComfyWidgets = app2.widgets || window.ComfyWidgets;
// Create widget exactly like rgthree does
this.textDisplayWidget = ComfyWidgets["STRING"](
this,
"displayed_text",
["STRING", { multiline: true }],
app2
).widget;
// Make it read-only
this.textDisplayWidget.inputEl.readOnly = true;
// Style it
this.textDisplayWidget.inputEl.style.fontFamily = "monospace";
// Ensure it doesn't get serialized
this.textDisplayWidget.serializeValue = () => undefined;
this.textDisplayWidget.serialize = false;
// Remove it from widgets_values array if it exists
const widgetIndex = this.widgets.indexOf(this.textDisplayWidget);
if (widgetIndex > -1 && this.widgets_values) {
this.widgets_values.splice(widgetIndex, 1);
}
}, 100);
};
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, [message]);
// Display the text output on the node
if (message.text && message.text.length > 0 && this.textDisplayWidget) {
this.textDisplayWidget.value = message.text[0];
}
};
}
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function (_, options) {
const r = origGetExtraMenuOptions
@@ -999,10 +1047,7 @@ const ext = {
"Comfy.Sidebar.Location",
"left"
);
await app.ui.settings.setSettingValueAsync(
"Comfy.TutorialCompleted",
true
);
// localStorage.setItem("Comfy.MenuPosition.Docked", "true");
console.log("native mode manmanman");
} catch (error) {
console.error("Error setting validation to false", error);
@@ -2003,10 +2048,13 @@ const currentOrigin = window.location.origin;
// serverURL: `${currentOrigin}/comfydeploy/api/`,
// });
const isComfyDeployDashboard = currentOrigin.includes("comfydeploy.com");
// 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 (!isComfyDeployDashboard) {
if (isLocalhost) {
app.extensionManager.registerSidebarTab({
id: "search",
icon: "pi pi-cloud-upload",