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