Compare commits
82 Commits
attach_nod
...
3.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
285d0c067f | ||
|
|
a6956ddafb | ||
|
|
f2bc05f5f1 | ||
|
|
1bd64e97cc | ||
|
|
a534296d97 | ||
|
|
b703384f6b | ||
|
|
722b40df80 | ||
|
|
8c58353e25 | ||
|
|
d968c55e48 | ||
|
|
e3aea8c3e2 | ||
|
|
3b8b91d648 | ||
|
|
bc4126f526 | ||
|
|
a735f60790 | ||
|
|
7bc1a944af | ||
|
|
41d4ba9721 | ||
|
|
164f25fb43 | ||
|
|
57861e9cbd | ||
|
|
5e5e567181 | ||
|
|
fd62ada1a6 | ||
|
|
a6eaba7e18 | ||
|
|
929453d105 | ||
|
|
559d4c1185 | ||
|
|
f8221b9b5d | ||
|
|
c1fc8aabdc | ||
|
|
f4442972bc | ||
|
|
851742e5e7 | ||
|
|
adf3f8d094 | ||
|
|
aa4b3d81ba | ||
|
|
cbb6432803 | ||
|
|
95df2ad212 | ||
|
|
b333342d5f | ||
|
|
14afc8d998 | ||
|
|
e83e15b9fc | ||
|
|
e34b59f16d | ||
|
|
76db17c7f8 | ||
|
|
f8306c0896 | ||
|
|
f6572e3f5a | ||
|
|
d48c936770 | ||
|
|
c694764c7a | ||
|
|
da42eca04a | ||
|
|
527c994d43 | ||
|
|
800faf96d4 | ||
|
|
2c3a11012f | ||
|
|
95311cb225 | ||
|
|
70471b54f6 | ||
|
|
f0205c8eba | ||
|
|
1a5fa290a3 | ||
|
|
4fc50d5019 | ||
|
|
a1c90ceb52 | ||
|
|
ecda9bd34e | ||
|
|
a952009d4a | ||
|
|
6f2e1345b2 | ||
|
|
7b93c831de | ||
|
|
80e1fcd672 | ||
|
|
bff8dbee30 | ||
|
|
32c828670a | ||
|
|
ad1faee2ef | ||
|
|
005fa14254 | ||
|
|
7b60b69968 | ||
|
|
ed123750d9 | ||
|
|
bede95cd05 | ||
|
|
693a226a41 | ||
|
|
7ec2793c9a | ||
|
|
a1f7f7069f | ||
|
|
f74d8cb470 | ||
|
|
b02cb2b833 | ||
|
|
243b65961f | ||
|
|
a6d20b0865 | ||
|
|
06b79287e2 | ||
|
|
e906d27606 | ||
|
|
0968dd85aa | ||
|
|
75240a028a | ||
|
|
3335c82350 | ||
|
|
e16e72cbbd | ||
|
|
0b6f7962a4 | ||
|
|
ea3413be9b | ||
|
|
10055f578b | ||
|
|
cddd000848 | ||
|
|
cdb400d32b | ||
|
|
8e1f792cd1 | ||
|
|
f0299e07f9 | ||
|
|
b3be556837 |
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -3,7 +3,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main-blocked
|
- main
|
||||||
paths:
|
paths:
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
|
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,8 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.history/
|
||||||
|
*.code-workspace
|
||||||
.tmp
|
.tmp
|
||||||
.cache
|
.cache
|
||||||
config.ini
|
config.ini
|
||||||
|
|||||||
130
README.md
130
README.md
@@ -1,21 +1,22 @@
|
|||||||
# ComfyUI Manager
|
# ComfyUI Manager (Extension)
|
||||||
|
|
||||||
**ComfyUI-Manager** is an extension designed to enhance the usability of [ComfyUI](https://github.com/comfyanonymous/ComfyUI). It offers management functions to **install, remove, disable, and enable** various custom nodes of ComfyUI. Furthermore, this extension provides a hub feature and convenience functions to access a wide range of information within ComfyUI.
|
**ComfyUI-Manager (Extension)** expands the functionality of [manager-core](https://github.com/Comfy-Org/manager-core), allowing users to access the existing ComfyUI-Manager features.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## NOTICE
|
## NOTICE
|
||||||
* V2.48.1: Security policy has been changed. Downloads of models in the list are allowed under the 'normal' security level.
|
* V3.0: ComfyUI-Manager (Extension)
|
||||||
* V2.47: Security policy has been changed. The former 'normal' is now 'normal-', and 'normal' no longer allows high-risk features, even if your ComfyUI is local.
|
|
||||||
* V2.37 Show a ✅ mark to accounts that have been active on GitHub for more than six months.
|
|
||||||
* V2.33 Security policy is applied.
|
|
||||||
* V2.21 [cm-cli](docs/en/cm-cli.md) tool is added.
|
|
||||||
* V2.18 to V2.18.3 is not functioning due to a severe bug. Users on these versions are advised to promptly update to V2.18.4. Please navigate to the `ComfyUI/custom_nodes/ComfyUI-Manager` directory and execute `git pull` to update.
|
|
||||||
* You can see whole nodes info on [ComfyUI Nodes Info](https://ltdrdata.github.io/) page.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Installation[method1] (General installation method: ComfyUI-Manager only)
|
### Installation[method1] installation via manager-core (Recommended)
|
||||||
|
|
||||||
|
Search for ComfyUI-Manager in the custom node installation feature of manager-core and install it.
|
||||||
|
|
||||||
|
|
||||||
|
### Installation[method1] manual (Not Recommended)
|
||||||
|
|
||||||
To install ComfyUI-Manager in addition to an existing installation of ComfyUI, you can follow the following steps:
|
To install ComfyUI-Manager in addition to an existing installation of ComfyUI, you can follow the following steps:
|
||||||
|
|
||||||
@@ -24,49 +25,6 @@ To install ComfyUI-Manager in addition to an existing installation of ComfyUI, y
|
|||||||
3. Restart ComfyUI
|
3. Restart ComfyUI
|
||||||
|
|
||||||
|
|
||||||
### Installation[method2] (Installation for portable ComfyUI version: ComfyUI-Manager only)
|
|
||||||
1. install git
|
|
||||||
- https://git-scm.com/download/win
|
|
||||||
- standalone version
|
|
||||||
- select option: use windows default console window
|
|
||||||
2. Download [scripts/install-manager-for-portable-version.bat](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-manager-for-portable-version.bat) into installed `"ComfyUI_windows_portable"` directory
|
|
||||||
3. double click `install-manager-for-portable-version.bat` batch file
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### Installation[method3] (Installation through comfy-cli: install ComfyUI and ComfyUI-Manager at once.)
|
|
||||||
> RECOMMENDED: comfy-cli provides various features to manage ComfyUI from the CLI.
|
|
||||||
|
|
||||||
* **prerequisite: python 3, git**
|
|
||||||
|
|
||||||
Windows:
|
|
||||||
```commandline
|
|
||||||
python -m venv venv
|
|
||||||
venv\Scripts\activate
|
|
||||||
pip install comfy-cli
|
|
||||||
comfy install
|
|
||||||
```
|
|
||||||
|
|
||||||
Linux/OSX:
|
|
||||||
```commandline
|
|
||||||
python -m venv venv
|
|
||||||
. venv/bin/activate
|
|
||||||
pip install comfy-cli
|
|
||||||
comfy install
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Installation[method4] (Installation for linux+venv: ComfyUI + ComfyUI-Manager)
|
|
||||||
|
|
||||||
To install ComfyUI with ComfyUI-Manager on Linux using a venv environment, you can follow these steps:
|
|
||||||
* **prerequisite: python-is-python3, python3-venv, git**
|
|
||||||
|
|
||||||
1. Download [scripts/install-comfyui-venv-linux.sh](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-comfyui-venv-linux.sh) into empty install directory
|
|
||||||
- ComfyUI will be installed in the subdirectory of the specified directory, and the directory will contain the generated executable script.
|
|
||||||
2. `chmod +x install-comfyui-venv-linux.sh`
|
|
||||||
3. `./install-comfyui-venv-linux.sh`
|
|
||||||
|
|
||||||
### Installation Precautions
|
### Installation Precautions
|
||||||
* **DO**: `ComfyUI-Manager` files must be accurately located in the path `ComfyUI/custom_nodes/ComfyUI-Manager`
|
* **DO**: `ComfyUI-Manager` files must be accurately located in the path `ComfyUI/custom_nodes/ComfyUI-Manager`
|
||||||
* Installing in a compressed file format is not recommended.
|
* Installing in a compressed file format is not recommended.
|
||||||
@@ -79,7 +37,6 @@ To install ComfyUI with ComfyUI-Manager on Linux using a venv environment, you c
|
|||||||
* You have to rename `ComfyUI/custom_nodes/ComfyUI-Manager-main` to `ComfyUI/custom_nodes/ComfyUI-Manager`
|
* You have to rename `ComfyUI/custom_nodes/ComfyUI-Manager-main` to `ComfyUI/custom_nodes/ComfyUI-Manager`
|
||||||
|
|
||||||
|
|
||||||
You can execute ComfyUI by running either `./run_gpu.sh` or `./run_cpu.sh` depending on your system configuration.
|
|
||||||
|
|
||||||
## Colab Notebook
|
## Colab Notebook
|
||||||
This repository provides Colab notebooks that allow you to install and use ComfyUI, including ComfyUI-Manager. To use ComfyUI, [click on this link](https://colab.research.google.com/github/ltdrdata/ComfyUI-Manager/blob/main/notebooks/comfyui_colab_with_manager.ipynb).
|
This repository provides Colab notebooks that allow you to install and use ComfyUI, including ComfyUI-Manager. To use ComfyUI, [click on this link](https://colab.research.google.com/github/ltdrdata/ComfyUI-Manager/blob/main/notebooks/comfyui_colab_with_manager.ipynb).
|
||||||
@@ -87,31 +44,6 @@ This repository provides Colab notebooks that allow you to install and use Comfy
|
|||||||
* Support for basic installation of ComfyUI-Manager
|
* Support for basic installation of ComfyUI-Manager
|
||||||
* Support for automatically installing dependencies of custom nodes upon restarting Colab notebooks.
|
* Support for automatically installing dependencies of custom nodes upon restarting Colab notebooks.
|
||||||
|
|
||||||
## Changes
|
|
||||||
* **2.38** `Install Custom Nodes` menu is changed to `Custom Nodes Manager`.
|
|
||||||
* **2.21** [cm-cli](docs/en/cm-cli.md) tool is added.
|
|
||||||
* **2.4** Copy the connections of the nearest node by double-clicking.
|
|
||||||
* **2.2.3** Support Components System
|
|
||||||
* **0.29** Add `Update all` feature
|
|
||||||
* **0.25** support db channel
|
|
||||||
* You can directly modify the db channel settings in the `config.ini` file.
|
|
||||||
* If you want to maintain a new DB channel, please modify the `channels.list` and submit a PR.
|
|
||||||
* **0.23** support multiple selection
|
|
||||||
* **0.18.1** `skip update check` feature added.
|
|
||||||
* A feature that allows quickly opening windows in environments where update checks take a long time.
|
|
||||||
* **0.17.1** Bug fix for the issue where enable/disable of the web extension was not working. Compatibility patch for StableSwarmUI.
|
|
||||||
* Requires latest version of ComfyUI (Revision: 1240)
|
|
||||||
* **0.17** Support preview method setting feature.
|
|
||||||
* **0.14** Support robust update.
|
|
||||||
* **0.13** Support additional 'pip' section for install spec.
|
|
||||||
* **0.12** Better installation support for Windows.
|
|
||||||
* **0.9** Support keyword search in installer menu.
|
|
||||||
* **V0.7.1** Bug fix for the issue where updates were not being applied on Windows.
|
|
||||||
* **For those who have been using versions 0.6, please perform a manual git pull in the custom_nodes/ComfyUI-Manager directory.**
|
|
||||||
* **V0.7** To address the issue of a slow list refresh, separate the fetch update and update check processes.
|
|
||||||
* **V0.6** Support extension installation for missing nodes.
|
|
||||||
* **V0.5** Removed external git program dependencies.
|
|
||||||
|
|
||||||
|
|
||||||
## How To Use
|
## How To Use
|
||||||
|
|
||||||
@@ -320,6 +252,10 @@ NODE_CLASS_MAPPINGS.update({
|
|||||||
* Use `aria2` as downloader
|
* Use `aria2` as downloader
|
||||||
* [howto](docs/en/use_aria2.md)
|
* [howto](docs/en/use_aria2.md)
|
||||||
|
|
||||||
|
* If you add the item `skip_migration_check = True` to `config.ini`, it will not check whether there are nodes that can be migrated at startup.
|
||||||
|
* This option can be used if performance issues occur in a Colab+GDrive environment.
|
||||||
|
|
||||||
|
|
||||||
## Scanner
|
## Scanner
|
||||||
When you run the `scan.sh` script:
|
When you run the `scan.sh` script:
|
||||||
|
|
||||||
@@ -375,42 +311,6 @@ When you run the `scan.sh` script:
|
|||||||
* Update ComfyUI
|
* Update ComfyUI
|
||||||
|
|
||||||
|
|
||||||
## TODO: Unconventional form of custom node list
|
|
||||||
|
|
||||||
* https://github.com/diontimmer/Sample-Diffusion-ComfyUI-Extension
|
|
||||||
* https://github.com/senshilabs/NINJA-plugin
|
|
||||||
* https://github.com/MockbaTheBorg/Nodes
|
|
||||||
* https://github.com/StartHua/Comfyui_GPT_Story
|
|
||||||
* https://github.com/NielsGercama/comfyui_customsampling
|
|
||||||
* https://github.com/wrightdaniel2017/ComfyUI-VideoLipSync
|
|
||||||
* https://github.com/bxdsjs/ComfyUI-Image-preprocessing
|
|
||||||
* https://github.com/SMUELDigital/ComfyUI-ONSET
|
|
||||||
* https://github.com/SimithWang/comfyui-renameImages
|
|
||||||
* https://github.com/icefairy64/comfyui-model-tilt
|
|
||||||
* https://github.com/andrewharp/ComfyUI-EasyNodes
|
|
||||||
* https://github.com/SimithWang/comfyui-renameImages
|
|
||||||
* https://github.com/Tcheko243/ComfyUI-Photographer-Alpha7-Nodes
|
|
||||||
* https://github.com/Limbicnation/ComfyUINodeToolbox
|
|
||||||
* https://github.com/chenpipi0807/pip_longsize
|
|
||||||
* https://github.com/APZmedia/ComfyUI-APZmedia-srtTools
|
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
- [x] System displaying information about failed custom nodes import.
|
|
||||||
- [x] Guide for missing nodes in ComfyUI vanilla nodes.
|
|
||||||
- [x] Collision checking system for nodes with the same ID across extensions.
|
|
||||||
- [x] Template sharing system. (-> Component system based on Group Nodes)
|
|
||||||
- [x] 3rd party API system.
|
|
||||||
- [ ] Auto migration for custom nodes with changed structures.
|
|
||||||
- [ ] Version control feature for nodes.
|
|
||||||
- [ ] List of currently used custom nodes.
|
|
||||||
- [x] Download support multiple model download.
|
|
||||||
- [x] Model download via url.
|
|
||||||
- [x] List sorting (custom nodes).
|
|
||||||
- [x] List sorting (model).
|
|
||||||
- [ ] Provides description of node.
|
|
||||||
|
|
||||||
|
|
||||||
# Disclaimer
|
# Disclaimer
|
||||||
|
|
||||||
* This extension simply provides the convenience of installing custom nodes and does not guarantee their proper functioning.
|
* This extension simply provides the convenience of installing custom nodes and does not guarantee their proper functioning.
|
||||||
|
|||||||
39
__init__.py
39
__init__.py
@@ -1,16 +1,37 @@
|
|||||||
|
legacy_manager_core_path = None
|
||||||
|
manager_core_path = None
|
||||||
|
|
||||||
|
|
||||||
|
def is_manager_core_exists():
|
||||||
|
global legacy_manager_core_path
|
||||||
|
global manager_core_path
|
||||||
import os
|
import os
|
||||||
|
import folder_paths
|
||||||
|
|
||||||
cli_mode_flag = os.path.join(os.path.dirname(__file__), '.enable-cli-only-mode')
|
comfy_path = os.path.dirname(folder_paths.__file__)
|
||||||
|
legacy_manager_core_path = os.path.join(comfy_path, 'custom_nodes', 'manager-core')
|
||||||
|
manager_core_path = legacy_manager_core_path
|
||||||
|
|
||||||
if not os.path.exists(cli_mode_flag):
|
manager_core_path_file = os.path.join(comfy_path, 'manager_core_path.txt')
|
||||||
from .glob import manager_server
|
if os.path.exists(manager_core_path_file):
|
||||||
from .glob import share_3rdparty
|
with open(manager_core_path_file, 'r') as f:
|
||||||
WEB_DIRECTORY = "js"
|
manager_core_path = os.path.join(f.read().strip(), 'manager-core')
|
||||||
|
|
||||||
|
return os.path.exists(manager_core_path) or os.path.exists(legacy_manager_core_path)
|
||||||
|
|
||||||
|
|
||||||
|
if not is_manager_core_exists():
|
||||||
|
from .modules import migration_server
|
||||||
|
migration_server.manager_core_path = manager_core_path
|
||||||
|
|
||||||
|
WEB_DIRECTORY = "migration_js"
|
||||||
|
NODE_CLASS_MAPPINGS = {}
|
||||||
else:
|
else:
|
||||||
print(f"\n[ComfyUI-Manager] !! cli-only-mode is enabled !!\n")
|
# Main code
|
||||||
|
from .modules import manager_ext_server
|
||||||
|
from .modules import share_3rdparty
|
||||||
|
|
||||||
|
WEB_DIRECTORY = "js"
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {}
|
NODE_CLASS_MAPPINGS = {}
|
||||||
__all__ = ['NODE_CLASS_MAPPINGS']
|
__all__ = ['NODE_CLASS_MAPPINGS']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
default::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main
|
|
||||||
recent::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/new
|
|
||||||
legacy::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/legacy
|
legacy::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/legacy
|
||||||
forked::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/forked
|
forked::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/forked
|
||||||
dev::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/dev
|
dev::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/dev
|
||||||
|
|||||||
1
check.sh
1
check.sh
@@ -9,6 +9,7 @@ files=(
|
|||||||
"alter-list.json"
|
"alter-list.json"
|
||||||
"extension-node-map.json"
|
"extension-node-map.json"
|
||||||
"github-stats.json"
|
"github-stats.json"
|
||||||
|
"extras.json"
|
||||||
"node_db/new/custom-node-list.json"
|
"node_db/new/custom-node-list.json"
|
||||||
"node_db/new/model-list.json"
|
"node_db/new/model-list.json"
|
||||||
"node_db/new/extension-node-map.json"
|
"node_db/new/extension-node-map.json"
|
||||||
|
|||||||
26
extras.json
Normal file
26
extras.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"favorites": [
|
||||||
|
"comfyui-manager",
|
||||||
|
"comfyui_ipadapter_plus",
|
||||||
|
"comfyui-animatediff-evolved",
|
||||||
|
"comfyui_controlnet_aux",
|
||||||
|
"comfyui-impact-pack",
|
||||||
|
"comfyui-custom-scripts",
|
||||||
|
"comfyui-layerdiffuse",
|
||||||
|
"comfyui-liveportraitkj",
|
||||||
|
"aigodlike-comfyui-translation",
|
||||||
|
"comfyui-reactor-node",
|
||||||
|
"comfyui_instantid",
|
||||||
|
"sd-dynamic-thresholding",
|
||||||
|
"pr-was-node-suite-comfyui-47064894",
|
||||||
|
"comfyui-advancedliveportrait",
|
||||||
|
"comfyui_layerstyle",
|
||||||
|
"efficiency-nodes-comfyui",
|
||||||
|
"comfyui-crystools",
|
||||||
|
"comfyui-advanced-controlnet",
|
||||||
|
"comfyui-videohelpersuite",
|
||||||
|
"comfyui-kjnodes",
|
||||||
|
"comfy-mtb",
|
||||||
|
"comfyui_essentials"
|
||||||
|
]
|
||||||
|
}
|
||||||
450
git_helper.py
450
git_helper.py
@@ -1,450 +0,0 @@
|
|||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
import git
|
|
||||||
import configparser
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import yaml
|
|
||||||
import requests
|
|
||||||
from tqdm.auto import tqdm
|
|
||||||
from git.remote import RemoteProgress
|
|
||||||
|
|
||||||
|
|
||||||
def download_url(url, dest_folder, filename=None):
|
|
||||||
# Ensure the destination folder exists
|
|
||||||
if not os.path.exists(dest_folder):
|
|
||||||
os.makedirs(dest_folder)
|
|
||||||
|
|
||||||
# Extract filename from URL if not provided
|
|
||||||
if filename is None:
|
|
||||||
filename = os.path.basename(url)
|
|
||||||
|
|
||||||
# Full path to save the file
|
|
||||||
dest_path = os.path.join(dest_folder, filename)
|
|
||||||
|
|
||||||
# Download the file
|
|
||||||
response = requests.get(url, stream=True)
|
|
||||||
if response.status_code == 200:
|
|
||||||
with open(dest_path, 'wb') as file:
|
|
||||||
for chunk in response.iter_content(chunk_size=1024):
|
|
||||||
if chunk:
|
|
||||||
file.write(chunk)
|
|
||||||
else:
|
|
||||||
print(f"Failed to download file from {url}")
|
|
||||||
|
|
||||||
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
|
||||||
nodelist_path = os.path.join(os.path.dirname(__file__), "custom-node-list.json")
|
|
||||||
working_directory = os.getcwd()
|
|
||||||
|
|
||||||
if os.path.basename(working_directory) != 'custom_nodes':
|
|
||||||
print(f"WARN: This script should be executed in custom_nodes dir")
|
|
||||||
print(f"DBG: INFO {working_directory}")
|
|
||||||
print(f"DBG: INFO {sys.argv}")
|
|
||||||
# exit(-1)
|
|
||||||
|
|
||||||
|
|
||||||
class GitProgress(RemoteProgress):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.pbar = tqdm(ascii=True)
|
|
||||||
|
|
||||||
def update(self, op_code, cur_count, max_count=None, message=''):
|
|
||||||
self.pbar.total = max_count
|
|
||||||
self.pbar.n = cur_count
|
|
||||||
self.pbar.pos = 0
|
|
||||||
self.pbar.refresh()
|
|
||||||
|
|
||||||
|
|
||||||
def gitclone(custom_nodes_path, url, target_hash=None):
|
|
||||||
repo_name = os.path.splitext(os.path.basename(url))[0]
|
|
||||||
repo_path = os.path.join(custom_nodes_path, repo_name)
|
|
||||||
|
|
||||||
# Clone the repository from the remote URL
|
|
||||||
repo = git.Repo.clone_from(url, repo_path, recursive=True, progress=GitProgress())
|
|
||||||
|
|
||||||
if target_hash is not None:
|
|
||||||
print(f"CHECKOUT: {repo_name} [{target_hash}]")
|
|
||||||
repo.git.checkout(target_hash)
|
|
||||||
|
|
||||||
repo.git.clear_cache()
|
|
||||||
repo.close()
|
|
||||||
|
|
||||||
|
|
||||||
def gitcheck(path, do_fetch=False):
|
|
||||||
try:
|
|
||||||
# Fetch the latest commits from the remote repository
|
|
||||||
repo = git.Repo(path)
|
|
||||||
|
|
||||||
if repo.head.is_detached:
|
|
||||||
print("CUSTOM NODE CHECK: True")
|
|
||||||
return
|
|
||||||
|
|
||||||
current_branch = repo.active_branch
|
|
||||||
branch_name = current_branch.name
|
|
||||||
|
|
||||||
remote_name = current_branch.tracking_branch().remote_name
|
|
||||||
remote = repo.remote(name=remote_name)
|
|
||||||
|
|
||||||
if do_fetch:
|
|
||||||
remote.fetch()
|
|
||||||
|
|
||||||
# Get the current commit hash and the commit hash of the remote branch
|
|
||||||
commit_hash = repo.head.commit.hexsha
|
|
||||||
remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
|
|
||||||
|
|
||||||
# Compare the commit hashes to determine if the local repository is behind the remote repository
|
|
||||||
if commit_hash != remote_commit_hash:
|
|
||||||
# Get the commit dates
|
|
||||||
commit_date = repo.head.commit.committed_datetime
|
|
||||||
remote_commit_date = repo.refs[f'{remote_name}/{branch_name}'].object.committed_datetime
|
|
||||||
|
|
||||||
# Compare the commit dates to determine if the local repository is behind the remote repository
|
|
||||||
if commit_date < remote_commit_date:
|
|
||||||
print("CUSTOM NODE CHECK: True")
|
|
||||||
else:
|
|
||||||
print("CUSTOM NODE CHECK: False")
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("CUSTOM NODE CHECK: Error")
|
|
||||||
|
|
||||||
|
|
||||||
def switch_to_default_branch(repo):
|
|
||||||
show_result = repo.git.remote("show", "origin")
|
|
||||||
matches = re.search(r"\s*HEAD branch:\s*(.*)", show_result)
|
|
||||||
if matches:
|
|
||||||
default_branch = matches.group(1)
|
|
||||||
repo.git.checkout(default_branch)
|
|
||||||
|
|
||||||
|
|
||||||
def gitpull(path):
|
|
||||||
# Check if the path is a git repository
|
|
||||||
if not os.path.exists(os.path.join(path, '.git')):
|
|
||||||
raise ValueError('Not a git repository')
|
|
||||||
|
|
||||||
# Pull the latest changes from the remote repository
|
|
||||||
repo = git.Repo(path)
|
|
||||||
if repo.is_dirty():
|
|
||||||
repo.git.stash()
|
|
||||||
|
|
||||||
commit_hash = repo.head.commit.hexsha
|
|
||||||
try:
|
|
||||||
if repo.head.is_detached:
|
|
||||||
switch_to_default_branch(repo)
|
|
||||||
|
|
||||||
current_branch = repo.active_branch
|
|
||||||
branch_name = current_branch.name
|
|
||||||
|
|
||||||
remote_name = current_branch.tracking_branch().remote_name
|
|
||||||
remote = repo.remote(name=remote_name)
|
|
||||||
|
|
||||||
remote.fetch()
|
|
||||||
remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
|
|
||||||
|
|
||||||
if commit_hash == remote_commit_hash:
|
|
||||||
print("CUSTOM NODE PULL: None") # there is no update
|
|
||||||
repo.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
remote.pull()
|
|
||||||
|
|
||||||
repo.git.submodule('update', '--init', '--recursive')
|
|
||||||
new_commit_hash = repo.head.commit.hexsha
|
|
||||||
|
|
||||||
if commit_hash != new_commit_hash:
|
|
||||||
print("CUSTOM NODE PULL: Success") # update success
|
|
||||||
else:
|
|
||||||
print("CUSTOM NODE PULL: Fail") # update fail
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print("CUSTOM NODE PULL: Fail") # unknown git error
|
|
||||||
|
|
||||||
repo.close()
|
|
||||||
|
|
||||||
|
|
||||||
def checkout_comfyui_hash(target_hash):
|
|
||||||
repo_path = os.path.abspath(os.path.join(working_directory, '..')) # ComfyUI dir
|
|
||||||
|
|
||||||
repo = git.Repo(repo_path)
|
|
||||||
commit_hash = repo.head.commit.hexsha
|
|
||||||
|
|
||||||
if commit_hash != target_hash:
|
|
||||||
try:
|
|
||||||
print(f"CHECKOUT: ComfyUI [{target_hash}]")
|
|
||||||
repo.git.checkout(target_hash)
|
|
||||||
except git.GitCommandError as e:
|
|
||||||
print(f"Error checking out the ComfyUI: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
def checkout_custom_node_hash(git_custom_node_infos):
|
|
||||||
repo_name_to_url = {}
|
|
||||||
|
|
||||||
for url in git_custom_node_infos.keys():
|
|
||||||
repo_name = url.split('/')[-1]
|
|
||||||
|
|
||||||
if repo_name.endswith('.git'):
|
|
||||||
repo_name = repo_name[:-4]
|
|
||||||
|
|
||||||
repo_name_to_url[repo_name] = url
|
|
||||||
|
|
||||||
for path in os.listdir(working_directory):
|
|
||||||
if path.endswith("ComfyUI-Manager"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
fullpath = os.path.join(working_directory, path)
|
|
||||||
|
|
||||||
if os.path.isdir(fullpath):
|
|
||||||
is_disabled = path.endswith(".disabled")
|
|
||||||
|
|
||||||
try:
|
|
||||||
git_dir = os.path.join(fullpath, '.git')
|
|
||||||
if not os.path.exists(git_dir):
|
|
||||||
continue
|
|
||||||
|
|
||||||
need_checkout = False
|
|
||||||
repo_name = os.path.basename(fullpath)
|
|
||||||
|
|
||||||
if repo_name.endswith('.disabled'):
|
|
||||||
repo_name = repo_name[:-9]
|
|
||||||
|
|
||||||
if repo_name not in repo_name_to_url:
|
|
||||||
if not is_disabled:
|
|
||||||
# should be disabled
|
|
||||||
print(f"DISABLE: {repo_name}")
|
|
||||||
new_path = fullpath + ".disabled"
|
|
||||||
os.rename(fullpath, new_path)
|
|
||||||
need_checkout = False
|
|
||||||
else:
|
|
||||||
item = git_custom_node_infos[repo_name_to_url[repo_name]]
|
|
||||||
if item['disabled'] and is_disabled:
|
|
||||||
pass
|
|
||||||
elif item['disabled'] and not is_disabled:
|
|
||||||
# disable
|
|
||||||
print(f"DISABLE: {repo_name}")
|
|
||||||
new_path = fullpath + ".disabled"
|
|
||||||
os.rename(fullpath, new_path)
|
|
||||||
|
|
||||||
elif not item['disabled'] and is_disabled:
|
|
||||||
# enable
|
|
||||||
print(f"ENABLE: {repo_name}")
|
|
||||||
new_path = fullpath[:-9]
|
|
||||||
os.rename(fullpath, new_path)
|
|
||||||
fullpath = new_path
|
|
||||||
need_checkout = True
|
|
||||||
else:
|
|
||||||
need_checkout = True
|
|
||||||
|
|
||||||
if need_checkout:
|
|
||||||
repo = git.Repo(fullpath)
|
|
||||||
commit_hash = repo.head.commit.hexsha
|
|
||||||
|
|
||||||
if commit_hash != item['hash']:
|
|
||||||
print(f"CHECKOUT: {repo_name} [{item['hash']}]")
|
|
||||||
repo.git.checkout(item['hash'])
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
print(f"Failed to restore snapshots for the custom node '{path}'")
|
|
||||||
|
|
||||||
# clone missing
|
|
||||||
for k, v in git_custom_node_infos.items():
|
|
||||||
if not v['disabled']:
|
|
||||||
repo_name = k.split('/')[-1]
|
|
||||||
if repo_name.endswith('.git'):
|
|
||||||
repo_name = repo_name[:-4]
|
|
||||||
|
|
||||||
path = os.path.join(working_directory, repo_name)
|
|
||||||
if not os.path.exists(path):
|
|
||||||
print(f"CLONE: {path}")
|
|
||||||
gitclone(working_directory, k, v['hash'])
|
|
||||||
|
|
||||||
|
|
||||||
def invalidate_custom_node_file(file_custom_node_infos):
|
|
||||||
global nodelist_path
|
|
||||||
|
|
||||||
enabled_set = set()
|
|
||||||
for item in file_custom_node_infos:
|
|
||||||
if not item['disabled']:
|
|
||||||
enabled_set.add(item['filename'])
|
|
||||||
|
|
||||||
for path in os.listdir(working_directory):
|
|
||||||
fullpath = os.path.join(working_directory, path)
|
|
||||||
|
|
||||||
if not os.path.isdir(fullpath) and fullpath.endswith('.py'):
|
|
||||||
if path not in enabled_set:
|
|
||||||
print(f"DISABLE: {path}")
|
|
||||||
new_path = fullpath+'.disabled'
|
|
||||||
os.rename(fullpath, new_path)
|
|
||||||
|
|
||||||
elif not os.path.isdir(fullpath) and fullpath.endswith('.py.disabled'):
|
|
||||||
path = path[:-9]
|
|
||||||
if path in enabled_set:
|
|
||||||
print(f"ENABLE: {path}")
|
|
||||||
new_path = fullpath[:-9]
|
|
||||||
os.rename(fullpath, new_path)
|
|
||||||
|
|
||||||
# download missing: just support for 'copy' style
|
|
||||||
py_to_url = {}
|
|
||||||
|
|
||||||
with open(nodelist_path, 'r', encoding="UTF-8") as json_file:
|
|
||||||
info = json.load(json_file)
|
|
||||||
for item in info['custom_nodes']:
|
|
||||||
if item['install_type'] == 'copy':
|
|
||||||
for url in item['files']:
|
|
||||||
if url.endswith('.py'):
|
|
||||||
py = url.split('/')[-1]
|
|
||||||
py_to_url[py] = url
|
|
||||||
|
|
||||||
for item in file_custom_node_infos:
|
|
||||||
filename = item['filename']
|
|
||||||
if not item['disabled']:
|
|
||||||
target_path = os.path.join(working_directory, filename)
|
|
||||||
|
|
||||||
if not os.path.exists(target_path) and filename in py_to_url:
|
|
||||||
url = py_to_url[filename]
|
|
||||||
print(f"DOWNLOAD: {filename}")
|
|
||||||
download_url(url, working_directory)
|
|
||||||
|
|
||||||
|
|
||||||
def apply_snapshot(target):
|
|
||||||
try:
|
|
||||||
path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{target}")
|
|
||||||
if os.path.exists(path):
|
|
||||||
if not target.endswith('.json') and not target.endswith('.yaml'):
|
|
||||||
print(f"Snapshot file not found: `{path}`")
|
|
||||||
print("APPLY SNAPSHOT: False")
|
|
||||||
return None
|
|
||||||
|
|
||||||
with open(path, 'r', encoding="UTF-8") as snapshot_file:
|
|
||||||
if target.endswith('.json'):
|
|
||||||
info = json.load(snapshot_file)
|
|
||||||
elif target.endswith('.yaml'):
|
|
||||||
info = yaml.load(snapshot_file, Loader=yaml.SafeLoader)
|
|
||||||
info = info['custom_nodes']
|
|
||||||
else:
|
|
||||||
# impossible case
|
|
||||||
print("APPLY SNAPSHOT: False")
|
|
||||||
return None
|
|
||||||
|
|
||||||
comfyui_hash = info['comfyui']
|
|
||||||
git_custom_node_infos = info['git_custom_nodes']
|
|
||||||
file_custom_node_infos = info['file_custom_nodes']
|
|
||||||
|
|
||||||
checkout_comfyui_hash(comfyui_hash)
|
|
||||||
checkout_custom_node_hash(git_custom_node_infos)
|
|
||||||
invalidate_custom_node_file(file_custom_node_infos)
|
|
||||||
|
|
||||||
print("APPLY SNAPSHOT: True")
|
|
||||||
if 'pips' in info:
|
|
||||||
return info['pips']
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
print(f"Snapshot file not found: `{path}`")
|
|
||||||
print("APPLY SNAPSHOT: False")
|
|
||||||
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
traceback.print_exc()
|
|
||||||
print("APPLY SNAPSHOT: False")
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def restore_pip_snapshot(pips, options):
|
|
||||||
non_url = []
|
|
||||||
local_url = []
|
|
||||||
non_local_url = []
|
|
||||||
for k, v in pips.items():
|
|
||||||
if v == "":
|
|
||||||
non_url.append(k)
|
|
||||||
else:
|
|
||||||
if v.startswith('file:'):
|
|
||||||
local_url.append(v)
|
|
||||||
else:
|
|
||||||
non_local_url.append(v)
|
|
||||||
|
|
||||||
failed = []
|
|
||||||
if '--pip-non-url' in options:
|
|
||||||
# try all at once
|
|
||||||
res = 1
|
|
||||||
try:
|
|
||||||
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install'] + non_url)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# fallback
|
|
||||||
if res != 0:
|
|
||||||
for x in non_url:
|
|
||||||
res = 1
|
|
||||||
try:
|
|
||||||
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if res != 0:
|
|
||||||
failed.append(x)
|
|
||||||
|
|
||||||
if '--pip-non-local-url' in options:
|
|
||||||
for x in non_local_url:
|
|
||||||
res = 1
|
|
||||||
try:
|
|
||||||
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if res != 0:
|
|
||||||
failed.append(x)
|
|
||||||
|
|
||||||
if '--pip-local-url' in options:
|
|
||||||
for x in local_url:
|
|
||||||
res = 1
|
|
||||||
try:
|
|
||||||
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if res != 0:
|
|
||||||
failed.append(x)
|
|
||||||
|
|
||||||
print(f"Installation failed for pip packages: {failed}")
|
|
||||||
|
|
||||||
|
|
||||||
def setup_environment():
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(config_path)
|
|
||||||
if 'default' in config and 'git_exe' in config['default'] and config['default']['git_exe'] != '':
|
|
||||||
git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=config['default']['git_exe'])
|
|
||||||
|
|
||||||
|
|
||||||
setup_environment()
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
if sys.argv[1] == "--clone":
|
|
||||||
gitclone(sys.argv[2], sys.argv[3])
|
|
||||||
elif sys.argv[1] == "--check":
|
|
||||||
gitcheck(sys.argv[2], False)
|
|
||||||
elif sys.argv[1] == "--fetch":
|
|
||||||
gitcheck(sys.argv[2], True)
|
|
||||||
elif sys.argv[1] == "--pull":
|
|
||||||
gitpull(sys.argv[2])
|
|
||||||
elif sys.argv[1] == "--apply-snapshot":
|
|
||||||
options = set()
|
|
||||||
for x in sys.argv:
|
|
||||||
if x in ['--pip-non-url', '--pip-local-url', '--pip-non-local-url']:
|
|
||||||
options.add(x)
|
|
||||||
|
|
||||||
pips = apply_snapshot(sys.argv[2])
|
|
||||||
|
|
||||||
if pips and len(options) > 0:
|
|
||||||
restore_pip_snapshot(pips, options)
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import traceback
|
|
||||||
|
|
||||||
#
|
|
||||||
# Global Var
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# import cm_global
|
|
||||||
# cm_global.variables['comfyui.revision'] = 1832
|
|
||||||
# print(f"log mode: {cm_global.variables['logger.enabled']}")
|
|
||||||
#
|
|
||||||
variables = {}
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Global API
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# [register API]
|
|
||||||
# import cm_global
|
|
||||||
#
|
|
||||||
# def api_hello(msg):
|
|
||||||
# print(f"hello: {msg}")
|
|
||||||
# return msg
|
|
||||||
#
|
|
||||||
# cm_global.register_api('hello', api_hello)
|
|
||||||
#
|
|
||||||
# [use API]
|
|
||||||
# import cm_global
|
|
||||||
#
|
|
||||||
# test = cm_global.try_call(api='hello', msg='an example')
|
|
||||||
# print(f"'{test}' is returned")
|
|
||||||
#
|
|
||||||
|
|
||||||
APIs = {}
|
|
||||||
|
|
||||||
|
|
||||||
def register_api(k, f):
|
|
||||||
global APIs
|
|
||||||
APIs[k] = f
|
|
||||||
|
|
||||||
|
|
||||||
def try_call(**kwargs):
|
|
||||||
if 'api' in kwargs:
|
|
||||||
api_name = kwargs['api']
|
|
||||||
try:
|
|
||||||
api = APIs.get(api_name)
|
|
||||||
if api is not None:
|
|
||||||
del kwargs['api']
|
|
||||||
return api(**kwargs)
|
|
||||||
else:
|
|
||||||
print(f"WARN: The '{kwargs['api']}' API has not been registered.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: An exception occurred while calling the '{api_name}' API.")
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Extension Info
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# import cm_global
|
|
||||||
#
|
|
||||||
# cm_global.extension_infos['my_extension'] = {'version': [0, 1], 'name': 'me', 'description': 'example extension', }
|
|
||||||
#
|
|
||||||
extension_infos = {}
|
|
||||||
|
|
||||||
on_extension_registered_handlers = {}
|
|
||||||
|
|
||||||
|
|
||||||
def register_extension(extension_name, v):
|
|
||||||
global extension_infos
|
|
||||||
global on_extension_registered_handlers
|
|
||||||
extension_infos[extension_name] = v
|
|
||||||
|
|
||||||
if extension_name in on_extension_registered_handlers:
|
|
||||||
for k, f in on_extension_registered_handlers[extension_name]:
|
|
||||||
try:
|
|
||||||
f(extension_name, v)
|
|
||||||
except Exception:
|
|
||||||
print(f"[ERROR] '{k}' on_extension_registered_handlers")
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
del on_extension_registered_handlers[extension_name]
|
|
||||||
|
|
||||||
|
|
||||||
def add_on_extension_registered(k, extension_name, f):
|
|
||||||
global on_extension_registered_handlers
|
|
||||||
if extension_name in extension_infos:
|
|
||||||
try:
|
|
||||||
v = extension_infos[extension_name]
|
|
||||||
f(extension_name, v)
|
|
||||||
except Exception:
|
|
||||||
print(f"[ERROR] '{k}' on_extension_registered_handler")
|
|
||||||
traceback.print_exc()
|
|
||||||
else:
|
|
||||||
if extension_name not in on_extension_registered_handlers:
|
|
||||||
on_extension_registered_handlers[extension_name] = []
|
|
||||||
|
|
||||||
on_extension_registered_handlers[extension_name].append((k, f))
|
|
||||||
|
|
||||||
|
|
||||||
def add_on_revision_detected(k, f):
|
|
||||||
if 'comfyui.revision' in variables:
|
|
||||||
try:
|
|
||||||
f(variables['comfyui.revision'])
|
|
||||||
except Exception:
|
|
||||||
print(f"[ERROR] '{k}' on_revision_detected_handler")
|
|
||||||
traceback.print_exc()
|
|
||||||
else:
|
|
||||||
variables['cm.on_revision_detected_handler'].append((k, f))
|
|
||||||
1238
glob/manager_core.py
1238
glob/manager_core.py
File diff suppressed because it is too large
Load Diff
@@ -1,70 +0,0 @@
|
|||||||
import os
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
aria2 = os.getenv('COMFYUI_MANAGER_ARIA2_SERVER')
|
|
||||||
HF_ENDPOINT = os.getenv('HF_ENDPOINT')
|
|
||||||
|
|
||||||
if aria2 is not None:
|
|
||||||
secret = os.getenv('COMFYUI_MANAGER_ARIA2_SECRET')
|
|
||||||
url = urlparse(aria2)
|
|
||||||
port = url.port
|
|
||||||
host = url.scheme + '://' + url.hostname
|
|
||||||
import aria2p
|
|
||||||
|
|
||||||
aria2 = aria2p.API(aria2p.Client(host=host, port=port, secret=secret))
|
|
||||||
|
|
||||||
|
|
||||||
def download_url(model_url: str, model_dir: str, filename: str):
|
|
||||||
if aria2:
|
|
||||||
return aria2_download_url(model_url, model_dir, filename)
|
|
||||||
else:
|
|
||||||
from torchvision.datasets.utils import download_url as torchvision_download_url
|
|
||||||
|
|
||||||
return torchvision_download_url(model_url, model_dir, filename)
|
|
||||||
|
|
||||||
|
|
||||||
def aria2_find_task(dir: str, filename: str):
|
|
||||||
target = os.path.join(dir, filename)
|
|
||||||
|
|
||||||
downloads = aria2.get_downloads()
|
|
||||||
|
|
||||||
for download in downloads:
|
|
||||||
for file in download.files:
|
|
||||||
if file.is_metadata:
|
|
||||||
continue
|
|
||||||
if str(file.path) == target:
|
|
||||||
return download
|
|
||||||
|
|
||||||
|
|
||||||
def aria2_download_url(model_url: str, model_dir: str, filename: str):
|
|
||||||
import manager_core as core
|
|
||||||
import tqdm
|
|
||||||
import time
|
|
||||||
|
|
||||||
if model_dir.startswith(core.comfy_path):
|
|
||||||
model_dir = model_dir[len(core.comfy_path) :]
|
|
||||||
|
|
||||||
if HF_ENDPOINT:
|
|
||||||
model_url = model_url.replace('https://huggingface.co', HF_ENDPOINT)
|
|
||||||
|
|
||||||
download_dir = model_dir if model_dir.startswith('/') else os.path.join('/models', model_dir)
|
|
||||||
|
|
||||||
download = aria2_find_task(download_dir, filename)
|
|
||||||
if download is None:
|
|
||||||
options = {'dir': download_dir, 'out': filename}
|
|
||||||
download = aria2.add(model_url, options)[0]
|
|
||||||
|
|
||||||
if download.is_active:
|
|
||||||
with tqdm.tqdm(
|
|
||||||
total=download.total_length,
|
|
||||||
bar_format='{l_bar}{bar}{r_bar}',
|
|
||||||
desc=filename,
|
|
||||||
unit='B',
|
|
||||||
unit_scale=True,
|
|
||||||
) as progress_bar:
|
|
||||||
while download.is_active:
|
|
||||||
if progress_bar.total == 0 and download.total_length != 0:
|
|
||||||
progress_bar.reset(download.total_length)
|
|
||||||
progress_bar.update(download.completed_length - progress_bar.n)
|
|
||||||
time.sleep(1)
|
|
||||||
download.update()
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,64 +0,0 @@
|
|||||||
# DON'T USE StrictVersion - cannot handle pre_release version
|
|
||||||
# try:
|
|
||||||
# from distutils.version import StrictVersion
|
|
||||||
# except:
|
|
||||||
# print(f"[ComfyUI-Manager] 'distutils' package not found. Activating fallback mode for compatibility.")
|
|
||||||
class StrictVersion:
|
|
||||||
def __init__(self, version_string):
|
|
||||||
self.version_string = version_string
|
|
||||||
self.major = 0
|
|
||||||
self.minor = 0
|
|
||||||
self.patch = 0
|
|
||||||
self.pre_release = None
|
|
||||||
self.parse_version_string()
|
|
||||||
|
|
||||||
def parse_version_string(self):
|
|
||||||
parts = self.version_string.split('.')
|
|
||||||
if not parts:
|
|
||||||
raise ValueError("Version string must not be empty")
|
|
||||||
|
|
||||||
self.major = int(parts[0])
|
|
||||||
self.minor = int(parts[1]) if len(parts) > 1 else 0
|
|
||||||
self.patch = int(parts[2]) if len(parts) > 2 else 0
|
|
||||||
|
|
||||||
# Handling pre-release versions if present
|
|
||||||
if len(parts) > 3:
|
|
||||||
self.pre_release = parts[3]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
version = f"{self.major}.{self.minor}.{self.patch}"
|
|
||||||
if self.pre_release:
|
|
||||||
version += f"-{self.pre_release}"
|
|
||||||
return version
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (self.major, self.minor, self.patch, self.pre_release) == \
|
|
||||||
(other.major, other.minor, other.patch, other.pre_release)
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
if (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch):
|
|
||||||
return self.pre_release_compare(self.pre_release, other.pre_release) < 0
|
|
||||||
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def pre_release_compare(pre1, pre2):
|
|
||||||
if pre1 == pre2:
|
|
||||||
return 0
|
|
||||||
if pre1 is None:
|
|
||||||
return 1
|
|
||||||
if pre2 is None:
|
|
||||||
return -1
|
|
||||||
return -1 if pre1 < pre2 else 1
|
|
||||||
|
|
||||||
def __le__(self, other):
|
|
||||||
return self == other or self < other
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return not self <= other
|
|
||||||
|
|
||||||
def __ge__(self, other):
|
|
||||||
return not self < other
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self == other
|
|
||||||
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def security_check():
|
|
||||||
print("[START] Security scan")
|
|
||||||
|
|
||||||
custom_nodes_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
||||||
comfyui_path = os.path.abspath(os.path.join(custom_nodes_path, '..'))
|
|
||||||
|
|
||||||
guide = {
|
|
||||||
"ComfyUI_LLMVISION": """
|
|
||||||
0.Remove ComfyUI\\custom_nodes\\ComfyUI_LLMVISION.
|
|
||||||
1.Remove pip packages: openai-1.16.3.dist-info, anthropic-0.21.4.dist-info, openai-1.30.2.dist-info, anthropic-0.21.5.dist-info, anthropic-0.26.1.dist-info, %LocalAppData%\\rundll64.exe
|
|
||||||
(For portable versions, it is recommended to reinstall. If you are using a venv, it is advised to recreate the venv.)
|
|
||||||
2.Remove these files in your system: lib/browser/admin.py, Cadmino.py, Fadmino.py, VISION-D.exe, BeamNG.UI.exe
|
|
||||||
3.Check your Windows registry for the key listed above and remove it.
|
|
||||||
(HKEY_CURRENT_USER\\Software\\OpenAICLI)
|
|
||||||
4.Run a malware scanner.
|
|
||||||
5.Change all of your passwords, everywhere.
|
|
||||||
|
|
||||||
(Reinstall OS is recommended.)
|
|
||||||
\n
|
|
||||||
Detailed information: https://old.reddit.com/r/comfyui/comments/1dbls5n/psa_if_youve_used_the_comfyui_llmvision_node_from/
|
|
||||||
""",
|
|
||||||
"lolMiner": """
|
|
||||||
1. Remove pip packages: lolMiner*
|
|
||||||
2. Remove files: lolMiner*, 4G_Ethash_Linux_Readme.txt, mine* in ComfyUI dir.
|
|
||||||
|
|
||||||
(Reinstall ComfyUI is recommended.)
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
node_blacklist = {"ComfyUI_LLMVISION": "ComfyUI_LLMVISION"}
|
|
||||||
|
|
||||||
pip_blacklist = {"AppleBotzz": "ComfyUI_LLMVISION"}
|
|
||||||
|
|
||||||
file_blacklist = {
|
|
||||||
"ComfyUI_LLMVISION": ["%LocalAppData%\\rundll64.exe"],
|
|
||||||
"lolMiner": [os.path.join(comfyui_path, 'lolMiner')]
|
|
||||||
}
|
|
||||||
|
|
||||||
installed_pips = subprocess.check_output([sys.executable, '-m', "pip", "freeze"], text=True)
|
|
||||||
|
|
||||||
detected = set()
|
|
||||||
try:
|
|
||||||
anthropic_info = subprocess.check_output([sys.executable, '-m', "pip", "show", "anthropic"], text=True, stderr=subprocess.DEVNULL)
|
|
||||||
anthropic_reqs = [x for x in anthropic_info.split('\n') if x.startswith("Requires")][0].split(': ')[1]
|
|
||||||
if "pycrypto" in anthropic_reqs:
|
|
||||||
location = [x for x in anthropic_info.split('\n') if x.startswith("Location")][0].split(': ')[1]
|
|
||||||
for fi in os.listdir(location):
|
|
||||||
if fi.startswith("anthropic"):
|
|
||||||
guide["ComfyUI_LLMVISION"] = f"\n0.Remove {os.path.join(location, fi)}" + guide["ComfyUI_LLMVISION"]
|
|
||||||
detected.add("ComfyUI_LLMVISION")
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for k, v in node_blacklist.items():
|
|
||||||
if os.path.exists(os.path.join(custom_nodes_path, k)):
|
|
||||||
print(f"[SECURITY ALERT] custom node '{k}' is dangerous.")
|
|
||||||
detected.add(v)
|
|
||||||
|
|
||||||
for k, v in pip_blacklist.items():
|
|
||||||
if k in installed_pips:
|
|
||||||
detected.add(v)
|
|
||||||
break
|
|
||||||
|
|
||||||
for k, v in file_blacklist.items():
|
|
||||||
for x in v:
|
|
||||||
if os.path.exists(os.path.expandvars(x)):
|
|
||||||
detected.add(k)
|
|
||||||
break
|
|
||||||
|
|
||||||
if len(detected) > 0:
|
|
||||||
for line in installed_pips.split('\n'):
|
|
||||||
for k, v in pip_blacklist.items():
|
|
||||||
if k in line:
|
|
||||||
print(f"[SECURITY ALERT] '{line}' is dangerous.")
|
|
||||||
|
|
||||||
print("\n########################################################################")
|
|
||||||
print(" Malware has been detected, forcibly terminating ComfyUI execution.")
|
|
||||||
print("########################################################################\n")
|
|
||||||
|
|
||||||
for x in detected:
|
|
||||||
print(f"\n======== TARGET: {x} =========")
|
|
||||||
print(f"\nTODO:")
|
|
||||||
print(guide.get(x))
|
|
||||||
|
|
||||||
exit(-1)
|
|
||||||
|
|
||||||
print("[DONE] Security scan")
|
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
showYouMLShareDialog
|
showYouMLShareDialog
|
||||||
} from "./comfyui-share-common.js";
|
} from "./comfyui-share-common.js";
|
||||||
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
|
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
|
||||||
import { free_models, install_pip, install_via_git_url, manager_instance, rebootAPI, setManagerInstance, show_message } from "./common.js";
|
import { free_models, install_pip, install_via_git_url, manager_instance, rebootAPI, migrateAPI, setManagerInstance, show_message } from "./common.js";
|
||||||
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
|
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
|
||||||
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
||||||
import { ModelManager } from "./model-manager.js";
|
import { ModelManager } from "./model-manager.js";
|
||||||
@@ -101,24 +101,6 @@ docStyle.innerHTML = `
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cm-channel-badge {
|
|
||||||
color: white;
|
|
||||||
background-color: #AA0000;
|
|
||||||
width: 220px;
|
|
||||||
height: 23px;
|
|
||||||
font-size: 13px;
|
|
||||||
border-radius: 5px;
|
|
||||||
left: 5px;
|
|
||||||
top: 5px;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
float: left;
|
|
||||||
vertical-align: middle;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#custom-nodes-grid a {
|
#custom-nodes-grid a {
|
||||||
color: #5555FF;
|
color: #5555FF;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -239,9 +221,9 @@ function is_legacy_front() {
|
|||||||
document.head.appendChild(docStyle);
|
document.head.appendChild(docStyle);
|
||||||
|
|
||||||
var update_comfyui_button = null;
|
var update_comfyui_button = null;
|
||||||
|
var switch_comfyui_button = null;
|
||||||
var fetch_updates_button = null;
|
var fetch_updates_button = null;
|
||||||
var update_all_button = null;
|
var update_all_button = null;
|
||||||
var badge_mode = "none";
|
|
||||||
let share_option = 'all';
|
let share_option = 'all';
|
||||||
|
|
||||||
// copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts
|
// copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts
|
||||||
@@ -287,6 +269,18 @@ const style = `
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.cm-button-orange {
|
||||||
|
width: 310px;
|
||||||
|
height: 30px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 17px !important;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: orange !important;
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-experimental-button {
|
.cm-experimental-button {
|
||||||
width: 290px;
|
width: 290px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
@@ -412,13 +406,6 @@ const style = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function init_badge_mode() {
|
|
||||||
api.fetchApi('/manager/badge_mode')
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(data => { badge_mode = data; })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function init_share_option() {
|
async function init_share_option() {
|
||||||
api.fetchApi('/manager/share_option')
|
api.fetchApi('/manager/share_option')
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
@@ -435,7 +422,6 @@ async function init_notice(notice) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await init_badge_mode();
|
|
||||||
await init_share_option();
|
await init_share_option();
|
||||||
|
|
||||||
async function fetchNicknames() {
|
async function fetchNicknames() {
|
||||||
@@ -502,65 +488,6 @@ function getNickname(node, nodename) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawBadge(node, orig, restArgs) {
|
|
||||||
let ctx = restArgs[0];
|
|
||||||
const r = orig?.apply?.(node, restArgs);
|
|
||||||
|
|
||||||
if (!node.flags.collapsed && badge_mode != 'none' && node.constructor.title_mode != LiteGraph.NO_TITLE) {
|
|
||||||
let text = "";
|
|
||||||
if (badge_mode.startsWith('id_nick'))
|
|
||||||
text = `#${node.id} `;
|
|
||||||
|
|
||||||
let nick = node.getNickname();
|
|
||||||
if (nick) {
|
|
||||||
if (nick == 'ComfyUI') {
|
|
||||||
if(badge_mode.endsWith('hide')) {
|
|
||||||
nick = "";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nick = "🦊"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nick.length > 25) {
|
|
||||||
text += nick.substring(0, 23) + "..";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
text += nick;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text != "") {
|
|
||||||
let fgColor = "white";
|
|
||||||
let bgColor = "#0F1F0F";
|
|
||||||
let visible = true;
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.font = "12px sans-serif";
|
|
||||||
const sz = ctx.measureText(text);
|
|
||||||
ctx.fillStyle = bgColor;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.roundRect(node.size[0] - sz.width - 12, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
ctx.fillStyle = fgColor;
|
|
||||||
ctx.fillText(text, node.size[0] - sz.width - 6, -LiteGraph.NODE_TITLE_HEIGHT - 6);
|
|
||||||
ctx.restore();
|
|
||||||
|
|
||||||
if (node.has_errors) {
|
|
||||||
ctx.save();
|
|
||||||
ctx.font = "bold 14px sans-serif";
|
|
||||||
const sz2 = ctx.measureText(node.type);
|
|
||||||
ctx.fillStyle = 'white';
|
|
||||||
ctx.fillText(node.type, node.size[0] / 2 - sz2.width / 2, node.size[1] / 2);
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function updateComfyUI() {
|
async function updateComfyUI() {
|
||||||
let prev_text = update_comfyui_button.innerText;
|
let prev_text = update_comfyui_button.innerText;
|
||||||
update_comfyui_button.innerText = "Updating ComfyUI...";
|
update_comfyui_button.innerText = "Updating ComfyUI...";
|
||||||
@@ -595,6 +522,154 @@ async function updateComfyUI() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showVersionSelectorDialog(versions, current, onSelect) {
|
||||||
|
const dialog = new ComfyDialog();
|
||||||
|
dialog.element.style.zIndex = 100003;
|
||||||
|
dialog.element.style.width = "300px";
|
||||||
|
dialog.element.style.padding = "0";
|
||||||
|
dialog.element.style.backgroundColor = "#2a2a2a";
|
||||||
|
dialog.element.style.border = "1px solid #3a3a3a";
|
||||||
|
dialog.element.style.borderRadius = "8px";
|
||||||
|
dialog.element.style.boxSizing = "border-box";
|
||||||
|
dialog.element.style.overflow = "hidden";
|
||||||
|
|
||||||
|
const contentStyle = {
|
||||||
|
width: "300px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "20px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
gap: "15px"
|
||||||
|
};
|
||||||
|
|
||||||
|
let selectedVersion = versions[0];
|
||||||
|
|
||||||
|
const versionList = $el("select", {
|
||||||
|
multiple: true,
|
||||||
|
size: Math.min(10, versions.length),
|
||||||
|
style: {
|
||||||
|
width: "260px",
|
||||||
|
height: "auto",
|
||||||
|
backgroundColor: "#383838",
|
||||||
|
color: "#ffffff",
|
||||||
|
border: "1px solid #4a4a4a",
|
||||||
|
borderRadius: "4px",
|
||||||
|
padding: "5px",
|
||||||
|
boxSizing: "border-box"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
versions.map((v, index) => $el("option", {
|
||||||
|
value: v,
|
||||||
|
textContent: v,
|
||||||
|
selected: v === current
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
versionList.addEventListener('change', (e) => {
|
||||||
|
selectedVersion = e.target.value;
|
||||||
|
Array.from(e.target.options).forEach(opt => {
|
||||||
|
opt.selected = opt.value === selectedVersion;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = $el("div", {
|
||||||
|
style: contentStyle
|
||||||
|
}, [
|
||||||
|
$el("h3", {
|
||||||
|
textContent: "Select Version",
|
||||||
|
style: {
|
||||||
|
color: "#ffffff",
|
||||||
|
backgroundColor: "#1a1a1a",
|
||||||
|
padding: "10px 15px",
|
||||||
|
margin: "0 0 10px 0",
|
||||||
|
width: "260px",
|
||||||
|
textAlign: "center",
|
||||||
|
borderRadius: "4px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
versionList,
|
||||||
|
$el("div", {
|
||||||
|
style: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "260px",
|
||||||
|
gap: "10px"
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
$el("button", {
|
||||||
|
textContent: "Cancel",
|
||||||
|
onclick: () => dialog.close(),
|
||||||
|
style: {
|
||||||
|
flex: "1",
|
||||||
|
padding: "8px",
|
||||||
|
backgroundColor: "#4a4a4a",
|
||||||
|
color: "#ffffff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
$el("button", {
|
||||||
|
textContent: "Select",
|
||||||
|
onclick: () => {
|
||||||
|
if (selectedVersion) {
|
||||||
|
onSelect(selectedVersion);
|
||||||
|
dialog.close();
|
||||||
|
} else {
|
||||||
|
alert("Please select a version.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
flex: "1",
|
||||||
|
padding: "8px",
|
||||||
|
backgroundColor: "#4CAF50",
|
||||||
|
color: "#ffffff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
dialog.show(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function switchComfyUI() {
|
||||||
|
let res = await api.fetchApi(`/comfyui_manager/comfyui_versions`, { cache: "no-store" });
|
||||||
|
|
||||||
|
if(res.status == 200) {
|
||||||
|
let obj = await res.json();
|
||||||
|
|
||||||
|
let versions = [];
|
||||||
|
let default_version;
|
||||||
|
|
||||||
|
for(let v of obj.versions) {
|
||||||
|
default_version = v;
|
||||||
|
versions.push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
showVersionSelectorDialog(versions, obj.current, (selected_version) => {
|
||||||
|
api.fetchApi(`/comfyui_manager/comfyui_switch_version?ver=${selected_version}`, { cache: "no-store" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
show_message('Failed to fetch ComfyUI versions.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async function fetchUpdates(update_check_checkbox) {
|
async function fetchUpdates(update_check_checkbox) {
|
||||||
let prev_text = fetch_updates_button.innerText;
|
let prev_text = fetch_updates_button.innerText;
|
||||||
fetch_updates_button.innerText = "Fetching updates...";
|
fetch_updates_button.innerText = "Fetching updates...";
|
||||||
@@ -745,6 +820,14 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
() => updateComfyUI()
|
() => updateComfyUI()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
switch_comfyui_button =
|
||||||
|
$el("button.cm-button", {
|
||||||
|
type: "button",
|
||||||
|
textContent: "Switch ComfyUI",
|
||||||
|
onclick:
|
||||||
|
() => switchComfyUI()
|
||||||
|
});
|
||||||
|
|
||||||
fetch_updates_button =
|
fetch_updates_button =
|
||||||
$el("button.cm-button", {
|
$el("button.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
@@ -815,6 +898,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
$el("br", {}, []),
|
$el("br", {}, []),
|
||||||
update_all_button,
|
update_all_button,
|
||||||
update_comfyui_button,
|
update_comfyui_button,
|
||||||
|
switch_comfyui_button,
|
||||||
fetch_updates_button,
|
fetch_updates_button,
|
||||||
|
|
||||||
$el("br", {}, []),
|
$el("br", {}, []),
|
||||||
@@ -838,6 +922,28 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let migration_btn =
|
||||||
|
$el("button.cm-button-orange", {
|
||||||
|
type: "button",
|
||||||
|
textContent: "Migrate to New Node System",
|
||||||
|
onclick: () => migrateAPI()
|
||||||
|
});
|
||||||
|
|
||||||
|
migration_btn.style.display = 'none';
|
||||||
|
|
||||||
|
res.push(migration_btn);
|
||||||
|
|
||||||
|
api.fetchApi('/manager/need_to_migrate')
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(text => {
|
||||||
|
if (text === 'True') {
|
||||||
|
migration_btn.style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error checking migration status:', error);
|
||||||
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,32 +981,9 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
api.fetchApi(`/manager/preview_method?value=${event.target.value}`);
|
api.fetchApi(`/manager/preview_method?value=${event.target.value}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// nickname
|
|
||||||
let badge_combo = "";
|
|
||||||
if(is_legacy_front()) {
|
|
||||||
badge_combo = document.createElement("select");
|
|
||||||
badge_combo.setAttribute("title", "Configure the content to be displayed on the badge at the top right corner of the node. The ID is the identifier of the node. If 'hide built-in' is selected, both unknown nodes and built-in nodes will be omitted, making them indistinguishable");
|
|
||||||
badge_combo.className = "cm-menu-combo";
|
|
||||||
badge_combo.appendChild($el('option', { value: 'none', text: 'Badge: None' }, []));
|
|
||||||
badge_combo.appendChild($el('option', { value: 'nick', text: 'Badge: Nickname' }, []));
|
|
||||||
badge_combo.appendChild($el('option', { value: 'nick_hide', text: 'Badge: Nickname (hide built-in)' }, []));
|
|
||||||
badge_combo.appendChild($el('option', { value: 'id_nick', text: 'Badge: #ID Nickname' }, []));
|
|
||||||
badge_combo.appendChild($el('option', { value: 'id_nick_hide', text: 'Badge: #ID Nickname (hide built-in)' }, []));
|
|
||||||
|
|
||||||
api.fetchApi('/manager/badge_mode')
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(data => { badge_combo.value = data; badge_mode = data; });
|
|
||||||
|
|
||||||
badge_combo.addEventListener('change', function (event) {
|
|
||||||
api.fetchApi(`/manager/badge_mode?value=${event.target.value}`);
|
|
||||||
badge_mode = event.target.value;
|
|
||||||
app.graph.setDirtyCanvas(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// channel
|
// channel
|
||||||
let channel_combo = document.createElement("select");
|
let channel_combo = document.createElement("select");
|
||||||
channel_combo.setAttribute("title", "Configure the channel for retrieving data from the Custom Node list (including missing nodes) or the Model list. Note that the badge utilizes local information.");
|
channel_combo.setAttribute("title", "Configure the channel for retrieving data from the Custom Node list (including missing nodes) or the Model list.");
|
||||||
channel_combo.className = "cm-menu-combo";
|
channel_combo.className = "cm-menu-combo";
|
||||||
api.fetchApi('/manager/channel_url_list')
|
api.fetchApi('/manager/channel_url_list')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@@ -1024,7 +1107,6 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
this.datasrc_combo,
|
this.datasrc_combo,
|
||||||
channel_combo,
|
channel_combo,
|
||||||
preview_combo,
|
preview_combo,
|
||||||
badge_combo,
|
|
||||||
default_ui_combo,
|
default_ui_combo,
|
||||||
share_combo,
|
share_combo,
|
||||||
component_policy_combo,
|
component_policy_combo,
|
||||||
@@ -1324,7 +1406,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
|
|
||||||
|
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "Comfy.ManagerMenu",
|
name: "Comfy.ManagerExtMenu",
|
||||||
init() {
|
init() {
|
||||||
$el("style", {
|
$el("style", {
|
||||||
textContent: style,
|
textContent: style,
|
||||||
@@ -1349,9 +1431,23 @@ app.registerExtension({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// new style Manager buttons
|
// new style Manager buttons
|
||||||
|
|
||||||
// unload models button into new style Manager button
|
// unload models button into new style Manager button
|
||||||
let cmGroup = new (await import("../../scripts/ui/components/buttonGroup.js")).ComfyButtonGroup(
|
let cmGroup = new (await import("../../scripts/ui/components/buttonGroup.js")).ComfyButtonGroup(
|
||||||
|
new(await import("../../scripts/ui/components/button.js")).ComfyButton({
|
||||||
|
icon: "star",
|
||||||
|
action: () => {
|
||||||
|
if(!manager_instance)
|
||||||
|
setManagerInstance(new ManagerMenuDialog());
|
||||||
|
|
||||||
|
if(!CustomNodesManager.instance) {
|
||||||
|
CustomNodesManager.instance = new CustomNodesManager(app, self);
|
||||||
|
}
|
||||||
|
CustomNodesManager.instance.show(CustomNodesManager.ShowMode.FAVORITES);
|
||||||
|
},
|
||||||
|
tooltip: "Show favorite custom node list",
|
||||||
|
content: "Manager",
|
||||||
|
classList: "comfyui-button comfyui-menu-mobile-collapse primary"
|
||||||
|
}).element,
|
||||||
new(await import("../../scripts/ui/components/button.js")).ComfyButton({
|
new(await import("../../scripts/ui/components/button.js")).ComfyButton({
|
||||||
icon: "puzzle",
|
icon: "puzzle",
|
||||||
action: () => {
|
action: () => {
|
||||||
@@ -1360,8 +1456,6 @@ app.registerExtension({
|
|||||||
manager_instance.show();
|
manager_instance.show();
|
||||||
},
|
},
|
||||||
tooltip: "ComfyUI Manager",
|
tooltip: "ComfyUI Manager",
|
||||||
content: "Manager",
|
|
||||||
classList: "comfyui-button comfyui-menu-mobile-collapse primary"
|
|
||||||
}).element,
|
}).element,
|
||||||
new(await import("../../scripts/ui/components/button.js")).ComfyButton({
|
new(await import("../../scripts/ui/components/button.js")).ComfyButton({
|
||||||
icon: "vacuum-outline",
|
icon: "vacuum-outline",
|
||||||
@@ -1452,32 +1546,6 @@ app.registerExtension({
|
|||||||
this._addExtraNodeContextMenu(nodeType, app);
|
this._addExtraNodeContextMenu(nodeType, app);
|
||||||
},
|
},
|
||||||
|
|
||||||
async nodeCreated(node, app) {
|
|
||||||
if(is_legacy_front()) {
|
|
||||||
if(!node.badge_enabled) {
|
|
||||||
node.getNickname = function () { return getNickname(node, node.comfyClass.trim()) };
|
|
||||||
let orig = node.onDrawForeground;
|
|
||||||
if(!orig)
|
|
||||||
orig = node.__proto__.onDrawForeground;
|
|
||||||
|
|
||||||
node.onDrawForeground = function (ctx) {
|
|
||||||
drawBadge(node, orig, arguments)
|
|
||||||
};
|
|
||||||
node.badge_enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async loadedGraphNode(node, app) {
|
|
||||||
if(is_legacy_front()) {
|
|
||||||
if(!node.badge_enabled) {
|
|
||||||
const orig = node.onDrawForeground;
|
|
||||||
node.getNickname = function () { return getNickname(node, node.type.trim()) };
|
|
||||||
node.onDrawForeground = function (ctx) { drawBadge(node, orig, arguments) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_addExtraNodeContextMenu(node, app) {
|
_addExtraNodeContextMenu(node, app) {
|
||||||
const origGetExtraMenuOptions = node.prototype.getExtraMenuOptions;
|
const origGetExtraMenuOptions = node.prototype.getExtraMenuOptions;
|
||||||
node.prototype.cm_menu_added = true;
|
node.prototype.cm_menu_added = true;
|
||||||
|
|||||||
17
js/common.js
17
js/common.js
@@ -25,6 +25,23 @@ export function rebootAPI() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function migrateAPI() {
|
||||||
|
if (confirm("When performing a migration, existing installed custom nodes will be renamed and the server will be restarted. Are you sure you want to apply this?\n\n(If you don't perform the migration, ComfyUI-Manager's start-up time will be longer each time due to re-checking during startup.)")) {
|
||||||
|
try {
|
||||||
|
await api.fetchApi("/manager/migrate_unmanaged_nodes");
|
||||||
|
api.fetchApi("/manager/reboot");
|
||||||
|
}
|
||||||
|
catch(exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export var manager_instance = null;
|
export var manager_instance = null;
|
||||||
|
|
||||||
export function setManagerInstance(obj) {
|
export function setManagerInstance(obj) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { $el } from "../../scripts/ui.js";
|
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||||
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
manager_instance, rebootAPI, install_via_git_url,
|
manager_instance, rebootAPI, install_via_git_url,
|
||||||
fetchData, md5, icons
|
fetchData, md5, icons, show_message
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
|
|
||||||
// https://cenfun.github.io/turbogrid/api.html
|
// https://cenfun.github.io/turbogrid/api.html
|
||||||
@@ -10,7 +12,7 @@ import TG from "./turbogrid.esm.js";
|
|||||||
|
|
||||||
const pageCss = `
|
const pageCss = `
|
||||||
.cn-manager {
|
.cn-manager {
|
||||||
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
--grid-font: -apple-system, BlinkMacSystemFont, "Segue UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||||
z-index: 10001;
|
z-index: 10001;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
@@ -212,17 +214,17 @@ const pageCss = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-btn-enable {
|
.cn-manager .cn-btn-enable {
|
||||||
background-color: blue;
|
background-color: #333399;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-btn-disable {
|
.cn-manager .cn-btn-disable {
|
||||||
background-color: MediumSlateBlue;
|
background-color: #442277;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-btn-update {
|
.cn-manager .cn-btn-update {
|
||||||
background-color: blue;
|
background-color: #1155AA;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,10 +249,21 @@ const pageCss = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-btn-uninstall {
|
.cn-manager .cn-btn-uninstall {
|
||||||
background-color: red;
|
background-color: #993333;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-reinstall {
|
||||||
|
background-color: #993333;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-switch {
|
||||||
|
background-color: #448833;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes cn-btn-loading-bg {
|
@keyframes cn-btn-loading-bg {
|
||||||
0% {
|
0% {
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -312,7 +325,7 @@ const pageHtml = `
|
|||||||
<div class="cn-manager-selection"></div>
|
<div class="cn-manager-selection"></div>
|
||||||
<div class="cn-manager-message"></div>
|
<div class="cn-manager-message"></div>
|
||||||
<div class="cn-manager-footer">
|
<div class="cn-manager-footer">
|
||||||
<button class="cn-manager-close">Close</button>
|
<button class="cn-manager-back">◀ Back</button>
|
||||||
<button class="cn-manager-restart">Restart</button>
|
<button class="cn-manager-restart">Restart</button>
|
||||||
<div class="cn-flex-auto"></div>
|
<div class="cn-flex-auto"></div>
|
||||||
<button class="cn-manager-check-update">Check Update</button>
|
<button class="cn-manager-check-update">Check Update</button>
|
||||||
@@ -325,6 +338,7 @@ const ShowMode = {
|
|||||||
NORMAL: "Normal",
|
NORMAL: "Normal",
|
||||||
UPDATE: "Update",
|
UPDATE: "Update",
|
||||||
MISSING: "Missing",
|
MISSING: "Missing",
|
||||||
|
FAVORITES: "Favorites",
|
||||||
ALTERNATIVES: "Alternatives"
|
ALTERNATIVES: "Alternatives"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -356,7 +370,6 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
||||||
if (!document.querySelector(`style[context="${this.id}"]`)) {
|
if (!document.querySelector(`style[context="${this.id}"]`)) {
|
||||||
const $style = document.createElement("style");
|
const $style = document.createElement("style");
|
||||||
$style.setAttribute("context", this.id);
|
$style.setAttribute("context", this.id);
|
||||||
@@ -374,6 +387,130 @@ export class CustomNodesManager {
|
|||||||
this.initGrid();
|
this.initGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showVersionSelectorDialog(versions, onSelect) {
|
||||||
|
const dialog = new ComfyDialog();
|
||||||
|
dialog.element.style.zIndex = 100003;
|
||||||
|
dialog.element.style.width = "300px";
|
||||||
|
dialog.element.style.padding = "0";
|
||||||
|
dialog.element.style.backgroundColor = "#2a2a2a";
|
||||||
|
dialog.element.style.border = "1px solid #3a3a3a";
|
||||||
|
dialog.element.style.borderRadius = "8px";
|
||||||
|
dialog.element.style.boxSizing = "border-box";
|
||||||
|
dialog.element.style.overflow = "hidden";
|
||||||
|
|
||||||
|
const contentStyle = {
|
||||||
|
width: "300px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "20px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
gap: "15px"
|
||||||
|
};
|
||||||
|
|
||||||
|
let selectedVersion = versions[0];
|
||||||
|
|
||||||
|
const versionList = $el("select", {
|
||||||
|
multiple: true,
|
||||||
|
size: Math.min(10, versions.length),
|
||||||
|
style: {
|
||||||
|
width: "260px",
|
||||||
|
height: "auto",
|
||||||
|
backgroundColor: "#383838",
|
||||||
|
color: "#ffffff",
|
||||||
|
border: "1px solid #4a4a4a",
|
||||||
|
borderRadius: "4px",
|
||||||
|
padding: "5px",
|
||||||
|
boxSizing: "border-box"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
versions.map((v, index) => $el("option", {
|
||||||
|
value: v,
|
||||||
|
textContent: v,
|
||||||
|
selected: index === 0
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
versionList.addEventListener('change', (e) => {
|
||||||
|
selectedVersion = e.target.value;
|
||||||
|
Array.from(e.target.options).forEach(opt => {
|
||||||
|
opt.selected = opt.value === selectedVersion;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = $el("div", {
|
||||||
|
style: contentStyle
|
||||||
|
}, [
|
||||||
|
$el("h3", {
|
||||||
|
textContent: "Select Version",
|
||||||
|
style: {
|
||||||
|
color: "#ffffff",
|
||||||
|
backgroundColor: "#1a1a1a",
|
||||||
|
padding: "10px 15px",
|
||||||
|
margin: "0 0 10px 0",
|
||||||
|
width: "260px",
|
||||||
|
textAlign: "center",
|
||||||
|
borderRadius: "4px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
versionList,
|
||||||
|
$el("div", {
|
||||||
|
style: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "260px",
|
||||||
|
gap: "10px"
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
$el("button", {
|
||||||
|
textContent: "Cancel",
|
||||||
|
onclick: () => dialog.close(),
|
||||||
|
style: {
|
||||||
|
flex: "1",
|
||||||
|
padding: "8px",
|
||||||
|
backgroundColor: "#4a4a4a",
|
||||||
|
color: "#ffffff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
$el("button", {
|
||||||
|
textContent: "Select",
|
||||||
|
onclick: () => {
|
||||||
|
if (selectedVersion) {
|
||||||
|
onSelect(selectedVersion);
|
||||||
|
dialog.close();
|
||||||
|
} else {
|
||||||
|
alert("Please select a version.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
flex: "1",
|
||||||
|
padding: "8px",
|
||||||
|
backgroundColor: "#4CAF50",
|
||||||
|
color: "#ffffff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
dialog.show(content);
|
||||||
|
}
|
||||||
|
|
||||||
initFilter() {
|
initFilter() {
|
||||||
const $filter = this.element.querySelector(".cn-manager-filter");
|
const $filter = this.element.querySelector(".cn-manager-filter");
|
||||||
const filterList = [{
|
const filterList = [{
|
||||||
@@ -382,23 +519,31 @@ export class CustomNodesManager {
|
|||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Installed",
|
label: "Installed",
|
||||||
value: "True",
|
value: "installed",
|
||||||
|
hasData: true
|
||||||
|
}, {
|
||||||
|
label: "Enabled",
|
||||||
|
value: "enabled",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Disabled",
|
label: "Disabled",
|
||||||
value: "Disabled",
|
value: "disabled",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Import Failed",
|
label: "Import Failed",
|
||||||
value: "Fail",
|
value: "import-fail",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Not Installed",
|
label: "Not Installed",
|
||||||
value: "False",
|
value: "not-installed",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Unknown",
|
label: "ComfyRegistry",
|
||||||
value: "None",
|
value: "cnr",
|
||||||
|
hasData: true
|
||||||
|
}, {
|
||||||
|
label: "Non-ComfyRegistry",
|
||||||
|
value: "unknown",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Update",
|
label: "Update",
|
||||||
@@ -408,6 +553,10 @@ export class CustomNodesManager {
|
|||||||
label: "Missing",
|
label: "Missing",
|
||||||
value: ShowMode.MISSING,
|
value: ShowMode.MISSING,
|
||||||
hasData: false
|
hasData: false
|
||||||
|
}, {
|
||||||
|
label: "Favorites",
|
||||||
|
value: ShowMode.FAVORITES,
|
||||||
|
hasData: false
|
||||||
}, {
|
}, {
|
||||||
label: "Alternatives of A1111",
|
label: "Alternatives of A1111",
|
||||||
value: ShowMode.ALTERNATIVES,
|
value: ShowMode.ALTERNATIVES,
|
||||||
@@ -423,16 +572,15 @@ export class CustomNodesManager {
|
|||||||
return this.filterList.find(it => it.value === filter)
|
return this.filterList.find(it => it.value === filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
getInstallButtons(installed, title) {
|
getActionButtons(action, rowItem, is_selected_button) {
|
||||||
|
|
||||||
const buttons = {
|
const buttons = {
|
||||||
"enable": {
|
"enable": {
|
||||||
label: "Enable",
|
label: "Enable",
|
||||||
mode: "toggle_active"
|
mode: "enable"
|
||||||
},
|
},
|
||||||
"disable": {
|
"disable": {
|
||||||
label: "Disable",
|
label: "Disable",
|
||||||
mode: "toggle_active"
|
mode: "disable"
|
||||||
},
|
},
|
||||||
|
|
||||||
"update": {
|
"update": {
|
||||||
@@ -449,45 +597,63 @@ export class CustomNodesManager {
|
|||||||
mode: "fix"
|
mode: "fix"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"reinstall": {
|
||||||
|
label: "Reinstall",
|
||||||
|
mode: "reinstall"
|
||||||
|
},
|
||||||
|
|
||||||
"install": {
|
"install": {
|
||||||
label: "Install",
|
label: "Install",
|
||||||
mode: "install"
|
mode: "install"
|
||||||
},
|
},
|
||||||
|
|
||||||
"try-install": {
|
"try-install": {
|
||||||
label: "Try install",
|
label: "Try install",
|
||||||
mode: "install"
|
mode: "install"
|
||||||
},
|
},
|
||||||
|
|
||||||
"uninstall": {
|
"uninstall": {
|
||||||
label: "Uninstall",
|
label: "Uninstall",
|
||||||
mode: "uninstall"
|
mode: "uninstall"
|
||||||
|
},
|
||||||
|
|
||||||
|
"switch": {
|
||||||
|
label: "Switch",
|
||||||
|
mode: "switch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const installGroups = {
|
const installGroups = {
|
||||||
"Disabled": ["enable", "uninstall"],
|
"disabled": ["enable", "switch", "uninstall"],
|
||||||
"Update": ["update", "disable", "uninstall"],
|
"updatable": ["update", "switch", "disable", "uninstall"],
|
||||||
"Fail": ["try-fix", "uninstall"],
|
"import-fail": ["try-fix", "switch", "disable", "uninstall"],
|
||||||
"True": ["try-update", "disable", "uninstall"],
|
"enabled": ["try-update", "switch", "disable", "uninstall"],
|
||||||
"False": ["install"],
|
"not-installed": ["install"],
|
||||||
'None': ["try-install"]
|
'unknown': ["try-install"],
|
||||||
|
"invalid-installation": ["reinstall"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!manager_instance.update_check_checkbox.checked) {
|
if (!manager_instance.update_check_checkbox.checked) {
|
||||||
installGroups.True = installGroups.True.filter(it => it !== "try-update");
|
installGroups.enabled = installGroups.enabled.filter(it => it !== "try-update");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (title === "ComfyUI-Manager") {
|
if (rowItem?.title === "ComfyUI-Manager") {
|
||||||
installGroups.True = installGroups.True.filter(it => it !== "disable");
|
installGroups.enabled = installGroups.enabled.filter(it => it !== "disable" && it !== "uninstall" && it !== "switch");
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = installGroups[action];
|
||||||
|
|
||||||
|
if(is_selected_button || rowItem?.version === "unknown") {
|
||||||
|
list = list.filter(it => it !== "switch");
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = installGroups[installed];
|
|
||||||
if (!list) {
|
if (!list) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return list.map(id => {
|
return list.map(id => {
|
||||||
const bt = buttons[id];
|
const bt = buttons[id];
|
||||||
return `<button class="cn-btn-${id}" group="${installed}" mode="${bt.mode}">${bt.label}</button>`;
|
return `<button class="cn-btn-${id}" group="${action}" mode="${bt.mode}">${bt.label}</button>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,8 +720,11 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
".cn-manager-close": {
|
".cn-manager-back": {
|
||||||
click: (e) => this.close()
|
click: (e) => {
|
||||||
|
this.close()
|
||||||
|
manager_instance.show();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
".cn-manager-restart": {
|
".cn-manager-restart": {
|
||||||
@@ -614,13 +783,9 @@ export class CustomNodesManager {
|
|||||||
|
|
||||||
let prevViewRowsLength = -1;
|
let prevViewRowsLength = -1;
|
||||||
grid.bind('onUpdated', (e, d) => {
|
grid.bind('onUpdated', (e, d) => {
|
||||||
|
|
||||||
const viewRows = grid.viewRows;
|
const viewRows = grid.viewRows;
|
||||||
if (viewRows.length !== prevViewRowsLength) {
|
|
||||||
prevViewRowsLength = viewRows.length;
|
prevViewRowsLength = viewRows.length;
|
||||||
this.showStatus(`${prevViewRowsLength.toLocaleString()} custom nodes`);
|
this.showStatus(`${prevViewRowsLength.toLocaleString()} custom nodes`);
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.bind('onSelectChanged', (e, changes) => {
|
grid.bind('onSelectChanged', (e, changes) => {
|
||||||
@@ -630,8 +795,17 @@ export class CustomNodesManager {
|
|||||||
grid.bind('onClick', (e, d) => {
|
grid.bind('onClick', (e, d) => {
|
||||||
const btn = this.getButton(d.e.target);
|
const btn = this.getButton(d.e.target);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
|
const item = this.grid.getRowItemBy("hash", d.rowItem.hash);
|
||||||
|
|
||||||
|
const { target, label, mode} = btn;
|
||||||
|
if((mode === "install" || mode === "switch" || mode == "enable") && item.originalData.version != 'unknown') {
|
||||||
|
// install after select version via dialog if item is cnr node
|
||||||
|
this.installNodeWithVersion(d.rowItem, btn, mode == 'enable');
|
||||||
|
}
|
||||||
|
else {
|
||||||
this.installNodes([d.rowItem.hash], btn, d.rowItem.title);
|
this.installNodes([d.rowItem.hash], btn, d.rowItem.title);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.setOption({
|
grid.setOption({
|
||||||
@@ -651,7 +825,7 @@ export class CustomNodesManager {
|
|||||||
bindContainerResize: true,
|
bindContainerResize: true,
|
||||||
|
|
||||||
cellResizeObserver: (rowItem, columnItem) => {
|
cellResizeObserver: (rowItem, columnItem) => {
|
||||||
const autoHeightColumns = ['title', 'installed', 'description', "alternatives"];
|
const autoHeightColumns = ['title', 'action', 'description', "alternatives"];
|
||||||
return autoHeightColumns.includes(columnItem.id)
|
return autoHeightColumns.includes(columnItem.id)
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -696,11 +870,11 @@ export class CustomNodesManager {
|
|||||||
theme: colorPalette === "light" ? "" : "dark"
|
theme: colorPalette === "light" ? "" : "dark"
|
||||||
};
|
};
|
||||||
|
|
||||||
const rows = this.custom_nodes || [];
|
const rows = this.custom_nodes || {};
|
||||||
rows.forEach((item, i) => {
|
for(let nodeKey in rows) {
|
||||||
item.id = i + 1;
|
let item = rows[nodeKey];
|
||||||
const nodeKey = item.files[0];
|
|
||||||
const extensionInfo = this.extension_mappings[nodeKey];
|
const extensionInfo = this.extension_mappings[nodeKey];
|
||||||
|
|
||||||
if(extensionInfo) {
|
if(extensionInfo) {
|
||||||
const { extensions, conflicts } = extensionInfo;
|
const { extensions, conflicts } = extensionInfo;
|
||||||
if (extensions.length) {
|
if (extensions.length) {
|
||||||
@@ -712,7 +886,7 @@ export class CustomNodesManager {
|
|||||||
item.conflictsList = conflicts;
|
item.conflictsList = conflicts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
id: 'id',
|
id: 'id',
|
||||||
@@ -727,22 +901,55 @@ export class CustomNodesManager {
|
|||||||
maxWidth: 500,
|
maxWidth: 500,
|
||||||
classMap: 'cn-node-name',
|
classMap: 'cn-node-name',
|
||||||
formatter: (title, rowItem, columnItem) => {
|
formatter: (title, rowItem, columnItem) => {
|
||||||
return `${rowItem.installed === 'Fail' ? '<font color="red"><B>(IMPORT FAILED)</B></font>' : ''}
|
var prefix = '';
|
||||||
<a href=${rowItem.reference} target="_blank"><b>${title}</b></a>`;
|
if(rowItem.action === 'invalid-installation') {
|
||||||
|
prefix = '<font color="red"><B>(INVALID)</B></font>';
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(rowItem.action === 'import-fail') {
|
||||||
|
prefix = '<font color="red"><B>(IMPORT FAILED)</B></font>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${prefix}<a href=${rowItem.reference} target="_blank"><b>${title}</b></a>`;
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
id: 'installed',
|
id: 'version',
|
||||||
name: 'Install',
|
name: 'Version',
|
||||||
|
width: 200,
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 500,
|
||||||
|
classMap: 'cn-node-desc',
|
||||||
|
formatter: (version, rowItem, columnItem) => {
|
||||||
|
if(version == undefined) {
|
||||||
|
return `undef`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(rowItem.cnr_latest && version != rowItem.cnr_latest) {
|
||||||
|
if(version == 'nightly') {
|
||||||
|
return `${version} [${rowItem.cnr_latest}]`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `${version} [↑${rowItem.cnr_latest}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `${version}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
id: 'action',
|
||||||
|
name: 'Action',
|
||||||
width: 130,
|
width: 130,
|
||||||
minWidth: 110,
|
minWidth: 110,
|
||||||
maxWidth: 200,
|
maxWidth: 200,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: (installed, rowItem, columnItem) => {
|
formatter: (action, rowItem, columnItem) => {
|
||||||
if (rowItem.restart) {
|
if (rowItem.restart) {
|
||||||
return `<font color="red">Restart Required</span>`;
|
return `<font color="red">Restart Required</span>`;
|
||||||
}
|
}
|
||||||
const buttons = this.getInstallButtons(installed, rowItem.title);
|
const buttons = this.getActionButtons(action, rowItem);
|
||||||
return `<div class="cn-install-buttons">${buttons}</div>`;
|
return `<div class="cn-install-buttons">${buttons}</div>`;
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@@ -845,14 +1052,35 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
this.grid.setData({
|
let rows_values = Object.keys(rows).map(key => rows[key]);
|
||||||
options,
|
|
||||||
rows,
|
rows_values =
|
||||||
columns
|
rows_values.sort((a, b) => {
|
||||||
|
if (a.version == 'unknown' && b.version != 'unknown') return 1;
|
||||||
|
if (a.version != 'unknown' && b.version == 'unknown') return -1;
|
||||||
|
|
||||||
|
if (a.stars !== b.stars) {
|
||||||
|
return b.stars - a.stars;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.last_update !== b.last_update) {
|
||||||
|
return new Date(b.last_update) - new Date(a.last_update);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.grid.render();
|
this.grid.setData({
|
||||||
|
options: options,
|
||||||
|
rows: rows_values,
|
||||||
|
columns: columns
|
||||||
|
});
|
||||||
|
|
||||||
|
for(let i=0; i<rows_values.length; i++) {
|
||||||
|
rows_values[i].id = i+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.grid.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGrid() {
|
updateGrid() {
|
||||||
@@ -877,7 +1105,7 @@ export class CustomNodesManager {
|
|||||||
|
|
||||||
const selectedMap = {};
|
const selectedMap = {};
|
||||||
selectedList.forEach(item => {
|
selectedList.forEach(item => {
|
||||||
let type = item.installed;
|
let type = item.action;
|
||||||
if (item.restart) {
|
if (item.restart) {
|
||||||
type = "Restart Required";
|
type = "Restart Required";
|
||||||
}
|
}
|
||||||
@@ -895,7 +1123,7 @@ export class CustomNodesManager {
|
|||||||
const filterItem = this.getFilterItem(v);
|
const filterItem = this.getFilterItem(v);
|
||||||
list.push(`<div class="cn-selected-buttons">
|
list.push(`<div class="cn-selected-buttons">
|
||||||
<span>Selected <b>${selectedMap[v].length}</b> ${filterItem ? filterItem.label : v}</span>
|
<span>Selected <b>${selectedMap[v].length}</b> ${filterItem ? filterItem.label : v}</span>
|
||||||
${this.grid.hasMask ? "" : this.getInstallButtons(v)}
|
${this.grid.hasMask ? "" : this.getActionButtons(v, null, true)}
|
||||||
</div>`);
|
</div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -913,8 +1141,67 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async installNodes(list, btn, title) {
|
async installNodeWithVersion(rowItem, btn, is_enable) {
|
||||||
|
let hash = rowItem.hash;
|
||||||
|
let title = rowItem.title;
|
||||||
|
|
||||||
|
const item = this.grid.getRowItemBy("hash", hash);
|
||||||
|
|
||||||
|
let node_id = item.originalData.id;
|
||||||
|
|
||||||
|
this.showLoading();
|
||||||
|
let res;
|
||||||
|
if(is_enable) {
|
||||||
|
res = await api.fetchApi(`/customnode/disabled_versions/${node_id}`, { cache: "no-store" });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res = await api.fetchApi(`/customnode/versions/${node_id}`, { cache: "no-store" });
|
||||||
|
}
|
||||||
|
this.hideLoading();
|
||||||
|
|
||||||
|
if(res.status == 200) {
|
||||||
|
let obj = await res.json();
|
||||||
|
|
||||||
|
let versions = [];
|
||||||
|
let default_version;
|
||||||
|
let version_cnt = 0;
|
||||||
|
|
||||||
|
if(!is_enable) {
|
||||||
|
if(rowItem.cnr_latest != rowItem.originalData.active_version) {
|
||||||
|
versions.push('latest');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rowItem.originalData.active_version != 'nightly') {
|
||||||
|
versions.push('nightly');
|
||||||
|
default_version = 'nightly';
|
||||||
|
version_cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let v of obj) {
|
||||||
|
if(rowItem.originalData.active_version != v.version) {
|
||||||
|
default_version = v.version;
|
||||||
|
versions.push(v.version);
|
||||||
|
version_cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(version_cnt == 1) {
|
||||||
|
// if only one version is available
|
||||||
|
this.installNodes([hash], btn, title, default_version);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.showVersionSelectorDialog(versions, (selected_version) => {
|
||||||
|
this.installNodes([hash], btn, title, selected_version);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
show_message('Failed to fetch versions from ComfyRegistry.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async installNodes(list, btn, title, selected_version) {
|
||||||
const { target, label, mode} = btn;
|
const { target, label, mode} = btn;
|
||||||
|
|
||||||
if(mode === "uninstall") {
|
if(mode === "uninstall") {
|
||||||
@@ -924,14 +1211,19 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(mode === "reinstall") {
|
||||||
|
title = title || `${list.length} custom nodes`;
|
||||||
|
if (!confirm(`Are you sure reinstall ${title}?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
target.classList.add("cn-btn-loading");
|
target.classList.add("cn-btn-loading");
|
||||||
this.showLoading();
|
|
||||||
this.showError("");
|
this.showError("");
|
||||||
|
|
||||||
let needRestart = false;
|
let needRestart = false;
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
for (const hash of list) {
|
for (const hash of list) {
|
||||||
|
|
||||||
const item = this.grid.getRowItemBy("hash", hash);
|
const item = this.grid.getRowItemBy("hash", hash);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
errorMsg = `Not found custom node: ${hash}`;
|
errorMsg = `Not found custom node: ${hash}`;
|
||||||
@@ -949,9 +1241,28 @@ export class CustomNodesManager {
|
|||||||
this.showStatus(`${label} ${item.title} ...`);
|
this.showStatus(`${label} ${item.title} ...`);
|
||||||
|
|
||||||
const data = item.originalData;
|
const data = item.originalData;
|
||||||
const res = await fetchData(`/customnode/${mode}`, {
|
data.selected_version = selected_version;
|
||||||
|
data.channel = this.channel;
|
||||||
|
data.mode = this.mode;
|
||||||
|
|
||||||
|
let install_mode = mode;
|
||||||
|
if(mode == 'switch') {
|
||||||
|
install_mode = 'install';
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't post install if install_mode == 'enable'
|
||||||
|
data.skip_post_install = install_mode == 'enable';
|
||||||
|
let api_mode = install_mode;
|
||||||
|
if(install_mode == 'enable') {
|
||||||
|
api_mode = 'install';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(install_mode == 'reinstall') {
|
||||||
|
api_mode = 'reinstall';
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.fetchApi(`/customnode/${api_mode}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -974,13 +1285,12 @@ export class CustomNodesManager {
|
|||||||
this.grid.setRowSelected(item, false);
|
this.grid.setRowSelected(item, false);
|
||||||
item.restart = true;
|
item.restart = true;
|
||||||
this.restartMap[item.hash] = true;
|
this.restartMap[item.hash] = true;
|
||||||
this.grid.updateCell(item, "installed");
|
this.grid.updateCell(item, "action");
|
||||||
|
|
||||||
//console.log(res.data);
|
//console.log(res.data);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hideLoading();
|
|
||||||
target.classList.remove("cn-btn-loading");
|
target.classList.remove("cn-btn-loading");
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
@@ -1064,26 +1374,28 @@ export class CustomNodesManager {
|
|||||||
const mappings = res.data;
|
const mappings = res.data;
|
||||||
|
|
||||||
// build regex->url map
|
// build regex->url map
|
||||||
const regex_to_url = [];
|
const regex_to_pack = [];
|
||||||
this.custom_nodes.forEach(node => {
|
for(let k in this.custom_nodes) {
|
||||||
|
let node = this.custom_nodes[k];
|
||||||
|
|
||||||
if(node.nodename_pattern) {
|
if(node.nodename_pattern) {
|
||||||
regex_to_url.push({
|
regex_to_pack.push({
|
||||||
regex: new RegExp(node.nodename_pattern),
|
regex: new RegExp(node.nodename_pattern),
|
||||||
url: node.files[0]
|
url: node.files[0]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// build name->url map
|
// build name->url map
|
||||||
const name_to_urls = {};
|
const name_to_packs = {};
|
||||||
for (const url in mappings) {
|
for (const url in mappings) {
|
||||||
const names = mappings[url];
|
const names = mappings[url];
|
||||||
|
|
||||||
for(const name in names[0]) {
|
for(const name in names[0]) {
|
||||||
let v = name_to_urls[names[0][name]];
|
let v = name_to_packs[names[0][name]];
|
||||||
if(v == undefined) {
|
if(v == undefined) {
|
||||||
v = [];
|
v = [];
|
||||||
name_to_urls[names[0][name]] = v;
|
name_to_packs[names[0][name]] = v;
|
||||||
}
|
}
|
||||||
v.push(url);
|
v.push(url);
|
||||||
}
|
}
|
||||||
@@ -1110,15 +1422,15 @@ export class CustomNodesManager {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!registered_nodes.has(node_type)) {
|
if (!registered_nodes.has(node_type)) {
|
||||||
const urls = name_to_urls[node_type.trim()];
|
const packs = name_to_packs[node_type.trim()];
|
||||||
if(urls)
|
if(packs)
|
||||||
urls.forEach(url => {
|
packs.forEach(url => {
|
||||||
missing_nodes.add(url);
|
missing_nodes.add(url);
|
||||||
});
|
});
|
||||||
else {
|
else {
|
||||||
for(let j in regex_to_url) {
|
for(let j in regex_to_pack) {
|
||||||
if(regex_to_url[j].regex.test(node_type)) {
|
if(regex_to_pack[j].regex.test(node_type)) {
|
||||||
missing_nodes.add(regex_to_url[j].url);
|
missing_nodes.add(regex_to_pack[j].url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1129,24 +1441,42 @@ export class CustomNodesManager {
|
|||||||
const unresolved = resUnresolved.data;
|
const unresolved = resUnresolved.data;
|
||||||
if (unresolved && unresolved.nodes) {
|
if (unresolved && unresolved.nodes) {
|
||||||
unresolved.nodes.forEach(node_type => {
|
unresolved.nodes.forEach(node_type => {
|
||||||
const url = name_to_urls[node_type];
|
const packs = name_to_packs[node_type];
|
||||||
if(url) {
|
if(packs) {
|
||||||
|
packs.forEach(url => {
|
||||||
missing_nodes.add(url);
|
missing_nodes.add(url);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashMap = {};
|
const hashMap = {};
|
||||||
this.custom_nodes.forEach(item => {
|
for(let k in this.custom_nodes) {
|
||||||
if (item.files.some(file => missing_nodes.has(file))) {
|
let item = this.custom_nodes[k];
|
||||||
|
|
||||||
|
if(missing_nodes.has(item.id)) {
|
||||||
hashMap[item.hash] = true;
|
hashMap[item.hash] = true;
|
||||||
}
|
}
|
||||||
});
|
else if (item.files?.some(file => missing_nodes.has(file))) {
|
||||||
|
hashMap[item.hash] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFavorites() {
|
||||||
|
const hashMap = {};
|
||||||
|
for(let k in this.custom_nodes) {
|
||||||
|
let item = this.custom_nodes[k];
|
||||||
|
if(item.is_favorite)
|
||||||
|
hashMap[item.hash] = true;
|
||||||
|
}
|
||||||
|
|
||||||
return hashMap;
|
return hashMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAlternatives() {
|
async getAlternatives() {
|
||||||
|
|
||||||
const mode = manager_instance.datasrc_combo.value;
|
const mode = manager_instance.datasrc_combo.value;
|
||||||
this.showStatus(`Loading alternatives (${mode}) ...`);
|
this.showStatus(`Loading alternatives (${mode}) ...`);
|
||||||
const res = await fetchData(`/customnode/alternatives?mode=${mode}`);
|
const res = await fetchData(`/customnode/alternatives?mode=${mode}`);
|
||||||
@@ -1156,27 +1486,28 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hashMap = {};
|
const hashMap = {};
|
||||||
const { items } = res.data;
|
const items = res.data;
|
||||||
|
|
||||||
items.forEach(item => {
|
for(let i in items) {
|
||||||
|
let item = items[i];
|
||||||
|
let custom_node = this.custom_nodes[i];
|
||||||
|
|
||||||
const custom_node = this.custom_nodes.find(node => node.files.find(file => file === item.id));
|
|
||||||
if (!custom_node) {
|
if (!custom_node) {
|
||||||
console.log(`Not found custom node: ${item.id}`);
|
console.log(`Not found custom node: ${item.id}`);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = `${item.tags}`.split(",").map(tag => {
|
const tags = `${item.tags}`.split(",").map(tag => {
|
||||||
return `<div>${tag.trim()}</div>`;
|
return `<div>${tag.trim()}</div>`;
|
||||||
}).join("")
|
}).join("");
|
||||||
|
|
||||||
hashMap[custom_node.hash] = {
|
hashMap[custom_node.hash] = {
|
||||||
alternatives: `<div class="cn-tag-list">${tags}</div> ${item.description}`
|
alternatives: `<div class="cn-tag-list">${tags}</div> ${item.description}`
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
}
|
||||||
|
|
||||||
return hashMap
|
return hashMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadData(show_mode = ShowMode.NORMAL) {
|
async loadData(show_mode = ShowMode.NORMAL) {
|
||||||
@@ -1198,18 +1529,19 @@ export class CustomNodesManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { channel, custom_nodes} = res.data;
|
const { channel, node_packs } = res.data;
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.custom_nodes = custom_nodes;
|
this.mode = mode;
|
||||||
|
this.custom_nodes = node_packs;
|
||||||
|
|
||||||
if(this.channel !== 'default') {
|
if(this.channel !== 'default') {
|
||||||
this.element.querySelector(".cn-manager-channel").innerHTML = `Channel: ${this.channel} (Incomplete list)`;
|
this.element.querySelector(".cn-manager-channel").innerHTML = `Channel: ${this.channel} (Incomplete list)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of custom_nodes) {
|
for (const k in node_packs) {
|
||||||
|
let item = node_packs[k];
|
||||||
item.originalData = JSON.parse(JSON.stringify(item));
|
item.originalData = JSON.parse(JSON.stringify(item));
|
||||||
const message = item.title + item.files[0];
|
item.hash = md5(k);
|
||||||
item.hash = md5(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterItem = this.getFilterItem(this.show_mode);
|
const filterItem = this.getFilterItem(this.show_mode);
|
||||||
@@ -1217,24 +1549,44 @@ export class CustomNodesManager {
|
|||||||
let hashMap;
|
let hashMap;
|
||||||
if(this.show_mode == ShowMode.UPDATE) {
|
if(this.show_mode == ShowMode.UPDATE) {
|
||||||
hashMap = {};
|
hashMap = {};
|
||||||
custom_nodes.forEach(it => {
|
for (const k in node_packs) {
|
||||||
if (it.installed === "Update") {
|
let it = node_packs[k];
|
||||||
|
if (it['update-state'] === "true") {
|
||||||
hashMap[it.hash] = true;
|
hashMap[it.hash] = true;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else if(this.show_mode == ShowMode.MISSING) {
|
} else if(this.show_mode == ShowMode.MISSING) {
|
||||||
hashMap = await this.getMissingNodes();
|
hashMap = await this.getMissingNodes();
|
||||||
} else if(this.show_mode == ShowMode.ALTERNATIVES) {
|
} else if(this.show_mode == ShowMode.ALTERNATIVES) {
|
||||||
hashMap = await this.getAlternatives();
|
hashMap = await this.getAlternatives();
|
||||||
|
} else if(this.show_mode == ShowMode.FAVORITES) {
|
||||||
|
hashMap = await this.getFavorites();
|
||||||
}
|
}
|
||||||
filterItem.hashMap = hashMap;
|
filterItem.hashMap = hashMap;
|
||||||
filterItem.hasData = true;
|
filterItem.hasData = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_nodes.forEach(nodeItem => {
|
for(let k in node_packs) {
|
||||||
|
let nodeItem = node_packs[k];
|
||||||
|
|
||||||
if (this.restartMap[nodeItem.hash]) {
|
if (this.restartMap[nodeItem.hash]) {
|
||||||
nodeItem.restart = true;
|
nodeItem.restart = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(nodeItem['update-state'] == "true") {
|
||||||
|
nodeItem.action = 'updatable';
|
||||||
|
}
|
||||||
|
else if(nodeItem['import-fail']) {
|
||||||
|
nodeItem.action = 'import-fail';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nodeItem.action = nodeItem.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nodeItem['invalid-installation']) {
|
||||||
|
nodeItem.action = 'invalid-installation';
|
||||||
|
}
|
||||||
|
|
||||||
const filterTypes = new Set();
|
const filterTypes = new Set();
|
||||||
this.filterList.forEach(filterItem => {
|
this.filterList.forEach(filterItem => {
|
||||||
const { value, hashMap } = filterItem;
|
const { value, hashMap } = filterItem;
|
||||||
@@ -1243,29 +1595,55 @@ export class CustomNodesManager {
|
|||||||
if (hashData) {
|
if (hashData) {
|
||||||
filterTypes.add(value);
|
filterTypes.add(value);
|
||||||
if (value === ShowMode.UPDATE) {
|
if (value === ShowMode.UPDATE) {
|
||||||
nodeItem.installed = "Update";
|
nodeItem['update-state'] = "true";
|
||||||
|
}
|
||||||
|
if (value === ShowMode.MISSING) {
|
||||||
|
nodeItem['missing-node'] = "true";
|
||||||
}
|
}
|
||||||
if (typeof hashData === "object") {
|
if (typeof hashData === "object") {
|
||||||
Object.assign(nodeItem, hashData);
|
Object.assign(nodeItem, hashData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (nodeItem.installed === value) {
|
if (nodeItem.state === value) {
|
||||||
filterTypes.add(value);
|
filterTypes.add(value);
|
||||||
}
|
}
|
||||||
const map = {
|
|
||||||
"Update": "True",
|
switch(nodeItem.state) {
|
||||||
"Disabled": "True",
|
case "enabled":
|
||||||
"Fail": "True",
|
filterTypes.add("enabled");
|
||||||
"None": "False"
|
case "disabled":
|
||||||
|
filterTypes.add("installed");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "not-installed":
|
||||||
|
filterTypes.add("not-installed");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (map[nodeItem.installed]) {
|
|
||||||
filterTypes.add(map[nodeItem.installed]);
|
if(nodeItem.version != 'unknown') {
|
||||||
|
filterTypes.add("cnr");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
filterTypes.add("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nodeItem['update-state'] == 'true') {
|
||||||
|
filterTypes.add("updatable");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nodeItem['import-fail']) {
|
||||||
|
filterTypes.add("import-fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nodeItem['invalid-installation']) {
|
||||||
|
filterTypes.add("invalid-installation");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
nodeItem.filterTypes = Array.from(filterTypes);
|
nodeItem.filterTypes = Array.from(filterTypes);
|
||||||
});
|
}
|
||||||
|
|
||||||
this.renderGrid();
|
this.renderGrid();
|
||||||
|
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ const pageHtml = `
|
|||||||
<div class="cmm-manager-selection"></div>
|
<div class="cmm-manager-selection"></div>
|
||||||
<div class="cmm-manager-message"></div>
|
<div class="cmm-manager-message"></div>
|
||||||
<div class="cmm-manager-footer">
|
<div class="cmm-manager-footer">
|
||||||
<button class="cmm-manager-close">Close</button>
|
<button class="cmm-manager-back">Back</button>
|
||||||
<div class="cmm-flex-auto"></div>
|
<div class="cmm-flex-auto"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -365,10 +365,12 @@ export class ModelManager {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
".cmm-manager-close": {
|
".cmm-manager-back": {
|
||||||
click: (e) => this.close()
|
click: (e) => {
|
||||||
|
this.close()
|
||||||
|
manager_instance.show();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
Object.keys(eventsMap).forEach(selector => {
|
Object.keys(eventsMap).forEach(selector => {
|
||||||
const target = this.element.querySelector(selector);
|
const target = this.element.querySelector(selector);
|
||||||
|
|||||||
34
migration_js/migration.js
Normal file
34
migration_js/migration.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { app } from "../../scripts/app.js";
|
||||||
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: "Comfy.ManagerExtMenu",
|
||||||
|
init() {
|
||||||
|
$el("style", {
|
||||||
|
textContent: style,
|
||||||
|
parent: document.head,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async setup() {
|
||||||
|
let cmGroup = new (await import("../../scripts/ui/components/buttonGroup.js")).ComfyButtonGroup(
|
||||||
|
new(await import("../../scripts/ui/components/button.js")).ComfyButton({
|
||||||
|
icon: "puzzle",
|
||||||
|
action: async () => {
|
||||||
|
if(confirm('As some features of ComfyUI Manager have been integrated into ComfyUI, they have been separated into manager-core.\n\nComfyUI Manager only includes additional extension features that are not provided by manager-core.\n\nWill you install manager-core?')) {
|
||||||
|
app.ui.dialog.show('Installing manager-core...');
|
||||||
|
app.ui.dialog.element.style.zIndex = 10010;
|
||||||
|
|
||||||
|
await api.fetchApi("/manager/install_manager_core");
|
||||||
|
|
||||||
|
app.ui.dialog.show('The installation of manager-core will be completed after restarting.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: "Need to install manager-core",
|
||||||
|
content: "Manager (Need To Install)",
|
||||||
|
classList: "comfyui-button comfyui-menu-mobile-collapse primary"
|
||||||
|
}).element
|
||||||
|
);
|
||||||
|
|
||||||
|
app.menu?.settingsGroup.element.before(cmGroup.element);
|
||||||
|
}
|
||||||
|
});
|
||||||
113
modules/manager_ext_core.py
Normal file
113
modules/manager_ext_core.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import configparser
|
||||||
|
import manager_core as core
|
||||||
|
import cm_global
|
||||||
|
from manager_util import *
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import folder_paths
|
||||||
|
from comfy.cli_args import args
|
||||||
|
import latent_preview
|
||||||
|
|
||||||
|
|
||||||
|
version_code = [3, 0, 1]
|
||||||
|
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||||
|
|
||||||
|
DEFAULT_CHANNEL = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main"
|
||||||
|
|
||||||
|
manager_ext_config_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'manager-ext.ini'))
|
||||||
|
cached_config = None
|
||||||
|
|
||||||
|
manager_ext_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
channel_list_path = os.path.join(manager_ext_path, 'channels.list')
|
||||||
|
|
||||||
|
|
||||||
|
def update_channel_dict():
|
||||||
|
if not os.path.exists(channel_list_path):
|
||||||
|
shutil.copy(channel_list_path+'.template', channel_list_path)
|
||||||
|
|
||||||
|
core.get_channel_dict() # for the loading
|
||||||
|
|
||||||
|
with open(os.path.join(manager_ext_path, 'channels.list'), 'r') as file:
|
||||||
|
channels = file.read()
|
||||||
|
for x in channels.split('\n'):
|
||||||
|
channel_info = x.split("::")
|
||||||
|
if len(channel_info) == 2:
|
||||||
|
core.channel_dict[channel_info[0]] = channel_info[1]
|
||||||
|
|
||||||
|
|
||||||
|
update_channel_dict()
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_preview_method():
|
||||||
|
if args.preview_method == latent_preview.LatentPreviewMethod.Auto:
|
||||||
|
return "auto"
|
||||||
|
elif args.preview_method == latent_preview.LatentPreviewMethod.Latent2RGB:
|
||||||
|
return "latent2rgb"
|
||||||
|
elif args.preview_method == latent_preview.LatentPreviewMethod.TAESD:
|
||||||
|
return "taesd"
|
||||||
|
else:
|
||||||
|
return "none"
|
||||||
|
|
||||||
|
|
||||||
|
def write_config():
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config['default'] = {
|
||||||
|
'preview_method': get_current_preview_method(),
|
||||||
|
'share_option': get_config()['share_option'],
|
||||||
|
'default_ui': get_config()['default_ui'],
|
||||||
|
'component_policy': get_config()['component_policy'],
|
||||||
|
'double_click_policy': get_config()['double_click_policy'],
|
||||||
|
'security_level': get_config()['security_level'],
|
||||||
|
}
|
||||||
|
|
||||||
|
directory = os.path.dirname(manager_ext_config_path)
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
os.makedirs(directory)
|
||||||
|
|
||||||
|
with open(manager_ext_config_path, 'w') as configfile:
|
||||||
|
config.write(configfile)
|
||||||
|
|
||||||
|
|
||||||
|
def read_config():
|
||||||
|
try:
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(manager_ext_config_path)
|
||||||
|
default_conf = config['default']
|
||||||
|
|
||||||
|
# policy migration: disable_unsecure_features -> security_level
|
||||||
|
security_level = default_conf['security_level'] if 'security_level' in default_conf else 'normal'
|
||||||
|
|
||||||
|
return {
|
||||||
|
'preview_method': default_conf['preview_method'] if 'preview_method' in default_conf else get_current_preview_method(),
|
||||||
|
'share_option': default_conf['share_option'] if 'share_option' in default_conf else 'all',
|
||||||
|
'default_ui': default_conf['default_ui'] if 'default_ui' in default_conf else 'none',
|
||||||
|
'component_policy': default_conf['component_policy'] if 'component_policy' in default_conf else 'workflow',
|
||||||
|
'double_click_policy': default_conf['double_click_policy'] if 'double_click_policy' in default_conf else 'copy-all',
|
||||||
|
'security_level': security_level
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return {
|
||||||
|
'preview_method': get_current_preview_method(),
|
||||||
|
'share_option': 'all',
|
||||||
|
'default_ui': 'none',
|
||||||
|
'component_policy': 'workflow',
|
||||||
|
'double_click_policy': 'copy-all',
|
||||||
|
'security_level': 'normal',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
global cached_config
|
||||||
|
|
||||||
|
if cached_config is None:
|
||||||
|
cached_config = read_config()
|
||||||
|
|
||||||
|
return cached_config
|
||||||
|
|
||||||
|
|
||||||
|
def pip_install(packages):
|
||||||
|
install_cmd = ['#FORCE', sys.executable, "-m", "pip", "install", '-U'] + packages
|
||||||
|
core.try_install_script('pip install via manager', '..', install_cmd)
|
||||||
745
modules/manager_ext_server.py
Normal file
745
modules/manager_ext_server.py
Normal file
@@ -0,0 +1,745 @@
|
|||||||
|
import traceback
|
||||||
|
|
||||||
|
import folder_paths
|
||||||
|
import locale
|
||||||
|
import concurrent
|
||||||
|
import nodes
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import git
|
||||||
|
|
||||||
|
from server import PromptServer
|
||||||
|
import manager_core as core
|
||||||
|
import cm_global
|
||||||
|
|
||||||
|
from . import manager_ext_core as ext_core
|
||||||
|
from . import manager_ext_util
|
||||||
|
|
||||||
|
print(f"### Loading: ComfyUI-Manager (ext) ({ext_core.version_str})")
|
||||||
|
|
||||||
|
comfy_ui_hash = "-"
|
||||||
|
|
||||||
|
SECURITY_MESSAGE_MIDDLE_OR_BELOW = f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||||
|
SECURITY_MESSAGE_NORMAL_MINUS = f"ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||||
|
SECURITY_MESSAGE_GENERAL = f"ERROR: This installation is not allowed in this security_level. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||||
|
|
||||||
|
routes = PromptServer.instance.routes
|
||||||
|
|
||||||
|
|
||||||
|
def handle_stream(stream, prefix):
|
||||||
|
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
||||||
|
for msg in stream:
|
||||||
|
if prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg):
|
||||||
|
if msg.startswith('100%'):
|
||||||
|
print('\r' + msg, end="", file=sys.stderr),
|
||||||
|
else:
|
||||||
|
print('\r' + msg[:-1], end="", file=sys.stderr),
|
||||||
|
else:
|
||||||
|
if prefix == '[!]':
|
||||||
|
print(prefix, msg, end="", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(prefix, msg, end="")
|
||||||
|
|
||||||
|
|
||||||
|
from comfy.cli_args import args
|
||||||
|
import latent_preview
|
||||||
|
|
||||||
|
|
||||||
|
is_local_mode = args.listen.startswith('127.') or args.listen.startswith('local.')
|
||||||
|
|
||||||
|
|
||||||
|
def is_allowed_security_level(level):
|
||||||
|
if level == 'block':
|
||||||
|
return False
|
||||||
|
elif level == 'high':
|
||||||
|
if is_local_mode:
|
||||||
|
return core.get_config()['security_level'].lower() in ['weak', 'normal-']
|
||||||
|
else:
|
||||||
|
return core.get_config()['security_level'].lower() == 'weak'
|
||||||
|
elif level == 'middle':
|
||||||
|
return core.get_config()['security_level'].lower() in ['weak', 'normal', 'normal-']
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def get_risky_level(files, pip_packages):
|
||||||
|
json_data1 = await core.get_data_by_mode('local', 'custom-node-list.json')
|
||||||
|
json_data2 = await core.get_data_by_mode('cache', 'custom-node-list.json', channel_url='https://github.com/ltdrdata/ComfyUI-Manager/raw/main')
|
||||||
|
|
||||||
|
all_urls = set()
|
||||||
|
for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:
|
||||||
|
all_urls.update(x['files'])
|
||||||
|
|
||||||
|
for x in files:
|
||||||
|
if x not in all_urls:
|
||||||
|
return "high"
|
||||||
|
|
||||||
|
all_pip_packages = set()
|
||||||
|
for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:
|
||||||
|
if "pip" in x:
|
||||||
|
all_pip_packages.update(x['pip'])
|
||||||
|
|
||||||
|
for p in pip_packages:
|
||||||
|
if p not in all_pip_packages:
|
||||||
|
return "block"
|
||||||
|
|
||||||
|
return "middle"
|
||||||
|
|
||||||
|
|
||||||
|
from manager_downloader import download_url
|
||||||
|
|
||||||
|
components_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'components'))
|
||||||
|
|
||||||
|
|
||||||
|
def set_preview_method(method):
|
||||||
|
if method == 'auto':
|
||||||
|
args.preview_method = latent_preview.LatentPreviewMethod.Auto
|
||||||
|
elif method == 'latent2rgb':
|
||||||
|
args.preview_method = latent_preview.LatentPreviewMethod.Latent2RGB
|
||||||
|
elif method == 'taesd':
|
||||||
|
args.preview_method = latent_preview.LatentPreviewMethod.TAESD
|
||||||
|
else:
|
||||||
|
args.preview_method = latent_preview.LatentPreviewMethod.NoPreviews
|
||||||
|
|
||||||
|
ext_core.get_config()['preview_method'] = args.preview_method
|
||||||
|
|
||||||
|
|
||||||
|
set_preview_method(ext_core.get_config()['preview_method'])
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_ui_mode(mode):
|
||||||
|
ext_core.get_config()['default_ui'] = mode
|
||||||
|
|
||||||
|
|
||||||
|
def set_component_policy(mode):
|
||||||
|
ext_core.get_config()['component_policy'] = mode
|
||||||
|
|
||||||
|
|
||||||
|
def set_double_click_policy(mode):
|
||||||
|
ext_core.get_config()['double_click_policy'] = mode
|
||||||
|
|
||||||
|
|
||||||
|
# Expand Server api
|
||||||
|
import server
|
||||||
|
from aiohttp import web
|
||||||
|
import aiohttp
|
||||||
|
import json
|
||||||
|
import zipfile
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
def check_state_of_git_node_pack(node_packs, do_fetch=False, do_update_check=True, do_update=False):
|
||||||
|
if do_fetch:
|
||||||
|
print("Start fetching...", end="")
|
||||||
|
elif do_update:
|
||||||
|
print("Start updating...", end="")
|
||||||
|
elif do_update_check:
|
||||||
|
print("Start update check...", end="")
|
||||||
|
|
||||||
|
def process_custom_node(item):
|
||||||
|
core.check_state_of_git_node_pack_single(item, do_fetch, do_update_check, do_update)
|
||||||
|
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(4) as executor:
|
||||||
|
for k, v in node_packs.items():
|
||||||
|
if v.get('active_version') in ['unknown', 'nightly']:
|
||||||
|
executor.submit(process_custom_node, v)
|
||||||
|
|
||||||
|
if do_fetch:
|
||||||
|
print(f"\x1b[2K\rFetching done.")
|
||||||
|
elif do_update:
|
||||||
|
update_exists = any(item.get('updatable', False) for item in node_packs.values())
|
||||||
|
if update_exists:
|
||||||
|
print(f"\x1b[2K\rUpdate done.")
|
||||||
|
else:
|
||||||
|
print(f"\x1b[2K\rAll extensions are already up-to-date.")
|
||||||
|
elif do_update_check:
|
||||||
|
print(f"\x1b[2K\rUpdate check done.")
|
||||||
|
|
||||||
|
|
||||||
|
def nickname_filter(json_obj):
|
||||||
|
preemptions_map = {}
|
||||||
|
|
||||||
|
for k, x in json_obj.items():
|
||||||
|
if 'preemptions' in x[1]:
|
||||||
|
for y in x[1]['preemptions']:
|
||||||
|
preemptions_map[y] = k
|
||||||
|
elif k.endswith("/ComfyUI"):
|
||||||
|
for y in x[0]:
|
||||||
|
preemptions_map[y] = k
|
||||||
|
|
||||||
|
updates = {}
|
||||||
|
for k, x in json_obj.items():
|
||||||
|
removes = set()
|
||||||
|
for y in x[0]:
|
||||||
|
k2 = preemptions_map.get(y)
|
||||||
|
if k2 is not None and k != k2:
|
||||||
|
removes.add(y)
|
||||||
|
|
||||||
|
if len(removes) > 0:
|
||||||
|
updates[k] = [y for y in x[0] if y not in removes]
|
||||||
|
|
||||||
|
for k, v in updates.items():
|
||||||
|
json_obj[k][0] = v
|
||||||
|
|
||||||
|
return json_obj
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/customnode/getmappings")
|
||||||
|
async def fetch_customnode_mappings(request):
|
||||||
|
"""
|
||||||
|
provide unified (node -> node pack) mapping list
|
||||||
|
"""
|
||||||
|
mode = request.rel_url.query["mode"]
|
||||||
|
|
||||||
|
nickname_mode = False
|
||||||
|
if mode == "nickname":
|
||||||
|
mode = "local"
|
||||||
|
nickname_mode = True
|
||||||
|
|
||||||
|
json_obj = await core.get_data_by_mode(mode, 'extension-node-map.json')
|
||||||
|
json_obj = core.map_to_unified_keys(json_obj)
|
||||||
|
|
||||||
|
if nickname_mode:
|
||||||
|
json_obj = nickname_filter(json_obj)
|
||||||
|
|
||||||
|
all_nodes = set()
|
||||||
|
patterns = []
|
||||||
|
for k, x in json_obj.items():
|
||||||
|
all_nodes.update(set(x[0]))
|
||||||
|
|
||||||
|
if 'nodename_pattern' in x[1]:
|
||||||
|
patterns.append((x[1]['nodename_pattern'], x[0]))
|
||||||
|
|
||||||
|
missing_nodes = set(nodes.NODE_CLASS_MAPPINGS.keys()) - all_nodes
|
||||||
|
|
||||||
|
for x in missing_nodes:
|
||||||
|
for pat, item in patterns:
|
||||||
|
if re.match(pat, x):
|
||||||
|
item.append(x)
|
||||||
|
|
||||||
|
return web.json_response(json_obj, content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/customnode/fetch_updates")
|
||||||
|
async def fetch_updates(request):
|
||||||
|
try:
|
||||||
|
if request.rel_url.query["mode"] == "local":
|
||||||
|
channel = 'local'
|
||||||
|
else:
|
||||||
|
channel = core.get_config()['channel_url']
|
||||||
|
|
||||||
|
await core.unified_manager.reload(request.rel_url.query["mode"])
|
||||||
|
await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])
|
||||||
|
|
||||||
|
res = core.unified_manager.fetch_or_pull_git_repo(is_pull=False)
|
||||||
|
|
||||||
|
for x in res['failed']:
|
||||||
|
print(f"FETCH FAILED: {x}")
|
||||||
|
|
||||||
|
print("\nDone.")
|
||||||
|
|
||||||
|
if len(res['updated']) > 0:
|
||||||
|
return web.Response(status=201)
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/customnode/update_all")
|
||||||
|
async def update_all(request):
|
||||||
|
if not is_allowed_security_level('middle'):
|
||||||
|
print(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
|
return web.Response(status=403)
|
||||||
|
|
||||||
|
try:
|
||||||
|
core.save_snapshot_with_postfix('autosave')
|
||||||
|
|
||||||
|
if request.rel_url.query["mode"] == "local":
|
||||||
|
channel = 'local'
|
||||||
|
else:
|
||||||
|
channel = core.get_config()['channel_url']
|
||||||
|
|
||||||
|
await core.unified_manager.reload(request.rel_url.query["mode"])
|
||||||
|
await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])
|
||||||
|
|
||||||
|
updated_cnr = []
|
||||||
|
for k, v in core.unified_manager.active_nodes.items():
|
||||||
|
if v[0] != 'nightly':
|
||||||
|
res = core.unified_manager.unified_update(k, v[0])
|
||||||
|
if res.action == 'switch-cnr' and res:
|
||||||
|
updated_cnr.append(k)
|
||||||
|
|
||||||
|
res = core.unified_manager.fetch_or_pull_git_repo(is_pull=True)
|
||||||
|
|
||||||
|
res['updated'] += updated_cnr
|
||||||
|
|
||||||
|
for x in res['failed']:
|
||||||
|
print(f"PULL FAILED: {x}")
|
||||||
|
|
||||||
|
if len(res['updated']) == 0 and len(res['failed']) == 0:
|
||||||
|
status = 200
|
||||||
|
else:
|
||||||
|
status = 201
|
||||||
|
|
||||||
|
print(f"\nDone.")
|
||||||
|
return web.json_response(res, status=status, content_type='application/json')
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
return web.Response(status=400)
|
||||||
|
finally:
|
||||||
|
core.clear_pip_cache()
|
||||||
|
|
||||||
|
|
||||||
|
def convert_markdown_to_html(input_text):
|
||||||
|
pattern_a = re.compile(r'\[a/([^]]+)\]\(([^)]+)\)')
|
||||||
|
pattern_w = re.compile(r'\[w/([^]]+)\]')
|
||||||
|
pattern_i = re.compile(r'\[i/([^]]+)\]')
|
||||||
|
pattern_bold = re.compile(r'\*\*([^*]+)\*\*')
|
||||||
|
pattern_white = re.compile(r'%%([^*]+)%%')
|
||||||
|
|
||||||
|
def replace_a(match):
|
||||||
|
return f"<a href='{match.group(2)}' target='blank'>{match.group(1)}</a>"
|
||||||
|
|
||||||
|
def replace_w(match):
|
||||||
|
return f"<p class='cm-warn-note'>{match.group(1)}</p>"
|
||||||
|
|
||||||
|
def replace_i(match):
|
||||||
|
return f"<p class='cm-info-note'>{match.group(1)}</p>"
|
||||||
|
|
||||||
|
def replace_bold(match):
|
||||||
|
return f"<B>{match.group(1)}</B>"
|
||||||
|
|
||||||
|
def replace_white(match):
|
||||||
|
return f"<font color='white'>{match.group(1)}</font>"
|
||||||
|
|
||||||
|
input_text = input_text.replace('\\[', '[').replace('\\]', ']').replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
|
result_text = re.sub(pattern_a, replace_a, input_text)
|
||||||
|
result_text = re.sub(pattern_w, replace_w, result_text)
|
||||||
|
result_text = re.sub(pattern_i, replace_i, result_text)
|
||||||
|
result_text = re.sub(pattern_bold, replace_bold, result_text)
|
||||||
|
result_text = re.sub(pattern_white, replace_white, result_text)
|
||||||
|
|
||||||
|
return result_text.replace("\n", "<BR>")
|
||||||
|
|
||||||
|
|
||||||
|
def populate_markdown(x):
|
||||||
|
if 'description' in x:
|
||||||
|
x['description'] = convert_markdown_to_html(manager_ext_util.sanitize_tag(x['description']))
|
||||||
|
|
||||||
|
if 'name' in x:
|
||||||
|
x['name'] = manager_ext_util.sanitize_tag(x['name'])
|
||||||
|
|
||||||
|
if 'title' in x:
|
||||||
|
x['title'] = manager_ext_util.sanitize_tag(x['title'])
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/customnode/getlist")
|
||||||
|
async def fetch_customnode_list(request):
|
||||||
|
"""
|
||||||
|
provide unified custom node list
|
||||||
|
"""
|
||||||
|
if "skip_update" in request.rel_url.query and request.rel_url.query["skip_update"] == "true":
|
||||||
|
skip_update = True
|
||||||
|
else:
|
||||||
|
skip_update = False
|
||||||
|
|
||||||
|
if request.rel_url.query["mode"] == "local":
|
||||||
|
channel = 'local'
|
||||||
|
else:
|
||||||
|
channel = core.get_config()['channel_url']
|
||||||
|
|
||||||
|
node_packs = await core.get_unified_total_nodes(channel, request.rel_url.query["mode"])
|
||||||
|
json_obj_github = core.get_data_by_mode(request.rel_url.query["mode"], 'github-stats.json', 'default')
|
||||||
|
json_obj_extras = core.get_data_by_mode(request.rel_url.query["mode"], 'extras.json', 'default')
|
||||||
|
|
||||||
|
core.populate_github_stats(node_packs, await json_obj_github)
|
||||||
|
core.populate_favorites(node_packs, await json_obj_extras)
|
||||||
|
|
||||||
|
check_state_of_git_node_pack(node_packs, False, do_update_check=not skip_update)
|
||||||
|
|
||||||
|
for v in node_packs.values():
|
||||||
|
populate_markdown(v)
|
||||||
|
|
||||||
|
if channel != 'local':
|
||||||
|
found = 'custom'
|
||||||
|
|
||||||
|
for name, url in core.get_channel_dict().items():
|
||||||
|
if url == channel:
|
||||||
|
found = name
|
||||||
|
break
|
||||||
|
|
||||||
|
channel = found
|
||||||
|
|
||||||
|
result = dict(channel=channel, node_packs=node_packs)
|
||||||
|
|
||||||
|
return web.json_response(result, content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/customnode/alternatives")
|
||||||
|
async def fetch_customnode_alternatives(request):
|
||||||
|
alter_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json')
|
||||||
|
|
||||||
|
res = {}
|
||||||
|
|
||||||
|
for item in alter_json['items']:
|
||||||
|
populate_markdown(item)
|
||||||
|
res[item['id']] = item
|
||||||
|
|
||||||
|
res = core.map_to_unified_keys(res)
|
||||||
|
|
||||||
|
return web.json_response(res, content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/snapshot/getlist")
|
||||||
|
async def get_snapshot_list(request):
|
||||||
|
snapshots_directory = os.path.join(core.comfyui_manager_path, 'snapshots')
|
||||||
|
items = [f[:-5] for f in os.listdir(snapshots_directory) if f.endswith('.json')]
|
||||||
|
items.sort(reverse=True)
|
||||||
|
return web.json_response({'items': items}, content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/snapshot/remove")
|
||||||
|
async def remove_snapshot(request):
|
||||||
|
if not is_allowed_security_level('middle'):
|
||||||
|
print(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
|
return web.Response(status=403)
|
||||||
|
|
||||||
|
try:
|
||||||
|
target = request.rel_url.query["target"]
|
||||||
|
|
||||||
|
path = os.path.join(core.comfyui_manager_path, 'snapshots', f"{target}.json")
|
||||||
|
if os.path.exists(path):
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
except:
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/snapshot/get_current")
|
||||||
|
async def get_current_snapshot_api(request):
|
||||||
|
try:
|
||||||
|
return web.json_response(core.get_current_snapshot(), content_type='application/json')
|
||||||
|
except:
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
def unzip_install(files):
|
||||||
|
temp_filename = 'manager-temp.zip'
|
||||||
|
for url in files:
|
||||||
|
if url.endswith("/"):
|
||||||
|
url = url[:-1]
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
|
||||||
|
|
||||||
|
req = urllib.request.Request(url, headers=headers)
|
||||||
|
response = urllib.request.urlopen(req)
|
||||||
|
data = response.read()
|
||||||
|
|
||||||
|
with open(temp_filename, 'wb') as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
with zipfile.ZipFile(temp_filename, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(core.custom_nodes_path)
|
||||||
|
|
||||||
|
os.remove(temp_filename)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Install(unzip) error: {url} / {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("Installation was successful.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def download_url_with_agent(url, save_path):
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
|
||||||
|
|
||||||
|
req = urllib.request.Request(url, headers=headers)
|
||||||
|
response = urllib.request.urlopen(req)
|
||||||
|
data = response.read()
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.dirname(save_path)):
|
||||||
|
os.makedirs(os.path.dirname(save_path))
|
||||||
|
|
||||||
|
with open(save_path, 'wb') as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Download error: {url} / {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("Installation was successful.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/customnode/install/git_url")
|
||||||
|
async def install_custom_node_git_url(request):
|
||||||
|
if not is_allowed_security_level('high'):
|
||||||
|
print(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||||
|
return web.Response(status=403)
|
||||||
|
|
||||||
|
url = await request.text()
|
||||||
|
res = await core.gitclone_install(url)
|
||||||
|
|
||||||
|
if res.action == 'skip':
|
||||||
|
print(f"Already installed: '{res.target}'")
|
||||||
|
return web.Response(status=200)
|
||||||
|
elif res.result:
|
||||||
|
print(f"After restarting ComfyUI, please refresh the browser.")
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
print(res.msg)
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/customnode/install/pip")
|
||||||
|
async def install_pip(request):
|
||||||
|
if not is_allowed_security_level('high'):
|
||||||
|
print(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||||
|
return web.Response(status=403)
|
||||||
|
|
||||||
|
packages = await request.text()
|
||||||
|
core.pip_install(packages.split(' '))
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/comfyui_manager/update_comfyui")
|
||||||
|
async def update_comfyui(request):
|
||||||
|
print(f"Update ComfyUI")
|
||||||
|
|
||||||
|
try:
|
||||||
|
repo_path = os.path.dirname(folder_paths.__file__)
|
||||||
|
res = core.update_path(repo_path)
|
||||||
|
if res == "fail":
|
||||||
|
print(f"ComfyUI update fail: The installed ComfyUI does not have a Git repository.")
|
||||||
|
return web.Response(status=400)
|
||||||
|
elif res == "updated":
|
||||||
|
return web.Response(status=201)
|
||||||
|
else: # skipped
|
||||||
|
return web.Response(status=200)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ComfyUI update fail: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/comfyui_manager/comfyui_switch_version")
|
||||||
|
async def comfyui_switch_version(request):
|
||||||
|
try:
|
||||||
|
if "ver" in request.rel_url.query:
|
||||||
|
core.switch_comfyui(request.rel_url.query['ver'])
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ComfyUI update fail: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/manager/preview_method")
|
||||||
|
async def preview_method(request):
|
||||||
|
if "value" in request.rel_url.query:
|
||||||
|
set_preview_method(request.rel_url.query['value'])
|
||||||
|
ext_core.write_config()
|
||||||
|
else:
|
||||||
|
return web.Response(text=ext_core.get_current_preview_method(), status=200)
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/manager/default_ui")
|
||||||
|
async def default_ui_mode(request):
|
||||||
|
if "value" in request.rel_url.query:
|
||||||
|
set_default_ui_mode(request.rel_url.query['value'])
|
||||||
|
ext_core.write_config()
|
||||||
|
else:
|
||||||
|
return web.Response(text=ext_core.get_config()['default_ui'], status=200)
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/manager/component/policy")
|
||||||
|
async def component_policy(request):
|
||||||
|
if "value" in request.rel_url.query:
|
||||||
|
set_component_policy(request.rel_url.query['value'])
|
||||||
|
ext_core.write_config()
|
||||||
|
else:
|
||||||
|
return web.Response(text=ext_core.get_config()['component_policy'], status=200)
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/manager/dbl_click/policy")
|
||||||
|
async def dbl_click_policy(request):
|
||||||
|
if "value" in request.rel_url.query:
|
||||||
|
set_double_click_policy(request.rel_url.query['value'])
|
||||||
|
ext_core.write_config()
|
||||||
|
else:
|
||||||
|
return web.Response(text=ext_core.get_config()['double_click_policy'], status=200)
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
def add_target_blank(html_text):
|
||||||
|
pattern = r'(<a\s+href="[^"]*"\s*[^>]*)(>)'
|
||||||
|
|
||||||
|
def add_target(match):
|
||||||
|
if 'target=' not in match.group(1):
|
||||||
|
return match.group(1) + ' target="_blank"' + match.group(2)
|
||||||
|
return match.group(0)
|
||||||
|
|
||||||
|
modified_html = re.sub(pattern, add_target, html_text)
|
||||||
|
|
||||||
|
return modified_html
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/manager/notice")
|
||||||
|
async def get_notice(request):
|
||||||
|
url = "github.com"
|
||||||
|
path = "/ltdrdata/ltdrdata.github.io/wiki/News"
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||||
|
async with session.get(f"https://{url}{path}") as response:
|
||||||
|
if response.status == 200:
|
||||||
|
# html_content = response.read().decode('utf-8')
|
||||||
|
html_content = await response.text()
|
||||||
|
|
||||||
|
pattern = re.compile(r'<div class="markdown-body">([\s\S]*?)</div>')
|
||||||
|
match = pattern.search(html_content)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
markdown_content = match.group(1)
|
||||||
|
if core.comfyui_tag:
|
||||||
|
markdown_content += f"<HR>ComfyUI: <b>{core.comfyui_tag}</b><BR>Commit Date: {core.comfy_ui_commit_datetime.date()}"
|
||||||
|
else:
|
||||||
|
markdown_content += f"<HR>ComfyUI: <b>{core.comfy_ui_revision}</b>[{comfy_ui_hash[:6]}]({core.comfy_ui_commit_datetime.date()})"
|
||||||
|
# markdown_content += f"<BR> ()"
|
||||||
|
markdown_content += f"<BR>manager-core: {core.version_str}"
|
||||||
|
markdown_content += f"<BR>manager-ext: {ext_core.version_str}"
|
||||||
|
|
||||||
|
markdown_content = add_target_blank(markdown_content)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if core.comfy_ui_required_commit_datetime.date() > core.comfy_ui_commit_datetime.date():
|
||||||
|
markdown_content = f'<P style="text-align: center; color:red; background-color:white; font-weight:bold">Your ComfyUI is too OUTDATED!!!</P>' + markdown_content
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return web.Response(text=markdown_content, status=200)
|
||||||
|
else:
|
||||||
|
return web.Response(text="Unable to retrieve Notice", status=200)
|
||||||
|
else:
|
||||||
|
return web.Response(text="Unable to retrieve Notice", status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/manager/reboot")
|
||||||
|
def restart(self):
|
||||||
|
if not is_allowed_security_level('middle'):
|
||||||
|
print(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
|
return web.Response(status=403)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stdout.close_log()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if '__COMFY_CLI_SESSION__' in os.environ:
|
||||||
|
with open(os.path.join(os.environ['__COMFY_CLI_SESSION__'] + '.reboot'), 'w') as file:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"\nRestarting...\n\n")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
print(f"\nRestarting... [Legacy Mode]\n\n")
|
||||||
|
if sys.platform.startswith('win32'):
|
||||||
|
return os.execv(sys.executable, ['"' + sys.executable + '"', '"' + sys.argv[0] + '"'] + sys.argv[1:])
|
||||||
|
else:
|
||||||
|
return os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_filename(input_string):
|
||||||
|
result_string = re.sub(r'[^a-zA-Z0-9_]', '_', input_string)
|
||||||
|
return result_string
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/manager/component/save")
|
||||||
|
async def save_component(request):
|
||||||
|
try:
|
||||||
|
data = await request.json()
|
||||||
|
name = data['name']
|
||||||
|
workflow = data['workflow']
|
||||||
|
|
||||||
|
if not os.path.exists(components_path):
|
||||||
|
os.mkdir(components_path)
|
||||||
|
|
||||||
|
if 'packname' in workflow and workflow['packname'] != '':
|
||||||
|
sanitized_name = sanitize_filename(workflow['packname']) + '.pack'
|
||||||
|
else:
|
||||||
|
sanitized_name = sanitize_filename(name) + '.json'
|
||||||
|
|
||||||
|
filepath = os.path.join(components_path, sanitized_name)
|
||||||
|
components = {}
|
||||||
|
if os.path.exists(filepath):
|
||||||
|
with open(filepath) as f:
|
||||||
|
components = json.load(f)
|
||||||
|
|
||||||
|
components[name] = workflow
|
||||||
|
|
||||||
|
with open(filepath, 'w') as f:
|
||||||
|
json.dump(components, f, indent=4, sort_keys=True)
|
||||||
|
return web.Response(text=filepath, status=200)
|
||||||
|
except:
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/manager/component/loads")
|
||||||
|
async def load_components(request):
|
||||||
|
try:
|
||||||
|
json_files = [f for f in os.listdir(components_path) if f.endswith('.json')]
|
||||||
|
pack_files = [f for f in os.listdir(components_path) if f.endswith('.pack')]
|
||||||
|
|
||||||
|
components = {}
|
||||||
|
for json_file in json_files + pack_files:
|
||||||
|
file_path = os.path.join(components_path, json_file)
|
||||||
|
with open(file_path, 'r') as file:
|
||||||
|
try:
|
||||||
|
# When there is a conflict between the .pack and the .json, the pack takes precedence and overrides.
|
||||||
|
components.update(json.load(file))
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"[ComfyUI-Manager] Error decoding component file in file {json_file}: {e}")
|
||||||
|
|
||||||
|
return web.json_response(components)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ComfyUI-Manager] failed to load components\n{e}")
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
args.enable_cors_header = "*"
|
||||||
|
if hasattr(PromptServer.instance, "app"):
|
||||||
|
app = PromptServer.instance.app
|
||||||
|
cors_middleware = server.create_cors_middleware(args.enable_cors_header)
|
||||||
|
app.middlewares.append(cors_middleware)
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize(data):
|
||||||
|
return data.replace("<", "<").replace(">", ">")
|
||||||
|
|
||||||
|
if not os.path.exists(ext_core.manager_ext_config_path):
|
||||||
|
ext_core.get_config()
|
||||||
|
ext_core.write_config()
|
||||||
|
|
||||||
|
cm_global.register_extension('ComfyUI-Manager',
|
||||||
|
{'version': core.version,
|
||||||
|
'name': 'ComfyUI Manager (Extension)',
|
||||||
|
'nodes': {},
|
||||||
|
'description': 'ComfyUI-Manager (Extension)', })
|
||||||
|
|
||||||
|
cm_global.variables['manager-core.show_menu'] = False
|
||||||
174
modules/manager_ext_util.py
Normal file
174
modules/manager_ext_util.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import traceback
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
cache_lock = threading.Lock()
|
||||||
|
|
||||||
|
comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
cache_dir = os.path.join(comfyui_manager_path, '.cache')
|
||||||
|
|
||||||
|
|
||||||
|
# DON'T USE StrictVersion - cannot handle pre_release version
|
||||||
|
# try:
|
||||||
|
# from distutils.version import StrictVersion
|
||||||
|
# except:
|
||||||
|
# print(f"[ComfyUI-Manager] 'distutils' package not found. Activating fallback mode for compatibility.")
|
||||||
|
class StrictVersion:
|
||||||
|
def __init__(self, version_string):
|
||||||
|
self.version_string = version_string
|
||||||
|
self.major = 0
|
||||||
|
self.minor = 0
|
||||||
|
self.patch = 0
|
||||||
|
self.pre_release = None
|
||||||
|
self.parse_version_string()
|
||||||
|
|
||||||
|
def parse_version_string(self):
|
||||||
|
parts = self.version_string.split('.')
|
||||||
|
if not parts:
|
||||||
|
raise ValueError("Version string must not be empty")
|
||||||
|
|
||||||
|
self.major = int(parts[0])
|
||||||
|
self.minor = int(parts[1]) if len(parts) > 1 else 0
|
||||||
|
self.patch = int(parts[2]) if len(parts) > 2 else 0
|
||||||
|
|
||||||
|
# Handling pre-release versions if present
|
||||||
|
if len(parts) > 3:
|
||||||
|
self.pre_release = parts[3]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
version = f"{self.major}.{self.minor}.{self.patch}"
|
||||||
|
if self.pre_release:
|
||||||
|
version += f"-{self.pre_release}"
|
||||||
|
return version
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (self.major, self.minor, self.patch, self.pre_release) == \
|
||||||
|
(other.major, other.minor, other.patch, other.pre_release)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch):
|
||||||
|
return self.pre_release_compare(self.pre_release, other.pre_release) < 0
|
||||||
|
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pre_release_compare(pre1, pre2):
|
||||||
|
if pre1 == pre2:
|
||||||
|
return 0
|
||||||
|
if pre1 is None:
|
||||||
|
return 1
|
||||||
|
if pre2 is None:
|
||||||
|
return -1
|
||||||
|
return -1 if pre1 < pre2 else 1
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self == other or self < other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return not self <= other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return not self < other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
|
||||||
|
def simple_hash(input_string):
|
||||||
|
hash_value = 0
|
||||||
|
for char in input_string:
|
||||||
|
hash_value = (hash_value * 31 + ord(char)) % (2**32)
|
||||||
|
|
||||||
|
return hash_value
|
||||||
|
|
||||||
|
|
||||||
|
def is_file_created_within_one_day(file_path):
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
file_creation_time = os.path.getctime(file_path)
|
||||||
|
current_time = datetime.now().timestamp()
|
||||||
|
time_difference = current_time - file_creation_time
|
||||||
|
|
||||||
|
return time_difference <= 86400
|
||||||
|
|
||||||
|
|
||||||
|
async def get_data(uri, silent=False):
|
||||||
|
if not silent:
|
||||||
|
print(f"FETCH DATA from: {uri}", end="")
|
||||||
|
|
||||||
|
if uri.startswith("http"):
|
||||||
|
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||||
|
async with session.get(uri) as resp:
|
||||||
|
json_text = await resp.text()
|
||||||
|
else:
|
||||||
|
with cache_lock:
|
||||||
|
with open(uri, "r", encoding="utf-8") as f:
|
||||||
|
json_text = f.read()
|
||||||
|
|
||||||
|
json_obj = json.loads(json_text)
|
||||||
|
|
||||||
|
if not silent:
|
||||||
|
print(f" [DONE]")
|
||||||
|
|
||||||
|
return json_obj
|
||||||
|
|
||||||
|
|
||||||
|
async def get_data_with_cache(uri, silent=False, cache_mode=True):
|
||||||
|
cache_uri = str(simple_hash(uri)) + '_' + os.path.basename(uri).replace('&', "_").replace('?', "_").replace('=', "_")
|
||||||
|
cache_uri = os.path.join(cache_dir, cache_uri+'.json')
|
||||||
|
|
||||||
|
if cache_mode and is_file_created_within_one_day(cache_uri):
|
||||||
|
json_obj = await get_data(cache_uri, silent=silent)
|
||||||
|
else:
|
||||||
|
json_obj = await get_data(uri, silent=silent)
|
||||||
|
|
||||||
|
with cache_lock:
|
||||||
|
with open(cache_uri, "w", encoding='utf-8') as file:
|
||||||
|
json.dump(json_obj, file, indent=4, sort_keys=True)
|
||||||
|
if not silent:
|
||||||
|
print(f"[ComfyUI-Manager] default cache updated: {uri}")
|
||||||
|
|
||||||
|
return json_obj
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_tag(x):
|
||||||
|
return x.replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
|
|
||||||
|
def download_url(url, dest_folder, filename):
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Ensure the destination folder exists
|
||||||
|
if not os.path.exists(dest_folder):
|
||||||
|
os.makedirs(dest_folder)
|
||||||
|
|
||||||
|
# Full path to save the file
|
||||||
|
dest_path = os.path.join(dest_folder, filename)
|
||||||
|
|
||||||
|
# Download the file
|
||||||
|
response = requests.get(url, stream=True)
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(dest_path, 'wb') as file:
|
||||||
|
for chunk in response.iter_content(chunk_size=1024):
|
||||||
|
if chunk:
|
||||||
|
file.write(chunk)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Failed to download file from {url}")
|
||||||
|
|
||||||
|
|
||||||
|
def extract_package_as_zip(file_path, extract_path):
|
||||||
|
import zipfile
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(file_path, "r") as zip_ref:
|
||||||
|
zip_ref.extractall(extract_path)
|
||||||
|
extracted_files = zip_ref.namelist()
|
||||||
|
print(f"Extracted zip file to {extract_path}")
|
||||||
|
return extracted_files
|
||||||
|
except zipfile.BadZipFile:
|
||||||
|
print(f"File '{file_path}' is not a zip or is corrupted.")
|
||||||
|
return None
|
||||||
17
modules/migration_server.py
Normal file
17
modules/migration_server.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import git
|
||||||
|
from aiohttp import web
|
||||||
|
from server import PromptServer
|
||||||
|
|
||||||
|
manager_core_path = None
|
||||||
|
manager_core_url = "https://github.com/Comfy-Org/manager-core"
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/manager/install_manager_core")
|
||||||
|
def install_manager_core(request):
|
||||||
|
if manager_core_path is not None:
|
||||||
|
repo = git.Repo.clone_from(manager_core_url, manager_core_path)
|
||||||
|
repo.git.clear_cache()
|
||||||
|
repo.close()
|
||||||
|
else:
|
||||||
|
print(f"[ComfyUI Manager] Failed to install `manager-core`")
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
@@ -1,648 +0,0 @@
|
|||||||
import datetime
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import atexit
|
|
||||||
import threading
|
|
||||||
import re
|
|
||||||
import locale
|
|
||||||
import platform
|
|
||||||
import json
|
|
||||||
import ast
|
|
||||||
import logging
|
|
||||||
|
|
||||||
glob_path = os.path.join(os.path.dirname(__file__), "glob")
|
|
||||||
sys.path.append(glob_path)
|
|
||||||
|
|
||||||
import security_check
|
|
||||||
from manager_util import *
|
|
||||||
import cm_global
|
|
||||||
|
|
||||||
security_check.security_check()
|
|
||||||
|
|
||||||
cm_global.pip_blacklist = ['torch', 'torchsde', 'torchvision']
|
|
||||||
cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
|
|
||||||
|
|
||||||
|
|
||||||
def skip_pip_spam(x):
|
|
||||||
return ('Requirement already satisfied:' in x) or ("DEPRECATION: Loading egg at" in x)
|
|
||||||
|
|
||||||
|
|
||||||
message_collapses = [skip_pip_spam]
|
|
||||||
import_failed_extensions = set()
|
|
||||||
cm_global.variables['cm.on_revision_detected_handler'] = []
|
|
||||||
enable_file_logging = True
|
|
||||||
|
|
||||||
|
|
||||||
def register_message_collapse(f):
|
|
||||||
global message_collapses
|
|
||||||
message_collapses.append(f)
|
|
||||||
|
|
||||||
|
|
||||||
def is_import_failed_extension(name):
|
|
||||||
global import_failed_extensions
|
|
||||||
return name in import_failed_extensions
|
|
||||||
|
|
||||||
|
|
||||||
def check_file_logging():
|
|
||||||
global enable_file_logging
|
|
||||||
try:
|
|
||||||
import configparser
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(config_path)
|
|
||||||
default_conf = config['default']
|
|
||||||
|
|
||||||
if 'file_logging' in default_conf and default_conf['file_logging'].lower() == 'false':
|
|
||||||
enable_file_logging = False
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
check_file_logging()
|
|
||||||
|
|
||||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
|
||||||
if comfy_path is None:
|
|
||||||
comfy_path = os.path.abspath(os.path.dirname(sys.modules['__main__'].__file__))
|
|
||||||
|
|
||||||
sys.__comfyui_manager_register_message_collapse = register_message_collapse
|
|
||||||
sys.__comfyui_manager_is_import_failed_extension = is_import_failed_extension
|
|
||||||
cm_global.register_api('cm.register_message_collapse', register_message_collapse)
|
|
||||||
cm_global.register_api('cm.is_import_failed_extension', is_import_failed_extension)
|
|
||||||
|
|
||||||
|
|
||||||
comfyui_manager_path = os.path.dirname(__file__)
|
|
||||||
custom_nodes_path = os.path.abspath(os.path.join(comfyui_manager_path, ".."))
|
|
||||||
startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts")
|
|
||||||
restore_snapshot_path = os.path.join(startup_script_path, "restore-snapshot.json")
|
|
||||||
git_script_path = os.path.join(comfyui_manager_path, "git_helper.py")
|
|
||||||
pip_overrides_path = os.path.join(comfyui_manager_path, "pip_overrides.json")
|
|
||||||
|
|
||||||
|
|
||||||
cm_global.pip_overrides = {}
|
|
||||||
if os.path.exists(pip_overrides_path):
|
|
||||||
with open(pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
|
|
||||||
cm_global.pip_overrides = json.load(json_file)
|
|
||||||
cm_global.pip_overrides['numpy'] = 'numpy<2'
|
|
||||||
|
|
||||||
|
|
||||||
def remap_pip_package(pkg):
|
|
||||||
if pkg in cm_global.pip_overrides:
|
|
||||||
res = cm_global.pip_overrides[pkg]
|
|
||||||
print(f"[ComfyUI-Manager] '{pkg}' is remapped to '{res}'")
|
|
||||||
return res
|
|
||||||
else:
|
|
||||||
return pkg
|
|
||||||
|
|
||||||
|
|
||||||
std_log_lock = threading.Lock()
|
|
||||||
|
|
||||||
|
|
||||||
def handle_stream(stream, prefix):
|
|
||||||
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
|
||||||
for msg in stream:
|
|
||||||
if prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg):
|
|
||||||
if msg.startswith('100%'):
|
|
||||||
print('\r' + msg, end="", file=sys.stderr),
|
|
||||||
else:
|
|
||||||
print('\r' + msg[:-1], end="", file=sys.stderr),
|
|
||||||
else:
|
|
||||||
if prefix == '[!]':
|
|
||||||
print(prefix, msg, end="", file=sys.stderr)
|
|
||||||
else:
|
|
||||||
print(prefix, msg, end="")
|
|
||||||
|
|
||||||
|
|
||||||
def process_wrap(cmd_str, cwd_path, handler=None, env=None):
|
|
||||||
process = subprocess.Popen(cmd_str, cwd=cwd_path, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
|
|
||||||
|
|
||||||
if handler is None:
|
|
||||||
handler = handle_stream
|
|
||||||
|
|
||||||
stdout_thread = threading.Thread(target=handler, args=(process.stdout, ""))
|
|
||||||
stderr_thread = threading.Thread(target=handler, args=(process.stderr, "[!]"))
|
|
||||||
|
|
||||||
stdout_thread.start()
|
|
||||||
stderr_thread.start()
|
|
||||||
|
|
||||||
stdout_thread.join()
|
|
||||||
stderr_thread.join()
|
|
||||||
|
|
||||||
return process.wait()
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
if '--port' in sys.argv:
|
|
||||||
port_index = sys.argv.index('--port')
|
|
||||||
if port_index + 1 < len(sys.argv):
|
|
||||||
port = int(sys.argv[port_index + 1])
|
|
||||||
postfix = f"_{port}"
|
|
||||||
else:
|
|
||||||
postfix = ""
|
|
||||||
else:
|
|
||||||
postfix = ""
|
|
||||||
|
|
||||||
# Logger setup
|
|
||||||
if enable_file_logging:
|
|
||||||
if os.path.exists(f"comfyui{postfix}.log"):
|
|
||||||
if os.path.exists(f"comfyui{postfix}.prev.log"):
|
|
||||||
if os.path.exists(f"comfyui{postfix}.prev2.log"):
|
|
||||||
os.remove(f"comfyui{postfix}.prev2.log")
|
|
||||||
os.rename(f"comfyui{postfix}.prev.log", f"comfyui{postfix}.prev2.log")
|
|
||||||
os.rename(f"comfyui{postfix}.log", f"comfyui{postfix}.prev.log")
|
|
||||||
|
|
||||||
log_file = open(f"comfyui{postfix}.log", "w", encoding="utf-8", errors="ignore")
|
|
||||||
|
|
||||||
log_lock = threading.Lock()
|
|
||||||
|
|
||||||
original_stdout = sys.stdout
|
|
||||||
original_stderr = sys.stderr
|
|
||||||
|
|
||||||
if original_stdout.encoding.lower() == 'utf-8':
|
|
||||||
write_stdout = original_stdout.write
|
|
||||||
write_stderr = original_stderr.write
|
|
||||||
else:
|
|
||||||
def wrapper_stdout(msg):
|
|
||||||
original_stdout.write(msg.encode('utf-8').decode(original_stdout.encoding, errors="ignore"))
|
|
||||||
|
|
||||||
def wrapper_stderr(msg):
|
|
||||||
original_stderr.write(msg.encode('utf-8').decode(original_stderr.encoding, errors="ignore"))
|
|
||||||
|
|
||||||
write_stdout = wrapper_stdout
|
|
||||||
write_stderr = wrapper_stderr
|
|
||||||
|
|
||||||
pat_tqdm = r'\d+%.*\[(.*?)\]'
|
|
||||||
pat_import_fail = r'seconds \(IMPORT FAILED\):.*[/\\]custom_nodes[/\\](.*)$'
|
|
||||||
|
|
||||||
is_start_mode = True
|
|
||||||
|
|
||||||
|
|
||||||
class ComfyUIManagerLogger:
|
|
||||||
def __init__(self, is_stdout):
|
|
||||||
self.is_stdout = is_stdout
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
self.last_char = ''
|
|
||||||
|
|
||||||
def fileno(self):
|
|
||||||
try:
|
|
||||||
if self.is_stdout:
|
|
||||||
return original_stdout.fileno()
|
|
||||||
else:
|
|
||||||
return original_stderr.fileno()
|
|
||||||
except AttributeError:
|
|
||||||
# Handle error
|
|
||||||
raise ValueError("The object does not have a fileno method")
|
|
||||||
|
|
||||||
def isatty(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def write(self, message):
|
|
||||||
global is_start_mode
|
|
||||||
|
|
||||||
if any(f(message) for f in message_collapses):
|
|
||||||
return
|
|
||||||
|
|
||||||
if is_start_mode:
|
|
||||||
match = re.search(pat_import_fail, message)
|
|
||||||
if match:
|
|
||||||
import_failed_extensions.add(match.group(1))
|
|
||||||
|
|
||||||
if 'Starting server' in message:
|
|
||||||
is_start_mode = False
|
|
||||||
|
|
||||||
if not self.is_stdout:
|
|
||||||
match = re.search(pat_tqdm, message)
|
|
||||||
if match:
|
|
||||||
message = re.sub(r'([#|])\d', r'\1▌', message)
|
|
||||||
message = re.sub('#', '█', message)
|
|
||||||
if '100%' in message:
|
|
||||||
self.sync_write(message)
|
|
||||||
else:
|
|
||||||
write_stderr(message)
|
|
||||||
original_stderr.flush()
|
|
||||||
else:
|
|
||||||
self.sync_write(message)
|
|
||||||
else:
|
|
||||||
self.sync_write(message)
|
|
||||||
|
|
||||||
def sync_write(self, message, file_only=False):
|
|
||||||
with log_lock:
|
|
||||||
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
|
||||||
if self.last_char != '\n':
|
|
||||||
log_file.write(message)
|
|
||||||
else:
|
|
||||||
log_file.write(f"[{timestamp}] {message}")
|
|
||||||
log_file.flush()
|
|
||||||
self.last_char = message if message == '' else message[-1]
|
|
||||||
|
|
||||||
if not file_only:
|
|
||||||
with std_log_lock:
|
|
||||||
if self.is_stdout:
|
|
||||||
write_stdout(message)
|
|
||||||
original_stdout.flush()
|
|
||||||
else:
|
|
||||||
write_stderr(message)
|
|
||||||
original_stderr.flush()
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
log_file.flush()
|
|
||||||
|
|
||||||
with std_log_lock:
|
|
||||||
if self.is_stdout:
|
|
||||||
original_stdout.flush()
|
|
||||||
else:
|
|
||||||
original_stderr.flush()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.flush()
|
|
||||||
|
|
||||||
def reconfigure(self, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# You can close through sys.stderr.close_log()
|
|
||||||
def close_log(self):
|
|
||||||
sys.stderr = original_stderr
|
|
||||||
sys.stdout = original_stdout
|
|
||||||
log_file.close()
|
|
||||||
|
|
||||||
def close_log():
|
|
||||||
sys.stderr = original_stderr
|
|
||||||
sys.stdout = original_stdout
|
|
||||||
log_file.close()
|
|
||||||
|
|
||||||
|
|
||||||
if enable_file_logging:
|
|
||||||
sys.stdout = ComfyUIManagerLogger(True)
|
|
||||||
stderr_wrapper = ComfyUIManagerLogger(False)
|
|
||||||
sys.stderr = stderr_wrapper
|
|
||||||
|
|
||||||
atexit.register(close_log)
|
|
||||||
else:
|
|
||||||
sys.stdout.close_log = lambda: None
|
|
||||||
stderr_wrapper = None
|
|
||||||
|
|
||||||
|
|
||||||
class LoggingHandler(logging.Handler):
|
|
||||||
def emit(self, record):
|
|
||||||
global is_start_mode
|
|
||||||
|
|
||||||
message = record.getMessage()
|
|
||||||
|
|
||||||
if is_start_mode:
|
|
||||||
match = re.search(pat_import_fail, message)
|
|
||||||
if match:
|
|
||||||
import_failed_extensions.add(match.group(1))
|
|
||||||
|
|
||||||
if 'Starting server' in message:
|
|
||||||
is_start_mode = False
|
|
||||||
|
|
||||||
if stderr_wrapper:
|
|
||||||
stderr_wrapper.sync_write(message+'\n', file_only=True)
|
|
||||||
|
|
||||||
|
|
||||||
logging.getLogger().addHandler(LoggingHandler())
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[ComfyUI-Manager] Logging failed: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
import git
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
my_path = os.path.dirname(__file__)
|
|
||||||
requirements_path = os.path.join(my_path, "requirements.txt")
|
|
||||||
|
|
||||||
print(f"## ComfyUI-Manager: installing dependencies. (GitPython)")
|
|
||||||
try:
|
|
||||||
result = subprocess.check_output([sys.executable, '-s', '-m', 'pip', 'install', '-r', requirements_path])
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"## [ERROR] ComfyUI-Manager: Attempting to reinstall dependencies using an alternative method.")
|
|
||||||
try:
|
|
||||||
result = subprocess.check_output([sys.executable, '-s', '-m', 'pip', 'install', '--user', '-r', requirements_path])
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"## [ERROR] ComfyUI-Manager: Failed to install the GitPython package in the correct Python environment. Please install it manually in the appropriate environment. (You can seek help at https://app.element.io/#/room/%23comfyui_space%3Amatrix.org)")
|
|
||||||
|
|
||||||
try:
|
|
||||||
import git
|
|
||||||
print(f"## ComfyUI-Manager: installing dependencies done.")
|
|
||||||
except:
|
|
||||||
# maybe we should sys.exit() here? there is at least two screens worth of error messages still being pumped after our error messages
|
|
||||||
print(f"## [ERROR] ComfyUI-Manager: GitPython package seems to be installed, but failed to load somehow. Make sure you have a working git client installed")
|
|
||||||
|
|
||||||
|
|
||||||
print("** ComfyUI startup time:", datetime.datetime.now())
|
|
||||||
print("** Platform:", platform.system())
|
|
||||||
print("** Python version:", sys.version)
|
|
||||||
print("** Python executable:", sys.executable)
|
|
||||||
print("** ComfyUI Path:", comfy_path)
|
|
||||||
|
|
||||||
if enable_file_logging:
|
|
||||||
print("** Log path:", os.path.abspath('comfyui.log'))
|
|
||||||
else:
|
|
||||||
print("** Log path: file logging is disabled")
|
|
||||||
|
|
||||||
|
|
||||||
def read_downgrade_blacklist():
|
|
||||||
try:
|
|
||||||
import configparser
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(config_path)
|
|
||||||
default_conf = config['default']
|
|
||||||
|
|
||||||
if 'downgrade_blacklist' in default_conf:
|
|
||||||
items = default_conf['downgrade_blacklist'].split(',')
|
|
||||||
items = [x.strip() for x in items if x != '']
|
|
||||||
cm_global.pip_downgrade_blacklist += items
|
|
||||||
cm_global.pip_downgrade_blacklist = list(set(cm_global.pip_downgrade_blacklist))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
read_downgrade_blacklist()
|
|
||||||
|
|
||||||
|
|
||||||
def check_bypass_ssl():
|
|
||||||
try:
|
|
||||||
import configparser
|
|
||||||
import ssl
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(config_path)
|
|
||||||
default_conf = config['default']
|
|
||||||
|
|
||||||
if 'bypass_ssl' in default_conf and default_conf['bypass_ssl'].lower() == 'true':
|
|
||||||
print(f"[ComfyUI-Manager] WARN: Unsafe - SSL verification bypass option is Enabled. (see ComfyUI-Manager/config.ini)")
|
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context # SSL certificate error fix.
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
check_bypass_ssl()
|
|
||||||
|
|
||||||
|
|
||||||
# Perform install
|
|
||||||
processed_install = set()
|
|
||||||
script_list_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "startup-scripts", "install-scripts.txt")
|
|
||||||
pip_map = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_installed_packages():
|
|
||||||
global pip_map
|
|
||||||
|
|
||||||
if pip_map is None:
|
|
||||||
try:
|
|
||||||
result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True)
|
|
||||||
|
|
||||||
pip_map = {}
|
|
||||||
for line in result.split('\n'):
|
|
||||||
x = line.strip()
|
|
||||||
if x:
|
|
||||||
y = line.split()
|
|
||||||
if y[0] == 'Package' or y[0].startswith('-'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
pip_map[y[0]] = y[1]
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.")
|
|
||||||
return set()
|
|
||||||
|
|
||||||
return pip_map
|
|
||||||
|
|
||||||
|
|
||||||
def is_installed(name):
|
|
||||||
name = name.strip()
|
|
||||||
|
|
||||||
if name.startswith('#'):
|
|
||||||
return True
|
|
||||||
|
|
||||||
pattern = r'([^<>!=]+)([<>!=]=?)([0-9.a-zA-Z]*)'
|
|
||||||
match = re.search(pattern, name)
|
|
||||||
|
|
||||||
if match:
|
|
||||||
name = match.group(1)
|
|
||||||
|
|
||||||
if name in cm_global.pip_blacklist:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if name in cm_global.pip_downgrade_blacklist:
|
|
||||||
pips = get_installed_packages()
|
|
||||||
|
|
||||||
if match is None:
|
|
||||||
if name in pips:
|
|
||||||
return True
|
|
||||||
elif match.group(2) in ['<=', '==', '<']:
|
|
||||||
if name in pips:
|
|
||||||
if StrictVersion(pips[name]) >= StrictVersion(match.group(3)):
|
|
||||||
print(f"[ComfyUI-Manager] skip black listed pip installation: '{name}'")
|
|
||||||
return True
|
|
||||||
|
|
||||||
pkg = get_installed_packages().get(name.lower())
|
|
||||||
if pkg is None:
|
|
||||||
return False # update if not installed
|
|
||||||
|
|
||||||
if match is None:
|
|
||||||
return True # don't update if version is not specified
|
|
||||||
|
|
||||||
if match.group(2) in ['>', '>=']:
|
|
||||||
if StrictVersion(pkg) < StrictVersion(match.group(3)):
|
|
||||||
return False
|
|
||||||
elif StrictVersion(pkg) > StrictVersion(match.group(3)):
|
|
||||||
print(f"[SKIP] Downgrading pip package isn't allowed: {name.lower()} (cur={pkg})")
|
|
||||||
|
|
||||||
return True # prevent downgrade
|
|
||||||
|
|
||||||
|
|
||||||
if os.path.exists(restore_snapshot_path):
|
|
||||||
try:
|
|
||||||
cloned_repos = []
|
|
||||||
|
|
||||||
def msg_capture(stream, prefix):
|
|
||||||
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
|
||||||
for msg in stream:
|
|
||||||
if msg.startswith("CLONE: "):
|
|
||||||
cloned_repos.append(msg[7:])
|
|
||||||
if prefix == '[!]':
|
|
||||||
print(prefix, msg, end="", file=sys.stderr)
|
|
||||||
else:
|
|
||||||
print(prefix, msg, end="")
|
|
||||||
|
|
||||||
elif prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg):
|
|
||||||
if msg.startswith('100%'):
|
|
||||||
print('\r' + msg, end="", file=sys.stderr),
|
|
||||||
else:
|
|
||||||
print('\r'+msg[:-1], end="", file=sys.stderr),
|
|
||||||
else:
|
|
||||||
if prefix == '[!]':
|
|
||||||
print(prefix, msg, end="", file=sys.stderr)
|
|
||||||
else:
|
|
||||||
print(prefix, msg, end="")
|
|
||||||
|
|
||||||
print(f"[ComfyUI-Manager] Restore snapshot.")
|
|
||||||
cmd_str = [sys.executable, git_script_path, '--apply-snapshot', restore_snapshot_path]
|
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
|
||||||
new_env["COMFYUI_PATH"] = comfy_path
|
|
||||||
exit_code = process_wrap(cmd_str, custom_nodes_path, handler=msg_capture, env=new_env)
|
|
||||||
|
|
||||||
repository_name = ''
|
|
||||||
for url in cloned_repos:
|
|
||||||
try:
|
|
||||||
repository_name = url.split("/")[-1].strip()
|
|
||||||
repo_path = os.path.join(custom_nodes_path, repository_name)
|
|
||||||
repo_path = os.path.abspath(repo_path)
|
|
||||||
|
|
||||||
requirements_path = os.path.join(repo_path, 'requirements.txt')
|
|
||||||
install_script_path = os.path.join(repo_path, 'install.py')
|
|
||||||
|
|
||||||
this_exit_code = 0
|
|
||||||
|
|
||||||
if os.path.exists(requirements_path):
|
|
||||||
with open(requirements_path, 'r', encoding="UTF-8", errors="ignore") as file:
|
|
||||||
for line in file:
|
|
||||||
package_name = remap_pip_package(line.strip())
|
|
||||||
if package_name and not is_installed(package_name):
|
|
||||||
if not package_name.startswith('#'):
|
|
||||||
if '--index-url' in package_name:
|
|
||||||
s = package_name.split('--index-url')
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", s[0].strip(), '--index-url', s[1].strip()]
|
|
||||||
else:
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", package_name]
|
|
||||||
|
|
||||||
this_exit_code += process_wrap(install_cmd, repo_path)
|
|
||||||
|
|
||||||
if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in processed_install:
|
|
||||||
processed_install.add(f'{repo_path}/install.py')
|
|
||||||
install_cmd = [sys.executable, install_script_path]
|
|
||||||
print(f">>> {install_cmd} / {repo_path}")
|
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
|
||||||
new_env["COMFYUI_PATH"] = comfy_path
|
|
||||||
this_exit_code += process_wrap(install_cmd, repo_path, env=new_env)
|
|
||||||
|
|
||||||
if this_exit_code != 0:
|
|
||||||
print(f"[ComfyUI-Manager] Restoring '{repository_name}' is failed.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print(f"[ComfyUI-Manager] Restoring '{repository_name}' is failed.")
|
|
||||||
|
|
||||||
if exit_code != 0:
|
|
||||||
print(f"[ComfyUI-Manager] Restore snapshot failed.")
|
|
||||||
else:
|
|
||||||
print(f"[ComfyUI-Manager] Restore snapshot done.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print(f"[ComfyUI-Manager] Restore snapshot failed.")
|
|
||||||
|
|
||||||
os.remove(restore_snapshot_path)
|
|
||||||
|
|
||||||
|
|
||||||
def execute_lazy_install_script(repo_path, executable):
|
|
||||||
global processed_install
|
|
||||||
|
|
||||||
install_script_path = os.path.join(repo_path, "install.py")
|
|
||||||
requirements_path = os.path.join(repo_path, "requirements.txt")
|
|
||||||
|
|
||||||
if os.path.exists(requirements_path):
|
|
||||||
print(f"Install: pip packages for '{repo_path}'")
|
|
||||||
with open(requirements_path, "r") as requirements_file:
|
|
||||||
for line in requirements_file:
|
|
||||||
package_name = remap_pip_package(line.strip())
|
|
||||||
if package_name and not is_installed(package_name):
|
|
||||||
if '--index-url' in package_name:
|
|
||||||
s = package_name.split('--index-url')
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", s[0].strip(), '--index-url', s[1].strip()]
|
|
||||||
else:
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", package_name]
|
|
||||||
|
|
||||||
process_wrap(install_cmd, repo_path)
|
|
||||||
|
|
||||||
if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in processed_install:
|
|
||||||
processed_install.add(f'{repo_path}/install.py')
|
|
||||||
print(f"Install: install script for '{repo_path}'")
|
|
||||||
install_cmd = [executable, "install.py"]
|
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
|
||||||
new_env["COMFYUI_PATH"] = comfy_path
|
|
||||||
process_wrap(install_cmd, repo_path, env=new_env)
|
|
||||||
|
|
||||||
|
|
||||||
# Check if script_list_path exists
|
|
||||||
if os.path.exists(script_list_path):
|
|
||||||
print("\n#######################################################################")
|
|
||||||
print("[ComfyUI-Manager] Starting dependency installation/(de)activation for the extension\n")
|
|
||||||
|
|
||||||
executed = set()
|
|
||||||
# Read each line from the file and convert it to a list using eval
|
|
||||||
with open(script_list_path, 'r', encoding="UTF-8", errors="ignore") as file:
|
|
||||||
for line in file:
|
|
||||||
if line in executed:
|
|
||||||
continue
|
|
||||||
|
|
||||||
executed.add(line)
|
|
||||||
|
|
||||||
try:
|
|
||||||
script = ast.literal_eval(line)
|
|
||||||
|
|
||||||
if script[1].startswith('#') and script[1] != '#FORCE':
|
|
||||||
if script[1] == "#LAZY-INSTALL-SCRIPT":
|
|
||||||
execute_lazy_install_script(script[0], script[2])
|
|
||||||
|
|
||||||
elif os.path.exists(script[0]):
|
|
||||||
if script[1] == "#FORCE":
|
|
||||||
del script[1]
|
|
||||||
else:
|
|
||||||
if 'pip' in script[1:] and 'install' in script[1:] and is_installed(script[-1]):
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"\n## ComfyUI-Manager: EXECUTE => {script[1:]}")
|
|
||||||
print(f"\n## Execute install/(de)activation script for '{script[0]}'")
|
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
|
||||||
new_env["COMFYUI_PATH"] = comfy_path
|
|
||||||
exit_code = process_wrap(script[1:], script[0], env=new_env)
|
|
||||||
|
|
||||||
if exit_code != 0:
|
|
||||||
print(f"install/(de)activation script failed: {script[0]}")
|
|
||||||
else:
|
|
||||||
print(f"\n## ComfyUI-Manager: CANCELED => {script[1:]}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[ERROR] Failed to execute install/(de)activation script: {line} / {e}")
|
|
||||||
|
|
||||||
# Remove the script_list_path file
|
|
||||||
if os.path.exists(script_list_path):
|
|
||||||
os.remove(script_list_path)
|
|
||||||
|
|
||||||
print("\n[ComfyUI-Manager] Startup script completed.")
|
|
||||||
print("#######################################################################\n")
|
|
||||||
|
|
||||||
del processed_install
|
|
||||||
del pip_map
|
|
||||||
|
|
||||||
|
|
||||||
def check_windows_event_loop_policy():
|
|
||||||
try:
|
|
||||||
import configparser
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(config_path)
|
|
||||||
default_conf = config['default']
|
|
||||||
|
|
||||||
if 'windows_selector_event_loop_policy' in default_conf and default_conf['windows_selector_event_loop_policy'].lower() == 'true':
|
|
||||||
try:
|
|
||||||
import asyncio
|
|
||||||
import asyncio.windows_events
|
|
||||||
asyncio.set_event_loop_policy(asyncio.windows_events.WindowsSelectorEventLoopPolicy())
|
|
||||||
print(f"[ComfyUI-Manager] Windows event loop policy mode enabled")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[ComfyUI-Manager] WARN: Windows initialization fail: {e}")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
check_windows_event_loop_policy()
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-manager"
|
name = "comfyui-manager"
|
||||||
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
||||||
version = "2.51.9"
|
version = "3.0.1"
|
||||||
license = { file = "LICENSE.txt" }
|
license = { file = "LICENSE.txt" }
|
||||||
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]
|
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def get_enabled_subdirectories_with_files(base_directory):
|
|
||||||
subdirs_with_files = []
|
|
||||||
for subdir in os.listdir(base_directory):
|
|
||||||
try:
|
|
||||||
full_path = os.path.join(base_directory, subdir)
|
|
||||||
if os.path.isdir(full_path) and not subdir.endswith(".disabled") and not subdir.startswith('.') and subdir != '__pycache__':
|
|
||||||
print(f"## Install dependencies for '{subdir}'")
|
|
||||||
requirements_file = os.path.join(full_path, "requirements.txt")
|
|
||||||
install_script = os.path.join(full_path, "install.py")
|
|
||||||
|
|
||||||
if os.path.exists(requirements_file) or os.path.exists(install_script):
|
|
||||||
subdirs_with_files.append((full_path, requirements_file, install_script))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"EXCEPTION During Dependencies INSTALL on '{subdir}':\n{e}")
|
|
||||||
|
|
||||||
return subdirs_with_files
|
|
||||||
|
|
||||||
|
|
||||||
def install_requirements(requirements_file_path):
|
|
||||||
if os.path.exists(requirements_file_path):
|
|
||||||
subprocess.run(["pip", "install", "-r", requirements_file_path])
|
|
||||||
|
|
||||||
|
|
||||||
def run_install_script(install_script_path):
|
|
||||||
if os.path.exists(install_script_path):
|
|
||||||
subprocess.run(["python", install_script_path])
|
|
||||||
|
|
||||||
|
|
||||||
custom_nodes_directory = "custom_nodes"
|
|
||||||
subdirs_with_files = get_enabled_subdirectories_with_files(custom_nodes_directory)
|
|
||||||
|
|
||||||
|
|
||||||
for subdir, requirements_file, install_script in subdirs_with_files:
|
|
||||||
install_requirements(requirements_file)
|
|
||||||
run_install_script(install_script)
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
git clone https://github.com/comfyanonymous/ComfyUI
|
|
||||||
cd ComfyUI/custom_nodes
|
|
||||||
git clone https://github.com/ltdrdata/ComfyUI-Manager
|
|
||||||
cd ..
|
|
||||||
python -m venv venv
|
|
||||||
source venv/bin/activate
|
|
||||||
python -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu121
|
|
||||||
python -m pip install -r requirements.txt
|
|
||||||
python -m pip install -r custom_nodes/ComfyUI-Manager/requirements.txt
|
|
||||||
cd ..
|
|
||||||
echo "#!/bin/bash" > run_gpu.sh
|
|
||||||
echo "cd ComfyUI" >> run_gpu.sh
|
|
||||||
echo "source venv/bin/activate" >> run_gpu.sh
|
|
||||||
echo "python main.py --preview-method auto" >> run_gpu.sh
|
|
||||||
chmod +x run_gpu.sh
|
|
||||||
|
|
||||||
echo "#!/bin/bash" > run_cpu.sh
|
|
||||||
echo "cd ComfyUI" >> run_cpu.sh
|
|
||||||
echo "source venv/bin/activate" >> run_cpu.sh
|
|
||||||
echo "python main.py --preview-method auto --cpu" >> run_cpu.sh
|
|
||||||
chmod +x run_cpu.sh
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
git clone https://github.com/comfyanonymous/ComfyUI
|
|
||||||
cd ComfyUI/custom_nodes
|
|
||||||
git clone https://github.com/ltdrdata/ComfyUI-Manager
|
|
||||||
cd ..
|
|
||||||
python -m venv venv
|
|
||||||
call venv/Scripts/activate
|
|
||||||
python -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu121
|
|
||||||
python -m pip install -r requirements.txt
|
|
||||||
python -m pip install -r custom_nodes/ComfyUI-Manager/requirements.txt
|
|
||||||
cd ..
|
|
||||||
echo "cd ComfyUI" >> run_gpu.bat
|
|
||||||
echo "call venv/Scripts/activate" >> run_gpu.bat
|
|
||||||
echo "python main.py" >> run_gpu.bat
|
|
||||||
|
|
||||||
echo "cd ComfyUI" >> run_cpu.bat
|
|
||||||
echo "call venv/Scripts/activate" >> run_cpu.bat
|
|
||||||
echo "python main.py --cpu" >> run_cpu.bat
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
.\python_embeded\python.exe -s -m pip install gitpython
|
|
||||||
.\python_embeded\python.exe -c "import git; git.Repo.clone_from('https://github.com/ltdrdata/ComfyUI-Manager', './ComfyUI/custom_nodes/ComfyUI-Manager')"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import git
|
|
||||||
|
|
||||||
commit_hash = "a361cc1"
|
|
||||||
|
|
||||||
repo = git.Repo('.')
|
|
||||||
|
|
||||||
if repo.is_dirty():
|
|
||||||
repo.git.stash()
|
|
||||||
|
|
||||||
repo.git.update_ref("refs/remotes/origin/main", commit_hash)
|
|
||||||
repo.remotes.origin.fetch()
|
|
||||||
repo.git.pull("origin", "main")
|
|
||||||
Reference in New Issue
Block a user