feat: support new frontend!
This commit is contained in:
parent
f6ea252652
commit
10268825d9
@ -17,12 +17,13 @@ from urllib.parse import quote
|
|||||||
import threading
|
import threading
|
||||||
import hashlib
|
import hashlib
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
from aiohttp import ClientSession, web
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from typing import Dict, List, Union, Any, Optional
|
from typing import Dict, List, Union, Any, Optional
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import copy
|
import copy
|
||||||
import struct
|
import struct
|
||||||
from aiohttp import ClientError
|
from aiohttp import web, ClientSession, ClientError
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
# Global session
|
# Global session
|
||||||
@ -836,6 +837,50 @@ async def send(event, data, sid=None):
|
|||||||
logger.info(f"Exception: {e}")
|
logger.info(f"Exception: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
@server.PromptServer.instance.routes.get('/comfydeploy/{tail:.*}')
|
||||||
|
@server.PromptServer.instance.routes.post('/comfydeploy/{tail:.*}')
|
||||||
|
async def proxy_to_comfydeploy(request):
|
||||||
|
# Get the base URL
|
||||||
|
base_url = f'https://www.comfydeploy.com/{request.match_info["tail"]}'
|
||||||
|
|
||||||
|
# Get all query parameters
|
||||||
|
query_params = request.query_string
|
||||||
|
|
||||||
|
# Construct the full target URL with query parameters
|
||||||
|
target_url = f"{base_url}?{query_params}" if query_params else base_url
|
||||||
|
|
||||||
|
print(f"Proxying request to: {target_url}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a new ClientSession for each request
|
||||||
|
async with ClientSession() as client_session:
|
||||||
|
# Forward the request
|
||||||
|
client_req = await client_session.request(
|
||||||
|
method=request.method,
|
||||||
|
url=target_url,
|
||||||
|
headers={k: v for k, v in request.headers.items() if k.lower() not in ('host', 'content-length')},
|
||||||
|
data=await request.read(),
|
||||||
|
allow_redirects=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read the entire response content
|
||||||
|
content = await client_req.read()
|
||||||
|
|
||||||
|
# Try to decode the content as JSON
|
||||||
|
try:
|
||||||
|
json_data = json.loads(content)
|
||||||
|
# If successful, return a JSON response
|
||||||
|
return web.json_response(json_data, status=client_req.status)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# If it's not valid JSON, return the content as-is
|
||||||
|
return web.Response(body=content, status=client_req.status, headers=client_req.headers)
|
||||||
|
|
||||||
|
except ClientError as e:
|
||||||
|
print(f"Client error occurred while proxying request: {str(e)}")
|
||||||
|
return web.Response(status=502, text=f"Bad Gateway: {str(e)}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error occurred while proxying request: {str(e)}")
|
||||||
|
return web.Response(status=500, text=f"Internal Server Error: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
prompt_server = server.PromptServer.instance
|
prompt_server = server.PromptServer.instance
|
||||||
|
@ -2,4 +2,5 @@ aiofiles
|
|||||||
pydantic
|
pydantic
|
||||||
opencv-python
|
opencv-python
|
||||||
imageio-ffmpeg
|
imageio-ffmpeg
|
||||||
|
brotli
|
||||||
# logfire
|
# logfire
|
@ -2,6 +2,7 @@ import { app } from "./app.js";
|
|||||||
import { api } from "./api.js";
|
import { api } from "./api.js";
|
||||||
import { ComfyWidgets, LGraphNode } from "./widgets.js";
|
import { ComfyWidgets, LGraphNode } from "./widgets.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@0.0.19-beta.30";
|
||||||
|
|
||||||
const loadingIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg>`;
|
const loadingIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="#888888" stroke-linecap="round" stroke-width="2"><path stroke-dasharray="60" stroke-dashoffset="60" stroke-opacity=".3" d="M12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3Z"><animate fill="freeze" attributeName="stroke-dashoffset" dur="1.3s" values="60;0"/></path><path stroke-dasharray="15" stroke-dashoffset="15" d="M12 3C16.9706 3 21 7.02944 21 12"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.3s" values="15;0"/><animateTransform attributeName="transform" dur="1.5s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></g></svg>`;
|
||||||
|
|
||||||
@ -24,10 +25,7 @@ function dispatchAPIEventData(data) {
|
|||||||
message += "\n" + nodeError.class_type + ":";
|
message += "\n" + nodeError.class_type + ":";
|
||||||
for (const errorReason of nodeError.errors) {
|
for (const errorReason of nodeError.errors) {
|
||||||
message +=
|
message +=
|
||||||
"\n - " +
|
"\n - " + errorReason.message + ": " + errorReason.details;
|
||||||
errorReason.message +
|
|
||||||
": " +
|
|
||||||
errorReason.details;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,38 +45,32 @@ function dispatchAPIEventData(data) {
|
|||||||
// window.name = this.clientId; // use window name so it isnt reused when duplicating tabs
|
// window.name = this.clientId; // use window name so it isnt reused when duplicating tabs
|
||||||
// sessionStorage.setItem("clientId", this.clientId); // store in session storage so duplicate tab can load correct workflow
|
// sessionStorage.setItem("clientId", this.clientId); // store in session storage so duplicate tab can load correct workflow
|
||||||
}
|
}
|
||||||
api.dispatchEvent(
|
api.dispatchEvent(new CustomEvent("status", { detail: msg.data.status }));
|
||||||
new CustomEvent("status", { detail: msg.data.status })
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case "progress":
|
case "progress":
|
||||||
api.dispatchEvent(
|
api.dispatchEvent(new CustomEvent("progress", { detail: msg.data }));
|
||||||
new CustomEvent("progress", { detail: msg.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":
|
||||||
api.dispatchEvent(
|
api.dispatchEvent(new CustomEvent("executed", { detail: msg.data }));
|
||||||
new CustomEvent("executed", { detail: msg.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:
|
||||||
@ -152,13 +144,11 @@ const ext = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!workflow_version_id) {
|
if (!workflow_version_id) {
|
||||||
console.error(
|
console.error("No workflow_version_id provided in query parameters.");
|
||||||
"No workflow_version_id provided in query parameters."
|
|
||||||
);
|
|
||||||
} 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",
|
||||||
@ -171,10 +161,7 @@ const ext = {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const { workflow, workflow_id, error } = data;
|
const { workflow, workflow_id, error } = data;
|
||||||
if (error) {
|
if (error) {
|
||||||
infoDialog.showMessage(
|
infoDialog.showMessage("Unable to load this workflow", error);
|
||||||
"Unable to load this workflow",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +184,7 @@ const ext = {
|
|||||||
window.history.replaceState(
|
window.history.replaceState(
|
||||||
{},
|
{},
|
||||||
document.title,
|
document.title,
|
||||||
window.location.pathname
|
window.location.pathname,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -227,7 +214,7 @@ const ext = {
|
|||||||
multiline: false,
|
multiline: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
app
|
app,
|
||||||
);
|
);
|
||||||
|
|
||||||
ComfyWidgets.STRING(
|
ComfyWidgets.STRING(
|
||||||
@ -240,17 +227,14 @@ const ext = {
|
|||||||
multiline: false,
|
multiline: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
app
|
app,
|
||||||
);
|
);
|
||||||
|
|
||||||
ComfyWidgets.STRING(
|
ComfyWidgets.STRING(
|
||||||
this,
|
this,
|
||||||
"version",
|
"version",
|
||||||
[
|
["", { default: this.properties.version, multiline: false }],
|
||||||
"",
|
app,
|
||||||
{ default: this.properties.version, multiline: false },
|
|
||||||
],
|
|
||||||
app
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// this.widgets.forEach((w) => {
|
// this.widgets.forEach((w) => {
|
||||||
@ -277,7 +261,7 @@ const ext = {
|
|||||||
title_mode: LiteGraph.NORMAL_TITLE,
|
title_mode: LiteGraph.NORMAL_TITLE,
|
||||||
title: "Comfy Deploy",
|
title: "Comfy Deploy",
|
||||||
collapsable: true,
|
collapsable: true,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
ComfyDeploy.category = "deploy";
|
ComfyDeploy.category = "deploy";
|
||||||
@ -310,9 +294,7 @@ const ext = {
|
|||||||
if (typeof api.handlePromptGenerated === "function") {
|
if (typeof api.handlePromptGenerated === "function") {
|
||||||
api.handlePromptGenerated(prompt);
|
api.handlePromptGenerated(prompt);
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn("api.handlePromptGenerated is not a function");
|
||||||
"api.handlePromptGenerated is not a function"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
sendEventToCD("cd_plugin_onQueuePrompt", prompt);
|
sendEventToCD("cd_plugin_onQueuePrompt", prompt);
|
||||||
} else if (message.type === "get_prompt") {
|
} else if (message.type === "get_prompt") {
|
||||||
@ -346,13 +328,9 @@ const ext = {
|
|||||||
const canvas = app.canvas;
|
const canvas = app.canvas;
|
||||||
const targetScale = 1;
|
const targetScale = 1;
|
||||||
const targetOffsetX =
|
const targetOffsetX =
|
||||||
canvas.canvas.width / 4 -
|
canvas.canvas.width / 4 - position[0] - node.size[0] / 2;
|
||||||
position[0] -
|
|
||||||
node.size[0] / 2;
|
|
||||||
const targetOffsetY =
|
const targetOffsetY =
|
||||||
canvas.canvas.height / 4 -
|
canvas.canvas.height / 4 - position[1] - node.size[1] / 2;
|
||||||
position[1] -
|
|
||||||
node.size[1] / 2;
|
|
||||||
|
|
||||||
const startScale = canvas.ds.scale;
|
const startScale = canvas.ds.scale;
|
||||||
const startOffsetX = canvas.ds.offset[0];
|
const startOffsetX = canvas.ds.offset[0];
|
||||||
@ -376,21 +354,9 @@ const ext = {
|
|||||||
|
|
||||||
const easedT = easeOutCubic(t);
|
const easedT = easeOutCubic(t);
|
||||||
|
|
||||||
const currentScale = lerp(
|
const currentScale = lerp(startScale, targetScale, easedT);
|
||||||
startScale,
|
const currentOffsetX = lerp(startOffsetX, targetOffsetX, easedT);
|
||||||
targetScale,
|
const currentOffsetY = lerp(startOffsetY, targetOffsetY, easedT);
|
||||||
easedT
|
|
||||||
);
|
|
||||||
const currentOffsetX = lerp(
|
|
||||||
startOffsetX,
|
|
||||||
targetOffsetX,
|
|
||||||
easedT
|
|
||||||
);
|
|
||||||
const currentOffsetY = lerp(
|
|
||||||
startOffsetY,
|
|
||||||
targetOffsetY,
|
|
||||||
easedT
|
|
||||||
);
|
|
||||||
|
|
||||||
canvas.setZoom(currentScale);
|
canvas.setZoom(currentScale);
|
||||||
canvas.ds.offset = [currentOffsetX, currentOffsetY];
|
canvas.ds.offset = [currentOffsetX, currentOffsetY];
|
||||||
@ -442,7 +408,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> `,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,7 +516,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);
|
||||||
@ -591,7 +557,7 @@ async function deployWorkflow() {
|
|||||||
<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;
|
||||||
|
|
||||||
@ -610,7 +576,7 @@ async function deployWorkflow() {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@ -631,7 +597,7 @@ async function deployWorkflow() {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: "Bearer " + apiKey,
|
Authorization: "Bearer " + apiKey,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.then((x) => x.json())
|
.then((x) => x.json())
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -650,7 +616,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,
|
||||||
@ -672,44 +638,36 @@ 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(
|
`/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent(file)}`,
|
||||||
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);
|
||||||
return hash.file_hash;
|
return hash.file_hash;
|
||||||
},
|
},
|
||||||
handleFileUpload: async (file, hash, prevhash) => {
|
// handleFileUpload: async (file, hash, prevhash) => {
|
||||||
console.log("Uploading ", file);
|
// console.log("Uploading ", file);
|
||||||
loadingDialog.showLoading("Uploading file", file);
|
// loadingDialog.showLoading("Uploading file", file);
|
||||||
try {
|
// try {
|
||||||
const { download_url } = await fetch(
|
// const { download_url } = await fetch(`/comfyui-deploy/upload-file`, {
|
||||||
`/comfyui-deploy/upload-file`,
|
// method: "POST",
|
||||||
{
|
// body: JSON.stringify({
|
||||||
method: "POST",
|
// file_path: file,
|
||||||
body: JSON.stringify({
|
// token: apiKey,
|
||||||
file_path: file,
|
// url: endpoint + "/api/upload-url",
|
||||||
token: apiKey,
|
// }),
|
||||||
url: endpoint + "/api/upload-url",
|
// })
|
||||||
}),
|
// .then((x) => x.json())
|
||||||
}
|
// .catch(() => {
|
||||||
)
|
// loadingDialog.close();
|
||||||
.then((x) => x.json())
|
// confirmDialog.confirm("Error", "Unable to upload file " + file);
|
||||||
.catch(() => {
|
// });
|
||||||
loadingDialog.close();
|
// loadingDialog.showLoading("Uploaded file", file);
|
||||||
confirmDialog.confirm(
|
// console.log(download_url);
|
||||||
"Error",
|
// return download_url;
|
||||||
"Unable to upload file " + file
|
// } catch (error) {
|
||||||
);
|
// return undefined;
|
||||||
});
|
// }
|
||||||
loadingDialog.showLoading("Uploaded file", file);
|
// },
|
||||||
console.log(download_url);
|
|
||||||
return download_url;
|
|
||||||
} catch (error) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
existingDependencies: existing_workflow.dependencies,
|
existingDependencies: existing_workflow.dependencies,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -734,12 +692,21 @@ async function deployWorkflow() {
|
|||||||
"Check dependencies",
|
"Check dependencies",
|
||||||
// JSON.stringify(deps, null, 2),
|
// JSON.stringify(deps, null, 2),
|
||||||
`
|
`
|
||||||
|
<div>
|
||||||
|
You will need to create a cloud machine with the following configuration on ComfyDeploy
|
||||||
|
<ol style="text-align: left; margin-top: 10px;">
|
||||||
|
<li>Review the dependencies listed in the graph below</li>
|
||||||
|
<li>Create a new cloud machine with the required configuration</li>
|
||||||
|
<li>Install missing models and check missing files</li>
|
||||||
|
<li>Deploy your workflow to the newly created machine</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">${loadingIcon}</div>
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">${loadingIcon}</div>
|
||||||
<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;
|
if (!depsOk) return;
|
||||||
@ -800,7 +767,7 @@ async function deployWorkflow() {
|
|||||||
graph.change();
|
graph.change();
|
||||||
|
|
||||||
infoDialog.show(
|
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: ${data.version} <br/>`
|
`<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/>`,
|
||||||
);
|
);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -997,22 +964,17 @@ export class InputDialog extends InfoDialog {
|
|||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Save",
|
textContent: "Save",
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
const input =
|
const input = this.textElement.querySelector("#input").value;
|
||||||
this.textElement.querySelector("#input").value;
|
|
||||||
if (input.trim() === "") {
|
if (input.trim() === "") {
|
||||||
showError(
|
showError("Input validation", "Input cannot be empty");
|
||||||
"Input validation",
|
|
||||||
"Input cannot be empty"
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.callback?.(input);
|
this.callback?.(input);
|
||||||
this.close();
|
this.close();
|
||||||
this.textElement.querySelector("#input").value =
|
this.textElement.querySelector("#input").value = "";
|
||||||
"";
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]
|
],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -1073,7 +1035,7 @@ export class ConfirmDialog extends InfoDialog {
|
|||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]
|
],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -1130,7 +1092,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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1145,9 +1107,7 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
this.element.style.paddingBottom = "20px";
|
this.element.style.paddingBottom = "20px";
|
||||||
|
|
||||||
this.container = document.createElement("div");
|
this.container = document.createElement("div");
|
||||||
this.element
|
this.element.querySelector(".comfy-modal-content").prepend(this.container);
|
||||||
.querySelector(".comfy-modal-content")
|
|
||||||
.prepend(this.container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createButtons() {
|
createButtons() {
|
||||||
@ -1181,7 +1141,7 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]
|
],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -1193,8 +1153,7 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save(api_key, displayName) {
|
save(api_key, displayName) {
|
||||||
const deployOption =
|
const deployOption = this.container.querySelector("#deployOption").value;
|
||||||
this.container.querySelector("#deployOption").value;
|
|
||||||
localStorage.setItem("comfy_deploy_env", deployOption);
|
localStorage.setItem("comfy_deploy_env", deployOption);
|
||||||
|
|
||||||
const endpoint = this.container.querySelector("#endpoint").value;
|
const endpoint = this.container.querySelector("#endpoint").value;
|
||||||
@ -1226,12 +1185,8 @@ 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" ${
|
<option value="cloud" ${data.environment === "cloud" ? "selected" : ""}>Cloud</option>
|
||||||
data.environment === "cloud" ? "selected" : ""
|
<option value="local" ${data.environment === "local" ? "selected" : ""}>Local</option>
|
||||||
}>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%;">
|
||||||
@ -1249,9 +1204,7 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
}">
|
}">
|
||||||
<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
|
data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
|
||||||
? "Re-login with ComfyDeploy"
|
|
||||||
: "Login with ComfyDeploy"
|
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -1272,7 +1225,7 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
clearInterval(poll);
|
clearInterval(poll);
|
||||||
infoDialog.showMessage(
|
infoDialog.showMessage(
|
||||||
"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
|
||||||
|
|
||||||
@ -1283,15 +1236,14 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
if (json.api_key) {
|
if (json.api_key) {
|
||||||
this.save(json.api_key, json.name);
|
this.save(json.api_key, json.name);
|
||||||
this.close();
|
this.close();
|
||||||
this.container.querySelector("#apiKey").value =
|
this.container.querySelector("#apiKey").value = json.api_key;
|
||||||
json.api_key;
|
|
||||||
// infoDialog.show();
|
// infoDialog.show();
|
||||||
clearInterval(this.poll);
|
clearInterval(this.poll);
|
||||||
clearTimeout(this.timeout);
|
clearTimeout(this.timeout);
|
||||||
// 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();
|
||||||
}
|
}
|
||||||
@ -1327,3 +1279,167 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const configDialog = new ConfigDialog();
|
export const configDialog = new ConfigDialog();
|
||||||
|
|
||||||
|
const currentOrigin = window.location.origin;
|
||||||
|
const client = new ComfyDeploy({
|
||||||
|
bearerAuth: getData().apiKey,
|
||||||
|
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">
|
||||||
|
<h4>Your Workflows</h4>
|
||||||
|
<ul id="workflows-list" style="list-style-type: none; padding: 0;"></ul>
|
||||||
|
</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();
|
||||||
|
};
|
||||||
|
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");
|
||||||
|
client.workflows
|
||||||
|
.getAll({
|
||||||
|
page: "1",
|
||||||
|
pageSize: "10",
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
result.forEach((workflow) => {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
li.style.marginBottom = "15px";
|
||||||
|
li.style.padding = "15px";
|
||||||
|
li.style.backgroundColor = "#2a2a2a";
|
||||||
|
li.style.borderRadius = "8px";
|
||||||
|
li.style.boxShadow = "0 2px 4px rgba(0,0,0,0.1)";
|
||||||
|
|
||||||
|
const lastRun = workflow.runs[0];
|
||||||
|
const lastRunStatus = lastRun ? lastRun.status : "No runs";
|
||||||
|
const statusColor =
|
||||||
|
lastRunStatus === "success"
|
||||||
|
? "#4CAF50"
|
||||||
|
: lastRunStatus === "error"
|
||||||
|
? "#F44336"
|
||||||
|
: "#FFC107";
|
||||||
|
|
||||||
|
const timeAgo = getTimeAgo(new Date(workflow.updatedAt));
|
||||||
|
|
||||||
|
li.innerHTML = `
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
||||||
|
<div style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||||
|
<strong style="font-size: 18px; color: #e0e0e0;">${workflow.name}</strong>
|
||||||
|
</div>
|
||||||
|
<span style="font-size: 12px; color: ${statusColor}; margin-left: 10px;">Last run: ${lastRunStatus}</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 14px; color: #bdbdbd; margin-bottom: 10px;">Last updated ${timeAgo}</div>
|
||||||
|
<div style="display: flex; gap: 10px;">
|
||||||
|
<button class="open-cloud-btn" style="padding: 5px 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">Open in Cloud</button>
|
||||||
|
<button class="load-api-btn" style="padding: 5px 10px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;">Load Workflow</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const openCloudBtn = li.querySelector(".open-cloud-btn");
|
||||||
|
openCloudBtn.onclick = () =>
|
||||||
|
window.open(
|
||||||
|
`${getData().endpoint}/workflows/${workflow.id}?workspace=true`,
|
||||||
|
"_blank",
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadApiBtn = li.querySelector(".load-api-btn");
|
||||||
|
loadApiBtn.onclick = () => loadWorkflowApi(workflow.versions[0].id);
|
||||||
|
|
||||||
|
workflowsList.appendChild(li);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error fetching workflows:", error);
|
||||||
|
workflowsList.innerHTML =
|
||||||
|
"<li style='color: #F44336;'>Error fetching workflows</li>";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function getTimeAgo(date) {
|
||||||
|
const seconds = Math.floor((new Date() - date) / 1000);
|
||||||
|
let interval = seconds / 31536000;
|
||||||
|
if (interval > 1) return Math.floor(interval) + " years ago";
|
||||||
|
interval = seconds / 2592000;
|
||||||
|
if (interval > 1) return Math.floor(interval) + " months ago";
|
||||||
|
interval = seconds / 86400;
|
||||||
|
if (interval > 1) return Math.floor(interval) + " days ago";
|
||||||
|
interval = seconds / 3600;
|
||||||
|
if (interval > 1) return Math.floor(interval) + " hours ago";
|
||||||
|
interval = seconds / 60;
|
||||||
|
if (interval > 1) return Math.floor(interval) + " minutes ago";
|
||||||
|
return Math.floor(seconds) + " seconds ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWorkflowApi(versionId) {
|
||||||
|
try {
|
||||||
|
const response = await client.comfyui.getWorkflowVersionVersionId({
|
||||||
|
versionId: versionId,
|
||||||
|
});
|
||||||
|
// Implement the logic to load the workflow API into the ComfyUI interface
|
||||||
|
console.log("Workflow API loaded:", response);
|
||||||
|
await window["app"].ui.settings.setSettingValueAsync(
|
||||||
|
"Comfy.Validation.Workflows",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
app.loadGraphData(response.workflow);
|
||||||
|
// You might want to update the UI or trigger some action in ComfyUI here
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading workflow API:", error);
|
||||||
|
// Show an error message to the user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user