feat: add new dependency viewer, reused previous cache for faster deploy
This commit is contained in:
parent
126d8e77fb
commit
f3370c1bb1
@ -24,6 +24,8 @@ from urllib.parse import quote
|
|||||||
import threading
|
import threading
|
||||||
import hashlib
|
import hashlib
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import aiofiles
|
||||||
|
import concurrent.futures
|
||||||
|
|
||||||
api = None
|
api = None
|
||||||
api_task = None
|
api_task = None
|
||||||
@ -163,15 +165,74 @@ def get_comfyui_path_from_file_path(file_path):
|
|||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
# Form ComfyUI Manager
|
# Form ComfyUI Manager
|
||||||
def compute_sha256_checksum(filepath):
|
async def compute_sha256_checksum(filepath):
|
||||||
|
print("computing sha256 checksum")
|
||||||
|
chunk_size = 1024 * 256 # Example: 256KB
|
||||||
filepath = get_comfyui_path_from_file_path(filepath)
|
filepath = get_comfyui_path_from_file_path(filepath)
|
||||||
"""Compute the SHA256 checksum of a file, in chunks"""
|
"""Compute the SHA256 checksum of a file, in chunks, asynchronously"""
|
||||||
sha256 = hashlib.sha256()
|
sha256 = hashlib.sha256()
|
||||||
with open(filepath, 'rb') as f:
|
async with aiofiles.open(filepath, 'rb') as f:
|
||||||
for chunk in iter(lambda: f.read(4096), b''):
|
while True:
|
||||||
|
chunk = await f.read(chunk_size)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
sha256.update(chunk)
|
sha256.update(chunk)
|
||||||
return sha256.hexdigest()
|
return sha256.hexdigest()
|
||||||
|
|
||||||
|
# def hash_chunk(start_end, filepath):
|
||||||
|
# """Hash a specific chunk of the file."""
|
||||||
|
# start, end = start_end
|
||||||
|
# sha256 = hashlib.sha256()
|
||||||
|
# with open(filepath, 'rb') as f:
|
||||||
|
# f.seek(start)
|
||||||
|
# chunk = f.read(end - start)
|
||||||
|
# sha256.update(chunk)
|
||||||
|
# return sha256.digest() # Return the digest of the chunk
|
||||||
|
|
||||||
|
# async def compute_sha256_checksum(filepath):
|
||||||
|
# file_size = os.path.getsize(filepath)
|
||||||
|
# parts = 1 # Or any other division based on file size or desired concurrency
|
||||||
|
# part_size = file_size // parts
|
||||||
|
# start_end_ranges = [(i * part_size, min((i + 1) * part_size, file_size)) for i in range(parts)]
|
||||||
|
|
||||||
|
# print(start_end_ranges, file_size)
|
||||||
|
|
||||||
|
# loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
|
# # Use ThreadPoolExecutor to process chunks in parallel
|
||||||
|
# with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
||||||
|
# futures = [loop.run_in_executor(executor, hash_chunk, start_end, filepath) for start_end in start_end_ranges]
|
||||||
|
# chunk_hashes = await asyncio.gather(*futures)
|
||||||
|
|
||||||
|
# # Combine the hashes sequentially
|
||||||
|
# final_sha256 = hashlib.sha256()
|
||||||
|
# for chunk_hash in chunk_hashes:
|
||||||
|
# final_sha256.update(chunk_hash)
|
||||||
|
|
||||||
|
# return final_sha256.hexdigest()
|
||||||
|
|
||||||
|
# def hash_chunk(filepath):
|
||||||
|
# chunk_size = 1024 * 256 # 256KB per chunk
|
||||||
|
# sha256 = hashlib.sha256()
|
||||||
|
# with open(filepath, 'rb') as f:
|
||||||
|
# while True:
|
||||||
|
# chunk = f.read(chunk_size)
|
||||||
|
# if not chunk:
|
||||||
|
# break # End of file
|
||||||
|
# sha256.update(chunk)
|
||||||
|
# return sha256.hexdigest()
|
||||||
|
|
||||||
|
# async def compute_sha256_checksum(filepath):
|
||||||
|
# print("computing sha256 checksum")
|
||||||
|
# filepath = get_comfyui_path_from_file_path(filepath)
|
||||||
|
|
||||||
|
# loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
|
# with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
||||||
|
# task = loop.run_in_executor(executor, hash_chunk, filepath)
|
||||||
|
|
||||||
|
# return await task
|
||||||
|
|
||||||
# This is start uploading the files to Comfy Deploy
|
# This is start uploading the files to Comfy Deploy
|
||||||
@server.PromptServer.instance.routes.post('/comfyui-deploy/upload-file')
|
@server.PromptServer.instance.routes.post('/comfyui-deploy/upload-file')
|
||||||
async def upload_file(request):
|
async def upload_file(request):
|
||||||
@ -269,9 +330,13 @@ async def get_file_hash(request):
|
|||||||
base = folder_paths.base_path
|
base = folder_paths.base_path
|
||||||
file_path = os.path.join(base, file_path)
|
file_path = os.path.join(base, file_path)
|
||||||
# print("file_path", file_path)
|
# print("file_path", file_path)
|
||||||
file_hash = compute_sha256_checksum(
|
start_time = time.time() # Capture the start time
|
||||||
|
file_hash = await compute_sha256_checksum(
|
||||||
file_path
|
file_path
|
||||||
)
|
)
|
||||||
|
end_time = time.time() # Capture the end time after the code execution
|
||||||
|
elapsed_time = end_time - start_time # Calculate the elapsed time
|
||||||
|
print(f"Execution time: {elapsed_time} seconds")
|
||||||
return web.json_response({
|
return web.json_response({
|
||||||
"file_hash": file_hash
|
"file_hash": file_hash
|
||||||
})
|
})
|
||||||
|
1
requirement.txt
Normal file
1
requirement.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
aiofiles
|
@ -3,6 +3,8 @@ 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.19";
|
import { generateDependencyGraph } from "https://esm.sh/comfyui-json@0.1.19";
|
||||||
|
|
||||||
|
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>`;
|
||||||
|
|
||||||
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
|
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
|
||||||
/** @type {ComfyExtension} */
|
/** @type {ComfyExtension} */
|
||||||
const ext = {
|
const ext = {
|
||||||
@ -284,21 +286,37 @@ function addButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ok = await confirmDialog.confirm(
|
const ok = await confirmDialog.confirm(
|
||||||
"Confirm deployment -> " +
|
`Confirm deployment`,
|
||||||
displayName +
|
`
|
||||||
"<br><br><small><div style='font-weight: normal;'>" +
|
<div>
|
||||||
endpoint +
|
|
||||||
"<div><br>",
|
A new version will be deployed, do you confirm?
|
||||||
`A new version will be deployed, are you confirm? <br><br><input id="include-deps" type="checkbox" checked>Include dependence</input>`,
|
<br><br>
|
||||||
|
|
||||||
|
<button style="font-size: 18px;">${displayName}</button>
|
||||||
|
<br>
|
||||||
|
<button style="font-size: 18px;">${endpoint}</button>
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
<label>
|
||||||
|
<input id="include-deps" type="checkbox" checked>Include dependency</input>
|
||||||
|
</label>
|
||||||
|
<br>
|
||||||
|
<label>
|
||||||
|
<input id="reuse-hash" type="checkbox" checked>Reuse hash from last version</input>
|
||||||
|
</label>
|
||||||
|
</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;
|
||||||
|
|
||||||
if (endpoint.endsWith("/")) {
|
if (endpoint.endsWith("/")) {
|
||||||
endpoint = endpoint.slice(0, -1);
|
endpoint = endpoint.slice(0, -1);
|
||||||
}
|
}
|
||||||
loadingDialog.showLoading("Generating snapshot", "Please wait...");
|
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);
|
||||||
@ -343,7 +361,7 @@ function addButton() {
|
|||||||
let deps = undefined;
|
let deps = undefined;
|
||||||
|
|
||||||
if (includeDeps) {
|
if (includeDeps) {
|
||||||
loadingDialog.showLoading("Fetching existing version", "Please wait...");
|
loadingDialog.showLoading("Fetching existing version");
|
||||||
|
|
||||||
const existing_workflow = await fetch(
|
const existing_workflow = await fetch(
|
||||||
endpoint + "/api/workflow/" + workflow_id,
|
endpoint + "/api/workflow/" + workflow_id,
|
||||||
@ -362,14 +380,35 @@ function addButton() {
|
|||||||
|
|
||||||
loadingDialog.close();
|
loadingDialog.close();
|
||||||
|
|
||||||
loadingDialog.showLoading(
|
loadingDialog.showLoading("Generating dependency graph");
|
||||||
"Generating dependency graph",
|
|
||||||
"Please wait...",
|
|
||||||
);
|
|
||||||
deps = await generateDependencyGraph({
|
deps = await generateDependencyGraph({
|
||||||
workflow_api: prompt.output,
|
workflow_api: prompt.output,
|
||||||
snapshot: snapshot,
|
snapshot: snapshot,
|
||||||
computeFileHash: async (file) => {
|
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);
|
console.log(file);
|
||||||
loadingDialog.showLoading("Generating hash", file);
|
loadingDialog.showLoading("Generating hash", file);
|
||||||
const hash = await fetch(
|
const hash = await fetch(
|
||||||
@ -416,7 +455,14 @@ function addButton() {
|
|||||||
const depsOk = await confirmDialog.confirm(
|
const depsOk = await confirmDialog.confirm(
|
||||||
"Check dependencies",
|
"Check dependencies",
|
||||||
// JSON.stringify(deps, null, 2),
|
// JSON.stringify(deps, null, 2),
|
||||||
createDynamicUIHtml(deps),
|
`
|
||||||
|
<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="${endpoint}/dependency-graph?deps=${encodeURIComponent(
|
||||||
|
JSON.stringify(deps),
|
||||||
|
)}" />`,
|
||||||
|
// createDynamicUIHtml(deps),
|
||||||
);
|
);
|
||||||
if (!depsOk) return;
|
if (!depsOk) return;
|
||||||
|
|
||||||
@ -570,12 +616,10 @@ export class InfoDialog extends ComfyDialog {
|
|||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
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>`;
|
|
||||||
|
|
||||||
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;">${title} ${this.loadingIcon}</h3>
|
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center;">${title} ${loadingIcon}</h3>
|
||||||
<label>
|
<label>
|
||||||
${message}
|
${message}
|
||||||
</label>
|
</label>
|
||||||
@ -620,7 +664,11 @@ export class LoadingDialog extends ComfyDialog {
|
|||||||
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${
|
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${
|
||||||
this.loadingIcon
|
this.loadingIcon
|
||||||
}</h3>
|
}</h3>
|
||||||
${message ? `<label>${message}</label>` : ""}
|
${
|
||||||
|
message
|
||||||
|
? `<label style="max-width: 100%; white-space: pre-wrap; word-wrap: break-word;">${message}</label>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
@ -739,11 +787,9 @@ export class ConfirmDialog extends InfoDialog {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.callback = resolve;
|
this.callback = resolve;
|
||||||
this.show(`
|
this.show(`
|
||||||
<div style="width: 100%; max-width: 600px; 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; position: relative;">
|
||||||
<h3 style="margin: 0px;">${title}</h3>
|
<h3 style="margin: 0px;">${title}</h3>
|
||||||
<label>
|
${message}
|
||||||
${message}
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user