diff --git a/js/button/actions.js b/js/button/actions.js
index 1d7c4ba..0b9e162 100644
--- a/js/button/actions.js
+++ b/js/button/actions.js
@@ -24,63 +24,10 @@ import { nimbus, local } from '../resource/index.js';
import { endpoint } from '../constants.js';
import { ComfyCloudDialog, ComfyCloudPopover } from '../comfy/ui.js';
-import van from '../lib/van.js';
-import { Await } from '../lib/van-ui.js';
-const {video, h2, img, a, button, div, b, span, source } = van.tags
+import { taskId, Progress } from '../ui/uploadProgress.js';
-const ProgressBar = (progress) => {
- const progressPercentage = 100 - (progress.value/progress.max * 100)
- return () => div({style: `width: ${progressPercentage}%; height: 24px; background-color: #1D4AFF; transition: all .2s;`}, `${progressPercentage.toFixed(2)}%`)
-}
-
-const taskId = van.state(null)
-const Progress = (dialogInstance) => {
-
- const activeTab = van.state(0)
- const data = van.state(local.pollUploadStatus(taskId.val)) // workflowRun data
-
- const start = () => dialogInstance.poll = dialogInstance.poll || setInterval(async () => {
- const res = await local.pollUploadStatus(taskId.val)
-
- if(!(res.status == "Task started" || res.status == "Task uploading" || res.status == "Task hashing")) {
- // Stop poll
- clearInterval(dialogInstance.poll)
- dialogInstance.close()
- dialogInstance, dialogInstance.poll = 0;
- }
-
- data.val = Promise.resolve(res)
- }, 1000)
-
- start()
-
- return () => van.tags.div({style: 'width: 320px'},
- div(
- h2("Uploading dependencies"),
- ),
- Await({
- value: data.val,
- container: span,
- Loading: () => "Loading",
- Error: () => "Request failed.",
- }, data => div(
- div(
- b("Status:"),
- data.status
- ),
- Object.keys(data.progress).length === 0 ? "Loading" :
- Object.entries(data.progress).map(([key, val]) => {
- return () => div(
- b(key),
- ProgressBar({value: val.value, max: val.max})
- )
- }))
- ))
-}
-
-export const progressDialog = new ComfyCloudPopover(Progress)
-//progressDialog.show();
+export const progressDialog = new ComfyCloudPopover(Progress, "Uploading dependencies...")
export async function onGeneration() {
try {
diff --git a/js/button/dependencies.js b/js/button/dependencies.js
index dd39629..e056fcb 100644
--- a/js/button/dependencies.js
+++ b/js/button/dependencies.js
@@ -119,10 +119,18 @@ export const resolveDependencies = async (diff) => {
}
}
+
+export const pollSyncDependenciesStatus = {
+ STARTED: 'Started',
+ COMPLETED: 'Completed',
+ HASHING: 'Hashing',
+ UPLOADING: 'Uploading',
+ ERROR: 'Failed',
+}
export const pollSyncDependencies = async (taskId) => {
let status = '';
let statusMessage = '';
- while (status !== 'Task completed' && status !== 'Task failed') {
+ while (status !== pollSyncDependenciesStatus.COMPLETED && status !== pollSyncDependenciesStatus.ERROR) {
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for 1 second before the next poll
try {
@@ -136,7 +144,7 @@ export const pollSyncDependencies = async (taskId) => {
}
}
- if (status == "Task failed") {
+ if (status == pollSyncDependenciesStatus.ERROR) {
throw new Error(statusMessage || "Failed to upload")
}
}
diff --git a/js/comfy/ui.js b/js/comfy/ui.js
index 832e7f1..c6fe222 100644
--- a/js/comfy/ui.js
+++ b/js/comfy/ui.js
@@ -1,46 +1,73 @@
import { ComfyDialog, $el } from './comfy.js';
import { getData, setData } from '../store.js';
-import { headerHtml } from '../ui/html.js';
+import { headerHtml, chevronUpIcon } from '../ui/html.js';
import van from '../lib/van.js';
export class ComfyCloudPopover extends ComfyDialog {
container = null;
- constructor(ui) {
+ constructor(ui, title = "") {
super();
this.element.classList.add("comfy-normal-modal");
this.element.style.padding = "16px";
this.element.style.overflowY = "auto";
-
- this.container = document.createElement("div");
- this.container.style.color = "white";
-
- this.header = document.createElement("div");
- this.header.innerHTML = headerHtml
- this.element.querySelector(".comfy-modal-content").prepend(this.container);
- //this.element.querySelector(".comfy-modal-content").prepend(this.header);
this.element.style.top = "24px";
this.element.style.left = "14px";
this.element.style.transform = "none";
+ // hotfix: set all p elements inside this.element to have 0 margin
+ this.element.querySelectorAll("p").forEach((p) => {
+ p.style.margin = "0";
+ });
+
+
+ // Title
+ this.title = document.createElement("div");
+ this.title.style.fontSize = "18px";
+ this.title.style.fontWeight = "bold";
+ this.title.style.color = "white";
+ this.title.innerHTML = title;
+
+ // Collapse button
+ this.collapsed = false;
+ this.collapseButton = document.createElement("button");
+ this.collapseButton.style.width = "36px";
+ this.collapseButton.innerHTML = chevronUpIcon;
+ this.collapseButton.onclick = () => {
+ this.collapsed = !this.collapsed;
+ this.component.style.height = this.collapsed ? "0" : "auto";
+ this.collapseButton.style.transform = this.collapsed ? "rotate(180deg)" : "rotate(0deg)";
+ }
+
+ // Title + collapse button wrapper
+ this.header = document.createElement("div");
+ this.header.style.display = "flex";
+ this.header.style.alignItems = "center";
+ this.header.style.justifyContent = "space-between";
+ this.header.style.gap = "8px";
+ this.header.append(this.title);
+ this.header.append(this.collapseButton);
+
+
+ // VanJS content
+ this.container = document.createElement("div");
+ this.container.style.color = "white";
+ this.container.style.overflow = "hidden";
+
this.component = null;
this.ui = ui
+
+
+ this.element.querySelector(".comfy-modal-content").prepend(this.container);
+ this.element.querySelector(".comfy-modal-content").prepend(this.header);
}
createButtons() {
return [
$el(
"div",
- [
- /*
- $el("button", {
- type: "button",
- textContent: "Close",
- onclick: () => this.close(),
- }),
- */
- ],
+ [],
),
];
}
diff --git a/js/ui/html.js b/js/ui/html.js
index 52f6e50..6f21f25 100644
--- a/js/ui/html.js
+++ b/js/ui/html.js
@@ -18,6 +18,13 @@ export const cloudIconWhite = `
`
+export const fileIcon = `
+
+`
+
+export const chevronUpIcon = `
+
+`
export const headerHtml = `
diff --git a/js/ui/uploadProgress.js b/js/ui/uploadProgress.js
new file mode 100644
index 0000000..20d66fa
--- /dev/null
+++ b/js/ui/uploadProgress.js
@@ -0,0 +1,87 @@
+import van from '../lib/van.js';
+import { Await } from '../lib/van-ui.js';
+import { nimbus, local } from '../resource/index.js';
+import { pollSyncDependenciesStatus } from '../button/dependencies.js';
+import { fileIcon } from './html.js';
+
+const { h2, img, a, button, div, b, span, source } = van.tags
+const { path, svg} = van.tags("http://www.w3.org/2000/svg")
+
+// Show the first 4 characters, and last 18 characters of this string
+function cropString(str) {
+ const firstThree = str.substring(0, 4);
+ const lastTen = str.substring(str.length - 36);
+ const middlePart = '...';
+ return firstThree + middlePart + lastTen;
+}
+
+
+const ProgressBar = (progress) => {
+ const progressPercentage = 100 - (progress.value/progress.max * 100)
+ return () => div({style: "height: 18px; width: 100%; background: rgba(255,255,255,.1); transition: all .2s; border-radius: 4px; overflow: hidden;"},
+ div({style: `width: ${progressPercentage}%; background-color: #1D4AFF; border-radius: 4px;`},
+ span({style: "font-size: 12px"}, `${progressPercentage.toFixed(2)}%`)
+ )
+ )
+}
+
+export const taskId = van.state(null)
+export const Progress = (dialogInstance) => {
+
+ const activeTab = van.state(0)
+ const data = van.state(local.pollUploadStatus(taskId.val)) // workflowRun data
+
+ const start = () => dialogInstance.poll = dialogInstance.poll || setInterval(async () => {
+ const res = await local.pollUploadStatus(taskId.val)
+
+ if(!(
+ res.status == pollSyncDependenciesStatus.STARTED ||
+ res.status == pollSyncDependenciesStatus.UPLOADING ||
+ res.status == pollSyncDependenciesStatus.HASHING)
+ ) {
+ // Stop poll
+ clearInterval(dialogInstance.poll)
+ dialogInstance.close()
+ dialogInstance, dialogInstance.poll = 0;
+ }
+
+ data.val = Promise.resolve(res)
+ }, 1000)
+
+ start()
+
+ return () => van.tags.div({style: 'width: 360px'},
+ Await({
+ value: data.val,
+ container: span,
+ Loading: () => "Loading",
+ Error: () => "Request failed.",
+ }, data => div(
+
+ div({style: "margin-bottom: 16px"},
+ b("Status:"),
+ data.status
+ ),
+
+ Object.entries(data.progress).map(([key, val]) => {
+ return () => div({style: "margin-bottom: 16px;"},
+ div({style: "display: flex; align-items: center; gap: 8px;"},
+ div({style: 'width: 12px; color: white;'},
+ svg({xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 384 512"},
+ path({"d": "M320 464c8.8 0 16-7.2 16-16V160H256c-17.7 0-32-14.3-32-32V48H64c-8.8 0-16 7.2-16 16V448c0 8.8 7.2 16 16 16H320zM0 64C0 28.7 28.7 0 64 0H229.5c17 0 33.3 6.7 45.3 18.7l90.5 90.5c12 12 18.7 28.3 18.7 45.3V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64z", "fill": "currentColor"}),
+ )
+ ),
+ span({style: "font-size: 12px"}, b(cropString(key))),
+ ),
+
+ div({style: "width: 100%"},
+ ProgressBar({value: val.value, max: val.max})
+ )
+ )
+ })
+
+ )
+ ))
+}
+
+
diff --git a/python/custom_routes.py b/python/custom_routes.py
index 1f66715..6a8926d 100644
--- a/python/custom_routes.py
+++ b/python/custom_routes.py
@@ -7,7 +7,6 @@ import uuid
import time
import traceback
import random
-import re
import requests
import importlib
import sys
@@ -21,11 +20,13 @@ import folder_paths
from .upload import upload_file_specs
from .upload.spec import FileSpecContextManager
-from .upload.progress import progress_dict
+from .upload.progress import progress_dict, reset_progress
from .utils.paths import build_paths
from .utils.task import task_create, task_set_status, task_set_progress, task_get_by_id, task_serialize, TaskStatus
+from .utils.requirements import update_requirements
+
@server.PromptServer.instance.routes.post("/comfy-cloud/validate-input-path")
async def validate_input_path(request):
@@ -148,44 +149,6 @@ def load_custom_node(module_path, ignore=set()):
print(f"Cannot import {module_path} module for custom nodes:", e)
return (module_name, [])
-def update_dependencies():
- """
- Formats requirements.txt
- """
-
- base = folder_paths.base_path
- custom_nodes_dir = os.path.join(base, "custom_nodes")
-
- # Loop over the files in the specified directory
- for custom_node in os.listdir(custom_nodes_dir):
- filepath = os.path.join(custom_nodes_dir, custom_node, "requirements.txt")
-
- # Check if the file is a requirements.txt file
- if os.path.exists(filepath):
-
- # Read the content of the requirements.txt file
- with open(filepath, 'r') as file:
- lines = file.readlines()
-
- # Update dependencies in the file content
- updated_lines = []
- for line in lines:
- # Check if the line contains a git dependency without the specified format
- match_git = re.match(r'^\s*git\+https://.*?/([^/]+)\.git', line)
- match_git_plus = re.match(r'^\s*git\+https://.*?/([^/]+)$', line)
-
- if match_git:
- package_name = match_git.group(1)
- updated_lines.append(f"{package_name} @ {line.strip()}\n")
- elif match_git_plus:
- package_name = match_git_plus.group(1).strip()
- updated_lines.append(f"{package_name} @ {line.strip()}\n")
- else:
- updated_lines.append(line)
-
- # Write the updated content back to the requirements.txt file
- with open(filepath, 'w') as file:
- file.writelines(updated_lines)
async def upload_task_execution(task_id, file_specs, workflow_id):
try:
@@ -202,6 +165,9 @@ async def upload_task_execution(task_id, file_specs, workflow_id):
print(e)
task_set_status(task_id, TaskStatus.ERROR)
+ finally:
+ reset_progress(progress_dict)
+
@server.PromptServer.instance.routes.post("/comfy-cloud/upload")
async def upload_dependencies(request):
@@ -229,7 +195,7 @@ async def upload_dependencies(request):
# Our server uses a custom dependency manager
# that requires a specific format for requirements.txt.
# Loop through all dependent custom nodes and patch.
- update_dependencies()
+ update_requirements()
# Get dependency paths
paths = build_paths(paths_to_upload, dep_lists, workflow_id)
diff --git a/python/upload/progress.py b/python/upload/progress.py
index 6d798f3..d49763f 100644
--- a/python/upload/progress.py
+++ b/python/upload/progress.py
@@ -17,6 +17,8 @@ def progress_update(filename, value, max):
progress_dict[filename] = progress
+def reset_progress(progress_dict):
+ progress_dict = {}
diff --git a/python/utils/requirements.py b/python/utils/requirements.py
new file mode 100644
index 0000000..b3008a6
--- /dev/null
+++ b/python/utils/requirements.py
@@ -0,0 +1,42 @@
+import os
+import re
+import folder_paths
+
+def update_requirements():
+ """
+ Formats requirements.txt
+ """
+
+ base = folder_paths.base_path
+ custom_nodes_dir = os.path.join(base, "custom_nodes")
+
+ # Loop over the files in the specified directory
+ for custom_node in os.listdir(custom_nodes_dir):
+ filepath = os.path.join(custom_nodes_dir, custom_node, "requirements.txt")
+
+ # Check if the file is a requirements.txt file
+ if os.path.exists(filepath):
+
+ # Read the content of the requirements.txt file
+ with open(filepath, 'r') as file:
+ lines = file.readlines()
+
+ # Update dependencies in the file content
+ updated_lines = []
+ for line in lines:
+ # Check if the line contains a git dependency without the specified format
+ match_git = re.match(r'^\s*git\+https://.*?/([^/]+)\.git', line)
+ match_git_plus = re.match(r'^\s*git\+https://.*?/([^/]+)$', line)
+
+ if match_git:
+ package_name = match_git.group(1)
+ updated_lines.append(f"{package_name} @ {line.strip()}\n")
+ elif match_git_plus:
+ package_name = match_git_plus.group(1).strip()
+ updated_lines.append(f"{package_name} @ {line.strip()}\n")
+ else:
+ updated_lines.append(line)
+
+ # Write the updated content back to the requirements.txt file
+ with open(filepath, 'w') as file:
+ file.writelines(updated_lines)
diff --git a/python/utils/task.py b/python/utils/task.py
index 7e6e934..87c5579 100644
--- a/python/utils/task.py
+++ b/python/utils/task.py
@@ -4,11 +4,11 @@ from enum import Enum
from typing import Any, Dict
class TaskStatus(Enum):
- STARTED = "Task started"
- COMPLETED = "Task completed"
- HASHING = "Task hashing"
- UPLOADING = "Task uploading"
- ERROR = "Task failed"
+ STARTED = "Started"
+ COMPLETED = "Completed"
+ HASHING = "Hashing"
+ UPLOADING = "Uploading"
+ ERROR = "Failed"
@dataclasses.dataclass
class Task: