feat: introduce dependencies upload
This commit is contained in:
+178
-20
@@ -1,6 +1,7 @@
|
||||
import { app } from "./app.js";
|
||||
import { api } from "./api.js";
|
||||
import { ComfyWidgets, LGraphNode } from "./widgets.js";
|
||||
import { generateDependencyGraph } from "https://esm.sh/[email protected]";
|
||||
|
||||
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
|
||||
/** @type {ComfyExtension} */
|
||||
@@ -178,6 +179,71 @@ function showError(title, message) {
|
||||
);
|
||||
}
|
||||
|
||||
function createDynamicUIHtml(data) {
|
||||
let html =
|
||||
'<div style="max-width: 1024px; margin: 14px auto; display: flex; flex-direction: column; gap: 24px;">';
|
||||
const bgcolor = "var(--comfy-input-bg)";
|
||||
const textColor = "var(--input-text)";
|
||||
|
||||
// Custom Nodes
|
||||
html += `<div style="background-color: ${bgcolor}; padding: 24px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">`;
|
||||
html +=
|
||||
'<h2 style="margin-top: 0px; font-size: 24px; font-weight: bold; margin-bottom: 16px;">Custom Nodes</h2>';
|
||||
Object.values(data.custom_nodes).forEach((node) => {
|
||||
html += `
|
||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 16px;">
|
||||
<a href="${
|
||||
node.url
|
||||
}" 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>
|
||||
${
|
||||
node.warning
|
||||
? `<p style="font-size: 14px; color: #d69e2e;">${node.warning}</p>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
html += "</div>";
|
||||
|
||||
// Models
|
||||
html += `<div style="background-color: ${bgcolor}; padding: 24px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">`;
|
||||
html +=
|
||||
'<h2 style="margin-top: 0px; font-size: 24px; font-weight: bold; margin-bottom: 16px;">Models</h2>';
|
||||
const modelSections = ["checkpoints", "ipadapter", "clip_vision"];
|
||||
modelSections.forEach((section) => {
|
||||
html += `
|
||||
<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>
|
||||
`;
|
||||
data.models[section].forEach((item) => {
|
||||
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
||||
});
|
||||
html += "</div>";
|
||||
});
|
||||
html += "</div>";
|
||||
|
||||
// Files
|
||||
html += `<div style="background-color: ${bgcolor}; padding: 24px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">`;
|
||||
html +=
|
||||
'<h2 style="margin-top: 0px; font-size: 24px; font-weight: bold; margin-bottom: 16px;">Files</h2>';
|
||||
html += `
|
||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
||||
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">Images</h3>
|
||||
`;
|
||||
data.files.images.forEach((image) => {
|
||||
html += `<p style="font-size: 14px; color: ${textColor};">${image.name}</p>`;
|
||||
});
|
||||
html += "</div></div>";
|
||||
|
||||
html += "</div>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function addButton() {
|
||||
const menu = document.querySelector(".comfy-menu");
|
||||
|
||||
@@ -189,8 +255,29 @@ function addButton() {
|
||||
/** @type {LGraph} */
|
||||
const graph = app.graph;
|
||||
|
||||
let { endpoint, apiKey, displayName } = getData();
|
||||
|
||||
if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
|
||||
configDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
const ok = await confirmDialog.confirm(
|
||||
"Confirm deployment -> " + displayName,
|
||||
`A new version will be deployed, are you conform? <br><br><input id="include-deps" type="checkbox" checked>Include dependence</input>`,
|
||||
);
|
||||
if (!ok) return;
|
||||
|
||||
const includeDeps = document.getElementById("include-deps").checked;
|
||||
|
||||
if (endpoint.endsWith("/")) {
|
||||
endpoint = endpoint.slice(0, -1);
|
||||
}
|
||||
loadingDialog.showLoading("Generating snapshot", "Please wait...");
|
||||
|
||||
const snapshot = await fetch("/snapshot/get_current").then((x) => x.json());
|
||||
// console.log(snapshot);
|
||||
loadingDialog.close();
|
||||
|
||||
if (!snapshot) {
|
||||
showError(
|
||||
@@ -224,32 +311,94 @@ function addButton() {
|
||||
|
||||
const deployMetaNode = deployMeta[0];
|
||||
|
||||
// console.log(deployMetaNode);
|
||||
|
||||
const workflow_name = deployMetaNode.widgets[0].value;
|
||||
const workflow_id = deployMetaNode.widgets[1].value;
|
||||
|
||||
console.log(workflow_name, workflow_id);
|
||||
|
||||
const prompt = await app.graphToPrompt();
|
||||
console.log(graph);
|
||||
console.log(prompt);
|
||||
let deps = undefined;
|
||||
|
||||
// const endpoint = localStorage.getItem("endpoint") ?? "";
|
||||
// const apiKey = localStorage.getItem("apiKey");
|
||||
if (includeDeps) {
|
||||
loadingDialog.showLoading("Fetching existing version", "Please wait...");
|
||||
|
||||
const { endpoint, apiKey, displayName } = getData();
|
||||
const existing_workflow = await fetch(
|
||||
endpoint + "/api/workflow/" + workflow_id,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + apiKey,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.catch(() => {
|
||||
return {};
|
||||
});
|
||||
|
||||
if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
|
||||
configDialog.show();
|
||||
return;
|
||||
loadingDialog.close();
|
||||
|
||||
loadingDialog.showLoading(
|
||||
"Generating dependency graph",
|
||||
"Please wait...",
|
||||
);
|
||||
deps = await generateDependencyGraph(
|
||||
prompt.output,
|
||||
snapshot,
|
||||
async (file) => {
|
||||
console.log(file);
|
||||
loadingDialog.showLoading("Generating hash", file);
|
||||
const hash = await fetch(
|
||||
`/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent(
|
||||
file,
|
||||
)}`,
|
||||
).then((x) => x.json());
|
||||
loadingDialog.showLoading("Generating hash", file);
|
||||
console.log(hash);
|
||||
return hash.file_hash;
|
||||
},
|
||||
async (file, hash, prevhash) => {
|
||||
console.log("Uploading ", file);
|
||||
loadingDialog.showLoading("Uploading file", file);
|
||||
try {
|
||||
const { download_url } = await fetch(
|
||||
`/comfyui-deploy/upload-file`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
file_path: file,
|
||||
token: apiKey,
|
||||
url: endpoint + "/api/upload-url",
|
||||
}),
|
||||
},
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.catch(() => {
|
||||
loadingDialog.close();
|
||||
confirmDialog.confirm("Error", "Unable to upload file " + file);
|
||||
});
|
||||
loadingDialog.showLoading("Uploaded file", file);
|
||||
console.log(download_url);
|
||||
return download_url;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
existing_workflow.dependencies,
|
||||
);
|
||||
|
||||
loadingDialog.close();
|
||||
|
||||
const depsOk = await confirmDialog.confirm(
|
||||
"Check dependencies",
|
||||
// JSON.stringify(deps, null, 2),
|
||||
createDynamicUIHtml(deps),
|
||||
);
|
||||
if (!depsOk) return;
|
||||
|
||||
console.log(deps);
|
||||
}
|
||||
|
||||
const ok = await confirmDialog.confirm(
|
||||
"Confirm deployment -> " + displayName,
|
||||
"A new version will be deployed, are you conform?",
|
||||
);
|
||||
if (!ok) return;
|
||||
loadingDialog.showLoading("Deploying...");
|
||||
|
||||
title.innerText = "Deploying...";
|
||||
title.style.color = "orange";
|
||||
@@ -261,6 +410,8 @@ function addButton() {
|
||||
endpoint = endpoint.slice(0, -1);
|
||||
}
|
||||
|
||||
// console.log(prompt.workflow);
|
||||
|
||||
const apiRoute = endpoint + "/api/workflow";
|
||||
// const userId = apiKey
|
||||
try {
|
||||
@@ -270,6 +421,7 @@ function addButton() {
|
||||
workflow: prompt.workflow,
|
||||
workflow_api: prompt.output,
|
||||
snapshot: snapshot,
|
||||
dependencies: deps,
|
||||
};
|
||||
console.log(body);
|
||||
let data = await fetch(apiRoute, {
|
||||
@@ -289,6 +441,8 @@ function addButton() {
|
||||
data = await data.json();
|
||||
}
|
||||
|
||||
loadingDialog.close();
|
||||
|
||||
title.textContent = "Done";
|
||||
title.style.color = "green";
|
||||
|
||||
@@ -305,6 +459,7 @@ function addButton() {
|
||||
title.style.color = "white";
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
loadingDialog.close();
|
||||
app.ui.dialog.show(e);
|
||||
console.error(e);
|
||||
title.textContent = "Error";
|
||||
@@ -381,7 +536,7 @@ export class InfoDialog extends ComfyDialog {
|
||||
|
||||
showMessage(title, message) {
|
||||
this.show(`
|
||||
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||
<div style="width: 100%; max-width: 600px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||
<h3 style="margin: 0px;">${title}</h3>
|
||||
<label>
|
||||
${message}
|
||||
@@ -437,7 +592,10 @@ export class LoadingDialog extends ComfyDialog {
|
||||
showLoading(title, message) {
|
||||
this.show(`
|
||||
<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: 4px;">${title} ${this.loadingIcon}</h3>
|
||||
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${
|
||||
this.loadingIcon
|
||||
}</h3>
|
||||
${message ? `<label>${message}</label>` : ""}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
@@ -556,7 +714,7 @@ export class ConfirmDialog extends InfoDialog {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.callback = resolve;
|
||||
this.show(`
|
||||
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||
<div style="width: 100%; max-width: 600px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||
<h3 style="margin: 0px;">${title}</h3>
|
||||
<label>
|
||||
${message}
|
||||
|
||||
Reference in New Issue
Block a user