Compare commits

..

116 Commits

Author SHA1 Message Date
bymyself
1a83bab57b [api] Add OpenAPI specification file 2025-05-20 15:55:19 -07:00
Dr.Lt.Data
416122d61d update DB 2025-05-21 00:03:10 +09:00
Dr.Lt.Data
d3c625e791 update DB 2025-05-20 23:43:34 +09:00
2frames
ca2c41783c Add AQnodes (#1849)
* add AQnodes

* add AQnodes - fix repo url

---------

Co-authored-by: pk <poczta@aquasite.pl>
2025-05-20 23:42:57 +09:00
Dr.Lt.Data
e2a6446585 update DB 2025-05-20 23:42:44 +09:00
ICAI Icelandic Center for Artificial Intelligence
839790b5ab Update custom-node-list.json (#1848)
added entry for Sample Scheduler Metrics Tester custom node
2025-05-20 23:41:32 +09:00
jqy-yo
58b9946936 Add Comfyui-BBoxLowerMask2 to custom-node-list (#1842) 2025-05-20 23:41:00 +09:00
Dr.Lt.Data
a19ba22eaf update DB 2025-05-20 23:40:40 +09:00
Yuan-Man
117715aa22 Add ComfyUI-MoviiGen node (#1846) 2025-05-20 23:35:37 +09:00
lum3on
891a5a85ee add ModelQuantizer node to custom node list (#1806)
* add-ModelQuantizer to custom node list

* Update custom-node-list.json

---------

Co-authored-by: yogotatara3 <milan.kastenmueller@thjnk.de>
Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2025-05-20 23:32:43 +09:00
Dr.Lt.Data
166debfabb modified: In Python 3.13, the functionality to forcibly downgrade the numpy version below 3.13 is disabled.
- Starting from Python 3.13, prebuilt wheels for `numpy` 1.26.4 are no longer provided.

https://github.com/comfyanonymous/ComfyUI/discussions/8187
2025-05-19 05:13:40 +09:00
Dr.Lt.Data
7258a09fe5 update DB 2025-05-19 05:03:54 +09:00
Dr.Lt.Data
058a436187 update DB 2025-05-17 17:39:31 +09:00
Yuan-Man
1950802c55 Update ComfyUI-Step1X-3D node (#1840) 2025-05-17 17:11:51 +09:00
Dr.Lt.Data
eb52a03372 update DB 2025-05-16 03:52:03 +09:00
Dr.Lt.Data
f8aa428be3 update DB 2025-05-15 22:09:48 +09:00
Dr.Lt.Data
ec0893f136 update DB 2025-05-15 21:48:56 +09:00
TrophiHunter
92b99ea963 Update custom-node-list.json (#1832)
add my nodes to manager
2025-05-15 21:47:37 +09:00
Dr.Lt.Data
02cd52bb65 update DB 2025-05-15 21:45:19 +09:00
Dontdrunk
af1ec2c87b Update custom-node-list.json (#1818)
* Submit Registration

* Update custom-node-list.json

* Update custom-node-list.json
2025-05-15 21:43:29 +09:00
Dr.Lt.Data
41006c3a33 update DB 2025-05-15 08:09:03 +09:00
Gilad Schreiber
116a6d500d model-list: add new ltxv 13b distilled models. (#1835)
Co-authored-by: gschreiber <gschreiber@infra-image-generator.c.ltx-research-vms.internal>
2025-05-15 08:03:12 +09:00
Dr.Lt.Data
87d0ac807f update DB 2025-05-15 07:24:34 +09:00
Dr.Lt.Data
fc943172eb update DB 2025-05-14 06:07:35 +09:00
Gilad Schreiber
9daa5a2fbd fix: update ltxv upscale models metadata. (#1830)
Co-authored-by: gschreiber <gschreiber@infra-image-generator.c.ltx-research-vms.internal>
2025-05-14 06:07:22 +09:00
Dr.Lt.Data
b7b2746a61 update DB 2025-05-13 03:36:18 +09:00
Dr.Lt.Data
d66a4fbfc8 update DB 2025-05-13 03:23:47 +09:00
Dr.Lt.Data
683a172ad8 modified: Added a feature to prevent numpy from being forcibly downgraded to below 2 via pip_overrides.json.
https://github.com/Comfy-Org/ComfyUI-Manager/issues/1665#issuecomment-2862099191
2025-05-13 03:04:27 +09:00
Dr.Lt.Data
6e12358f5a update DB 2025-05-13 02:56:36 +09:00
Dr.Lt.Data
8bcf16dc90 fixed: A type error occurred during the creation of the pip fixer object when an error occurred while retrieving the list of installed packages.
https://github.com/Comfy-Org/ComfyUI-Manager/issues/1804
2025-05-13 02:46:34 +09:00
Dr.Lt.Data
65c0a2a1f5 update DB 2025-05-13 02:10:21 +09:00
Alastor 666 1933
115236eb9c adding caching_to_not_waste custom node (#1786) 2025-05-13 02:06:23 +09:00
Dr.Lt.Data
08de942abe update DB 2025-05-13 02:05:51 +09:00
Seb Hirsch
e9dff83290 Update custom-node-list.json (#1802)
added seb nodes
2025-05-13 02:02:55 +09:00
Yuan-Man
3bc6c7584d Add ComfyUI-Muyan-TTS node (#1805) 2025-05-13 02:00:54 +09:00
Dr.Lt.Data
22a2bf1584 Apply https://github.com/Comfy-Org/ComfyUI-Manager/pull/1811 to prestartup_script as well. 2025-05-13 01:59:42 +09:00
Tomasz Dowgielewicz
79ece5f72c fix: handle pip package names with inline comments during installation (#1811)
Co-authored-by: Tomasz Dowgielewicz <todowgielewicz@artflow.me>
2025-05-13 01:53:44 +09:00
VitoChenLY
5da6fe1373 extract_url_and_commit_id (#1813)
Co-authored-by: chenyijian <chenyijian@infini-ai.com>
2025-05-13 01:52:02 +09:00
moldwebs
48c10d0b95 Show models used in current workflow (#1819)
Simple javascript modify that filter models used in current workflow
2025-05-13 01:48:29 +09:00
Dr.Lt.Data
9bb56b1457 update DB 2025-05-13 01:46:26 +09:00
1hew
83420fd828 Add ComfyUI-1hewNodes to custom node list (#1826)
Co-authored-by: yige1127 <wangyihe370875982@gmail.com>
2025-05-13 01:45:34 +09:00
Dr.Lt.Data
52f4b9506f update DB 2025-05-13 01:44:07 +09:00
fpgaminer
b501e9b20b Add fpgaminer/joycaption_comfyui to custom-node-list.json (#1827) 2025-05-13 01:43:28 +09:00
Dr.Lt.Data
1f7ae5319a update DB 2025-05-13 01:42:35 +09:00
Goshe-nite
68c201239d Update custom-node-list.json (#1825) 2025-05-13 01:42:13 +09:00
Dr.Lt.Data
6e4e43f612 update DB 2025-05-13 01:41:12 +09:00
AIWarper
81c3708f39 Add NormalCrafterWrapper custom node by AIWarper (#1816) 2025-05-13 01:40:43 +09:00
Dr.Lt.Data
f4d2bbde34 update DB 2025-05-13 01:40:25 +09:00
gasparuff
d14b42a42c Update custom-node-list.json (#1810)
added customselector node to custom-node-list.json
2025-05-13 01:34:46 +09:00
Dr.Lt.Data
0e9c32344c fix: syntax error 2025-05-12 18:33:24 +09:00
Liangbin Lian
30c4ea06af fix model DB for Hyper-SD LoRA (4steps) - SDXL (#1815) 2025-05-12 18:20:42 +09:00
Fadel Mochammad
8211264993 Add inline comment to __init__.py (#1823) 2025-05-12 18:15:27 +09:00
ClownsharkBatwing
67cf5b49e1 Update custom-node-list.json (#1821) 2025-05-12 18:15:12 +09:00
Dr.Lt.Data
8e7ba18e05 update DB 2025-05-09 08:04:39 +09:00
Dr.Lt.Data
8359e1063e update DB 2025-05-09 07:23:33 +09:00
VitoChenLY
ca078e54b9 Add 'exit-on-fail' parameter to control failure behavior (#1807)
Co-authored-by: chenyijian <chenyijian@infini-ai.com>
2025-05-09 07:08:41 +09:00
Dr.Lt.Data
f7e930c5a2 update DB 2025-05-08 02:03:46 +09:00
Dr.Lt.Data
479d95e1c8 update DB 2025-05-08 01:43:01 +09:00
Demis Bellot
2b0ff08eef Add ComfyUI Asset Downloader (#1799) 2025-05-08 01:34:02 +09:00
Dr.Lt.Data
67a487db15 update DB 2025-05-08 01:30:54 +09:00
Dr.Lt.Data
2488cb3458 update DB 2025-05-08 00:11:28 +09:00
Dr.Lt.Data
157e6336fa update DB 2025-05-08 00:09:38 +09:00
IrsalKhan
d808a1f406 Add ComfyUI DAM Object Extractor node (#1796)
* Update custom-node-list.json

* Update custom-node-list.json

* Update custom-node-list.json

---------

Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2025-05-08 00:08:58 +09:00
Dr.Lt.Data
2bb4d8cd63 update DB 2025-05-08 00:08:42 +09:00
CY-CHENYUE
a8164e1631 Update custom-node-list.json (#1791)
* Update custom-node-list.json

* Update custom-node-list.json

---------

Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2025-05-08 00:07:50 +09:00
Dr.Lt.Data
a31d286945 update DB 2025-05-08 00:05:49 +09:00
wakattac
12eeef4cf0 Update custom-node-list.json (#1793) 2025-05-08 00:04:36 +09:00
Yuan-Man
ce8e6dc36e Add ComfyUI-AudioX node (#1798) 2025-05-08 00:03:58 +09:00
Sssnap
7a32e544a7 Update custom-node-list.json (#1792) 2025-05-07 23:54:45 +09:00
Dr.Lt.Data
e16e9d7a0e update DB 2025-05-03 23:40:58 +09:00
unicough
821f908dbc Update custom-node-list.json (#1784) 2025-05-03 23:12:05 +09:00
Dr.Lt.Data
e007e6f897 update DB 2025-05-01 02:08:58 +09:00
Yuan-Man
94f496fd65 Add ComfyUI-Step1X-Edit node (#1780) 2025-05-01 01:15:03 +09:00
Dr.Lt.Data
d2ce35d2e6 update DB 2025-05-01 01:13:31 +09:00
somesomebody
2eeebb32dc Add comfyui-lorainfo-sidebar to custom node list (#1778) 2025-05-01 01:12:44 +09:00
Sander
f6d636d82f Add fixed MagicQuill node (#1768) 2025-05-01 01:08:11 +09:00
Dr.Lt.Data
0cd397623e update DB 2025-04-29 00:21:59 +09:00
Dr.Lt.Data
5978b6c9ee updated: PIPFixer - support for pytorch 2.7.0 2025-04-28 23:49:42 +09:00
Dr.Lt.Data
9e132811bc update DB 2025-04-28 00:43:52 +09:00
Dr.Lt.Data
3a3b5c1f92 update DB 2025-04-27 23:16:48 +09:00
hua(Kungfu)
26be01ff82 Update custom-node-list.json (#1774) 2025-04-27 22:52:56 +09:00
Dr.Lt.Data
8f6dd92374 update DB 2025-04-26 18:24:56 +09:00
Dr.Lt.Data
d50b71a887 update DB 2025-04-26 14:51:07 +09:00
Dr.Lt.Data
3bc9cbc767 update DB 2025-04-26 13:16:26 +09:00
Yuan-Man
b6f6b4fd8a Add ComfyUI-LiveCC node (#1770) 2025-04-26 13:12:14 +09:00
Christian Byrne
a66bada8a3 Update workflow-metadata.js 2025-04-23 17:24:07 -07:00
Dr.Lt.Data
a804f7de19 update DB 2025-04-22 02:14:50 +09:00
Dr.Lt.Data
72a61a9966 modified: pipfixer/blacklisting - add torchaudio 2025-04-22 01:17:22 +09:00
Dr.Lt.Data
b08bb658ea update DB 2025-04-22 01:13:57 +09:00
Dr.Lt.Data
7b28bf608b modified: release pinning ultralytics version 2025-04-22 00:43:20 +09:00
Dr.Lt.Data
b57747fdf1 update DB 2025-04-20 18:49:43 +09:00
Dr.Lt.Data
0735271b10 update DB 2025-04-20 17:13:47 +09:00
Dr.Lt.Data
770cd0f9f5 update DB 2025-04-19 10:31:07 +09:00
Dr.Lt.Data
32b6266dd9 update DB 2025-04-19 09:39:43 +09:00
NumZ
2a8412a2bf Update custom-node-list.json for Comfyui-Orpheus (#1754)
add custom nodes from https://github.com/numz/Comfyui-Orpheus
2025-04-19 09:35:28 +09:00
Dr.Lt.Data
0c4d289002 update DB 2025-04-19 09:34:52 +09:00
Nisaruj Rattanaaram
cee01fec25 Add comfyui-daam to custom node list (#1753)
* Update custom-node-list.json

* Update description
2025-04-19 09:34:17 +09:00
Dr.Lt.Data
f00686f3f2 update DB 2025-04-19 09:34:07 +09:00
FunnyFinger
bd33f7726e Add Dynamic Sliders Stack to custom node list (#1750)
* Update custom-node-list.json

* Update custom-node-list.json

Added my custom node to the list
2025-04-19 09:33:08 +09:00
Dr.Lt.Data
22ab526b0c update DB 2025-04-19 09:32:30 +09:00
Christian Byrne
af269d198d trim version string embedded in workflow (#1758) 2025-04-19 09:30:41 +09:00
Yuan-Man
995ef6356e Add ComfyUI-Kimi-VL node (#1756) 2025-04-19 09:30:02 +09:00
杨必赞
aa3bf77c28 Update custom-node-list.json (#1752) 2025-04-19 09:29:15 +09:00
Danteday
15667c1259 Update custom-node-list.json (#1751) 2025-04-19 09:28:53 +09:00
zzw5516
c7b6b565da feat: Add ComfyUI-zw-tools custom node to list (#1749) 2025-04-19 09:27:55 +09:00
Dr.Lt.Data
3214ab52c6 update DB 2025-04-15 23:40:14 +09:00
Legende
e3062ff613 Add custom node xLegende/ComfyUI-Prompt-Formatter (#1741)
Custom node for formating prompts
2025-04-15 23:18:13 +09:00
Yoland Yan
036b63efe7 Change order of manager to be default install lateste (#1747) 2025-04-15 18:46:24 +09:00
Dr.Lt.Data
8d3e1d60d0 update DB 2025-04-15 01:24:42 +09:00
Dr.Lt.Data
59876452f4 update DB 2025-04-15 00:37:10 +09:00
BIGMON
04972ad87f feat: Register ComfyUI-ResolutionPresets to custom nodes list (#1738)
* Add: register ComfyUI-ResolutionPresets

* Update custom-node-list.json

---------

Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2025-04-15 00:29:27 +09:00
Dr.Lt.Data
c7e69f4e26 update DB 2025-04-15 00:28:53 +09:00
leolee
7a59b6d0d9 Update custom-node-list.json (#1745)
* Update custom-node-list.json

Add Comfy-Topaz-Photo

* Update custom-node-list.json

* Update custom-node-list.json

---------

Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2025-04-15 00:28:03 +09:00
Yuan-Man
d227ad97a4 Add ComfyUI-HiDream-I1 node (#1744) 2025-04-15 00:25:45 +09:00
Dr.Lt.Data
b93a474dae update DB 2025-04-15 00:23:42 +09:00
Silver
a5fe075bf3 Add custom node silveroxides/ComfyUI-ModelUtils (#1652)
Custom nodes project for model management.
2025-04-15 00:22:38 +09:00
61 changed files with 21001 additions and 8501 deletions

View File

@@ -1 +0,0 @@
PYPI_TOKEN=your-pypi-token

View File

@@ -1,58 +0,0 @@
name: Publish to PyPI
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "pyproject.toml"
jobs:
build-and-publish:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
python -m pip install build twine
- name: Get current version
id: current_version
run: |
CURRENT_VERSION=$(grep -oP 'version = "\K[^"]+' pyproject.toml)
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "Current version: $CURRENT_VERSION"
- name: Build package
run: python -m build
- name: Create GitHub Release
id: create_release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: dist/*
tag_name: v${{ steps.current_version.outputs.version }}
draft: false
prerelease: false
generate_release_notes: true
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_TOKEN }}
skip-existing: true
verbose: true

View File

@@ -14,7 +14,7 @@ jobs:
publish-node: publish-node:
name: Publish Custom Node to registry name: Publish Custom Node to registry
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }} if: ${{ github.repository_owner == 'ltdrdata' }}
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v4 uses: actions/checkout@v4

5
.gitignore vendored
View File

@@ -17,7 +17,4 @@ github-stats-cache.json
pip_overrides.json pip_overrides.json
*.json *.json
check2.sh check2.sh
/venv/ /venv/
build
*.egg-info
.env

View File

@@ -1,47 +0,0 @@
## Testing Changes
1. Activate the ComfyUI environment.
2. Build package locally after making changes.
```bash
# from inside the ComfyUI-Manager directory, with the ComfyUI environment activated
python -m build
```
3. Install the package locally in the ComfyUI environment.
```bash
# Uninstall existing package
pip uninstall comfyui-manager
# Install the locale package
pip install dist/comfyui-manager-*.whl
```
4. Start ComfyUI.
```bash
# after navigating to the ComfyUI directory
python main.py
```
## Manually Publish Test Version to PyPi
1. Set the `PYPI_TOKEN` environment variable in env file.
2. If manually publishing, you likely want to use a release candidate version, so set the version in [pyproject.toml](pyproject.toml) to something like `0.0.1rc1`.
3. Build the package.
```bash
python -m build
```
4. Upload the package to PyPi.
```bash
python -m twine upload dist/* --username __token__ --password $PYPI_TOKEN
```
5. View at https://pypi.org/project/comfyui-manager/

View File

@@ -1,7 +0,0 @@
include comfyui_manager/js/*
include comfyui_manager/*.json
include comfyui_manager/glob/*
include LICENSE.txt
include README.md
include requirements.txt
include pyproject.toml

View File

@@ -5,7 +5,6 @@
![menu](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/refs/heads/Main/ComfyUI-Manager/images/dialog.jpg) ![menu](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/refs/heads/Main/ComfyUI-Manager/images/dialog.jpg)
## NOTICE ## NOTICE
* V4.0: Modify the structure to be installable via pip instead of using git clone.
* V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`. * V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`.
* V3.10: `double-click feature` is removed * V3.10: `double-click feature` is removed
* This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper * This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper
@@ -14,26 +13,78 @@
## Installation ## Installation
* When installing the latest ComfyUI, it will be automatically installed as a dependency, so manual installation is no longer necessary. ### Installation[method1] (General installation method: ComfyUI-Manager only)
* Manual installation of the nightly version: To install ComfyUI-Manager in addition to an existing installation of ComfyUI, you can follow the following steps:
* Clone to a temporary directory (**Note:** Do **not** clone into `ComfyUI/custom_nodes`.)
```
git clone https://github.com/Comfy-Org/ComfyUI-Manager
```
* Install via pip
```
cd ComfyUI-Manager
pip install .
```
1. goto `ComfyUI/custom_nodes` dir in terminal(cmd)
2. `git clone https://github.com/ltdrdata/ComfyUI-Manager comfyui-manager`
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
- Don't click. Right click the link and use save as...
3. double click `install-manager-for-portable-version.bat` batch file
![portable-install](https://raw.githubusercontent.com/ltdrdata/ComfyUI-extension-tutorials/Main/ComfyUI-Manager/images/portable-install.jpg)
### 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
```
* See also: https://github.com/Comfy-Org/comfy-cli * See also: https://github.com/Comfy-Org/comfy-cli
## Front-end ### Installation[method4] (Installation for linux+venv: ComfyUI + ComfyUI-Manager)
* The built-in front-end of ComfyUI-Manager is the legacy front-end. The front-end for ComfyUI-Manager is now provided via [ComfyUI Frontend](https://github.com/Comfy-Org/ComfyUI_frontend). To install ComfyUI with ComfyUI-Manager on Linux using a venv environment, you can follow these steps:
* To enable the legacy front-end, set the environment variable `ENABLE_LEGACY_COMFYUI_MANAGER_FRONT` to `true` before running. * **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
- Don't click. Right click the link and use save as...
- 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
* **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.
* **DON'T**: Decompress directly into the `ComfyUI/custom_nodes` location, resulting in the Manager contents like `__init__.py` being placed directly in that directory.
* You have to remove all ComfyUI-Manager files from `ComfyUI/custom_nodes`
* **DON'T**: In a form where decompression occurs in a path such as `ComfyUI/custom_nodes/ComfyUI-Manager/ComfyUI-Manager`.
* **DON'T**: In a form where decompression occurs in a path such as `ComfyUI/custom_nodes/ComfyUI-Manager-main`.
* In such cases, `ComfyUI-Manager` may operate, but it won't be recognized within `ComfyUI-Manager`, and updates cannot be performed. It also poses the risk of duplicate installations. Remove it and install properly via `git clone` method.
You can execute ComfyUI by running either `./run_gpu.sh` or `./run_cpu.sh` depending on your system configuration.
## 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).
* Support for installing ComfyUI
* Support for basic installation of ComfyUI-Manager
* Support for automatically installing dependencies of custom nodes upon restarting Colab notebooks.
## How To Use ## How To Use

25
__init__.py Normal file
View File

@@ -0,0 +1,25 @@
"""
This file is the entry point for the ComfyUI-Manager package, handling CLI-only mode and initial setup.
"""
import os
import sys
cli_mode_flag = os.path.join(os.path.dirname(__file__), '.enable-cli-only-mode')
if not os.path.exists(cli_mode_flag):
sys.path.append(os.path.join(os.path.dirname(__file__), "glob"))
import manager_server # noqa: F401
import share_3rdparty # noqa: F401
import cm_global
if not cm_global.disable_front and not 'DISABLE_COMFYUI_MANAGER_FRONT' in os.environ:
WEB_DIRECTORY = "js"
else:
print("\n[ComfyUI-Manager] !! cli-only-mode is enabled !!\n")
NODE_CLASS_MAPPINGS = {}
__all__ = ['NODE_CLASS_MAPPINGS']

View File

@@ -15,36 +15,41 @@ import git
import importlib import importlib
sys.path.append(os.path.dirname(__file__))
sys.path.append(os.path.join(os.path.dirname(__file__), "glob"))
import manager_util import manager_util
# read env vars # read env vars
# COMFYUI_FOLDERS_BASE_PATH is not required in cm-cli.py # COMFYUI_FOLDERS_BASE_PATH is not required in cm-cli.py
# `comfy_path` should be resolved before importing manager_core # `comfy_path` should be resolved before importing manager_core
comfy_path = os.environ.get('COMFYUI_PATH') comfy_path = os.environ.get('COMFYUI_PATH')
if comfy_path is None: if comfy_path is None:
print("[bold red]cm-cli: environment variable 'COMFYUI_PATH' is not specified.[/bold red]") try:
exit(-1) import folder_paths
comfy_path = os.path.join(os.path.dirname(folder_paths.__file__))
except:
print("\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
comfy_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..', '..'))
# This should be placed here
sys.path.append(comfy_path) sys.path.append(comfy_path)
if not os.path.exists(os.path.join(comfy_path, 'folder_paths.py')):
print("[bold red]cm-cli: '{comfy_path}' is not a valid 'COMFYUI_PATH' location.[/bold red]")
exit(-1)
import utils.extra_config import utils.extra_config
from .glob import cm_global import cm_global
from .glob import manager_core as core import manager_core as core
from .glob.manager_core import unified_manager from manager_core import unified_manager
from .glob import cnr_utils import cnr_utils
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__)) comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
cm_global.pip_blacklist = {'torch', 'torchsde', 'torchvision'} cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia'] cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
cm_global.pip_overrides = {'numpy': 'numpy<2'}
if sys.version_info < (3, 13):
cm_global.pip_overrides = {'numpy': 'numpy<2'}
else:
cm_global.pip_overrides = {}
if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json")): if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json")):
with open(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json"), 'r', encoding="UTF-8", errors="ignore") as json_file: with open(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json"), 'r', encoding="UTF-8", errors="ignore") as json_file:
@@ -146,7 +151,9 @@ class Ctx:
if os.path.exists(core.manager_pip_overrides_path): if os.path.exists(core.manager_pip_overrides_path):
with open(core.manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file: with open(core.manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
cm_global.pip_overrides = json.load(json_file) cm_global.pip_overrides = json.load(json_file)
cm_global.pip_overrides = {'numpy': 'numpy<2'}
if sys.version_info < (3, 13):
cm_global.pip_overrides = {'numpy': 'numpy<2'}
if os.path.exists(core.manager_pip_blacklist_path): if os.path.exists(core.manager_pip_blacklist_path):
with open(core.manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f: with open(core.manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:
@@ -183,13 +190,18 @@ class Ctx:
cmd_ctx = Ctx() cmd_ctx = Ctx()
def install_node(node_spec_str, is_all=False, cnt_msg=''): def install_node(node_spec_str, is_all=False, cnt_msg='', **kwargs):
exit_on_fail = kwargs.get('exit_on_fail', False)
print(f"install_node exit on fail:{exit_on_fail}...")
if core.is_valid_url(node_spec_str): if core.is_valid_url(node_spec_str):
# install via urls # install via urls
res = asyncio.run(core.gitclone_install(node_spec_str, no_deps=cmd_ctx.no_deps)) res = asyncio.run(core.gitclone_install(node_spec_str, no_deps=cmd_ctx.no_deps))
if not res.result: if not res.result:
print(res.msg) print(res.msg)
print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]") print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]")
if exit_on_fail:
sys.exit(1)
else: else:
print(f"{cnt_msg} [INSTALLED] {node_spec_str:50}") print(f"{cnt_msg} [INSTALLED] {node_spec_str:50}")
else: else:
@@ -224,6 +236,8 @@ def install_node(node_spec_str, is_all=False, cnt_msg=''):
print("") print("")
else: else:
print(f"[bold red]ERROR: An error occurred while installing '{node_name}'.\n{res.msg}[/bold red]") print(f"[bold red]ERROR: An error occurred while installing '{node_name}'.\n{res.msg}[/bold red]")
if exit_on_fail:
sys.exit(1)
def reinstall_node(node_spec_str, is_all=False, cnt_msg=''): def reinstall_node(node_spec_str, is_all=False, cnt_msg=''):
@@ -585,7 +599,7 @@ def get_all_installed_node_specs():
return res return res
def for_each_nodes(nodes, act, allow_all=True): def for_each_nodes(nodes, act, allow_all=True, **kwargs):
is_all = False is_all = False
if allow_all and 'all' in nodes: if allow_all and 'all' in nodes:
is_all = True is_all = True
@@ -597,7 +611,7 @@ def for_each_nodes(nodes, act, allow_all=True):
i = 1 i = 1
for x in nodes: for x in nodes:
try: try:
act(x, is_all=is_all, cnt_msg=f'{i}/{total}') act(x, is_all=is_all, cnt_msg=f'{i}/{total}', **kwargs)
except Exception as e: except Exception as e:
print(f"ERROR: {e}") print(f"ERROR: {e}")
traceback.print_exc() traceback.print_exc()
@@ -641,13 +655,17 @@ def install(
None, None,
help="user directory" help="user directory"
), ),
exit_on_fail: bool = typer.Option(
False,
help="Exit on failure"
)
): ):
cmd_ctx.set_user_directory(user_directory) cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode) cmd_ctx.set_channel_mode(channel, mode)
cmd_ctx.set_no_deps(no_deps) cmd_ctx.set_no_deps(no_deps)
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path) pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path)
for_each_nodes(nodes, act=install_node) for_each_nodes(nodes, act=install_node, exit_on_fail=exit_on_fail)
pip_fixer.fix_broken() pip_fixer.fix_broken()

View File

@@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
python ./comfyui_manager/cm-cli.py $* python cm-cli.py $*

View File

@@ -1,40 +0,0 @@
import os
import logging
from comfy.cli_args import args
ENABLE_LEGACY_COMFYUI_MANAGER_FRONT_DEFAULT = True # Enable legacy ComfyUI Manager frontend while new UI is in beta phase
def prestartup():
from . import prestartup_script # noqa: F401
logging.info('[PRE] ComfyUI-Manager')
def start():
logging.info('[START] ComfyUI-Manager')
from .glob import manager_server # noqa: F401
from .glob import share_3rdparty # noqa: F401
from .glob import cm_global # noqa: F401
should_show_legacy_manager_front = os.environ.get('ENABLE_LEGACY_COMFYUI_MANAGER_FRONT', 'false') == 'true' or ENABLE_LEGACY_COMFYUI_MANAGER_FRONT_DEFAULT
if not args.disable_manager and should_show_legacy_manager_front:
try:
import nodes
nodes.EXTENSION_WEB_DIRS['comfyui-manager-legacy'] = os.path.join(os.path.dirname(__file__), 'js')
except Exception as e:
print("Error enabling legacy ComfyUI Manager frontend:", e)
def should_be_disabled(fullpath:str) -> bool:
"""
1. Disables the legacy ComfyUI-Manager.
2. The blocklist can be expanded later based on policies.
"""
if not args.disable_manager:
# In cases where installation is done via a zip archive, the directory name may not be comfyui-manager, and it may not contain a git repository.
# It is assumed that any installed legacy ComfyUI-Manager will have at least 'comfyui-manager' in its directory name.
dir_name = os.path.basename(fullpath).lower()
if 'comfyui-manager' in dir_name:
return True
return False

View File

@@ -1,17 +0,0 @@
import enum
class NetworkMode(enum.Enum):
PUBLIC = "public"
PRIVATE = "private"
OFFLINE = "offline"
class SecurityLevel(enum.Enum):
STRONG = "strong"
NORMAL = "normal"
NORMAL_MINUS = "normal-minus"
WEAK = "weak"
class DBMode(enum.Enum):
LOCAL = "local"
CACHE = "cache"
REMOTE = "remote"

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -15,12 +15,9 @@ comfy_path = os.environ.get('COMFYUI_PATH')
git_exe_path = os.environ.get('GIT_EXE_PATH') git_exe_path = os.environ.get('GIT_EXE_PATH')
if comfy_path is None: if comfy_path is None:
print("git_helper: environment variable 'COMFYUI_PATH' is not specified.") print("\nWARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path.", file=sys.stderr)
exit(-1) comfy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
if not os.path.exists(os.path.join(comfy_path, 'folder_paths.py')):
print("git_helper: '{comfy_path}' is not a valid 'COMFYUI_PATH' location.")
exit(-1)
def download_url(url, dest_folder, filename=None): def download_url(url, dest_folder, filename=None):
# Ensure the destination folder exists # Ensure the destination folder exists

View File

File diff suppressed because it is too large Load Diff

View File

@@ -6,9 +6,8 @@ import time
from dataclasses import dataclass from dataclasses import dataclass
from typing import List from typing import List
from . import manager_core import manager_core
from . import manager_util import manager_util
import requests import requests
import toml import toml

View File

@@ -32,15 +32,18 @@ from packaging import version
import uuid import uuid
from . import cm_global glob_path = os.path.join(os.path.dirname(__file__)) # ComfyUI-Manager/glob
from . import cnr_utils sys.path.append(glob_path)
from . import manager_util
from . import git_utils
from . import manager_downloader
from .node_package import InstalledNodePackage
from .enums import NetworkMode, SecurityLevel, DBMode
version_code = [4, 0] import cm_global
import cnr_utils
import manager_util
import git_utils
import manager_downloader
from node_package import InstalledNodePackage
version_code = [3, 32, 3]
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
@@ -55,7 +58,6 @@ class InvalidChannel(Exception):
self.channel = channel self.channel = channel
super().__init__(channel) super().__init__(channel)
def get_default_custom_nodes_path(): def get_default_custom_nodes_path():
global default_custom_nodes_path global default_custom_nodes_path
if default_custom_nodes_path is None: if default_custom_nodes_path is None:
@@ -181,11 +183,10 @@ comfy_base_path = os.environ.get('COMFYUI_FOLDERS_BASE_PATH')
if comfy_path is None: if comfy_path is None:
try: try:
comfy_path = os.path.abspath(os.path.dirname(sys.modules['__main__'].__file__)) import folder_paths
os.environ['COMFYUI_PATH'] = comfy_path comfy_path = os.path.join(os.path.dirname(folder_paths.__file__))
except: except:
logging.error("[ComfyUI-Manager] environment variable 'COMFYUI_PATH' is not specified.") comfy_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..', '..'))
exit(-1)
if comfy_base_path is None: if comfy_base_path is None:
comfy_base_path = comfy_path comfy_base_path = comfy_path
@@ -202,7 +203,6 @@ manager_snapshot_path = None
manager_pip_overrides_path = None manager_pip_overrides_path = None
manager_pip_blacklist_path = None manager_pip_blacklist_path = None
manager_components_path = None manager_components_path = None
manager_batch_history_path = None
def update_user_directory(user_dir): def update_user_directory(user_dir):
global manager_files_path global manager_files_path
@@ -213,7 +213,6 @@ def update_user_directory(user_dir):
global manager_pip_overrides_path global manager_pip_overrides_path
global manager_pip_blacklist_path global manager_pip_blacklist_path
global manager_components_path global manager_components_path
global manager_batch_history_path
manager_files_path = os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager')) manager_files_path = os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager'))
if not os.path.exists(manager_files_path): if not os.path.exists(manager_files_path):
@@ -233,14 +232,10 @@ def update_user_directory(user_dir):
manager_pip_blacklist_path = os.path.join(manager_files_path, "pip_blacklist.list") manager_pip_blacklist_path = os.path.join(manager_files_path, "pip_blacklist.list")
manager_components_path = os.path.join(manager_files_path, "components") manager_components_path = os.path.join(manager_files_path, "components")
manager_util.cache_dir = os.path.join(manager_files_path, "cache") manager_util.cache_dir = os.path.join(manager_files_path, "cache")
manager_batch_history_path = os.path.join(manager_files_path, "batch_history")
if not os.path.exists(manager_util.cache_dir): if not os.path.exists(manager_util.cache_dir):
os.makedirs(manager_util.cache_dir) os.makedirs(manager_util.cache_dir)
if not os.path.exists(manager_batch_history_path):
os.makedirs(manager_batch_history_path)
try: try:
import folder_paths import folder_paths
update_user_directory(folder_paths.get_user_directory()) update_user_directory(folder_paths.get_user_directory())
@@ -558,7 +553,7 @@ class UnifiedManager:
ver = str(manager_util.StrictVersion(info['version'])) ver = str(manager_util.StrictVersion(info['version']))
return {'id': cnr['id'], 'cnr': cnr, 'ver': ver} return {'id': cnr['id'], 'cnr': cnr, 'ver': ver}
else: else:
return {'id': info['id'], 'ver': info['version']} return None
else: else:
return None return None
@@ -734,7 +729,7 @@ class UnifiedManager:
return latest return latest
async def reload(self, cache_mode, dont_wait=True, update_cnr_map=True): async def reload(self, cache_mode, dont_wait=True):
self.custom_node_map_cache = {} self.custom_node_map_cache = {}
self.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath self.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath
self.nightly_inactive_nodes = {} # node_id -> fullpath self.nightly_inactive_nodes = {} # node_id -> fullpath
@@ -742,18 +737,17 @@ class UnifiedManager:
self.unknown_active_nodes = {} # node_id -> repo url * fullpath self.unknown_active_nodes = {} # node_id -> repo url * fullpath
self.active_nodes = {} # node_id -> node_version * fullpath self.active_nodes = {} # node_id -> node_version * fullpath
if get_config()['network_mode'] != 'public' or manager_util.is_manager_pip_package(): if get_config()['network_mode'] != 'public':
dont_wait = True dont_wait = True
if update_cnr_map: # reload 'cnr_map' and 'repo_cnr_map'
# reload 'cnr_map' and 'repo_cnr_map' cnrs = await cnr_utils.get_cnr_data(cache_mode=cache_mode=='cache', dont_wait=dont_wait)
cnrs = await cnr_utils.get_cnr_data(cache_mode=cache_mode=='cache', dont_wait=dont_wait)
for x in cnrs: for x in cnrs:
self.cnr_map[x['id']] = x self.cnr_map[x['id']] = x
if 'repository' in x: if 'repository' in x:
normalized_url = git_utils.normalize_url(x['repository']) normalized_url = git_utils.normalize_url(x['repository'])
self.repo_cnr_map[normalized_url] = x self.repo_cnr_map[normalized_url] = x
# reload node status info from custom_nodes/* # reload node status info from custom_nodes/*
for custom_nodes_path in folder_paths.get_folder_paths('custom_nodes'): for custom_nodes_path in folder_paths.get_folder_paths('custom_nodes'):
@@ -874,8 +868,9 @@ class UnifiedManager:
package_name = remap_pip_package(line.strip()) package_name = remap_pip_package(line.strip())
if package_name and not package_name.startswith('#') and package_name not in self.processed_install: if package_name and not package_name.startswith('#') and package_name not in self.processed_install:
self.processed_install.add(package_name) self.processed_install.add(package_name)
install_cmd = manager_util.make_pip_cmd(["install", package_name]) clean_package_name = package_name.split('#')[0].strip()
if package_name.strip() != "" and not package_name.startswith('#'): install_cmd = manager_util.make_pip_cmd(["install", clean_package_name])
if clean_package_name != "" and not clean_package_name.startswith('#'):
res = res and try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution) res = res and try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution)
pip_fixer.fix_broken() pip_fixer.fix_broken()
@@ -1447,20 +1442,12 @@ class UnifiedManager:
return self.unified_enable(node_id, version_spec) return self.unified_enable(node_id, version_spec)
elif version_spec == 'unknown' or version_spec == 'nightly': elif version_spec == 'unknown' or version_spec == 'nightly':
to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), node_id))
if version_spec == 'nightly': if version_spec == 'nightly':
# disable cnr nodes # disable cnr nodes
if self.is_enabled(node_id, 'cnr'): if self.is_enabled(node_id, 'cnr'):
self.unified_disable(node_id, False) self.unified_disable(node_id, False)
# use `repo name` as a dir name instead of `cnr id` if system added nodepack (i.e. publisher is null) to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), node_id))
cnr = self.cnr_map.get(node_id)
if cnr is not None and cnr.get('publisher') is None:
repo_name = os.path.basename(git_utils.normalize_url(repo_url))
to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), repo_name))
res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall) res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall)
if res.result: if res.result:
if version_spec == 'unknown': if version_spec == 'unknown':
@@ -1677,9 +1664,9 @@ def read_config():
'model_download_by_agent': get_bool('model_download_by_agent', False), 'model_download_by_agent': get_bool('model_download_by_agent', False),
'downgrade_blacklist': default_conf.get('downgrade_blacklist', '').lower(), 'downgrade_blacklist': default_conf.get('downgrade_blacklist', '').lower(),
'always_lazy_install': get_bool('always_lazy_install', False), 'always_lazy_install': get_bool('always_lazy_install', False),
'network_mode': default_conf.get('network_mode', NetworkMode.PUBLIC.value).lower(), 'network_mode': default_conf.get('network_mode', 'public').lower(),
'security_level': default_conf.get('security_level', SecurityLevel.NORMAL.value).lower(), 'security_level': default_conf.get('security_level', 'normal').lower(),
'db_mode': default_conf.get('db_mode', DBMode.CACHE.value).lower(), 'db_mode': default_conf.get('db_mode', 'cache').lower(),
} }
except Exception: except Exception:
@@ -1700,9 +1687,9 @@ def read_config():
'model_download_by_agent': False, 'model_download_by_agent': False,
'downgrade_blacklist': '', 'downgrade_blacklist': '',
'always_lazy_install': False, 'always_lazy_install': False,
'network_mode': NetworkMode.OFFLINE.value, 'network_mode': 'public', # public | private | offline
'security_level': SecurityLevel.NORMAL.value, 'security_level': 'normal', # strong | normal | normal- | weak
'db_mode': DBMode.CACHE.value, 'db_mode': 'cache', # local | cache | remote
} }
@@ -2086,6 +2073,13 @@ def is_valid_url(url):
return False return False
def extract_url_and_commit_id(s):
index = s.rfind('@')
if index == -1:
return (s, '')
else:
return (s[:index], s[index+1:])
async def gitclone_install(url, instant_execution=False, msg_prefix='', no_deps=False): async def gitclone_install(url, instant_execution=False, msg_prefix='', no_deps=False):
await unified_manager.reload('cache') await unified_manager.reload('cache')
await unified_manager.get_custom_nodes('default', 'cache') await unified_manager.get_custom_nodes('default', 'cache')
@@ -2103,8 +2097,11 @@ async def gitclone_install(url, instant_execution=False, msg_prefix='', no_deps=
cnr = unified_manager.get_cnr_by_repo(url) cnr = unified_manager.get_cnr_by_repo(url)
if cnr: if cnr:
cnr_id = cnr['id'] cnr_id = cnr['id']
return await unified_manager.install_by_id(cnr_id, version_spec='nightly', channel='default', mode='cache') return await unified_manager.install_by_id(cnr_id, version_spec=None, channel='default', mode='cache')
else: else:
new_url, commit_id = extract_url_and_commit_id(url)
if commit_id != "":
url = new_url
repo_name = os.path.splitext(os.path.basename(url))[0] repo_name = os.path.splitext(os.path.basename(url))[0]
# NOTE: Keep original name as possible if unknown node # NOTE: Keep original name as possible if unknown node
@@ -2137,6 +2134,10 @@ async def gitclone_install(url, instant_execution=False, msg_prefix='', no_deps=
return result.fail(f"Failed to clone '{clone_url}' into '{repo_path}'") return result.fail(f"Failed to clone '{clone_url}' into '{repo_path}'")
else: else:
repo = git.Repo.clone_from(clone_url, repo_path, recursive=True, progress=GitProgress()) repo = git.Repo.clone_from(clone_url, repo_path, recursive=True, progress=GitProgress())
if commit_id!= "":
repo.git.checkout(commit_id)
repo.git.submodule('update', '--init', '--recursive')
repo.git.clear_cache() repo.git.clear_cache()
repo.close() repo.close()
@@ -2199,7 +2200,7 @@ async def get_data_by_mode(mode, filename, channel_url=None):
cache_uri = str(manager_util.simple_hash(uri))+'_'+filename cache_uri = str(manager_util.simple_hash(uri))+'_'+filename
cache_uri = os.path.join(manager_util.cache_dir, cache_uri) cache_uri = os.path.join(manager_util.cache_dir, cache_uri)
if get_config()['network_mode'] == 'offline' or manager_util.is_manager_pip_package(): if get_config()['network_mode'] == 'offline':
# offline network mode # offline network mode
if os.path.exists(cache_uri): if os.path.exists(cache_uri):
json_obj = await manager_util.get_data(cache_uri) json_obj = await manager_util.get_data(cache_uri)
@@ -2219,7 +2220,7 @@ async def get_data_by_mode(mode, filename, channel_url=None):
with open(cache_uri, "w", encoding='utf-8') as file: with open(cache_uri, "w", encoding='utf-8') as file:
json.dump(json_obj, file, indent=4, sort_keys=True) json.dump(json_obj, file, indent=4, sort_keys=True)
except Exception as e: except Exception as e:
print(f"[ComfyUI-Manager] Due to a network error, switching to local mode.\n=> {filename} @ {channel_url}/{mode}\n=> {e}") print(f"[ComfyUI-Manager] Due to a network error, switching to local mode.\n=> {filename}\n=> {e}")
uri = os.path.join(manager_util.comfyui_manager_path, filename) uri = os.path.join(manager_util.comfyui_manager_path, filename)
json_obj = await manager_util.get_data(uri) json_obj = await manager_util.get_data(uri)

View File

@@ -14,23 +14,18 @@ import git
from datetime import datetime from datetime import datetime
from server import PromptServer from server import PromptServer
import manager_core as core
import manager_util
import cm_global
import logging import logging
import asyncio import asyncio
from collections import deque import queue
from . import manager_core as core import manager_downloader
from . import manager_util
from . import cm_global
from . import manager_downloader
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})") logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
logging.info("[ComfyUI-Manager] network_mode: " + core.get_config()['network_mode'])
if not manager_util.is_manager_pip_package():
network_mode_description = "offline"
else:
network_mode_description = core.get_config()['network_mode']
logging.info("[ComfyUI-Manager] network_mode: " + network_mode_description)
comfy_ui_hash = "-" comfy_ui_hash = "-"
comfyui_tag = None comfyui_tag = None
@@ -160,7 +155,9 @@ class ManagerFuncsInComfyUI(core.ManagerFuncs):
core.manager_funcs = ManagerFuncsInComfyUI() core.manager_funcs = ManagerFuncsInComfyUI()
from .manager_downloader import download_url, download_url_with_agent sys.path.append('../..')
from manager_downloader import download_url, download_url_with_agent
core.comfy_path = os.path.dirname(folder_paths.__file__) core.comfy_path = os.path.dirname(folder_paths.__file__)
core.js_path = os.path.join(core.comfy_path, "web", "extensions") core.js_path = os.path.join(core.comfy_path, "web", "extensions")
@@ -394,73 +391,16 @@ def nickname_filter(json_obj):
return json_obj return json_obj
class TaskBatch: task_queue = queue.Queue()
def __init__(self, batch_json, tasks, failed): nodepack_result = {}
self.nodepack_result = {} model_result = {}
self.model_result = {}
self.batch_id = batch_json.get('batch_id') if batch_json is not None else None
self.batch_json = batch_json
self.tasks = tasks
self.current_index = 0
self.stats = {}
self.failed = failed if failed is not None else set()
self.is_aborted = False
def is_done(self):
return len(self.tasks) <= self.current_index
def get_next(self):
if self.is_done():
return None
item = self.tasks[self.current_index]
self.current_index += 1
return item
def done_count(self):
return len(self.nodepack_result) + len(self.model_result)
def total_count(self):
return len(self.tasks)
def abort(self):
self.is_aborted = True
def finalize(self):
if self.batch_id is not None:
batch_path = os.path.join(core.manager_batch_history_path, self.batch_id+".json")
json_obj = {
"batch": self.batch_json,
"nodepack_result": self.nodepack_result,
"model_result": self.model_result,
"failed": list(self.failed)
}
with open(batch_path, "w") as json_file:
json.dump(json_obj, json_file, indent=4)
temp_queue_batch = []
task_batch_queue = deque()
tasks_in_progress = set() tasks_in_progress = set()
task_worker_lock = threading.Lock() task_worker_lock = threading.Lock()
aborted_batch = None
def finalize_temp_queue_batch(batch_json=None, failed=None):
"""
make temp_queue_batch as a batch snapshot and add to batch_queue
"""
global temp_queue_batch
if len(temp_queue_batch):
batch = TaskBatch(batch_json, temp_queue_batch, failed)
task_batch_queue.append(batch)
temp_queue_batch = []
async def task_worker(): async def task_worker():
global task_queue global task_queue
global nodepack_result
global model_result
global tasks_in_progress global tasks_in_progress
async def do_install(item) -> str: async def do_install(item) -> str:
@@ -473,7 +413,8 @@ async def task_worker():
return f"Cannot resolve install target: '{node_spec_str}'" return f"Cannot resolve install target: '{node_spec_str}'"
node_name, version_spec, is_specified = node_spec node_name, version_spec, is_specified = node_spec
res = await core.unified_manager.install_by_id(node_name, version_spec, channel, mode, return_postinstall=skip_post_install) # discard post install if skip_post_install mode res = await core.unified_manager.install_by_id(node_name, version_spec, channel, mode, return_postinstall=skip_post_install)
# discard post install if skip_post_install mode
if res.action not in ['skip', 'enable', 'install-git', 'install-cnr', 'switch-cnr']: if res.action not in ['skip', 'enable', 'install-git', 'install-cnr', 'switch-cnr']:
logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}") logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}")
@@ -488,11 +429,6 @@ async def task_worker():
traceback.print_exc() traceback.print_exc()
return f"Installation failed:\n{node_spec_str}" return f"Installation failed:\n{node_spec_str}"
async def do_enable(item) -> str:
ui_id, cnr_id = item
core.unified_manager.unified_enable(cnr_id)
return 'success'
async def do_update(item): async def do_update(item):
ui_id, node_name, node_ver = item ui_id, node_name, node_ver = item
@@ -651,45 +587,31 @@ async def task_worker():
return f"Model installation error: {model_url}" return f"Model installation error: {model_url}"
stats = {}
while True: while True:
with task_worker_lock: done_count = len(nodepack_result) + len(model_result)
if len(task_batch_queue) > 0: total_count = done_count + task_queue.qsize()
cur_batch = task_batch_queue[0]
else:
logging.info("\n[ComfyUI-Manager] All tasks are completed.")
logging.info("\nAfter restarting ComfyUI, please refresh the browser.")
res = {'status': 'all-done'} if task_queue.empty():
logging.info(f"\n[ComfyUI-Manager] Queued works are completed.\n{stats}")
PromptServer.instance.send_sync("cm-queue-status", res) logging.info("\nAfter restarting ComfyUI, please refresh the browser.")
PromptServer.instance.send_sync("cm-queue-status",
return {'status': 'done',
'nodepack_result': nodepack_result, 'model_result': model_result,
if cur_batch.is_done(): 'total_count': total_count, 'done_count': done_count})
logging.info(f"\n[ComfyUI-Manager] A tasks batch(batch_id={cur_batch.batch_id}) is completed.\nstat={cur_batch.stats}") nodepack_result = {}
task_queue = queue.Queue()
res = {'status': 'batch-done', return # terminate worker thread
'nodepack_result': cur_batch.nodepack_result,
'model_result': cur_batch.model_result,
'total_count': cur_batch.total_count(),
'done_count': cur_batch.done_count(),
'batch_id': cur_batch.batch_id,
'remaining_batch_count': len(task_batch_queue) }
PromptServer.instance.send_sync("cm-queue-status", res)
cur_batch.finalize()
task_batch_queue.popleft()
continue
with task_worker_lock: with task_worker_lock:
kind, item = cur_batch.get_next() kind, item = task_queue.get()
tasks_in_progress.add((kind, item[0])) tasks_in_progress.add((kind, item[0]))
try: try:
if kind == 'install': if kind == 'install':
msg = await do_install(item) msg = await do_install(item)
elif kind == 'enable':
msg = await do_enable(item)
elif kind == 'install-model': elif kind == 'install-model':
msg = await do_install_model(item) msg = await do_install_model(item)
elif kind == 'update': elif kind == 'update':
@@ -713,131 +635,31 @@ async def task_worker():
with task_worker_lock: with task_worker_lock:
tasks_in_progress.remove((kind, item[0])) tasks_in_progress.remove((kind, item[0]))
ui_id = item[0] ui_id = item[0]
if kind == 'install-model': if kind == 'install-model':
cur_batch.model_result[ui_id] = msg model_result[ui_id] = msg
ui_target = "model_manager" ui_target = "model_manager"
elif kind == 'update-main': elif kind == 'update-main':
cur_batch.nodepack_result[ui_id] = msg nodepack_result[ui_id] = msg
ui_target = "main" ui_target = "main"
elif kind == 'update-comfyui': elif kind == 'update-comfyui':
cur_batch.nodepack_result['comfyui'] = msg nodepack_result['comfyui'] = msg
ui_target = "main" ui_target = "main"
elif kind == 'update': elif kind == 'update':
cur_batch.nodepack_result[ui_id] = msg['msg'] nodepack_result[ui_id] = msg['msg']
ui_target = "nodepack_manager" ui_target = "nodepack_manager"
else: else:
cur_batch.nodepack_result[ui_id] = msg nodepack_result[ui_id] = msg
ui_target = "nodepack_manager" ui_target = "nodepack_manager"
cur_batch.stats[kind] = cur_batch.stats.get(kind, 0) + 1 stats[kind] = stats.get(kind, 0) + 1
PromptServer.instance.send_sync("cm-queue-status", PromptServer.instance.send_sync("cm-queue-status",
{'status': 'in_progress', {'status': 'in_progress', 'target': item[0], 'ui_target': ui_target,
'target': item[0], 'total_count': total_count, 'done_count': done_count})
'batch_id': cur_batch.batch_id,
'ui_target': ui_target,
'total_count': cur_batch.total_count(),
'done_count': cur_batch.done_count()})
@routes.post("/v2/manager/queue/batch") @routes.get("/customnode/getmappings")
async def queue_batch(request):
json_data = await request.json()
failed = set()
for k, v in json_data.items():
if k == 'update_all':
await _update_all({'mode': v})
elif k == 'reinstall':
for x in v:
res = await _uninstall_custom_node(x)
if res.status != 200:
failed.add(x[0])
else:
res = await _install_custom_node(x)
if res.status != 200:
failed.add(x[0])
elif k == 'install':
for x in v:
res = await _install_custom_node(x)
if res.status != 200:
failed.add(x[0])
elif k == 'uninstall':
for x in v:
res = await _uninstall_custom_node(x)
if res.status != 200:
failed.add(x[0])
elif k == 'update':
for x in v:
res = await _update_custom_node(x)
if res.status != 200:
failed.add(x[0])
elif k == 'update_comfyui':
await update_comfyui(None)
elif k == 'disable':
for x in v:
await _disable_node(x)
elif k == 'install_model':
for x in v:
res = await _install_model(x)
if res.status != 200:
failed.add(x[0])
elif k == 'fix':
for x in v:
res = await _fix_custom_node(x)
if res.status != 200:
failed.add(x[0])
with task_worker_lock:
finalize_temp_queue_batch(json_data, failed)
_queue_start()
return web.json_response({"failed": list(failed)}, content_type='application/json')
@routes.get("/v2/manager/queue/history_list")
async def get_history_list(request):
history_path = core.manager_batch_history_path
try:
files = [os.path.join(history_path, f) for f in os.listdir(history_path) if os.path.isfile(os.path.join(history_path, f))]
files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
history_ids = [os.path.basename(f)[:-5] for f in files]
return web.json_response({"ids": list(history_ids)}, content_type='application/json')
except Exception as e:
logging.error(f"[ComfyUI-Manager] /v2/manager/queue/history_list - {e}")
return web.Response(status=400)
@routes.get("/v2/manager/queue/history")
async def get_history(request):
try:
json_name = request.rel_url.query["id"]+'.json'
batch_path = os.path.join(core.manager_batch_history_path, json_name)
with open(batch_path, 'r', encoding='utf-8') as file:
json_str = file.read()
json_obj = json.loads(json_str)
return web.json_response(json_obj, content_type='application/json')
except Exception as e:
logging.error(f"[ComfyUI-Manager] /v2/manager/queue/history - {e}")
return web.Response(status=400)
@routes.get("/v2/customnode/getmappings")
async def fetch_customnode_mappings(request): async def fetch_customnode_mappings(request):
""" """
provide unified (node -> node pack) mapping list provide unified (node -> node pack) mapping list
@@ -873,7 +695,7 @@ async def fetch_customnode_mappings(request):
return web.json_response(json_obj, content_type='application/json') return web.json_response(json_obj, content_type='application/json')
@routes.get("/v2/customnode/fetch_updates") @routes.get("/customnode/fetch_updates")
async def fetch_updates(request): async def fetch_updates(request):
try: try:
if request.rel_url.query["mode"] == "local": if request.rel_url.query["mode"] == "local":
@@ -900,13 +722,8 @@ async def fetch_updates(request):
return web.Response(status=400) return web.Response(status=400)
@routes.get("/v2/manager/queue/update_all") @routes.get("/manager/queue/update_all")
async def update_all(request): async def update_all(request):
json_data = dict(request.rel_url.query)
return await _update_all(json_data)
async def _update_all(json_data):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
return web.Response(status=403) return web.Response(status=403)
@@ -918,13 +735,13 @@ async def _update_all(json_data):
await core.save_snapshot_with_postfix('autosave') await core.save_snapshot_with_postfix('autosave')
if json_data["mode"] == "local": if request.rel_url.query["mode"] == "local":
channel = 'local' channel = 'local'
else: else:
channel = core.get_config()['channel_url'] channel = core.get_config()['channel_url']
await core.unified_manager.reload(json_data["mode"]) await core.unified_manager.reload(request.rel_url.query["mode"])
await core.unified_manager.get_custom_nodes(channel, json_data["mode"]) await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])
for k, v in core.unified_manager.active_nodes.items(): for k, v in core.unified_manager.active_nodes.items():
if k == 'comfyui-manager': if k == 'comfyui-manager':
@@ -933,7 +750,7 @@ async def _update_all(json_data):
continue continue
update_item = k, k, v[0] update_item = k, k, v[0]
temp_queue_batch.append(("update-main", update_item)) task_queue.put(("update-main", update_item))
for k, v in core.unified_manager.unknown_active_nodes.items(): for k, v in core.unified_manager.unknown_active_nodes.items():
if k == 'comfyui-manager': if k == 'comfyui-manager':
@@ -942,7 +759,7 @@ async def _update_all(json_data):
continue continue
update_item = k, k, 'unknown' update_item = k, k, 'unknown'
temp_queue_batch.append(("update-main", update_item)) task_queue.put(("update-main", update_item))
return web.Response(status=200) return web.Response(status=200)
@@ -993,7 +810,7 @@ def populate_markdown(x):
# freeze imported version # freeze imported version
startup_time_installed_node_packs = core.get_installed_node_packs() startup_time_installed_node_packs = core.get_installed_node_packs()
@routes.get("/v2/customnode/installed") @routes.get("/customnode/installed")
async def installed_list(request): async def installed_list(request):
mode = request.query.get('mode', 'default') mode = request.query.get('mode', 'default')
@@ -1005,7 +822,7 @@ async def installed_list(request):
return web.json_response(res, content_type='application/json') return web.json_response(res, content_type='application/json')
@routes.get("/v2/customnode/getlist") @routes.get("/customnode/getlist")
async def fetch_customnode_list(request): async def fetch_customnode_list(request):
""" """
provide unified custom node list provide unified custom node list
@@ -1118,7 +935,7 @@ def check_model_installed(json_obj):
executor.submit(process_model_phase, item) executor.submit(process_model_phase, item)
@routes.get("/v2/externalmodel/getlist") @routes.get("/externalmodel/getlist")
async def fetch_externalmodel_list(request): async def fetch_externalmodel_list(request):
# The model list is only allowed in the default channel, yet. # The model list is only allowed in the default channel, yet.
json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'model-list.json') json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'model-list.json')
@@ -1131,14 +948,14 @@ async def fetch_externalmodel_list(request):
return web.json_response(json_obj, content_type='application/json') return web.json_response(json_obj, content_type='application/json')
@PromptServer.instance.routes.get("/v2/snapshot/getlist") @PromptServer.instance.routes.get("/snapshot/getlist")
async def get_snapshot_list(request): async def get_snapshot_list(request):
items = [f[:-5] for f in os.listdir(core.manager_snapshot_path) if f.endswith('.json')] items = [f[:-5] for f in os.listdir(core.manager_snapshot_path) if f.endswith('.json')]
items.sort(reverse=True) items.sort(reverse=True)
return web.json_response({'items': items}, content_type='application/json') return web.json_response({'items': items}, content_type='application/json')
@routes.get("/v2/snapshot/remove") @routes.get("/snapshot/remove")
async def remove_snapshot(request): async def remove_snapshot(request):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
@@ -1156,7 +973,7 @@ async def remove_snapshot(request):
return web.Response(status=400) return web.Response(status=400)
@routes.get("/v2/snapshot/restore") @routes.get("/snapshot/restore")
async def restore_snapshot(request): async def restore_snapshot(request):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
@@ -1182,7 +999,7 @@ async def restore_snapshot(request):
return web.Response(status=400) return web.Response(status=400)
@routes.get("/v2/snapshot/get_current") @routes.get("/snapshot/get_current")
async def get_current_snapshot_api(request): async def get_current_snapshot_api(request):
try: try:
return web.json_response(await core.get_current_snapshot(), content_type='application/json') return web.json_response(await core.get_current_snapshot(), content_type='application/json')
@@ -1190,7 +1007,7 @@ async def get_current_snapshot_api(request):
return web.Response(status=400) return web.Response(status=400)
@routes.get("/v2/snapshot/save") @routes.get("/snapshot/save")
async def save_snapshot(request): async def save_snapshot(request):
try: try:
await core.save_snapshot_with_postfix('snapshot') await core.save_snapshot_with_postfix('snapshot')
@@ -1227,7 +1044,82 @@ def unzip_install(files):
return True return True
@routes.get("/v2/customnode/versions/{node_name}") def copy_install(files, js_path_name=None):
for url in files:
if url.endswith("/"):
url = url[:-1]
try:
filename = os.path.basename(url)
if url.endswith(".py"):
download_url(url, core.get_default_custom_nodes_path(), filename)
else:
path = os.path.join(core.js_path, js_path_name) if js_path_name is not None else core.js_path
if not os.path.exists(path):
os.makedirs(path)
download_url(url, path, filename)
except Exception as e:
logging.error(f"Install(copy) error: {url} / {e}", file=sys.stderr)
return False
logging.info("Installation was successful.")
return True
def copy_uninstall(files, js_path_name='.'):
for url in files:
if url.endswith("/"):
url = url[:-1]
dir_name = os.path.basename(url)
base_path = core.get_default_custom_nodes_path() if url.endswith('.py') else os.path.join(core.js_path, js_path_name)
file_path = os.path.join(base_path, dir_name)
try:
if os.path.exists(file_path):
os.remove(file_path)
elif os.path.exists(file_path + ".disabled"):
os.remove(file_path + ".disabled")
except Exception as e:
logging.error(f"Uninstall(copy) error: {url} / {e}", file=sys.stderr)
return False
logging.info("Uninstallation was successful.")
return True
def copy_set_active(files, is_disable, js_path_name='.'):
if is_disable:
action_name = "Disable"
else:
action_name = "Enable"
for url in files:
if url.endswith("/"):
url = url[:-1]
dir_name = os.path.basename(url)
base_path = core.get_default_custom_nodes_path() if url.endswith('.py') else os.path.join(core.js_path, js_path_name)
file_path = os.path.join(base_path, dir_name)
try:
if is_disable:
current_name = file_path
new_name = file_path + ".disabled"
else:
current_name = file_path + ".disabled"
new_name = file_path
os.rename(current_name, new_name)
except Exception as e:
logging.error(f"{action_name}(copy) error: {url} / {e}", file=sys.stderr)
return False
logging.info(f"{action_name} was successful.")
return True
@routes.get("/customnode/versions/{node_name}")
async def get_cnr_versions(request): async def get_cnr_versions(request):
node_name = request.match_info.get("node_name", None) node_name = request.match_info.get("node_name", None)
versions = core.cnr_utils.all_versions_of_node(node_name) versions = core.cnr_utils.all_versions_of_node(node_name)
@@ -1238,7 +1130,7 @@ async def get_cnr_versions(request):
return web.Response(status=400) return web.Response(status=400)
@routes.get("/v2/customnode/disabled_versions/{node_name}") @routes.get("/customnode/disabled_versions/{node_name}")
async def get_disabled_versions(request): async def get_disabled_versions(request):
node_name = request.match_info.get("node_name", None) node_name = request.match_info.get("node_name", None)
versions = [] versions = []
@@ -1254,7 +1146,7 @@ async def get_disabled_versions(request):
return web.Response(status=400) return web.Response(status=400)
@routes.post("/v2/customnode/import_fail_info") @routes.post("/customnode/import_fail_info")
async def import_fail_info(request): async def import_fail_info(request):
json_data = await request.json() json_data = await request.json()
@@ -1271,74 +1163,42 @@ async def import_fail_info(request):
return web.Response(status=400) return web.Response(status=400)
@routes.post("/v2/manager/queue/reinstall") @routes.post("/manager/queue/reinstall")
async def reinstall_custom_node(request): async def reinstall_custom_node(request):
await uninstall_custom_node(request) await uninstall_custom_node(request)
await install_custom_node(request) await install_custom_node(request)
@routes.get("/v2/manager/queue/reset") @routes.get("/manager/queue/reset")
async def reset_queue(request): async def reset_queue(request):
global task_batch_queue global task_queue
global temp_queue_batch task_queue = queue.Queue()
with task_worker_lock:
temp_queue_batch = []
task_batch_queue = deque()
return web.Response(status=200) return web.Response(status=200)
@routes.get("/v2/manager/queue/abort_current") @routes.get("/manager/queue/status")
async def abort_queue(request):
global task_batch_queue
global temp_queue_batch
with task_worker_lock:
temp_queue_batch = []
if len(task_batch_queue) > 0:
task_batch_queue[0].abort()
task_batch_queue.popleft()
return web.Response(status=200)
@routes.get("/v2/manager/queue/status")
async def queue_count(request): async def queue_count(request):
global task_queue global task_queue
with task_worker_lock: with task_worker_lock:
if len(task_batch_queue) > 0: done_count = len(nodepack_result) + len(model_result)
cur_batch = task_batch_queue[0] in_progress_count = len(tasks_in_progress)
done_count = cur_batch.done_count() total_count = done_count + in_progress_count + task_queue.qsize()
total_count = cur_batch.total_count() is_processing = task_worker_thread is not None and task_worker_thread.is_alive()
in_progress_count = len(tasks_in_progress)
is_processing = task_worker_thread is not None and task_worker_thread.is_alive()
else:
done_count = 0
total_count = 0
in_progress_count = 0
is_processing = False
return web.json_response({ return web.json_response({
'total_count': total_count, 'total_count': total_count, 'done_count': done_count, 'in_progress_count': in_progress_count,
'done_count': done_count,
'in_progress_count': in_progress_count,
'is_processing': is_processing}) 'is_processing': is_processing})
@routes.post("/v2/manager/queue/install") @routes.post("/manager/queue/install")
async def install_custom_node(request): async def install_custom_node(request):
json_data = await request.json()
print(f"install={json_data}")
return await _install_custom_node(json_data)
async def _install_custom_node(json_data):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
json_data = await request.json()
# non-nightly cnr is safe # non-nightly cnr is safe
risky_level = None risky_level = None
cnr_id = json_data.get('id') cnr_id = json_data.get('id')
@@ -1350,10 +1210,8 @@ async def _install_custom_node(json_data):
if json_data['version'] != 'unknown' and selected_version != 'unknown': if json_data['version'] != 'unknown' and selected_version != 'unknown':
if skip_post_install: if skip_post_install:
if cnr_id in core.unified_manager.nightly_inactive_nodes or cnr_id in core.unified_manager.cnr_inactive_nodes: if cnr_id in core.unified_manager.nightly_inactive_nodes or cnr_id in core.unified_manager.cnr_inactive_nodes:
enable_item = json_data.get('ui_id'), cnr_id core.unified_manager.unified_enable(cnr_id)
temp_queue_batch.append(("enable", enable_item))
return web.Response(status=200) return web.Response(status=200)
elif selected_version is None: elif selected_version is None:
selected_version = 'latest' selected_version = 'latest'
@@ -1366,11 +1224,9 @@ async def _install_custom_node(json_data):
if git_url is None: if git_url is None:
logging.error(f"[ComfyUI-Manager] Following node pack doesn't provide `nightly` version: ${git_url}") logging.error(f"[ComfyUI-Manager] Following node pack doesn't provide `nightly` version: ${git_url}")
return web.Response(status=404, text=f"Following node pack doesn't provide `nightly` version: ${git_url}") return web.Response(status=404, text=f"Following node pack doesn't provide `nightly` version: ${git_url}")
elif json_data['version'] != 'unknown' and selected_version == 'unknown': elif json_data['version'] != 'unknown' and selected_version == 'unknown':
logging.error(f"[ComfyUI-Manager] Invalid installation request: {json_data}") logging.error(f"[ComfyUI-Manager] Invalid installation request: {json_data}")
return web.Response(status=400, text="Invalid installation request") return web.Response(status=400, text="Invalid installation request")
else: else:
# unknown # unknown
unknown_name = os.path.basename(json_data['files'][0]) unknown_name = os.path.basename(json_data['files'][0])
@@ -1389,42 +1245,39 @@ async def _install_custom_node(json_data):
return web.Response(status=404, text="A security error has occurred. Please check the terminal logs") return web.Response(status=404, text="A security error has occurred. Please check the terminal logs")
install_item = json_data.get('ui_id'), node_spec_str, json_data['channel'], json_data['mode'], skip_post_install install_item = json_data.get('ui_id'), node_spec_str, json_data['channel'], json_data['mode'], skip_post_install
temp_queue_batch.append(("install", install_item)) task_queue.put(("install", install_item))
return web.Response(status=200) return web.Response(status=200)
task_worker_thread:threading.Thread = None task_worker_thread:threading.Thread = None
@routes.get("/v2/manager/queue/start") @routes.get("/manager/queue/start")
async def queue_start(request): async def queue_start(request):
with task_worker_lock: global nodepack_result
finalize_temp_queue_batch() global model_result
return _queue_start()
def _queue_start():
global task_worker_thread global task_worker_thread
if task_worker_thread is not None and task_worker_thread.is_alive(): if task_worker_thread is not None and task_worker_thread.is_alive():
return web.Response(status=201) # already in-progress return web.Response(status=201) # already in-progress
nodepack_result = {}
model_result = {}
task_worker_thread = threading.Thread(target=lambda: asyncio.run(task_worker())) task_worker_thread = threading.Thread(target=lambda: asyncio.run(task_worker()))
task_worker_thread.start() task_worker_thread.start()
return web.Response(status=200) return web.Response(status=200)
@routes.post("/v2/manager/queue/fix") @routes.post("/manager/queue/fix")
async def fix_custom_node(request): async def fix_custom_node(request):
json_data = await request.json()
return await _fix_custom_node(json_data)
async def _fix_custom_node(json_data):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_GENERAL) logging.error(SECURITY_MESSAGE_GENERAL)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
json_data = await request.json()
node_id = json_data.get('id') node_id = json_data.get('id')
node_ver = json_data['version'] node_ver = json_data['version']
if node_ver != 'unknown': if node_ver != 'unknown':
@@ -1434,12 +1287,12 @@ async def _fix_custom_node(json_data):
node_name = os.path.basename(json_data['files'][0]) node_name = os.path.basename(json_data['files'][0])
update_item = json_data.get('ui_id'), node_name, json_data['version'] update_item = json_data.get('ui_id'), node_name, json_data['version']
temp_queue_batch.append(("fix", update_item)) task_queue.put(("fix", update_item))
return web.Response(status=200) return web.Response(status=200)
@routes.post("/v2/customnode/install/git_url") @routes.post("/customnode/install/git_url")
async def install_custom_node_git_url(request): async def install_custom_node_git_url(request):
if not is_allowed_security_level('high'): if not is_allowed_security_level('high'):
logging.error(SECURITY_MESSAGE_NORMAL_MINUS) logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
@@ -1459,7 +1312,7 @@ async def install_custom_node_git_url(request):
return web.Response(status=400) return web.Response(status=400)
@routes.post("/v2/customnode/install/pip") @routes.post("/customnode/install/pip")
async def install_custom_node_pip(request): async def install_custom_node_pip(request):
if not is_allowed_security_level('high'): if not is_allowed_security_level('high'):
logging.error(SECURITY_MESSAGE_NORMAL_MINUS) logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
@@ -1471,17 +1324,14 @@ async def install_custom_node_pip(request):
return web.Response(status=200) return web.Response(status=200)
@routes.post("/v2/manager/queue/uninstall") @routes.post("/manager/queue/uninstall")
async def uninstall_custom_node(request): async def uninstall_custom_node(request):
json_data = await request.json()
return await _uninstall_custom_node(json_data)
async def _uninstall_custom_node(json_data):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
json_data = await request.json()
node_id = json_data.get('id') node_id = json_data.get('id')
if json_data['version'] != 'unknown': if json_data['version'] != 'unknown':
is_unknown = False is_unknown = False
@@ -1492,22 +1342,19 @@ async def _uninstall_custom_node(json_data):
node_name = os.path.basename(json_data['files'][0]) node_name = os.path.basename(json_data['files'][0])
uninstall_item = json_data.get('ui_id'), node_name, is_unknown uninstall_item = json_data.get('ui_id'), node_name, is_unknown
temp_queue_batch.append(("uninstall", uninstall_item)) task_queue.put(("uninstall", uninstall_item))
return web.Response(status=200) return web.Response(status=200)
@routes.post("/v2/manager/queue/update") @routes.post("/manager/queue/update")
async def update_custom_node(request): async def update_custom_node(request):
json_data = await request.json()
return await _update_custom_node(json_data)
async def _update_custom_node(json_data):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
json_data = await request.json()
node_id = json_data.get('id') node_id = json_data.get('id')
if json_data['version'] != 'unknown': if json_data['version'] != 'unknown':
node_name = node_id node_name = node_id
@@ -1516,19 +1363,19 @@ async def _update_custom_node(json_data):
node_name = os.path.basename(json_data['files'][0]) node_name = os.path.basename(json_data['files'][0])
update_item = json_data.get('ui_id'), node_name, json_data['version'] update_item = json_data.get('ui_id'), node_name, json_data['version']
temp_queue_batch.append(("update", update_item)) task_queue.put(("update", update_item))
return web.Response(status=200) return web.Response(status=200)
@routes.get("/v2/manager/queue/update_comfyui") @routes.get("/manager/queue/update_comfyui")
async def update_comfyui(request): async def update_comfyui(request):
is_stable = core.get_config()['update_policy'] != 'nightly-comfyui' is_stable = core.get_config()['update_policy'] != 'nightly-comfyui'
temp_queue_batch.append(("update-comfyui", ('comfyui', is_stable))) task_queue.put(("update-comfyui", ('comfyui', is_stable)))
return web.Response(status=200) return web.Response(status=200)
@routes.get("/v2/comfyui_manager/comfyui_versions") @routes.get("/comfyui_manager/comfyui_versions")
async def comfyui_versions(request): async def comfyui_versions(request):
try: try:
res, current, latest = core.get_comfyui_versions() res, current, latest = core.get_comfyui_versions()
@@ -1539,7 +1386,7 @@ async def comfyui_versions(request):
return web.Response(status=400) return web.Response(status=400)
@routes.get("/v2/comfyui_manager/comfyui_switch_version") @routes.get("/comfyui_manager/comfyui_switch_version")
async def comfyui_switch_version(request): async def comfyui_switch_version(request):
try: try:
if "ver" in request.rel_url.query: if "ver" in request.rel_url.query:
@@ -1552,14 +1399,10 @@ async def comfyui_switch_version(request):
return web.Response(status=400) return web.Response(status=400)
@routes.post("/v2/manager/queue/disable") @routes.post("/manager/queue/disable")
async def disable_node(request): async def disable_node(request):
json_data = await request.json() json_data = await request.json()
await _disable_node(json_data)
return web.Response(status=200)
async def _disable_node(json_data):
node_id = json_data.get('id') node_id = json_data.get('id')
if json_data['version'] != 'unknown': if json_data['version'] != 'unknown':
is_unknown = False is_unknown = False
@@ -1570,7 +1413,9 @@ async def _disable_node(json_data):
node_name = os.path.basename(json_data['files'][0]) node_name = os.path.basename(json_data['files'][0])
update_item = json_data.get('ui_id'), node_name, is_unknown update_item = json_data.get('ui_id'), node_name, is_unknown
temp_queue_batch.append(("disable", update_item)) task_queue.put(("disable", update_item))
return web.Response(status=200)
async def check_whitelist_for_model(item): async def check_whitelist_for_model(item):
@@ -1589,13 +1434,10 @@ async def check_whitelist_for_model(item):
return False return False
@routes.post("/v2/manager/queue/install_model") @routes.post("/manager/queue/install_model")
async def install_model(request): async def install_model(request):
json_data = await request.json() json_data = await request.json()
return await _install_model(json_data)
async def _install_model(json_data):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
@@ -1619,12 +1461,12 @@ async def _install_model(json_data):
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs") return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
install_item = json_data.get('ui_id'), json_data install_item = json_data.get('ui_id'), json_data
temp_queue_batch.append(("install-model", install_item)) task_queue.put(("install-model", install_item))
return web.Response(status=200) return web.Response(status=200)
@routes.get("/v2/manager/preview_method") @routes.get("/manager/preview_method")
async def preview_method(request): async def preview_method(request):
if "value" in request.rel_url.query: if "value" in request.rel_url.query:
set_preview_method(request.rel_url.query['value']) set_preview_method(request.rel_url.query['value'])
@@ -1635,7 +1477,7 @@ async def preview_method(request):
return web.Response(status=200) return web.Response(status=200)
@routes.get("/v2/manager/db_mode") @routes.get("/manager/db_mode")
async def db_mode(request): async def db_mode(request):
if "value" in request.rel_url.query: if "value" in request.rel_url.query:
set_db_mode(request.rel_url.query['value']) set_db_mode(request.rel_url.query['value'])
@@ -1647,7 +1489,7 @@ async def db_mode(request):
@routes.get("/v2/manager/policy/component") @routes.get("/manager/policy/component")
async def component_policy(request): async def component_policy(request):
if "value" in request.rel_url.query: if "value" in request.rel_url.query:
set_component_policy(request.rel_url.query['value']) set_component_policy(request.rel_url.query['value'])
@@ -1658,7 +1500,7 @@ async def component_policy(request):
return web.Response(status=200) return web.Response(status=200)
@routes.get("/v2/manager/policy/update") @routes.get("/manager/policy/update")
async def update_policy(request): async def update_policy(request):
if "value" in request.rel_url.query: if "value" in request.rel_url.query:
set_update_policy(request.rel_url.query['value']) set_update_policy(request.rel_url.query['value'])
@@ -1669,7 +1511,7 @@ async def update_policy(request):
return web.Response(status=200) return web.Response(status=200)
@routes.get("/v2/manager/channel_url_list") @routes.get("/manager/channel_url_list")
async def channel_url_list(request): async def channel_url_list(request):
channels = core.get_channel_dict() channels = core.get_channel_dict()
if "value" in request.rel_url.query: if "value" in request.rel_url.query:
@@ -1706,7 +1548,7 @@ def add_target_blank(html_text):
return modified_html return modified_html
@routes.get("/v2/manager/notice") @routes.get("/manager/notice")
async def get_notice(request): async def get_notice(request):
url = "github.com" url = "github.com"
path = "/ltdrdata/ltdrdata.github.io/wiki/News" path = "/ltdrdata/ltdrdata.github.io/wiki/News"
@@ -1753,13 +1595,7 @@ async def get_notice(request):
return web.Response(text="Unable to retrieve Notice", status=200) return web.Response(text="Unable to retrieve Notice", status=200)
# legacy /manager/notice @routes.get("/manager/reboot")
@routes.get("/manager/notice")
async def get_notice_legacy(request):
return web.Response(text="""<font color="red">Starting from ComfyUI-Manager V4.0+, it should be installed via pip.<BR><BR>Please remove the ComfyUI-Manager installed in the <font color="white">'custom_nodes'</font> directory.</font>""", status=200)
@routes.get("/v2/manager/reboot")
def restart(self): def restart(self):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
@@ -1796,7 +1632,7 @@ def restart(self):
return os.execv(sys.executable, cmds) return os.execv(sys.executable, cmds)
@routes.post("/v2/manager/component/save") @routes.post("/manager/component/save")
async def save_component(request): async def save_component(request):
try: try:
data = await request.json() data = await request.json()
@@ -1826,7 +1662,7 @@ async def save_component(request):
return web.Response(status=400) return web.Response(status=400)
@routes.post("/v2/manager/component/loads") @routes.post("/manager/component/loads")
async def load_components(request): async def load_components(request):
if os.path.exists(core.manager_components_path): if os.path.exists(core.manager_components_path):
try: try:
@@ -1851,7 +1687,7 @@ async def load_components(request):
return web.json_response({}) return web.json_response({})
@routes.get("/v2/manager/version") @routes.get("/manager/version")
async def get_version(request): async def get_version(request):
return web.Response(text=core.version_str, status=200) return web.Response(text=core.version_str, status=200)
@@ -1900,7 +1736,7 @@ async def default_cache_update():
logging.error(f"[ComfyUI-Manager] Failed to perform initial fetching '{filename}': {e}") logging.error(f"[ComfyUI-Manager] Failed to perform initial fetching '{filename}': {e}")
traceback.print_exc() traceback.print_exc()
if core.get_config()['network_mode'] != 'offline' and not manager_util.is_manager_pip_package(): if core.get_config()['network_mode'] != 'offline':
a = get_cache("custom-node-list.json") a = get_cache("custom-node-list.json")
b = get_cache("extension-node-map.json") b = get_cache("extension-node-map.json")
c = get_cache("model-list.json") c = get_cache("model-list.json")
@@ -1915,8 +1751,6 @@ async def default_cache_update():
# load at least once # load at least once
await core.unified_manager.reload('remote', dont_wait=False) await core.unified_manager.reload('remote', dont_wait=False)
await core.unified_manager.get_custom_nodes(channel_url, 'remote') await core.unified_manager.get_custom_nodes(channel_url, 'remote')
else:
await core.unified_manager.reload('remote', dont_wait=False, update_cnr_map=False)
logging.info("[ComfyUI-Manager] All startup tasks have been completed.") logging.info("[ComfyUI-Manager] All startup tasks have been completed.")
@@ -1934,3 +1768,4 @@ cm_global.register_extension('ComfyUI-Manager',
'nodes': {}, 'nodes': {},
'description': 'This extension provides the ability to manage custom nodes in ComfyUI.', }) 'description': 'This extension provides the ability to manage custom nodes in ComfyUI.', })

View File

@@ -15,18 +15,16 @@ import re
import logging import logging
import platform import platform
import shlex import shlex
import cm_global
cache_lock = threading.Lock() cache_lock = threading.Lock()
session_lock = threading.Lock()
comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
cache_dir = os.path.join(comfyui_manager_path, '.cache') # This path is also updated together in **manager_core.update_user_directory**. cache_dir = os.path.join(comfyui_manager_path, '.cache') # This path is also updated together in **manager_core.update_user_directory**.
use_uv = False use_uv = False
def is_manager_pip_package():
return not os.path.exists(os.path.join(comfyui_manager_path, '..', 'custom_nodes'))
def add_python_path_to_env(): def add_python_path_to_env():
if platform.system() != "Windows": if platform.system() != "Windows":
@@ -259,7 +257,7 @@ def get_installed_packages(renew=False):
pip_map[normalized_name] = y[1] pip_map[normalized_name] = y[1]
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
logging.error("[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.") logging.error("[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.")
return set() return {}
return pip_map return pip_map
@@ -310,6 +308,7 @@ def parse_requirement_line(line):
torch_torchvision_torchaudio_version_map = { torch_torchvision_torchaudio_version_map = {
'2.7.0': ('0.22.0', '2.7.0'),
'2.6.0': ('0.21.0', '2.6.0'), '2.6.0': ('0.21.0', '2.6.0'),
'2.5.1': ('0.20.0', '2.5.0'), '2.5.1': ('0.20.0', '2.5.0'),
'2.5.0': ('0.20.0', '2.5.0'), '2.5.0': ('0.20.0', '2.5.0'),
@@ -413,8 +412,9 @@ class PIPFixer:
if len(targets) > 0: if len(targets) > 0:
for x in targets: for x in targets:
cmd = make_pip_cmd(['install', f"{x}=={versions[0].version_string}", "numpy<2"]) if sys.version_info < (3, 13):
subprocess.check_output(cmd, universal_newlines=True) cmd = make_pip_cmd(['install', f"{x}=={versions[0].version_string}", "numpy<2"])
subprocess.check_output(cmd, universal_newlines=True)
logging.info(f"[ComfyUI-Manager] 'opencv' dependencies were fixed: {targets}") logging.info(f"[ComfyUI-Manager] 'opencv' dependencies were fixed: {targets}")
except Exception as e: except Exception as e:
@@ -422,17 +422,21 @@ class PIPFixer:
logging.error(e) logging.error(e)
# fix numpy # fix numpy
try: if sys.version_info >= (3, 13):
np = new_pip_versions.get('numpy') logging.info("[ComfyUI-Manager] In Python 3.13 and above, PIP Fixer does not downgrade `numpy` below version 2.0. If you need to force a downgrade of `numpy`, please use `pip_auto_fix.list`.")
if np is not None: else:
if StrictVersion(np) >= StrictVersion('2'): try:
cmd = make_pip_cmd(['install', "numpy<2"]) np = new_pip_versions.get('numpy')
subprocess.check_output(cmd , universal_newlines=True) if cm_global.pip_overrides.get('numpy') == 'numpy<2':
if np is not None:
if StrictVersion(np) >= StrictVersion('2'):
cmd = make_pip_cmd(['install', "numpy<2"])
subprocess.check_output(cmd , universal_newlines=True)
logging.info("[ComfyUI-Manager] 'numpy' dependency were fixed") logging.info("[ComfyUI-Manager] 'numpy' dependency were fixed")
except Exception as e: except Exception as e:
logging.error("[ComfyUI-Manager] Failed to restore numpy") logging.error("[ComfyUI-Manager] Failed to restore numpy")
logging.error(e) logging.error(e)
# fix missing frontend # fix missing frontend
try: try:

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import os import os
from .git_utils import get_commit_hash from git_utils import get_commit_hash
@dataclass @dataclass

View File

@@ -1,6 +1,5 @@
import mimetypes import mimetypes
from . import manager_core as core import manager_core as core
import os import os
from aiohttp import web from aiohttp import web
import aiohttp import aiohttp
@@ -54,7 +53,7 @@ def compute_sha256_checksum(filepath):
return sha256.hexdigest() return sha256.hexdigest()
@PromptServer.instance.routes.get("/v2/manager/share_option") @PromptServer.instance.routes.get("/manager/share_option")
async def share_option(request): async def share_option(request):
if "value" in request.rel_url.query: if "value" in request.rel_url.query:
core.get_config()['share_option'] = request.rel_url.query['value'] core.get_config()['share_option'] = request.rel_url.query['value']
@@ -123,7 +122,7 @@ def set_youml_settings(settings):
f.write(settings) f.write(settings)
@PromptServer.instance.routes.get("/v2/manager/get_openart_auth") @PromptServer.instance.routes.get("/manager/get_openart_auth")
async def api_get_openart_auth(request): async def api_get_openart_auth(request):
# print("Getting stored Matrix credentials...") # print("Getting stored Matrix credentials...")
openart_key = get_openart_auth() openart_key = get_openart_auth()
@@ -132,7 +131,7 @@ async def api_get_openart_auth(request):
return web.json_response({"openart_key": openart_key}) return web.json_response({"openart_key": openart_key})
@PromptServer.instance.routes.post("/v2/manager/set_openart_auth") @PromptServer.instance.routes.post("/manager/set_openart_auth")
async def api_set_openart_auth(request): async def api_set_openart_auth(request):
json_data = await request.json() json_data = await request.json()
openart_key = json_data['openart_key'] openart_key = json_data['openart_key']
@@ -141,7 +140,7 @@ async def api_set_openart_auth(request):
return web.Response(status=200) return web.Response(status=200)
@PromptServer.instance.routes.get("/v2/manager/get_matrix_auth") @PromptServer.instance.routes.get("/manager/get_matrix_auth")
async def api_get_matrix_auth(request): async def api_get_matrix_auth(request):
# print("Getting stored Matrix credentials...") # print("Getting stored Matrix credentials...")
matrix_auth = get_matrix_auth() matrix_auth = get_matrix_auth()
@@ -150,7 +149,7 @@ async def api_get_matrix_auth(request):
return web.json_response(matrix_auth) return web.json_response(matrix_auth)
@PromptServer.instance.routes.get("/v2/manager/youml/settings") @PromptServer.instance.routes.get("/manager/youml/settings")
async def api_get_youml_settings(request): async def api_get_youml_settings(request):
youml_settings = get_youml_settings() youml_settings = get_youml_settings()
if not youml_settings: if not youml_settings:
@@ -158,14 +157,14 @@ async def api_get_youml_settings(request):
return web.json_response(json.loads(youml_settings)) return web.json_response(json.loads(youml_settings))
@PromptServer.instance.routes.post("/v2/manager/youml/settings") @PromptServer.instance.routes.post("/manager/youml/settings")
async def api_set_youml_settings(request): async def api_set_youml_settings(request):
json_data = await request.json() json_data = await request.json()
set_youml_settings(json.dumps(json_data)) set_youml_settings(json.dumps(json_data))
return web.Response(status=200) return web.Response(status=200)
@PromptServer.instance.routes.get("/v2/manager/get_comfyworkflows_auth") @PromptServer.instance.routes.get("/manager/get_comfyworkflows_auth")
async def api_get_comfyworkflows_auth(request): async def api_get_comfyworkflows_auth(request):
# Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken' # Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken'
# in the same directory as the ComfyUI base folder # in the same directory as the ComfyUI base folder
@@ -176,7 +175,7 @@ async def api_get_comfyworkflows_auth(request):
return web.json_response({"comfyworkflows_sharekey": comfyworkflows_auth}) return web.json_response({"comfyworkflows_sharekey": comfyworkflows_auth})
@PromptServer.instance.routes.post("/v2/manager/set_esheep_workflow_and_images") @PromptServer.instance.routes.post("/manager/set_esheep_workflow_and_images")
async def set_esheep_workflow_and_images(request): async def set_esheep_workflow_and_images(request):
json_data = await request.json() json_data = await request.json()
with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), "w", encoding='utf-8') as file: with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), "w", encoding='utf-8') as file:
@@ -184,7 +183,7 @@ async def set_esheep_workflow_and_images(request):
return web.Response(status=200) return web.Response(status=200)
@PromptServer.instance.routes.get("/v2/manager/get_esheep_workflow_and_images") @PromptServer.instance.routes.get("/manager/get_esheep_workflow_and_images")
async def get_esheep_workflow_and_images(request): async def get_esheep_workflow_and_images(request):
with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file: with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file:
data = json.load(file) data = json.load(file)
@@ -212,7 +211,7 @@ def has_provided_comfyworkflows_auth(comfyworkflows_sharekey):
return comfyworkflows_sharekey.strip() return comfyworkflows_sharekey.strip()
@PromptServer.instance.routes.post("/v2/manager/share") @PromptServer.instance.routes.post("/manager/share")
async def share_art(request): async def share_art(request):
# get json data # get json data
json_data = await request.json() json_data = await request.json()

View File

@@ -25,7 +25,7 @@ async function tryInstallCustomNode(event) {
const res = await customConfirm(msg); const res = await customConfirm(msg);
if(res) { if(res) {
if(event.detail.target.installed == 'Disabled') { if(event.detail.target.installed == 'Disabled') {
const response = await api.fetchApi(`/v2/customnode/toggle_active`, { const response = await api.fetchApi(`/customnode/toggle_active`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event.detail.target) body: JSON.stringify(event.detail.target)
@@ -35,7 +35,7 @@ async function tryInstallCustomNode(event) {
await sleep(300); await sleep(300);
app.ui.dialog.show(`Installing... '${event.detail.target.title}'`); app.ui.dialog.show(`Installing... '${event.detail.target.title}'`);
const response = await api.fetchApi(`/v2/customnode/install`, { const response = await api.fetchApi(`/customnode/install`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event.detail.target) body: JSON.stringify(event.detail.target)
@@ -52,7 +52,7 @@ async function tryInstallCustomNode(event) {
} }
} }
let response = await api.fetchApi("/v2/manager/reboot"); let response = await api.fetchApi("/manager/reboot");
if(response.status == 403) { if(response.status == 403) {
show_message('This action is not allowed with this security level configuration.'); show_message('This action is not allowed with this security level configuration.');
return false; return false;

View File

@@ -14,9 +14,9 @@ import { OpenArtShareDialog } from "./comfyui-share-openart.js";
import { import {
free_models, install_pip, install_via_git_url, manager_instance, free_models, install_pip, install_via_git_url, manager_instance,
rebootAPI, setManagerInstance, show_message, customAlert, customPrompt, rebootAPI, setManagerInstance, show_message, customAlert, customPrompt,
infoToast, showTerminal, setNeedRestart, generateUUID infoToast, showTerminal, setNeedRestart
} from "./common.js"; } from "./common.js";
import { ComponentBuilderDialog, 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";
import { SnapshotManager } from "./snapshot.js"; import { SnapshotManager } from "./snapshot.js";
@@ -189,7 +189,8 @@ docStyle.innerHTML = `
} }
`; `;
function isBeforeFrontendVersion(compareVersion) { function is_legacy_front() {
let compareVersion = '1.2.49';
try { try {
const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__']; const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__'];
if (typeof frontendVersion !== 'string') { if (typeof frontendVersion !== 'string') {
@@ -222,9 +223,6 @@ function isBeforeFrontendVersion(compareVersion) {
} }
} }
const is_legacy_front = () => isBeforeFrontendVersion('1.2.49');
const isNotNewManagerUI = () => isBeforeFrontendVersion('1.16.4');
document.head.appendChild(docStyle); document.head.appendChild(docStyle);
var update_comfyui_button = null; var update_comfyui_button = null;
@@ -234,7 +232,7 @@ var restart_stop_button = null;
var update_policy_combo = null; var update_policy_combo = null;
let share_option = 'all'; let share_option = 'all';
var batch_id = null; var is_updating = false;
// copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts // copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts
@@ -417,7 +415,7 @@ const style = `
`; `;
async function init_share_option() { async function init_share_option() {
api.fetchApi('/v2/manager/share_option') api.fetchApi('/manager/share_option')
.then(response => response.text()) .then(response => response.text())
.then(data => { .then(data => {
share_option = data || 'all'; share_option = data || 'all';
@@ -425,7 +423,7 @@ async function init_share_option() {
} }
async function init_notice(notice) { async function init_notice(notice) {
api.fetchApi('/v2/manager/notice') api.fetchApi('/manager/notice')
.then(response => response.text()) .then(response => response.text())
.then(data => { .then(data => {
notice.innerHTML = data; notice.innerHTML = data;
@@ -476,19 +474,14 @@ 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...";
// set_inprogress_mode(); set_inprogress_mode();
const response = await api.fetchApi('/manager/queue/update_comfyui');
showTerminal(); showTerminal();
batch_id = generateUUID(); is_updating = true;
await api.fetchApi('/manager/queue/start');
let batch = {};
batch['batch_id'] = batch_id;
batch['update_comfyui'] = true;
const res = await api.fetchApi(`/v2/manager/queue/batch`, {
method: 'POST',
body: JSON.stringify(batch)
});
} }
function showVersionSelectorDialog(versions, current, onSelect) { function showVersionSelectorDialog(versions, current, onSelect) {
@@ -619,7 +612,7 @@ async function switchComfyUI() {
switch_comfyui_button.disabled = true; switch_comfyui_button.disabled = true;
switch_comfyui_button.style.backgroundColor = "gray"; switch_comfyui_button.style.backgroundColor = "gray";
let res = await api.fetchApi(`/v2/comfyui_manager/comfyui_versions`, { cache: "no-store" }); let res = await api.fetchApi(`/comfyui_manager/comfyui_versions`, { cache: "no-store" });
switch_comfyui_button.disabled = false; switch_comfyui_button.disabled = false;
switch_comfyui_button.style.backgroundColor = ""; switch_comfyui_button.style.backgroundColor = "";
@@ -638,14 +631,14 @@ async function switchComfyUI() {
showVersionSelectorDialog(versions, obj.current, async (selected_version) => { showVersionSelectorDialog(versions, obj.current, async (selected_version) => {
if(selected_version == 'nightly') { if(selected_version == 'nightly') {
update_policy_combo.value = 'nightly-comfyui'; update_policy_combo.value = 'nightly-comfyui';
api.fetchApi('/v2/manager/policy/update?value=nightly-comfyui'); api.fetchApi('/manager/policy/update?value=nightly-comfyui');
} }
else { else {
update_policy_combo.value = 'stable-comfyui'; update_policy_combo.value = 'stable-comfyui';
api.fetchApi('/v2/manager/policy/update?value=stable-comfyui'); api.fetchApi('/manager/policy/update?value=stable-comfyui');
} }
let response = await api.fetchApi(`/v2/comfyui_manager/comfyui_switch_version?ver=${selected_version}`, { cache: "no-store" }); let response = await api.fetchApi(`/comfyui_manager/comfyui_switch_version?ver=${selected_version}`, { cache: "no-store" });
if (response.status == 200) { if (response.status == 200) {
infoToast(`ComfyUI version is switched to ${selected_version}`); infoToast(`ComfyUI version is switched to ${selected_version}`);
} }
@@ -663,17 +656,18 @@ async function onQueueStatus(event) {
const isElectron = 'electronAPI' in window; const isElectron = 'electronAPI' in window;
if(event.detail.status == 'in_progress') { if(event.detail.status == 'in_progress') {
// set_inprogress_mode(); set_inprogress_mode();
update_all_button.innerText = `in progress.. (${event.detail.done_count}/${event.detail.total_count})`; update_all_button.innerText = `in progress.. (${event.detail.done_count}/${event.detail.total_count})`;
} }
else if(event.detail.status == 'all-done') { else if(event.detail.status == 'done') {
reset_action_buttons(); reset_action_buttons();
}
else if(event.detail.status == 'batch-done') { if(!is_updating) {
if(batch_id != event.detail.batch_id) {
return; return;
} }
is_updating = false;
let success_list = []; let success_list = [];
let failed_list = []; let failed_list = [];
let comfyui_state = null; let comfyui_state = null;
@@ -773,28 +767,41 @@ api.addEventListener("cm-queue-status", onQueueStatus);
async function updateAll(update_comfyui) { async function updateAll(update_comfyui) {
update_all_button.innerText = "Updating..."; update_all_button.innerText = "Updating...";
// set_inprogress_mode(); set_inprogress_mode();
var mode = manager_instance.datasrc_combo.value; var mode = manager_instance.datasrc_combo.value;
showTerminal(); showTerminal();
batch_id = generateUUID();
let batch = {};
if(update_comfyui) { if(update_comfyui) {
update_all_button.innerText = "Updating ComfyUI..."; update_all_button.innerText = "Updating ComfyUI...";
batch['update_comfyui'] = true; await api.fetchApi('/manager/queue/update_comfyui');
} }
batch['update_all'] = mode; const response = await api.fetchApi(`/manager/queue/update_all?mode=${mode}`);
const res = await api.fetchApi(`/v2/manager/queue/batch`, { if (response.status == 401) {
method: 'POST', customAlert('Another task is already in progress. Please stop the ongoing task first.');
body: JSON.stringify(batch) }
}); else if(response.status == 200) {
is_updating = true;
await api.fetchApi('/manager/queue/start');
}
} }
function newDOMTokenList(initialTokens) {
const tmp = document.createElement(`div`);
const classList = tmp.classList;
if (initialTokens) {
initialTokens.forEach(token => {
classList.add(token);
});
}
return classList;
}
/** /**
* Check whether the node is a potential output node (img, gif or video output) * Check whether the node is a potential output node (img, gif or video output)
*/ */
@@ -807,7 +814,7 @@ function restartOrStop() {
rebootAPI(); rebootAPI();
} }
else { else {
api.fetchApi('/v2/manager/queue/reset'); api.fetchApi('/manager/queue/reset');
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.'); infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
} }
} }
@@ -955,12 +962,12 @@ class ManagerMenuDialog extends ComfyDialog {
this.datasrc_combo.appendChild($el('option', { value: 'local', text: 'DB: Local' }, [])); this.datasrc_combo.appendChild($el('option', { value: 'local', text: 'DB: Local' }, []));
this.datasrc_combo.appendChild($el('option', { value: 'remote', text: 'DB: Channel (remote)' }, [])); this.datasrc_combo.appendChild($el('option', { value: 'remote', text: 'DB: Channel (remote)' }, []));
api.fetchApi('/v2/manager/db_mode') api.fetchApi('/manager/db_mode')
.then(response => response.text()) .then(response => response.text())
.then(data => { this.datasrc_combo.value = data; }); .then(data => { this.datasrc_combo.value = data; });
this.datasrc_combo.addEventListener('change', function (event) { this.datasrc_combo.addEventListener('change', function (event) {
api.fetchApi(`/v2/manager/db_mode?value=${event.target.value}`); api.fetchApi(`/manager/db_mode?value=${event.target.value}`);
}); });
// preview method // preview method
@@ -972,19 +979,19 @@ class ManagerMenuDialog extends ComfyDialog {
preview_combo.appendChild($el('option', { value: 'latent2rgb', text: 'Preview method: Latent2RGB (fast)' }, [])); preview_combo.appendChild($el('option', { value: 'latent2rgb', text: 'Preview method: Latent2RGB (fast)' }, []));
preview_combo.appendChild($el('option', { value: 'none', text: 'Preview method: None (very fast)' }, [])); preview_combo.appendChild($el('option', { value: 'none', text: 'Preview method: None (very fast)' }, []));
api.fetchApi('/v2/manager/preview_method') api.fetchApi('/manager/preview_method')
.then(response => response.text()) .then(response => response.text())
.then(data => { preview_combo.value = data; }); .then(data => { preview_combo.value = data; });
preview_combo.addEventListener('change', function (event) { preview_combo.addEventListener('change', function (event) {
api.fetchApi(`/v2/manager/preview_method?value=${event.target.value}`); api.fetchApi(`/manager/preview_method?value=${event.target.value}`);
}); });
// 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."); 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('/v2/manager/channel_url_list') api.fetchApi('/manager/channel_url_list')
.then(response => response.json()) .then(response => response.json())
.then(async data => { .then(async data => {
try { try {
@@ -997,7 +1004,7 @@ class ManagerMenuDialog extends ComfyDialog {
} }
channel_combo.addEventListener('change', function (event) { channel_combo.addEventListener('change', function (event) {
api.fetchApi(`/v2/manager/channel_url_list?value=${event.target.value}`); api.fetchApi(`/manager/channel_url_list?value=${event.target.value}`);
}); });
channel_combo.value = data.selected; channel_combo.value = data.selected;
@@ -1025,7 +1032,7 @@ class ManagerMenuDialog extends ComfyDialog {
share_combo.appendChild($el('option', { value: option[0], text: `Share: ${option[1]}` }, [])); share_combo.appendChild($el('option', { value: option[0], text: `Share: ${option[1]}` }, []));
} }
api.fetchApi('/v2/manager/share_option') api.fetchApi('/manager/share_option')
.then(response => response.text()) .then(response => response.text())
.then(data => { .then(data => {
share_combo.value = data || 'all'; share_combo.value = data || 'all';
@@ -1035,7 +1042,7 @@ class ManagerMenuDialog extends ComfyDialog {
share_combo.addEventListener('change', function (event) { share_combo.addEventListener('change', function (event) {
const value = event.target.value; const value = event.target.value;
share_option = value; share_option = value;
api.fetchApi(`/v2/manager/share_option?value=${value}`); api.fetchApi(`/manager/share_option?value=${value}`);
const shareButton = document.getElementById("shareButton"); const shareButton = document.getElementById("shareButton");
if (value === 'none') { if (value === 'none') {
shareButton.style.display = "none"; shareButton.style.display = "none";
@@ -1050,7 +1057,7 @@ class ManagerMenuDialog extends ComfyDialog {
component_policy_combo.appendChild($el('option', { value: 'workflow', text: 'Component: Use workflow version' }, [])); component_policy_combo.appendChild($el('option', { value: 'workflow', text: 'Component: Use workflow version' }, []));
component_policy_combo.appendChild($el('option', { value: 'higher', text: 'Component: Use higher version' }, [])); component_policy_combo.appendChild($el('option', { value: 'higher', text: 'Component: Use higher version' }, []));
component_policy_combo.appendChild($el('option', { value: 'mine', text: 'Component: Use my version' }, [])); component_policy_combo.appendChild($el('option', { value: 'mine', text: 'Component: Use my version' }, []));
api.fetchApi('/v2/manager/policy/component') api.fetchApi('/manager/policy/component')
.then(response => response.text()) .then(response => response.text())
.then(data => { .then(data => {
component_policy_combo.value = data; component_policy_combo.value = data;
@@ -1058,7 +1065,7 @@ class ManagerMenuDialog extends ComfyDialog {
}); });
component_policy_combo.addEventListener('change', function (event) { component_policy_combo.addEventListener('change', function (event) {
api.fetchApi(`/v2/manager/policy/component?value=${event.target.value}`); api.fetchApi(`/manager/policy/component?value=${event.target.value}`);
set_component_policy(event.target.value); set_component_policy(event.target.value);
}); });
@@ -1071,14 +1078,14 @@ class ManagerMenuDialog extends ComfyDialog {
update_policy_combo.className = "cm-menu-combo"; update_policy_combo.className = "cm-menu-combo";
update_policy_combo.appendChild($el('option', { value: 'stable-comfyui', text: 'Update: ComfyUI Stable Version' }, [])); update_policy_combo.appendChild($el('option', { value: 'stable-comfyui', text: 'Update: ComfyUI Stable Version' }, []));
update_policy_combo.appendChild($el('option', { value: 'nightly-comfyui', text: 'Update: ComfyUI Nightly Version' }, [])); update_policy_combo.appendChild($el('option', { value: 'nightly-comfyui', text: 'Update: ComfyUI Nightly Version' }, []));
api.fetchApi('/v2/manager/policy/update') api.fetchApi('/manager/policy/update')
.then(response => response.text()) .then(response => response.text())
.then(data => { .then(data => {
update_policy_combo.value = data; update_policy_combo.value = data;
}); });
update_policy_combo.addEventListener('change', function (event) { update_policy_combo.addEventListener('change', function (event) {
api.fetchApi(`/v2/manager/policy/update?value=${event.target.value}`); api.fetchApi(`/manager/policy/update?value=${event.target.value}`);
}); });
return [ return [
@@ -1381,12 +1388,12 @@ class ManagerMenuDialog extends ComfyDialog {
} }
async function getVersion() { async function getVersion() {
let version = await api.fetchApi(`/v2/manager/version`); let version = await api.fetchApi(`/manager/version`);
return await version.text(); return await version.text();
} }
app.registerExtension({ app.registerExtension({
name: "Comfy.Legacy.ManagerMenu", name: "Comfy.ManagerMenu",
aboutPageBadges: [ aboutPageBadges: [
{ {
@@ -1518,10 +1525,7 @@ app.registerExtension({
}).element }).element
); );
const shouldShowLegacyMenuItems = isNotNewManagerUI(); app.menu?.settingsGroup.element.before(cmGroup.element);
if (shouldShowLegacyMenuItems) {
app.menu?.settingsGroup.element.before(cmGroup.element);
}
} }
catch(exception) { catch(exception) {
console.log('ComfyUI is outdated. New style menu based features are disabled.'); console.log('ComfyUI is outdated. New style menu based features are disabled.');

View File

@@ -172,7 +172,7 @@ export const shareToEsheep= () => {
const nodes = app.graph._nodes const nodes = app.graph._nodes
const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes);
const workflow = prompt['workflow'] const workflow = prompt['workflow']
api.fetchApi(`/v2/manager/set_esheep_workflow_and_images`, { api.fetchApi(`/manager/set_esheep_workflow_and_images`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
@@ -812,7 +812,7 @@ export class ShareDialog extends ComfyDialog {
// get the user's existing matrix auth and share key // get the user's existing matrix auth and share key
ShareDialog.matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; ShareDialog.matrix_auth = { homeserver: "matrix.org", username: "", password: "" };
try { try {
api.fetchApi(`/v2/manager/get_matrix_auth`) api.fetchApi(`/manager/get_matrix_auth`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
ShareDialog.matrix_auth = data; ShareDialog.matrix_auth = data;
@@ -831,7 +831,7 @@ export class ShareDialog extends ComfyDialog {
ShareDialog.cw_sharekey = ""; ShareDialog.cw_sharekey = "";
try { try {
// console.log("Fetching comfyworkflows share key") // console.log("Fetching comfyworkflows share key")
api.fetchApi(`/v2/manager/get_comfyworkflows_auth`) api.fetchApi(`/manager/get_comfyworkflows_auth`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
ShareDialog.cw_sharekey = data.comfyworkflows_sharekey; ShareDialog.cw_sharekey = data.comfyworkflows_sharekey;
@@ -891,7 +891,7 @@ export class ShareDialog extends ComfyDialog {
// Change the text of the share button to "Sharing..." to indicate that the share process has started // Change the text of the share button to "Sharing..." to indicate that the share process has started
this.share_button.textContent = "Sharing..."; this.share_button.textContent = "Sharing...";
const response = await api.fetchApi(`/v2/manager/share`, { const response = await api.fetchApi(`/manager/share`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({

View File

@@ -67,7 +67,7 @@ export class OpenArtShareDialog extends ComfyDialog {
async readKey() { async readKey() {
let key = "" let key = ""
try { try {
key = await api.fetchApi(`/v2/manager/get_openart_auth`) key = await api.fetchApi(`/manager/get_openart_auth`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
return data.openart_key; return data.openart_key;
@@ -82,7 +82,7 @@ export class OpenArtShareDialog extends ComfyDialog {
} }
async saveKey(value) { async saveKey(value) {
await api.fetchApi(`/v2/manager/set_openart_auth`, { await api.fetchApi(`/manager/set_openart_auth`, {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
@@ -399,7 +399,7 @@ export class OpenArtShareDialog extends ComfyDialog {
form.append("file", uploadFile); form.append("file", uploadFile);
try { try {
const res = await this.fetchApi( const res = await this.fetchApi(
`/v2/workflows/upload_thumbnail`, `/workflows/upload_thumbnail`,
{ {
method: "POST", method: "POST",
body: form, body: form,
@@ -459,7 +459,7 @@ export class OpenArtShareDialog extends ComfyDialog {
throw new Error("Title is required"); throw new Error("Title is required");
} }
const current_snapshot = await api.fetchApi(`/v2/snapshot/get_current`) const current_snapshot = await api.fetchApi(`/snapshot/get_current`)
.then(response => response.json()) .then(response => response.json())
.catch(error => { .catch(error => {
// console.log(error); // console.log(error);
@@ -489,7 +489,7 @@ export class OpenArtShareDialog extends ComfyDialog {
try { try {
const response = await this.fetchApi( const response = await this.fetchApi(
"/v2/workflows/publish", "/workflows/publish",
{ {
method: "POST", method: "POST",
headers: {"Content-Type": "application/json"}, headers: {"Content-Type": "application/json"},

View File

@@ -179,7 +179,7 @@ export class YouMLShareDialog extends ComfyDialog {
async loadToken() { async loadToken() {
let key = "" let key = ""
try { try {
const response = await api.fetchApi(`/v2/manager/youml/settings`) const response = await api.fetchApi(`/manager/youml/settings`)
const settings = await response.json() const settings = await response.json()
return settings.token return settings.token
} catch (error) { } catch (error) {
@@ -188,7 +188,7 @@ export class YouMLShareDialog extends ComfyDialog {
} }
async saveToken(value) { async saveToken(value) {
await api.fetchApi(`/v2/manager/youml/settings`, { await api.fetchApi(`/manager/youml/settings`, {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
@@ -380,7 +380,7 @@ export class YouMLShareDialog extends ComfyDialog {
try { try {
let snapshotData = null; let snapshotData = null;
try { try {
const snapshot = await api.fetchApi(`/v2/snapshot/get_current`) const snapshot = await api.fetchApi(`/snapshot/get_current`)
snapshotData = await snapshot.json() snapshotData = await snapshot.json()
} catch (e) { } catch (e) {
console.error("Failed to get snapshot", e) console.error("Failed to get snapshot", e)

View File

@@ -172,7 +172,7 @@ export function rebootAPI() {
customConfirm("Are you sure you'd like to reboot the server?").then((isConfirmed) => { customConfirm("Are you sure you'd like to reboot the server?").then((isConfirmed) => {
if (isConfirmed) { if (isConfirmed) {
try { try {
api.fetchApi("/v2/manager/reboot"); api.fetchApi("/manager/reboot");
} }
catch(exception) {} catch(exception) {}
} }
@@ -210,7 +210,7 @@ export async function install_pip(packages) {
if(packages.includes('&')) if(packages.includes('&'))
app.ui.dialog.show(`Invalid PIP package enumeration: '${packages}'`); app.ui.dialog.show(`Invalid PIP package enumeration: '${packages}'`);
const res = await api.fetchApi("/v2/customnode/install/pip", { const res = await api.fetchApi("/customnode/install/pip", {
method: "POST", method: "POST",
body: packages, body: packages,
}); });
@@ -245,7 +245,7 @@ export async function install_via_git_url(url, manager_dialog) {
show_message(`Wait...<BR><BR>Installing '${url}'`); show_message(`Wait...<BR><BR>Installing '${url}'`);
const res = await api.fetchApi("/v2/customnode/install/git_url", { const res = await api.fetchApi("/customnode/install/git_url", {
method: "POST", method: "POST",
body: url, body: url,
}); });
@@ -630,14 +630,6 @@ export function showTooltip(target, text, className = 'cn-tooltip', styleMap = {
}); });
} }
export function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
function initTooltip () { function initTooltip () {
const mouseenterHandler = (e) => { const mouseenterHandler = (e) => {
const target = e.target; const target = e.target;

View File

@@ -64,7 +64,7 @@ function storeGroupNode(name, data, register=true) {
} }
export async function load_components() { export async function load_components() {
let data = await api.fetchApi('/v2/manager/component/loads', {method: "POST"}); let data = await api.fetchApi('/manager/component/loads', {method: "POST"});
let components = await data.json(); let components = await data.json();
let start_time = Date.now(); let start_time = Date.now();
@@ -222,7 +222,7 @@ async function save_as_component(node, version, author, prefix, nodename, packna
pack_map[packname] = component_name; pack_map[packname] = component_name;
rpack_map[component_name] = subgraph; rpack_map[component_name] = subgraph;
const res = await api.fetchApi('/v2/manager/component/save', { const res = await api.fetchApi('/manager/component/save', {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -259,7 +259,7 @@ async function import_component(component_name, component, mode) {
workflow: component workflow: component
}; };
const res = await api.fetchApi('/v2/manager/component/save', { const res = await api.fetchApi('/manager/component/save', {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", }, headers: { "Content-Type": "application/json", },
body: JSON.stringify(body) body: JSON.stringify(body)
@@ -709,7 +709,7 @@ app.handleFile = handleFile;
let current_component_policy = 'workflow'; let current_component_policy = 'workflow';
try { try {
api.fetchApi('/v2/manager/policy/component') api.fetchApi('/manager/policy/component')
.then(response => response.text()) .then(response => response.text())
.then(data => { current_component_policy = data; }); .then(data => { current_component_policy = data; });
} }

View File

@@ -7,7 +7,7 @@ import {
fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt, fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt,
sanitizeHTML, infoToast, showTerminal, setNeedRestart, sanitizeHTML, infoToast, showTerminal, setNeedRestart,
storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss, storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss,
showPopover, hidePopover, generateUUID showPopover, hidePopover
} from "./common.js"; } from "./common.js";
// https://cenfun.github.io/turbogrid/api.html // https://cenfun.github.io/turbogrid/api.html
@@ -66,7 +66,7 @@ export class CustomNodesManager {
this.id = "cn-manager"; this.id = "cn-manager";
app.registerExtension({ app.registerExtension({
name: "Comfy.Legacy.CustomNodesManager", name: "Comfy.CustomNodesManager",
afterConfigureGraph: (missingNodeTypes) => { afterConfigureGraph: (missingNodeTypes) => {
const item = this.getFilterItem(ShowMode.MISSING); const item = this.getFilterItem(ShowMode.MISSING);
if (item) { if (item) {
@@ -459,7 +459,7 @@ export class CustomNodesManager {
".cn-manager-stop": { ".cn-manager-stop": {
click: () => { click: () => {
api.fetchApi('/v2/manager/queue/reset'); api.fetchApi('/manager/queue/reset');
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.'); infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
} }
}, },
@@ -635,7 +635,7 @@ export class CustomNodesManager {
}; };
} }
const response = await api.fetchApi(`/v2/customnode/import_fail_info`, { const response = await api.fetchApi(`/customnode/import_fail_info`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(info) body: JSON.stringify(info)
@@ -1243,7 +1243,7 @@ export class CustomNodesManager {
async loadNodes(node_packs) { async loadNodes(node_packs) {
const mode = manager_instance.datasrc_combo.value; const mode = manager_instance.datasrc_combo.value;
this.showStatus(`Loading node mappings (${mode}) ...`); this.showStatus(`Loading node mappings (${mode}) ...`);
const res = await fetchData(`/v2/customnode/getmappings?mode=${mode}`); const res = await fetchData(`/customnode/getmappings?mode=${mode}`);
if (res.error) { if (res.error) {
console.log(res.error); console.log(res.error);
return; return;
@@ -1395,10 +1395,10 @@ export class CustomNodesManager {
this.showLoading(); this.showLoading();
let res; let res;
if(is_enable) { if(is_enable) {
res = await api.fetchApi(`/v2/customnode/disabled_versions/${node_id}`, { cache: "no-store" }); res = await api.fetchApi(`/customnode/disabled_versions/${node_id}`, { cache: "no-store" });
} }
else { else {
res = await api.fetchApi(`/v2/customnode/versions/${node_id}`, { cache: "no-store" }); res = await api.fetchApi(`/customnode/versions/${node_id}`, { cache: "no-store" });
} }
this.hideLoading(); this.hideLoading();
@@ -1410,15 +1410,16 @@ export class CustomNodesManager {
let version_cnt = 0; let version_cnt = 0;
if(!is_enable) { if(!is_enable) {
if(rowItem.cnr_latest != rowItem.originalData.active_version && obj.length > 0) {
versions.push('latest');
}
if(rowItem.originalData.active_version != 'nightly') { if(rowItem.originalData.active_version != 'nightly') {
versions.push('nightly'); versions.push('nightly');
default_version = 'nightly'; default_version = 'nightly';
version_cnt++; version_cnt++;
} }
if(rowItem.cnr_latest != rowItem.originalData.active_version && obj.length > 0) {
versions.push('latest');
}
} }
for(let v of obj) { for(let v of obj) {
@@ -1439,6 +1440,13 @@ export class CustomNodesManager {
} }
async installNodes(list, btn, title, selected_version) { async installNodes(list, btn, title, selected_version) {
let stats = await api.fetchApi('/manager/queue/status');
stats = await stats.json();
if(stats.is_processing) {
customAlert(`[ComfyUI-Manager] There are already tasks in progress. Please try again after it is completed. (${stats.done_count}/${stats.total_count})`);
return;
}
const { target, label, mode} = btn; const { target, label, mode} = btn;
if(mode === "uninstall") { if(mode === "uninstall") {
@@ -1465,9 +1473,9 @@ export class CustomNodesManager {
let needRestart = false; let needRestart = false;
let errorMsg = ""; let errorMsg = "";
let target_items = []; await api.fetchApi('/manager/queue/reset');
let batch = {}; let target_items = [];
for (const hash of list) { for (const hash of list) {
const item = this.grid.getRowItemBy("hash", hash); const item = this.grid.getRowItemBy("hash", hash);
@@ -1510,11 +1518,23 @@ export class CustomNodesManager {
api_mode = 'reinstall'; api_mode = 'reinstall';
} }
if(batch[api_mode]) { const res = await api.fetchApi(`/manager/queue/${api_mode}`, {
batch[api_mode].push(data); method: 'POST',
} body: JSON.stringify(data)
else { });
batch[api_mode] = [data];
if (res.status != 200) {
errorMsg = `'${item.title}': `;
if(res.status == 403) {
errorMsg += `This action is not allowed with this security level configuration.\n`;
} else if(res.status == 404) {
errorMsg += `With the current security level configuration, only custom nodes from the <B>"default channel"</B> can be installed.\n`;
} else {
errorMsg += await res.text() + '\n';
}
break;
} }
} }
@@ -1531,24 +1551,7 @@ export class CustomNodesManager {
} }
} }
else { else {
this.batch_id = generateUUID(); await api.fetchApi('/manager/queue/start');
batch['batch_id'] = this.batch_id;
const res = await api.fetchApi(`/v2/manager/queue/batch`, {
method: 'POST',
body: JSON.stringify(batch)
});
let failed = await res.json();
if(failed.length > 0) {
for(let k in failed) {
let hash = failed[k];
const item = this.grid.getRowItemBy("hash", hash);
errorMsg = `[FAIL] ${item.title}`;
}
}
this.showStop(); this.showStop();
showTerminal(); showTerminal();
} }
@@ -1556,9 +1559,6 @@ export class CustomNodesManager {
async onQueueStatus(event) { async onQueueStatus(event) {
let self = CustomNodesManager.instance; let self = CustomNodesManager.instance;
// If legacy manager front is not open, return early (using new manager front)
if (self.element?.style.display === 'none') return
if(event.detail.status == 'in_progress' && event.detail.ui_target == 'nodepack_manager') { if(event.detail.status == 'in_progress' && event.detail.ui_target == 'nodepack_manager') {
const hash = event.detail.target; const hash = event.detail.target;
@@ -1569,7 +1569,7 @@ export class CustomNodesManager {
self.grid.updateCell(item, "action"); self.grid.updateCell(item, "action");
self.grid.setRowSelected(item, false); self.grid.setRowSelected(item, false);
} }
else if(event.detail.status == 'batch-done' && event.detail.batch_id == self.batch_id) { else if(event.detail.status == 'done') {
self.hideStop(); self.hideStop();
self.onQueueCompleted(event.detail); self.onQueueCompleted(event.detail);
} }
@@ -1745,7 +1745,7 @@ export class CustomNodesManager {
async getMissingNodesLegacy(hashMap, missing_nodes) { async getMissingNodesLegacy(hashMap, missing_nodes) {
const mode = manager_instance.datasrc_combo.value; const mode = manager_instance.datasrc_combo.value;
this.showStatus(`Loading missing nodes (${mode}) ...`); this.showStatus(`Loading missing nodes (${mode}) ...`);
const res = await fetchData(`/v2/customnode/getmappings?mode=${mode}`); const res = await fetchData(`/customnode/getmappings?mode=${mode}`);
if (res.error) { if (res.error) {
this.showError(`Failed to get custom node mappings: ${res.error}`); this.showError(`Failed to get custom node mappings: ${res.error}`);
return; return;
@@ -1860,7 +1860,7 @@ export class CustomNodesManager {
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(`/v2/customnode/alternatives?mode=${mode}`); const res = await fetchData(`/customnode/alternatives?mode=${mode}`);
if (res.error) { if (res.error) {
this.showError(`Failed to get alternatives: ${res.error}`); this.showError(`Failed to get alternatives: ${res.error}`);
return []; return [];
@@ -1908,7 +1908,7 @@ export class CustomNodesManager {
infoToast('Fetching updated information. This may take some time if many custom nodes are installed.'); infoToast('Fetching updated information. This may take some time if many custom nodes are installed.');
} }
const res = await fetchData(`/v2/customnode/getlist?mode=${mode}${skip_update}`); const res = await fetchData(`/customnode/getlist?mode=${mode}${skip_update}`);
if (res.error) { if (res.error) {
this.showError("Failed to get custom node list."); this.showError("Failed to get custom node list.");
this.hideLoading(); this.hideLoading();

View File

@@ -3,7 +3,7 @@ import { $el } from "../../scripts/ui.js";
import { import {
manager_instance, rebootAPI, manager_instance, rebootAPI,
fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal, fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal,
storeColumnWidth, restoreColumnWidth, loadCss, generateUUID storeColumnWidth, restoreColumnWidth, loadCss
} from "./common.js"; } from "./common.js";
import { api } from "../../scripts/api.js"; import { api } from "../../scripts/api.js";
@@ -81,10 +81,13 @@ export class ModelManager {
value: "" value: ""
}, { }, {
label: "Installed", label: "Installed",
value: "True" value: "installed"
}, { }, {
label: "Not Installed", label: "Not Installed",
value: "False" value: "not_installed"
}, {
label: "In Workflow",
value: "in_workflow"
}]; }];
this.typeList = [{ this.typeList = [{
@@ -172,7 +175,7 @@ export class ModelManager {
".cmm-manager-stop": { ".cmm-manager-stop": {
click: () => { click: () => {
api.fetchApi('/v2/manager/queue/reset'); api.fetchApi('/manager/queue/reset');
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.'); infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
} }
}, },
@@ -254,12 +257,31 @@ export class ModelManager {
rowFilter: (rowItem) => { rowFilter: (rowItem) => {
const searchableColumns = ["name", "type", "base", "description", "filename", "save_path"]; const searchableColumns = ["name", "type", "base", "description", "filename", "save_path"];
const models_extensions = ['.ckpt', '.pt', '.pt2', '.bin', '.pth', '.safetensors', '.pkl', '.sft'];
let shouldShown = grid.highlightKeywordsFilter(rowItem, searchableColumns, this.keywords); let shouldShown = grid.highlightKeywordsFilter(rowItem, searchableColumns, this.keywords);
if (shouldShown) { if (shouldShown) {
if(this.filter && rowItem.installed !== this.filter) { if(this.filter) {
return false; if (this.filter == "in_workflow") {
rowItem.in_workflow = null;
if (Array.isArray(app.graph._nodes)) {
app.graph._nodes.forEach((item, i) => {
if (Array.isArray(item.widgets_values)) {
item.widgets_values.forEach((_item, i) => {
if (rowItem.in_workflow === null && _item !== null && models_extensions.includes("." + _item.toString().split('.').pop())) {
let filename = _item.match(/([^\/]+)(?=\.\w+$)/)[0];
if (grid.highlightKeywordsFilter(rowItem, searchableColumns, filename)) {
rowItem.in_workflow = "True";
grid.highlightKeywordsFilter(rowItem, searchableColumns, "");
}
}
});
}
});
}
}
return ((this.filter == "installed" && rowItem.installed == "True") || (this.filter == "not_installed" && rowItem.installed == "False") || (this.filter == "in_workflow" && rowItem.in_workflow == "True"));
} }
if(this.type && rowItem.type !== this.type) { if(this.type && rowItem.type !== this.type) {
@@ -413,15 +435,23 @@ export class ModelManager {
} }
async installModels(list, btn) { async installModels(list, btn) {
let stats = await api.fetchApi('/manager/queue/status');
stats = await stats.json();
if(stats.is_processing) {
customAlert(`[ComfyUI-Manager] There are already tasks in progress. Please try again after it is completed. (${stats.done_count}/${stats.total_count})`);
return;
}
btn.classList.add("cmm-btn-loading"); btn.classList.add("cmm-btn-loading");
this.showError(""); this.showError("");
let needRefresh = false; let needRefresh = false;
let errorMsg = ""; let errorMsg = "";
let target_items = []; await api.fetchApi('/manager/queue/reset');
let batch = {}; let target_items = [];
for (const item of list) { for (const item of list) {
this.grid.scrollRowIntoView(item); this.grid.scrollRowIntoView(item);
@@ -438,12 +468,21 @@ export class ModelManager {
const data = item.originalData; const data = item.originalData;
data.ui_id = item.hash; data.ui_id = item.hash;
const res = await api.fetchApi(`/manager/queue/install_model`, {
method: 'POST',
body: JSON.stringify(data)
});
if(batch['install_model']) { if (res.status != 200) {
batch['install_model'].push(data); errorMsg = `'${item.name}': `;
}
else { if(res.status == 403) {
batch['install_model'] = [data]; errorMsg += `This action is not allowed with this security level configuration.\n`;
} else {
errorMsg += await res.text() + '\n';
}
break;
} }
} }
@@ -460,24 +499,7 @@ export class ModelManager {
} }
} }
else { else {
this.batch_id = generateUUID(); await api.fetchApi('/manager/queue/start');
batch['batch_id'] = this.batch_id;
const res = await api.fetchApi(`/v2/manager/queue/batch`, {
method: 'POST',
body: JSON.stringify(batch)
});
let failed = await res.json();
if(failed.length > 0) {
for(let k in failed) {
let hash = failed[k];
const item = self.grid.getRowItemBy("hash", hash);
errorMsg = `[FAIL] ${item.title}`;
}
}
this.showStop(); this.showStop();
showTerminal(); showTerminal();
} }
@@ -497,7 +519,7 @@ export class ModelManager {
// self.grid.updateCell(item, "tg-column-select"); // self.grid.updateCell(item, "tg-column-select");
self.grid.updateRow(item); self.grid.updateRow(item);
} }
else if(event.detail.status == 'batch-done') { else if(event.detail.status == 'done') {
self.hideStop(); self.hideStop();
self.onQueueCompleted(event.detail); self.onQueueCompleted(event.detail);
} }
@@ -623,7 +645,7 @@ export class ModelManager {
const mode = manager_instance.datasrc_combo.value; const mode = manager_instance.datasrc_combo.value;
const res = await fetchData(`/v2/externalmodel/getlist?mode=${mode}`); const res = await fetchData(`/externalmodel/getlist?mode=${mode}`);
if (res.error) { if (res.error) {
this.showError("Failed to get external model list."); this.showError("Failed to get external model list.");
this.hideLoading(); this.hideLoading();
@@ -795,4 +817,4 @@ export class ModelManager {
close() { close() {
this.element.style.display = "none"; this.element.style.display = "none";
} }
} }

View File

@@ -142,7 +142,7 @@ function node_info_copy(src, dest, connect_both, copy_shape) {
} }
app.registerExtension({ app.registerExtension({
name: "Comfy.Legacy.Manager.NodeFixer", name: "Comfy.Manager.NodeFixer",
beforeRegisterNodeDef(nodeType, nodeData, app) { beforeRegisterNodeDef(nodeType, nodeData, app) {
addMenuHandler(nodeType, function (_, options) { addMenuHandler(nodeType, function (_, options) {
options.push({ options.push({

View File

@@ -7,7 +7,7 @@ import { manager_instance, rebootAPI, show_message } from "./common.js";
async function restore_snapshot(target) { async function restore_snapshot(target) {
if(SnapshotManager.instance) { if(SnapshotManager.instance) {
try { try {
const response = await api.fetchApi(`/v2/snapshot/restore?target=${target}`, { cache: "no-store" }); const response = await api.fetchApi(`/snapshot/restore?target=${target}`, { cache: "no-store" });
if(response.status == 403) { if(response.status == 403) {
show_message('This action is not allowed with this security level configuration.'); show_message('This action is not allowed with this security level configuration.');
@@ -35,7 +35,7 @@ async function restore_snapshot(target) {
async function remove_snapshot(target) { async function remove_snapshot(target) {
if(SnapshotManager.instance) { if(SnapshotManager.instance) {
try { try {
const response = await api.fetchApi(`/v2/snapshot/remove?target=${target}`, { cache: "no-store" }); const response = await api.fetchApi(`/snapshot/remove?target=${target}`, { cache: "no-store" });
if(response.status == 403) { if(response.status == 403) {
show_message('This action is not allowed with this security level configuration.'); show_message('This action is not allowed with this security level configuration.');
@@ -61,7 +61,7 @@ async function remove_snapshot(target) {
async function save_current_snapshot() { async function save_current_snapshot() {
try { try {
const response = await api.fetchApi('/v2/snapshot/save', { cache: "no-store" }); const response = await api.fetchApi('/snapshot/save', { cache: "no-store" });
app.ui.dialog.close(); app.ui.dialog.close();
return true; return true;
} }
@@ -76,7 +76,7 @@ async function save_current_snapshot() {
} }
async function getSnapshotList() { async function getSnapshotList() {
const response = await api.fetchApi(`/v2/snapshot/getlist`); const response = await api.fetchApi(`/snapshot/getlist`);
const data = await response.json(); const data = await response.json();
return data; return data;
} }

View File

@@ -38,7 +38,7 @@ class WorkflowMetadataExtension {
* enabled is true if the node is enabled, false if it is disabled * enabled is true if the node is enabled, false if it is disabled
*/ */
async getInstalledNodes() { async getInstalledNodes() {
const res = await api.fetchApi("/v2/customnode/installed"); const res = await api.fetchApi("/customnode/installed");
return await res.json(); return await res.json();
} }
@@ -70,8 +70,8 @@ class WorkflowMetadataExtension {
if (cnr_id === "comfy-core") return; // don't allow hijacking comfy-core name if (cnr_id === "comfy-core") return; // don't allow hijacking comfy-core name
if (cnr_id) nodeProperties.cnr_id = cnr_id; if (cnr_id) nodeProperties.cnr_id = cnr_id;
else nodeProperties.aux_id = aux_id; else nodeProperties.aux_id = aux_id;
if (ver) nodeProperties.ver = ver; if (ver) nodeProperties.ver = ver.trim();
} else if (["nodes", "comfy_extras"].includes(moduleType)) { } else if (["nodes", "comfy_extras", "comfy_api_nodes"].includes(moduleType)) {
nodeProperties.cnr_id = "comfy-core"; nodeProperties.cnr_id = "comfy-core";
nodeProperties.ver = this.comfyCoreVersion; nodeProperties.ver = this.comfyCoreVersion;
} }

View File

@@ -749,8 +749,8 @@
"save_path": "loras/HyperSD/SDXL", "save_path": "loras/HyperSD/SDXL",
"description": "Hyper-SD LoRA (4steps) - SDXL", "description": "Hyper-SD LoRA (4steps) - SDXL",
"reference": "https://huggingface.co/ByteDance/Hyper-SD", "reference": "https://huggingface.co/ByteDance/Hyper-SD",
"filename": "Hyper-SD15-4steps-lora.safetensors", "filename": "Hyper-SDXL-4steps-lora.safetensors",
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-4steps-lora.safetensors", "url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-4steps-lora.safetensors",
"size": "787MB" "size": "787MB"
}, },
{ {
@@ -4953,6 +4953,107 @@
"filename": "umt5_xxl_fp8_e4m3fn_scaled.safetensors", "filename": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors", "url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"size": "6.74GB" "size": "6.74GB"
},
{
"name": "lllyasviel/FramePackI2V_HY",
"type": "FramePackI2V",
"base": "FramePackI2V",
"save_path": "diffusers/lllyasviel",
"description": "[SNAPSHOT] This is the f1k1_x_g9_f1k1f2k2f16k4_td FramePack for HY. [w/You cannot download this item on ComfyUI-Manager versions below V3.18]",
"reference": "https://huggingface.co/lllyasviel/FramePackI2V_HY",
"filename": "<huggingface>",
"url": "lllyasviel/FramePackI2V_HY",
"size": "25.75GB"
},
{
"name": "LTX-Video Spatial Upscaler v0.9.7",
"type": "upscale",
"base": "upscale",
"save_path": "default",
"description": "Spatial upscaler model for LTX-Video. This model enhances the spatial resolution of generated videos.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-spatial-upscaler-0.9.7.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-spatial-upscaler-0.9.7.safetensors",
"size": "505MB"
},
{
"name": "LTX-Video Temporal Upscaler v0.9.7",
"type": "upscale",
"base": "upscale",
"save_path": "default",
"description": "Temporal upscaler model for LTX-Video. This model enhances the temporal resolution and smoothness of generated videos.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-temporal-upscaler-0.9.7.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-temporal-upscaler-0.9.7.safetensors",
"size": "524MB"
},
{
"name": "LTX-Video 13B v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "High-resolution quality LTX-Video 13B model.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-dev.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev.safetensors",
"size": "28.6GB"
},
{
"name": "LTX-Video 13B FP8 v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "Quantized version of the LTX-Video 13B model, optimized for lower VRAM usage while maintaining high quality.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-dev-fp8.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev-fp8.safetensors",
"size": "15.7GB"
},
{
"name": "LTX-Video 13B Distilled v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "Distilled version of the LTX-Video 13B model, providing improved efficiency while maintaining high-resolution quality.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-distilled.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled.safetensors",
"size": "28.6GB"
},
{
"name": "LTX-Video 13B Distilled FP8 v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "Quantized distilled version of the LTX-Video 13B model, optimized for even lower VRAM usage while maintaining quality.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-distilled-fp8.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-fp8.safetensors",
"size": "15.7GB"
},
{
"name": "LTX-Video 13B Distilled LoRA v0.9.7",
"type": "lora",
"base": "LTX-Video",
"save_path": "loras",
"description": "A LoRA adapter that transforms the standard LTX-Video 13B model into a distilled version when loaded.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-distilled-lora128.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-lora128.safetensors",
"size": "1.33GB"
},
{
"name": "Latent Bridge Matching for Image Relighting",
"type": "diffusion_model",
"base": "LBM",
"save_path": "diffusion_models/LBM",
"description": "Latent Bridge Matching (LBM) Relighting model",
"reference": "https://huggingface.co/jasperai/LBM_relighting",
"filename": "LBM_relighting.safetensors",
"url": "https://huggingface.co/jasperai/LBM_relighting/resolve/main/model.safetensors",
"size": "5.02GB"
} }
] ]
} }

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,341 @@
}, },
{
"author": "syaofox",
"title": "ComfyUI_fnodes [REMOVED]",
"reference": "https://github.com/syaofox/ComfyUI_fnodes",
"files": [
"https://github.com/syaofox/ComfyUI_fnodes"
],
"install_type": "git-clone",
"description": "ComfyUI_fnodes is a collection of custom nodes designed for ComfyUI. These nodes provide additional functionality that can enhance your ComfyUI workflows.\nFile manipulation tools, Image resizing tools, IPAdapter tools, Image processing tools, Mask tools, Face analysis tools, Sampler tools, Miscellaneous tools"
},
{
"author": "Hangover3832",
"title": "ComfyUI-Hangover-Moondream [DEPRECATED]",
"reference": "https://github.com/Hangover3832/ComfyUI-Hangover-Moondream",
"files": [
"https://github.com/Hangover3832/ComfyUI-Hangover-Moondream"
],
"install_type": "git-clone",
"description": "Moondream is a lightweight multimodal large language model.\n[w/WARN:Additional python code will be downloaded from huggingface and executed. You have to trust this creator if you want to use this node!]"
},
{
"author": "Hangover3832",
"title": "Recognize Anything Model (RAM) for ComfyUI [DEPRECATED]",
"reference": "https://github.com/Hangover3832/ComfyUI-Hangover-Recognize_Anything",
"files": [
"https://github.com/Hangover3832/ComfyUI-Hangover-Recognize_Anything"
],
"install_type": "git-clone",
"description": "This is an image recognition node for ComfyUI based on the RAM++ model from [a/xinyu1205](https://huggingface.co/xinyu1205).\nThis node outputs a string of tags with all the recognized objects and elements in the image in English or Chinese language.\nFor image tagging and captioning."
},
{
"author": "Hangover3832",
"title": "ComfyUI-Hangover-Nodes [DEPRECATED]",
"reference": "https://github.com/Hangover3832/ComfyUI-Hangover-Nodes",
"files": [
"https://github.com/Hangover3832/ComfyUI-Hangover-Nodes"
],
"install_type": "git-clone",
"description": "Nodes: MS kosmos-2 Interrogator, Save Image w/o Metadata, Image Scale Bounding Box. An implementation of Microsoft [a/kosmos-2](https://huggingface.co/microsoft/kosmos-2-patch14-224) image to text transformer."
},
{
"author": "SirLatore",
"title": "ComfyUI-IPAdapterWAN [REMOVED]",
"reference": "https://github.com/SirLatore/ComfyUI-IPAdapterWAN",
"files": [
"https://github.com/SirLatore/ComfyUI-IPAdapterWAN"
],
"install_type": "git-clone",
"description": "This extension adapts the [a/InstantX IP-Adapter for SD3.5-Large](https://huggingface.co/InstantX/SD3.5-Large-IP-Adapter) to work with Wan 2.1 and other UNet-based video/image models in ComfyUI.\nUnlike the original SD3 version (which depends on joint_blocks from MMDiT), this version performs sampling-time identity conditioning by dynamically injecting into attention layers — making it compatible with models like Wan 2.1, AnimateDiff, and other non-SD3 pipelines."
},
{
"author": "Jpzz",
"title": "ComfyUI-VirtualInteraction [UNSAFE/REMOVED]",
"reference": "https://github.com/Jpzz/ComfyUI-VirtualInteraction",
"files": [
"https://github.com/Jpzz/ComfyUI-VirtualInteraction"
],
"install_type": "git-clone",
"description": "NODES: virtual interaction custom node when using generative movie\n[w/This nodepack contains a node which is reading arbitrary excel file.]"
},
{
"author": "satche",
"title": "Prompt Factory [REMOVED]",
"reference": "https://github.com/satche/comfyui-prompt-factory",
"files": [
"https://github.com/satche/comfyui-prompt-factory"
],
"install_type": "git-clone",
"description": "A modular system that adds randomness to prompt generation"
},
{
"author": "MITCAP",
"title": "ComfyUI OpenAI DALL-E 3 Node [REMOVED]",
"reference": "https://github.com/MITCAP/OpenAI-ComfyUI",
"files": [
"https://github.com/MITCAP/OpenAI-ComfyUI"
],
"install_type": "git-clone",
"description": "This project provides custom nodes for ComfyUI that integrate with OpenAI's DALL-E 3 and GPT-4o models. The nodes allow users to generate images and describe images using OpenAI's API.\nNOTE: The files in the repo are not organized."
},
{
"author": "raspie10032",
"title": "ComfyUI NAI Prompt Converter [REMOVED]",
"reference": "https://github.com/raspie10032/ComfyUI_RS_NAI_Local_Prompt_converter",
"files": [
"https://github.com/raspie10032/ComfyUI_RS_NAI_Local_Prompt_converter"
],
"install_type": "git-clone",
"description": "A custom node extension for ComfyUI that enables conversion between different prompt formats: NovelAI V4, ComfyUI, and old NovelAI."
},
{
"author": "holchan",
"title": "ComfyUI-ModelDownloader [REMOVED]",
"reference": "https://github.com/holchan/ComfyUI-ModelDownloader",
"files": [
"https://github.com/holchan/ComfyUI-ModelDownloader"
],
"install_type": "git-clone",
"description": "A ComfyUI node to download models(Checkpoints and LoRA) from external links and act as an output standalone node."
},
{
"author": "Kur0butiMegane",
"title": "Comfyui-StringUtils [DEPRECATED]",
"reference": "https://github.com/Kur0butiMegane/Comfyui-StringUtils",
"files": [
"https://github.com/Kur0butiMegane/Comfyui-StringUtils"
],
"install_type": "git-clone",
"description": "NODES: Prompt Normalizer, String Splitter, String Line Selector, Extract Markup Value"
},
{
"author": "Apache0ne",
"title": "ComfyUI-LantentCompose [REMOVED]",
"reference": "https://github.com/Apache0ne/ComfyUI-LantentCompose",
"files": [
"https://github.com/Apache0ne/ComfyUI-LantentCompose"
],
"install_type": "git-clone",
"description": "Interpolate sdxl latents using slerp with and without a mask. use with unsample nodes for best effect.\nNOTE: The files in the repo are not organized."
},
{
"author": "jax-explorer",
"title": "ComfyUI-H-flow [REMOVED]",
"reference": "https://github.com/jax-explorer/ComfyUI-H-flow",
"files": [
"https://github.com/jax-explorer/ComfyUI-H-flow"
],
"install_type": "git-clone",
"description": "NODES: Wan2-1 Image To Video, LLM Task, Save Image, Save Video, Show Text, FluxPro Ultra, IdeogramV2 Turbo, Runway Image To Video, Kling Image To Video, Replace Text, Join Text, Test Image, Test Text"
},
{
"author": "Apache0ne",
"title": "SambaNova [REMOVED]",
"id": "SambaNovaAPI",
"reference": "https://github.com/Apache0ne/SambaNova",
"files": [
"https://github.com/Apache0ne/SambaNova"
],
"install_type": "git-clone",
"description": "Super Fast LLM's llama3.1-405B,70B,8B and more"
},
{
"author": "Apache0ne",
"title": "ComfyUI-EasyUrlLoader [REMOVED]",
"id": "easy-url-loader",
"reference": "https://github.com/Apache0ne/ComfyUI-EasyUrlLoader",
"files": [
"https://github.com/Apache0ne/ComfyUI-EasyUrlLoader"
],
"install_type": "git-clone",
"description": "A simple YT downloader node for ComfyUI using video Urls. Can be used with VHS nodes etc."
},
{
"author": "nxt5656",
"title": "ComfyUI-Image2OSS [REMOVED]",
"reference": "https://github.com/nxt5656/ComfyUI-Image2OSS",
"files": [
"https://github.com/nxt5656/ComfyUI-Image2OSS"
],
"install_type": "git-clone",
"description": "Upload the image to Alibaba Cloud OSS."
},
{
"author": "ainewsto",
"title": "Comfyui_Comfly",
"reference": "https://github.com/ainewsto/Comfyui_Comfly",
"files": [
"https://github.com/ainewsto/Comfyui_Comfly"
],
"install_type": "git-clone",
"description": "NODES: Comfly_Mj, Comfly_mjstyle, Comfly_upload, Comfly_Mju, Comfly_Mjv, Comfly_kling_videoPreview\nNOTE: Comfyui_Comfly_v2 is introduced."
},
{
"author": "shinich39",
"title": "comfyui-to-inpaint",
"reference": "https://github.com/shinich39/comfyui-to-inpaint",
"files": [
"https://github.com/shinich39/comfyui-to-inpaint"
],
"install_type": "git-clone",
"description": "Send preview image to inpaint workflow."
},
{
"author": "magic-quill",
"title": "ComfyUI_MagicQuill [NOT MAINTAINED]",
"id": "MagicQuill",
"reference": "https://github.com/magic-quill/ComfyUI_MagicQuill",
"files": [
"https://github.com/magic-quill/ComfyUI_MagicQuill"
],
"install_type": "git-clone",
"description": "Towards GPT-4 like large language and visual assistant.\nNOTE: The current version has not been maintained for a long time and does not work. Please use https://github.com/brantje/ComfyUI_MagicQuill instead."
},
{
"author": "shinich39",
"title": "comfyui-event-handler [USAFE/REMOVED]",
"reference": "https://github.com/shinich39/comfyui-event-handler",
"files": [
"https://github.com/shinich39/comfyui-event-handler"
],
"install_type": "git-clone",
"description": "Javascript code will run when an event fires. [w/This node allows you to execute arbitrary JavaScript code as input for the workflow.]"
},
{
"author": "Moooonet",
"title": "ComfyUI-ArteMoon [REMOVED]",
"reference": "https://github.com/Moooonet/ComfyUI-ArteMoon",
"files": [
"https://github.com/Moooonet/ComfyUI-ArteMoon"
],
"install_type": "git-clone",
"description": "This plugin works with [a/IF_AI_Tools](https://github.com/if-ai/ComfyUI-IF_AI_tools) to build a workflow in ComfyUI that uses AI to assist in generating prompts."
},
{
"author": "ryanontheinside",
"title": "ComfyUI-MediaPipe-Vision [REMOVED]",
"reference": "https://github.com/ryanontheinside/ComfyUI-MediaPipe-Vision",
"files": [
"https://github.com/ryanontheinside/ComfyUI-MediaPipe-Vision"
],
"install_type": "git-clone",
"description": "A centralized wrapper of all MediaPipe vision tasks for ComfyUI."
},
{
"author": "shinich39",
"title": "comfyui-textarea-command [REMOVED]",
"reference": "https://github.com/shinich39/comfyui-textarea-command",
"files": [
"https://github.com/shinich39/comfyui-textarea-command"
],
"install_type": "git-clone",
"description": "Add command and comment in textarea. (e.g. // Disabled line)"
},
{
"author": "shinich39",
"title": "comfyui-parse-image [REMOVED]",
"reference": "https://github.com/shinich39/comfyui-parse-image",
"files": [
"https://github.com/shinich39/comfyui-parse-image"
],
"install_type": "git-clone",
"description": "Extract metadata from image."
},
{
"author": "shinich39",
"title": "comfyui-put-image [REMOVED]",
"reference": "https://github.com/shinich39/comfyui-put-image",
"files": [
"https://github.com/shinich39/comfyui-put-image"
],
"install_type": "git-clone",
"description": "Load image from directory."
},
{
"author": "fredconex",
"title": "TripoSG Nodes for ComfyUI [REMOVED]",
"reference": "https://github.com/fredconex/ComfyUI-TripoSG",
"files": [
"https://github.com/fredconex/ComfyUI-TripoSG"
],
"install_type": "git-clone",
"description": "Created by Alfredo Fernandes inspired by Hunyuan3D nodes by Kijai. This extension adds TripoSG 3D mesh generation capabilities to ComfyUI, allowing you to generate 3D meshes from a single image using the TripoSG model."
},
{
"author": "fredconex",
"title": "ComfyUI-PaintTurbo [REMOVED]",
"reference": "https://github.com/fredconex/ComfyUI-PaintTurbo",
"files": [
"https://github.com/fredconex/ComfyUI-PaintTurbo"
],
"install_type": "git-clone",
"description": "NODES: Hunyuan3D Texture Mesh"
},
{
"author": "zhuanqianfish",
"title": "TaesdDecoder [REMOVED]",
"reference": "https://github.com/zhuanqianfish/TaesdDecoder",
"files": [
"https://github.com/zhuanqianfish/TaesdDecoder"
],
"install_type": "git-clone",
"description": "use TAESD decoded image.you need donwload taesd_decoder.pth and taesdxl_decoder.pth to vae_approx folder first.\n It will result in a slight loss of image quality and a significant decrease in peak video memory during decoding."
},
{
"author": "myAiLemon",
"title": "MagicAutomaticPicture [REMOVED]",
"reference": "https://github.com/myAiLemon/MagicAutomaticPicture",
"files": [
"https://github.com/myAiLemon/MagicAutomaticPicture"
],
"install_type": "git-clone",
"description": "A comfyui node package that can generate pictures and automatically save positive prompts and eliminate unwanted prompts"
},
{
"author": "thisiseddy-ab",
"title": "ComfyUI-Edins-Ultimate-Pack [REMOVED]",
"reference": "https://github.com/thisiseddy-ab/ComfyUI-Edins-Ultimate-Pack",
"files": [
"https://github.com/thisiseddy-ab/ComfyUI-Edins-Ultimate-Pack"
],
"install_type": "git-clone",
"description": "Well i needet a Tiled Ksampler that still works for Comfy UI there were none so i made one, in this Package i will put all Nodes i will develop for Comfy Ui still in beta alot will change.."
},
{
"author": "Davros666",
"title": "safetriggers [REMOVED]",
"reference": "https://github.com/Davros666/safetriggers",
"files": [
"https://github.com/Davros666/safetriggers"
],
"install_type": "git-clone",
"description": "ComfyUI Nodes for READING TRIGGERS, TRIGGER-WORDS, TRIGGER-PHRASES FROM LoRAs"
},
{
"author": "cubiq",
"title": "Simple Math [REMOVED]",
"id": "simplemath",
"reference": "https://github.com/cubiq/ComfyUI_SimpleMath",
"files": [
"https://github.com/cubiq/ComfyUI_SimpleMath"
],
"install_type": "git-clone",
"description": "custom node for ComfyUI to perform simple math operations"
},
{
"author": "lucafoscili",
"title": "LF Nodes [DEPRECATED]",
"reference": "https://github.com/lucafoscili/comfyui-lf",
"files": [
"https://github.com/lucafoscili/comfyui-lf"
],
"install_type": "git-clone",
"description": "Custom nodes with a touch of extra UX, including: history for primitives, JSON manipulation, logic switches with visual feedback, LLM chat... and more!"
},
{ {
"author": "AI2lab", "author": "AI2lab",
"title": "comfyUI-tool-2lab [REMOVED]", "title": "comfyUI-tool-2lab [REMOVED]",

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,106 @@
{ {
"models": [ "models": [
{
"name": "Latent Bridge Matching for Image Relighting",
"type": "diffusion_model",
"base": "LBM",
"save_path": "diffusion_models/LBM",
"description": "Latent Bridge Matching (LBM) Relighting model",
"reference": "https://huggingface.co/jasperai/LBM_relighting",
"filename": "LBM_relighting.safetensors",
"url": "https://huggingface.co/jasperai/LBM_relighting/resolve/main/model.safetensors",
"size": "5.02GB"
},
{
"name": "LTX-Video 13B Distilled v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "Distilled version of the LTX-Video 13B model, providing improved efficiency while maintaining high-resolution quality.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-distilled.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled.safetensors",
"size": "28.6GB"
},
{
"name": "LTX-Video 13B Distilled FP8 v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "Quantized distilled version of the LTX-Video 13B model, optimized for even lower VRAM usage while maintaining quality.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-distilled-fp8.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-fp8.safetensors",
"size": "15.7GB"
},
{
"name": "LTX-Video 13B Distilled LoRA v0.9.7",
"type": "lora",
"base": "LTX-Video",
"save_path": "loras",
"description": "A LoRA adapter that transforms the standard LTX-Video 13B model into a distilled version when loaded.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-distilled-lora128.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-lora128.safetensors",
"size": "1.33GB"
},
{
"name": "lllyasviel/FramePackI2V_HY",
"type": "FramePackI2V",
"base": "FramePackI2V",
"save_path": "diffusers/lllyasviel",
"description": "[SNAPSHOT] This is the f1k1_x_g9_f1k1f2k2f16k4_td FramePack for HY. [w/You cannot download this item on ComfyUI-Manager versions below V3.18]",
"reference": "https://huggingface.co/lllyasviel/FramePackI2V_HY",
"filename": "<huggingface>",
"url": "lllyasviel/FramePackI2V_HY",
"size": "25.75GB"
},
{
"name": "LTX-Video Spatial Upscaler v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "Spatial upscaler model for LTX-Video. This model enhances the spatial resolution of generated videos.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-spatial-upscaler-0.9.7.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-spatial-upscaler-0.9.7.safetensors",
"size": "505MB"
},
{
"name": "LTX-Video Temporal Upscaler v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "Temporal upscaler model for LTX-Video. This model enhances the temporal resolution and smoothness of generated videos.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-temporal-upscaler-0.9.7.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-temporal-upscaler-0.9.7.safetensors",
"size": "524MB"
},
{
"name": "LTX-Video 13B v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "High-resolution quality LTX-Video 13B model.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-dev.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev.safetensors",
"size": "28.6GB"
},
{
"name": "LTX-Video 13B FP8 v0.9.7",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "Quantized version of the LTX-Video 13B model, optimized for lower VRAM usage while maintaining high quality.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltxv-13b-0.9.7-dev-fp8.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev-fp8.safetensors",
"size": "15.7GB"
},
{ {
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (bf16)", "name": "Comfy-Org/Wan2.1 i2v 480p 14B (bf16)",
"type": "diffusion_model", "type": "diffusion_model",
@@ -590,121 +691,6 @@
"filename": "sigclip_vision_patch14_384.safetensors", "filename": "sigclip_vision_patch14_384.safetensors",
"url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors", "url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors",
"size": "857MB" "size": "857MB"
},
{
"name": "comfyanonymous/flux_text_encoders - t5xxl (fp16)",
"type": "clip",
"base": "t5",
"save_path": "text_encoders/t5",
"description": "Text Encoders for FLUX (fp16)",
"reference": "https://huggingface.co/comfyanonymous/flux_text_encoders",
"filename": "t5xxl_fp16.safetensors",
"url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp16.safetensors",
"size": "9.79GB"
},
{
"name": "comfyanonymous/flux_text_encoders - t5xxl (fp8_e4m3fn)",
"type": "clip",
"base": "t5",
"save_path": "text_encoders/t5",
"description": "Text Encoders for FLUX (fp8_e4m3fn)",
"reference": "https://huggingface.co/comfyanonymous/flux_text_encoders",
"filename": "t5xxl_fp8_e4m3fn.safetensors",
"url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp8_e4m3fn.safetensors",
"size": "4.89GB"
},
{
"name": "comfyanonymous/flux_text_encoders - t5xxl (fp8_e4m3fn_scaled)",
"type": "clip",
"base": "t5",
"save_path": "text_encoders/t5",
"description": "Text Encoders for FLUX (fp16)",
"reference": "https://huggingface.co/comfyanonymous/flux_text_encoders",
"filename": "t5xxl_fp8_e4m3fn_scaled.safetensors",
"url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp8_e4m3fn_scaled.safetensors",
"size": "5.16GB"
},
{
"name": "FLUX.1 [Dev] Diffusion model (scaled fp8)",
"type": "diffusion_model",
"base": "FLUX.1",
"save_path": "diffusion_models/FLUX1",
"description": "FLUX.1 [Dev] Diffusion model (scaled fp8)[w/Due to the large size of the model, it is recommended to download it through a browser if possible.]",
"reference": "https://huggingface.co/comfyanonymous/flux_dev_scaled_fp8_test",
"filename": "flux_dev_fp8_scaled_diffusion_model.safetensors",
"url": "https://huggingface.co/comfyanonymous/flux_dev_scaled_fp8_test/resolve/main/flux_dev_fp8_scaled_diffusion_model.safetensors",
"size": "11.9GB"
},
{
"name": "kijai/MoGe_ViT_L_fp16.safetensors",
"type": "MoGe",
"base": "MoGe",
"save_path": "MoGe",
"description": "Safetensors versions of [a/https://github.com/microsoft/MoGe](https://github.com/microsoft/MoGe)",
"reference": "https://huggingface.co/Kijai/MoGe_safetensors",
"filename": "MoGe_ViT_L_fp16.safetensors",
"url": "https://huggingface.co/Kijai/MoGe_safetensors/resolve/main/MoGe_ViT_L_fp16.safetensors",
"size": "628MB"
},
{
"name": "kijai/MoGe_ViT_L_fp16.safetensors",
"type": "MoGe",
"base": "MoGe",
"save_path": "MoGe",
"description": "Safetensors versions of [a/https://github.com/microsoft/MoGe](https://github.com/microsoft/MoGe)",
"reference": "https://huggingface.co/Kijai/MoGe_safetensors",
"filename": "MoGe_ViT_L_fp16.safetensors",
"url": "https://huggingface.co/Kijai/MoGe_safetensors/resolve/main/MoGe_ViT_L_fp16.safetensors",
"size": "1.26GB"
},
{
"name": "pulid_flux_v0.9.1.safetensors",
"type": "PuLID",
"base": "FLUX",
"save_path": "pulid",
"description": "This is required for PuLID (FLUX)",
"reference": "https://huggingface.co/guozinan/PuLID",
"filename": "pulid_flux_v0.9.1.safetensors",
"url": "https://huggingface.co/guozinan/PuLID/resolve/main/pulid_flux_v0.9.1.safetensors",
"size": "1.14GB"
},
{
"name": "pulid_v1.1.safetensors",
"type": "PuLID",
"base": "SDXL",
"save_path": "pulid",
"description": "This is required for PuLID (SDXL)",
"reference": "https://huggingface.co/guozinan/PuLID",
"filename": "pulid_v1.1.safetensors",
"url": "https://huggingface.co/guozinan/PuLID/resolve/main/pulid_v1.1.safetensors",
"size": "984MB"
},
{
"name": "Kolors-IP-Adapter-Plus.bin (Kwai-Kolors/Kolors-IP-Adapter-Plus)",
"type": "IP-Adapter",
"base": "Kolors",
"save_path": "ipadapter",
"description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.",
"reference": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus",
"filename": "Kolors-IP-Adapter-Plus.bin",
"url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus/resolve/main/ip_adapter_plus_general.bin",
"size": "1.01GB"
},
{
"name": "Kolors-IP-Adapter-FaceID-Plus.bin (Kwai-Kolors/Kolors-IP-Adapter-Plus)",
"type": "IP-Adapter",
"base": "Kolors",
"save_path": "ipadapter",
"description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.",
"reference": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-FaceID-Plus",
"filename": "Kolors-IP-Adapter-FaceID-Plus.bin",
"url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-FaceID-Plus/resolve/main/ipa-faceid-plus.bin",
"size": "2.39GB"
} }
] ]
} }

View File

@@ -1,5 +1,15 @@
{ {
"custom_nodes": [ "custom_nodes": [
{
"author": "Comfy-Org",
"title": "ComfyUI React Extension Template",
"reference": "https://github.com/Comfy-Org/ComfyUI-React-Extension-Template",
"files": [
"https://github.com/Comfy-Org/ComfyUI-React-Extension-Template"
],
"install_type": "git-clone",
"description": "A minimal template for creating React/TypeScript frontend extensions for ComfyUI, with complete boilerplate setup including internationalization and unit testing."
},
{ {
"author": "Suzie1", "author": "Suzie1",
"title": "Guide To Making Custom Nodes in ComfyUI", "title": "Guide To Making Custom Nodes in ComfyUI",

View File

@@ -0,0 +1,373 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "aaaaaaaaaa"
},
"source": [
"Git clone the repo and install the requirements. (ignore the pip errors about protobuf)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "bbbbbbbbbb"
},
"outputs": [],
"source": [
"# #@title Environment Setup\n",
"\n",
"from pathlib import Path\n",
"\n",
"OPTIONS = {}\n",
"\n",
"USE_GOOGLE_DRIVE = True #@param {type:\"boolean\"}\n",
"UPDATE_COMFY_UI = True #@param {type:\"boolean\"}\n",
"USE_COMFYUI_MANAGER = True #@param {type:\"boolean\"}\n",
"INSTALL_CUSTOM_NODES_DEPENDENCIES = True #@param {type:\"boolean\"}\n",
"OPTIONS['USE_GOOGLE_DRIVE'] = USE_GOOGLE_DRIVE\n",
"OPTIONS['UPDATE_COMFY_UI'] = UPDATE_COMFY_UI\n",
"OPTIONS['USE_COMFYUI_MANAGER'] = USE_COMFYUI_MANAGER\n",
"OPTIONS['INSTALL_CUSTOM_NODES_DEPENDENCIES'] = INSTALL_CUSTOM_NODES_DEPENDENCIES\n",
"\n",
"current_dir = !pwd\n",
"WORKSPACE = f\"{current_dir[0]}/ComfyUI\"\n",
"\n",
"if OPTIONS['USE_GOOGLE_DRIVE']:\n",
" !echo \"Mounting Google Drive...\"\n",
" %cd /\n",
"\n",
" from google.colab import drive\n",
" drive.mount('/content/drive')\n",
"\n",
" WORKSPACE = \"/content/drive/MyDrive/ComfyUI\"\n",
" %cd /content/drive/MyDrive\n",
"\n",
"![ ! -d $WORKSPACE ] && echo -= Initial setup ComfyUI =- && git clone https://github.com/comfyanonymous/ComfyUI\n",
"%cd $WORKSPACE\n",
"\n",
"if OPTIONS['UPDATE_COMFY_UI']:\n",
" !echo -= Updating ComfyUI =-\n",
"\n",
" # Correction of the issue of permissions being deleted on Google Drive.\n",
" ![ -f \".ci/nightly/update_windows/update_comfyui_and_python_dependencies.bat\" ] && chmod 755 .ci/nightly/update_windows/update_comfyui_and_python_dependencies.bat\n",
" ![ -f \".ci/nightly/windows_base_files/run_nvidia_gpu.bat\" ] && chmod 755 .ci/nightly/windows_base_files/run_nvidia_gpu.bat\n",
" ![ -f \".ci/update_windows/update_comfyui_and_python_dependencies.bat\" ] && chmod 755 .ci/update_windows/update_comfyui_and_python_dependencies.bat\n",
" ![ -f \".ci/update_windows_cu118/update_comfyui_and_python_dependencies.bat\" ] && chmod 755 .ci/update_windows_cu118/update_comfyui_and_python_dependencies.bat\n",
" ![ -f \".ci/update_windows/update.py\" ] && chmod 755 .ci/update_windows/update.py\n",
" ![ -f \".ci/update_windows/update_comfyui.bat\" ] && chmod 755 .ci/update_windows/update_comfyui.bat\n",
" ![ -f \".ci/update_windows/README_VERY_IMPORTANT.txt\" ] && chmod 755 .ci/update_windows/README_VERY_IMPORTANT.txt\n",
" ![ -f \".ci/update_windows/run_cpu.bat\" ] && chmod 755 .ci/update_windows/run_cpu.bat\n",
" ![ -f \".ci/update_windows/run_nvidia_gpu.bat\" ] && chmod 755 .ci/update_windows/run_nvidia_gpu.bat\n",
"\n",
" !git pull\n",
"\n",
"!echo -= Install dependencies =-\n",
"!pip3 install accelerate\n",
"!pip3 install einops transformers>=4.28.1 safetensors>=0.4.2 aiohttp pyyaml Pillow scipy tqdm psutil tokenizers>=0.13.3\n",
"!pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121\n",
"!pip3 install torchsde\n",
"!pip3 install kornia>=0.7.1 spandrel soundfile sentencepiece\n",
"\n",
"if OPTIONS['USE_COMFYUI_MANAGER']:\n",
" %cd custom_nodes\n",
"\n",
" # Correction of the issue of permissions being deleted on Google Drive.\n",
" ![ -f \"ComfyUI-Manager/check.sh\" ] && chmod 755 ComfyUI-Manager/check.sh\n",
" ![ -f \"ComfyUI-Manager/scan.sh\" ] && chmod 755 ComfyUI-Manager/scan.sh\n",
" ![ -f \"ComfyUI-Manager/node_db/dev/scan.sh\" ] && chmod 755 ComfyUI-Manager/node_db/dev/scan.sh\n",
" ![ -f \"ComfyUI-Manager/node_db/tutorial/scan.sh\" ] && chmod 755 ComfyUI-Manager/node_db/tutorial/scan.sh\n",
" ![ -f \"ComfyUI-Manager/scripts/install-comfyui-venv-linux.sh\" ] && chmod 755 ComfyUI-Manager/scripts/install-comfyui-venv-linux.sh\n",
" ![ -f \"ComfyUI-Manager/scripts/install-comfyui-venv-win.bat\" ] && chmod 755 ComfyUI-Manager/scripts/install-comfyui-venv-win.bat\n",
"\n",
" ![ ! -d ComfyUI-Manager ] && echo -= Initial setup ComfyUI-Manager =- && git clone https://github.com/ltdrdata/ComfyUI-Manager\n",
" %cd ComfyUI-Manager\n",
" !git pull\n",
"\n",
"%cd $WORKSPACE\n",
"\n",
"if OPTIONS['INSTALL_CUSTOM_NODES_DEPENDENCIES']:\n",
" !echo -= Install custom nodes dependencies =-\n",
" !pip install GitPython\n",
" !python custom_nodes/ComfyUI-Manager/cm-cli.py restore-dependencies\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "cccccccccc"
},
"source": [
"Download some models/checkpoints/vae or custom comfyui nodes (uncomment the commands for the ones you want)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dddddddddd"
},
"outputs": [],
"source": [
"# Checkpoints\n",
"\n",
"### SDXL\n",
"### I recommend these workflow examples: https://comfyanonymous.github.io/ComfyUI_examples/sdxl/\n",
"\n",
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors -P ./models/checkpoints/\n",
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/resolve/main/sd_xl_refiner_1.0.safetensors -P ./models/checkpoints/\n",
"\n",
"# SDXL ReVision\n",
"#!wget -c https://huggingface.co/comfyanonymous/clip_vision_g/resolve/main/clip_vision_g.safetensors -P ./models/clip_vision/\n",
"\n",
"# SD1.5\n",
"!wget -c https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt -P ./models/checkpoints/\n",
"\n",
"# SD2\n",
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-ema-pruned.safetensors -P ./models/checkpoints/\n",
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors -P ./models/checkpoints/\n",
"\n",
"# Some SD1.5 anime style\n",
"#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix2/AbyssOrangeMix2_hard.safetensors -P ./models/checkpoints/\n",
"#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A1_orangemixs.safetensors -P ./models/checkpoints/\n",
"#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A3_orangemixs.safetensors -P ./models/checkpoints/\n",
"#!wget -c https://huggingface.co/Linaqruf/anything-v3.0/resolve/main/anything-v3-fp16-pruned.safetensors -P ./models/checkpoints/\n",
"\n",
"# Waifu Diffusion 1.5 (anime style SD2.x 768-v)\n",
"#!wget -c https://huggingface.co/waifu-diffusion/wd-1-5-beta3/resolve/main/wd-illusion-fp16.safetensors -P ./models/checkpoints/\n",
"\n",
"\n",
"# unCLIP models\n",
"#!wget -c https://huggingface.co/comfyanonymous/illuminatiDiffusionV1_v11_unCLIP/resolve/main/illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors -P ./models/checkpoints/\n",
"#!wget -c https://huggingface.co/comfyanonymous/wd-1.5-beta2_unCLIP/resolve/main/wd-1-5-beta2-aesthetic-unclip-h-fp16.safetensors -P ./models/checkpoints/\n",
"\n",
"\n",
"# VAE\n",
"!wget -c https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors -P ./models/vae/\n",
"#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/VAEs/orangemix.vae.pt -P ./models/vae/\n",
"#!wget -c https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime2.ckpt -P ./models/vae/\n",
"\n",
"\n",
"# Loras\n",
"#!wget -c https://civitai.com/api/download/models/10350 -O ./models/loras/theovercomer8sContrastFix_sd21768.safetensors #theovercomer8sContrastFix SD2.x 768-v\n",
"#!wget -c https://civitai.com/api/download/models/10638 -O ./models/loras/theovercomer8sContrastFix_sd15.safetensors #theovercomer8sContrastFix SD1.x\n",
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors -P ./models/loras/ #SDXL offset noise lora\n",
"\n",
"\n",
"# T2I-Adapter\n",
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_depth_sd14v1.pth -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_seg_sd14v1.pth -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_sketch_sd14v1.pth -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_keypose_sd14v1.pth -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_openpose_sd14v1.pth -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_color_sd14v1.pth -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_canny_sd14v1.pth -P ./models/controlnet/\n",
"\n",
"# T2I Styles Model\n",
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_style_sd14v1.pth -P ./models/style_models/\n",
"\n",
"# CLIPVision model (needed for styles model)\n",
"#!wget -c https://huggingface.co/openai/clip-vit-large-patch14/resolve/main/pytorch_model.bin -O ./models/clip_vision/clip_vit14.bin\n",
"\n",
"\n",
"# ControlNet\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_ip2p_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_shuffle_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_canny_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11f1p_sd15_depth_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_inpaint_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_lineart_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_mlsd_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_normalbae_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_openpose_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_scribble_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_seg_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_softedge_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15s2_lineart_anime_fp16.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11u_sd15_tile_fp16.safetensors -P ./models/controlnet/\n",
"\n",
"# ControlNet SDXL\n",
"#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-canny-rank256.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-depth-rank256.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-recolor-rank256.safetensors -P ./models/controlnet/\n",
"#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-sketch-rank256.safetensors -P ./models/controlnet/\n",
"\n",
"# Controlnet Preprocessor nodes by Fannovel16\n",
"#!cd custom_nodes && git clone https://github.com/Fannovel16/comfy_controlnet_preprocessors; cd comfy_controlnet_preprocessors && python install.py\n",
"\n",
"\n",
"# GLIGEN\n",
"#!wget -c https://huggingface.co/comfyanonymous/GLIGEN_pruned_safetensors/resolve/main/gligen_sd14_textbox_pruned_fp16.safetensors -P ./models/gligen/\n",
"\n",
"\n",
"# ESRGAN upscale model\n",
"#!wget -c https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P ./models/upscale_models/\n",
"#!wget -c https://huggingface.co/sberbank-ai/Real-ESRGAN/resolve/main/RealESRGAN_x2.pth -P ./models/upscale_models/\n",
"#!wget -c https://huggingface.co/sberbank-ai/Real-ESRGAN/resolve/main/RealESRGAN_x4.pth -P ./models/upscale_models/\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "kkkkkkkkkkkkkkk"
},
"source": [
"### Run ComfyUI with cloudflared (Recommended Way)\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "jjjjjjjjjjjjjj"
},
"outputs": [],
"source": [
"!wget -P ~ https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb\n",
"!dpkg -i ~/cloudflared-linux-amd64.deb\n",
"\n",
"import subprocess\n",
"import threading\n",
"import time\n",
"import socket\n",
"import urllib.request\n",
"\n",
"def iframe_thread(port):\n",
" while True:\n",
" time.sleep(0.5)\n",
" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
" result = sock.connect_ex(('127.0.0.1', port))\n",
" if result == 0:\n",
" break\n",
" sock.close()\n",
" print(\"\\nComfyUI finished loading, trying to launch cloudflared (if it gets stuck here cloudflared is having issues)\\n\")\n",
"\n",
" p = subprocess.Popen([\"cloudflared\", \"tunnel\", \"--url\", \"http://127.0.0.1:{}\".format(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n",
" for line in p.stderr:\n",
" l = line.decode()\n",
" if \"trycloudflare.com \" in l:\n",
" print(\"This is the URL to access ComfyUI:\", l[l.find(\"http\"):], end='')\n",
" #print(l, end='')\n",
"\n",
"\n",
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
"\n",
"!python main.py --dont-print-server"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "kkkkkkkkkkkkkk"
},
"source": [
"### Run ComfyUI with localtunnel\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "jjjjjjjjjjjjj"
},
"outputs": [],
"source": [
"!npm install -g localtunnel\n",
"\n",
"import subprocess\n",
"import threading\n",
"import time\n",
"import socket\n",
"import urllib.request\n",
"\n",
"def iframe_thread(port):\n",
" while True:\n",
" time.sleep(0.5)\n",
" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
" result = sock.connect_ex(('127.0.0.1', port))\n",
" if result == 0:\n",
" break\n",
" sock.close()\n",
" print(\"\\nComfyUI finished loading, trying to launch localtunnel (if it gets stuck here localtunnel is having issues)\\n\")\n",
"\n",
" print(\"The password/enpoint ip for localtunnel is:\", urllib.request.urlopen('https://ipv4.icanhazip.com').read().decode('utf8').strip(\"\\n\"))\n",
" p = subprocess.Popen([\"lt\", \"--port\", \"{}\".format(port)], stdout=subprocess.PIPE)\n",
" for line in p.stdout:\n",
" print(line.decode(), end='')\n",
"\n",
"\n",
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
"\n",
"!python main.py --dont-print-server"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "gggggggggg"
},
"source": [
"### Run ComfyUI with colab iframe (use only in case the previous way with localtunnel doesn't work)\n",
"\n",
"You should see the ui appear in an iframe. If you get a 403 error, it's your firefox settings or an extension that's messing things up.\n",
"\n",
"If you want to open it in another window use the link.\n",
"\n",
"Note that some UI features like live image previews won't work because the colab iframe blocks websockets."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "hhhhhhhhhh"
},
"outputs": [],
"source": [
"import threading\n",
"import time\n",
"import socket\n",
"def iframe_thread(port):\n",
" while True:\n",
" time.sleep(0.5)\n",
" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
" result = sock.connect_ex(('127.0.0.1', port))\n",
" if result == 0:\n",
" break\n",
" sock.close()\n",
" from google.colab import output\n",
" output.serve_kernel_port_as_iframe(port, height=1024)\n",
" print(\"to open it in a window you can open this link here:\")\n",
" output.serve_kernel_port_as_window(port)\n",
"\n",
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
"\n",
"!python main.py --dont-print-server"
]
}
],
"metadata": {
"accelerator": "GPU",
"colab": {
"provenance": []
},
"gpuClass": "standard",
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

846
openapi.yaml Normal file
View File

@@ -0,0 +1,846 @@
openapi: 3.1.0
info:
title: ComfyUI-Manager API
description: |
API for ComfyUI-Manager, a comprehensive management tool for ComfyUI custom nodes, models, and components.
This API enables programmatic access to node management, model downloading, snapshot operations,
and overall system configuration.
version: "3.32.3"
contact:
name: ComfyUI-Manager Maintainers
servers:
- url: '/'
description: Default ComfyUI server
# Common API components
components:
schemas:
Error:
type: object
properties:
error:
type: string
description: Error message
NodePackageMetadata:
type: object
properties:
title:
type: string
description: Display name of the node package
name:
type: string
description: Repository/package name
files:
type: array
items:
type: string
description: Source URLs for the package
description:
type: string
description: Description of the node package functionality
install_type:
type: string
enum: [git, copy, pip]
description: Installation method
version:
type: string
description: Version identifier
id:
type: string
description: Unique identifier for the node package
ui_id:
type: string
description: ID for UI reference
channel:
type: string
description: Source channel
mode:
type: string
description: Source mode
ModelMetadata:
type: object
properties:
name:
type: string
description: Name of the model
type:
type: string
description: Type of model
base:
type: string
description: Base model type
save_path:
type: string
description: Path for saving the model
url:
type: string
description: Download URL
filename:
type: string
description: Target filename
ui_id:
type: string
description: ID for UI reference
SnapshotItem:
type: string
description: Name of the snapshot
QueueStatus:
type: object
properties:
total_count:
type: integer
description: Total number of tasks
done_count:
type: integer
description: Number of completed tasks
in_progress_count:
type: integer
description: Number of tasks in progress
is_processing:
type: boolean
description: Whether the queue is currently processing
securitySchemes:
securityLevel:
type: apiKey
in: header
name: Security-Level
description: Security level for sensitive operations
parameters:
modeParam:
name: mode
in: query
description: Source mode (e.g., "local", "remote")
schema:
type: string
enum: [local, remote, default]
targetParam:
name: target
in: query
description: Target identifier
required: true
schema:
type: string
valueParam:
name: value
in: query
description: New value to set
required: true
schema:
type: string
# API Paths
paths:
# Custom Nodes Endpoints
/customnode/getmappings:
get:
summary: Get node-to-package mappings
description: Provides unified mapping between nodes and node packages
parameters:
- $ref: '#/components/parameters/modeParam'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
additionalProperties:
type: array
items:
type: array
description: Mapping of node packages to node classes
/customnode/fetch_updates:
get:
summary: Check for updates
description: Fetches updates for custom nodes
parameters:
- $ref: '#/components/parameters/modeParam'
responses:
'200':
description: No updates available
'201':
description: Updates found
'400':
description: Error occurred
/customnode/installed:
get:
summary: Get installed custom nodes
description: Returns a list of installed node packages
parameters:
- name: mode
in: query
description: Lists mode, default or imported
schema:
type: string
enum: [default, imported]
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
additionalProperties:
$ref: '#/components/schemas/NodePackageMetadata'
/customnode/getlist:
get:
summary: Get custom node list
description: Provides a list of available custom nodes
parameters:
- $ref: '#/components/parameters/modeParam'
- name: skip_update
in: query
description: Skip update check
schema:
type: boolean
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
properties:
channel:
type: string
node_packs:
type: object
additionalProperties:
$ref: '#/components/schemas/NodePackageMetadata'
/customnode/alternatives:
get:
summary: Get alternative node options
description: Provides alternatives for nodes
parameters:
- $ref: '#/components/parameters/modeParam'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
additionalProperties:
type: object
/customnode/versions/{node_name}:
get:
summary: Get available versions for a node
description: Lists all available versions for a specific node
parameters:
- name: node_name
in: path
required: true
schema:
type: string
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: array
items:
type: object
properties:
version:
type: string
'400':
description: Node not found
/customnode/disabled_versions/{node_name}:
get:
summary: Get disabled versions for a node
description: Lists all disabled versions for a specific node
parameters:
- name: node_name
in: path
required: true
schema:
type: string
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: array
items:
type: object
properties:
version:
type: string
'400':
description: Node not found
/customnode/import_fail_info:
post:
summary: Get import failure information
description: Returns information about why a node failed to import
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
cnr_id:
type: string
url:
type: string
responses:
'200':
description: Successful operation
'400':
description: No information available
/customnode/install/git_url:
post:
summary: Install custom node via Git URL
description: Installs a custom node from a Git repository URL
security:
- securityLevel: []
requestBody:
required: true
content:
text/plain:
schema:
type: string
responses:
'200':
description: Installation successful or already installed
'400':
description: Installation failed
'403':
description: Security policy violation
/customnode/install/pip:
post:
summary: Install custom node dependencies via pip
description: Installs Python package dependencies for custom nodes
security:
- securityLevel: []
requestBody:
required: true
content:
text/plain:
schema:
type: string
responses:
'200':
description: Installation successful
'403':
description: Security policy violation
# Model Management Endpoints
/externalmodel/getlist:
get:
summary: Get external model list
description: Provides a list of available external models
parameters:
- $ref: '#/components/parameters/modeParam'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
properties:
models:
type: array
items:
$ref: '#/components/schemas/ModelMetadata'
# Queue Management Endpoints
/manager/queue/update_all:
get:
summary: Update all custom nodes
description: Queues update operations for all installed custom nodes
security:
- securityLevel: []
parameters:
- $ref: '#/components/parameters/modeParam'
responses:
'200':
description: Update queued successfully
'401':
description: Processing already in progress
'403':
description: Security policy violation
/manager/queue/reset:
get:
summary: Reset queue
description: Resets the operation queue
responses:
'200':
description: Queue reset successfully
/manager/queue/status:
get:
summary: Get queue status
description: Returns the current status of the operation queue
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/QueueStatus'
/manager/queue/install:
post:
summary: Install custom node
description: Queues installation of a custom node
security:
- securityLevel: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NodePackageMetadata'
responses:
'200':
description: Installation queued successfully
'403':
description: Security policy violation
'404':
description: Target node not found or security issue
/manager/queue/start:
get:
summary: Start queue processing
description: Starts processing the operation queue
responses:
'200':
description: Processing started
'201':
description: Processing already in progress
/manager/queue/fix:
post:
summary: Fix custom node
description: Attempts to fix a broken custom node installation
security:
- securityLevel: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NodePackageMetadata'
responses:
'200':
description: Fix operation queued successfully
'403':
description: Security policy violation
/manager/queue/reinstall:
post:
summary: Reinstall custom node
description: Uninstalls and then reinstalls a custom node
security:
- securityLevel: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NodePackageMetadata'
responses:
'200':
description: Reinstall operation queued successfully
'403':
description: Security policy violation
/manager/queue/uninstall:
post:
summary: Uninstall custom node
description: Queues uninstallation of a custom node
security:
- securityLevel: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NodePackageMetadata'
responses:
'200':
description: Uninstallation queued successfully
'403':
description: Security policy violation
/manager/queue/update:
post:
summary: Update custom node
description: Queues update of a custom node
security:
- securityLevel: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NodePackageMetadata'
responses:
'200':
description: Update queued successfully
'403':
description: Security policy violation
/manager/queue/disable:
post:
summary: Disable custom node
description: Disables a custom node without uninstalling it
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NodePackageMetadata'
responses:
'200':
description: Disable operation queued successfully
/manager/queue/update_comfyui:
get:
summary: Update ComfyUI
description: Queues an update operation for ComfyUI itself
responses:
'200':
description: Update queued successfully
/manager/queue/install_model:
post:
summary: Install model
description: Queues installation of a model
security:
- securityLevel: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ModelMetadata'
responses:
'200':
description: Installation queued successfully
'400':
description: Invalid model request
'403':
description: Security policy violation
# Snapshot Management Endpoints
/snapshot/getlist:
get:
summary: Get snapshot list
description: Returns a list of available snapshots
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/SnapshotItem'
/snapshot/remove:
get:
summary: Remove snapshot
description: Removes a specified snapshot
security:
- securityLevel: []
parameters:
- $ref: '#/components/parameters/targetParam'
responses:
'200':
description: Snapshot removed successfully
'400':
description: Error removing snapshot
'403':
description: Security policy violation
/snapshot/restore:
get:
summary: Restore snapshot
description: Restores a specified snapshot
security:
- securityLevel: []
parameters:
- $ref: '#/components/parameters/targetParam'
responses:
'200':
description: Snapshot restoration scheduled
'400':
description: Error restoring snapshot
'403':
description: Security policy violation
/snapshot/get_current:
get:
summary: Get current snapshot
description: Returns the current system state as a snapshot
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
'400':
description: Error creating snapshot
/snapshot/save:
get:
summary: Save snapshot
description: Saves the current system state as a new snapshot
responses:
'200':
description: Snapshot saved successfully
'400':
description: Error saving snapshot
# ComfyUI Management Endpoints
/comfyui_manager/comfyui_versions:
get:
summary: Get ComfyUI versions
description: Returns available and current ComfyUI versions
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: object
properties:
versions:
type: array
items:
type: string
current:
type: string
'400':
description: Error retrieving versions
/comfyui_manager/comfyui_switch_version:
get:
summary: Switch ComfyUI version
description: Switches to a specified ComfyUI version
parameters:
- name: ver
in: query
description: Target version
schema:
type: string
responses:
'200':
description: Version switch successful
'400':
description: Error switching version
/manager/reboot:
get:
summary: Reboot ComfyUI
description: Restarts the ComfyUI server
security:
- securityLevel: []
responses:
'200':
description: Reboot initiated
'403':
description: Security policy violation
# Configuration Endpoints
/manager/preview_method:
get:
summary: Get or set preview method
description: Gets or sets the latent preview method
parameters:
- name: value
in: query
required: false
description: New preview method
schema:
type: string
enum: [auto, latent2rgb, taesd, none]
responses:
'200':
description: Setting updated or current value returned
content:
text/plain:
schema:
type: string
/manager/db_mode:
get:
summary: Get or set database mode
description: Gets or sets the database mode
parameters:
- name: value
in: query
required: false
description: New database mode
schema:
type: string
enum: [channel, local, remote]
responses:
'200':
description: Setting updated or current value returned
content:
text/plain:
schema:
type: string
/manager/policy/component:
get:
summary: Get or set component policy
description: Gets or sets the component policy
parameters:
- name: value
in: query
required: false
description: New component policy
schema:
type: string
responses:
'200':
description: Setting updated or current value returned
content:
text/plain:
schema:
type: string
/manager/policy/update:
get:
summary: Get or set update policy
description: Gets or sets the update policy
parameters:
- name: value
in: query
required: false
description: New update policy
schema:
type: string
enum: [stable, nightly, nightly-comfyui]
responses:
'200':
description: Setting updated or current value returned
content:
text/plain:
schema:
type: string
/manager/channel_url_list:
get:
summary: Get or set channel URL
description: Gets or sets the channel URL for custom node sources
parameters:
- name: value
in: query
required: false
description: New channel name
schema:
type: string
responses:
'200':
description: Setting updated or channel list returned
content:
application/json:
schema:
type: object
properties:
selected:
type: string
list:
type: array
items:
type: object
properties:
name:
type: string
url:
type: string
# Component Management Endpoints
/manager/component/save:
post:
summary: Save component
description: Saves a reusable workflow component
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
workflow:
type: object
responses:
'200':
description: Component saved successfully
content:
text/plain:
schema:
type: string
'400':
description: Error saving component
/manager/component/loads:
post:
summary: Load components
description: Loads all available workflow components
responses:
'200':
description: Components loaded successfully
content:
application/json:
schema:
type: object
'400':
description: Error loading components
# Miscellaneous Endpoints
/manager/version:
get:
summary: Get manager version
description: Returns the current version of ComfyUI-Manager
responses:
'200':
description: Successful operation
content:
text/plain:
schema:
type: string
/manager/notice:
get:
summary: Get manager notice
description: Returns HTML content with notices and version information
responses:
'200':
description: Successful operation
content:
text/html:
schema:
type: string

View File

@@ -12,10 +12,13 @@ import ast
import logging import logging
import traceback import traceback
from .glob import security_check glob_path = os.path.join(os.path.dirname(__file__), "glob")
from .glob import manager_util sys.path.append(glob_path)
from .glob import cm_global
from .glob import manager_downloader import security_check
import manager_util
import cm_global
import manager_downloader
import folder_paths import folder_paths
manager_util.add_python_path_to_env() manager_util.add_python_path_to_env()
@@ -37,8 +40,8 @@ else:
security_check.security_check() security_check.security_check()
cm_global.pip_blacklist = {'torch', 'torchsde', 'torchvision'} cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia'] cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
def skip_pip_spam(x): def skip_pip_spam(x):
@@ -65,17 +68,15 @@ comfy_path = os.environ.get('COMFYUI_PATH')
comfy_base_path = os.environ.get('COMFYUI_FOLDERS_BASE_PATH') comfy_base_path = os.environ.get('COMFYUI_FOLDERS_BASE_PATH')
if comfy_path is None: if comfy_path is None:
try: # legacy env var
comfy_path = os.path.abspath(os.path.dirname(sys.modules['__main__'].__file__)) comfy_path = os.environ.get('COMFYUI_PATH')
os.environ['COMFYUI_PATH'] = comfy_path
except: if comfy_path is None:
print("[ComfyUI-Manager] environment variable 'COMFYUI_PATH' is not specified.") comfy_path = os.path.abspath(os.path.dirname(sys.modules['__main__'].__file__))
exit(-1)
if comfy_base_path is None: if comfy_base_path is None:
comfy_base_path = comfy_path comfy_base_path = comfy_path
sys.__comfyui_manager_register_message_collapse = register_message_collapse sys.__comfyui_manager_register_message_collapse = register_message_collapse
sys.__comfyui_manager_is_import_failed_extension = is_import_failed_extension 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.register_message_collapse', register_message_collapse)
@@ -120,11 +121,17 @@ read_config()
read_uv_mode() read_uv_mode()
check_file_logging() check_file_logging()
cm_global.pip_overrides = {'numpy': 'numpy<2'} if sys.version_info < (3, 13):
cm_global.pip_overrides = {'numpy': 'numpy<2'}
else:
cm_global.pip_overrides = {}
if os.path.exists(manager_pip_overrides_path): if os.path.exists(manager_pip_overrides_path):
with open(manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file: with open(manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
cm_global.pip_overrides = json.load(json_file) cm_global.pip_overrides = json.load(json_file)
cm_global.pip_overrides['numpy'] = 'numpy<2'
if sys.version_info < (3, 13):
cm_global.pip_overrides['numpy'] = 'numpy<2'
if os.path.exists(manager_pip_blacklist_path): if os.path.exists(manager_pip_blacklist_path):
@@ -391,11 +398,7 @@ try:
def emit(self, record): def emit(self, record):
global is_start_mode global is_start_mode
try: message = record.getMessage()
message = record.getMessage()
except Exception as e:
message = f"<<logging error>>: {record} - {e}"
original_stderr.write(message)
if is_start_mode: if is_start_mode:
match = re.search(pat_import_fail, message) match = re.search(pat_import_fail, message)
@@ -623,6 +626,7 @@ def execute_lazy_install_script(repo_path, executable):
lines = manager_util.robust_readlines(requirements_path) lines = manager_util.robust_readlines(requirements_path)
for line in lines: for line in lines:
package_name = remap_pip_package(line.strip()) package_name = remap_pip_package(line.strip())
package_name = package_name.split('#')[0].strip()
if package_name and not is_installed(package_name): if package_name and not is_installed(package_name):
if '--index-url' in package_name: if '--index-url' in package_name:
s = package_name.split('--index-url') s = package_name.split('--index-url')

View File

@@ -1,62 +1,15 @@
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
[project] [project]
name = "comfyui-manager" name = "comfyui-manager"
license = { text = "GPL-3.0-only" }
version = "4.0.0-beta.1"
requires-python = ">= 3.9"
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."
readme = "README.md" version = "3.32.3"
keywords = ["comfyui", "comfyui-manager"] license = { file = "LICENSE.txt" }
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
maintainers = [
{ name = "Dr.Lt.Data", email = "dr.lt.data@gmail.com" },
{ name = "Yoland Yan", email = "yoland@drip.art" },
{ name = "James Kwon", email = "hongilkwon316@gmail.com" },
{ name = "Robin Huang", email = "robin@drip.art" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
]
dependencies = [
"GitPython",
"PyGithub",
"matrix-client==0.4.0",
"transformers",
"huggingface-hub>0.20",
"typer",
"rich",
"typing-extensions",
"toml",
"uv",
"chardet"
]
[project.optional-dependencies]
dev = ["pre-commit", "pytest", "ruff", "pytest-cov"]
[project.urls] [project.urls]
Repository = "https://github.com/ltdrdata/ComfyUI-Manager" Repository = "https://github.com/ltdrdata/ComfyUI-Manager"
# Used by Comfy Registry https://comfyregistry.org
[tool.setuptools.packages.find] [tool.comfy]
where = ["."] PublisherId = "drltdata"
include = ["comfyui_manager*"] DisplayName = "ComfyUI-Manager"
Icon = ""
[tool.ruff]
line-length = 120
target-version = "py39"
[tool.ruff.lint]
select = [
"E4", # default
"E7", # default
"E9", # default
"F", # default
"I", # isort-like behavior (import statement sorting)
]

View File

@@ -0,0 +1,39 @@
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)

View File

@@ -0,0 +1,21 @@
git clone https://github.com/comfyanonymous/ComfyUI
cd ComfyUI/custom_nodes
git clone https://github.com/ltdrdata/ComfyUI-Manager 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

View File

@@ -0,0 +1,17 @@
git clone https://github.com/comfyanonymous/ComfyUI
cd ComfyUI/custom_nodes
git clone https://github.com/ltdrdata/ComfyUI-Manager 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

View File

@@ -0,0 +1,3 @@
.\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')"
.\python_embeded\python.exe -m pip install -r ./ComfyUI/custom_nodes/comfyui-manager/requirements.txt

View File