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 hashlib
|
||||
import aiohttp
|
||||
import aiofiles
|
||||
import concurrent.futures
|
||||
|
||||
api = None
|
||||
api_task = None
|
||||
@ -163,15 +165,74 @@ def get_comfyui_path_from_file_path(file_path):
|
||||
return file_path
|
||||
|
||||
# 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)
|
||||
"""Compute the SHA256 checksum of a file, in chunks"""
|
||||
"""Compute the SHA256 checksum of a file, in chunks, asynchronously"""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(4096), b''):
|
||||
async with aiofiles.open(filepath, 'rb') as f:
|
||||
while True:
|
||||
chunk = await f.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
sha256.update(chunk)
|
||||
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
|
||||
@server.PromptServer.instance.routes.post('/comfyui-deploy/upload-file')
|
||||
async def upload_file(request):
|
||||
@ -269,9 +330,13 @@ async def get_file_hash(request):
|
||||
base = folder_paths.base_path
|
||||
file_path = os.path.join(base, 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
|
||||
)
|
||||
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({
|
||||
"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 { 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*/
|
||||
/** @type {ComfyExtension} */
|
||||
const ext = {
|
||||
@ -284,21 +286,37 @@ function addButton() {
|
||||
}
|
||||
|
||||
const ok = await confirmDialog.confirm(
|
||||
"Confirm deployment -> " +
|
||||
displayName +
|
||||
"<br><br><small><div style='font-weight: normal;'>" +
|
||||
endpoint +
|
||||
"<div><br>",
|
||||
`A new version will be deployed, are you confirm? <br><br><input id="include-deps" type="checkbox" checked>Include dependence</input>`,
|
||||
`Confirm deployment`,
|
||||
`
|
||||
<div>
|
||||
|
||||
A new version will be deployed, do you confirm?
|
||||
<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;
|
||||
|
||||
const includeDeps = document.getElementById("include-deps").checked;
|
||||
const reuseHash = document.getElementById("reuse-hash").checked;
|
||||
|
||||
if (endpoint.endsWith("/")) {
|
||||
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());
|
||||
// console.log(snapshot);
|
||||
@ -343,7 +361,7 @@ function addButton() {
|
||||
let deps = undefined;
|
||||
|
||||
if (includeDeps) {
|
||||
loadingDialog.showLoading("Fetching existing version", "Please wait...");
|
||||
loadingDialog.showLoading("Fetching existing version");
|
||||
|
||||
const existing_workflow = await fetch(
|
||||
endpoint + "/api/workflow/" + workflow_id,
|
||||
@ -362,14 +380,35 @@ function addButton() {
|
||||
|
||||
loadingDialog.close();
|
||||
|
||||
loadingDialog.showLoading(
|
||||
"Generating dependency graph",
|
||||
"Please wait...",
|
||||
);
|
||||
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(
|
||||
@ -416,7 +455,14 @@ function addButton() {
|
||||
const depsOk = await confirmDialog.confirm(
|
||||
"Check dependencies",
|
||||
// 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;
|
||||
|
||||
@ -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) {
|
||||
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;">${title} ${this.loadingIcon}</h3>
|
||||
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center;">${title} ${loadingIcon}</h3>
|
||||
<label>
|
||||
${message}
|
||||
</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} ${
|
||||
this.loadingIcon
|
||||
}</h3>
|
||||
${message ? `<label>${message}</label>` : ""}
|
||||
${
|
||||
message
|
||||
? `<label style="max-width: 100%; white-space: pre-wrap; word-wrap: break-word;">${message}</label>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
@ -739,11 +787,9 @@ export class ConfirmDialog extends InfoDialog {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.callback = resolve;
|
||||
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>
|
||||
<label>
|
||||
${message}
|
||||
</label>
|
||||
${message}
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user