Compare commits

...
Author SHA1 Message Date
KarrixLee 3ed9729960 chore: version to 2.3.0 2025-08-21 18:37:30 +08:00
KarrixLee 9147ddd2e1 tweak machine display 2025-08-21 04:09:03 +08:00
KarrixLee 4c4fcc09c0 small tweak 2025-08-21 03:50:43 +08:00
KarrixLee dee5c9e071 refactor: improve snapshot fetching and UI adjustments
- Introduced centralized snapshot fetching utilities in snapshot-utils.js, including a fallback for ComfyUI version retrieval.
- Updated machine-manager.js to utilize the new snapshot fetching methods, enhancing error handling and code clarity.
- Adjusted index.js to streamline snapshot fetching during workflow deployment.
- Made minor UI adjustments in workflow-list.js for better layout consistency.

These changes enhance the application's robustness in handling snapshots and improve overall user experience.
2025-08-21 03:44:11 +08:00
KarrixLee 0539464d62 add: blacklist node 2025-08-21 03:04:12 +08:00
KarrixLee e5d3f66296 add error 2025-08-21 02:57:14 +08:00
KarrixLee 8962549393 add: create machine from local 2025-08-21 02:50:43 +08:00
KarrixLee 1a78c04cba can sync machine 2025-08-21 02:29:41 +08:00
KarrixLee 4fdfe7c18a compare list 2025-08-21 01:53:13 +08:00
KarrixLee 19e175c571 fetch docker steps in backend 2025-08-20 23:30:02 +08:00
KarrixLee de5a1791d4 remove unused code 2025-08-19 22:40:02 +08:00
KarrixLee ab265cf3ec add: machine id when create 2025-08-19 21:57:34 +08:00
KarrixLee 1a8519b5d4 refactor: enhance machine management UI and functionality
- Removed console error logs for improved user experience during initialization.
- Updated the display styles for "No machine found" and error messages to enhance visual clarity.
- Added buttons for adding and updating machines directly from the error and no machine states.
- Implemented a dialog for adding and updating machine IDs, improving user interaction.
- Adjusted the machine addition process to show loading states and ensure proper DOM updates.

These changes significantly improve the usability and functionality of the machine management feature.
2025-08-19 21:08:45 +08:00
KarrixLee 10b84ab722 initt: add machine management functionality
- Implemented a new endpoint in custom_routes.py to retrieve machine details by ID.
- Introduced machine-manager.js to handle machine data fetching, display, and local storage management.
- Updated index.js to initialize the machine manager and display machine information in the UI.
- Adjusted workflow-list.js to modify the layout for better integration with the new machine management features.

These changes enhance the application's capability to manage and display serverless machine information effectively.
2025-08-19 20:49:57 +08:00
KarrixLee 7f6eea361e tweak: add tutorial complete to true by default (#104) 2025-08-18 00:19:57 +08:00
6 changed files with 2622 additions and 146 deletions
+146
View File
@@ -2995,6 +2995,7 @@ async def create_workflow_proxy(request):
name = data.get("name")
workflow_json = data.get("workflow_json")
workflow_api = data.get("workflow_api")
machine_id = data.get("machine_id")
api_url = data.get("api_url", "https://api.comfydeploy.com")
auth_header = request.headers.get("Authorization")
@@ -3014,6 +3015,7 @@ async def create_workflow_proxy(request):
"name": name,
"workflow_json": json.dumps(workflow_json),
"workflow_api": json.dumps(workflow_api),
"machine_id": machine_id,
}
try:
@@ -3136,3 +3138,147 @@ async def get_workflow_proxy(request):
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 machine by id
@server.PromptServer.instance.routes.get("/comfyui-deploy/machine")
async def get_machine_proxy(request):
machine_id = request.rel_url.query.get("machine_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/machine/{machine_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)
# for fetching docker steps from current snapshot
@server.PromptServer.instance.routes.post("/comfyui-deploy/snapshot-to-docker")
async def snapshot_to_docker_proxy(request):
data = await request.json()
snapshot = data.get("snapshot")
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/snapshot-to-docker"
request_body = snapshot
try:
await ensure_client_session()
async with client_session.post(
target_url, json=request_body, 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)
# update a serverless machine with machine id
@server.PromptServer.instance.routes.post("/comfyui-deploy/machine/update")
async def update_machine_proxy(request):
data = await request.json()
machine_id = data.get("machine_id")
comfyui_version = data.get("comfyui_version", None)
docker_steps = data.get("docker_steps")
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/machine/serverless/{machine_id}"
request_body = {"docker_command_steps": docker_steps}
if comfyui_version:
request_body["comfyui_version"] = comfyui_version
try:
await ensure_client_session()
async with client_session.patch(
target_url, json=request_body, 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)
@server.PromptServer.instance.routes.post("/comfyui-deploy/machine/create")
async def create_machine_proxy(request):
data = await request.json()
name = data.get("name")
docker_command_steps = data.get("docker_command_steps")
comfyui_version = data.get("comfyui_version")
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/machine/serverless"
request_body = {
"name": name,
"docker_command_steps": docker_command_steps,
"comfyui_version": comfyui_version,
"gpu": "A10G",
}
try:
await ensure_client_session()
async with client_session.post(
target_url, json=request_body, 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)
# get latest comfyui version
@server.PromptServer.instance.routes.get("/comfyui-deploy/comfyui-version")
async def get_comfyui_version_proxy(request):
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/latest-hashes"
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)
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-deploy"
description = "Open source comfyui deployment platform, a vercel for generative workflow infra."
version = "2.2.1"
version = "2.3.0"
license = { file = "LICENSE" }
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg", "tabulate", "brotli"]
+21 -144
View File
@@ -4,6 +4,8 @@ import { api } from "../../scripts/api.js";
LGraphNode = LiteGraph.LGraphNode;
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { initializeWorkflowsList, addWorkflowSearch } from "./workflow-list.js";
import { initializeMachineManager } from "./machine-manager.js";
import { fetchSnapshotSimple } from "./snapshot-utils.js";
import { generateDependencyGraph } from "https://esm.sh/comfyui-json@0.1.25";
// import { ComfyDeploy } from "https://esm.sh/comfydeploy@2.0.0-beta.69";
@@ -999,7 +1001,10 @@ const ext = {
"Comfy.Sidebar.Location",
"left"
);
// localStorage.setItem("Comfy.MenuPosition.Docked", "true");
await app.ui.settings.setSettingValueAsync(
"Comfy.TutorialCompleted",
true
);
console.log("native mode manmanman");
} catch (error) {
console.error("Error setting validation to false", error);
@@ -1168,24 +1173,12 @@ async function deployWorkflow() {
<br>
<button style="font-size: 18px;">${endpoint}</button>
<br><br>
<label>
<input id="include-deps" type="checkbox">Include dependency</input>
</label>
<br>
<label>
<input id="reuse-hash" type="checkbox" checked>Reuse hash from last version</input>
</label>
</div>
`
);
if (!ok) return;
const includeDeps = document.getElementById("include-deps").checked;
const reuseHash = document.getElementById("reuse-hash").checked;
const prompt = await app.graphToPrompt();
let deps = undefined;
console.log(prompt);
@@ -1241,7 +1234,7 @@ async function deployWorkflow() {
}
loadingDialog.showLoading("Generating snapshot");
const snapshot = await fetch("/snapshot/get_current").then((x) => x.json());
const snapshot = await fetchSnapshotSimple();
console.log(snapshot);
loadingDialog.close();
@@ -1255,136 +1248,6 @@ async function deployWorkflow() {
const title = deploy.querySelector("#button-title");
if (includeDeps) {
loadingDialog.showLoading("Fetching existing version");
const existing_workflow = await fetch(
apiUrl + "/api/workflow/" + workflow_id,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + apiKey,
},
}
)
.then((x) => x.json())
.catch(() => {
return {};
});
console.log("workflow", existing_workflow);
loadingDialog.close();
loadingDialog.showLoading("Generating dependency graph");
deps = await generateDependencyGraph({
workflow_api: prompt.output,
snapshot: snapshot,
computeFileHash: async (file) => {
console.log(existing_workflow?.dependencies?.models);
// Match previous hash for models
if (reuseHash && existing_workflow?.dependencies?.models) {
const previousModelHash = Object.entries(
existing_workflow?.dependencies?.models
).flatMap(([key, value]) => {
return Object.values(value).map((x) => ({
...x,
name: "models/" + key + "/" + x.name,
}));
});
console.log(previousModelHash);
const match = previousModelHash.find((x) => {
console.log(file, x.name);
return file == x.name;
});
console.log(match);
if (match && match.hash) {
console.log("cached hash used");
return match.hash;
}
}
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;
},
// handleFileUpload: 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;
// }
// },
existingDependencies: existing_workflow.dependencies,
});
// Need to find a way to include this if this is not included in comfyui-json level
if (
!deps.custom_nodes["https://github.com/BennyKok/comfyui-deploy"] &&
!deps.custom_nodes["https://github.com/BennyKok/comfyui-deploy.git"]
)
deps.custom_nodes["https://github.com/BennyKok/comfyui-deploy"] = {
url: "https://github.com/BennyKok/comfyui-deploy",
install_type: "git-clone",
hash:
snapshot?.git_custom_nodes?.[
"https://github.com/BennyKok/comfyui-deploy"
]?.hash ?? "HEAD",
name: "ComfyUI Deploy",
};
loadingDialog.close();
console.log(deps);
const depsOk = await confirmDialog.confirm(
"Check dependencies",
// 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>
<iframe
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(
JSON.stringify(deps)
)}" />`
// createDynamicUIHtml(deps),
);
if (!depsOk) return;
}
loadingDialog.showLoading("Deploying...");
title.innerText = "Deploying...";
@@ -1397,6 +1260,10 @@ async function deployWorkflow() {
workflow_api: prompt.output,
api_url: apiUrl,
};
const machineId = localStorage.getItem("comfy_deploy_machine_id");
if (machineId) {
body.machine_id = machineId;
}
console.log(body);
let data = await fetch("/comfyui-deploy/workflow", {
method: "POST",
@@ -2015,6 +1882,13 @@ if (!isComfyDeployDashboard) {
<div style="padding: 20px;">
<h3>Comfy Deploy</h3>
<div id="deploy-container" style="margin-bottom: 20px;"></div>
<div id="machine-container" style="margin-bottom: 20px;">
<h4>Serverless Machine</h4>
<div id="machine-loading" style="display: flex; justify-content: center; align-items: center; height: 100px;">
${loadingIcon}
</div>
<ul id="machine-list" style="list-style-type: none; padding: 0; display: none;"></ul>
</div>
<div id="workflows-container" style="display: none;">
<h4>Your Workflows</h4>
<div id="workflows-loading" style="display: flex; justify-content: center; align-items: center; height: 100px;">
@@ -2075,6 +1949,9 @@ if (!isComfyDeployDashboard) {
const workflowsList = el.querySelector("#workflows-list");
const workflowsLoading = el.querySelector("#workflows-loading");
// Initialize machine manager
await initializeMachineManager(el, getData);
// Initialize workflows list and search
const data = getData();
if (data.apiKey) {
File diff suppressed because it is too large Load Diff
+82
View File
@@ -0,0 +1,82 @@
// Snapshot Utilities
// Centralized snapshot fetching with ComfyUI version fallback
/**
* Fetches the current snapshot with ComfyUI version fallback
* If the snapshot response has null comfyui field, it will fetch the latest ComfyUI version
* and update the snapshot with the comfyui_hash
*
* @param {Function} getDataFn - Function that returns { apiKey, apiUrl } for ComfyUI version API calls
* @returns {Promise<Object>} - The snapshot data with comfyui field populated
*/
export async function fetchSnapshot(getDataFn = null) {
try {
// Fetch the current snapshot
const response = await fetch("/snapshot/get_current");
if (!response.ok) {
throw new Error(`Snapshot fetch failed: ${response.status}`);
}
const snapshot = await response.json();
// Check if comfyui field is null and we have getDataFn for fallback
if (snapshot.comfyui === null && getDataFn) {
console.log(
"ComfyUI version is null in snapshot, fetching latest version..."
);
try {
const data = getDataFn();
if (data && data.apiKey) {
const comfyuiVersionResponse = await fetch(
`/comfyui-deploy/comfyui-version?api_url=${encodeURIComponent(
data.apiUrl || "https://api.comfydeploy.com"
)}`,
{
headers: {
Authorization: `Bearer ${data.apiKey}`,
},
}
);
if (comfyuiVersionResponse.ok) {
const versionData = await comfyuiVersionResponse.json();
if (versionData.comfyui_hash) {
console.log(
`Using ComfyUI hash from API: ${versionData.comfyui_hash}`
);
snapshot.comfyui = versionData.comfyui_hash;
}
} else {
console.warn(
"Failed to fetch ComfyUI version from API:",
comfyuiVersionResponse.status
);
}
}
} catch (error) {
console.warn("Error fetching ComfyUI version fallback:", error);
// Continue with original snapshot even if fallback fails
}
}
return snapshot;
} catch (error) {
console.error("Error fetching snapshot:", error);
throw error;
}
}
/**
* Simple snapshot fetch without ComfyUI version fallback
* Use this when you don't need the ComfyUI version fallback logic
*
* @returns {Promise<Object>} - The snapshot data as-is
*/
export async function fetchSnapshotSimple() {
const response = await fetch("/snapshot/get_current");
if (!response.ok) {
throw new Error(`Snapshot fetch failed: ${response.status}`);
}
return response.json();
}
+1 -1
View File
@@ -279,7 +279,7 @@ async function initializeWorkflowsList(element, getData, getTimeAgo) {
list-style-type: none;
padding: 0;
margin: 0;
height: calc(100vh - 350px);
height: calc(100vh - 550px);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #666 transparent;