Compare commits
14
Commits
if-dev
...
text-display-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94210021e3 | ||
|
|
6d2b918ef1 | ||
|
|
d8b1e88c03 | ||
|
|
22458a1cd6 | ||
|
|
0a58eba554 | ||
|
|
def54df9c2 | ||
|
|
32a950afe8 | ||
|
|
8130779d94 | ||
|
|
4cbd2a8225 | ||
|
|
d6fb2daeff | ||
|
|
a91effe3c8 | ||
|
|
c015b710fe | ||
|
|
2738d1913a | ||
|
|
3f4c11e3f1 |
@@ -0,0 +1,78 @@
|
|||||||
|
# In file: comfyui-deploy/comfy-nodes/output_exr.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
import folder_paths
|
||||||
|
|
||||||
|
# Try to set up OpenCV for EXR writing.
|
||||||
|
try:
|
||||||
|
os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1"
|
||||||
|
import cv2
|
||||||
|
OPENCV_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
print("Warning: OpenCV not found for ComfyDeployOutputEXR. Please add opencv-python-headless to requirements.txt")
|
||||||
|
OPENCV_AVAILABLE = False
|
||||||
|
|
||||||
|
# ALIGNED: Renamed class to match project conventions
|
||||||
|
class ComfyDeployOutputEXR:
|
||||||
|
def __init__(self):
|
||||||
|
self.output_dir = folder_paths.get_output_directory()
|
||||||
|
self.type = "output"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"images": ("IMAGE", ),
|
||||||
|
"filename_prefix": ("STRING", {"default": "ComfyDeploy_EXR"})
|
||||||
|
},
|
||||||
|
# ADDED: Optional output_id for consistency with other ComfyDeploy nodes
|
||||||
|
"optional": {
|
||||||
|
"output_id": ("STRING", {"multiline": False, "default": "output_exr"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_TYPES = ()
|
||||||
|
# ALIGNED: Changed function name to 'run'
|
||||||
|
FUNCTION = "run"
|
||||||
|
OUTPUT_NODE = True
|
||||||
|
# ALIGNED: Matched the category name
|
||||||
|
CATEGORY = "🔗ComfyDeploy"
|
||||||
|
DESCRIPTION = "Saves the input images as EXR (HDR) files."
|
||||||
|
|
||||||
|
def run(self, images, filename_prefix="ComfyDeploy_EXR", output_id="output_exr"):
|
||||||
|
if not OPENCV_AVAILABLE:
|
||||||
|
raise ImportError("OpenCV is required to save EXR files. Please ensure opencv-python-headless is in requirements.txt.")
|
||||||
|
|
||||||
|
full_output_folder, filename, counter, subfolder, filename_prefix = (
|
||||||
|
folder_paths.get_save_image_path(
|
||||||
|
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
results = list()
|
||||||
|
|
||||||
|
for image in images:
|
||||||
|
image_np = image.cpu().numpy()
|
||||||
|
|
||||||
|
if image_np.dtype != np.float32:
|
||||||
|
image_np = image_np.astype(np.float32)
|
||||||
|
|
||||||
|
file = f"{filename}_{counter:05}.exr"
|
||||||
|
file_path = os.path.join(full_output_folder, file)
|
||||||
|
|
||||||
|
image_np_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
|
||||||
|
cv2.imwrite(file_path, image_np_bgr)
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"filename": file,
|
||||||
|
"subfolder": subfolder,
|
||||||
|
"type": self.type,
|
||||||
|
"output_id": output_id, # ADDED
|
||||||
|
})
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
return {"ui": {"images": results}}
|
||||||
|
|
||||||
|
# ALIGNED: Mappings are defined at the bottom of the node file in this project
|
||||||
|
NODE_CLASS_MAPPINGS = {"ComfyDeployOutputEXR": ComfyDeployOutputEXR}
|
||||||
|
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyDeployOutputEXR": "EXR Output (ComfyDeploy)"}
|
||||||
@@ -92,7 +92,7 @@ class ComfyDeployOutputText:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"ui": {"text_file": results}}
|
return {"ui": {"text_file": results, "text": [text]}}
|
||||||
|
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {"ComfyDeployOutputText": ComfyDeployOutputText}
|
NODE_CLASS_MAPPINGS = {"ComfyDeployOutputText": ComfyDeployOutputText}
|
||||||
|
|||||||
+178
-3
@@ -31,6 +31,7 @@ import torch
|
|||||||
import psutil
|
import psutil
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import io
|
import io
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
# Global session
|
# Global session
|
||||||
client_session = None
|
client_session = None
|
||||||
@@ -2769,9 +2770,13 @@ class UploadQueue:
|
|||||||
del self.node_uploads[prompt_id][node_id]
|
del self.node_uploads[prompt_id][node_id]
|
||||||
if prompt_id in self.node_output_data:
|
if prompt_id in self.node_output_data:
|
||||||
if node_id in self.node_output_data[prompt_id]:
|
if node_id in self.node_output_data[prompt_id]:
|
||||||
if self.node_output_data[prompt_id][node_id]["data"]:
|
if self.node_output_data[prompt_id][node_id][
|
||||||
|
"data"
|
||||||
|
]:
|
||||||
# Send final node data to API before cleanup
|
# Send final node data to API before cleanup
|
||||||
if prompt_metadata[prompt_id].status_endpoint:
|
if prompt_metadata[
|
||||||
|
prompt_id
|
||||||
|
].status_endpoint:
|
||||||
body = {
|
body = {
|
||||||
"run_id": prompt_id,
|
"run_id": prompt_id,
|
||||||
"output_data": self.node_output_data[
|
"output_data": self.node_output_data[
|
||||||
@@ -2785,7 +2790,9 @@ class UploadQueue:
|
|||||||
prompt_metadata[
|
prompt_metadata[
|
||||||
prompt_id
|
prompt_id
|
||||||
].status_endpoint,
|
].status_endpoint,
|
||||||
token=prompt_metadata[prompt_id].token,
|
token=prompt_metadata[
|
||||||
|
prompt_id
|
||||||
|
].token,
|
||||||
json=body,
|
json=body,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -2885,3 +2892,171 @@ def format_execution_timeline(execution_times):
|
|||||||
current_time += duration
|
current_time += duration
|
||||||
|
|
||||||
return format_table(headers, rows)
|
return format_table(headers, rows)
|
||||||
|
|
||||||
|
|
||||||
|
@server.PromptServer.instance.routes.get("/comfyui-deploy/auth-response")
|
||||||
|
async def auth_response_proxy(request):
|
||||||
|
request_id = request.rel_url.query.get("request_id")
|
||||||
|
api_url = request.rel_url.query.get("api_url", "https://api.comfydeploy.com")
|
||||||
|
|
||||||
|
if not request_id:
|
||||||
|
return web.json_response({"error": "request_id is required"}, status=400)
|
||||||
|
|
||||||
|
target_url = f"{api_url}/api/platform/comfyui/auth-response?request_id={request_id}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
await ensure_client_session()
|
||||||
|
async with client_session.get(target_url) as response:
|
||||||
|
json_data = await response.json()
|
||||||
|
return web.json_response(json_data, status=response.status)
|
||||||
|
except Exception as e:
|
||||||
|
return web.json_response({"error": str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
@server.PromptServer.instance.routes.post("/comfyui-deploy/workflow")
|
||||||
|
async def create_workflow_proxy(request):
|
||||||
|
data = await request.json()
|
||||||
|
name = data.get("name")
|
||||||
|
workflow_json = data.get("workflow_json")
|
||||||
|
workflow_api = data.get("workflow_api")
|
||||||
|
api_url = data.get("api_url", "https://api.comfydeploy.com")
|
||||||
|
|
||||||
|
auth_header = request.headers.get("Authorization")
|
||||||
|
if not auth_header:
|
||||||
|
return web.json_response(
|
||||||
|
{"error": "Authorization header is required"}, status=401
|
||||||
|
)
|
||||||
|
|
||||||
|
if not name or not workflow_json or not workflow_api:
|
||||||
|
return web.json_response(
|
||||||
|
{"error": "name, workflow_json, workflow_api are required"}, status=400
|
||||||
|
)
|
||||||
|
|
||||||
|
target_url = f"{api_url}/api/workflow"
|
||||||
|
|
||||||
|
request_body = {
|
||||||
|
"name": name,
|
||||||
|
"workflow_json": json.dumps(workflow_json),
|
||||||
|
"workflow_api": json.dumps(workflow_api),
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
await ensure_client_session()
|
||||||
|
async with client_session.post(
|
||||||
|
target_url,
|
||||||
|
json=request_body,
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": auth_header,
|
||||||
|
},
|
||||||
|
) as response:
|
||||||
|
json_data = await response.json()
|
||||||
|
return web.json_response(json_data, status=response.status)
|
||||||
|
except Exception as e:
|
||||||
|
return web.json_response({"error": str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
@server.PromptServer.instance.routes.post("/comfyui-deploy/workflow/version")
|
||||||
|
async def create_workflow_version_proxy(request):
|
||||||
|
data = await request.json()
|
||||||
|
workflow_id = data.get("workflow_id")
|
||||||
|
workflow = data.get("workflow")
|
||||||
|
workflow_api = data.get("workflow_api")
|
||||||
|
comment = data.get("comment", "")
|
||||||
|
api_url = data.get("api_url", "https://api.comfydeploy.com")
|
||||||
|
|
||||||
|
auth_header = request.headers.get("Authorization")
|
||||||
|
|
||||||
|
if not auth_header:
|
||||||
|
return web.json_response(
|
||||||
|
{"error": "Authorization header is required"}, status=401
|
||||||
|
)
|
||||||
|
|
||||||
|
target_url = f"{api_url}/api/workflow/{workflow_id}/version"
|
||||||
|
|
||||||
|
request_body = {
|
||||||
|
"workflow": workflow,
|
||||||
|
"workflow_api": workflow_api,
|
||||||
|
"comment": comment,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
await ensure_client_session()
|
||||||
|
async with client_session.post(
|
||||||
|
target_url,
|
||||||
|
json=request_body,
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": auth_header,
|
||||||
|
},
|
||||||
|
) as response:
|
||||||
|
json_data = await response.json()
|
||||||
|
return web.json_response(json_data, status=response.status)
|
||||||
|
except Exception as e:
|
||||||
|
return web.json_response({"error": str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
@server.PromptServer.instance.routes.get("/comfyui-deploy/workflows")
|
||||||
|
async def get_workflows_proxy(request):
|
||||||
|
api_url = request.rel_url.query.get("api_url", "https://api.comfydeploy.com")
|
||||||
|
search = request.rel_url.query.get("search", "")
|
||||||
|
limit = request.rel_url.query.get("limit", 10)
|
||||||
|
offset = request.rel_url.query.get("offset", 0)
|
||||||
|
auth_header = request.headers.get("Authorization")
|
||||||
|
|
||||||
|
if not auth_header:
|
||||||
|
return web.json_response(
|
||||||
|
{"error": "Authorization header is required"}, status=401
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build query parameters properly
|
||||||
|
params = {}
|
||||||
|
if search:
|
||||||
|
params["search"] = search
|
||||||
|
if limit:
|
||||||
|
params["limit"] = limit
|
||||||
|
if offset:
|
||||||
|
params["offset"] = offset
|
||||||
|
|
||||||
|
target_url = f"{api_url}/api/workflows"
|
||||||
|
if params:
|
||||||
|
target_url += f"?{urlencode(params)}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
await ensure_client_session()
|
||||||
|
async with client_session.get(
|
||||||
|
target_url,
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": auth_header,
|
||||||
|
},
|
||||||
|
) as response:
|
||||||
|
json_data = await response.json()
|
||||||
|
return web.json_response(json_data, status=response.status)
|
||||||
|
except Exception as e:
|
||||||
|
return web.json_response({"error": str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
# for getting a workflow by id
|
||||||
|
@server.PromptServer.instance.routes.get("/comfyui-deploy/workflow")
|
||||||
|
async def get_workflow_proxy(request):
|
||||||
|
workflow_id = request.rel_url.query.get("workflow_id")
|
||||||
|
api_url = request.rel_url.query.get("api_url", "https://api.comfydeploy.com")
|
||||||
|
auth_header = request.headers.get("Authorization")
|
||||||
|
|
||||||
|
if not auth_header:
|
||||||
|
return web.json_response(
|
||||||
|
{"error": "Authorization header is required"}, status=401
|
||||||
|
)
|
||||||
|
|
||||||
|
target_url = f"{api_url}/api/workflow/{workflow_id}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
await ensure_client_session()
|
||||||
|
async with client_session.get(
|
||||||
|
target_url, headers={"Authorization": auth_header}
|
||||||
|
) as response:
|
||||||
|
json_data = await response.json()
|
||||||
|
return web.json_response(json_data, status=response.status)
|
||||||
|
except Exception as e:
|
||||||
|
return web.json_response({"error": str(e)}, status=500)
|
||||||
|
|||||||
+352
-152
@@ -3,6 +3,7 @@ import { api } from "../../scripts/api.js";
|
|||||||
// import { LGraphNode } from "../../scripts/widgets.js";
|
// import { LGraphNode } from "../../scripts/widgets.js";
|
||||||
LGraphNode = LiteGraph.LGraphNode;
|
LGraphNode = LiteGraph.LGraphNode;
|
||||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||||
|
import { initializeWorkflowsList, addWorkflowSearch } from "./workflow-list.js";
|
||||||
|
|
||||||
import { generateDependencyGraph } from "https://esm.sh/comfyui-json@0.1.25";
|
import { generateDependencyGraph } from "https://esm.sh/comfyui-json@0.1.25";
|
||||||
// import { ComfyDeploy } from "https://esm.sh/comfydeploy@2.0.0-beta.69";
|
// import { ComfyDeploy } from "https://esm.sh/comfydeploy@2.0.0-beta.69";
|
||||||
@@ -96,7 +97,7 @@ function dispatchAPIEventData(data) {
|
|||||||
break;
|
break;
|
||||||
case "executing":
|
case "executing":
|
||||||
api.dispatchEvent(
|
api.dispatchEvent(
|
||||||
new CustomEvent("executing", { detail: msg.data.node }),
|
new CustomEvent("executing", { detail: msg.data.node })
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "executed":
|
case "executed":
|
||||||
@@ -104,17 +105,17 @@ function dispatchAPIEventData(data) {
|
|||||||
break;
|
break;
|
||||||
case "execution_start":
|
case "execution_start":
|
||||||
api.dispatchEvent(
|
api.dispatchEvent(
|
||||||
new CustomEvent("execution_start", { detail: msg.data }),
|
new CustomEvent("execution_start", { detail: msg.data })
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "execution_error":
|
case "execution_error":
|
||||||
api.dispatchEvent(
|
api.dispatchEvent(
|
||||||
new CustomEvent("execution_error", { detail: msg.data }),
|
new CustomEvent("execution_error", { detail: msg.data })
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "execution_cached":
|
case "execution_cached":
|
||||||
api.dispatchEvent(
|
api.dispatchEvent(
|
||||||
new CustomEvent("execution_cached", { detail: msg.data }),
|
new CustomEvent("execution_cached", { detail: msg.data })
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -217,28 +218,26 @@ async function convertToInput(node, widget, config) {
|
|||||||
|
|
||||||
console.log(node, widget, config);
|
console.log(node, widget, config);
|
||||||
|
|
||||||
const result = await app.extensionManager.dialog.prompt(
|
const result = await app.extensionManager.dialog.prompt({
|
||||||
{
|
title: "Convert " + widget.name + " to external input",
|
||||||
title: "Convert " + widget.name + " to external input",
|
message: "Input name",
|
||||||
message: "Input name",
|
defaultValue: widget.name,
|
||||||
defaultValue: widget.name,
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
// Check for duplicate input IDs across existing external input nodes
|
// Check for duplicate input IDs across existing external input nodes
|
||||||
const existingInputIds = Object.values(app.graph.nodes)
|
const existingInputIds = Object.values(app.graph.nodes)
|
||||||
.filter(n => n.type.startsWith("ComfyUIDeployExternal"))
|
.filter((n) => n.type.startsWith("ComfyUIDeployExternal"))
|
||||||
.map(n => n.widgets_values?.[0])
|
.map((n) => n.widgets_values?.[0])
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
if (existingInputIds.includes(result)) {
|
if (existingInputIds.includes(result)) {
|
||||||
app.extensionManager.toast.add({
|
app.extensionManager.toast.add({
|
||||||
severity: 'error',
|
severity: "error",
|
||||||
summary: 'Input ID already exists',
|
summary: "Input ID already exists",
|
||||||
detail: 'Please choose a different name.',
|
detail: "Please choose a different name.",
|
||||||
life: 3000
|
life: 3000,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -273,7 +272,7 @@ async function convertToInput(node, widget, config) {
|
|||||||
console.log(links[link]);
|
console.log(links[link]);
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => inputNode.connect(0, llink.target_id, llink.target_slot),
|
() => inputNode.connect(0, llink.target_id, llink.target_slot),
|
||||||
100,
|
100
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +308,10 @@ async function convertToInput(node, widget, config) {
|
|||||||
|
|
||||||
node.convertWidgetToInput(widget);
|
node.convertWidgetToInput(widget);
|
||||||
|
|
||||||
var inputNode = LiteGraph.createNode(externalNode, "External Input: " + inputId);
|
var inputNode = LiteGraph.createNode(
|
||||||
|
externalNode,
|
||||||
|
"External Input: " + inputId
|
||||||
|
);
|
||||||
|
|
||||||
// if (type === "COMBO") {
|
// if (type === "COMBO") {
|
||||||
// inputNode = LiteGraph.createNode(externalNode, "External Input: " + inputId, {
|
// inputNode = LiteGraph.createNode(externalNode, "External Input: " + inputId, {
|
||||||
@@ -342,7 +344,8 @@ async function convertToInput(node, widget, config) {
|
|||||||
if (type === "COMBO") {
|
if (type === "COMBO") {
|
||||||
console.log(inputNode);
|
console.log(inputNode);
|
||||||
console.log(options);
|
console.log(options);
|
||||||
inputNode.widgets.find((x) => x.name == "default_value").options.values = options;
|
inputNode.widgets.find((x) => x.name == "default_value").options.values =
|
||||||
|
options;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.graph.add(inputNode);
|
app.graph.add(inputNode);
|
||||||
@@ -365,7 +368,11 @@ function getConfig(widgetName) {
|
|||||||
|
|
||||||
function isConvertibleWidget(node, widget, config) {
|
function isConvertibleWidget(node, widget, config) {
|
||||||
// console.log(config);
|
// console.log(config);
|
||||||
if (node.type === "LoadImage" && widget.type === "combo" && widget.name == "image") {
|
if (
|
||||||
|
node.type === "LoadImage" &&
|
||||||
|
widget.type === "combo" &&
|
||||||
|
widget.name == "image"
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +454,7 @@ const ext = {
|
|||||||
} else {
|
} else {
|
||||||
loadingDialog.showLoading(
|
loadingDialog.showLoading(
|
||||||
"Loading workflow from " + org_display,
|
"Loading workflow from " + org_display,
|
||||||
"Please wait...",
|
"Please wait..."
|
||||||
);
|
);
|
||||||
fetch(endpoint + "/api/workflow-version/" + workflow_version_id, {
|
fetch(endpoint + "/api/workflow-version/" + workflow_version_id, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -483,13 +490,63 @@ const ext = {
|
|||||||
window.history.replaceState(
|
window.history.replaceState(
|
||||||
{},
|
{},
|
||||||
document.title,
|
document.title,
|
||||||
window.location.pathname,
|
window.location.pathname
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async beforeRegisterNodeDef(nodeType, nodeData, app2) {
|
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;
|
||||||
|
|
||||||
|
// Create a multiline text widget to display the output (similar to rgthree)
|
||||||
|
this.textDisplayWidget = app.widgets.STRING(
|
||||||
|
this,
|
||||||
|
"displayed_text",
|
||||||
|
["STRING", { multiline: true }],
|
||||||
|
app2
|
||||||
|
).widget;
|
||||||
|
|
||||||
|
// Style the widget to look like a proper text display box
|
||||||
|
if (this.textDisplayWidget.inputEl) {
|
||||||
|
this.textDisplayWidget.inputEl.readOnly = true;
|
||||||
|
this.textDisplayWidget.inputEl.style.backgroundColor =
|
||||||
|
"var(--comfy-input-bg)";
|
||||||
|
this.textDisplayWidget.inputEl.style.border =
|
||||||
|
"1px solid var(--border-color)";
|
||||||
|
this.textDisplayWidget.inputEl.style.color = "var(--input-text)";
|
||||||
|
this.textDisplayWidget.inputEl.style.fontFamily = "monospace";
|
||||||
|
this.textDisplayWidget.inputEl.style.fontSize = "12px";
|
||||||
|
this.textDisplayWidget.inputEl.style.minHeight = "100px";
|
||||||
|
this.textDisplayWidget.inputEl.style.resize = "vertical";
|
||||||
|
this.textDisplayWidget.inputEl.style.padding = "8px";
|
||||||
|
this.textDisplayWidget.inputEl.style.whiteSpace = "pre-wrap";
|
||||||
|
this.textDisplayWidget.inputEl.style.wordWrap = "break-word";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override the serialize function to not save the display value
|
||||||
|
this.textDisplayWidget.serializeValue = () => "";
|
||||||
|
};
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
// Resize the node to fit the content
|
||||||
|
this.setSize(this.computeSize());
|
||||||
|
app.graph.setDirtyCanvas(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||||
nodeType.prototype.getExtraMenuOptions = function (_, options) {
|
nodeType.prototype.getExtraMenuOptions = function (_, options) {
|
||||||
const r = origGetExtraMenuOptions
|
const r = origGetExtraMenuOptions
|
||||||
@@ -507,7 +564,7 @@ const ext = {
|
|||||||
content: `Convert ${w.name} to widget`,
|
content: `Convert ${w.name} to widget`,
|
||||||
callback: /* @__PURE__ */ __name(
|
callback: /* @__PURE__ */ __name(
|
||||||
() => convertToWidget(this, w),
|
() => convertToWidget(this, w),
|
||||||
"callback",
|
"callback"
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -520,7 +577,7 @@ const ext = {
|
|||||||
content: `Convert ${w.name} to external input`,
|
content: `Convert ${w.name} to external input`,
|
||||||
callback: /* @__PURE__ */ __name(
|
callback: /* @__PURE__ */ __name(
|
||||||
async () => convertToInput(this, w, config),
|
async () => convertToInput(this, w, config),
|
||||||
"callback",
|
"callback"
|
||||||
),
|
),
|
||||||
className: "comfydeploy-menu-item",
|
className: "comfydeploy-menu-item",
|
||||||
});
|
});
|
||||||
@@ -545,7 +602,7 @@ const ext = {
|
|||||||
// className: "comfydeploy-menu-item"
|
// className: "comfydeploy-menu-item"
|
||||||
// },
|
// },
|
||||||
...toInput,
|
...toInput,
|
||||||
null,
|
null
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
options.push(...toInput, null);
|
options.push(...toInput, null);
|
||||||
@@ -574,9 +631,7 @@ const ext = {
|
|||||||
console.log(nodeData.input.optional.default_value_url);
|
console.log(nodeData.input.optional.default_value_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (nodeData?.input?.optional?.default_value?.[1]?.dynamic_enum === true) {
|
||||||
nodeData?.input?.optional?.default_value?.[1]?.dynamic_enum === true
|
|
||||||
) {
|
|
||||||
nodeData.input.optional.default_value = ["DYNAMIC_ENUM"];
|
nodeData.input.optional.default_value = ["DYNAMIC_ENUM"];
|
||||||
// console.log(nodeData.input.optional.default_value);
|
// console.log(nodeData.input.optional.default_value);
|
||||||
}
|
}
|
||||||
@@ -642,7 +697,7 @@ const ext = {
|
|||||||
(v) => {
|
(v) => {
|
||||||
this.properties.workflow_name = v;
|
this.properties.workflow_name = v;
|
||||||
},
|
},
|
||||||
{ multiline: false },
|
{ multiline: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addWidget(
|
this.addWidget(
|
||||||
@@ -652,7 +707,7 @@ const ext = {
|
|||||||
(v) => {
|
(v) => {
|
||||||
this.properties.workflow_id = v;
|
this.properties.workflow_id = v;
|
||||||
},
|
},
|
||||||
{ multiline: false },
|
{ multiline: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addWidget(
|
this.addWidget(
|
||||||
@@ -662,7 +717,7 @@ const ext = {
|
|||||||
(v) => {
|
(v) => {
|
||||||
this.properties.version = v;
|
this.properties.version = v;
|
||||||
},
|
},
|
||||||
{ multiline: false },
|
{ multiline: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
this.widgets_start_y = 10;
|
this.widgets_start_y = 10;
|
||||||
@@ -688,8 +743,19 @@ const ext = {
|
|||||||
|
|
||||||
onConfigure(o) {
|
onConfigure(o) {
|
||||||
// This method is called when the node is being configured (e.g., when loading a saved graph)
|
// This method is called when the node is being configured (e.g., when loading a saved graph)
|
||||||
// Ensure all necessary data is restored
|
// ComfyUI stores widget values in widgets_values array, not properties
|
||||||
if (o.properties) {
|
if (o.widgets_values && o.widgets_values.length >= 3) {
|
||||||
|
// Set the widget values directly
|
||||||
|
this.widgets[0].value = o.widgets_values[0] || "";
|
||||||
|
this.widgets[1].value = o.widgets_values[1] || "";
|
||||||
|
this.widgets[2].value = o.widgets_values[2] || "1";
|
||||||
|
|
||||||
|
// Also update properties to stay in sync
|
||||||
|
this.properties.workflow_name = o.widgets_values[0] || "";
|
||||||
|
this.properties.workflow_id = o.widgets_values[1] || "";
|
||||||
|
this.properties.version = o.widgets_values[2] || "1";
|
||||||
|
} else if (o.properties) {
|
||||||
|
// Fallback to properties if widgets_values is not available
|
||||||
this.properties = { ...this.properties, ...o.properties };
|
this.properties = { ...this.properties, ...o.properties };
|
||||||
this.widgets[0].value = this.properties.workflow_name || "";
|
this.widgets[0].value = this.properties.workflow_name || "";
|
||||||
this.widgets[1].value = this.properties.workflow_id || "";
|
this.widgets[1].value = this.properties.workflow_id || "";
|
||||||
@@ -705,7 +771,7 @@ const ext = {
|
|||||||
title: "Comfy Deploy",
|
title: "Comfy Deploy",
|
||||||
title_mode: LiteGraph.NORMAL_TITLE,
|
title_mode: LiteGraph.NORMAL_TITLE,
|
||||||
collapsable: true,
|
collapsable: true,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
ComfyDeploy.category = "deploy";
|
ComfyDeploy.category = "deploy";
|
||||||
@@ -719,8 +785,8 @@ const ext = {
|
|||||||
"string",
|
"string",
|
||||||
inputName,
|
inputName,
|
||||||
/* value=*/ "",
|
/* value=*/ "",
|
||||||
() => { },
|
() => {},
|
||||||
{ serialize: true },
|
{ serialize: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const buttonWidget = node.addWidget(
|
const buttonWidget = node.addWidget(
|
||||||
@@ -734,7 +800,7 @@ const ext = {
|
|||||||
});
|
});
|
||||||
// console.log("load image");
|
// console.log("load image");
|
||||||
},
|
},
|
||||||
{ serialize: false },
|
{ serialize: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(node.widgets);
|
console.log(node.widgets);
|
||||||
@@ -783,12 +849,10 @@ const ext = {
|
|||||||
|
|
||||||
DYNAMIC_ENUM(node, inputName, inputData) {
|
DYNAMIC_ENUM(node, inputName, inputData) {
|
||||||
// console.log("DYNAMIC_ENUM", JSON.parse(JSON.stringify(node)), inputName, inputData);
|
// console.log("DYNAMIC_ENUM", JSON.parse(JSON.stringify(node)), inputName, inputData);
|
||||||
const enumWidget = node.addWidget(
|
const enumWidget = node.addWidget("combo", inputName, "", {
|
||||||
"combo",
|
serialize: true,
|
||||||
inputName,
|
values: [],
|
||||||
"",
|
});
|
||||||
{ serialize: true, values: [] },
|
|
||||||
);
|
|
||||||
|
|
||||||
return { widget: enumWidget };
|
return { widget: enumWidget };
|
||||||
},
|
},
|
||||||
@@ -796,10 +860,14 @@ const ext = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async afterConfigureGraph() {
|
async afterConfigureGraph() {
|
||||||
app.graph.nodes.forEach(node => {
|
app.graph.nodes.forEach((node) => {
|
||||||
if (node.type === "ComfyUIDeployExternalEnum") {
|
if (node.type === "ComfyUIDeployExternalEnum") {
|
||||||
const default_value_index = node.widgets.findIndex(x => x.name === "default_value");
|
const default_value_index = node.widgets.findIndex(
|
||||||
const options_index = node.widgets.findIndex(x => x.name === "options");
|
(x) => x.name === "default_value"
|
||||||
|
);
|
||||||
|
const options_index = node.widgets.findIndex(
|
||||||
|
(x) => x.name === "options"
|
||||||
|
);
|
||||||
|
|
||||||
var dynamic_enum_options = [node.widgets[default_value_index].value];
|
var dynamic_enum_options = [node.widgets[default_value_index].value];
|
||||||
if (node.widgets[options_index].value) {
|
if (node.widgets[options_index].value) {
|
||||||
@@ -828,12 +896,12 @@ const ext = {
|
|||||||
try {
|
try {
|
||||||
await window["app"].ui.settings.setSettingValueAsync(
|
await window["app"].ui.settings.setSettingValueAsync(
|
||||||
"Comfy.Validation.Workflows",
|
"Comfy.Validation.Workflows",
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warning(
|
console.warning(
|
||||||
"Error setting validation to false, is fine to ignore this",
|
"Error setting validation to false, is fine to ignore this",
|
||||||
error,
|
error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log("loadGraphData");
|
console.log("loadGraphData");
|
||||||
@@ -975,11 +1043,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",
|
||||||
"left",
|
"left"
|
||||||
);
|
);
|
||||||
// localStorage.setItem("Comfy.MenuPosition.Docked", "true");
|
// localStorage.setItem("Comfy.MenuPosition.Docked", "true");
|
||||||
console.log("native mode manmanman");
|
console.log("native mode manmanman");
|
||||||
@@ -998,7 +1066,6 @@ const ext = {
|
|||||||
}
|
}
|
||||||
})(app.graph.onAfterChange);
|
})(app.graph.onAfterChange);
|
||||||
|
|
||||||
|
|
||||||
sendEventToCD("cd_plugin_setup");
|
sendEventToCD("cd_plugin_setup");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1010,7 +1077,7 @@ const ext = {
|
|||||||
|
|
||||||
function showError(title, message) {
|
function showError(title, message) {
|
||||||
infoDialog.show(
|
infoDialog.show(
|
||||||
`<h3 style="margin: 0px; color: red;">${title}</h3><br><span>${message}</span> `,
|
`<h3 style="margin: 0px; color: red;">${title}</h3><br><span>${message}</span> `
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1033,10 +1100,10 @@ function createDynamicUIHtml(data) {
|
|||||||
<h3 style="font-size: 14px; font-weight: semibold; margin-bottom: 8px;">Missing Nodes</h3>
|
<h3 style="font-size: 14px; font-weight: semibold; margin-bottom: 8px;">Missing Nodes</h3>
|
||||||
<p style="font-size: 12px;">These nodes are not found with any matching custom_nodes in the ComfyUI Manager Database</p>
|
<p style="font-size: 12px;">These nodes are not found with any matching custom_nodes in the ComfyUI Manager Database</p>
|
||||||
${data.missing_nodes
|
${data.missing_nodes
|
||||||
.map((node) => {
|
.map((node) => {
|
||||||
return `<p style="font-size: 14px; color: #d69e2e;">${node}</p>`;
|
return `<p style="font-size: 14px; color: #d69e2e;">${node}</p>`;
|
||||||
})
|
})
|
||||||
.join("")}
|
.join("")}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -1044,14 +1111,17 @@ function createDynamicUIHtml(data) {
|
|||||||
Object.values(data.custom_nodes).forEach((node) => {
|
Object.values(data.custom_nodes).forEach((node) => {
|
||||||
html += `
|
html += `
|
||||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 16px;">
|
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 16px;">
|
||||||
<a href="${node.url
|
<a href="${
|
||||||
}" target="_blank" style="font-size: 18px; font-weight: semibold; color: white; text-decoration: none;">${node.name
|
node.url
|
||||||
}</a>
|
}" target="_blank" style="font-size: 18px; font-weight: semibold; color: white; text-decoration: none;">${
|
||||||
|
node.name
|
||||||
|
}</a>
|
||||||
<p style="font-size: 14px; color: #4b5563;">${node.hash}</p>
|
<p style="font-size: 14px; color: #4b5563;">${node.hash}</p>
|
||||||
${node.warning
|
${
|
||||||
? `<p style="font-size: 14px; color: #d69e2e;">${node.warning}</p>`
|
node.warning
|
||||||
: ""
|
? `<p style="font-size: 14px; color: #d69e2e;">${node.warning}</p>`
|
||||||
}
|
: ""
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
@@ -1065,8 +1135,9 @@ function createDynamicUIHtml(data) {
|
|||||||
Object.entries(data.models).forEach(([section, items]) => {
|
Object.entries(data.models).forEach(([section, items]) => {
|
||||||
html += `
|
html += `
|
||||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
||||||
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${section.charAt(0).toUpperCase() + section.slice(1)
|
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${
|
||||||
}</h3>`;
|
section.charAt(0).toUpperCase() + section.slice(1)
|
||||||
|
}</h3>`;
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
||||||
});
|
});
|
||||||
@@ -1082,8 +1153,9 @@ function createDynamicUIHtml(data) {
|
|||||||
Object.entries(data.files).forEach(([section, items]) => {
|
Object.entries(data.files).forEach(([section, items]) => {
|
||||||
html += `
|
html += `
|
||||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
||||||
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${section.charAt(0).toUpperCase() + section.slice(1)
|
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${
|
||||||
}</h3>`;
|
section.charAt(0).toUpperCase() + section.slice(1)
|
||||||
|
}</h3>`;
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
||||||
});
|
});
|
||||||
@@ -1102,9 +1174,9 @@ async function deployWorkflow() {
|
|||||||
/** @type {LGraph} */
|
/** @type {LGraph} */
|
||||||
const graph = app.graph;
|
const graph = app.graph;
|
||||||
|
|
||||||
let { endpoint, apiKey, displayName } = getData();
|
let { endpoint, apiKey, apiUrl, displayName } = getData();
|
||||||
|
|
||||||
if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
|
if (!endpoint || !apiKey || apiKey === "" || endpoint === "" || !apiUrl) {
|
||||||
configDialog.show();
|
configDialog.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1114,7 +1186,7 @@ async function deployWorkflow() {
|
|||||||
if (deployMeta.length == 0) {
|
if (deployMeta.length == 0) {
|
||||||
const text = await inputDialog.input(
|
const text = await inputDialog.input(
|
||||||
"Create your deployment",
|
"Create your deployment",
|
||||||
"Workflow name",
|
"Workflow name"
|
||||||
);
|
);
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
console.log(text);
|
console.log(text);
|
||||||
@@ -1148,62 +1220,111 @@ async function deployWorkflow() {
|
|||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
<label>
|
<label>
|
||||||
<input id="include-deps" type="checkbox" checked>Include dependency</input>
|
<input id="include-deps" type="checkbox">Include dependency</input>
|
||||||
</label>
|
</label>
|
||||||
<br>
|
<br>
|
||||||
<label>
|
<label>
|
||||||
<input id="reuse-hash" type="checkbox" checked>Reuse hash from last version</input>
|
<input id="reuse-hash" type="checkbox" checked>Reuse hash from last version</input>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`
|
||||||
);
|
);
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
||||||
const includeDeps = document.getElementById("include-deps").checked;
|
const includeDeps = document.getElementById("include-deps").checked;
|
||||||
const reuseHash = document.getElementById("reuse-hash").checked;
|
const reuseHash = document.getElementById("reuse-hash").checked;
|
||||||
|
|
||||||
|
const prompt = await app.graphToPrompt();
|
||||||
|
let deps = undefined;
|
||||||
|
|
||||||
|
console.log(prompt);
|
||||||
|
|
||||||
|
if (workflow_id.trim() !== "") {
|
||||||
|
const text = await inputDialog.input("Save changes", "Comment");
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
loadingDialog.showLoading("Saving changes");
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
api_url: apiUrl,
|
||||||
|
workflow: prompt.workflow,
|
||||||
|
workflow_id: workflow_id,
|
||||||
|
workflow_api: prompt.output,
|
||||||
|
comment: text,
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = await fetch("/comfyui-deploy/workflow/version", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + apiKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.status !== 200) {
|
||||||
|
throw new Error(await data.text());
|
||||||
|
} else {
|
||||||
|
data = await data.json();
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
infoDialog.show(
|
||||||
|
`<span style="color:green;">New version created!</span> <br/> <br/> Workflow ID: ${data.workflow_id} <br/> Workflow Name: ${workflow_name} <br/> Workflow Version: ${data.version} <br/>`
|
||||||
|
);
|
||||||
|
|
||||||
|
deployMetaNode.widgets[2].value = data.version;
|
||||||
|
graph.change();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
infoDialog.showError("Error", e.message);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
loadingDialog.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (endpoint.endsWith("/")) {
|
if (endpoint.endsWith("/")) {
|
||||||
endpoint = endpoint.slice(0, -1);
|
endpoint = endpoint.slice(0, -1);
|
||||||
}
|
}
|
||||||
loadingDialog.showLoading("Generating snapshot");
|
loadingDialog.showLoading("Generating snapshot");
|
||||||
|
|
||||||
const snapshot = await fetch("/snapshot/get_current").then((x) => x.json());
|
const snapshot = await fetch("/snapshot/get_current").then((x) => x.json());
|
||||||
// console.log(snapshot);
|
console.log(snapshot);
|
||||||
loadingDialog.close();
|
loadingDialog.close();
|
||||||
|
|
||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
showError(
|
showError(
|
||||||
"Error when deploying",
|
"Error when deploying",
|
||||||
"Unable to generate snapshot, please install ComfyUI Manager",
|
"Unable to generate snapshot, please install ComfyUI Manager"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = deploy.querySelector("#button-title");
|
const title = deploy.querySelector("#button-title");
|
||||||
|
|
||||||
const prompt = await app.graphToPrompt();
|
|
||||||
let deps = undefined;
|
|
||||||
|
|
||||||
console.log(prompt);
|
|
||||||
|
|
||||||
if (includeDeps) {
|
if (includeDeps) {
|
||||||
loadingDialog.showLoading("Fetching existing version");
|
loadingDialog.showLoading("Fetching existing version");
|
||||||
|
|
||||||
const existing_workflow = await fetch(
|
const existing_workflow = await fetch(
|
||||||
endpoint + "/api/workflow/" + workflow_id,
|
apiUrl + "/api/workflow/" + workflow_id,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: "Bearer " + apiKey,
|
Authorization: "Bearer " + apiKey,
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
.then((x) => x.json())
|
.then((x) => x.json())
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("workflow", existing_workflow);
|
||||||
|
|
||||||
loadingDialog.close();
|
loadingDialog.close();
|
||||||
|
|
||||||
loadingDialog.showLoading("Generating dependency graph");
|
loadingDialog.showLoading("Generating dependency graph");
|
||||||
@@ -1216,7 +1337,7 @@ async function deployWorkflow() {
|
|||||||
// Match previous hash for models
|
// Match previous hash for models
|
||||||
if (reuseHash && existing_workflow?.dependencies?.models) {
|
if (reuseHash && existing_workflow?.dependencies?.models) {
|
||||||
const previousModelHash = Object.entries(
|
const previousModelHash = Object.entries(
|
||||||
existing_workflow?.dependencies?.models,
|
existing_workflow?.dependencies?.models
|
||||||
).flatMap(([key, value]) => {
|
).flatMap(([key, value]) => {
|
||||||
return Object.values(value).map((x) => ({
|
return Object.values(value).map((x) => ({
|
||||||
...x,
|
...x,
|
||||||
@@ -1238,7 +1359,7 @@ async function deployWorkflow() {
|
|||||||
console.log(file);
|
console.log(file);
|
||||||
loadingDialog.showLoading("Generating hash", file);
|
loadingDialog.showLoading("Generating hash", file);
|
||||||
const hash = await fetch(
|
const hash = await fetch(
|
||||||
`/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent(file)}`,
|
`/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent(file)}`
|
||||||
).then((x) => x.json());
|
).then((x) => x.json());
|
||||||
loadingDialog.showLoading("Generating hash", file);
|
loadingDialog.showLoading("Generating hash", file);
|
||||||
console.log(hash);
|
console.log(hash);
|
||||||
@@ -1287,6 +1408,7 @@ async function deployWorkflow() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadingDialog.close();
|
loadingDialog.close();
|
||||||
|
console.log(deps);
|
||||||
|
|
||||||
const depsOk = await confirmDialog.confirm(
|
const depsOk = await confirmDialog.confirm(
|
||||||
"Check dependencies",
|
"Check dependencies",
|
||||||
@@ -1305,13 +1427,12 @@ async function deployWorkflow() {
|
|||||||
<iframe
|
<iframe
|
||||||
style="z-index: 10; min-width: 600px; max-width: 1024px; min-height: 600px; border: none; background-color: transparent;"
|
style="z-index: 10; min-width: 600px; max-width: 1024px; min-height: 600px; border: none; background-color: transparent;"
|
||||||
src="https://www.comfydeploy.com/dependency-graph?deps=${encodeURIComponent(
|
src="https://www.comfydeploy.com/dependency-graph?deps=${encodeURIComponent(
|
||||||
JSON.stringify(deps),
|
JSON.stringify(deps)
|
||||||
)}" />`,
|
)}" />`
|
||||||
// createDynamicUIHtml(deps),
|
// createDynamicUIHtml(deps),
|
||||||
);
|
);
|
||||||
if (!depsOk) return;
|
|
||||||
|
|
||||||
console.log(deps);
|
if (!depsOk) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingDialog.showLoading("Deploying...");
|
loadingDialog.showLoading("Deploying...");
|
||||||
@@ -1319,28 +1440,15 @@ async function deployWorkflow() {
|
|||||||
title.innerText = "Deploying...";
|
title.innerText = "Deploying...";
|
||||||
title.style.color = "orange";
|
title.style.color = "orange";
|
||||||
|
|
||||||
// console.log(prompt);
|
|
||||||
|
|
||||||
// TODO trim the ending / from endpoint is there is
|
|
||||||
if (endpoint.endsWith("/")) {
|
|
||||||
endpoint = endpoint.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log(prompt.workflow);
|
|
||||||
|
|
||||||
const apiRoute = endpoint + "/api/workflow";
|
|
||||||
// const userId = apiKey
|
|
||||||
try {
|
try {
|
||||||
const body = {
|
const body = {
|
||||||
workflow_name,
|
name: workflow_name,
|
||||||
workflow_id,
|
workflow_json: prompt.workflow,
|
||||||
workflow: prompt.workflow,
|
|
||||||
workflow_api: prompt.output,
|
workflow_api: prompt.output,
|
||||||
snapshot: snapshot,
|
api_url: apiUrl,
|
||||||
dependencies: deps,
|
|
||||||
};
|
};
|
||||||
console.log(body);
|
console.log(body);
|
||||||
let data = await fetch(apiRoute, {
|
let data = await fetch("/comfyui-deploy/workflow", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
headers: {
|
headers: {
|
||||||
@@ -1350,11 +1458,11 @@ async function deployWorkflow() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
if (data.status !== 200) {
|
if (data.status !== 200) {
|
||||||
throw new Error(await data.text());
|
throw new Error(await data.text());
|
||||||
} else {
|
} else {
|
||||||
data = await data.json();
|
data = await data.json();
|
||||||
|
await refreshWorkflowListIfOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingDialog.close();
|
loadingDialog.close();
|
||||||
@@ -1363,20 +1471,37 @@ async function deployWorkflow() {
|
|||||||
title.style.color = "green";
|
title.style.color = "green";
|
||||||
|
|
||||||
deployMetaNode.widgets[1].value = data.workflow_id;
|
deployMetaNode.widgets[1].value = data.workflow_id;
|
||||||
deployMetaNode.widgets[2].value = data.version;
|
deployMetaNode.widgets[2].value = 2;
|
||||||
graph.change();
|
graph.change();
|
||||||
|
|
||||||
infoDialog.show(
|
if (data.workflow_id) {
|
||||||
`<span style="color:green;">Deployed successfully!</span> <a style="color:white;" target="_blank" href=${endpoint}/workflows/${data.workflow_id}>-> View here</a> <br/> <br/> Workflow ID: ${data.workflow_id} <br/> Workflow Name: ${workflow_name} <br/> Workflow Version: ${data.version} <br/>`,
|
const prompt_with_workflow_id = await app.graphToPrompt();
|
||||||
);
|
|
||||||
|
|
||||||
// // Refresh the workflows list in the sidebar
|
const body = {
|
||||||
// const sidebarEl = document.querySelector(
|
api_url: apiUrl,
|
||||||
// '.comfy-sidebar-tab[data-id="search"]',
|
workflow: prompt_with_workflow_id.workflow,
|
||||||
// );
|
workflow_id: data.workflow_id,
|
||||||
// if (sidebarEl) {
|
workflow_api: prompt_with_workflow_id.output,
|
||||||
// refreshWorkflowsList(sidebarEl);
|
comment: "chore: apply workflow id",
|
||||||
// }
|
};
|
||||||
|
|
||||||
|
let new_version_data = await fetch("/comfyui-deploy/workflow/version", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + apiKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (new_version_data.status !== 200) {
|
||||||
|
throw new Error(await new_version_data.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
infoDialog.show(
|
||||||
|
`<span style="color:green;">Deployed successfully!</span> <a style="color:white;" target="_blank" href=${endpoint}/workflows/${data.workflow_id}>-> View here</a> <br/> <br/> Workflow ID: ${data.workflow_id} <br/> Workflow Name: ${workflow_name} <br/> Workflow Version: 2 <br/>`
|
||||||
|
);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
title.textContent = "Deploy";
|
title.textContent = "Deploy";
|
||||||
@@ -1489,6 +1614,24 @@ export class InfoDialog extends ComfyDialog {
|
|||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showError(title, message) {
|
||||||
|
this.show(`
|
||||||
|
<div style="width: 100%; max-width: 600px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||||
|
<h3 style="margin: 0px; color: #ff4444; display: flex; align-items: center; gap: 8px;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M18 6L6 18M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
${title}
|
||||||
|
</h3>
|
||||||
|
<div style="padding: 12px;">
|
||||||
|
${message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
// Set higher z-index to appear above config dialog
|
||||||
|
this.element.style.zIndex = 1010;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LoadingDialog extends ComfyDialog {
|
export class LoadingDialog extends ComfyDialog {
|
||||||
@@ -1524,12 +1667,14 @@ export class LoadingDialog extends ComfyDialog {
|
|||||||
showLoading(title, message) {
|
showLoading(title, message) {
|
||||||
this.show(`
|
this.show(`
|
||||||
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||||
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${this.loadingIcon
|
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${
|
||||||
}</h3>
|
this.loadingIcon
|
||||||
${message
|
}</h3>
|
||||||
? `<label style="max-width: 100%; white-space: pre-wrap; word-wrap: break-word;">${message}</label>`
|
${
|
||||||
: ""
|
message
|
||||||
}
|
? `<label style="max-width: 100%; white-space: pre-wrap; word-wrap: break-word;">${message}</label>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
@@ -1578,7 +1723,7 @@ export class InputDialog extends InfoDialog {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
]
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1639,7 +1784,7 @@ export class ConfirmDialog extends InfoDialog {
|
|||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
]
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1674,12 +1819,14 @@ function getData(environment) {
|
|||||||
if (!data) {
|
if (!data) {
|
||||||
if (deployOption == "cloud")
|
if (deployOption == "cloud")
|
||||||
return {
|
return {
|
||||||
endpoint: "https://www.comfydeploy.com",
|
endpoint: "https://app.comfydeploy.com",
|
||||||
|
apiUrl: "https://api.comfydeploy.com",
|
||||||
apiKey: "",
|
apiKey: "",
|
||||||
};
|
};
|
||||||
else
|
else
|
||||||
return {
|
return {
|
||||||
endpoint: "http://localhost:3000",
|
endpoint: "http://localhost:3001",
|
||||||
|
apiUrl: "http://localhost:3011",
|
||||||
apiKey: "",
|
apiKey: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1696,7 +1843,7 @@ function getData(environment) {
|
|||||||
function saveData(data) {
|
function saveData(data) {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"comfy_deploy_env_data_" + data.environment,
|
"comfy_deploy_env_data_" + data.environment,
|
||||||
JSON.stringify(data),
|
JSON.stringify(data)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1745,7 +1892,7 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
]
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1756,7 +1903,7 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
clearTimeout(this.timeout);
|
clearTimeout(this.timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
save(api_key, displayName) {
|
async save(api_key, displayName) {
|
||||||
const deployOption = this.container.querySelector("#deployOption").value;
|
const deployOption = this.container.querySelector("#deployOption").value;
|
||||||
localStorage.setItem("comfy_deploy_env", deployOption);
|
localStorage.setItem("comfy_deploy_env", deployOption);
|
||||||
|
|
||||||
@@ -1773,10 +1920,14 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
|
|
||||||
saveData({
|
saveData({
|
||||||
endpoint,
|
endpoint,
|
||||||
|
apiUrl: getData(deployOption).apiUrl,
|
||||||
apiKey,
|
apiKey,
|
||||||
displayName,
|
displayName,
|
||||||
environment: deployOption,
|
environment: deployOption,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Refresh workflow list after saving configuration
|
||||||
|
await refreshWorkflowListIfOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
@@ -1789,23 +1940,33 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
<h3 style="margin: 0px;">Comfy Deploy Config</h3>
|
<h3 style="margin: 0px;">Comfy Deploy Config</h3>
|
||||||
<label style="color: white; width: 100%;">
|
<label style="color: white; width: 100%;">
|
||||||
<select id="deployOption" style="margin: 8px 0px; width: 100%; height:30px; box-sizing: border-box;" >
|
<select id="deployOption" style="margin: 8px 0px; width: 100%; height:30px; box-sizing: border-box;" >
|
||||||
<option value="cloud" ${data.environment === "cloud" ? "selected" : ""}>Cloud</option>
|
<option value="cloud" ${
|
||||||
<option value="local" ${data.environment === "local" ? "selected" : ""}>Local</option>
|
data.environment === "cloud" ? "selected" : ""
|
||||||
|
}>Cloud</option>
|
||||||
|
<option value="local" ${
|
||||||
|
data.environment === "local" ? "selected" : ""
|
||||||
|
}>Local</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label style="color: white; width: 100%;">
|
<label style="color: white; width: 100%;">
|
||||||
Endpoint:
|
Endpoint:
|
||||||
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${data.endpoint
|
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${
|
||||||
}">
|
data.endpoint
|
||||||
|
}">
|
||||||
</label>
|
</label>
|
||||||
<div style="color: white;">
|
<div style="color: white;">
|
||||||
API Key: User / Org <button style="font-size: 18px;">${data.displayName ?? ""
|
API Key: User / Org ${
|
||||||
}</button>
|
data.displayName
|
||||||
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${data.apiKey
|
? `<button style="font-size: 18px;">${data.displayName}</button>`
|
||||||
}">
|
: ""
|
||||||
|
}
|
||||||
|
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${
|
||||||
|
data.apiKey
|
||||||
|
}">
|
||||||
<button id="loginButton" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;">
|
<button id="loginButton" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;">
|
||||||
${data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
|
${
|
||||||
}
|
data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
|
||||||
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1816,21 +1977,23 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
this.save();
|
this.save();
|
||||||
const data = getData();
|
const data = getData();
|
||||||
|
|
||||||
const uuid =
|
const uuid = crypto.randomUUID();
|
||||||
Math.random().toString(36).substring(2, 15) +
|
window.open(data.endpoint + "/auth/request/" + uuid, "_blank");
|
||||||
Math.random().toString(36).substring(2, 15);
|
|
||||||
window.open(data.endpoint + "/auth-request/" + uuid, "_blank");
|
|
||||||
|
|
||||||
this.timeout = setTimeout(() => {
|
this.timeout = setTimeout(() => {
|
||||||
clearInterval(poll);
|
clearInterval(this.poll);
|
||||||
infoDialog.showMessage(
|
infoDialog.showError(
|
||||||
"Timeout",
|
"Timeout",
|
||||||
"Wait too long for the response, please try re-login",
|
"Wait too long for the response, please try re-login"
|
||||||
);
|
);
|
||||||
}, 30000); // Stop polling after 30 seconds
|
}, 30000); // Stop polling after 30 seconds
|
||||||
|
|
||||||
this.poll = setInterval(() => {
|
this.poll = setInterval(() => {
|
||||||
fetch(data.endpoint + "/api/auth-response/" + uuid)
|
fetch(
|
||||||
|
`/comfyui-deploy/auth-response?request_id=${uuid}&api_url=${encodeURIComponent(
|
||||||
|
data.apiUrl
|
||||||
|
)}`
|
||||||
|
)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then(async (json) => {
|
.then(async (json) => {
|
||||||
if (json.api_key) {
|
if (json.api_key) {
|
||||||
@@ -1843,8 +2006,9 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
// Refresh dialog
|
// Refresh dialog
|
||||||
const a = await confirmDialog.confirm(
|
const a = await confirmDialog.confirm(
|
||||||
"Authenticated",
|
"Authenticated",
|
||||||
`<div>You will be able to upload workflow to <button style="font-size: 18px; width: fit;">${json.name}</button></div>`,
|
`<div>You will be able to upload workflow to <button style="font-size: 18px; width: fit;">${json.name}</button></div>`
|
||||||
);
|
);
|
||||||
|
|
||||||
configDialog.show();
|
configDialog.show();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1899,7 +2063,7 @@ if (isLocalhost) {
|
|||||||
title: "Deploy",
|
title: "Deploy",
|
||||||
tooltip: "Deploy and Configure",
|
tooltip: "Deploy and Configure",
|
||||||
type: "custom",
|
type: "custom",
|
||||||
render: (el) => {
|
render: async (el) => {
|
||||||
el.innerHTML = `
|
el.innerHTML = `
|
||||||
<div style="padding: 20px;">
|
<div style="padding: 20px;">
|
||||||
<h3>Comfy Deploy</h3>
|
<h3>Comfy Deploy</h3>
|
||||||
@@ -1964,7 +2128,12 @@ if (isLocalhost) {
|
|||||||
const workflowsList = el.querySelector("#workflows-list");
|
const workflowsList = el.querySelector("#workflows-list");
|
||||||
const workflowsLoading = el.querySelector("#workflows-loading");
|
const workflowsLoading = el.querySelector("#workflows-loading");
|
||||||
|
|
||||||
// refreshWorkflowsList(el);
|
// Initialize workflows list and search
|
||||||
|
const data = getData();
|
||||||
|
if (data.apiKey) {
|
||||||
|
addWorkflowSearch(el, getData, getTimeAgo);
|
||||||
|
await initializeWorkflowsList(el, getData, getTimeAgo);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2250,7 +2419,7 @@ function addMenuButtons(options) {
|
|||||||
|
|
||||||
// Remove any existing CD buttons
|
// Remove any existing CD buttons
|
||||||
const existingButtons = document.querySelectorAll(
|
const existingButtons = document.querySelectorAll(
|
||||||
`[id^="${buttonIdPrefix}"]`,
|
`[id^="${buttonIdPrefix}"]`
|
||||||
);
|
);
|
||||||
existingButtons.forEach((button) => button.remove());
|
existingButtons.forEach((button) => button.remove());
|
||||||
|
|
||||||
@@ -2347,3 +2516,34 @@ function addMenuRightButtons(buttonConfigs) {
|
|||||||
containerStyle: {},
|
containerStyle: {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to refresh workflow list if sidebar is open
|
||||||
|
async function refreshWorkflowListIfOpen() {
|
||||||
|
try {
|
||||||
|
const workflowsContainer = document.querySelector("#workflows-container");
|
||||||
|
const workflowsList = document.querySelector("#workflows-list");
|
||||||
|
|
||||||
|
// Only refresh if workflows container is visible and has been initialized
|
||||||
|
if (!workflowsContainer || !workflowsList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import the functions we need
|
||||||
|
const { initializeWorkflowsList } = await import("./workflow-list.js");
|
||||||
|
|
||||||
|
// Clear existing workflows and reset state safely
|
||||||
|
workflowsList.innerHTML = "";
|
||||||
|
if (window.workflowsState) {
|
||||||
|
window.workflowsState.initialized = false;
|
||||||
|
window.workflowsState.workflows = [];
|
||||||
|
window.workflowsState.offset = 0;
|
||||||
|
window.workflowsState.hasMore = true;
|
||||||
|
}
|
||||||
|
// Reinitialize the workflow list
|
||||||
|
await initializeWorkflowsList(document, getData, getTimeAgo);
|
||||||
|
|
||||||
|
console.log("Workflow list refreshed successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error refreshing workflow list:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,387 @@
|
|||||||
|
// Workflow list management
|
||||||
|
let workflowsState = {
|
||||||
|
workflows: [],
|
||||||
|
offset: 0,
|
||||||
|
limit: 20,
|
||||||
|
loading: false,
|
||||||
|
hasMore: true,
|
||||||
|
initialized: false,
|
||||||
|
currentSearch: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make workflowsState accessible globally
|
||||||
|
window.workflowsState = workflowsState;
|
||||||
|
|
||||||
|
async function fetchWorkflows(getData, offset = 0, limit = 20, search = "") {
|
||||||
|
try {
|
||||||
|
const data = getData();
|
||||||
|
if (!data.apiKey) {
|
||||||
|
throw new Error("API key not configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
offset: offset.toString(),
|
||||||
|
limit: limit.toString(),
|
||||||
|
api_url: data.apiUrl || "https://api.comfydeploy.com",
|
||||||
|
...(search && { search }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(`/comfyui-deploy/workflows?${params}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${data.apiKey}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch workflows: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
console.log("result", result);
|
||||||
|
return Array.isArray(result) ? result : [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching workflows:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWorkflowItem(workflow, getTimeAgo, getData) {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
li.style.cssText = `
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
background: transparent;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
|
li.addEventListener("mouseenter", () => {
|
||||||
|
li.style.background = "#333";
|
||||||
|
});
|
||||||
|
|
||||||
|
li.addEventListener("mouseleave", () => {
|
||||||
|
li.style.background = "transparent";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add click handler to fetch and load workflow data
|
||||||
|
li.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
const data = getData();
|
||||||
|
if (!data.apiKey) {
|
||||||
|
console.error("No API key configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading toast
|
||||||
|
const loadingToast = window.app.extensionManager.toast.add({
|
||||||
|
severity: "info",
|
||||||
|
summary: "Loading workflow...",
|
||||||
|
detail: `Loading "${workflow.name}"`,
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
workflow_id: workflow.id,
|
||||||
|
api_url: data.apiUrl || "https://api.comfydeploy.com",
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(`/comfyui-deploy/workflow?${params}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${data.apiKey}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch workflow: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflowData = await response.json();
|
||||||
|
console.log("Workflow data:", workflowData);
|
||||||
|
|
||||||
|
// Load the workflow into the graph
|
||||||
|
if (workflowData.versions && workflowData.versions.length > 0) {
|
||||||
|
const latestVersion = workflowData.versions[0];
|
||||||
|
if (latestVersion.workflow && window.app) {
|
||||||
|
// Load the workflow
|
||||||
|
window.app.loadGraphData(latestVersion.workflow);
|
||||||
|
|
||||||
|
// Show success toast
|
||||||
|
window.app.extensionManager.toast.add({
|
||||||
|
severity: "success",
|
||||||
|
summary: "Workflow loaded successfully",
|
||||||
|
detail: `Loaded "${workflow.name}" v${latestVersion.version}`,
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading workflow:", error);
|
||||||
|
// Show error toast
|
||||||
|
window.app.extensionManager.toast.add({
|
||||||
|
severity: "error",
|
||||||
|
summary: "Failed to load workflow",
|
||||||
|
detail: error.message,
|
||||||
|
life: 5000,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loadingToast.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedDate = new Date(workflow.updated_at);
|
||||||
|
const timeAgo = getTimeAgo(updatedDate);
|
||||||
|
|
||||||
|
li.innerHTML = `
|
||||||
|
<div style="padding: 12px 16px;">
|
||||||
|
<div style="display: flex; align-items: flex-start; gap: 12px;">
|
||||||
|
${
|
||||||
|
workflow.cover_image
|
||||||
|
? `<img src="${workflow.cover_image}"
|
||||||
|
style="width: 40px; height: 40px; border-radius: 4px; object-fit: cover; flex-shrink: 0;"
|
||||||
|
onerror="this.style.display='none'">`
|
||||||
|
: `<div style="width: 40px; height: 40px; border-radius: 4px; background: #444; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 14px; color: #888;">
|
||||||
|
${workflow.name.charAt(0).toUpperCase()}
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
<div style="flex: 1; min-width: 0;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
|
||||||
|
<h4 style="margin: 0; font-size: 14px; font-weight: 400; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||||
|
${workflow.name}
|
||||||
|
</h4>
|
||||||
|
${
|
||||||
|
workflow.pinned
|
||||||
|
? `<span style="color: #ffd700; font-size: 12px;">📌</span>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${
|
||||||
|
workflow.description
|
||||||
|
? `<p style="margin: 0 0 8px 0; font-size: 12px; color: #bbb; line-height: 1.3; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;">
|
||||||
|
${workflow.description}
|
||||||
|
</p>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px; margin-top: 8px;">
|
||||||
|
<img src="${workflow.user_icon}"
|
||||||
|
style="width: 16px; height: 16px; border-radius: 50%;"
|
||||||
|
onerror="this.style.display='none'">
|
||||||
|
<span style="font-size: 11px; color: #888;">
|
||||||
|
${workflow.user_name} • Updated ${timeAgo}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMoreWorkflows(element, getData, getTimeAgo) {
|
||||||
|
if (workflowsState.loading || !workflowsState.hasMore) return;
|
||||||
|
|
||||||
|
workflowsState.loading = true;
|
||||||
|
|
||||||
|
const workflowsList = element.querySelector("#workflows-list");
|
||||||
|
const workflowsLoading = element.querySelector("#workflows-loading");
|
||||||
|
|
||||||
|
// Show loading indicator
|
||||||
|
workflowsLoading.style.display = "flex";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newWorkflows = await fetchWorkflows(
|
||||||
|
getData,
|
||||||
|
workflowsState.offset,
|
||||||
|
workflowsState.limit,
|
||||||
|
workflowsState.currentSearch
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newWorkflows.length === 0) {
|
||||||
|
workflowsState.hasMore = false;
|
||||||
|
} else {
|
||||||
|
workflowsState.workflows.push(...newWorkflows);
|
||||||
|
workflowsState.offset += newWorkflows.length;
|
||||||
|
|
||||||
|
// Render new workflow items
|
||||||
|
newWorkflows.forEach((workflow) => {
|
||||||
|
const workflowItem = createWorkflowItem(workflow, getTimeAgo, getData);
|
||||||
|
workflowsList.appendChild(workflowItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading more workflows:", error);
|
||||||
|
} finally {
|
||||||
|
workflowsState.loading = false;
|
||||||
|
workflowsLoading.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupInfiniteScroll(container, element, getData, getTimeAgo) {
|
||||||
|
let isScrolling = false;
|
||||||
|
|
||||||
|
container.addEventListener("scroll", () => {
|
||||||
|
if (isScrolling) return;
|
||||||
|
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||||
|
|
||||||
|
// Load more when scrolled to bottom (with 100px threshold)
|
||||||
|
if (scrollTop + clientHeight >= scrollHeight - 100) {
|
||||||
|
isScrolling = true;
|
||||||
|
loadMoreWorkflows(element, getData, getTimeAgo).finally(() => {
|
||||||
|
isScrolling = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeWorkflowsList(element, getData, getTimeAgo) {
|
||||||
|
const workflowsContainer = element.querySelector("#workflows-container");
|
||||||
|
const workflowsList = element.querySelector("#workflows-list");
|
||||||
|
const workflowsLoading = element.querySelector("#workflows-loading");
|
||||||
|
|
||||||
|
// Check if already initialized AND the DOM elements still exist
|
||||||
|
if (
|
||||||
|
workflowsState.initialized &&
|
||||||
|
workflowsList &&
|
||||||
|
workflowsList.children.length > 0
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Reset state (always reset when reinitializing)
|
||||||
|
workflowsState = {
|
||||||
|
workflows: [],
|
||||||
|
offset: 0,
|
||||||
|
limit: 20,
|
||||||
|
loading: false,
|
||||||
|
hasMore: true,
|
||||||
|
initialized: true,
|
||||||
|
currentSearch: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear existing content in case of reinitialization
|
||||||
|
if (workflowsList) {
|
||||||
|
workflowsList.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show container and loading
|
||||||
|
workflowsContainer.style.display = "block";
|
||||||
|
workflowsLoading.style.display = "flex";
|
||||||
|
|
||||||
|
// Style the workflows list for full height scrolling
|
||||||
|
workflowsList.style.cssText = `
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: calc(100vh - 350px);
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #666 transparent;
|
||||||
|
border-top: 1px solid #444;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add webkit scrollbar styles
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.textContent = `
|
||||||
|
#workflows-list::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
#workflows-list::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
#workflows-list::-webkit-scrollbar-thumb {
|
||||||
|
background: #666;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
#workflows-list::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #777;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
// Setup infinite scroll
|
||||||
|
setupInfiniteScroll(workflowsList, element, getData, getTimeAgo);
|
||||||
|
|
||||||
|
// Load initial workflows
|
||||||
|
await loadMoreWorkflows(element, getData, getTimeAgo);
|
||||||
|
|
||||||
|
// Show the list
|
||||||
|
workflowsList.style.display = "block";
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing workflows list:", error);
|
||||||
|
workflowsLoading.innerHTML = `
|
||||||
|
<div style="text-align: center; color: #e74c3c; font-size: 12px; padding: 20px;">
|
||||||
|
<div>Failed to load workflows</div>
|
||||||
|
<button onclick="initializeWorkflowsList(this.closest('.comfy-menu'), getData, getTimeAgo)"
|
||||||
|
style="margin-top: 8px; padding: 4px 8px; font-size: 11px; background: #f0f0f0; border: 1px solid #ccc; border-radius: 4px; cursor: pointer;">
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search functionality
|
||||||
|
function addWorkflowSearch(element, getData, getTimeAgo) {
|
||||||
|
const workflowsContainer = element.querySelector("#workflows-container");
|
||||||
|
const h4 = workflowsContainer.querySelector("h4");
|
||||||
|
|
||||||
|
const searchContainer = document.createElement("div");
|
||||||
|
searchContainer.style.cssText = "margin-bottom: 12px;";
|
||||||
|
|
||||||
|
const searchInput = document.createElement("input");
|
||||||
|
searchInput.type = "text";
|
||||||
|
searchInput.placeholder = "Search workflows...";
|
||||||
|
searchInput.style.cssText = `
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #333;
|
||||||
|
color: #fff;
|
||||||
|
`;
|
||||||
|
|
||||||
|
let searchTimeout;
|
||||||
|
searchInput.addEventListener("input", (e) => {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
searchTimeout = setTimeout(async () => {
|
||||||
|
const searchTerm = e.target.value.trim();
|
||||||
|
|
||||||
|
// Update the tracked search term
|
||||||
|
workflowsState.currentSearch = searchTerm;
|
||||||
|
|
||||||
|
// Reset state for new search
|
||||||
|
workflowsState.workflows = [];
|
||||||
|
workflowsState.offset = 0;
|
||||||
|
workflowsState.hasMore = true;
|
||||||
|
|
||||||
|
// Clear current list
|
||||||
|
const workflowsList = element.querySelector("#workflows-list");
|
||||||
|
workflowsList.innerHTML = "";
|
||||||
|
|
||||||
|
// Load with search term
|
||||||
|
workflowsState.loading = false;
|
||||||
|
await loadMoreWorkflows(element, getData, getTimeAgo);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
searchContainer.appendChild(searchInput);
|
||||||
|
h4.after(searchContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export the functions
|
||||||
|
export {
|
||||||
|
initializeWorkflowsList,
|
||||||
|
addWorkflowSearch,
|
||||||
|
workflowsState,
|
||||||
|
fetchWorkflows,
|
||||||
|
loadMoreWorkflows,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user