feat: support new frontend!
This commit is contained in:
		
							parent
							
								
									f6ea252652
								
							
						
					
					
						commit
						10268825d9
					
				@ -17,12 +17,13 @@ from urllib.parse import quote
 | 
			
		||||
import threading
 | 
			
		||||
import hashlib
 | 
			
		||||
import aiohttp
 | 
			
		||||
from aiohttp import ClientSession, web
 | 
			
		||||
import aiofiles
 | 
			
		||||
from typing import Dict, List, Union, Any, Optional
 | 
			
		||||
from PIL import Image
 | 
			
		||||
import copy
 | 
			
		||||
import struct
 | 
			
		||||
from aiohttp import ClientError
 | 
			
		||||
from aiohttp import web, ClientSession, ClientError
 | 
			
		||||
import atexit
 | 
			
		||||
 | 
			
		||||
# Global session
 | 
			
		||||
@ -836,6 +837,50 @@ async def send(event, data, sid=None):
 | 
			
		||||
        logger.info(f"Exception: {e}")
 | 
			
		||||
        traceback.print_exc()
 | 
			
		||||
        
 | 
			
		||||
@server.PromptServer.instance.routes.get('/comfydeploy/{tail:.*}')
 | 
			
		||||
@server.PromptServer.instance.routes.post('/comfydeploy/{tail:.*}')
 | 
			
		||||
async def proxy_to_comfydeploy(request):
 | 
			
		||||
    # Get the base URL
 | 
			
		||||
    base_url = f'https://www.comfydeploy.com/{request.match_info["tail"]}'
 | 
			
		||||
    
 | 
			
		||||
    # Get all query parameters
 | 
			
		||||
    query_params = request.query_string
 | 
			
		||||
    
 | 
			
		||||
    # Construct the full target URL with query parameters
 | 
			
		||||
    target_url = f"{base_url}?{query_params}" if query_params else base_url
 | 
			
		||||
    
 | 
			
		||||
    print(f"Proxying request to: {target_url}")
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        # Create a new ClientSession for each request
 | 
			
		||||
        async with ClientSession() as client_session:
 | 
			
		||||
            # Forward the request
 | 
			
		||||
            client_req = await client_session.request(
 | 
			
		||||
                method=request.method,
 | 
			
		||||
                url=target_url,
 | 
			
		||||
                headers={k: v for k, v in request.headers.items() if k.lower() not in ('host', 'content-length')},
 | 
			
		||||
                data=await request.read(),
 | 
			
		||||
                allow_redirects=False,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Read the entire response content
 | 
			
		||||
            content = await client_req.read()
 | 
			
		||||
 | 
			
		||||
            # Try to decode the content as JSON
 | 
			
		||||
            try:
 | 
			
		||||
                json_data = json.loads(content)
 | 
			
		||||
                # If successful, return a JSON response
 | 
			
		||||
                return web.json_response(json_data, status=client_req.status)
 | 
			
		||||
            except json.JSONDecodeError:
 | 
			
		||||
                # If it's not valid JSON, return the content as-is
 | 
			
		||||
                return web.Response(body=content, status=client_req.status, headers=client_req.headers)
 | 
			
		||||
 | 
			
		||||
    except ClientError as e:
 | 
			
		||||
        print(f"Client error occurred while proxying request: {str(e)}")
 | 
			
		||||
        return web.Response(status=502, text=f"Bad Gateway: {str(e)}")
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"Error occurred while proxying request: {str(e)}")
 | 
			
		||||
        return web.Response(status=500, text=f"Internal Server Error: {str(e)}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
prompt_server = server.PromptServer.instance
 | 
			
		||||
 | 
			
		||||
@ -2,4 +2,5 @@ aiofiles
 | 
			
		||||
pydantic
 | 
			
		||||
opencv-python
 | 
			
		||||
imageio-ffmpeg
 | 
			
		||||
brotli
 | 
			
		||||
# logfire
 | 
			
		||||
@ -2,6 +2,7 @@ import { app } from "./app.js";
 | 
			
		||||
import { api } from "./api.js";
 | 
			
		||||
import { ComfyWidgets, LGraphNode } from "./widgets.js";
 | 
			
		||||
import { generateDependencyGraph } from "https://esm.sh/comfyui-json@0.1.25";
 | 
			
		||||
import { ComfyDeploy } from "https://esm.sh/comfydeploy@0.0.19-beta.30";
 | 
			
		||||
 | 
			
		||||
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>`;
 | 
			
		||||
 | 
			
		||||
@ -24,10 +25,7 @@ function dispatchAPIEventData(data) {
 | 
			
		||||
      message += "\n" + nodeError.class_type + ":";
 | 
			
		||||
      for (const errorReason of nodeError.errors) {
 | 
			
		||||
        message +=
 | 
			
		||||
                    "\n    - " +
 | 
			
		||||
                    errorReason.message +
 | 
			
		||||
                    ": " +
 | 
			
		||||
                    errorReason.details;
 | 
			
		||||
          "\n    - " + errorReason.message + ": " + errorReason.details;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -47,38 +45,32 @@ function dispatchAPIEventData(data) {
 | 
			
		||||
        // window.name = this.clientId; // use window name so it isnt reused when duplicating tabs
 | 
			
		||||
        // sessionStorage.setItem("clientId", this.clientId); // store in session storage so duplicate tab can load correct workflow
 | 
			
		||||
      }
 | 
			
		||||
            api.dispatchEvent(
 | 
			
		||||
                new CustomEvent("status", { detail: msg.data.status })
 | 
			
		||||
            );
 | 
			
		||||
      api.dispatchEvent(new CustomEvent("status", { detail: msg.data.status }));
 | 
			
		||||
      break;
 | 
			
		||||
    case "progress":
 | 
			
		||||
            api.dispatchEvent(
 | 
			
		||||
                new CustomEvent("progress", { detail: msg.data })
 | 
			
		||||
            );
 | 
			
		||||
      api.dispatchEvent(new CustomEvent("progress", { detail: msg.data }));
 | 
			
		||||
      break;
 | 
			
		||||
    case "executing":
 | 
			
		||||
      api.dispatchEvent(
 | 
			
		||||
                new CustomEvent("executing", { detail: msg.data.node })
 | 
			
		||||
        new CustomEvent("executing", { detail: msg.data.node }),
 | 
			
		||||
      );
 | 
			
		||||
      break;
 | 
			
		||||
    case "executed":
 | 
			
		||||
            api.dispatchEvent(
 | 
			
		||||
                new CustomEvent("executed", { detail: msg.data })
 | 
			
		||||
            );
 | 
			
		||||
      api.dispatchEvent(new CustomEvent("executed", { detail: msg.data }));
 | 
			
		||||
      break;
 | 
			
		||||
    case "execution_start":
 | 
			
		||||
      api.dispatchEvent(
 | 
			
		||||
                new CustomEvent("execution_start", { detail: msg.data })
 | 
			
		||||
        new CustomEvent("execution_start", { detail: msg.data }),
 | 
			
		||||
      );
 | 
			
		||||
      break;
 | 
			
		||||
    case "execution_error":
 | 
			
		||||
      api.dispatchEvent(
 | 
			
		||||
                new CustomEvent("execution_error", { detail: msg.data })
 | 
			
		||||
        new CustomEvent("execution_error", { detail: msg.data }),
 | 
			
		||||
      );
 | 
			
		||||
      break;
 | 
			
		||||
    case "execution_cached":
 | 
			
		||||
      api.dispatchEvent(
 | 
			
		||||
                new CustomEvent("execution_cached", { detail: msg.data })
 | 
			
		||||
        new CustomEvent("execution_cached", { detail: msg.data }),
 | 
			
		||||
      );
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
@ -152,13 +144,11 @@ const ext = {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!workflow_version_id) {
 | 
			
		||||
            console.error(
 | 
			
		||||
                "No workflow_version_id provided in query parameters."
 | 
			
		||||
            );
 | 
			
		||||
      console.error("No workflow_version_id provided in query parameters.");
 | 
			
		||||
    } else {
 | 
			
		||||
      loadingDialog.showLoading(
 | 
			
		||||
        "Loading workflow from " + org_display,
 | 
			
		||||
                "Please wait..."
 | 
			
		||||
        "Please wait...",
 | 
			
		||||
      );
 | 
			
		||||
      fetch(endpoint + "/api/workflow-version/" + workflow_version_id, {
 | 
			
		||||
        method: "GET",
 | 
			
		||||
@ -171,10 +161,7 @@ const ext = {
 | 
			
		||||
          const data = await res.json();
 | 
			
		||||
          const { workflow, workflow_id, error } = data;
 | 
			
		||||
          if (error) {
 | 
			
		||||
                        infoDialog.showMessage(
 | 
			
		||||
                            "Unable to load this workflow",
 | 
			
		||||
                            error
 | 
			
		||||
                        );
 | 
			
		||||
            infoDialog.showMessage("Unable to load this workflow", error);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@ -197,7 +184,7 @@ const ext = {
 | 
			
		||||
          window.history.replaceState(
 | 
			
		||||
            {},
 | 
			
		||||
            document.title,
 | 
			
		||||
                        window.location.pathname
 | 
			
		||||
            window.location.pathname,
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
@ -227,7 +214,7 @@ const ext = {
 | 
			
		||||
              multiline: false,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
                    app
 | 
			
		||||
          app,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        ComfyWidgets.STRING(
 | 
			
		||||
@ -240,17 +227,14 @@ const ext = {
 | 
			
		||||
              multiline: false,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
                    app
 | 
			
		||||
          app,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        ComfyWidgets.STRING(
 | 
			
		||||
          this,
 | 
			
		||||
          "version",
 | 
			
		||||
                    [
 | 
			
		||||
                        "",
 | 
			
		||||
                        { default: this.properties.version, multiline: false },
 | 
			
		||||
                    ],
 | 
			
		||||
                    app
 | 
			
		||||
          ["", { default: this.properties.version, multiline: false }],
 | 
			
		||||
          app,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // this.widgets.forEach((w) => {
 | 
			
		||||
@ -277,7 +261,7 @@ const ext = {
 | 
			
		||||
        title_mode: LiteGraph.NORMAL_TITLE,
 | 
			
		||||
        title: "Comfy Deploy",
 | 
			
		||||
        collapsable: true,
 | 
			
		||||
            })
 | 
			
		||||
      }),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    ComfyDeploy.category = "deploy";
 | 
			
		||||
@ -310,9 +294,7 @@ const ext = {
 | 
			
		||||
          if (typeof api.handlePromptGenerated === "function") {
 | 
			
		||||
            api.handlePromptGenerated(prompt);
 | 
			
		||||
          } else {
 | 
			
		||||
                        console.warn(
 | 
			
		||||
                            "api.handlePromptGenerated is not a function"
 | 
			
		||||
                        );
 | 
			
		||||
            console.warn("api.handlePromptGenerated is not a function");
 | 
			
		||||
          }
 | 
			
		||||
          sendEventToCD("cd_plugin_onQueuePrompt", prompt);
 | 
			
		||||
        } else if (message.type === "get_prompt") {
 | 
			
		||||
@ -346,13 +328,9 @@ const ext = {
 | 
			
		||||
          const canvas = app.canvas;
 | 
			
		||||
          const targetScale = 1;
 | 
			
		||||
          const targetOffsetX =
 | 
			
		||||
                        canvas.canvas.width / 4 -
 | 
			
		||||
                        position[0] -
 | 
			
		||||
                        node.size[0] / 2;
 | 
			
		||||
            canvas.canvas.width / 4 - position[0] - node.size[0] / 2;
 | 
			
		||||
          const targetOffsetY =
 | 
			
		||||
                        canvas.canvas.height / 4 -
 | 
			
		||||
                        position[1] -
 | 
			
		||||
                        node.size[1] / 2;
 | 
			
		||||
            canvas.canvas.height / 4 - position[1] - node.size[1] / 2;
 | 
			
		||||
 | 
			
		||||
          const startScale = canvas.ds.scale;
 | 
			
		||||
          const startOffsetX = canvas.ds.offset[0];
 | 
			
		||||
@ -376,21 +354,9 @@ const ext = {
 | 
			
		||||
 | 
			
		||||
            const easedT = easeOutCubic(t);
 | 
			
		||||
 | 
			
		||||
                        const currentScale = lerp(
 | 
			
		||||
                            startScale,
 | 
			
		||||
                            targetScale,
 | 
			
		||||
                            easedT
 | 
			
		||||
                        );
 | 
			
		||||
                        const currentOffsetX = lerp(
 | 
			
		||||
                            startOffsetX,
 | 
			
		||||
                            targetOffsetX,
 | 
			
		||||
                            easedT
 | 
			
		||||
                        );
 | 
			
		||||
                        const currentOffsetY = lerp(
 | 
			
		||||
                            startOffsetY,
 | 
			
		||||
                            targetOffsetY,
 | 
			
		||||
                            easedT
 | 
			
		||||
                        );
 | 
			
		||||
            const currentScale = lerp(startScale, targetScale, easedT);
 | 
			
		||||
            const currentOffsetX = lerp(startOffsetX, targetOffsetX, easedT);
 | 
			
		||||
            const currentOffsetY = lerp(startOffsetY, targetOffsetY, easedT);
 | 
			
		||||
 | 
			
		||||
            canvas.setZoom(currentScale);
 | 
			
		||||
            canvas.ds.offset = [currentOffsetX, currentOffsetY];
 | 
			
		||||
@ -442,7 +408,7 @@ const ext = {
 | 
			
		||||
 | 
			
		||||
function showError(title, message) {
 | 
			
		||||
  infoDialog.show(
 | 
			
		||||
        `<h3 style="margin: 0px; color: red;">${title}</h3><br><span>${message}</span> `
 | 
			
		||||
    `<h3 style="margin: 0px; color: red;">${title}</h3><br><span>${message}</span> `,
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -550,7 +516,7 @@ async function deployWorkflow() {
 | 
			
		||||
  if (deployMeta.length == 0) {
 | 
			
		||||
    const text = await inputDialog.input(
 | 
			
		||||
      "Create your deployment",
 | 
			
		||||
            "Workflow name"
 | 
			
		||||
      "Workflow name",
 | 
			
		||||
    );
 | 
			
		||||
    if (!text) return;
 | 
			
		||||
    console.log(text);
 | 
			
		||||
@ -591,7 +557,7 @@ async function deployWorkflow() {
 | 
			
		||||
    <input id="reuse-hash" type="checkbox" checked>Reuse hash from last version</input>
 | 
			
		||||
    </label>
 | 
			
		||||
    </div>
 | 
			
		||||
    `
 | 
			
		||||
    `,
 | 
			
		||||
  );
 | 
			
		||||
  if (!ok) return;
 | 
			
		||||
 | 
			
		||||
@ -610,7 +576,7 @@ async function deployWorkflow() {
 | 
			
		||||
  if (!snapshot) {
 | 
			
		||||
    showError(
 | 
			
		||||
      "Error when deploying",
 | 
			
		||||
            "Unable to generate snapshot, please install ComfyUI Manager"
 | 
			
		||||
      "Unable to generate snapshot, please install ComfyUI Manager",
 | 
			
		||||
    );
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@ -631,7 +597,7 @@ async function deployWorkflow() {
 | 
			
		||||
          "Content-Type": "application/json",
 | 
			
		||||
          Authorization: "Bearer " + apiKey,
 | 
			
		||||
        },
 | 
			
		||||
            }
 | 
			
		||||
      },
 | 
			
		||||
    )
 | 
			
		||||
      .then((x) => x.json())
 | 
			
		||||
      .catch(() => {
 | 
			
		||||
@ -650,7 +616,7 @@ async function deployWorkflow() {
 | 
			
		||||
        // Match previous hash for models
 | 
			
		||||
        if (reuseHash && existing_workflow?.dependencies?.models) {
 | 
			
		||||
          const previousModelHash = Object.entries(
 | 
			
		||||
                        existing_workflow?.dependencies?.models
 | 
			
		||||
            existing_workflow?.dependencies?.models,
 | 
			
		||||
          ).flatMap(([key, value]) => {
 | 
			
		||||
            return Object.values(value).map((x) => ({
 | 
			
		||||
              ...x,
 | 
			
		||||
@ -672,44 +638,36 @@ async function deployWorkflow() {
 | 
			
		||||
        console.log(file);
 | 
			
		||||
        loadingDialog.showLoading("Generating hash", file);
 | 
			
		||||
        const hash = await fetch(
 | 
			
		||||
                    `/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent(
 | 
			
		||||
                        file
 | 
			
		||||
                    )}`
 | 
			
		||||
          `/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;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
      //   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,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -734,12 +692,21 @@ async function deployWorkflow() {
 | 
			
		||||
      "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)
 | 
			
		||||
      )}" />`
 | 
			
		||||
        JSON.stringify(deps),
 | 
			
		||||
      )}" />`,
 | 
			
		||||
      // createDynamicUIHtml(deps),
 | 
			
		||||
    );
 | 
			
		||||
    if (!depsOk) return;
 | 
			
		||||
@ -800,7 +767,7 @@ async function deployWorkflow() {
 | 
			
		||||
    graph.change();
 | 
			
		||||
 | 
			
		||||
    infoDialog.show(
 | 
			
		||||
            `<span style="color:green;">Deployed successfully!</span>  <a style="color:white;" target="_blank" href=${endpoint}/workflows/${data.workflow_id}>-> View here</a> <br/> <br/> Workflow ID: ${data.workflow_id} <br/> Workflow Name: ${workflow_name} <br/> Workflow Version: ${data.version} <br/>`
 | 
			
		||||
      `<span style="color:green;">Deployed successfully!</span>  <a style="color:white;" target="_blank" href=${endpoint}/workflows/${data.workflow_id}>-> View here</a> <br/> <br/> Workflow ID: ${data.workflow_id} <br/> Workflow Name: ${workflow_name} <br/> Workflow Version: ${data.version} <br/>`,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
@ -997,22 +964,17 @@ export class InputDialog extends InfoDialog {
 | 
			
		||||
            type: "button",
 | 
			
		||||
            textContent: "Save",
 | 
			
		||||
            onclick: () => {
 | 
			
		||||
                            const input =
 | 
			
		||||
                                this.textElement.querySelector("#input").value;
 | 
			
		||||
              const input = this.textElement.querySelector("#input").value;
 | 
			
		||||
              if (input.trim() === "") {
 | 
			
		||||
                                showError(
 | 
			
		||||
                                    "Input validation",
 | 
			
		||||
                                    "Input cannot be empty"
 | 
			
		||||
                                );
 | 
			
		||||
                showError("Input validation", "Input cannot be empty");
 | 
			
		||||
              } else {
 | 
			
		||||
                this.callback?.(input);
 | 
			
		||||
                this.close();
 | 
			
		||||
                                this.textElement.querySelector("#input").value =
 | 
			
		||||
                                    "";
 | 
			
		||||
                this.textElement.querySelector("#input").value = "";
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
          }),
 | 
			
		||||
                ]
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
@ -1073,7 +1035,7 @@ export class ConfirmDialog extends InfoDialog {
 | 
			
		||||
              this.close();
 | 
			
		||||
            },
 | 
			
		||||
          }),
 | 
			
		||||
                ]
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
@ -1130,7 +1092,7 @@ function getData(environment) {
 | 
			
		||||
function saveData(data) {
 | 
			
		||||
  localStorage.setItem(
 | 
			
		||||
    "comfy_deploy_env_data_" + data.environment,
 | 
			
		||||
        JSON.stringify(data)
 | 
			
		||||
    JSON.stringify(data),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1145,9 +1107,7 @@ export class ConfigDialog extends ComfyDialog {
 | 
			
		||||
    this.element.style.paddingBottom = "20px";
 | 
			
		||||
 | 
			
		||||
    this.container = document.createElement("div");
 | 
			
		||||
        this.element
 | 
			
		||||
            .querySelector(".comfy-modal-content")
 | 
			
		||||
            .prepend(this.container);
 | 
			
		||||
    this.element.querySelector(".comfy-modal-content").prepend(this.container);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createButtons() {
 | 
			
		||||
@ -1181,7 +1141,7 @@ export class ConfigDialog extends ComfyDialog {
 | 
			
		||||
              this.close();
 | 
			
		||||
            },
 | 
			
		||||
          }),
 | 
			
		||||
                ]
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
@ -1193,8 +1153,7 @@ export class ConfigDialog extends ComfyDialog {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  save(api_key, displayName) {
 | 
			
		||||
        const deployOption =
 | 
			
		||||
            this.container.querySelector("#deployOption").value;
 | 
			
		||||
    const deployOption = this.container.querySelector("#deployOption").value;
 | 
			
		||||
    localStorage.setItem("comfy_deploy_env", deployOption);
 | 
			
		||||
 | 
			
		||||
    const endpoint = this.container.querySelector("#endpoint").value;
 | 
			
		||||
@ -1226,12 +1185,8 @@ export class ConfigDialog extends ComfyDialog {
 | 
			
		||||
    <h3 style="margin: 0px;">Comfy Deploy Config</h3>
 | 
			
		||||
    <label style="color: white; width: 100%;">
 | 
			
		||||
      <select id="deployOption" style="margin: 8px 0px; width: 100%; height:30px; box-sizing: border-box;" >
 | 
			
		||||
        <option value="cloud" ${
 | 
			
		||||
            data.environment === "cloud" ? "selected" : ""
 | 
			
		||||
        }>Cloud</option>
 | 
			
		||||
        <option value="local" ${
 | 
			
		||||
            data.environment === "local" ? "selected" : ""
 | 
			
		||||
        }>Local</option>
 | 
			
		||||
        <option value="cloud" ${data.environment === "cloud" ? "selected" : ""}>Cloud</option>
 | 
			
		||||
        <option value="local" ${data.environment === "local" ? "selected" : ""}>Local</option>
 | 
			
		||||
      </select>
 | 
			
		||||
    </label>
 | 
			
		||||
      <label style="color: white; width: 100%;">
 | 
			
		||||
@ -1249,9 +1204,7 @@ export class ConfigDialog extends ComfyDialog {
 | 
			
		||||
        }">
 | 
			
		||||
        <button id="loginButton" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;">
 | 
			
		||||
          ${
 | 
			
		||||
              data.apiKey
 | 
			
		||||
                  ? "Re-login with ComfyDeploy"
 | 
			
		||||
                  : "Login with ComfyDeploy"
 | 
			
		||||
            data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
 | 
			
		||||
          }
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
@ -1272,7 +1225,7 @@ export class ConfigDialog extends ComfyDialog {
 | 
			
		||||
        clearInterval(poll);
 | 
			
		||||
        infoDialog.showMessage(
 | 
			
		||||
          "Timeout",
 | 
			
		||||
                    "Wait too long for the response, please try re-login"
 | 
			
		||||
          "Wait too long for the response, please try re-login",
 | 
			
		||||
        );
 | 
			
		||||
      }, 30000); // Stop polling after 30 seconds
 | 
			
		||||
 | 
			
		||||
@ -1283,15 +1236,14 @@ export class ConfigDialog extends ComfyDialog {
 | 
			
		||||
            if (json.api_key) {
 | 
			
		||||
              this.save(json.api_key, json.name);
 | 
			
		||||
              this.close();
 | 
			
		||||
                            this.container.querySelector("#apiKey").value =
 | 
			
		||||
                                json.api_key;
 | 
			
		||||
              this.container.querySelector("#apiKey").value = json.api_key;
 | 
			
		||||
              // infoDialog.show();
 | 
			
		||||
              clearInterval(this.poll);
 | 
			
		||||
              clearTimeout(this.timeout);
 | 
			
		||||
              // Refresh dialog
 | 
			
		||||
              const a = await confirmDialog.confirm(
 | 
			
		||||
                "Authenticated",
 | 
			
		||||
                                `<div>You will be able to upload workflow to <button style="font-size: 18px; width: fit;">${json.name}</button></div>`
 | 
			
		||||
                `<div>You will be able to upload workflow to <button style="font-size: 18px; width: fit;">${json.name}</button></div>`,
 | 
			
		||||
              );
 | 
			
		||||
              configDialog.show();
 | 
			
		||||
            }
 | 
			
		||||
@ -1327,3 +1279,167 @@ export class ConfigDialog extends ComfyDialog {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const configDialog = new ConfigDialog();
 | 
			
		||||
 | 
			
		||||
const currentOrigin = window.location.origin;
 | 
			
		||||
const client = new ComfyDeploy({
 | 
			
		||||
  bearerAuth: getData().apiKey,
 | 
			
		||||
  serverURL: `${currentOrigin}/comfydeploy/api/`,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.extensionManager.registerSidebarTab({
 | 
			
		||||
  id: "search",
 | 
			
		||||
  icon: "pi pi-cloud-upload",
 | 
			
		||||
  title: "Deploy",
 | 
			
		||||
  tooltip: "Deploy and Configure",
 | 
			
		||||
  type: "custom",
 | 
			
		||||
  render: (el) => {
 | 
			
		||||
    el.innerHTML = `
 | 
			
		||||
      <div style="padding: 20px;">
 | 
			
		||||
        <h3>Comfy Deploy</h3>
 | 
			
		||||
        <div id="deploy-container" style="margin-bottom: 20px;"></div>
 | 
			
		||||
        <div id="workflows-container">
 | 
			
		||||
          <h4>Your Workflows</h4>
 | 
			
		||||
          <ul id="workflows-list" style="list-style-type: none; padding: 0;"></ul>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="config-container"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
    // Add deploy button
 | 
			
		||||
    const deployContainer = el.querySelector("#deploy-container");
 | 
			
		||||
    const deployButton = document.createElement("button");
 | 
			
		||||
    deployButton.id = "sidebar-deploy-button";
 | 
			
		||||
    deployButton.style.display = "flex";
 | 
			
		||||
    deployButton.style.alignItems = "center";
 | 
			
		||||
    deployButton.style.justifyContent = "center";
 | 
			
		||||
    deployButton.style.width = "100%";
 | 
			
		||||
    deployButton.style.marginBottom = "10px";
 | 
			
		||||
    deployButton.style.padding = "10px";
 | 
			
		||||
    deployButton.style.fontSize = "16px";
 | 
			
		||||
    deployButton.style.fontWeight = "bold";
 | 
			
		||||
    deployButton.style.backgroundColor = "#4CAF50";
 | 
			
		||||
    deployButton.style.color = "white";
 | 
			
		||||
    deployButton.style.border = "none";
 | 
			
		||||
    deployButton.style.borderRadius = "5px";
 | 
			
		||||
    deployButton.style.cursor = "pointer";
 | 
			
		||||
    deployButton.innerHTML = `<i class="pi pi-cloud-upload" style="margin-right: 8px;"></i><div id='sidebar-button-title'>Deploy</div>`;
 | 
			
		||||
    deployButton.onclick = async () => {
 | 
			
		||||
      await deployWorkflow();
 | 
			
		||||
    };
 | 
			
		||||
    deployContainer.appendChild(deployButton);
 | 
			
		||||
 | 
			
		||||
    // Add config button
 | 
			
		||||
    const configContainer = el.querySelector("#config-container");
 | 
			
		||||
    const configButton = document.createElement("button");
 | 
			
		||||
    configButton.style.display = "flex";
 | 
			
		||||
    configButton.style.alignItems = "center";
 | 
			
		||||
    configButton.style.justifyContent = "center";
 | 
			
		||||
    configButton.style.width = "100%";
 | 
			
		||||
    configButton.style.padding = "8px";
 | 
			
		||||
    configButton.style.fontSize = "14px";
 | 
			
		||||
    configButton.style.backgroundColor = "#f0f0f0";
 | 
			
		||||
    configButton.style.color = "#333";
 | 
			
		||||
    configButton.style.border = "1px solid #ccc";
 | 
			
		||||
    configButton.style.borderRadius = "5px";
 | 
			
		||||
    configButton.style.cursor = "pointer";
 | 
			
		||||
    configButton.innerHTML = `<i class="pi pi-cog" style="margin-right: 8px;"></i>Configure`;
 | 
			
		||||
    configButton.onclick = () => {
 | 
			
		||||
      configDialog.show();
 | 
			
		||||
    };
 | 
			
		||||
    deployContainer.appendChild(configButton);
 | 
			
		||||
 | 
			
		||||
    // Fetch and display workflows
 | 
			
		||||
    const workflowsList = el.querySelector("#workflows-list");
 | 
			
		||||
    client.workflows
 | 
			
		||||
      .getAll({
 | 
			
		||||
        page: "1",
 | 
			
		||||
        pageSize: "10",
 | 
			
		||||
      })
 | 
			
		||||
      .then((result) => {
 | 
			
		||||
        result.forEach((workflow) => {
 | 
			
		||||
          const li = document.createElement("li");
 | 
			
		||||
          li.style.marginBottom = "15px";
 | 
			
		||||
          li.style.padding = "15px";
 | 
			
		||||
          li.style.backgroundColor = "#2a2a2a";
 | 
			
		||||
          li.style.borderRadius = "8px";
 | 
			
		||||
          li.style.boxShadow = "0 2px 4px rgba(0,0,0,0.1)";
 | 
			
		||||
 | 
			
		||||
          const lastRun = workflow.runs[0];
 | 
			
		||||
          const lastRunStatus = lastRun ? lastRun.status : "No runs";
 | 
			
		||||
          const statusColor =
 | 
			
		||||
            lastRunStatus === "success"
 | 
			
		||||
              ? "#4CAF50"
 | 
			
		||||
              : lastRunStatus === "error"
 | 
			
		||||
                ? "#F44336"
 | 
			
		||||
                : "#FFC107";
 | 
			
		||||
 | 
			
		||||
          const timeAgo = getTimeAgo(new Date(workflow.updatedAt));
 | 
			
		||||
 | 
			
		||||
          li.innerHTML = `
 | 
			
		||||
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
 | 
			
		||||
              <div style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
 | 
			
		||||
                <strong style="font-size: 18px; color: #e0e0e0;">${workflow.name}</strong>
 | 
			
		||||
              </div>
 | 
			
		||||
              <span style="font-size: 12px; color: ${statusColor}; margin-left: 10px;">Last run: ${lastRunStatus}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div style="font-size: 14px; color: #bdbdbd; margin-bottom: 10px;">Last updated ${timeAgo}</div>
 | 
			
		||||
            <div style="display: flex; gap: 10px;">
 | 
			
		||||
              <button class="open-cloud-btn" style="padding: 5px 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">Open in Cloud</button>
 | 
			
		||||
              <button class="load-api-btn" style="padding: 5px 10px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;">Load Workflow</button>
 | 
			
		||||
            </div>
 | 
			
		||||
          `;
 | 
			
		||||
 | 
			
		||||
          const openCloudBtn = li.querySelector(".open-cloud-btn");
 | 
			
		||||
          openCloudBtn.onclick = () =>
 | 
			
		||||
            window.open(
 | 
			
		||||
              `${getData().endpoint}/workflows/${workflow.id}?workspace=true`,
 | 
			
		||||
              "_blank",
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
          const loadApiBtn = li.querySelector(".load-api-btn");
 | 
			
		||||
          loadApiBtn.onclick = () => loadWorkflowApi(workflow.versions[0].id);
 | 
			
		||||
 | 
			
		||||
          workflowsList.appendChild(li);
 | 
			
		||||
        });
 | 
			
		||||
      })
 | 
			
		||||
      .catch((error) => {
 | 
			
		||||
        console.error("Error fetching workflows:", error);
 | 
			
		||||
        workflowsList.innerHTML =
 | 
			
		||||
          "<li style='color: #F44336;'>Error fetching workflows</li>";
 | 
			
		||||
      });
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function getTimeAgo(date) {
 | 
			
		||||
  const seconds = Math.floor((new Date() - date) / 1000);
 | 
			
		||||
  let interval = seconds / 31536000;
 | 
			
		||||
  if (interval > 1) return Math.floor(interval) + " years ago";
 | 
			
		||||
  interval = seconds / 2592000;
 | 
			
		||||
  if (interval > 1) return Math.floor(interval) + " months ago";
 | 
			
		||||
  interval = seconds / 86400;
 | 
			
		||||
  if (interval > 1) return Math.floor(interval) + " days ago";
 | 
			
		||||
  interval = seconds / 3600;
 | 
			
		||||
  if (interval > 1) return Math.floor(interval) + " hours ago";
 | 
			
		||||
  interval = seconds / 60;
 | 
			
		||||
  if (interval > 1) return Math.floor(interval) + " minutes ago";
 | 
			
		||||
  return Math.floor(seconds) + " seconds ago";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function loadWorkflowApi(versionId) {
 | 
			
		||||
  try {
 | 
			
		||||
    const response = await client.comfyui.getWorkflowVersionVersionId({
 | 
			
		||||
      versionId: versionId,
 | 
			
		||||
    });
 | 
			
		||||
    // Implement the logic to load the workflow API into the ComfyUI interface
 | 
			
		||||
    console.log("Workflow API loaded:", response);
 | 
			
		||||
    await window["app"].ui.settings.setSettingValueAsync(
 | 
			
		||||
      "Comfy.Validation.Workflows",
 | 
			
		||||
      false,
 | 
			
		||||
    );
 | 
			
		||||
    app.loadGraphData(response.workflow);
 | 
			
		||||
    // You might want to update the UI or trigger some action in ComfyUI here
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error loading workflow API:", error);
 | 
			
		||||
    // Show an error message to the user
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user