Compare commits

..

2 Commits

Author SHA1 Message Date
Dr.Lt.Data
338d7abfa8 more detailed message 2025-03-03 01:04:39 +09:00
Dr.Lt.Data
f8e5f5bcbb print dbg message 2025-02-14 07:36:02 +09:00
48 changed files with 11590 additions and 53205 deletions

View File

@@ -7,19 +7,15 @@ on:
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ltdrdata' }}
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@v1
uses: Comfy-Org/publish-node-action@main
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}

View File

@@ -8,7 +8,7 @@
* V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`.
* V3.10: `double-click feature` is removed
* This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper
* V3.3.2: Overhauled. Officially supports [https://registry.comfy.org/](https://registry.comfy.org/).
* V3.3.2: Overhauled. Officially supports [https://comfyregistry.org/](https://comfyregistry.org/).
* You can see whole nodes info on [ComfyUI Nodes Info](https://ltdrdata.github.io/) page.
## Installation
@@ -149,8 +149,6 @@ In `ComfyUI-Manager` V3.0 and later, configuration files and dynamically generat
* Basic config files: `<USER_DIRECTORY>/default/ComfyUI-Manager/config.ini`
* Configurable channel lists: `<USER_DIRECTORY>/default/ComfyUI-Manager/channels.ini`
* Configurable pip overrides: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_overrides.json`
* Configurable pip blacklist: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_blacklist.list`
* Configurable pip auto fix: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_auto_fix.list`
* Saved snapshot files: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
* Startup script files: `<USER_DIRECTORY>/default/ComfyUI-Manager/startup-scripts`
* Component files: `<USER_DIRECTORY>/default/ComfyUI-Manager/components`
@@ -303,40 +301,13 @@ The following settings are applied based on the section marked as `is_default`.
* Custom pip mapping
* When you create the `pip_overrides.json` file, it changes the installation of specific pip packages to installations defined by the user.
* Please refer to the `pip_overrides.json.template` file.
* Prevent the installation of specific pip packages
* List the package names one per line in the `pip_blacklist.list` file.
* Automatically Restoring pip Installation
* If you list pip spec requirements in `pip_auto_fix.list`, similar to `requirements.txt`, it will automatically restore the specified versions when starting ComfyUI or when versions get mismatched during various custom node installations.
* `--index-url` can be used.
* Use `aria2` as downloader
* [howto](docs/en/use_aria2.md)
* If you add the item `skip_migration_check = True` to `config.ini`, it will not check whether there are nodes that can be migrated at startup.
* This option can be used if performance issues occur in a Colab+GDrive environment.
## Environment Variables
The following features can be configured using environment variables:
* **COMFYUI_PATH**: The installation path of ComfyUI
* **GITHUB_ENDPOINT**: Reverse proxy configuration for environments with limited access to GitHub
* **HF_ENDPOINT**: Reverse proxy configuration for environments with limited access to Hugging Face
### Example 1:
Redirecting `https://github.com/ltdrdata/ComfyUI-Impact-Pack` to `https://mirror.ghproxy.com/https://github.com/ltdrdata/ComfyUI-Impact-Pack`
```
GITHUB_ENDPOINT=https://mirror.ghproxy.com/https://github.com
```
#### Example 2:
Changing `https://huggingface.co/path/to/somewhere` to `https://some-hf-mirror.com/path/to/somewhere`
```
HF_ENDPOINT=https://some-hf-mirror.com
```
## Scanner
When you run the `scan.sh` script:

View File

@@ -1,20 +1,15 @@
"""
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')
print("[DBG] point14")
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"
WEB_DIRECTORY = "js"
else:
print("\n[ComfyUI-Manager] !! cli-only-mode is enabled !!\n")

124
cm-cli.py
View File

@@ -32,7 +32,6 @@ if comfy_path is None:
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)
import utils.extra_config
@@ -43,39 +42,23 @@ import cnr_utils
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
if sys.version_info < (3, 13):
cm_global.pip_overrides = {'numpy': 'numpy<2'}
else:
cm_global.pip_overrides = {}
cm_global.pip_blacklist = ['torch', 'torchsde', 'torchvision']
cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
cm_global.pip_overrides = {'numpy': 'numpy<2'}
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:
cm_global.pip_overrides = json.load(json_file)
if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_blacklist.list")):
with open(os.path.join(manager_util.comfyui_manager_path, "pip_blacklist.list"), 'r', encoding="UTF-8", errors="ignore") as f:
for x in f.readlines():
y = x.strip()
if y != '':
cm_global.pip_blacklist.add(y)
def check_comfyui_hash():
try:
repo = git.Repo(comfy_path)
core.comfy_ui_revision = len(list(repo.iter_commits('HEAD')))
core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime
except:
print('[bold yellow]INFO: Frozen ComfyUI mode.[/bold yellow]')
core.comfy_ui_revision = 0
core.comfy_ui_commit_datetime = 0
repo = git.Repo(comfy_path)
core.comfy_ui_revision = len(list(repo.iter_commits('HEAD')))
cm_global.variables['comfyui.revision'] = core.comfy_ui_revision
core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime
check_comfyui_hash() # This is a preparation step for manager_core
core.check_invalid_nodes()
@@ -84,7 +67,7 @@ core.check_invalid_nodes()
def read_downgrade_blacklist():
try:
import configparser
config = configparser.ConfigParser(strict=False)
config = configparser.ConfigParser()
config.read(core.manager_config.path)
default_conf = config['default']
@@ -151,21 +134,7 @@ class Ctx:
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:
cm_global.pip_overrides = json.load(json_file)
if sys.version_info < (3, 13):
cm_global.pip_overrides = {'numpy': 'numpy<2'}
if os.path.exists(core.manager_pip_blacklist_path):
with open(core.manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:
for x in f.readlines():
y = x.strip()
if y != '':
cm_global.pip_blacklist.add(y)
def update_custom_nodes_dir(self, target_dir):
import folder_paths
a, b = folder_paths.folder_names_and_paths['custom_nodes']
folder_paths.folder_names_and_paths['custom_nodes'] = [os.path.abspath(target_dir)], set()
cm_global.pip_overrides = {'numpy': 'numpy<2'}
@staticmethod
def get_startup_scripts_path():
@@ -190,18 +159,13 @@ class Ctx:
cmd_ctx = Ctx()
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}...")
def install_node(node_spec_str, is_all=False, cnt_msg=''):
if core.is_valid_url(node_spec_str):
# install via urls
res = asyncio.run(core.gitclone_install(node_spec_str, no_deps=cmd_ctx.no_deps))
if not res.result:
print(res.msg)
print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]")
if exit_on_fail:
sys.exit(1)
else:
print(f"{cnt_msg} [INSTALLED] {node_spec_str:50}")
else:
@@ -236,8 +200,6 @@ def install_node(node_spec_str, is_all=False, cnt_msg='', **kwargs):
print("")
else:
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=''):
@@ -267,7 +229,7 @@ def fix_node(node_spec_str, is_all=False, cnt_msg=''):
res = unified_manager.unified_fix(node_name, version_spec, no_deps=cmd_ctx.no_deps)
if not res.result:
print(f"[bold red]ERROR: f{res.msg}[/bold red]")
print(f"ERROR: f{res.msg}")
def uninstall_node(node_spec_str: str, is_all: bool = False, cnt_msg: str = ''):
@@ -599,7 +561,7 @@ def get_all_installed_node_specs():
return res
def for_each_nodes(nodes, act, allow_all=True, **kwargs):
def for_each_nodes(nodes, act, allow_all=True):
is_all = False
if allow_all and 'all' in nodes:
is_all = True
@@ -611,7 +573,7 @@ def for_each_nodes(nodes, act, allow_all=True, **kwargs):
i = 1
for x in nodes:
try:
act(x, is_all=is_all, cnt_msg=f'{i}/{total}', **kwargs)
act(x, is_all=is_all, cnt_msg=f'{i}/{total}')
except Exception as e:
print(f"ERROR: {e}")
traceback.print_exc()
@@ -655,17 +617,13 @@ def install(
None,
help="user directory"
),
exit_on_fail: bool = typer.Option(
False,
help="Exit on failure"
)
):
cmd_ctx.set_user_directory(user_directory)
cmd_ctx.set_channel_mode(channel, mode)
cmd_ctx.set_no_deps(no_deps)
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, core.manager_files_path)
for_each_nodes(nodes, act=install_node, exit_on_fail=exit_on_fail)
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages())
for_each_nodes(nodes, act=install_node)
pip_fixer.fix_broken()
@@ -702,7 +660,7 @@ def reinstall(
cmd_ctx.set_channel_mode(channel, mode)
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())
for_each_nodes(nodes, act=reinstall_node)
pip_fixer.fix_broken()
@@ -728,7 +686,7 @@ def uninstall(
for_each_nodes(nodes, act=uninstall_node)
@app.command(help="Update custom nodes")
@app.command(help="Disable custom nodes")
def update(
nodes: List[str] = typer.Argument(
...,
@@ -756,7 +714,7 @@ def update(
if 'all' in nodes:
asyncio.run(auto_save_snapshot())
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())
for x in nodes:
if x.lower() in ['comfyui', 'comfy', 'all']:
@@ -857,7 +815,7 @@ def fix(
if 'all' in nodes:
asyncio.run(auto_save_snapshot())
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())
for_each_nodes(nodes, fix_node, allow_all=True)
pip_fixer.fix_broken()
@@ -1063,19 +1021,7 @@ def save_snapshot(
] = True,
):
cmd_ctx.set_user_directory(user_directory)
if output is not None:
if(not output.endswith('.json') and not output.endswith('.yaml')):
print("[bold red]ERROR: output path should be either '.json' or '.yaml' file.[/bold red]")
raise typer.Exit(code=1)
dir_path = os.path.dirname(output)
if(dir_path != '' and not os.path.exists(dir_path)):
print(f"[bold red]ERROR: {output} path not exists.[/bold red]")
raise typer.Exit(code=1)
path = asyncio.run(core.save_snapshot_with_postfix('snapshot', output, not full_snapshot))
path = core.save_snapshot_with_postfix('snapshot', output, not full_snapshot)
print(f"Current snapshot is saved as `{path}`")
@@ -1103,17 +1049,10 @@ def restore_snapshot(
user_directory: str = typer.Option(
None,
help="user directory"
),
restore_to: Optional[str] = typer.Option(
None,
help="Manually specify the installation path for the custom node. Ignore user directory."
)
):
cmd_ctx.set_user_directory(user_directory)
if restore_to:
cmd_ctx.update_custom_nodes_dir(restore_to)
extras = []
if pip_non_url:
extras.append('--pip-non-url')
@@ -1134,7 +1073,7 @@ def restore_snapshot(
print(f"[bold red]ERROR: `{snapshot_path}` is not exists.[/bold red]")
exit(1)
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())
try:
asyncio.run(core.restore_snapshot(snapshot_path, extras))
except Exception:
@@ -1166,7 +1105,7 @@ def restore_dependencies(
total = len(node_paths)
i = 1
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())
for x in node_paths:
print("----------------------------------------------------------------------------------------------------")
print(f"Restoring [{i}/{total}]: {x}")
@@ -1185,7 +1124,7 @@ def post_install(
):
path = os.path.expanduser(path)
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())
unified_manager.execute_install_script('', path, instant_execution=True)
pip_fixer.fix_broken()
@@ -1229,7 +1168,8 @@ def install_deps(
print(f"[bold red]Invalid json file: {deps}[/bold red]")
exit(1)
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())
for k in json_obj['custom_nodes'].keys():
state = core.simple_check_custom_node(k)
if state == 'installed':
@@ -1286,6 +1226,20 @@ def export_custom_node_ids(
print(f"{x['id']}@unknown", file=output_file)
@app.command(
"migrate",
help="Migrate legacy node system to new node system",
)
def migrate(
user_directory: str = typer.Option(
None,
help="user directory"
)
):
cmd_ctx.set_user_directory(user_directory)
asyncio.run(unified_manager.migrate_unmanaged_nodes())
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(app())

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +0,0 @@
# ComfyUI-Manager: Documentation
This directory contains documentation for the ComfyUI-Manager, providing guides and tutorials for users in multiple languages.
## Directory Structure
The documentation is organized into language-specific directories:
- **en/**: English documentation
- **ko/**: Korean documentation
## Core Documentation Files
### Command-Line Interface
- **cm-cli.md**: Documentation for the ComfyUI-Manager Command Line Interface (CLI), which allows using manager functionality without the UI.
### Advanced Features
- **use_aria2.md**: Guide for using the aria2 download accelerator with ComfyUI-Manager for faster model downloads.
## Documentation Standards
The documentation follows these standards:
1. **Markdown Format**: All documentation is written in Markdown for easy rendering on GitHub and other platforms
2. **Language-specific Directories**: Content is separated by language to facilitate localization
3. **Feature-focused Documentation**: Each major feature has its own documentation file
4. **Updated with Releases**: Documentation is kept in sync with software releases
## Contributing to Documentation
When contributing new documentation:
1. Place files in the appropriate language directory
2. Use clear, concise language appropriate for the target audience
3. Include examples where helpful
4. Consider adding screenshots or diagrams for complex features
5. Maintain consistent formatting with existing documentation
This documentation directory will continue to grow to support the expanding feature set of ComfyUI-Manager.

View File

@@ -121,9 +121,8 @@ ComfyUI-Loopchain
* If no file exists at the snapshot path, it is implicitly assumed to be in ComfyUI-Manager/snapshots.
* `--pip-non-url`: Restore for pip packages registered on PyPI.
* `--pip-non-local-url`: Restore for pip packages registered at web URLs.
* `--pip-local-url`: Restore for pip packages specified by local paths.
* `--user-directory`: Set the user directory.
* `--restore-to`: The path where the restored custom nodes will be installed. (When this option is applied, only the custom nodes installed in the target path are recognized as installed.)
* `--pip-local-url`: Restore for pip packages specified by local paths.
### 5. CLI Only Mode

View File

@@ -123,8 +123,7 @@ ComfyUI-Loopchain
* `--pip-non-url`: PyPI 에 등록된 pip 패키지들에 대해서 복구를 수행
* `--pip-non-local-url`: web URL에 등록된 pip 패키지들에 대해서 복구를 수행
* `--pip-local-url`: local 경로를 지정하고 있는 pip 패키지들에 대해서 복구를 수행
* `--user-directory`: 사용자 디렉토리 설정
* `--restore-to`: 복구될 커스텀 노드가 설치될 경로. (이 옵션을 적용할 경우 오직 대상 경로에 설치된 custom nodes 만 설치된 것으로 인식함.)
### 5. CLI only mode

View File

File diff suppressed because it is too large Load Diff

View File

@@ -154,27 +154,14 @@ def switch_to_default_branch(repo):
repo.git.checkout(default_branch)
return True
except:
# try checkout master
# try checkout main if failed
try:
repo.git.checkout(repo.heads.master)
return True
except:
try:
if remote_name is not None:
repo.git.checkout('-b', 'master', f'{remote_name}/master')
return True
except:
try:
repo.git.checkout(repo.heads.main)
return True
except:
try:
if remote_name is not None:
repo.git.checkout('-b', 'main', f'{remote_name}/main')
return True
except:
pass
pass
print("[ComfyUI Manager] Failed to switch to the default branch")
return False

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,53 +0,0 @@
# ComfyUI-Manager: Core Backend (glob)
This directory contains the Python backend modules that power ComfyUI-Manager, handling the core functionality of node management, downloading, security, and server operations.
## Core Modules
- **manager_core.py**: The central implementation of management functions, handling configuration, installation, updates, and node management.
- **manager_server.py**: Implements server functionality and API endpoints for the web interface to interact with the backend.
- **manager_downloader.py**: Handles downloading operations for models, extensions, and other resources.
- **manager_util.py**: Provides utility functions used throughout the system.
## Specialized Modules
- **cm_global.py**: Maintains global variables and state management across the system.
- **cnr_utils.py**: Helper utilities for interacting with the custom node registry (CNR).
- **git_utils.py**: Git-specific utilities for repository operations.
- **node_package.py**: Handles the packaging and installation of node extensions.
- **security_check.py**: Implements the multi-level security system for installation safety.
- **share_3rdparty.py**: Manages integration with third-party sharing platforms.
## Architecture
The backend follows a modular design pattern with clear separation of concerns:
1. **Core Layer**: Manager modules provide the primary API and business logic
2. **Utility Layer**: Helper modules provide specialized functionality
3. **Integration Layer**: Modules that connect to external systems
## Security Model
The system implements a comprehensive security framework with multiple levels:
- **Block**: Highest security - blocks most remote operations
- **High**: Allows only specific trusted operations
- **Middle**: Standard security for most users
- **Normal-**: More permissive for advanced users
- **Weak**: Lowest security for development environments
## Implementation Details
- The backend is designed to work seamlessly with ComfyUI
- Asynchronous task queuing is implemented for background operations
- The system supports multiple installation modes
- Error handling and risk assessment are integrated throughout the codebase
## API Integration
The backend exposes a REST API via `manager_server.py` that enables:
- Custom node management (install, update, disable, remove)
- Model downloading and organization
- System configuration
- Snapshot management
- Workflow component handling

View File

@@ -112,6 +112,4 @@ def add_on_revision_detected(k, f):
variables['cm.on_revision_detected_handler'].append((k, f))
error_dict = {}
disable_front = False
error_dict = {}

View File

@@ -1,15 +1,12 @@
import asyncio
import json
import os
import platform
import time
import requests
from dataclasses import dataclass
from typing import List
import manager_core
import manager_util
import requests
import toml
import os
import asyncio
import json
import time
base_url = "https://api.comfy.org"
@@ -35,43 +32,9 @@ async def _get_cnr_data(cache_mode=True, dont_wait=True):
page = 1
full_nodes = {}
# Determine form factor based on environment and platform
is_desktop = bool(os.environ.get('__COMFYUI_DESKTOP_VERSION__'))
system = platform.system().lower()
is_windows = system == 'windows'
is_mac = system == 'darwin'
is_linux = system == 'linux'
# Get ComfyUI version tag
if is_desktop:
# extract version from pyproject.toml instead of git tag
comfyui_ver = manager_core.get_current_comfyui_ver() or 'unknown'
else:
comfyui_ver = manager_core.get_comfyui_tag() or 'unknown'
if is_desktop:
if is_windows:
form_factor = 'desktop-win'
elif is_mac:
form_factor = 'desktop-mac'
else:
form_factor = 'other'
else:
if is_windows:
form_factor = 'git-windows'
elif is_mac:
form_factor = 'git-mac'
elif is_linux:
form_factor = 'git-linux'
else:
form_factor = 'other'
while remained:
# Add comfyui_version and form_factor to the API request
sub_uri = f'{base_url}/nodes?page={page}&limit=30&comfyui_version={comfyui_ver}&form_factor={form_factor}'
sub_json_obj = await asyncio.wait_for(manager_util.get_data_with_cache(sub_uri, cache_mode=False, silent=True, dont_cache=True), timeout=30)
sub_uri = f'{base_url}/nodes?page={page}&limit=30'
sub_json_obj = await asyncio.wait_for(manager_util.get_data_with_cache(sub_uri, cache_mode=False, silent=True), timeout=30)
remained = page < sub_json_obj['totalPages']
for x in sub_json_obj['nodes']:
@@ -210,10 +173,7 @@ def read_cnr_info(fullpath):
project = data.get('project', {})
name = project.get('name').strip().lower()
# normalize version
# for example: 2.5 -> 2.5.0
version = str(manager_util.StrictVersion(project.get('version')))
version = project.get('version')
urls = project.get('urls', {})
repository = urls.get('Repository')

View File

@@ -2,9 +2,6 @@ import os
import configparser
GITHUB_ENDPOINT = os.getenv('GITHUB_ENDPOINT')
def is_git_repo(path: str) -> bool:
""" Check if the path is a git repository. """
# NOTE: Checking it through `git.Repo` must be avoided.
@@ -40,48 +37,25 @@ def git_url(fullpath):
if not os.path.exists(git_config_path):
return None
# Set `strict=False` to allow duplicate `vscode-merge-base` sections, addressing <https://github.com/ltdrdata/ComfyUI-Manager/issues/1529>
config = configparser.ConfigParser(strict=False)
config = configparser.ConfigParser()
config.read(git_config_path)
for k, v in config.items():
if k.startswith('remote ') and 'url' in v:
if 'Comfy-Org/ComfyUI-Manager' in v['url']:
return "https://github.com/ltdrdata/ComfyUI-Manager"
return v['url']
return None
def normalize_url(url) -> str:
github_id = normalize_to_github_id(url)
if github_id is not None:
url = f"https://github.com/{github_id}"
url = url.replace("git@github.com:", "https://github.com/")
if url.endswith('.git'):
url = url[:-4]
return url
def normalize_url_http(url) -> str:
url = url.replace("https://github.com/", "git@github.com:")
if url.endswith('.git'):
url = url[:-4]
def normalize_to_github_id(url) -> str:
if 'github' in url or (GITHUB_ENDPOINT is not None and GITHUB_ENDPOINT in url):
author = os.path.basename(os.path.dirname(url))
if author.startswith('git@github.com:'):
author = author.split(':')[1]
repo_name = os.path.basename(url)
if repo_name.endswith('.git'):
repo_name = repo_name[:-4]
return f"{author}/{repo_name}"
return None
def get_url_for_clone(url):
url = normalize_url(url)
if GITHUB_ENDPOINT is not None and url.startswith('https://github.com/'):
url = GITHUB_ENDPOINT + url[18:] # url[18:] -> remove `https://github.com`
return url
return url

View File

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,6 @@ from tqdm.auto import tqdm
aria2 = os.getenv('COMFYUI_MANAGER_ARIA2_SERVER')
HF_ENDPOINT = os.getenv('HF_ENDPOINT')
if aria2 is not None:
secret = os.getenv('COMFYUI_MANAGER_ARIA2_SECRET')
url = urlparse(aria2)

View File

@@ -55,14 +55,8 @@ def handle_stream(stream, prefix):
from comfy.cli_args import args
import latent_preview
def is_loopback(address):
import ipaddress
try:
return ipaddress.ip_address(address).is_loopback
except ValueError:
return False
is_local_mode = is_loopback(args.listen)
is_local_mode = args.listen.startswith('127.') or args.listen.startswith('local.')
model_dir_name_map = {
@@ -91,11 +85,11 @@ def is_allowed_security_level(level):
return False
elif level == 'high':
if is_local_mode:
return core.get_config()['security_level'] in ['weak', 'normal-']
return core.get_config()['security_level'].lower() in ['weak', 'normal-']
else:
return core.get_config()['security_level'] == 'weak'
return core.get_config()['security_level'].lower() == 'weak'
elif level == 'middle':
return core.get_config()['security_level'] in ['weak', 'normal', 'normal-']
return core.get_config()['security_level'].lower() in ['weak', 'normal', 'normal-']
else:
return True
@@ -181,20 +175,12 @@ def set_preview_method(method):
core.get_config()['preview_method'] = method
if args.preview_method == latent_preview.LatentPreviewMethod.NoPreviews:
set_preview_method(core.get_config()['preview_method'])
else:
logging.warning("[ComfyUI-Manager] Since --preview-method is set, ComfyUI-Manager's preview method feature will be ignored.")
set_preview_method(core.get_config()['preview_method'])
def set_component_policy(mode):
core.get_config()['component_policy'] = mode
def set_update_policy(mode):
core.get_config()['update_policy'] = mode
def set_db_mode(mode):
core.get_config()['db_mode'] = mode
def print_comfyui_version():
global comfy_ui_hash
@@ -217,7 +203,7 @@ def print_comfyui_version():
comfyui_tag = core.get_comfyui_tag()
try:
if not os.environ.get('__COMFYUI_DESKTOP_VERSION__') and core.comfy_ui_commit_datetime.date() < core.comfy_ui_required_commit_datetime.date():
if core.comfy_ui_commit_datetime.date() < core.comfy_ui_required_commit_datetime.date():
logging.warning(f"\n\n## [WARN] ComfyUI-Manager: Your ComfyUI version ({core.comfy_ui_revision})[{core.comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version. ##\n\n")
except:
pass
@@ -282,17 +268,8 @@ def get_model_dir(data, show_log=False):
else:
models_base = folder_paths.models_dir
# NOTE: Validate to prevent path traversal.
if any(char in data['filename'] for char in {'/', '\\', ':'}):
return None
def resolve_custom_node(save_path):
save_path = save_path[13:] # remove 'custom_nodes/'
# NOTE: Validate to prevent path traversal.
if save_path.startswith(os.path.sep) or ':' in save_path:
return None
repo_name = save_path.replace('\\','/').split('/')[0] # get custom node repo name
# NOTE: The creation of files within the custom node path should be removed in the future.
@@ -411,6 +388,7 @@ async def task_worker():
try:
node_spec = core.unified_manager.resolve_node_spec(node_spec_str)
if node_spec is None:
logging.error(f"Cannot resolve install target: '{node_spec_str}'")
return f"Cannot resolve install target: '{node_spec_str}'"
@@ -432,68 +410,40 @@ async def task_worker():
traceback.print_exc()
return f"Installation failed:\n{node_spec_str}"
async def do_update(item):
async def do_update(item) -> str:
ui_id, node_name, node_ver = item
try:
res = core.unified_manager.unified_update(node_name, node_ver)
if res.ver == 'unknown':
url = core.unified_manager.unknown_active_nodes[node_name][0]
try:
title = os.path.basename(url)
except Exception:
title = node_name
else:
url = core.unified_manager.cnr_map[node_name].get('repository')
title = core.unified_manager.cnr_map[node_name]['name']
manager_util.clear_pip_cache()
if url is not None:
base_res = {'url': url, 'title': title}
else:
base_res = {'title': title}
if res.result:
if res.action == 'skip':
base_res['msg'] = 'skip'
return base_res
return 'skip'
else:
base_res['msg'] = 'success'
return base_res
return 'success'
base_res['msg'] = f"An error occurred while updating '{node_name}'."
logging.error(f"\nERROR: An error occurred while updating '{node_name}'. (res.result={res.result}, res.action={res.action})")
return base_res
logging.error(f"\nERROR: An error occurred while updating '{node_name}'.")
except Exception:
traceback.print_exc()
return {'msg':f"An error occurred while updating '{node_name}'."}
return f"An error occurred while updating '{node_name}'."
async def do_update_comfyui(is_stable) -> str:
async def do_update_comfyui() -> str:
try:
repo_path = os.path.dirname(folder_paths.__file__)
latest_tag = None
if is_stable:
res, latest_tag = core.update_to_stable_comfyui(repo_path)
else:
res = core.update_path(repo_path)
res = core.update_path(repo_path)
if res == "fail":
logging.error("ComfyUI update failed")
return "fail"
logging.error("ComfyUI update fail: The installed ComfyUI does not have a Git repository.")
return "The installed ComfyUI does not have a Git repository."
elif res == "updated":
if is_stable:
logging.info("ComfyUI is updated to latest stable version.")
return "success-stable-"+latest_tag
else:
logging.info("ComfyUI is updated to latest nightly version.")
return "success-nightly"
logging.info("ComfyUI is updated.")
return "success"
else: # skipped
logging.info("ComfyUI is up-to-date.")
return "skip"
except Exception:
traceback.print_exc()
@@ -625,7 +575,7 @@ async def task_worker():
elif kind == 'update-main':
msg = await do_update(item)
elif kind == 'update-comfyui':
msg = await do_update_comfyui(item[1])
msg = await do_update_comfyui()
elif kind == 'fix':
msg = await do_fix(item)
elif kind == 'uninstall':
@@ -649,11 +599,7 @@ async def task_worker():
nodepack_result[ui_id] = msg
ui_target = "main"
elif kind == 'update-comfyui':
nodepack_result['comfyui'] = msg
ui_target = "main"
elif kind == 'update':
nodepack_result[ui_id] = msg['msg']
ui_target = "nodepack_manager"
else:
nodepack_result[ui_id] = msg
ui_target = "nodepack_manager"
@@ -758,15 +704,6 @@ async def update_all(request):
update_item = k, k, v[0]
task_queue.put(("update-main", update_item))
for k, v in core.unified_manager.unknown_active_nodes.items():
if k == 'comfyui-manager':
# skip updating comfyui-manager if desktop version
if os.environ.get('__COMFYUI_DESKTOP_VERSION__'):
continue
update_item = k, k, 'unknown'
task_queue.put(("update-main", update_item))
return web.Response(status=200)
@@ -833,7 +770,7 @@ async def fetch_customnode_list(request):
"""
provide unified custom node list
"""
if request.rel_url.query.get("skip_update", '').lower() == "true":
if "skip_update" in request.rel_url.query and request.rel_url.query["skip_update"] == "true":
skip_update = True
else:
skip_update = False
@@ -850,7 +787,7 @@ async def fetch_customnode_list(request):
core.populate_github_stats(node_packs, await json_obj_github)
core.populate_favorites(node_packs, await json_obj_extras)
check_state_of_git_node_pack(node_packs, not skip_update, do_update_check=not skip_update)
check_state_of_git_node_pack(node_packs, False, do_update_check=not skip_update)
for v in node_packs.values():
populate_markdown(v)
@@ -865,7 +802,7 @@ async def fetch_customnode_list(request):
channel = found
result = dict(channel=channel, node_packs=node_packs.to_dict())
result = dict(channel=channel, node_packs=node_packs)
return web.json_response(result, content_type='application/json')
@@ -943,7 +880,6 @@ def check_model_installed(json_obj):
@routes.get("/externalmodel/getlist")
async def fetch_externalmodel_list(request):
# 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')
check_model_installed(json_obj)
@@ -1212,15 +1148,8 @@ async def install_custom_node(request):
git_url = None
selected_version = json_data.get('selected_version')
if json_data['version'] != 'unknown' and selected_version != 'unknown':
if skip_post_install:
if cnr_id in core.unified_manager.nightly_inactive_nodes or cnr_id in core.unified_manager.cnr_inactive_nodes:
core.unified_manager.unified_enable(cnr_id)
return web.Response(status=200)
elif selected_version is None:
selected_version = 'latest'
if json_data['version'] != 'unknown':
selected_version = json_data.get('selected_version', 'latest')
if selected_version != 'nightly':
risky_level = 'low'
node_spec_str = f"{cnr_id}@{selected_version}"
@@ -1230,9 +1159,6 @@ async def install_custom_node(request):
if git_url is None:
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}")
elif json_data['version'] != 'unknown' and selected_version == 'unknown':
logging.error(f"[ComfyUI-Manager] Invalid installation request: {json_data}")
return web.Response(status=400, text="Invalid installation request")
else:
# unknown
unknown_name = os.path.basename(json_data['files'][0])
@@ -1376,15 +1302,14 @@ async def update_custom_node(request):
@routes.get("/manager/queue/update_comfyui")
async def update_comfyui(request):
is_stable = core.get_config()['update_policy'] != 'nightly-comfyui'
task_queue.put(("update-comfyui", ('comfyui', is_stable)))
task_queue.put(("update-comfyui", ('comfyui',)))
return web.Response(status=200)
@routes.get("/comfyui_manager/comfyui_versions")
async def comfyui_versions(request):
try:
res, current, latest = core.get_comfyui_versions()
res, current = core.get_comfyui_versions()
return web.json_response({'versions': res, 'current': current}, status=200, content_type='application/json')
except Exception as e:
logging.error(f"ComfyUI update fail: {e}", file=sys.stderr)
@@ -1424,20 +1349,17 @@ async def disable_node(request):
return web.Response(status=200)
async def check_whitelist_for_model(item):
json_obj = await core.get_data_by_mode('cache', 'model-list.json')
@routes.get("/manager/migrate_unmanaged_nodes")
async def migrate_unmanaged_nodes(request):
logging.info("[ComfyUI-Manager] Migrating unmanaged nodes...")
await core.unified_manager.migrate_unmanaged_nodes()
logging.info("Done.")
return web.Response(status=200)
for x in json_obj.get('models', []):
if x['save_path'] == item['save_path'] and x['base'] == item['base'] and x['filename'] == item['filename']:
return True
json_obj = await core.get_data_by_mode('local', 'model-list.json')
for x in json_obj.get('models', []):
if x['save_path'] == item['save_path'] and x['base'] == item['base'] and x['filename'] == item['filename']:
return True
return False
@routes.get("/manager/need_to_migrate")
async def need_to_migrate(request):
return web.Response(text=str(core.need_to_migrate), status=200)
@routes.post("/manager/queue/install_model")
@@ -1448,11 +1370,6 @@ async def install_model(request):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
# validate request
if not await check_whitelist_for_model(json_data):
logging.error(f"[ComfyUI-Manager] Invalid model install request is detected: {json_data}")
return web.Response(status=400, text="Invalid model install request is detected")
if not json_data['filename'].endswith('.safetensors') and not is_allowed_security_level('high'):
models_json = await core.get_data_by_mode('cache', 'model-list.json', 'default')
@@ -1483,19 +1400,7 @@ async def preview_method(request):
return web.Response(status=200)
@routes.get("/manager/db_mode")
async def db_mode(request):
if "value" in request.rel_url.query:
set_db_mode(request.rel_url.query['value'])
core.write_config()
else:
return web.Response(text=core.get_config()['db_mode'], status=200)
return web.Response(status=200)
@routes.get("/manager/policy/component")
@routes.get("/manager/component/policy")
async def component_policy(request):
if "value" in request.rel_url.query:
set_component_policy(request.rel_url.query['value'])
@@ -1506,17 +1411,6 @@ async def component_policy(request):
return web.Response(status=200)
@routes.get("/manager/policy/update")
async def update_policy(request):
if "value" in request.rel_url.query:
set_update_policy(request.rel_url.query['value'])
core.write_config()
else:
return web.Response(text=core.get_config()['update_policy'], status=200)
return web.Response(status=200)
@routes.get("/manager/channel_url_list")
async def channel_url_list(request):
channels = core.get_channel_dict()
@@ -1570,27 +1464,22 @@ async def get_notice(request):
if match:
markdown_content = match.group(1)
version_tag = os.environ.get('__COMFYUI_DESKTOP_VERSION__')
if version_tag is not None:
markdown_content += f"<HR>ComfyUI: {version_tag} [Desktop]"
version_tag = core.get_comfyui_tag()
if version_tag is None:
markdown_content += f"<HR>ComfyUI: {core.comfy_ui_revision}[{comfy_ui_hash[:6]}]({core.comfy_ui_commit_datetime.date()})"
else:
version_tag = core.get_comfyui_tag()
if version_tag is None:
markdown_content += f"<HR>ComfyUI: {core.comfy_ui_revision}[{comfy_ui_hash[:6]}]({core.comfy_ui_commit_datetime.date()})"
else:
markdown_content += (f"<HR>ComfyUI: {version_tag}<BR>"
f"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;({core.comfy_ui_commit_datetime.date()})")
markdown_content += (f"<HR>ComfyUI: {version_tag}<BR>"
f"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;({core.comfy_ui_commit_datetime.date()})")
# markdown_content += f"<BR>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;()"
markdown_content += f"<BR>Manager: {core.version_str}"
markdown_content = add_target_blank(markdown_content)
try:
if '__COMFYUI_DESKTOP_VERSION__' not in os.environ:
if core.comfy_ui_commit_datetime == datetime(1900, 1, 1, 0, 0, 0):
markdown_content = '<P style="text-align: center; color:red; background-color:white; font-weight:bold">Your ComfyUI isn\'t git repo.</P>' + markdown_content
elif core.comfy_ui_required_commit_datetime.date() > core.comfy_ui_commit_datetime.date():
markdown_content = '<P style="text-align: center; color:red; background-color:white; font-weight:bold">Your ComfyUI is too OUTDATED!!!</P>' + markdown_content
if core.comfy_ui_commit_datetime == datetime(1900, 1, 1, 0, 0, 0):
markdown_content = '<P style="text-align: center; color:red; background-color:white; font-weight:bold">Your ComfyUI isn\'t git repo.</P>' + markdown_content
elif core.comfy_ui_required_commit_datetime.date() > core.comfy_ui_commit_datetime.date():
markdown_content = '<P style="text-align: center; color:red; background-color:white; font-weight:bold">Your ComfyUI is too OUTDATED!!!</P>' + markdown_content
except:
pass
@@ -1625,10 +1514,7 @@ def restart(self):
if '--windows-standalone-build' in sys_argv:
sys_argv.remove('--windows-standalone-build')
if sys_argv[0].endswith("__main__.py"): # this is a python module
module_name = os.path.basename(os.path.dirname(sys_argv[0]))
cmds = [sys.executable, '-m', module_name] + sys_argv[1:]
elif sys.platform.startswith('win32'):
if sys.platform.startswith('win32'):
cmds = ['"' + sys.executable + '"', '"' + sys_argv[0] + '"'] + sys_argv[1:]
else:
cmds = [sys.executable] + sys_argv
@@ -1720,27 +1606,22 @@ cm_global.register_api('cm.try-install-custom-node', confirm_try_install)
async def default_cache_update():
core.refresh_channel_dict()
channel_url = core.get_config()['channel_url']
async def get_cache(filename):
try:
if core.get_config()['default_cache_as_channel_url']:
uri = f"{channel_url}/{filename}"
else:
uri = f"{core.DEFAULT_CHANNEL}/{filename}"
if core.get_config()['default_cache_as_channel_url']:
uri = f"{channel_url}/{filename}"
else:
uri = f"{core.DEFAULT_CHANNEL}/{filename}"
cache_uri = str(manager_util.simple_hash(uri)) + '_' + filename
cache_uri = os.path.join(manager_util.cache_dir, cache_uri)
cache_uri = str(manager_util.simple_hash(uri)) + '_' + filename
cache_uri = os.path.join(manager_util.cache_dir, cache_uri)
json_obj = await manager_util.get_data(uri, True)
json_obj = await manager_util.get_data(uri, True)
with manager_util.cache_lock:
with open(cache_uri, "w", encoding='utf-8') as file:
json.dump(json_obj, file, indent=4, sort_keys=True)
logging.info(f"[ComfyUI-Manager] default cache updated: {uri}")
except Exception as e:
logging.error(f"[ComfyUI-Manager] Failed to perform initial fetching '{filename}': {e}")
traceback.print_exc()
with manager_util.cache_lock:
with open(cache_uri, "w", encoding='utf-8') as file:
json.dump(json_obj, file, indent=4, sort_keys=True)
logging.info(f"[ComfyUI-Manager] default cache updated: {uri}")
if core.get_config()['network_mode'] != 'offline':
a = get_cache("custom-node-list.json")
@@ -1760,6 +1641,11 @@ async def default_cache_update():
logging.info("[ComfyUI-Manager] All startup tasks have been completed.")
# NOTE: hide migration button temporarily.
# if not core.get_config()['skip_migration_check']:
# await core.check_need_to_migrate()
# else:
# logging.info("[ComfyUI-Manager] Migration check is skipped...")
threading.Thread(target=lambda: asyncio.run(default_cache_update())).start()

View File

@@ -2,7 +2,6 @@
description:
`manager_util` is the lightest module shared across the prestartup_script, main code, and cm-cli of ComfyUI-Manager.
"""
import traceback
import aiohttp
import json
@@ -13,9 +12,6 @@ import subprocess
import sys
import re
import logging
import platform
import shlex
import cm_global
cache_lock = threading.Lock()
@@ -25,28 +21,12 @@ cache_dir = os.path.join(comfyui_manager_path, '.cache') # This path is also up
use_uv = False
def add_python_path_to_env():
if platform.system() != "Windows":
sep = ':'
else:
sep = ';'
os.environ['PATH'] = os.path.dirname(sys.executable)+sep+os.environ['PATH']
def make_pip_cmd(cmd):
if 'python_embeded' in sys.executable:
if use_uv:
return [sys.executable, '-s', '-m', 'uv', 'pip'] + cmd
else:
return [sys.executable, '-s', '-m', 'pip'] + cmd
if use_uv:
return [sys.executable, '-m', 'uv', 'pip'] + cmd
else:
# FIXED: https://github.com/ltdrdata/ComfyUI-Manager/issues/1667
if use_uv:
return [sys.executable, '-m', 'uv', 'pip'] + cmd
else:
return [sys.executable, '-m', 'pip'] + cmd
return [sys.executable, '-m', 'pip'] + cmd
# DON'T USE StrictVersion - cannot handle pre_release version
# try:
@@ -189,7 +169,7 @@ def save_to_cache(uri, json_obj, silent=False):
logging.info(f"[ComfyUI-Manager] default cache updated: {uri}")
async def get_data_with_cache(uri, silent=False, cache_mode=True, dont_wait=False, dont_cache=False):
async def get_data_with_cache(uri, silent=False, cache_mode=True, dont_wait=False):
cache_uri = get_cache_path(uri)
if cache_mode and dont_wait:
@@ -208,12 +188,11 @@ async def get_data_with_cache(uri, silent=False, cache_mode=True, dont_wait=Fals
json_obj = await get_data(cache_uri, silent=silent)
else:
json_obj = await get_data(uri, silent=silent)
if not dont_cache:
with cache_lock:
with open(cache_uri, "w", encoding='utf-8') as file:
json.dump(json_obj, file, indent=4, sort_keys=True)
if not silent:
logging.info(f"[ComfyUI-Manager] default cache updated: {uri}")
with cache_lock:
with open(cache_uri, "w", encoding='utf-8') as file:
json.dump(json_obj, file, indent=4, sort_keys=True)
if not silent:
logging.info(f"[ComfyUI-Manager] default cache updated: {uri}")
return json_obj
@@ -253,11 +232,10 @@ def get_installed_packages(renew=False):
if y[0] == 'Package' or y[0].startswith('-'):
continue
normalized_name = y[0].lower().replace('-', '_')
pip_map[normalized_name] = y[1]
pip_map[y[0]] = y[1]
except subprocess.CalledProcessError:
logging.error("[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.")
return {}
return set()
return pip_map
@@ -267,48 +245,7 @@ def clear_pip_cache():
pip_map = None
def parse_requirement_line(line):
tokens = shlex.split(line)
if not tokens:
return None
package_spec = tokens[0]
pattern = re.compile(
r'^(?P<package>[A-Za-z0-9_.+-]+)'
r'(?P<operator>==|>=|<=|!=|~=|>|<)?'
r'(?P<version>[A-Za-z0-9_.+-]*)$'
)
m = pattern.match(package_spec)
if not m:
return None
package = m.group('package')
operator = m.group('operator') or None
version = m.group('version') or None
index_url = None
if '--index-url' in tokens:
idx = tokens.index('--index-url')
if idx + 1 < len(tokens):
index_url = tokens[idx + 1]
res = {'package': package}
if operator is not None:
res['operator'] = operator
if version is not None:
res['version'] = StrictVersion(version)
if index_url is not None:
res['index_url'] = index_url
return res
torch_torchvision_torchaudio_version_map = {
'2.7.0': ('0.22.0', '2.7.0'),
'2.6.0': ('0.21.0', '2.6.0'),
'2.5.1': ('0.20.0', '2.5.0'),
'2.5.0': ('0.20.0', '2.5.0'),
@@ -327,26 +264,32 @@ torch_torchvision_torchaudio_version_map = {
}
class PIPFixer:
def __init__(self, prev_pip_versions, comfyui_path, manager_files_path):
def __init__(self, prev_pip_versions):
self.prev_pip_versions = { **prev_pip_versions }
self.comfyui_path = comfyui_path
self.manager_files_path = manager_files_path
def torch_rollback(self):
print("[DBG] point(torch_rollback) 1")
spec = self.prev_pip_versions['torch'].split('+')
print("[DBG] point(torch_rollback) 2")
if len(spec) > 0:
platform = spec[1]
else:
print("[DBG] point(torch_rollback) 3")
cmd = make_pip_cmd(['install', '--force', 'torch', 'torchvision', 'torchaudio'])
subprocess.check_output(cmd, universal_newlines=True)
print("[DBG] point(torch_rollback) 4")
logging.error(cmd)
print("[DBG] point(torch_rollback) 5")
return
print("[DBG] point(torch_rollback) 6")
torch_ver = StrictVersion(spec[0])
print("[DBG] point(torch_rollback) 7")
torch_ver = f"{torch_ver.major}.{torch_ver.minor}.{torch_ver.patch}"
print("[DBG] point(torch_rollback) 8")
torch_torchvision_torchaudio_ver = torch_torchvision_torchaudio_version_map.get(torch_ver)
print("[DBG] point(torch_rollback) 9")
if torch_torchvision_torchaudio_ver is None:
cmd = make_pip_cmd(['install', '--pre', 'torch', 'torchvision', 'torchaudio',
@@ -358,46 +301,64 @@ class PIPFixer:
'--index-url', f"https://download.pytorch.org/whl/{platform}"])
logging.info(f"[ComfyUI-Manager] restore PyTorch to {torch_ver}+{platform}")
print("[DBG] point(torch_rollback) 10")
subprocess.check_output(cmd, universal_newlines=True)
print("[DBG] point(torch_rollback) 11")
def fix_broken(self):
print("[DBG] point9-1")
new_pip_versions = get_installed_packages(True)
print("[DBG] point9-2")
# remove `comfy` python package
try:
print("[DBG] point9-3")
if 'comfy' in new_pip_versions:
print("[DBG] point9-3-1")
cmd = make_pip_cmd(['uninstall', 'comfy'])
subprocess.check_output(cmd, universal_newlines=True)
logging.warning("[ComfyUI-Manager] 'comfy' python package is uninstalled.\nWARN: The 'comfy' package is completely unrelated to ComfyUI and should never be installed as it causes conflicts with ComfyUI.")
print("[DBG] point9-4")
except Exception as e:
print("[DBG] point9-5")
logging.error("[ComfyUI-Manager] Failed to uninstall `comfy` python package")
logging.error(e)
# fix torch - reinstall torch packages if version is changed
print("[DBG] point9-6")
try:
print("[DBG] point9-7")
if 'torch' not in self.prev_pip_versions or 'torchvision' not in self.prev_pip_versions or 'torchaudio' not in self.prev_pip_versions:
print("[DBG] point9-8")
logging.error("[ComfyUI-Manager] PyTorch is not installed")
elif self.prev_pip_versions['torch'] != new_pip_versions['torch'] \
or self.prev_pip_versions['torchvision'] != new_pip_versions['torchvision'] \
or self.prev_pip_versions['torchaudio'] != new_pip_versions['torchaudio']:
print("[DBG] point9-9")
self.torch_rollback()
print("[DBG] point9-10")
except Exception as e:
print("[DBG] point9-11")
logging.error("[ComfyUI-Manager] Failed to restore PyTorch")
logging.error(e)
# fix opencv
try:
print("[DBG] point9-12")
ocp = new_pip_versions.get('opencv-contrib-python')
ocph = new_pip_versions.get('opencv-contrib-python-headless')
op = new_pip_versions.get('opencv-python')
oph = new_pip_versions.get('opencv-python-headless')
print("[DBG] point9-13")
versions = [ocp, ocph, op, oph]
versions = [StrictVersion(x) for x in versions if x is not None]
versions.sort(reverse=True)
print("[DBG] point9-14")
if len(versions) > 0:
print("[DBG] point9-15")
# upgrade to maximum version
targets = []
cur = versions[0]
@@ -410,105 +371,40 @@ class PIPFixer:
if oph is not None and StrictVersion(oph) != cur:
targets.append('opencv-python-headless')
print("[DBG] point9-16")
if len(targets) > 0:
print("[DBG] point9-17")
for x in targets:
if sys.version_info < (3, 13):
cmd = make_pip_cmd(['install', f"{x}=={versions[0].version_string}", "numpy<2"])
subprocess.check_output(cmd, universal_newlines=True)
cmd = make_pip_cmd(['install', f"{x}=={versions[0].version_string}"])
subprocess.check_output(cmd, universal_newlines=True)
logging.info(f"[ComfyUI-Manager] 'opencv' dependencies were fixed: {targets}")
print("[DBG] point9-18")
except Exception as e:
print("[DBG] point9-19")
logging.error("[ComfyUI-Manager] Failed to restore opencv")
logging.error(e)
print("[DBG] point9-20")
# fix numpy
if sys.version_info >= (3, 13):
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`.")
else:
try:
np = new_pip_versions.get('numpy')
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")
except Exception as e:
logging.error("[ComfyUI-Manager] Failed to restore numpy")
logging.error(e)
# fix missing frontend
try:
# NOTE: package name in requirements is 'comfyui-frontend-package'
# but, package name from `pip freeze` is 'comfyui_frontend_package'
# but, package name from `uv pip freeze` is 'comfyui-frontend-package'
#
# get_installed_packages returns normalized name (i.e. comfyui_frontend_package)
if 'comfyui_frontend_package' not in new_pip_versions:
requirements_path = os.path.join(self.comfyui_path, 'requirements.txt')
with open(requirements_path, 'r') as file:
lines = file.readlines()
front_line = next((line.strip() for line in lines if line.startswith('comfyui-frontend-package')), None)
if front_line is None:
logging.info("[ComfyUI-Manager] Skipped fixing the 'comfyui-frontend-package' dependency because the ComfyUI is outdated.")
else:
cmd = make_pip_cmd(['install', front_line])
print("[DBG] point9-21")
np = new_pip_versions.get('numpy')
print("[DBG] point9-22")
if np is not None:
print("[DBG] point9-23")
if StrictVersion(np) >= StrictVersion('2'):
print("[DBG] point9-24")
cmd = make_pip_cmd(['install', "numpy<2"])
subprocess.check_output(cmd , universal_newlines=True)
logging.info("[ComfyUI-Manager] 'comfyui-frontend-package' dependency were fixed")
print("[DBG] point9-25")
except Exception as e:
logging.error("[ComfyUI-Manager] Failed to restore comfyui-frontend-package")
print("[DBG] point9-26")
logging.error("[ComfyUI-Manager] Failed to restore numpy")
logging.error(e)
# restore based on custom list
pip_auto_fix_path = os.path.join(self.manager_files_path, "pip_auto_fix.list")
if os.path.exists(pip_auto_fix_path):
with open(pip_auto_fix_path, 'r', encoding="UTF-8", errors="ignore") as f:
fixed_list = []
print("[DBG] point9-28")
for x in f.readlines():
try:
parsed = parse_requirement_line(x)
need_to_reinstall = True
normalized_name = parsed['package'].lower().replace('-', '_')
if normalized_name in new_pip_versions:
if 'version' in parsed and 'operator' in parsed:
cur = StrictVersion(new_pip_versions[normalized_name])
dest = parsed['version']
op = parsed['operator']
if cur == dest:
if op in ['==', '>=', '<=']:
need_to_reinstall = False
elif cur < dest:
if op in ['<=', '<', '~=', '!=']:
need_to_reinstall = False
elif cur > dest:
if op in ['>=', '>', '~=', '!=']:
need_to_reinstall = False
if need_to_reinstall:
cmd_args = ['install']
if 'version' in parsed and 'operator' in parsed:
cmd_args.append(parsed['package']+parsed['operator']+parsed['version'].version_string)
if 'index_url' in parsed:
cmd_args.append('--index-url')
cmd_args.append(parsed['index_url'])
cmd = make_pip_cmd(cmd_args)
subprocess.check_output(cmd, universal_newlines=True)
fixed_list.append(parsed['package'])
except Exception as e:
traceback.print_exc()
logging.error(f"[ComfyUI-Manager] Failed to restore '{x}'")
logging.error(e)
if len(fixed_list) > 0:
logging.info(f"[ComfyUI-Manager] dependencies in pip_auto_fix.json were fixed: {fixed_list}")
def sanitize(data):
return data.replace("<", "&lt;").replace(">", "&gt;")
@@ -517,23 +413,3 @@ def sanitize(data):
def sanitize_filename(input_string):
result_string = re.sub(r'[^a-zA-Z0-9_]', '_', input_string)
return result_string
def robust_readlines(fullpath):
import chardet
try:
with open(fullpath, "r") as f:
return f.readlines()
except:
encoding = None
with open(fullpath, "rb") as f:
raw_data = f.read()
result = chardet.detect(raw_data)
encoding = result['encoding']
if encoding is not None:
with open(fullpath, "r", encoding=encoding) as f:
return f.readlines()
print(f"[ComfyUI-Manager] Failed to recognize encoding for: {fullpath}")
return []

View File

@@ -1,50 +0,0 @@
# ComfyUI-Manager: Frontend (js)
This directory contains the JavaScript frontend implementation for ComfyUI-Manager, providing the user interface components that interact with the backend API.
## Core Components
- **comfyui-manager.js**: Main entry point that initializes the manager UI and integrates with ComfyUI.
- **custom-nodes-manager.js**: Implements the UI for browsing, installing, and managing custom nodes.
- **model-manager.js**: Handles the model management interface for downloading and organizing AI models.
- **components-manager.js**: Manages reusable workflow components system.
- **snapshot.js**: Implements the snapshot system for backing up and restoring installations.
## Sharing Components
- **comfyui-share-common.js**: Base functionality for workflow sharing features.
- **comfyui-share-copus.js**: Integration with the ComfyUI Opus sharing platform.
- **comfyui-share-openart.js**: Integration with the OpenArt sharing platform.
- **comfyui-share-youml.js**: Integration with the YouML sharing platform.
## Utility Components
- **cm-api.js**: Client-side API wrapper for communication with the backend.
- **common.js**: Shared utilities and helper functions used across the frontend.
- **node_fixer.js**: Utilities for fixing disconnected links and repairing malformed nodes by recreating them while preserving connections.
- **popover-helper.js**: UI component for popup tooltips and contextual information.
- **turbogrid.esm.js**: Grid component library - https://github.com/cenfun/turbogrid
- **workflow-metadata.js**: Handles workflow metadata parsing, validation and cross-repository compatibility including versioning, dependencies tracking, and resource management.
## Architecture
The frontend follows a modular component-based architecture:
1. **Integration Layer**: Connects with ComfyUI's existing UI system
2. **Manager Components**: Individual functional UI components (node manager, model manager, etc.)
3. **Sharing Components**: Platform-specific sharing implementations
4. **Utility Layer**: Reusable UI components and helpers
## Implementation Details
- The frontend integrates directly with ComfyUI's UI system through `app.js`
- Dialog-based UI for most manager functions to avoid cluttering the main interface
- Asynchronous API calls to handle backend operations without blocking the UI
## Styling
CSS files are included for specific components:
- **custom-nodes-manager.css**: Styling for the node management UI
- **model-manager.css**: Styling for the model management UI
This frontend implementation provides a comprehensive yet user-friendly interface for managing the ComfyUI ecosystem.

View File

@@ -13,7 +13,7 @@ import {
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
import {
free_models, install_pip, install_via_git_url, manager_instance,
rebootAPI, setManagerInstance, show_message, customAlert, customPrompt,
rebootAPI, migrateAPI, setManagerInstance, show_message, customAlert, customPrompt,
infoToast, showTerminal, setNeedRestart
} from "./common.js";
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
@@ -21,8 +21,6 @@ import { CustomNodesManager } from "./custom-nodes-manager.js";
import { ModelManager } from "./model-manager.js";
import { SnapshotManager } from "./snapshot.js";
let manager_version = await getVersion();
var docStyle = document.createElement('style');
docStyle.innerHTML = `
.comfy-toast {
@@ -44,7 +42,7 @@ docStyle.innerHTML = `
#cm-manager-dialog {
width: 1000px;
height: 455px;
height: 450px;
box-sizing: content-box;
z-index: 1000;
overflow-y: auto;
@@ -141,7 +139,7 @@ docStyle.innerHTML = `
.cm-notice-board {
width: 290px;
height: 230px;
height: 210px;
overflow: auto;
color: var(--input-text);
border: 1px solid var(--descrip-text);
@@ -227,12 +225,12 @@ document.head.appendChild(docStyle);
var update_comfyui_button = null;
var switch_comfyui_button = null;
var fetch_updates_button = null;
var update_all_button = null;
var restart_stop_button = null;
var update_policy_combo = null;
let share_option = 'all';
var is_updating = false;
var is_updating_all = false;
// copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts
@@ -479,8 +477,6 @@ async function updateComfyUI() {
const response = await api.fetchApi('/manager/queue/update_comfyui');
showTerminal();
is_updating = true;
await api.fetchApi('/manager/queue/start');
}
@@ -609,14 +605,8 @@ function showVersionSelectorDialog(versions, current, onSelect) {
}
async function switchComfyUI() {
switch_comfyui_button.disabled = true;
switch_comfyui_button.style.backgroundColor = "gray";
let res = await api.fetchApi(`/comfyui_manager/comfyui_versions`, { cache: "no-store" });
switch_comfyui_button.disabled = false;
switch_comfyui_button.style.backgroundColor = "";
if(res.status == 200) {
let obj = await res.json();
@@ -629,15 +619,6 @@ async function switchComfyUI() {
}
showVersionSelectorDialog(versions, obj.current, async (selected_version) => {
if(selected_version == 'nightly') {
update_policy_combo.value = 'nightly-comfyui';
api.fetchApi('/manager/policy/update?value=nightly-comfyui');
}
else {
update_policy_combo.value = 'stable-comfyui';
api.fetchApi('/manager/policy/update?value=stable-comfyui');
}
let response = await api.fetchApi(`/comfyui_manager/comfyui_switch_version?ver=${selected_version}`, { cache: "no-store" });
if (response.status == 200) {
infoToast(`ComfyUI version is switched to ${selected_version}`);
@@ -652,6 +633,57 @@ async function switchComfyUI() {
}
}
async function fetchUpdates(update_check_checkbox) {
let prev_text = fetch_updates_button.innerText;
fetch_updates_button.innerText = "Fetching updates...";
fetch_updates_button.disabled = true;
fetch_updates_button.style.backgroundColor = "gray";
try {
var mode = manager_instance.datasrc_combo.value;
const response = await api.fetchApi(`/customnode/fetch_updates?mode=${mode}`);
if (response.status != 200 && response.status != 201) {
show_message('Failed to fetch updates.');
return false;
}
if (response.status == 201) {
show_message("There is an updated extension available.<BR><BR><P><B>NOTE:<BR>Fetch Updates is not an update.<BR>Please update from <button id='cm-install-customnodes-button'>Install Custom Nodes</button> </B></P>");
const button = document.getElementById('cm-install-customnodes-button');
button.addEventListener("click",
async function() {
app.ui.dialog.close();
if(!CustomNodesManager.instance) {
CustomNodesManager.instance = new CustomNodesManager(app, self);
}
await CustomNodesManager.instance.show(CustomNodesManager.ShowMode.UPDATE);
}
);
update_check_checkbox.checked = false;
}
else {
show_message('All extensions are already up-to-date with the latest versions.');
}
return true;
}
catch (exception) {
show_message(`Failed to update custom nodes / ${exception}`);
return false;
}
finally {
fetch_updates_button.disabled = false;
fetch_updates_button.innerText = prev_text;
fetch_updates_button.style.backgroundColor = "";
}
}
async function onQueueStatus(event) {
const isElectron = 'electronAPI' in window;
@@ -662,72 +694,39 @@ async function onQueueStatus(event) {
else if(event.detail.status == 'done') {
reset_action_buttons();
if(!is_updating) {
if(!is_updating_all) {
return;
}
is_updating = false;
let success_list = [];
let failed_list = [];
let comfyui_state = null;
for(let k in event.detail.nodepack_result){
let v = event.detail.nodepack_result[k];
if(k == 'comfyui') {
comfyui_state = v;
continue;
}
if(v.msg == 'success') {
if(v == 'success')
success_list.push(k);
else if(v == 'skip') {
// do nothing
}
else if(v.msg != 'skip')
else
failed_list.push(k);
}
let msg = "";
if(success_list.length == 0 && comfyui_state.startsWith('skip')) {
if(success_list.length == 0) {
if(failed_list.length == 0) {
msg += "You are already up to date.";
msg += "All custom nodes are already up to date.";
}
}
else {
msg = "To apply the updates, you need to <button class='cm-small-button' id='cm-reboot-button5'>RESTART</button> ComfyUI.<hr>";
if(comfyui_state == 'success-nightly') {
msg += "ComfyUI has been updated to latest nightly version.<BR><BR>";
infoToast("ComfyUI has been updated to the latest nightly version.");
}
else if(comfyui_state.startsWith('success-stable')) {
const ver = comfyui_state.split("-").pop();
msg += `ComfyUI has been updated to ${ver}.<BR><BR>`;
infoToast(`ComfyUI has been updated to ${ver}`);
}
else if(comfyui_state == 'skip') {
msg += "ComfyUI is already up to date.<BR><BR>"
}
else if(comfyui_state != null) {
msg += "Failed to update ComfyUI.<BR><BR>"
}
if(success_list.length > 0) {
msg += "The following custom nodes have been updated:<ul>";
for(let x in success_list) {
let k = success_list[x];
let url = event.detail.nodepack_result[k].url;
let title = event.detail.nodepack_result[k].title;
if(url) {
msg += `<li><a href='${url}' target='_blank'>${title}</a></li>`;
}
else {
msg += `<li>${k}</li>`;
}
}
msg += "</ul>";
msg += "The following custom nodes have been updated:<ul>";
for(let x in success_list) {
msg += '<li>'+success_list[x]+'</li>';
}
msg += "</ul>";
setNeedRestart(true);
}
@@ -735,15 +734,7 @@ async function onQueueStatus(event) {
if(failed_list.length > 0) {
msg += '<br>The update for the following custom nodes has failed:<ul>';
for(let x in failed_list) {
let k = failed_list[x];
let url = event.detail.nodepack_result[k].url;
let title = event.detail.nodepack_result[k].title;
if(url) {
msg += `<li><a href='${url}' target='_blank'>${title}</a></li>`;
}
else {
msg += `<li>${k}</li>`;
}
msg += '<li>'+failed_list[x]+'</li>';
}
msg += '</ul>'
@@ -764,7 +755,8 @@ async function onQueueStatus(event) {
api.addEventListener("cm-queue-status", onQueueStatus);
async function updateAll(update_comfyui) {
async function updateAll(update_comfyui, manager_dialog) {
let prev_text = update_all_button.innerText;
update_all_button.innerText = "Updating...";
set_inprogress_mode();
@@ -784,7 +776,7 @@ async function updateAll(update_comfyui) {
customAlert('Another task is already in progress. Please stop the ongoing task first.');
}
else if(response.status == 200) {
is_updating = true;
is_updating_all = true;
await api.fetchApi('/manager/queue/start');
}
}
@@ -847,6 +839,14 @@ class ManagerMenuDialog extends ComfyDialog {
() => switchComfyUI()
});
fetch_updates_button =
$el("button.cm-button", {
type: "button",
textContent: "Fetch Updates",
onclick:
() => fetchUpdates(this.update_check_checkbox)
});
restart_stop_button =
$el("button.cm-button-red", {
type: "button",
@@ -860,7 +860,7 @@ class ManagerMenuDialog extends ComfyDialog {
type: "button",
textContent: "Update All Custom Nodes",
onclick:
() => updateAll(false)
() => updateAll(false, self)
});
}
else {
@@ -869,7 +869,7 @@ class ManagerMenuDialog extends ComfyDialog {
type: "button",
textContent: "Update All",
onclick:
() => updateAll(true)
() => updateAll(true, self)
});
}
@@ -899,19 +899,7 @@ class ManagerMenuDialog extends ComfyDialog {
}
}),
$el("button.cm-button", {
type: "button",
textContent: "Custom Nodes In Workflow",
onclick:
() => {
if(!CustomNodesManager.instance) {
CustomNodesManager.instance = new CustomNodesManager(app, self);
}
CustomNodesManager.instance.show(CustomNodesManager.ShowMode.IN_WORKFLOW);
}
}),
$el("br", {}, []),
$el("button.cm-button", {
type: "button",
textContent: "Model Manager",
@@ -940,20 +928,46 @@ class ManagerMenuDialog extends ComfyDialog {
update_all_button,
update_comfyui_button,
switch_comfyui_button,
// fetch_updates_button,
fetch_updates_button,
$el("br", {}, []),
restart_stop_button,
];
let migration_btn =
$el("button.cm-button-orange", {
type: "button",
textContent: "Migrate to New Node System",
onclick: () => migrateAPI()
});
migration_btn.style.display = 'none';
res.push(migration_btn);
api.fetchApi('/manager/need_to_migrate')
.then(response => response.text())
.then(text => {
if (text === 'True') {
migration_btn.style.display = 'block';
}
})
.catch(error => {
console.error('Error checking migration status:', error);
});
return res;
}
createControlsLeft() {
const isElectron = 'electronAPI' in window;
let self = this;
this.update_check_checkbox = $el("input",{type:'checkbox', id:"skip_update_check"},[])
const uc_checkbox_text = $el("label",{for:"skip_update_check"},[" Skip update check"])
uc_checkbox_text.style.color = "var(--fg-color)";
uc_checkbox_text.style.cursor = "pointer";
this.update_check_checkbox.checked = true;
// db mode
this.datasrc_combo = document.createElement("select");
this.datasrc_combo.setAttribute("title", "Configure where to retrieve node/model information. If set to 'local,' the channel is ignored, and if set to 'channel (remote),' it fetches the latest information each time the list is opened.");
@@ -962,14 +976,6 @@ class ManagerMenuDialog extends ComfyDialog {
this.datasrc_combo.appendChild($el('option', { value: 'local', text: 'DB: Local' }, []));
this.datasrc_combo.appendChild($el('option', { value: 'remote', text: 'DB: Channel (remote)' }, []));
api.fetchApi('/manager/db_mode')
.then(response => response.text())
.then(data => { this.datasrc_combo.value = data; });
this.datasrc_combo.addEventListener('change', function (event) {
api.fetchApi(`/manager/db_mode?value=${event.target.value}`);
});
// preview method
let preview_combo = document.createElement("select");
preview_combo.setAttribute("title", "Configure how latent variables will be decoded during preview in the sampling process.");
@@ -1032,6 +1038,25 @@ class ManagerMenuDialog extends ComfyDialog {
share_combo.appendChild($el('option', { value: option[0], text: `Share: ${option[1]}` }, []));
}
// default ui state
let component_policy_combo = document.createElement("select");
component_policy_combo.setAttribute("title", "When loading the workflow, configure which version of the component to use.");
component_policy_combo.className = "cm-menu-combo";
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: 'mine', text: 'Component: Use my version' }, []));
api.fetchApi('/manager/component/policy')
.then(response => response.text())
.then(data => {
component_policy_combo.value = data;
set_component_policy(data);
});
component_policy_combo.addEventListener('change', function (event) {
api.fetchApi(`/manager/component/policy?value=${event.target.value}`);
set_component_policy(event.target.value);
});
api.fetchApi('/manager/share_option')
.then(response => response.text())
.then(data => {
@@ -1051,51 +1076,14 @@ class ManagerMenuDialog extends ComfyDialog {
}
});
let component_policy_combo = document.createElement("select");
component_policy_combo.setAttribute("title", "When loading the workflow, configure which version of the component to use.");
component_policy_combo.className = "cm-menu-combo";
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: 'mine', text: 'Component: Use my version' }, []));
api.fetchApi('/manager/policy/component')
.then(response => response.text())
.then(data => {
component_policy_combo.value = data;
set_component_policy(data);
});
component_policy_combo.addEventListener('change', function (event) {
api.fetchApi(`/manager/policy/component?value=${event.target.value}`);
set_component_policy(event.target.value);
});
update_policy_combo = document.createElement("select");
if(isElectron)
update_policy_combo.style.display = 'none';
update_policy_combo.setAttribute("title", "Sets the policy to be applied when performing an update.");
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: 'nightly-comfyui', text: 'Update: ComfyUI Nightly Version' }, []));
api.fetchApi('/manager/policy/update')
.then(response => response.text())
.then(data => {
update_policy_combo.value = data;
});
update_policy_combo.addEventListener('change', function (event) {
api.fetchApi(`/manager/policy/update?value=${event.target.value}`);
});
return [
$el("div", {}, [this.update_check_checkbox, uc_checkbox_text]),
$el("br", {}, []),
this.datasrc_combo,
channel_combo,
preview_combo,
share_combo,
component_policy_combo,
update_policy_combo,
$el("br", {}, []),
$el("br", {}, []),
@@ -1122,6 +1110,11 @@ class ManagerMenuDialog extends ComfyDialog {
install_pip(url, self);
}
}
}),
$el("button.cm-experimental-button", {
type: "button",
textContent: "Unload models",
onclick: () => { free_models(); }
})
]),
];
@@ -1250,7 +1243,7 @@ class ManagerMenuDialog extends ComfyDialog {
$el("div.comfy-modal-content",
[
$el("tr.cm-title", {}, [
$el("font", {size:6, color:"white"}, [`ComfyUI Manager ${manager_version}`])]
$el("font", {size:6, color:"white"}, [`ComfyUI Manager Menu`])]
),
$el("br", {}, []),
$el("div.cm-menu-container",
@@ -1392,12 +1385,13 @@ async function getVersion() {
return await version.text();
}
app.registerExtension({
name: "Comfy.ManagerMenu",
aboutPageBadges: [
{
label: `ComfyUI-Manager ${manager_version}`,
label: `ComfyUI-Manager ${await getVersion()}`,
url: 'https://github.com/ltdrdata/ComfyUI-Manager',
icon: 'pi pi-th-large'
}

View File

@@ -1,7 +1,6 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { $el, ComfyDialog } from "../../scripts/ui.js";
import { getBestPosition, getPositionStyle, getRect } from './popover-helper.js';
function internalCustomConfirm(message, confirmMessage, cancelMessage) {
@@ -182,6 +181,23 @@ export function rebootAPI() {
}
export async function migrateAPI() {
let confirmed = await customConfirm("When performing a migration, existing installed custom nodes will be renamed and the server will be restarted. Are you sure you want to apply this?\n\n(If you don't perform the migration, ComfyUI-Manager's start-up time will be longer each time due to re-checking during startup.)")
if (confirmed) {
try {
await api.fetchApi("/manager/migrate_unmanaged_nodes");
api.fetchApi("/manager/reboot");
}
catch(exception) {
}
return true;
}
return false;
}
export var manager_instance = null;
export function setManagerInstance(obj) {
@@ -388,14 +404,12 @@ export async function fetchData(route, options) {
}
}
// https://cenfun.github.io/open-icons/
export const icons = {
search: '<svg viewBox="0 0 24 24" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21-4.486-4.494M19 10.5a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0"/></svg>',
extensions: '<svg viewBox="64 64 896 896" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M843.5 737.4c-12.4-75.2-79.2-129.1-155.3-125.4S550.9 676 546 752c-153.5-4.8-208-40.7-199.1-113.7 3.3-27.3 19.8-41.9 50.1-49 18.4-4.3 38.8-4.9 57.3-3.2 1.7.2 3.5.3 5.2.5 11.3 2.7 22.8 5 34.3 6.8 34.1 5.6 68.8 8.4 101.8 6.6 92.8-5 156-45.9 159.2-132.7 3.1-84.1-54.7-143.7-147.9-183.6-29.9-12.8-61.6-22.7-93.3-30.2-14.3-3.4-26.3-5.7-35.2-7.2-7.9-75.9-71.5-133.8-147.8-134.4S189.7 168 180.5 243.8s40 146.3 114.2 163.9 149.9-23.3 175.7-95.1c9.4 1.7 18.7 3.6 28 5.8 28.2 6.6 56.4 15.4 82.4 26.6 70.7 30.2 109.3 70.1 107.5 119.9-1.6 44.6-33.6 65.2-96.2 68.6-27.5 1.5-57.6-.9-87.3-5.8-8.3-1.4-15.9-2.8-22.6-4.3-3.9-.8-6.6-1.5-7.8-1.8l-3.1-.6c-2.2-.3-5.9-.8-10.7-1.3-25-2.3-52.1-1.5-78.5 4.6-55.2 12.9-93.9 47.2-101.1 105.8-15.7 126.2 78.6 184.7 276 188.9 29.1 70.4 106.4 107.9 179.6 87 73.3-20.9 119.3-93.4 106.9-168.6M329.1 345.2a83.3 83.3 0 1 1 .01-166.61 83.3 83.3 0 0 1-.01 166.61M695.6 845a83.3 83.3 0 1 1 .01-166.61A83.3 83.3 0 0 1 695.6 845"/></svg>',
conflicts: '<svg viewBox="0 0 400 400" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="m397.2 350.4.2-.2-180-320-.2.2C213.8 24.2 207.4 20 200 20s-13.8 4.2-17.2 10.4l-.2-.2-180 320 .2.2c-1.6 2.8-2.8 6-2.8 9.6 0 11 9 20 20 20h360c11 0 20-9 20-20 0-3.6-1.2-6.8-2.8-9.6M220 340h-40v-40h40zm0-60h-40V120h40z"/></svg>',
passed: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 426.667 426.667"><path fill="#6AC259" d="M213.333,0C95.518,0,0,95.514,0,213.333s95.518,213.333,213.333,213.333c117.828,0,213.333-95.514,213.333-213.333S331.157,0,213.333,0z M174.199,322.918l-93.935-93.931l31.309-31.309l62.626,62.622l140.894-140.898l31.309,31.309L174.199,322.918z"/></svg>',
download: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" width="100%" height="100%" viewBox="0 0 32 32"><path fill="currentColor" d="M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10l10-10z"></path></svg>',
close: '<svg xmlns="http://www.w3.org/2000/svg" pointer-events="none" width="100%" height="100%" viewBox="0 0 16 16"><g fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="m7.116 8-4.558 4.558.884.884L8 8.884l4.558 4.558.884-.884L8.884 8l4.558-4.558-.884-.884L8 7.116 3.442 2.558l-.884.884L7.116 8z"/></g></svg>',
arrowRight: '<svg xmlns="http://www.w3.org/2000/svg" pointer-events="none" width="100%" height="100%" viewBox="0 0 20 20"><path fill="currentColor" fill-rule="evenodd" d="m2.542 2.154 7.254 7.26c.136.14.204.302.204.483a.73.73 0 0 1-.204.5l-7.575 7.398c-.383.317-.724.317-1.022 0-.299-.317-.299-.643 0-.98l7.08-6.918-6.754-6.763c-.237-.343-.215-.654.066-.935.281-.28.598-.295.951-.045Zm9 0 7.254 7.26c.136.14.204.302.204.483a.73.73 0 0 1-.204.5l-7.575 7.398c-.383.317-.724.317-1.022 0-.299-.317-.299-.643 0-.98l7.08-6.918-6.754-6.763c-.237-.343-.215-.654.066-.935.281-.28.598-.295.951-.045Z"/></svg>'
download: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" width="100%" height="100%" viewBox="0 0 32 32"><path fill="currentColor" d="M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10l10-10z"></path></svg>'
}
export function sanitizeHTML(str) {
@@ -489,166 +503,3 @@ export function restoreColumnWidth(gridId, columns) {
});
}
export function getTimeAgo(dateStr) {
const date = new Date(dateStr);
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
return "";
}
const units = [
{ max: 2760000, value: 60000, name: 'minute', past: 'a minute ago', future: 'in a minute' },
{ max: 72000000, value: 3600000, name: 'hour', past: 'an hour ago', future: 'in an hour' },
{ max: 518400000, value: 86400000, name: 'day', past: 'yesterday', future: 'tomorrow' },
{ max: 2419200000, value: 604800000, name: 'week', past: 'last week', future: 'in a week' },
{ max: 28512000000, value: 2592000000, name: 'month', past: 'last month', future: 'in a month' }
];
const diff = Date.now() - date.getTime();
// less than a minute
if (Math.abs(diff) < 60000)
return 'just now';
for (let i = 0; i < units.length; i++) {
if (Math.abs(diff) < units[i].max) {
return format(diff, units[i].value, units[i].name, units[i].past, units[i].future, diff < 0);
}
}
function format(diff, divisor, unit, past, future, isInTheFuture) {
const val = Math.round(Math.abs(diff) / divisor);
if (isInTheFuture)
return val <= 1 ? future : 'in ' + val + ' ' + unit + 's';
return val <= 1 ? past : val + ' ' + unit + 's ago';
}
return format(diff, 31536000000, 'year', 'last year', 'in a year', diff < 0);
};
export const loadCss = (cssFile) => {
const cssPath = import.meta.resolve(cssFile);
//console.log(cssPath);
const $link = document.createElement("link");
$link.setAttribute("rel", 'stylesheet');
$link.setAttribute("href", cssPath);
document.head.appendChild($link);
};
export const copyText = (text) => {
return new Promise((resolve) => {
let err;
try {
navigator.clipboard.writeText(text);
} catch (e) {
err = e;
}
if (err) {
resolve(false);
} else {
resolve(true);
}
});
};
function renderPopover($elem, target, options = {}) {
// async microtask
queueMicrotask(() => {
const containerRect = getRect(window);
const targetRect = getRect(target);
const elemRect = getRect($elem);
const positionInfo = getBestPosition(
containerRect,
targetRect,
elemRect,
options.positions
);
const style = getPositionStyle(positionInfo, {
bgColor: options.bgColor,
borderColor: options.borderColor,
borderRadius: options.borderRadius
});
$elem.style.top = positionInfo.top + "px";
$elem.style.left = positionInfo.left + "px";
$elem.style.background = style.background;
});
}
let $popover;
export function hidePopover() {
if ($popover) {
$popover.remove();
$popover = null;
}
}
export function showPopover(target, text, className, options) {
hidePopover();
$popover = document.createElement("div");
$popover.className = ['cn-popover', className].filter(it => it).join(" ");
document.body.appendChild($popover);
$popover.innerHTML = text;
$popover.style.display = "block";
renderPopover($popover, target, {
borderRadius: 10,
... options
});
}
let $tooltip;
export function hideTooltip(target) {
if ($tooltip) {
$tooltip.style.display = "none";
$tooltip.innerHTML = "";
$tooltip.style.top = "0px";
$tooltip.style.left = "0px";
}
}
export function showTooltip(target, text, className = 'cn-tooltip', styleMap = {}) {
if (!$tooltip) {
$tooltip = document.createElement("div");
$tooltip.className = className;
$tooltip.style.cssText = `
pointer-events: none;
position: fixed;
z-index: 10001;
padding: 20px;
color: #1e1e1e;
max-width: 350px;
filter: drop-shadow(1px 5px 5px rgb(0 0 0 / 30%));
${Object.keys(styleMap).map(k=>k+":"+styleMap[k]+";").join("")}
`;
document.body.appendChild($tooltip);
}
$tooltip.innerHTML = text;
$tooltip.style.display = "block";
renderPopover($tooltip, target, {
positions: ['top', 'bottom', 'right', 'center'],
bgColor: "#ffffff",
borderColor: "#cccccc",
borderRadius: 5
});
}
function initTooltip () {
const mouseenterHandler = (e) => {
const target = e.target;
const text = target.getAttribute('tooltip');
if (text) {
showTooltip(target, text);
}
};
const mouseleaveHandler = (e) => {
const target = e.target;
const text = target.getAttribute('tooltip');
if (text) {
hideTooltip(target);
}
};
document.body.removeEventListener('mouseenter', mouseenterHandler, true);
document.body.removeEventListener('mouseleave', mouseleaveHandler, true);
document.body.addEventListener('mouseenter', mouseenterHandler, true);
document.body.addEventListener('mouseleave', mouseleaveHandler, true);
}
initTooltip();

View File

@@ -709,7 +709,7 @@ app.handleFile = handleFile;
let current_component_policy = 'workflow';
try {
api.fetchApi('/manager/policy/component')
api.fetchApi('/manager/component/policy')
.then(response => response.text())
.then(data => { current_component_policy = data; });
}

View File

@@ -1,699 +0,0 @@
.cn-manager {
--grid-font: -apple-system, BlinkMacSystemFont, "Segue UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
z-index: 1099;
width: 80%;
height: 80%;
display: flex;
flex-direction: column;
gap: 10px;
color: var(--fg-color);
font-family: arial, sans-serif;
text-underline-offset: 3px;
outline: none;
}
.cn-manager .cn-flex-auto {
flex: auto;
}
.cn-manager button {
font-size: 16px;
color: var(--input-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
margin: 0;
padding: 4px 8px;
min-width: 100px;
}
.cn-manager button:disabled,
.cn-manager input:disabled,
.cn-manager select:disabled {
color: gray;
}
.cn-manager button:disabled {
background-color: var(--comfy-input-bg);
}
.cn-manager .cn-manager-restart {
display: none;
background-color: #500000;
color: white;
}
.cn-manager .cn-manager-stop {
display: none;
background-color: #500000;
color: white;
}
.cn-manager .cn-manager-back {
align-items: center;
justify-content: center;
}
.arrow-icon {
height: 1em;
width: 1em;
margin-right: 5px;
transform: translateY(2px);
}
.cn-icon {
display: block;
width: 16px;
height: 16px;
}
.cn-icon svg {
display: block;
margin: 0;
pointer-events: none;
}
.cn-manager-header {
display: flex;
flex-wrap: wrap;
gap: 5px;
align-items: center;
padding: 0 5px;
}
.cn-manager-header label {
display: flex;
gap: 5px;
align-items: center;
}
.cn-manager-filter {
height: 28px;
line-height: 28px;
}
.cn-manager-keywords {
height: 28px;
line-height: 28px;
padding: 0 5px 0 26px;
background-size: 16px;
background-position: 5px center;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E");
}
.cn-manager-status {
padding-left: 10px;
}
.cn-manager-grid {
flex: auto;
border: 1px solid var(--border-color);
overflow: hidden;
position: relative;
}
.cn-manager-selection {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.cn-manager-message {
position: relative;
}
.cn-manager-footer {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.cn-manager-grid .tg-turbogrid {
font-family: var(--grid-font);
font-size: 15px;
background: var(--bg-color);
}
.cn-manager-grid .tg-turbogrid .tg-highlight::after {
position: absolute;
top: 0;
left: 0;
content: "";
display: block;
width: 100%;
height: 100%;
box-sizing: border-box;
background-color: #80bdff11;
pointer-events: none;
}
.cn-manager-grid .cn-pack-name a {
color: skyblue;
text-decoration: none;
word-break: break-word;
}
.cn-manager-grid .cn-pack-desc a {
color: #5555FF;
font-weight: bold;
text-decoration: none;
}
.cn-manager-grid .tg-cell a:hover {
text-decoration: underline;
}
.cn-manager-grid .cn-pack-version {
line-height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
gap: 5px;
}
.cn-manager-grid .cn-pack-nodes {
line-height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
gap: 5px;
cursor: pointer;
height: 100%;
}
.cn-manager-grid .cn-pack-nodes:hover {
text-decoration: underline;
}
.cn-manager-grid .cn-pack-conflicts {
color: orange;
}
.cn-popover {
position: fixed;
z-index: 10000;
padding: 20px;
color: #1e1e1e;
filter: drop-shadow(1px 5px 5px rgb(0 0 0 / 30%));
overflow: hidden;
}
.cn-flyover {
position: absolute;
top: 0;
right: 0;
z-index: 1000;
display: none;
width: 50%;
height: 100%;
background-color: var(--comfy-menu-bg);
animation-duration: 0.2s;
animation-fill-mode: both;
flex-direction: column;
}
.cn-flyover::before {
position: absolute;
top: 0;
content: "";
z-index: 10;
display: block;
width: 10px;
height: 100%;
pointer-events: none;
left: -10px;
background-image: linear-gradient(to left, rgb(0 0 0 / 20%), rgb(0 0 0 / 0%));
}
.cn-flyover-header {
height: 45px;
display: flex;
align-items: center;
gap: 5px;
border-bottom: 1px solid var(--border-color);
}
.cn-flyover-close {
display: flex;
align-items: center;
padding: 0 10px;
justify-content: center;
cursor: pointer;
opacity: 0.8;
height: 100%;
}
.cn-flyover-close:hover {
opacity: 1;
}
.cn-flyover-close svg {
display: block;
margin: 0;
pointer-events: none;
width: 20px;
height: 20px;
}
.cn-flyover-title {
display: flex;
align-items: center;
font-weight: bold;
gap: 10px;
flex: auto;
}
.cn-flyover-body {
height: calc(100% - 45px);
overflow-y: auto;
position: relative;
background-color: var(--comfy-menu-secondary-bg);
}
@keyframes cn-slide-in-right {
from {
visibility: visible;
transform: translate3d(100%, 0, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}
.cn-slide-in-right {
animation-name: cn-slide-in-right;
}
@keyframes cn-slide-out-right {
from {
transform: translate3d(0, 0, 0);
}
to {
visibility: hidden;
transform: translate3d(100%, 0, 0);
}
}
.cn-slide-out-right {
animation-name: cn-slide-out-right;
}
.cn-nodes-list {
width: 100%;
}
.cn-nodes-row {
display: flex;
align-items: center;
gap: 10px;
}
.cn-nodes-row:nth-child(odd) {
background-color: rgb(0 0 0 / 5%);
}
.cn-nodes-row:hover {
background-color: rgb(0 0 0 / 10%);
}
.cn-nodes-sn {
text-align: right;
min-width: 35px;
color: var(--drag-text);
flex-shrink: 0;
font-size: 12px;
padding: 8px 5px;
}
.cn-nodes-name {
cursor: pointer;
white-space: nowrap;
flex-shrink: 0;
position: relative;
padding: 8px 5px;
}
.cn-nodes-name::after {
content: attr(action);
position: absolute;
pointer-events: none;
top: 50%;
left: 100%;
transform: translate(5px, -50%);
font-size: 12px;
color: var(--drag-text);
background-color: var(--comfy-input-bg);
border-radius: 10px;
border: 1px solid var(--border-color);
padding: 3px 8px;
display: none;
}
.cn-nodes-name.action::after {
display: block;
}
.cn-nodes-name:hover {
text-decoration: underline;
}
.cn-nodes-conflict .cn-nodes-name,
.cn-nodes-conflict .cn-icon {
color: orange;
}
.cn-conflicts-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
align-items: center;
padding: 5px 0;
}
.cn-conflicts-list b {
font-weight: normal;
color: var(--descrip-text);
}
.cn-nodes-pack {
cursor: pointer;
color: skyblue;
}
.cn-nodes-pack:hover {
text-decoration: underline;
}
.cn-pack-badge {
font-size: 12px;
font-weight: normal;
background-color: var(--comfy-input-bg);
border-radius: 10px;
border: 1px solid var(--border-color);
padding: 3px 8px;
color: var(--error-text);
}
.cn-preview {
min-width: 300px;
max-width: 500px;
min-height: 120px;
overflow: hidden;
font-size: 12px;
pointer-events: none;
padding: 12px;
color: var(--fg-color);
}
.cn-preview-header {
display: flex;
gap: 8px;
align-items: center;
border-bottom: 1px solid var(--comfy-input-bg);
padding: 5px 10px;
}
.cn-preview-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: grey;
position: relative;
filter: drop-shadow(1px 2px 3px rgb(0 0 0 / 30%));
}
.cn-preview-dot.cn-preview-optional::after {
content: "";
position: absolute;
pointer-events: none;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--comfy-input-bg);
border-radius: 50%;
width: 3px;
height: 3px;
}
.cn-preview-dot.cn-preview-grid {
border-radius: 0;
}
.cn-preview-dot.cn-preview-grid::before {
content: '';
position: absolute;
border-left: 1px solid var(--comfy-input-bg);
border-right: 1px solid var(--comfy-input-bg);
width: 4px;
height: 100%;
left: 2px;
top: 0;
z-index: 1;
}
.cn-preview-dot.cn-preview-grid::after {
content: '';
position: absolute;
border-top: 1px solid var(--comfy-input-bg);
border-bottom: 1px solid var(--comfy-input-bg);
width: 100%;
height: 4px;
left: 0;
top: 2px;
z-index: 1;
}
.cn-preview-name {
flex: auto;
font-size: 14px;
}
.cn-preview-io {
display: flex;
justify-content: space-between;
padding: 10px 10px;
}
.cn-preview-column > div {
display: flex;
gap: 10px;
align-items: center;
height: 18px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.cn-preview-input {
justify-content: flex-start;
}
.cn-preview-output {
justify-content: flex-end;
}
.cn-preview-list {
display: flex;
flex-direction: column;
gap: 3px;
padding: 0 10px 10px 10px;
}
.cn-preview-switch {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
background: var(--bg-color);
border: 2px solid var(--border-color);
border-radius: 10px;
text-wrap: nowrap;
padding: 2px 20px;
gap: 10px;
}
.cn-preview-switch::before,
.cn-preview-switch::after {
position: absolute;
pointer-events: none;
top: 50%;
transform: translate(0, -50%);
color: var(--fg-color);
opacity: 0.8;
}
.cn-preview-switch::before {
content: "◀";
left: 5px;
}
.cn-preview-switch::after {
content: "▶";
right: 5px;
}
.cn-preview-value {
color: var(--descrip-text);
}
.cn-preview-string {
min-height: 30px;
max-height: 300px;
background: var(--bg-color);
color: var(--descrip-text);
border-radius: 3px;
padding: 3px 5px;
overflow-y: auto;
overflow-x: hidden;
}
.cn-preview-description {
margin: 0px 10px 10px 10px;
padding: 6px;
background: var(--border-color);
color: var(--descrip-text);
border-radius: 5px;
font-style: italic;
word-break: break-word;
}
.cn-tag-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
align-items: center;
margin-bottom: 5px;
}
.cn-tag-list > div {
background-color: var(--border-color);
border-radius: 5px;
padding: 0 5px;
}
.cn-install-buttons {
display: flex;
flex-direction: column;
gap: 3px;
padding: 3px;
align-items: center;
justify-content: center;
height: 100%;
}
.cn-selected-buttons {
display: flex;
gap: 5px;
align-items: center;
padding-right: 20px;
}
.cn-manager .cn-btn-enable {
background-color: #333399;
color: white;
}
.cn-manager .cn-btn-disable {
background-color: #442277;
color: white;
}
.cn-manager .cn-btn-update {
background-color: #1155AA;
color: white;
}
.cn-manager .cn-btn-try-update {
background-color: Gray;
color: white;
}
.cn-manager .cn-btn-try-fix {
background-color: #6495ED;
color: white;
}
.cn-manager .cn-btn-import-failed {
background-color: #AA1111;
font-size: 10px;
font-weight: bold;
color: white;
}
.cn-manager .cn-btn-install {
background-color: black;
color: white;
}
.cn-manager .cn-btn-try-install {
background-color: Gray;
color: white;
}
.cn-manager .cn-btn-uninstall {
background-color: #993333;
color: white;
}
.cn-manager .cn-btn-reinstall {
background-color: #993333;
color: white;
}
.cn-manager .cn-btn-switch {
background-color: #448833;
color: white;
}
@keyframes cn-btn-loading-bg {
0% {
left: 0;
}
100% {
left: -105px;
}
}
.cn-manager button.cn-btn-loading {
position: relative;
overflow: hidden;
border-color: rgb(0 119 207 / 80%);
background-color: var(--comfy-input-bg);
}
.cn-manager button.cn-btn-loading::after {
position: absolute;
top: 0;
left: 0;
content: "";
width: 500px;
height: 100%;
background-image: repeating-linear-gradient(
-45deg,
rgb(0 119 207 / 30%),
rgb(0 119 207 / 30%) 10px,
transparent 10px,
transparent 15px
);
animation: cn-btn-loading-bg 2s linear infinite;
}
.cn-manager-light .cn-pack-name a {
color: blue;
}
.cn-manager-light .cm-warn-note {
background-color: #ccc !important;
}
.cn-manager-light .cn-btn-install {
background-color: #333;
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,213 +0,0 @@
.cmm-manager {
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
z-index: 1099;
width: 80%;
height: 80%;
display: flex;
flex-direction: column;
gap: 10px;
color: var(--fg-color);
font-family: arial, sans-serif;
}
.cmm-manager .cmm-flex-auto {
flex: auto;
}
.cmm-manager button {
font-size: 16px;
color: var(--input-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
margin: 0;
padding: 4px 8px;
min-width: 100px;
}
.cmm-manager button:disabled,
.cmm-manager input:disabled,
.cmm-manager select:disabled {
color: gray;
}
.cmm-manager button:disabled {
background-color: var(--comfy-input-bg);
}
.cmm-manager .cmm-manager-refresh {
display: none;
background-color: #000080;
color: white;
}
.cmm-manager .cmm-manager-stop {
display: none;
background-color: #500000;
color: white;
}
.cmm-manager-header {
display: flex;
flex-wrap: wrap;
gap: 5px;
align-items: center;
padding: 0 5px;
}
.cmm-manager-header label {
display: flex;
gap: 5px;
align-items: center;
}
.cmm-manager-type,
.cmm-manager-base,
.cmm-manager-filter {
height: 28px;
line-height: 28px;
}
.cmm-manager-keywords {
height: 28px;
line-height: 28px;
padding: 0 5px 0 26px;
background-size: 16px;
background-position: 5px center;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E");
}
.cmm-manager-status {
padding-left: 10px;
}
.cmm-manager-grid {
flex: auto;
border: 1px solid var(--border-color);
overflow: hidden;
}
.cmm-manager-selection {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.cmm-manager-footer {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.cmm-manager-grid .tg-turbogrid {
font-family: var(--grid-font);
font-size: 15px;
background: var(--bg-color);
}
.cmm-manager-grid .cmm-node-name a {
color: skyblue;
text-decoration: none;
word-break: break-word;
}
.cmm-manager-grid .cmm-node-desc a {
color: #5555FF;
font-weight: bold;
text-decoration: none;
}
.cmm-manager-grid .tg-cell a:hover {
text-decoration: underline;
}
.cmm-icon-passed {
width: 20px;
height: 20px;
position: absolute;
left: calc(50% - 10px);
top: calc(50% - 10px);
}
.cmm-manager .cmm-btn-enable {
background-color: blue;
color: white;
}
.cmm-manager .cmm-btn-disable {
background-color: MediumSlateBlue;
color: white;
}
.cmm-manager .cmm-btn-install {
background-color: black;
color: white;
}
.cmm-btn-download {
width: 18px;
height: 18px;
position: absolute;
left: calc(50% - 10px);
top: calc(50% - 10px);
cursor: pointer;
opacity: 0.8;
color: #fff;
}
.cmm-btn-download:hover {
opacity: 1;
}
.cmm-manager-light .cmm-btn-download {
color: #000;
}
@keyframes cmm-btn-loading-bg {
0% {
left: 0;
}
100% {
left: -105px;
}
}
.cmm-manager button.cmm-btn-loading {
position: relative;
overflow: hidden;
border-color: rgb(0 119 207 / 80%);
background-color: var(--comfy-input-bg);
}
.cmm-manager button.cmm-btn-loading::after {
position: absolute;
top: 0;
left: 0;
content: "";
width: 500px;
height: 100%;
background-image: repeating-linear-gradient(
-45deg,
rgb(0 119 207 / 30%),
rgb(0 119 207 / 30%) 10px,
transparent 10px,
transparent 15px
);
animation: cmm-btn-loading-bg 2s linear infinite;
}
.cmm-manager-light .cmm-node-name a {
color: blue;
}
.cmm-manager-light .cm-warn-note {
background-color: #ccc !important;
}
.cmm-manager-light .cmm-btn-install {
background-color: #333;
}

View File

@@ -3,17 +3,236 @@ import { $el } from "../../scripts/ui.js";
import {
manager_instance, rebootAPI,
fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal,
storeColumnWidth, restoreColumnWidth, loadCss
storeColumnWidth, restoreColumnWidth
} from "./common.js";
import { api } from "../../scripts/api.js";
// https://cenfun.github.io/turbogrid/api.html
import TG from "./turbogrid.esm.js";
loadCss("./model-manager.css");
const gridId = "model";
const pageCss = `
.cmm-manager {
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
z-index: 1099;
width: 80%;
height: 80%;
display: flex;
flex-direction: column;
gap: 10px;
color: var(--fg-color);
font-family: arial, sans-serif;
}
.cmm-manager .cmm-flex-auto {
flex: auto;
}
.cmm-manager button {
font-size: 16px;
color: var(--input-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
margin: 0;
padding: 4px 8px;
min-width: 100px;
}
.cmm-manager button:disabled,
.cmm-manager input:disabled,
.cmm-manager select:disabled {
color: gray;
}
.cmm-manager button:disabled {
background-color: var(--comfy-input-bg);
}
.cmm-manager .cmm-manager-refresh {
display: none;
background-color: #000080;
color: white;
}
.cmm-manager .cmm-manager-stop {
display: none;
background-color: #500000;
color: white;
}
.cmm-manager-header {
display: flex;
flex-wrap: wrap;
gap: 5px;
align-items: center;
padding: 0 5px;
}
.cmm-manager-header label {
display: flex;
gap: 5px;
align-items: center;
}
.cmm-manager-type,
.cmm-manager-base,
.cmm-manager-filter {
height: 28px;
line-height: 28px;
}
.cmm-manager-keywords {
height: 28px;
line-height: 28px;
padding: 0 5px 0 26px;
background-size: 16px;
background-position: 5px center;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml;charset=utf8,${encodeURIComponent(icons.search.replace("currentColor", "#888"))}");
}
.cmm-manager-status {
padding-left: 10px;
}
.cmm-manager-grid {
flex: auto;
border: 1px solid var(--border-color);
overflow: hidden;
}
.cmm-manager-selection {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.cmm-manager-message {
}
.cmm-manager-footer {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.cmm-manager-grid .tg-turbogrid {
font-family: var(--grid-font);
font-size: 15px;
background: var(--bg-color);
}
.cmm-manager-grid .cmm-node-name a {
color: skyblue;
text-decoration: none;
word-break: break-word;
}
.cmm-manager-grid .cmm-node-desc a {
color: #5555FF;
font-weight: bold;
text-decoration: none;
}
.cmm-manager-grid .tg-cell a:hover {
text-decoration: underline;
}
.cmm-icon-passed {
width: 20px;
height: 20px;
position: absolute;
left: calc(50% - 10px);
top: calc(50% - 10px);
}
.cmm-manager .cmm-btn-enable {
background-color: blue;
color: white;
}
.cmm-manager .cmm-btn-disable {
background-color: MediumSlateBlue;
color: white;
}
.cmm-manager .cmm-btn-install {
background-color: black;
color: white;
}
.cmm-btn-download {
width: 18px;
height: 18px;
position: absolute;
left: calc(50% - 10px);
top: calc(50% - 10px);
cursor: pointer;
opacity: 0.8;
color: #fff;
}
.cmm-btn-download:hover {
opacity: 1;
}
.cmm-manager-light .cmm-btn-download {
color: #000;
}
@keyframes cmm-btn-loading-bg {
0% {
left: 0;
}
100% {
left: -105px;
}
}
.cmm-manager button.cmm-btn-loading {
position: relative;
overflow: hidden;
border-color: rgb(0 119 207 / 80%);
background-color: var(--comfy-input-bg);
}
.cmm-manager button.cmm-btn-loading::after {
position: absolute;
top: 0;
left: 0;
content: "";
width: 500px;
height: 100%;
background-image: repeating-linear-gradient(
-45deg,
rgb(0 119 207 / 30%),
rgb(0 119 207 / 30%) 10px,
transparent 10px,
transparent 15px
);
animation: cmm-btn-loading-bg 2s linear infinite;
}
.cmm-manager-light .cmm-node-name a {
color: blue;
}
.cmm-manager-light .cm-warn-note {
background-color: #ccc !important;
}
.cmm-manager-light .cmm-btn-install {
background-color: #333;
}
`;
const pageHtml = `
<div class="cmm-manager-header">
<label>Filter
@@ -64,6 +283,14 @@ export class ModelManager {
}
init() {
if (!document.querySelector(`style[context="${this.id}"]`)) {
const $style = document.createElement("style");
$style.setAttribute("context", this.id);
$style.innerHTML = pageCss;
document.head.appendChild($style);
}
this.element = $el("div", {
parent: document.body,
className: "comfy-modal cmm-manager"
@@ -81,13 +308,10 @@ export class ModelManager {
value: ""
}, {
label: "Installed",
value: "installed"
value: "True"
}, {
label: "Not Installed",
value: "not_installed"
}, {
label: "In Workflow",
value: "in_workflow"
value: "False"
}];
this.typeList = [{
@@ -257,31 +481,12 @@ export class ModelManager {
rowFilter: (rowItem) => {
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);
if (shouldShown) {
if(this.filter) {
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.filter && rowItem.installed !== this.filter) {
return false;
}
if(this.type && rowItem.type !== this.type) {
@@ -356,7 +561,7 @@ export class ModelManager {
sortable: false,
align: 'center',
formatter: (url, rowItem, columnItem) => {
return `<a class="cmm-btn-download" tooltip="Download file" href="${url}" target="_blank">${icons.download}</a>`;
return `<a class="cmm-btn-download" title="Download file" href="${url}" target="_blank">${icons.download}</a>`;
}
}, {
id: 'size',
@@ -817,4 +1022,4 @@ export class ModelManager {
close() {
this.element.style.display = "none";
}
}
}

View File

@@ -153,7 +153,6 @@ app.registerExtension({
app.canvas.graph.add(new_node, false);
node_info_copy(this, new_node, true);
app.canvas.graph.remove(this);
requestAnimationFrame(() => app.canvas.setDirty(true, true))
},
});
});

View File

@@ -1,619 +0,0 @@
const hasOwn = function(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
};
const isNum = function(num) {
if (typeof num !== 'number' || isNaN(num)) {
return false;
}
const isInvalid = function(n) {
if (n === Number.MAX_VALUE || n === Number.MIN_VALUE || n === Number.NEGATIVE_INFINITY || n === Number.POSITIVE_INFINITY) {
return true;
}
return false;
};
if (isInvalid(num)) {
return false;
}
return true;
};
const toNum = (num) => {
if (typeof (num) !== 'number') {
num = parseFloat(num);
}
if (isNaN(num)) {
num = 0;
}
num = Math.round(num);
return num;
};
const clamp = function(value, min, max) {
return Math.max(min, Math.min(max, value));
};
const isWindow = (obj) => {
return Boolean(obj && obj === obj.window);
};
const isDocument = (obj) => {
return Boolean(obj && obj.nodeType === 9);
};
const isElement = (obj) => {
return Boolean(obj && obj.nodeType === 1);
};
// ===========================================================================================
export const toRect = (obj) => {
if (obj) {
return {
left: toNum(obj.left || obj.x),
top: toNum(obj.top || obj.y),
width: toNum(obj.width),
height: toNum(obj.height)
};
}
return {
left: 0,
top: 0,
width: 0,
height: 0
};
};
export const getElement = (selector) => {
if (typeof selector === 'string' && selector) {
if (selector.startsWith('#')) {
return document.getElementById(selector.slice(1));
}
return document.querySelector(selector);
}
if (isDocument(selector)) {
return selector.body;
}
if (isElement(selector)) {
return selector;
}
};
export const getRect = (target, fixed) => {
if (!target) {
return toRect();
}
if (isWindow(target)) {
return {
left: 0,
top: 0,
width: window.innerWidth,
height: window.innerHeight
};
}
const elem = getElement(target);
if (!elem) {
return toRect(target);
}
const br = elem.getBoundingClientRect();
const rect = toRect(br);
// fix offset
if (!fixed) {
rect.left += window.scrollX;
rect.top += window.scrollY;
}
rect.width = elem.offsetWidth;
rect.height = elem.offsetHeight;
return rect;
};
// ===========================================================================================
const calculators = {
bottom: (info, containerRect, targetRect) => {
info.space = containerRect.top + containerRect.height - targetRect.top - targetRect.height - info.height;
info.top = targetRect.top + targetRect.height;
info.left = Math.round(targetRect.left + targetRect.width * 0.5 - info.width * 0.5);
},
top: (info, containerRect, targetRect) => {
info.space = targetRect.top - info.height - containerRect.top;
info.top = targetRect.top - info.height;
info.left = Math.round(targetRect.left + targetRect.width * 0.5 - info.width * 0.5);
},
right: (info, containerRect, targetRect) => {
info.space = containerRect.left + containerRect.width - targetRect.left - targetRect.width - info.width;
info.top = Math.round(targetRect.top + targetRect.height * 0.5 - info.height * 0.5);
info.left = targetRect.left + targetRect.width;
},
left: (info, containerRect, targetRect) => {
info.space = targetRect.left - info.width - containerRect.left;
info.top = Math.round(targetRect.top + targetRect.height * 0.5 - info.height * 0.5);
info.left = targetRect.left - info.width;
}
};
// with order
export const getDefaultPositions = () => {
return Object.keys(calculators);
};
const calculateSpace = (info, containerRect, targetRect) => {
const calculator = calculators[info.position];
calculator(info, containerRect, targetRect);
if (info.space >= 0) {
info.passed += 1;
}
};
// ===========================================================================================
const calculateAlignOffset = (info, containerRect, targetRect, alignType, sizeType) => {
const popoverStart = info[alignType];
const popoverSize = info[sizeType];
const containerStart = containerRect[alignType];
const containerSize = containerRect[sizeType];
const targetStart = targetRect[alignType];
const targetSize = targetRect[sizeType];
const targetCenter = targetStart + targetSize * 0.5;
// size overflow
if (popoverSize > containerSize) {
const overflow = (popoverSize - containerSize) * 0.5;
info[alignType] = containerStart - overflow;
info.offset = targetCenter - containerStart + overflow;
return;
}
const space1 = popoverStart - containerStart;
const space2 = (containerStart + containerSize) - (popoverStart + popoverSize);
// both side passed, default to center
if (space1 >= 0 && space2 >= 0) {
if (info.passed) {
info.passed += 2;
}
info.offset = popoverSize * 0.5;
return;
}
// one side passed
if (info.passed) {
info.passed += 1;
}
if (space1 < 0) {
const min = containerStart;
info[alignType] = min;
info.offset = targetCenter - min;
return;
}
// space2 < 0
const max = containerStart + containerSize - popoverSize;
info[alignType] = max;
info.offset = targetCenter - max;
};
const calculateHV = (info, containerRect) => {
if (['top', 'bottom'].includes(info.position)) {
info.top = clamp(info.top, containerRect.top, containerRect.top + containerRect.height - info.height);
return ['left', 'width'];
}
info.left = clamp(info.left, containerRect.left, containerRect.left + containerRect.width - info.width);
return ['top', 'height'];
};
const calculateOffset = (info, containerRect, targetRect) => {
const [alignType, sizeType] = calculateHV(info, containerRect);
calculateAlignOffset(info, containerRect, targetRect, alignType, sizeType);
info.offset = clamp(info.offset, 0, info[sizeType]);
};
// ===========================================================================================
const calculateDistance = (info, previousPositionInfo) => {
if (!previousPositionInfo) {
return;
}
// no change if position no change with previous
if (info.position === previousPositionInfo.position) {
return;
}
const ax = info.left + info.width * 0.5;
const ay = info.top + info.height * 0.5;
const bx = previousPositionInfo.left + previousPositionInfo.width * 0.5;
const by = previousPositionInfo.top + previousPositionInfo.height * 0.5;
const dx = Math.abs(ax - bx);
const dy = Math.abs(ay - by);
info.distance = Math.round(Math.sqrt(dx * dx + dy * dy));
};
// ===========================================================================================
const calculatePositionInfo = (info, containerRect, targetRect, previousPositionInfo) => {
calculateSpace(info, containerRect, targetRect);
calculateOffset(info, containerRect, targetRect);
calculateDistance(info, previousPositionInfo);
};
// ===========================================================================================
const calculateBestPosition = (containerRect, targetRect, infoMap, withOrder, previousPositionInfo) => {
// position space: +1
// align space:
// two side passed: +2
// one side passed: +1
const safePassed = 3;
if (previousPositionInfo) {
const prevInfo = infoMap[previousPositionInfo.position];
if (prevInfo) {
calculatePositionInfo(prevInfo, containerRect, targetRect);
if (prevInfo.passed >= safePassed) {
return prevInfo;
}
prevInfo.calculated = true;
}
}
const positionList = [];
Object.values(infoMap).forEach((info) => {
if (!info.calculated) {
calculatePositionInfo(info, containerRect, targetRect, previousPositionInfo);
}
positionList.push(info);
});
positionList.sort((a, b) => {
if (a.passed !== b.passed) {
return b.passed - a.passed;
}
if (withOrder && a.passed >= safePassed && b.passed >= safePassed) {
return a.index - b.index;
}
if (a.space !== b.space) {
return b.space - a.space;
}
return a.index - b.index;
});
// logTable(positionList);
return positionList[0];
};
// const logTable = (() => {
// let time_id;
// return (info) => {
// clearTimeout(time_id);
// time_id = setTimeout(() => {
// console.table(info);
// }, 10);
// };
// })();
// ===========================================================================================
const getAllowPositions = (positions, defaultAllowPositions) => {
if (!positions) {
return;
}
if (Array.isArray(positions)) {
positions = positions.join(',');
}
positions = String(positions).split(',').map((it) => it.trim().toLowerCase()).filter((it) => it);
positions = positions.filter((it) => defaultAllowPositions.includes(it));
if (!positions.length) {
return;
}
return positions;
};
const isPositionChanged = (info, previousPositionInfo) => {
if (!previousPositionInfo) {
return true;
}
if (info.left !== previousPositionInfo.left) {
return true;
}
if (info.top !== previousPositionInfo.top) {
return true;
}
return false;
};
// ===========================================================================================
// const log = (name, time) => {
// if (time > 0.1) {
// console.log(name, time);
// }
// };
export const getBestPosition = (containerRect, targetRect, popoverRect, positions, previousPositionInfo) => {
const defaultAllowPositions = getDefaultPositions();
let withOrder = true;
let allowPositions = getAllowPositions(positions, defaultAllowPositions);
if (!allowPositions) {
allowPositions = defaultAllowPositions;
withOrder = false;
}
// console.log('withOrder', withOrder);
// const start_time = performance.now();
const infoMap = {};
allowPositions.forEach((k, i) => {
infoMap[k] = {
position: k,
index: i,
top: 0,
left: 0,
width: popoverRect.width,
height: popoverRect.height,
space: 0,
offset: 0,
passed: 0,
distance: 0
};
});
// log('infoMap', performance.now() - start_time);
const bestPosition = calculateBestPosition(containerRect, targetRect, infoMap, withOrder, previousPositionInfo);
// check left/top
bestPosition.changed = isPositionChanged(bestPosition, previousPositionInfo);
return bestPosition;
};
// ===========================================================================================
const getTemplatePath = (width, height, arrowOffset, arrowSize, borderRadius) => {
const p = (px, py) => {
return [px, py].join(',');
};
const px = function(num, alignEnd) {
const floor = Math.floor(num);
let n = num < floor + 0.5 ? floor + 0.5 : floor + 1.5;
if (alignEnd) {
n -= 1;
}
return n;
};
const pxe = function(num) {
return px(num, true);
};
const ls = [];
const innerLeft = px(arrowSize);
const innerRight = pxe(width - arrowSize);
arrowOffset = clamp(arrowOffset, innerLeft, innerRight);
const innerTop = px(arrowSize);
const innerBottom = pxe(height - arrowSize);
const startPoint = p(innerLeft, innerTop + borderRadius);
const arrowPoint = p(arrowOffset, 1);
const LT = p(innerLeft, innerTop);
const RT = p(innerRight, innerTop);
const AOT = p(arrowOffset - arrowSize, innerTop);
const RRT = p(innerRight - borderRadius, innerTop);
ls.push(`M${startPoint}`);
ls.push(`V${innerBottom - borderRadius}`);
ls.push(`Q${p(innerLeft, innerBottom)} ${p(innerLeft + borderRadius, innerBottom)}`);
ls.push(`H${innerRight - borderRadius}`);
ls.push(`Q${p(innerRight, innerBottom)} ${p(innerRight, innerBottom - borderRadius)}`);
ls.push(`V${innerTop + borderRadius}`);
if (arrowOffset < innerLeft + arrowSize + borderRadius) {
ls.push(`Q${RT} ${RRT}`);
ls.push(`H${arrowOffset + arrowSize}`);
ls.push(`L${arrowPoint}`);
if (arrowOffset < innerLeft + arrowSize) {
ls.push(`L${LT}`);
ls.push(`L${startPoint}`);
} else {
ls.push(`L${AOT}`);
ls.push(`Q${LT} ${startPoint}`);
}
} else if (arrowOffset > innerRight - arrowSize - borderRadius) {
if (arrowOffset > innerRight - arrowSize) {
ls.push(`L${RT}`);
} else {
ls.push(`Q${RT} ${p(arrowOffset + arrowSize, innerTop)}`);
}
ls.push(`L${arrowPoint}`);
ls.push(`L${AOT}`);
ls.push(`H${innerLeft + borderRadius}`);
ls.push(`Q${LT} ${startPoint}`);
} else {
ls.push(`Q${RT} ${RRT}`);
ls.push(`H${arrowOffset + arrowSize}`);
ls.push(`L${arrowPoint}`);
ls.push(`L${AOT}`);
ls.push(`H${innerLeft + borderRadius}`);
ls.push(`Q${LT} ${startPoint}`);
}
return ls.join('');
};
const getPathData = function(position, width, height, arrowOffset, arrowSize, borderRadius) {
const handlers = {
bottom: () => {
const d = getTemplatePath(width, height, arrowOffset, arrowSize, borderRadius);
return {
d,
transform: ''
};
},
top: () => {
const d = getTemplatePath(width, height, width - arrowOffset, arrowSize, borderRadius);
return {
d,
transform: `rotate(180,${width * 0.5},${height * 0.5})`
};
},
left: () => {
const d = getTemplatePath(height, width, arrowOffset, arrowSize, borderRadius);
const x = (width - height) * 0.5;
const y = (height - width) * 0.5;
return {
d,
transform: `translate(${x} ${y}) rotate(90,${height * 0.5},${width * 0.5})`
};
},
right: () => {
const d = getTemplatePath(height, width, height - arrowOffset, arrowSize, borderRadius);
const x = (width - height) * 0.5;
const y = (height - width) * 0.5;
return {
d,
transform: `translate(${x} ${y}) rotate(-90,${height * 0.5},${width * 0.5})`
};
}
};
return handlers[position]();
};
// ===========================================================================================
// position style cache
const styleCache = {
// position: '',
// top: {},
// bottom: {},
// left: {},
// right: {}
};
export const getPositionStyle = (info, options = {}) => {
const o = {
bgColor: '#fff',
borderColor: '#ccc',
borderRadius: 5,
arrowSize: 10
};
Object.keys(o).forEach((k) => {
if (hasOwn(options, k)) {
const d = o[k];
const v = options[k];
if (typeof d === 'string') {
// string
if (typeof v === 'string' && v) {
o[k] = v;
}
} else {
// number
if (isNum(v) && v >= 0) {
o[k] = v;
}
}
}
});
const key = [
info.width,
info.height,
info.offset,
o.arrowSize,
o.borderRadius,
o.bgColor,
o.borderColor
].join('-');
const positionCache = styleCache[info.position];
if (positionCache && key === positionCache.key) {
const st = positionCache.style;
st.changed = styleCache.position !== info.position;
styleCache.position = info.position;
return st;
}
// console.log(options);
const data = getPathData(info.position, info.width, info.height, info.offset, o.arrowSize, o.borderRadius);
// console.log(data);
const viewBox = [0, 0, info.width, info.height].join(' ');
const svg = [
`<svg viewBox="${viewBox}" xmlns="http://www.w3.org/2000/svg">`,
`<path d="${data.d}" fill="${o.bgColor}" stroke="${o.borderColor}" transform="${data.transform}" />`,
'</svg>'
].join('');
// console.log(svg);
const backgroundImage = `url("data:image/svg+xml;charset=utf8,${encodeURIComponent(svg)}")`;
const background = `${backgroundImage} center no-repeat`;
const padding = `${o.arrowSize + o.borderRadius}px`;
const style = {
background,
backgroundImage,
padding,
changed: true
};
styleCache.position = info.position;
styleCache[info.position] = {
key,
style
};
return style;
};

View File

@@ -3,21 +3,12 @@
* - custom node pack version to all custom nodes used in the workflow
*
* Example metadata:
* "nodes": {
* "1": {
* type: "CheckpointLoaderSimple",
* ...
* properties: {
* cnr_id: "comfy-core",
* version: "0.3.8",
* },
* },
* }
*
* @typedef {Object} NodeInfo
* @property {string} ver - Version (git hash or semantic version)
* @property {string} cnr_id - ComfyRegistry node ID
* @property {boolean} enabled - Whether the node is enabled
"extra": {
"node_versions": {
"comfy-core": "v0.3.8-4-g0b2eb7f",
"comfyui-easy-use": "1.2.5"
}
},
*/
import { app } from "../../scripts/app.js";
@@ -32,7 +23,7 @@ class WorkflowMetadataExtension {
/**
* Get the installed nodes info
* @returns {Promise<Record<string, NodeInfo>>} The mapping from node name to its info.
* @returns {Promise<Record<string, {ver: string, cnr_id: string, enabled: boolean}>>} The mapping from node name to its info.
* ver can either be a git commit hash or a semantic version such as "1.0.0"
* cnr_id is the id of the node in the ComfyRegistry
* enabled is true if the node is enabled, false if it is disabled
@@ -42,42 +33,61 @@ class WorkflowMetadataExtension {
return await res.json();
}
async init() {
this.installedNodes = await this.getInstalledNodes();
this.comfyCoreVersion = (await api.getSystemStats()).system.comfyui_version;
/**
* Get the node versions for the given graph
* @param {LGraph} graph The graph to get the node versions for
* @returns {Promise<Record<string, string>>} The mapping from node name to version
*/
getGraphNodeVersions(graph) {
const nodeVersions = {};
for (const node of graph.nodes) {
const nodeData = node.constructor.nodeData;
// Frontend only nodes don't have nodeData
if (!nodeData) {
continue;
}
const modules = nodeData.python_module.split(".");
if (modules[0] === "custom_nodes") {
const nodePackageName = modules[1];
const nodeInfo =
this.installedNodes[nodePackageName] ??
this.installedNodes[nodePackageName.toLowerCase()];
if (nodeInfo) {
nodeVersions[nodePackageName] = nodeInfo.ver;
}
} else if (["nodes", "comfy_extras"].includes(modules[0])) {
nodeVersions["comfy-core"] = this.comfyCoreVersion;
} else {
console.warn(`Unknown node source: ${nodeData.python_module}`);
}
}
return nodeVersions;
}
/**
* Called when any node is created
* @param {LGraphNode} node The newly created node
*/
nodeCreated(node) {
try {
// nodeData doesn't exist if node is missing or node is frontend only node
if (!node?.constructor?.nodeData?.python_module) return;
async init() {
const extension = this;
this.installedNodes = await this.getInstalledNodes();
this.comfyCoreVersion = (await api.getSystemStats()).system.comfyui_version;
const nodeProperties = (node.properties ??= {});
const modules = node.constructor.nodeData.python_module.split(".");
const moduleType = modules[0];
// Attach metadata when app.graphToPrompt is called.
const originalSerialize = LGraph.prototype.serialize;
LGraph.prototype.serialize = function () {
const workflow = originalSerialize.apply(this, arguments);
if (moduleType === "custom_nodes") {
const nodePackageName = modules[1];
const { cnr_id, aux_id, ver } =
this.installedNodes[nodePackageName] ??
this.installedNodes[nodePackageName.toLowerCase()] ??
{};
if (cnr_id === "comfy-core") return; // don't allow hijacking comfy-core name
if (cnr_id) nodeProperties.cnr_id = cnr_id;
else nodeProperties.aux_id = aux_id;
if (ver) nodeProperties.ver = ver.trim();
} else if (["nodes", "comfy_extras", "comfy_api_nodes"].includes(moduleType)) {
nodeProperties.cnr_id = "comfy-core";
nodeProperties.ver = this.comfyCoreVersion;
// Add metadata to the workflow
if (!workflow.extra) {
workflow.extra = {};
}
} catch (e) {
console.error(e);
}
const graph = this;
try {
workflow.extra["node_versions"] = extension.getGraphNodeVersions(graph);
} catch (e) {
console.error(e);
}
return workflow;
};
}
}

View File

@@ -749,8 +749,8 @@
"save_path": "loras/HyperSD/SDXL",
"description": "Hyper-SD LoRA (4steps) - SDXL",
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
"filename": "Hyper-SDXL-4steps-lora.safetensors",
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-4steps-lora.safetensors",
"filename": "Hyper-SD15-4steps-lora.safetensors",
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-4steps-lora.safetensors",
"size": "787MB"
},
{
@@ -1068,28 +1068,18 @@
"size": "19.1GB"
},
{
"name": "Comfy-Org/clip_l",
"name": "comfyanonymous/clip_l",
"type": "clip",
"base": "clip",
"save_path": "default",
"description": "clip_l model (for SD1.x, SD2.x, SDXL, SD3.5, FLUX.1, HunyuanVideo, ...) ",
"reference": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8",
"description": "clip_l model",
"reference": "https://huggingface.co/comfyanonymous/flux_text_encoders/tree/main",
"filename": "clip_l.safetensors",
"url": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8/resolve/main/text_encoders/clip_l.safetensors",
"url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/clip_l.safetensors",
"size": "246MB"
},
{
"name": "Comfy-Org/clip_g",
"type": "clip",
"base": "clip",
"save_path": "default",
"description": "clip_g model (for SDXL, SD3.5)",
"reference": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8",
"filename": "clip_g.safetensors",
"url": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8/resolve/main/text_encoders/clip_g.safetensors",
"size": "1.39GB"
},
{
"name": "v1-5-pruned-emaonly.ckpt",
@@ -3960,17 +3950,6 @@
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/vae/hunyuan_video_vae_bf16.safetensors",
"size": "493MB"
},
{
"name": "Comfy-Org/hunyuan_video_image_to_video_720p_bf16.safetensors",
"type": "diffusion_model",
"base": "Hunyuan Video",
"save_path": "diffusion_models/hunyuan_video",
"description": "Huyuan Video Image2Video diffusion model. repackaged version.",
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
"filename": "hunyuan_video_image_to_video_720p_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/diffusion_models/hunyuan_video_image_to_video_720p_bf16.safetensors",
"size": "25.6GB"
},
{
"name": "Comfy-Org/llava_llama3_fp8_scaled.safetensors",
@@ -3994,17 +3973,6 @@
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/text_encoders/llava_llama3_fp16.safetensors",
"size": "16.1GB"
},
{
"name": "Comfy-Org/llava_llama3_vision.safetensors",
"type": "clip_vision",
"base": "LLaVA-Llama-3",
"save_path": "text_encoders",
"description": "llava_llama3_vision clip vison model. This is required for using Hunyuan Video Image2Video.",
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
"filename": "llava_llama3_vision.safetensors",
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/clip_vision/llava_llama3_vision.safetensors",
"size": "649MB"
},
{
"name": "FLUX.1 [Schnell] Diffusion model",
@@ -4569,17 +4537,6 @@
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.1.safetensors",
"size": "5.72GB"
},
{
"name": "LTX-Video 2B v0.9.5 Checkpoint",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "LTX-Video is the first DiT-based video generation model capable of generating high-quality videos in real-time. It produces 24 FPS videos at a 768x512 resolution faster than they can be watched. Trained on a large-scale dataset of diverse videos, the model generates high-resolution videos with realistic and varied content.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltx-video-2b-v0.9.5.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.5.safetensors",
"size": "6.34GB"
},
{
"name": "XLabs-AI/flux-canny-controlnet-v3.safetensors",
@@ -4728,332 +4685,6 @@
"filename": "<huggingface>",
"url": "deepseek-ai/Janus-Pro-7B",
"size": "14.85GB"
},
{
"name": "kolors/vae/diffusion_pytorch_model.fp16.safetensors",
"type": "VAE",
"base": "Kolors",
"save_path": "vae/kolors",
"description": "Kolors VAE",
"reference": "https://huggingface.co/Kwai-Kolors/Kolors",
"filename": "diffusion_pytorch_model.fp16.safetensors",
"url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.fp16.safetensors",
"size": "167MB"
},
{
"name": "kolors/vae/diffusion_pytorch_model.safetensors",
"type": "VAE",
"base": "Kolors",
"save_path": "vae/kolors",
"description": "Kolors VAE",
"reference": "https://huggingface.co/Kwai-Kolors/Kolors",
"filename": "diffusion_pytorch_model.safetensors",
"url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.safetensors",
"size": "335MB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (bf16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 480p 14B (bf16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_480p_14B_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_bf16.safetensors",
"size": "32.8GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 480p 14B (fp16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_480p_14B_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp16.safetensors",
"size": "32.8GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_e4m3fn)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 480p 14B (fp8_e4m3fn)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors",
"size": "16.4GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_scaled)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 480p 14B (fp8_scaled)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_480p_14B_fp8_scaled.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_scaled.safetensors",
"size": "16.4GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (bf16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 720p 14B (bf16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_720p_14B_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_bf16.safetensors",
"size": "32.8GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 720p 14B (fp16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_720p_14B_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp16.safetensors",
"size": "32.8GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_e4m3fn)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 720p 14B (fp8_e4m3fn)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors",
"size": "16.4GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_scaled)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 720p 14B (fp8_scaled)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_720p_14B_fp8_scaled.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_scaled.safetensors",
"size": "16.4GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 1.3B (bf16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 1.3B (bf16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_1.3B_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_bf16.safetensors",
"size": "2.84GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 1.3B (fp16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 1.3B (fp16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_1.3B_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_fp16.safetensors",
"size": "2.84GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 14B (bf16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 14B (bf16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_14B_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_bf16.safetensors",
"size": "28.6GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 14B (fp16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 14B (fp16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_14B_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp16.safetensors",
"size": "28.6GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 14B (fp8_e4m3fn)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 14B (fp8_e4m3fn)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_14B_fp8_e4m3fn.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_e4m3fn.safetensors",
"size": "14.3GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 14B (fp8_scaled)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 14B (fp8_scaled)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_14B_fp8_scaled.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_scaled.safetensors",
"size": "14.3GB"
},
{
"name": "Comfy-Org/Wan2.1 VAE",
"type": "vae",
"base": "Wan2.1",
"save_path": "vae",
"description": "Wan2.1 VAE model",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan_2.1_vae.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors",
"size": "254MB"
},
{
"name": "Comfy-Org/clip_vision_h.safetensors",
"type": "clip_vision",
"base": "clip_vision_h",
"save_path": "clip_vision",
"description": "clip_vision_h model for Wan2.1",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "clip_vision_h.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors",
"size": "1.26GB"
},
{
"name": "Comfy-Org/umt5_xxl_fp16.safetensors",
"type": "clip",
"base": "umt5_xxl",
"save_path": "text_encoders",
"description": "umt5_xxl_fp16 text encoder for Wan2.1",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "umt5_xxl_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp16.safetensors",
"size": "11.4GB"
},
{
"name": "Comfy-Org/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"type": "clip",
"base": "umt5_xxl",
"save_path": "text_encoders",
"description": "umt5_xxl_fp8_e4m3fn_scaled text encoder for Wan2.1",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"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",
"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

@@ -1,95 +0,0 @@
# ComfyUI-Manager: Node Database (node_db)
This directory contains the JSON database files that power ComfyUI-Manager's legacy node registry system. While the manager is gradually transitioning to the online Custom Node Registry (CNR), these local JSON files continue to provide important metadata about custom nodes, models, and their integrations.
## Directory Structure
The node_db directory is organized into several subdirectories, each serving a specific purpose:
- **dev/**: Development channel files with latest additions and experimental nodes
- **legacy/**: Historical/legacy nodes that may require special handling
- **new/**: New nodes that have passed initial verification but are still being evaluated
- **forked/**: Forks of existing nodes with modifications
- **tutorial/**: Example and tutorial nodes designed for learning purposes
## Core Database Files
Each subdirectory contains a standard set of JSON files:
- **custom-node-list.json**: Primary database of custom nodes with metadata
- **extension-node-map.json**: Maps between extensions and individual nodes they provide
- **model-list.json**: Catalog of models that can be downloaded through the manager
- **alter-list.json**: Alternative implementations of nodes for compatibility or functionality
- **github-stats.json**: GitHub repository statistics for node popularity metrics
## Database Schema
### custom-node-list.json
```json
{
"custom_nodes": [
{
"title": "Node display name",
"name": "Repository name",
"reference": "Original repository if forked",
"files": ["GitHub URL or other source location"],
"install_type": "git",
"description": "Description of the node's functionality",
"pip": ["optional pip dependencies"],
"js": ["optional JavaScript files"],
"tags": ["categorization tags"]
}
]
}
```
### extension-node-map.json
```json
{
"extension-id": [
["list", "of", "node", "classes"],
{
"author": "Author name",
"description": "Extension description",
"nodename_pattern": "Optional regex pattern for node name matching"
}
]
}
```
## Transition to Custom Node Registry (CNR)
This local database system is being progressively replaced by the online Custom Node Registry (CNR), which provides:
- Real-time updates without manual JSON maintenance
- Improved versioning support
- Better security validation
- Enhanced metadata
The Manager supports both systems simultaneously during the transition period.
## Implementation Details
- The database follows a channel-based architecture for different sources
- Multiple database modes are supported: Channel, Local, and Remote
- The system supports differential updates to minimize bandwidth usage
- Security levels are enforced for different node installations based on source
## Usage in the Application
The Manager's backend uses these database files to:
1. Provide browsable lists of available nodes and models
2. Resolve dependencies for installation
3. Track updates and new versions
4. Map node classes to their source repositories
5. Assess risk levels for installation security
## Maintenance Scripts
Each subdirectory contains a `scan.sh` script that assists with:
- Scanning repositories for new nodes
- Updating metadata
- Validating database integrity
- Generating proper JSON structures
This database system enables a flexible, secure, and comprehensive management system for the ComfyUI ecosystem while the transition to CNR continues.

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

@@ -1,25 +1,5 @@
{
"custom_nodes": [
{
"author": "joaomede",
"title": "ComfyUI-Unload-Model-Fork",
"reference": "https://github.com/joaomede/ComfyUI-Unload-Model-Fork",
"files": [
"https://github.com/joaomede/ComfyUI-Unload-Model-Fork"
],
"install_type": "git-clone",
"description": "For unloading a model or all models, using the memory management that is already present in ComfyUI. Copied from [a/https://github.com/willblaschko/ComfyUI-Unload-Models](https://github.com/willblaschko/ComfyUI-Unload-Models) but without the unnecessary extra stuff."
},
{
"author": "SanDiegoDude",
"title": "ComfyUI-HiDream-Sampler [WIP]",
"reference": "https://github.com/SanDiegoDude/ComfyUI-HiDream-Sampler",
"files": [
"https://github.com/SanDiegoDude/ComfyUI-HiDream-Sampler"
],
"install_type": "git-clone",
"description": "A collection of enhanced nodes for ComfyUI that provide powerful additional functionality to your workflows.\nNOTE: The files in the repo are not organized."
},
{
"author": "PramaLLC",
"title": "ComfyUI BEN - Background Erase Network",
@@ -149,16 +129,6 @@
],
"install_type": "git-clone",
"description": "A forked version of ComfyUI_ExtraModels. (modified by Efficient-Large-Model)"
},
{
"author": "Pablerdo",
"title": "ComfyUI-PSNodes",
"reference": "https://github.com/Pablerdo/ComfyUI-PSNodes",
"files": [
"https://github.com/Pablerdo/ComfyUI-PSNodes"
],
"install_type": "git-clone",
"description": "A fork of KJNodes for ComfyUI.\nVarious quality of life -nodes for ComfyUI, mostly just visual stuff to improve usability"
}
]
}

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

@@ -1,366 +1,5 @@
{
"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)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 480p 14B (bf16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_480p_14B_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_bf16.safetensors",
"size": "32.8GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 480p 14B (fp16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_480p_14B_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp16.safetensors",
"size": "32.8GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_e4m3fn)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 480p 14B (fp8_e4m3fn)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors",
"size": "16.4GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_scaled)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 480p 14B (fp8_scaled)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_480p_14B_fp8_scaled.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_scaled.safetensors",
"size": "16.4GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (bf16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 720p 14B (bf16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_720p_14B_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_bf16.safetensors",
"size": "32.8GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 720p 14B (fp16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_720p_14B_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp16.safetensors",
"size": "32.8GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_e4m3fn)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 720p 14B (fp8_e4m3fn)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors",
"size": "16.4GB"
},
{
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_scaled)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for i2v 720p 14B (fp8_scaled)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_i2v_720p_14B_fp8_scaled.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_scaled.safetensors",
"size": "16.4GB"
},
{
"name": "Comfy-Org/clip_vision_h.safetensors",
"type": "clip_vision",
"base": "clip_vision_h",
"save_path": "clip_vision",
"description": "clip_vision_h model for Wan2.1",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "clip_vision_h.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors",
"size": "1.26GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 1.3B (bf16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 1.3B (bf16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_1.3B_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_bf16.safetensors",
"size": "2.84GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 1.3B (fp16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 1.3B (fp16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_1.3B_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_fp16.safetensors",
"size": "2.84GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 14B (bf16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 14B (bf16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_14B_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_bf16.safetensors",
"size": "28.6GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 14B (fp16)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 14B (fp16)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_14B_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp16.safetensors",
"size": "28.6GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 14B (fp8_e4m3fn)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 14B (fp8_e4m3fn)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_14B_fp8_e4m3fn.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_e4m3fn.safetensors",
"size": "14.3GB"
},
{
"name": "Comfy-Org/Wan2.1 t2v 14B (fp8_scaled)",
"type": "diffusion_model",
"base": "Wan2.1",
"save_path": "diffusion_models/Wan2.1",
"description": "Wan2.1 difussion model for t2v 14B (fp8_scaled)",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan2.1_t2v_14B_fp8_scaled.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_scaled.safetensors",
"size": "14.3GB"
},
{
"name": "Comfy-Org/Wan2.1 VAE",
"type": "vae",
"base": "Wan2.1",
"save_path": "vae",
"description": "Wan2.1 VAE model",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "wan_2.1_vae.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors",
"size": "254MB"
},
{
"name": "Comfy-Org/umt5_xxl_fp16.safetensors",
"type": "clip",
"base": "umt5_xxl",
"save_path": "text_encoders",
"description": "umt5_xxl_fp16 text encoder for Wan2.1",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"filename": "umt5_xxl_fp16.safetensors",
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp16.safetensors",
"size": "11.4GB"
},
{
"name": "Comfy-Org/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"type": "clip",
"base": "umt5_xxl",
"save_path": "text_encoders",
"description": "umt5_xxl_fp8_e4m3fn_scaled text encoder for Wan2.1",
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
"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",
"size": "6.74GB"
},
{
"name": "Comfy-Org/hunyuan_video_image_to_video_720p_bf16.safetensors",
"type": "diffusion_model",
"base": "Hunyuan Video",
"save_path": "diffusion_models/hunyuan_video",
"description": "Huyuan Video Image2Video diffusion model. repackaged version.",
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
"filename": "hunyuan_video_image_to_video_720p_bf16.safetensors",
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/diffusion_models/hunyuan_video_image_to_video_720p_bf16.safetensors",
"size": "25.6GB"
},
{
"name": "Comfy-Org/llava_llama3_vision.safetensors",
"type": "clip_vision",
"base": "LLaVA-Llama-3",
"save_path": "text_encoders",
"description": "llava_llama3_vision clip vison model. This is required for using Hunyuan Video Image2Video.",
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
"filename": "llava_llama3_vision.safetensors",
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/clip_vision/llava_llama3_vision.safetensors",
"size": "649MB"
},
{
"name": "LTX-Video 2B v0.9.5 Checkpoint",
"type": "checkpoint",
"base": "LTX-Video",
"save_path": "checkpoints/LTXV",
"description": "LTX-Video is the first DiT-based video generation model capable of generating high-quality videos in real-time. It produces 24 FPS videos at a 768x512 resolution faster than they can be watched. Trained on a large-scale dataset of diverse videos, the model generates high-resolution videos with realistic and varied content.",
"reference": "https://huggingface.co/Lightricks/LTX-Video",
"filename": "ltx-video-2b-v0.9.5.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.5.safetensors",
"size": "6.34GB"
},
{
"name": "kolors/vae/diffusion_pytorch_model.fp16.safetensors",
"type": "VAE",
"base": "Kolors",
"save_path": "vae/kolors",
"description": "Kolors VAE",
"reference": "https://huggingface.co/Kwai-Kolors/Kolors",
"filename": "diffusion_pytorch_model.fp16.safetensors",
"url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.fp16.safetensors",
"size": "167MB"
},
{
"name": "kolors/vae/diffusion_pytorch_model.safetensors",
"type": "VAE",
"base": "Kolors",
"save_path": "vae/kolors",
"description": "Kolors VAE",
"reference": "https://huggingface.co/Kwai-Kolors/Kolors",
"filename": "diffusion_pytorch_model.safetensors",
"url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.safetensors",
"size": "335MB"
},
{
"name": "deepseek-ai/Janus-Pro-1B",
"type": "Janus-Pro",
@@ -691,6 +330,349 @@
"filename": "sigclip_vision_patch14_384.safetensors",
"url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors",
"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"
},
{
"name": "CLIPVision model (Kwai-Kolors/Kolors-IP-Adapter-Plus/clip-vit-large)",
"type": "clip_vision",
"base": "ViT-L",
"save_path": "clip_vision",
"description": "CLIPVision model (This is required in cubiq/ComfyUI_IPAdapter_plus)",
"reference": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus",
"filename": "clip-vit-large-patch14-336.bin",
"url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus/resolve/main/image_encoder/pytorch_model.bin",
"size": "1.71GB"
},
{
"name": "kijai/lotus depth d model v1.1 (fp16)",
"type": "diffusion_model",
"base": "lotus",
"save_path": "diffusion_models",
"description": "lotus depth d model v1.1 (fp16). This model can be used in ComfyUI-Lotus custom nodes.",
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
"filename": "lotus-depth-d-v-1-1-fp16.safetensors",
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-depth-d-v-1-1-fp16.safetensors",
"size": "1.74GB"
},
{
"name": "kijai/lotus depth g model v1.0 (fp16)",
"type": "diffusion_model",
"base": "lotus",
"save_path": "diffusion_models",
"description": "lotus depth g model v1.0 (fp16). This model can be used in ComfyUI-Lotus custom nodes.",
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
"filename": "lotus-depth-g-v1-0-fp16.safetensors",
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-depth-g-v1-0-fp16.safetensors",
"size": "1.74GB"
},
{
"name": "kijai/lotus depth g model v1.0",
"type": "diffusion_model",
"base": "lotus",
"save_path": "diffusion_models",
"description": "lotus depth g model v1.0. This model can be used in ComfyUI-Lotus custom nodes.",
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
"filename": "lotus-depth-g-v1-0.safetensors",
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-depth-g-v1-0.safetensors",
"size": "3.47GB"
},
{
"name": "kijai/lotus normal d model v1.0 (fp16)",
"type": "diffusion_model",
"base": "lotus",
"save_path": "diffusion_models",
"description": "lotus normal d model v1.0 (fp16). This model can be used in ComfyUI-Lotus custom nodes.",
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
"filename": "lotus-normal-d-v1-0-fp16.safetensors",
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-d-v1-0-fp16.safetensors",
"size": "1.74GB"
},
{
"name": "kijai/lotus normal d model v1.0",
"type": "diffusion_model",
"base": "lotus",
"save_path": "diffusion_models",
"description": "lotus normal d model v1.0. This model can be used in ComfyUI-Lotus custom nodes.",
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
"filename": "lotus-normal-d-v1-0.safetensors",
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-d-v1-0.safetensors",
"size": "3.47GB"
},
{
"name": "kijai/lotus normal g model v1.0 (fp16)",
"type": "diffusion_model",
"base": "lotus",
"save_path": "diffusion_models",
"description": "lotus normal g model v1.0 (fp16). This model can be used in ComfyUI-Lotus custom nodes.",
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
"filename": "lotus-normal-g-v1-0-fp16.safetensors",
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-g-v1-0-fp16.safetensors",
"size": "1.74GB"
},
{
"name": "kijai/lotus normal g model v1.0",
"type": "diffusion_model",
"base": "lotus",
"save_path": "diffusion_models",
"description": "lotus normal g model v1.0. This model can be used in ComfyUI-Lotus custom nodes.",
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
"filename": "lotus-normal-g-v1-0.safetensors",
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-g-v1-0.safetensors",
"size": "3.47GB"
},
{
"name": "Depth Pro model",
"type": "depth-pro",
"base": "depth-pro",
"save_path": "depth/ml-depth-pro",
"description": "Depth pro model for [a/ComfyUI-Depth-Pro](https://github.com/spacepxl/ComfyUI-Depth-Pro)",
"reference": "https://huggingface.co/spacepxl/ml-depth-pro",
"filename": "depth_pro.fp16.safetensors",
"url": "https://huggingface.co/spacepxl/ml-depth-pro/resolve/main/depth_pro.fp16.safetensors",
"size": "1.9GB"
},
{
"name": "jasperai/FLUX.1-dev-Controlnet-Upscaler",
"type": "controlnet",
"base": "FLUX.1",
"save_path": "controlnet/FLUX.1/jasperai-dev-Upscaler",
"description": "This is Flux.1-dev ControlNet for low resolution images developed by Jasper research team.",
"reference": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Upscaler",
"filename": "diffusion_pytorch_model.safetensors",
"url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Upscaler/resolve/main/diffusion_pytorch_model.safetensors",
"size": "3.58GB"
},
{
"name": "jasperai/FLUX.1-dev-Controlnet-Depth",
"type": "controlnet",
"base": "FLUX.1",
"save_path": "controlnet/FLUX.1/jasperai-dev-Depth",
"description": "This is Flux.1-dev ControlNet for Depth map developed by Jasper research team.",
"reference": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Depth",
"filename": "diffusion_pytorch_model.safetensors",
"url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Depth/resolve/main/diffusion_pytorch_model.safetensors",
"size": "3.58GB"
},
{
"name": "jasperai/Flux.1-dev-Controlnet-Surface-Normals",
"type": "controlnet",
"base": "FLUX.1",
"save_path": "controlnet/FLUX.1/jasperai-dev-Surface-Normals",
"description": "This is Flux.1-dev ControlNet for Surface Normals map developed by Jasper research team.",
"reference": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Surface-Normals",
"filename": "diffusion_pytorch_model.safetensors",
"url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Surface-Normals/resolve/main/diffusion_pytorch_model.safetensors",
"size": "3.58GB"
},
{
"name": "Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro (fp8_e4m3fn) by Kijai",
"type": "controlnet",
"base": "FLUX.1",
"save_path": "controlnet/FLUX.1",
"description": "FLUX.1 [Dev] Union Controlnet. Supports Canny, Tile, Depth, Blur, Pose, Gray, Low Quality\nVersion quantized to fp8_e4m3fn by Kijai",
"reference": "https://huggingface.co/Kijai/flux-fp8",
"filename": "flux_shakker_labs_union_pro-fp8_e4m3fn.safetensors",
"url": "https://huggingface.co/Kijai/flux-fp8/resolve/main/flux_shakker_labs_union_pro-fp8_e4m3fn.safetensors",
"size": "3.3GB"
},
{
"name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]",
"type": "clip",
"base": "clip",
"save_path": "text_encoders/long_clip",
"description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)",
"reference": "https://huggingface.co/zer0int",
"filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors",
"url": "https://huggingface.co/zer0int/CLIP-GmP-ViT-L-14/resolve/main/ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors",
"size": "931MB"
},
{
"name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]",
"type": "clip",
"base": "clip",
"save_path": "text_encoders/long_clip",
"description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)",
"reference": "https://huggingface.co/zer0int",
"filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors",
"url": "https://huggingface.co/zer0int/CLIP-GmP-ViT-L-14/resolve/main/ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors",
"size": "323MB"
},
{
"name": "Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro",
"type": "controlnet",
"base": "FLUX.1",
"save_path": "controlnet/FLUX.1/Shakker-Labs-ControlNet-Union-Pro",
"description": "FLUX.1 [Dev] Union Controlnet. Supports Canny, Tile, Depth, Blur, Pose, Gray, Low Quality",
"reference": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro",
"filename": "diffusion_pytorch_model.safetensors",
"url": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro/resolve/main/diffusion_pytorch_model.safetensors",
"size": "6.6GB"
},
{
"name": "Hyper-SD LoRA (8steps) - FLUX.1 [Dev]",
"type": "lora",
"base": "FLUX.1",
"save_path": "loras/HyperSD/FLUX.1",
"description": "Hyper-SD LoRA (8steps) - FLUX.1 [Dev]",
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
"filename": "Hyper-FLUX.1-dev-8steps-lora.safetensors",
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-FLUX.1-dev-8steps-lora.safetensors",
"size": "1.39GB"
},
{
"name": "Hyper-SD LoRA (16steps) - FLUX.1 [Dev]",
"type": "lora",
"base": "FLUX.1",
"save_path": "loras/HyperSD/FLUX.1",
"description": "Hyper-SD LoRA (16steps) - FLUX.1 [Dev]",
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
"filename": "Hyper-FLUX.1-dev-16steps-lora.safetensors",
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-FLUX.1-dev-16steps-lora.safetensors",
"size": "1.39GB"
},
{
"name": "DMD2 LoRA (4steps)",
"type": "lora",
"base": "SDXL",
"save_path": "loras/DMD2",
"description": "DMD2 LoRA (4steps)",
"reference": "https://huggingface.co/tianweiy/DMD2",
"filename": "dmd2_sdxl_4step_lora.safetensors",
"url": "https://huggingface.co/tianweiy/DMD2/resolve/main/dmd2_sdxl_4step_lora.safetensors",
"size": "787MB"
},
{
"name": "DMD2 LoRA (4steps/fp16)",
"type": "lora",
"base": "SDXL",
"save_path": "loras/DMD2",
"description": "DMD2 LoRA (4steps/fp16)",
"reference": "https://huggingface.co/tianweiy/DMD2",
"filename": "dmd2_sdxl_4step_lora_fp16.safetensors",
"url": "https://huggingface.co/tianweiy/DMD2/resolve/main/dmd2_sdxl_4step_lora_fp16.safetensors",
"size": "394MB"
}
]
}

View File

@@ -1,15 +1,5 @@
{
"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",
"title": "Guide To Making Custom Nodes in ComfyUI",
@@ -301,36 +291,6 @@
],
"install_type": "git-clone",
"description": "Example of using ComfyUI Toolbar to Toggle ComfyUI links on/off"
},
{
"author": "xhiroga",
"title": "ComfyUI-TypeScript-CustomNode",
"reference": "https://github.com/xhiroga/ComfyUI-TypeScript-CustomNode",
"files": [
"https://github.com/xhiroga/ComfyUI-TypeScript-CustomNode"
],
"install_type": "git-clone",
"description": "This project is generated from xhiroga/ComfyUI-TypeScript-CustomNode"
},
{
"author": "zentrocdot",
"title": "ComfyUI-Turtle_Graphics_Demos",
"reference": "https://github.com/zentrocdot/ComfyUI-Turtle_Graphics_Demo",
"files": [
"https://github.com/zentrocdot/ComfyUI-Turtle_Graphics_Demo"
],
"description": "ComfyUI node for creating some Turtle Graphic demos.",
"install_type": "git-clone"
},
{
"author": "cozy-comfyui",
"title": "cozy_ex_dynamic",
"reference": "https://github.com/cozy-comfyui/cozy_ex_dynamic",
"files": [
"https://github.com/cozy-comfyui/cozy_ex_dynamic"
],
"description": "Dynamic Node examples for ComfyUI",
"install_type": "git-clone"
}
]
}

View File

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

@@ -1,5 +1,4 @@
import os
import shutil
import subprocess
import sys
import atexit
@@ -21,27 +20,22 @@ import cm_global
import manager_downloader
import folder_paths
manager_util.add_python_path_to_env()
import datetime as dt
if hasattr(dt, 'datetime'):
from datetime import datetime as dt_datetime
import datetime
if hasattr(datetime, 'datetime'):
from datetime import datetime
def current_timestamp():
return dt_datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
return datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
else:
# NOTE: Occurs in some Mac environments.
import time
logging.error(f"[ComfyUI-Manager] fallback timestamp mode\n datetime module is invalid: '{dt.__file__}'")
logging.error(f"[ComfyUI-Manager] fallback timestamp mode\n datetime module is invalid: '{datetime.__file__}'")
def current_timestamp():
return str(time.time()).split('.')[0]
security_check.security_check()
cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
cm_global.pip_blacklist = ['torch', 'torchsde', 'torchvision']
cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
def skip_pip_spam(x):
@@ -88,7 +82,6 @@ comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
custom_nodes_base_path = folder_paths.get_folder_paths('custom_nodes')[0]
manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'ComfyUI-Manager'))
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
manager_pip_blacklist_path = os.path.join(manager_files_path, "pip_blacklist.list")
restore_snapshot_path = os.path.join(manager_files_path, "startup-scripts", "restore-snapshot.json")
manager_config_path = os.path.join(manager_files_path, 'config.ini')
@@ -101,7 +94,7 @@ def read_config():
global default_conf
try:
import configparser
config = configparser.ConfigParser(strict=False)
config = configparser.ConfigParser()
config.read(manager_config_path)
default_conf = config['default']
except Exception:
@@ -121,25 +114,12 @@ read_config()
read_uv_mode()
check_file_logging()
if sys.version_info < (3, 13):
cm_global.pip_overrides = {'numpy': 'numpy<2'}
else:
cm_global.pip_overrides = {}
cm_global.pip_overrides = {'numpy': 'numpy<2', 'ultralytics': 'ultralytics==8.3.40'}
if os.path.exists(manager_pip_overrides_path):
with open(manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
cm_global.pip_overrides = json.load(json_file)
if sys.version_info < (3, 13):
cm_global.pip_overrides['numpy'] = 'numpy<2'
if os.path.exists(manager_pip_blacklist_path):
with open(manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:
for x in f.readlines():
y = x.strip()
if y != '':
cm_global.pip_blacklist.add(y)
cm_global.pip_overrides['numpy'] = 'numpy<2'
cm_global.pip_overrides['ultralytics'] = 'ultralytics==8.3.40' # for security
def remap_pip_package(pkg):
@@ -344,12 +324,7 @@ try:
log_file.write(message)
else:
log_file.write(f"[{timestamp}] {message}")
try:
log_file.flush()
except Exception:
pass
log_file.flush()
self.last_char = message if message == '' else message[-1]
if not file_only:
@@ -362,10 +337,7 @@ try:
original_stderr.flush()
def flush(self):
try:
log_file.flush()
except Exception:
pass
log_file.flush()
with std_log_lock:
if self.is_stdout:
@@ -449,33 +421,29 @@ except Exception as e:
print(f"[ComfyUI-Manager] Logging failed: {e}")
def ensure_dependencies():
try:
import git # noqa: F401
import toml # noqa: F401
import rich # noqa: F401
import chardet # noqa: F401
except ModuleNotFoundError:
my_path = os.path.dirname(__file__)
requirements_path = os.path.join(my_path, "requirements.txt")
try:
import git # noqa: F401
import toml # noqa: F401
import rich # noqa: F401
except ModuleNotFoundError:
my_path = os.path.dirname(__file__)
requirements_path = os.path.join(my_path, "requirements.txt")
print("## ComfyUI-Manager: installing dependencies. (GitPython)")
print("## ComfyUI-Manager: installing dependencies. (GitPython)")
try:
result = subprocess.check_output(manager_util.make_pip_cmd(['install', '-r', requirements_path]))
except subprocess.CalledProcessError:
print("## [ERROR] ComfyUI-Manager: Attempting to reinstall dependencies using an alternative method.")
try:
subprocess.check_output(manager_util.make_pip_cmd(['install', '-r', requirements_path]))
result = subprocess.check_output(manager_util.make_pip_cmd(['install', '--user', '-r', requirements_path]))
except subprocess.CalledProcessError:
print("## [ERROR] ComfyUI-Manager: Attempting to reinstall dependencies using an alternative method.")
try:
subprocess.check_output(manager_util.make_pip_cmd(['install', '--user', '-r', requirements_path]))
except subprocess.CalledProcessError:
print("## [ERROR] ComfyUI-Manager: Failed to install the GitPython package in the correct Python environment. Please install it manually in the appropriate environment. (You can seek help at https://app.element.io/#/room/%23comfyui_space%3Amatrix.org)")
print("## [ERROR] ComfyUI-Manager: Failed to install the GitPython package in the correct Python environment. Please install it manually in the appropriate environment. (You can seek help at https://app.element.io/#/room/%23comfyui_space%3Amatrix.org)")
try:
print("## ComfyUI-Manager: installing dependencies done.")
except:
# maybe we should sys.exit() here? there is at least two screens worth of error messages still being pumped after our error messages
print("## [ERROR] ComfyUI-Manager: GitPython package seems to be installed, but failed to load somehow. Make sure you have a working git client installed")
ensure_dependencies()
try:
print("## ComfyUI-Manager: installing dependencies done.")
except:
# maybe we should sys.exit() here? there is at least two screens worth of error messages still being pumped after our error messages
print("## [ERROR] ComfyUI-Manager: GitPython package seems to be installed, but failed to load somehow. Make sure you have a working git client installed")
print("** ComfyUI startup time:", current_timestamp())
@@ -507,6 +475,7 @@ def read_downgrade_blacklist():
read_downgrade_blacklist()
print("[DBG] point1")
def check_bypass_ssl():
try:
@@ -519,12 +488,14 @@ def check_bypass_ssl():
check_bypass_ssl()
print("[DBG] point2")
# Perform install
processed_install = set()
script_list_path = os.path.join(folder_paths.user_directory, "default", "ComfyUI-Manager", "startup-scripts", "install-scripts.txt")
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages())
print("[DBG] point3")
def is_installed(name):
name = name.strip()
@@ -576,6 +547,7 @@ def is_installed(name):
return True # prevent downgrade
print("[DBG] point4")
if os.path.exists(restore_snapshot_path):
try:
@@ -625,24 +597,24 @@ if os.path.exists(restore_snapshot_path):
def execute_lazy_install_script(repo_path, executable):
global processed_install
print("[DBG] point5")
install_script_path = os.path.join(repo_path, "install.py")
requirements_path = os.path.join(repo_path, "requirements.txt")
if os.path.exists(requirements_path):
print(f"Install: pip packages for '{repo_path}'")
with open(requirements_path, "r") as requirements_file:
for line in requirements_file:
package_name = remap_pip_package(line.strip())
if package_name and not is_installed(package_name):
if '--index-url' in package_name:
s = package_name.split('--index-url')
install_cmd = manager_util.make_pip_cmd(["install", s[0].strip(), '--index-url', s[1].strip()])
else:
install_cmd = manager_util.make_pip_cmd(["install", package_name])
lines = manager_util.robust_readlines(requirements_path)
for line in lines:
package_name = remap_pip_package(line.strip())
package_name = package_name.split('#')[0].strip()
if package_name and not is_installed(package_name):
if '--index-url' in package_name:
s = package_name.split('--index-url')
install_cmd = manager_util.make_pip_cmd(["install", s[0].strip(), '--index-url', s[1].strip()])
else:
install_cmd = manager_util.make_pip_cmd(["install", package_name])
process_wrap(install_cmd, repo_path)
process_wrap(install_cmd, repo_path)
if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in processed_install:
processed_install.add(f'{repo_path}/install.py')
@@ -659,6 +631,8 @@ def execute_lazy_cnr_switch(target, zip_url, from_path, to_path, no_deps, custom
import uuid
import shutil
print("[DBG] point6")
# 1. download
archive_name = f"CNR_temp_{str(uuid.uuid4())}.zip" # should be unpredictable name - security precaution
download_path = os.path.join(custom_nodes_path, archive_name)
@@ -706,43 +680,24 @@ def execute_lazy_cnr_switch(target, zip_url, from_path, to_path, no_deps, custom
file.write('\n'.join(list(extracted)))
script_executed = False
def execute_migration(moves):
import shutil
def execute_startup_script():
global script_executed
print("[DBG] point7")
for x in moves:
if os.path.exists(x[0]) and not os.path.exists(x[1]):
shutil.move(x[0], x[1])
print(f"[ComfyUI-Manager] MIGRATION: '{x[0]}' -> '{x[1]}'")
print("[DBG] point8")
# Check if script_list_path exists
if os.path.exists(script_list_path):
print("\n#######################################################################")
print("[ComfyUI-Manager] Starting dependency installation/(de)activation for the extension\n")
custom_nodelist_cache = None
def get_custom_node_paths():
nonlocal custom_nodelist_cache
if custom_nodelist_cache is None:
custom_nodelist_cache = set()
for base in folder_paths.get_folder_paths('custom_nodes'):
for x in os.listdir(base):
fullpath = os.path.join(base, x)
if os.path.isdir(fullpath):
custom_nodelist_cache.add(fullpath)
return custom_nodelist_cache
def execute_lazy_delete(path):
# Validate to prevent arbitrary paths from being deleted
if path not in get_custom_node_paths():
logging.error(f"## ComfyUI-Manager: The scheduled '{path}' is not a custom node path, so the deletion has been canceled.")
return
if not os.path.exists(path):
logging.info(f"## ComfyUI-Manager: SKIP-DELETE => '{path}' (already deleted)")
return
try:
shutil.rmtree(path)
logging.info(f"## ComfyUI-Manager: DELETE => '{path}'")
except Exception as e:
logging.error(f"## ComfyUI-Manager: Failed to delete '{path}' ({e})")
executed = set()
# Read each line from the file and convert it to a list using eval
with open(script_list_path, 'r', encoding="UTF-8", errors="ignore") as file:
@@ -763,8 +718,8 @@ def execute_startup_script():
execute_lazy_cnr_switch(script[0], script[2], script[3], script[4], script[5], script[6])
execute_lazy_install_script(script[3], script[7])
elif script[1] == "#LAZY-DELETE-NODEPACK":
execute_lazy_delete(script[2])
elif script[1] == "#LAZY-MIGRATION":
execute_migration(script[2])
elif os.path.exists(script[0]):
if script[1] == "#FORCE":
@@ -774,7 +729,7 @@ def execute_startup_script():
continue
print(f"\n## ComfyUI-Manager: EXECUTE => {script[1:]}")
print(f"\n## Execute management script for '{script[0]}'")
print(f"\n## Execute install/(de)activation script for '{script[0]}'")
new_env = os.environ.copy()
if 'COMFYUI_FOLDERS_BASE_PATH' not in new_env:
@@ -782,64 +737,37 @@ def execute_startup_script():
exit_code = process_wrap(script[1:], script[0], env=new_env)
if exit_code != 0:
print(f"management script failed: {script[0]}")
print(f"install/(de)activation script failed: {script[0]}")
else:
print(f"\n## ComfyUI-Manager: CANCELED => {script[1:]}")
except Exception as e:
print(f"[ERROR] Failed to execute management script: {line} / {e}")
print(f"[ERROR] Failed to execute install/(de)activation script: {line} / {e}")
# Remove the script_list_path file
if os.path.exists(script_list_path):
script_executed = True
os.remove(script_list_path)
print("\n[ComfyUI-Manager] Startup script completed.")
print("#######################################################################\n")
# Check if script_list_path exists
if os.path.exists(script_list_path):
execute_startup_script()
print("[DBG] point9")
pip_fixer.fix_broken()
print("[DBG] point10")
del processed_install
del pip_fixer
manager_util.clear_pip_cache()
if script_executed:
# Restart
print("[ComfyUI-Manager] Restarting to reapply dependency installation.")
if '__COMFY_CLI_SESSION__' in os.environ:
with open(os.path.join(os.environ['__COMFY_CLI_SESSION__'] + '.reboot'), 'w'):
pass
print("--------------------------------------------------------------------------\n")
exit(0)
else:
sys_argv = sys.argv.copy()
if sys_argv[0].endswith("__main__.py"): # this is a python module
module_name = os.path.basename(os.path.dirname(sys_argv[0]))
cmds = [sys.executable, '-m', module_name] + sys_argv[1:]
elif sys.platform.startswith('win32'):
cmds = ['"' + sys.executable + '"', '"' + sys_argv[0] + '"'] + sys_argv[1:]
else:
cmds = [sys.executable] + sys_argv
print(f"Command: {cmds}", flush=True)
print("--------------------------------------------------------------------------\n")
os.execv(sys.executable, cmds)
print("[DBG] point11")
def check_windows_event_loop_policy():
print("[DBG] point12")
try:
import configparser
config = configparser.ConfigParser(strict=False)
config = configparser.ConfigParser()
config.read(manager_config_path)
default_conf = config['default']
@@ -854,6 +782,10 @@ def check_windows_event_loop_policy():
except Exception:
pass
print("[DBG] point13")
if platform.system() == 'Windows':
check_windows_event_loop_policy()
print("[DBG] point14")

View File

@@ -1,9 +1,9 @@
[project]
name = "comfyui-manager"
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
version = "3.33.2"
version = "3.21"
license = { file = "LICENSE.txt" }
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]
[project.urls]
Repository = "https://github.com/ltdrdata/ComfyUI-Manager"

View File

@@ -8,4 +8,3 @@ rich
typing-extensions
toml
uv
chardet

View File

@@ -102,8 +102,12 @@ def extract_nodes(code_text):
def scan_in_file(filename, is_builtin=False):
global builtin_nodes
with open(filename, encoding='utf-8', errors='ignore') as file:
code = file.read()
try:
with open(filename, encoding='utf-8') as file:
code = file.read()
except UnicodeDecodeError:
with open(filename, encoding='cp949') as file:
code = file.read()
pattern = r"_CLASS_MAPPINGS\s*=\s*{([^}]*)}"
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
@@ -293,7 +297,7 @@ def update_custom_nodes():
pass
def is_rate_limit_exceeded():
return g.rate_limiting[0] <= 20
return g.rate_limiting[0] == 0
if is_rate_limit_exceeded():
print(f"GitHub API Rate Limit Exceeded: remained - {(g.rate_limiting_resettime - datetime.datetime.now().timestamp())/60:.2f} min")

View File

@@ -6,7 +6,7 @@ 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
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

View File

@@ -1,3 +1,2 @@
.\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