Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
338d7abfa8 | ||
|
|
f8e5f5bcbb |
8
.github/workflows/publish.yml
vendored
8
.github/workflows/publish.yml
vendored
@@ -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 }}
|
||||
37
README.md
37
README.md
@@ -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:
|
||||
|
||||
11
__init__.py
11
__init__.py
@@ -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
124
cm-cli.py
@@ -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())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
11103
extension-node-map.json
11103
extension-node-map.json
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
11685
github-stats.json
11685
github-stats.json
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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 = {}
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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" ({core.comfy_ui_commit_datetime.date()})")
|
||||
markdown_content += (f"<HR>ComfyUI: {version_tag}<BR>"
|
||||
f" ({core.comfy_ui_commit_datetime.date()})")
|
||||
# markdown_content += f"<BR> ()"
|
||||
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()
|
||||
|
||||
|
||||
@@ -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("<", "<").replace(">", ">")
|
||||
@@ -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 []
|
||||
|
||||
50
js/README.md
50
js/README.md
@@ -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.
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
187
js/common.js
187
js/common.js
@@ -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();
|
||||
@@ -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; });
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
383
model-list.json
383
model-list.json
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,5 @@
|
||||
{
|
||||
"custom_nodes": [
|
||||
{
|
||||
"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",
|
||||
@@ -139,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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,929 +1,15 @@
|
||||
{
|
||||
"custom_nodes": [
|
||||
{
|
||||
"author": "theUpsider",
|
||||
"title": "ComfyUI-Logic [DEPRECATED]",
|
||||
"id": "comfy-logic",
|
||||
"reference": "https://github.com/theUpsider/ComfyUI-Logic",
|
||||
"files": [
|
||||
"https://github.com/theUpsider/ComfyUI-Logic"
|
||||
],
|
||||
"author": "#NOTICE_1.13",
|
||||
"title": "NOTICE: This channel is not the default channel.",
|
||||
"reference": "https://github.com/ltdrdata/ComfyUI-Manager",
|
||||
"files": [],
|
||||
"install_type": "git-clone",
|
||||
"description": "An extension to ComfyUI that introduces logic nodes and conditional rendering capabilities."
|
||||
},
|
||||
{
|
||||
"author": "Malloc-pix",
|
||||
"title": "comfyui_qwen2.4_vl_node [REMOVED]",
|
||||
"reference": "https://github.com/Malloc-pix/comfyui_qwen2.4_vl_node",
|
||||
"files": [
|
||||
"https://github.com/Malloc-pix/comfyui_qwen2.4_vl_node"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: CogVLM2 Captioner, CLIP Dynamic Text Encode(cy)"
|
||||
},
|
||||
{
|
||||
"author": "inyourdreams-studio",
|
||||
"title": "ComfyUI-RBLM [REMOVED]",
|
||||
"reference": "https://github.com/inyourdreams-studio/comfyui-rblm",
|
||||
"files": [
|
||||
"https://github.com/inyourdreams-studio/comfyui-rblm"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node pack for ComfyUI that provides text manipulation nodes."
|
||||
},
|
||||
{
|
||||
"author": "dream-computing",
|
||||
"title": "SyntaxNodes - Image Processing Effects for ComfyUI [REMOVED]",
|
||||
"reference": "https://github.com/dream-computing/syntax-nodes",
|
||||
"files": [
|
||||
"https://github.com/dream-computing/syntax-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A collection of custom nodes for ComfyUI designed to apply various image processing effects, stylizations, and analyses."
|
||||
},
|
||||
{
|
||||
"author": "UD1sto",
|
||||
"title": "plugin-utils-nodes [DEPRECATED]",
|
||||
"reference": "https://github.com/its-DeFine/plugin-utils-nodes",
|
||||
"files": [
|
||||
"https://github.com/its-DeFine/plugin-utils-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Compare Images (SimHash), Image Selector, Temporal Consistency, Update Image Reference, Frame Blend."
|
||||
},
|
||||
{
|
||||
"author": "hanyingcho",
|
||||
"title": "ComfyUI LLM Promp [REMOVED]",
|
||||
"reference": "https://github.com/hanyingcho/comfyui-llmprompt",
|
||||
"files": [
|
||||
"https://github.com/hanyingcho/comfyui-llmprompt"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Load llm, Generate Text with LLM, Inference Qwen2VL, Inference Qwen2"
|
||||
},
|
||||
{
|
||||
"author": "WASasquatch",
|
||||
"title": "WAS Node Suite [DEPRECATED]",
|
||||
"id": "was",
|
||||
"reference": "https://github.com/WASasquatch/was-node-suite-comfyui",
|
||||
"pip": ["numba"],
|
||||
"files": [
|
||||
"https://github.com/WASasquatch/was-node-suite-comfyui"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A node suite for ComfyUI with many new nodes, such as image processing, text processing, and more."
|
||||
},
|
||||
{
|
||||
"author": "TOM1063",
|
||||
"title": "ComfyUI-SamuraiTools [REMOVED]",
|
||||
"reference": "https://github.com/TOM1063/ComfyUI-SamuraiTools",
|
||||
"files": [
|
||||
"https://github.com/TOM1063/ComfyUI-SamuraiTools"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom node for switching integer values based on boolean conditions"
|
||||
},
|
||||
{
|
||||
"author": "whitemoney293",
|
||||
"title": "ComfyUI-MediaUtilities [REMOVED]",
|
||||
"reference": "https://github.com/ThanaritKanjanametawatAU/ComfyUI-MediaUtilities",
|
||||
"files": [
|
||||
"https://github.com/ThanaritKanjanametawatAU/ComfyUI-MediaUtilities"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom nodes for loading and previewing media from URLs in ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "pureexe",
|
||||
"title": "DiffusionLight-ComfyUI [REMOVED]",
|
||||
"reference": "https://github.com/pureexe/DiffusionLight-ComfyUI",
|
||||
"files": [
|
||||
"https://github.com/pureexe/DiffusionLight-ComfyUI"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "DiffusionLight (Turbo) implemented in ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "gondar-software",
|
||||
"title": "comfyui-custom-padding [REMOVED]",
|
||||
"reference": "https://github.com/gondar-software/comfyui-custom-padding",
|
||||
"files": [
|
||||
"https://github.com/gondar-software/comfyui-custom-padding"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Adaptive image padding, Adaptive image unpadding"
|
||||
},
|
||||
{
|
||||
"author": "Charonartist",
|
||||
"title": "ComfyUI-EagleExporter [REMOVED]",
|
||||
"reference": "https://github.com/Charonartist/ComfyUI-EagleExporter",
|
||||
"files": [
|
||||
"https://github.com/Charonartist/ComfyUI-EagleExporter"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This is an extension that automatically saves video files generated with ComfyUI's 'video combine' extension to the Eagle library."
|
||||
},
|
||||
{
|
||||
"author": "pomePLaszlo-collablyu",
|
||||
"title": "comfyui_ejam [REMOVED]",
|
||||
"reference": "https://github.com/PLaszlo-collab/comfyui_ejam",
|
||||
"files": [
|
||||
"https://github.com/PLaszlo-collab/comfyui_ejam"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Ejam nodes for comfyui"
|
||||
},
|
||||
{
|
||||
"author": "jonnydolake",
|
||||
"title": "ComfyUI-AIR-Nodes [REMOVED]",
|
||||
"reference": "https://github.com/jonnydolake/ComfyUI-AIR-Nodes",
|
||||
"files": [
|
||||
"https://github.com/jonnydolake/ComfyUI-AIR-Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: String List To Prompt Schedule, Force Minimum Batch Size, Target Location (Crop), Target Location (Paste), Image Composite Chained, Match Image Count To Mask Count, Random Character Prompts, Parallax Test, Easy Parallax, Parallax GPU Test"
|
||||
},
|
||||
{
|
||||
"author": "solution9th",
|
||||
"title": "Comfyui_mobilesam [REMOVED]",
|
||||
"reference": "https://github.com/solution9th/Comfyui_mobilesam",
|
||||
"files": [
|
||||
"https://github.com/solution9th/Comfyui_mobilesam"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Mobile SAM Model Loader, Mobile SAM Detector, Mobile SAM Predictor"
|
||||
},
|
||||
{
|
||||
"author": "syaofox",
|
||||
"title": "ComfyUI_fnodes [REMOVED]",
|
||||
"reference": "https://github.com/syaofox/ComfyUI_fnodes",
|
||||
"files": [
|
||||
"https://github.com/syaofox/ComfyUI_fnodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI_fnodes is a collection of custom nodes designed for ComfyUI. These nodes provide additional functionality that can enhance your ComfyUI workflows.\nFile manipulation tools, Image resizing tools, IPAdapter tools, Image processing tools, Mask tools, Face analysis tools, Sampler tools, Miscellaneous tools"
|
||||
},
|
||||
{
|
||||
"author": "Hangover3832",
|
||||
"title": "ComfyUI-Hangover-Moondream [DEPRECATED]",
|
||||
"reference": "https://github.com/Hangover3832/ComfyUI-Hangover-Moondream",
|
||||
"files": [
|
||||
"https://github.com/Hangover3832/ComfyUI-Hangover-Moondream"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Moondream is a lightweight multimodal large language model.\n[w/WARN:Additional python code will be downloaded from huggingface and executed. You have to trust this creator if you want to use this node!]"
|
||||
},
|
||||
{
|
||||
"author": "Hangover3832",
|
||||
"title": "Recognize Anything Model (RAM) for ComfyUI [DEPRECATED]",
|
||||
"reference": "https://github.com/Hangover3832/ComfyUI-Hangover-Recognize_Anything",
|
||||
"files": [
|
||||
"https://github.com/Hangover3832/ComfyUI-Hangover-Recognize_Anything"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This is an image recognition node for ComfyUI based on the RAM++ model from [a/xinyu1205](https://huggingface.co/xinyu1205).\nThis node outputs a string of tags with all the recognized objects and elements in the image in English or Chinese language.\nFor image tagging and captioning."
|
||||
},
|
||||
{
|
||||
"author": "Hangover3832",
|
||||
"title": "ComfyUI-Hangover-Nodes [DEPRECATED]",
|
||||
"reference": "https://github.com/Hangover3832/ComfyUI-Hangover-Nodes",
|
||||
"files": [
|
||||
"https://github.com/Hangover3832/ComfyUI-Hangover-Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: MS kosmos-2 Interrogator, Save Image w/o Metadata, Image Scale Bounding Box. An implementation of Microsoft [a/kosmos-2](https://huggingface.co/microsoft/kosmos-2-patch14-224) image to text transformer."
|
||||
},
|
||||
{
|
||||
"author": "SirLatore",
|
||||
"title": "ComfyUI-IPAdapterWAN [REMOVED]",
|
||||
"reference": "https://github.com/SirLatore/ComfyUI-IPAdapterWAN",
|
||||
"files": [
|
||||
"https://github.com/SirLatore/ComfyUI-IPAdapterWAN"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This extension adapts the [a/InstantX IP-Adapter for SD3.5-Large](https://huggingface.co/InstantX/SD3.5-Large-IP-Adapter) to work with Wan 2.1 and other UNet-based video/image models in ComfyUI.\nUnlike the original SD3 version (which depends on joint_blocks from MMDiT), this version performs sampling-time identity conditioning by dynamically injecting into attention layers — making it compatible with models like Wan 2.1, AnimateDiff, and other non-SD3 pipelines."
|
||||
},
|
||||
{
|
||||
"author": "Jpzz",
|
||||
"title": "ComfyUI-VirtualInteraction [UNSAFE/REMOVED]",
|
||||
"reference": "https://github.com/Jpzz/ComfyUI-VirtualInteraction",
|
||||
"files": [
|
||||
"https://github.com/Jpzz/ComfyUI-VirtualInteraction"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: virtual interaction custom node when using generative movie\n[w/This nodepack contains a node which is reading arbitrary excel file.]"
|
||||
},
|
||||
{
|
||||
"author": "satche",
|
||||
"title": "Prompt Factory [REMOVED]",
|
||||
"reference": "https://github.com/satche/comfyui-prompt-factory",
|
||||
"files": [
|
||||
"https://github.com/satche/comfyui-prompt-factory"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A modular system that adds randomness to prompt generation"
|
||||
},
|
||||
{
|
||||
"author": "MITCAP",
|
||||
"title": "ComfyUI OpenAI DALL-E 3 Node [REMOVED]",
|
||||
"reference": "https://github.com/MITCAP/OpenAI-ComfyUI",
|
||||
"files": [
|
||||
"https://github.com/MITCAP/OpenAI-ComfyUI"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This project provides custom nodes for ComfyUI that integrate with OpenAI's DALL-E 3 and GPT-4o models. The nodes allow users to generate images and describe images using OpenAI's API.\nNOTE: The files in the repo are not organized."
|
||||
},
|
||||
{
|
||||
"author": "raspie10032",
|
||||
"title": "ComfyUI NAI Prompt Converter [REMOVED]",
|
||||
"reference": "https://github.com/raspie10032/ComfyUI_RS_NAI_Local_Prompt_converter",
|
||||
"files": [
|
||||
"https://github.com/raspie10032/ComfyUI_RS_NAI_Local_Prompt_converter"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node extension for ComfyUI that enables conversion between different prompt formats: NovelAI V4, ComfyUI, and old NovelAI."
|
||||
},
|
||||
{
|
||||
"author": "holchan",
|
||||
"title": "ComfyUI-ModelDownloader [REMOVED]",
|
||||
"reference": "https://github.com/holchan/ComfyUI-ModelDownloader",
|
||||
"files": [
|
||||
"https://github.com/holchan/ComfyUI-ModelDownloader"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A ComfyUI node to download models(Checkpoints and LoRA) from external links and act as an output standalone node."
|
||||
},
|
||||
{
|
||||
"author": "Kur0butiMegane",
|
||||
"title": "Comfyui-StringUtils [DEPRECATED]",
|
||||
"reference": "https://github.com/Kur0butiMegane/Comfyui-StringUtils",
|
||||
"files": [
|
||||
"https://github.com/Kur0butiMegane/Comfyui-StringUtils"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Prompt Normalizer, String Splitter, String Line Selector, Extract Markup Value"
|
||||
},
|
||||
{
|
||||
"author": "Apache0ne",
|
||||
"title": "ComfyUI-LantentCompose [REMOVED]",
|
||||
"reference": "https://github.com/Apache0ne/ComfyUI-LantentCompose",
|
||||
"files": [
|
||||
"https://github.com/Apache0ne/ComfyUI-LantentCompose"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Interpolate sdxl latents using slerp with and without a mask. use with unsample nodes for best effect.\nNOTE: The files in the repo are not organized."
|
||||
},
|
||||
{
|
||||
"author": "jax-explorer",
|
||||
"title": "ComfyUI-H-flow [REMOVED]",
|
||||
"reference": "https://github.com/jax-explorer/ComfyUI-H-flow",
|
||||
"files": [
|
||||
"https://github.com/jax-explorer/ComfyUI-H-flow"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Wan2-1 Image To Video, LLM Task, Save Image, Save Video, Show Text, FluxPro Ultra, IdeogramV2 Turbo, Runway Image To Video, Kling Image To Video, Replace Text, Join Text, Test Image, Test Text"
|
||||
},
|
||||
{
|
||||
"author": "Apache0ne",
|
||||
"title": "SambaNova [REMOVED]",
|
||||
"id": "SambaNovaAPI",
|
||||
"reference": "https://github.com/Apache0ne/SambaNova",
|
||||
"files": [
|
||||
"https://github.com/Apache0ne/SambaNova"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Super Fast LLM's llama3.1-405B,70B,8B and more"
|
||||
},
|
||||
{
|
||||
"author": "Apache0ne",
|
||||
"title": "ComfyUI-EasyUrlLoader [REMOVED]",
|
||||
"id": "easy-url-loader",
|
||||
"reference": "https://github.com/Apache0ne/ComfyUI-EasyUrlLoader",
|
||||
"files": [
|
||||
"https://github.com/Apache0ne/ComfyUI-EasyUrlLoader"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A simple YT downloader node for ComfyUI using video Urls. Can be used with VHS nodes etc."
|
||||
},
|
||||
{
|
||||
"author": "nxt5656",
|
||||
"title": "ComfyUI-Image2OSS [REMOVED]",
|
||||
"reference": "https://github.com/nxt5656/ComfyUI-Image2OSS",
|
||||
"files": [
|
||||
"https://github.com/nxt5656/ComfyUI-Image2OSS"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Upload the image to Alibaba Cloud OSS."
|
||||
},
|
||||
{
|
||||
"author": "ainewsto",
|
||||
"title": "Comfyui_Comfly",
|
||||
"reference": "https://github.com/ainewsto/Comfyui_Comfly",
|
||||
"files": [
|
||||
"https://github.com/ainewsto/Comfyui_Comfly"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Comfly_Mj, Comfly_mjstyle, Comfly_upload, Comfly_Mju, Comfly_Mjv, Comfly_kling_videoPreview\nNOTE: Comfyui_Comfly_v2 is introduced."
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-to-inpaint",
|
||||
"reference": "https://github.com/shinich39/comfyui-to-inpaint",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-to-inpaint"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Send preview image to inpaint workflow."
|
||||
},
|
||||
{
|
||||
"author": "magic-quill",
|
||||
"title": "ComfyUI_MagicQuill [NOT MAINTAINED]",
|
||||
"id": "MagicQuill",
|
||||
"reference": "https://github.com/magic-quill/ComfyUI_MagicQuill",
|
||||
"files": [
|
||||
"https://github.com/magic-quill/ComfyUI_MagicQuill"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Towards GPT-4 like large language and visual assistant.\nNOTE: The current version has not been maintained for a long time and does not work. Please use https://github.com/brantje/ComfyUI_MagicQuill instead."
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-event-handler [USAFE/REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-event-handler",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-event-handler"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Javascript code will run when an event fires. [w/This node allows you to execute arbitrary JavaScript code as input for the workflow.]"
|
||||
},
|
||||
{
|
||||
"author": "Moooonet",
|
||||
"title": "ComfyUI-ArteMoon [REMOVED]",
|
||||
"reference": "https://github.com/Moooonet/ComfyUI-ArteMoon",
|
||||
"files": [
|
||||
"https://github.com/Moooonet/ComfyUI-ArteMoon"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This plugin works with [a/IF_AI_Tools](https://github.com/if-ai/ComfyUI-IF_AI_tools) to build a workflow in ComfyUI that uses AI to assist in generating prompts."
|
||||
},
|
||||
{
|
||||
"author": "ryanontheinside",
|
||||
"title": "ComfyUI-MediaPipe-Vision [REMOVED]",
|
||||
"reference": "https://github.com/ryanontheinside/ComfyUI-MediaPipe-Vision",
|
||||
"files": [
|
||||
"https://github.com/ryanontheinside/ComfyUI-MediaPipe-Vision"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A centralized wrapper of all MediaPipe vision tasks for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-textarea-command [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-textarea-command",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-textarea-command"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Add command and comment in textarea. (e.g. // Disabled line)"
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-parse-image [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-parse-image",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-parse-image"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Extract metadata from image."
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-put-image [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-put-image",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-put-image"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Load image from directory."
|
||||
},
|
||||
{
|
||||
"author": "fredconex",
|
||||
"title": "TripoSG Nodes for ComfyUI [REMOVED]",
|
||||
"reference": "https://github.com/fredconex/ComfyUI-TripoSG",
|
||||
"files": [
|
||||
"https://github.com/fredconex/ComfyUI-TripoSG"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Created by Alfredo Fernandes inspired by Hunyuan3D nodes by Kijai. This extension adds TripoSG 3D mesh generation capabilities to ComfyUI, allowing you to generate 3D meshes from a single image using the TripoSG model."
|
||||
},
|
||||
{
|
||||
"author": "fredconex",
|
||||
"title": "ComfyUI-PaintTurbo [REMOVED]",
|
||||
"reference": "https://github.com/fredconex/ComfyUI-PaintTurbo",
|
||||
"files": [
|
||||
"https://github.com/fredconex/ComfyUI-PaintTurbo"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Hunyuan3D Texture Mesh"
|
||||
},
|
||||
{
|
||||
"author": "zhuanqianfish",
|
||||
"title": "TaesdDecoder [REMOVED]",
|
||||
"reference": "https://github.com/zhuanqianfish/TaesdDecoder",
|
||||
"files": [
|
||||
"https://github.com/zhuanqianfish/TaesdDecoder"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "use TAESD decoded image.you need donwload taesd_decoder.pth and taesdxl_decoder.pth to vae_approx folder first.\n It will result in a slight loss of image quality and a significant decrease in peak video memory during decoding."
|
||||
},
|
||||
{
|
||||
"author": "myAiLemon",
|
||||
"title": "MagicAutomaticPicture [REMOVED]",
|
||||
"reference": "https://github.com/myAiLemon/MagicAutomaticPicture",
|
||||
"files": [
|
||||
"https://github.com/myAiLemon/MagicAutomaticPicture"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A comfyui node package that can generate pictures and automatically save positive prompts and eliminate unwanted prompts"
|
||||
},
|
||||
{
|
||||
"author": "thisiseddy-ab",
|
||||
"title": "ComfyUI-Edins-Ultimate-Pack [REMOVED]",
|
||||
"reference": "https://github.com/thisiseddy-ab/ComfyUI-Edins-Ultimate-Pack",
|
||||
"files": [
|
||||
"https://github.com/thisiseddy-ab/ComfyUI-Edins-Ultimate-Pack"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Well i needet a Tiled Ksampler that still works for Comfy UI there were none so i made one, in this Package i will put all Nodes i will develop for Comfy Ui still in beta alot will change.."
|
||||
},
|
||||
{
|
||||
"author": "Davros666",
|
||||
"title": "safetriggers [REMOVED]",
|
||||
"reference": "https://github.com/Davros666/safetriggers",
|
||||
"files": [
|
||||
"https://github.com/Davros666/safetriggers"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI Nodes for READING TRIGGERS, TRIGGER-WORDS, TRIGGER-PHRASES FROM LoRAs"
|
||||
},
|
||||
{
|
||||
"author": "cubiq",
|
||||
"title": "Simple Math [REMOVED]",
|
||||
"id": "simplemath",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_SimpleMath",
|
||||
"files": [
|
||||
"https://github.com/cubiq/ComfyUI_SimpleMath"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "custom node for ComfyUI to perform simple math operations"
|
||||
},
|
||||
{
|
||||
"author": "lucafoscili",
|
||||
"title": "LF Nodes [DEPRECATED]",
|
||||
"reference": "https://github.com/lucafoscili/comfyui-lf",
|
||||
"files": [
|
||||
"https://github.com/lucafoscili/comfyui-lf"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom nodes with a touch of extra UX, including: history for primitives, JSON manipulation, logic switches with visual feedback, LLM chat... and more!"
|
||||
},
|
||||
{
|
||||
"author": "AI2lab",
|
||||
"title": "comfyUI-tool-2lab [REMOVED]",
|
||||
"id": "tool-2lab",
|
||||
"reference": "https://github.com/AI2lab/comfyUI-tool-2lab",
|
||||
"files": [
|
||||
"https://github.com/AI2lab/comfyUI-tool-2lab"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "tool set for developing workflow and publish to web api server"
|
||||
},
|
||||
{
|
||||
"author": "AI2lab",
|
||||
"title": "comfyUI-DeepSeek-2lab [REMOVED]",
|
||||
"id": "deepseek",
|
||||
"reference": "https://github.com/AI2lab/comfyUI-DeepSeek-2lab",
|
||||
"files": [
|
||||
"https://github.com/AI2lab/comfyUI-DeepSeek-2lab"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Unofficial implementation of DeepSeek for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "AI2lab",
|
||||
"title": "comfyUI-kling-api-2lab [REMOVED]",
|
||||
"reference": "https://github.com/AI2lab/comfyUI-kling-api-2lab",
|
||||
"files": [
|
||||
"https://github.com/AI2lab/comfyUI-kling-api-2lab"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Unofficial implementation of KLing for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "ZhiHui6",
|
||||
"title": "comfyui_zhihui_nodes [REMOVED]",
|
||||
"reference": "https://github.com/ZhiHui6/comfyui_zhihui_nodes",
|
||||
"files": [
|
||||
"https://github.com/ZhiHui6/comfyui_zhihui_nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Prompt Preset, Video Batch Loader, Video Combiner"
|
||||
},
|
||||
{
|
||||
"author": "ImagineerNL",
|
||||
"title": "comfyui_potrace_svg [REMOVED]",
|
||||
"reference": "https://github.com/ImagineerNL/comfyui_potrace_svg",
|
||||
"files": [
|
||||
"https://github.com/ImagineerNL/comfyui_potrace_svg"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This project converts raster images into SVG format using the Potrace library."
|
||||
},
|
||||
{
|
||||
"author": "kayselmecnun",
|
||||
"title": "ComfyUI-Qwen-25-VL [REMOVED]",
|
||||
"reference": "https://github.com/kayselmecnun/ComfyUI-Qwen-25-VL",
|
||||
"files": [
|
||||
"https://github.com/kayselmecnun/ComfyUI-Qwen-25-VL"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom Comfy UI node for using Qwen2.5-VL-3B/7B-Instruct models"
|
||||
},
|
||||
{
|
||||
"author": "IfnotFr",
|
||||
"title": "⚡ ComfyUI Connect [REMOVED]",
|
||||
"reference": "https://github.com/IfnotFr/ComfyUI-Connect",
|
||||
"files": [
|
||||
"https://github.com/IfnotFr/ComfyUI-Connect"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Transform your ComfyUI into a powerful API, exposing all your saved workflows as ready-to-use HTTP endpoints."
|
||||
},
|
||||
{
|
||||
"author": "ginlov",
|
||||
"title": "segment_to_mask_comfyui [REMOVED]",
|
||||
"reference": "https://github.com/ginlov/segment_to_mask_comfyui",
|
||||
"files": [
|
||||
"https://github.com/ginlov/segment_to_mask_comfyui"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:SegToMask"
|
||||
},
|
||||
{
|
||||
"author": "TGu-97",
|
||||
"title": "TGu Utilities [REMOVED]",
|
||||
"id": "tgu",
|
||||
"reference": "https://github.com/TGu-97/ComfyUI-TGu-utils",
|
||||
"files": [
|
||||
"https://github.com/TGu-97/ComfyUI-TGu-utils"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: MPN Switch, MPN Reroute, PN Switch. This is a set of custom nodes for ComfyUI. Mainly focus on control switches."
|
||||
},
|
||||
{
|
||||
"author": "IfnotFr",
|
||||
"title": "ComfyUI-Connect [REMOVED]",
|
||||
"reference": "https://github.com/IfnotFr/ComfyUI-Connect",
|
||||
"files": [
|
||||
"https://github.com/IfnotFr/ComfyUI-Connect"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Transform your ComfyUI into a powerful API, exposing all your saved workflows as ready-to-use HTTP endpoints."
|
||||
},
|
||||
{
|
||||
"author": "KurtHokke",
|
||||
"title": "ComfyUI_KurtHokke-Nodes [REMOVED]",
|
||||
"reference": "https://github.com/KurtHokke/ComfyUI_KurtHokke-Nodes",
|
||||
"files": [
|
||||
"https://github.com/KurtHokke/ComfyUI_KurtHokke-Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI_KurtHokke-Nodes"
|
||||
},
|
||||
{
|
||||
"author": "SpatialDeploy",
|
||||
"title": "ComfyUI-Voxels [REMOVED]",
|
||||
"reference": "https://github.com/SpatialDeploy/ComfyUI-Voxels",
|
||||
"files": [
|
||||
"https://github.com/SpatialDeploy/ComfyUI-Voxels"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Tools for creating voxel based videos"
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-group-selection [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-group-selection",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-group-selection"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Create a new group of nodes."
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "connect-from-afar [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-connect-from-afar",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-connect-from-afar"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Connect a new link from out of screen."
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-local-db [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-local-db",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-local-db"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Store text to Key-Values pair json."
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-model-db [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-model-db",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-model-db"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Store settings by model."
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-target-search [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-target-search",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-target-search"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Move canvas to target on dragging connection."
|
||||
},
|
||||
{
|
||||
"author": "chrisgoringe",
|
||||
"title": "Image chooser [DEPRECATED]",
|
||||
"id": "image-chooser",
|
||||
"reference": "https://github.com/chrisgoringe/cg-image-picker",
|
||||
"files": [
|
||||
"https://github.com/chrisgoringe/cg-image-picker"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node that pauses the flow while you choose which image (or latent) to pass on to the rest of the workflow."
|
||||
},
|
||||
{
|
||||
"author": "weilin9999",
|
||||
"title": "WeiLin-ComfyUI-prompt-all-in-one [DEPRECATED]",
|
||||
"id": "prompt-all-in-one",
|
||||
"reference": "https://github.com/weilin9999/WeiLin-ComfyUI-prompt-all-in-one",
|
||||
"files": [
|
||||
"https://github.com/weilin9999/WeiLin-ComfyUI-prompt-all-in-one"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Write prompt words like WebUI"
|
||||
},
|
||||
{
|
||||
"author": "svetozarov",
|
||||
"title": "AS_GeminiCaptioning Node [REMOVED]",
|
||||
"reference": "https://github.com/svetozarov/AS_GeminiCaptioning",
|
||||
"files": [
|
||||
"https://github.com/svetozarov/AS_GeminiCaptioning"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A ComfyUI node that combines an image with simple text parameters to create a prompt, sends it to the Google Gemini API via the google-generativeai SDK, and returns the generated text response along with the original prompt and an execution log"
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-load-image-in-seq [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-load-image-in-seq",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-load-image-in-seq"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This node is load png image sequentially with metadata. Only supported for PNG format that has been created by ComfyUI.[w/renamed from comfyui-load-image-39. You need to remove previous one and reinstall to this.]"
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-model-metadata [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-model-metadata",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-model-metadata"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Print model metadata on note node"
|
||||
},
|
||||
{
|
||||
"author": "shinich39",
|
||||
"title": "comfyui-view-recommendations [REMOVED]",
|
||||
"reference": "https://github.com/shinich39/comfyui-view-recommendations",
|
||||
"files": [
|
||||
"https://github.com/shinich39/comfyui-view-recommendations"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Load model generation data from civitai."
|
||||
},
|
||||
{
|
||||
"author": "jonstreeter",
|
||||
"title": "Comfyui-PySceneDetect [REMOVED]",
|
||||
"reference": "https://github.com/jonstreeter/Comfyui-PySceneDetect",
|
||||
"files": [
|
||||
"https://github.com/jonstreeter/Comfyui-PySceneDetect"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: PySceneDetect Video Processor"
|
||||
},
|
||||
{
|
||||
"author": "muxueChen",
|
||||
"title": "ComfyUI-NTQwen25-VL [REMOVED]",
|
||||
"reference": "https://github.com/muxueChen/ComfyUI-NTQwen25-VL",
|
||||
"files": [
|
||||
"https://github.com/muxueChen/ComfyUI-NTQwen25-VL"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Qwen25-VL is a plugin for ComfyU"
|
||||
},
|
||||
{
|
||||
"author": "Makki_Shizu",
|
||||
"title": "ComfyUI-SaveAnimatedGIF [DEPRECATED]",
|
||||
"id": "SaveAnimatedGIF",
|
||||
"reference": "https://github.com/MakkiShizu/ComfyUI-SaveAnimatedGIF",
|
||||
"files": [
|
||||
"https://github.com/MakkiShizu/ComfyUI-SaveAnimatedGIF"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Save animated GIF format nodes in ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "l1yongch1",
|
||||
"title": "ComfyUI_PhiCaption [REMOVED]",
|
||||
"reference": "https://github.com/l1yongch1/ComfyUI_PhiCaption",
|
||||
"files": [
|
||||
"https://github.com/l1yongch1/ComfyUI_PhiCaption"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "In addition to achieving conventional single-image, single-round reverse engineering, it can also achieve single-image multi-round and multi-image single-round reverse engineering. Moreover, the Phi model has a better understanding of prompts."
|
||||
},
|
||||
{
|
||||
"author": "nova-florealis",
|
||||
"title": "comfyui-alien [REMOVED]",
|
||||
"reference": "https://github.com/nova-florealis/comfyui-alien",
|
||||
"files": [
|
||||
"https://github.com/nova-florealis/comfyui-alien"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES: Text to Text (LLM), Text Output, Convert to Markdown, List Display (Debug)"
|
||||
},
|
||||
{
|
||||
"author": "PluMaZero",
|
||||
"title": "ComfyUI-SpaceFlower [REMOVED]",
|
||||
"reference": "https://github.com/PluMaZero/ComfyUI-SpaceFlower",
|
||||
"files": [
|
||||
"https://github.com/PluMaZero/ComfyUI-SpaceFlower"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: SpaceFlower_Prompt, SpaceFlower_HangulPrompt, ..."
|
||||
},
|
||||
{
|
||||
"author": "vahidzxc",
|
||||
"title": "ComfyUI-My-Handy-Nodes [REMOVED]",
|
||||
"reference": "https://github.com/vahidzxc/ComfyUI-My-Handy-Nodes",
|
||||
"files": [
|
||||
"https://github.com/vahidzxc/ComfyUI-My-Handy-Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES:VahCropImage"
|
||||
},
|
||||
{
|
||||
"author": "Samulebotin",
|
||||
"title": "ComfyUI-FreeVC_wrapper [REMOVED]",
|
||||
"reference": "https://github.com/Samulebotin/ComfyUI-FreeVC_wrapper",
|
||||
"files": [
|
||||
"https://github.com/Samulebotin/ComfyUI-FreeVC_wrapper"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A voice conversion extension node for ComfyUI based on FreeVC, enabling high-quality voice conversion capabilities within the ComfyUI framework."
|
||||
},
|
||||
{
|
||||
"author": "GoingAI1998",
|
||||
"title": "ComfyUI Web Canvas Node [REMOVED]",
|
||||
"reference": "https://github.com/GoingAI1998/Comfyui_imgcanvas",
|
||||
"files": [
|
||||
"https://github.com/GoingAI1998/Comfyui_imgcanvas"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI_imgcanvas At present, I have not used the useful comfyui custom node about layer mixing, and I have written a comfyui runtime automatic pop-up window for layer editing node"
|
||||
},
|
||||
{
|
||||
"author": "807502278",
|
||||
"title": "ComfyUI_TensorRT_Merge [REMOVED]",
|
||||
"reference": "https://github.com/807502278/ComfyUI_TensorRT_Merge",
|
||||
"files": [
|
||||
"https://github.com/807502278/ComfyUI_TensorRT_Merge"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Non diffusion models supported by TensorRT, merged Comfyui plugin, added onnx automatic download and trt model conversion nodes."
|
||||
},
|
||||
{
|
||||
"author": "logtd",
|
||||
"title": "ComfyUI-LTXTricks [DEPRECATED]",
|
||||
"reference": "https://github.com/logtd/ComfyUI-LTXTricks",
|
||||
"files": [
|
||||
"https://github.com/logtd/ComfyUI-LTXTricks"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A set of nodes that provide additional controls for the LTX Video model"
|
||||
},
|
||||
{
|
||||
"author": "JichaoLiang",
|
||||
"title": "Immortal_comfyUI [REMOVED]",
|
||||
"reference": "https://github.com/JichaoLiang/Immortal_comfyUI",
|
||||
"files": [
|
||||
"https://github.com/JichaoLiang/Immortal_comfyUI"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "NODES:ImNewNode, ImAppendNode, MergeNode, SetProperties, SaveToDirectory, batchNodes, redirectToNode, SetEvent, ..."
|
||||
},
|
||||
{
|
||||
"author": "Rvage0815",
|
||||
"title": "ComfyUI-RvTools [REMOVED]",
|
||||
"reference": "https://github.com/Rvage0815/ComfyUI-RvTools",
|
||||
"files": [
|
||||
"https://github.com/Rvage0815/ComfyUI-RvTools"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "this node contains a lot of small little helpers like switches, passers and selectors that i use a lot to build my workflows."
|
||||
},
|
||||
{
|
||||
"author": "Rvage0815",
|
||||
"title": "RvTComfyUI-RvTools_v2 [REMOVED]",
|
||||
"reference": "https://github.com/Rvage0815/ComfyUI-RvTools_v2",
|
||||
"files": [
|
||||
"https://github.com/Rvage0815/ComfyUI-RvTools_v2"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "this node contains a lot of small little helpers like switches, passers and selectors that i use a lot to build my workflows."
|
||||
},
|
||||
{
|
||||
"author": "scottmudge",
|
||||
"title": "ComfyUI_BiscuitNodes [REMOVED]",
|
||||
"reference": "https://github.com/scottmudge/ComfyUI_BiscuitNodes",
|
||||
"files": [
|
||||
"https://github.com/scottmudge/ComfyUI_BiscuitNodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Load Image From Path Using File Selector"
|
||||
},
|
||||
{
|
||||
"author": "thanhduong0213929",
|
||||
"title": "ComfyUI-DeepUnlock [REMOVED]",
|
||||
"reference": "https://github.com/thanhduong0213929/ComfyUI-DeepUnlock",
|
||||
"files": [
|
||||
"https://github.com/thanhduong0213929/ComfyUI-DeepUnlock"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "DeepFuze is a state-of-the-art deep learning tool that seamlessly integrates with ComfyUI to revolutionize facial transformations, lipsyncing, video generation, voice cloning, face swapping, and lipsync translation. Leveraging advanced algorithms, DeepFuze enables users to combine audio and video with unparalleled realism, ensuring perfectly synchronized facial movements. This innovative solution is ideal for content creators, animators, developers, and anyone seeking to elevate their video editing projects with sophisticated AI-driven features."
|
||||
},
|
||||
{
|
||||
"author": "pathway8-sudo",
|
||||
"title": "RMBG [REMOVED]",
|
||||
"reference": "https://github.com/pathway8-sudo/RMBG",
|
||||
"files": [
|
||||
"https://github.com/pathway8-sudo/RMBG"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This repository provides a custom node for ComfyUI, leveraging the BriaRMBG model to remove backgrounds from images and output a transparent PNG."
|
||||
},
|
||||
{
|
||||
"author": "iris-Neko",
|
||||
"title": "ComfyUI_ascii_art [REMOVED]",
|
||||
"reference": "https://github.com/iris-Neko/ComfyUI_ascii_art",
|
||||
"files": [
|
||||
"https://github.com/iris-Neko/ComfyUI_ascii_art"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI node for [a/ASCII art controlnet](https://civitai.com/models/986392)"
|
||||
},
|
||||
{
|
||||
"author": "apesplat",
|
||||
"title": "ezXY scripts and nodes [NOT MAINTAINED]",
|
||||
"id": "ezxy",
|
||||
"reference": "https://github.com/GMapeSplat/ComfyUI_ezXY",
|
||||
"files": [
|
||||
"https://github.com/GMapeSplat/ComfyUI_ezXY"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Extensions/Patches: Enables linking float and integer inputs and ouputs. Values are automatically cast to the correct type and clamped to the correct range. Works with both builtin and custom nodes.[w/NOTE: This repo patches ComfyUI's validate_inputs and map_node_over_list functions while running. May break depending on your version of ComfyUI. Can be deactivated in config.yaml.]Nodes: A collection of nodes for facilitating the generation of XY plots. Capable of plotting changes over most primitive values.[w/Does not work with current version of Comfyui]"
|
||||
},
|
||||
{
|
||||
"author": "mie",
|
||||
"title": "ComfyUI_JanusProCaption [REMOVED]",
|
||||
"reference": "https://github.com/MieMieeeee/ComfyUI-JanusProCaption",
|
||||
"files": [
|
||||
"https://github.com/MieMieeeee/ComfyUI-JanusProCaption"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Describe image or create caption files using Janus Pro Model"
|
||||
"description": "If you see this message, your ComfyUI-Manager is outdated.\nLegacy channel provides only the list of the deprecated nodes. If you want to find the complete node list, please go to the Default channel."
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"author": "Njbx",
|
||||
"title": "ComfyUI-blockswap [REMOVED]",
|
||||
@@ -977,7 +63,7 @@
|
||||
},
|
||||
{
|
||||
"author": "myAiLemon",
|
||||
"title": "MagicGetPromptAutomatically [REMOVED]",
|
||||
"title": "MagicGetPromptAutomatically",
|
||||
"reference": "https://github.com/myAiLemon/MagicGetPromptAutomatically",
|
||||
"files": [
|
||||
"https://github.com/myAiLemon/MagicGetPromptAutomatically"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
846
openapi.yaml
846
openapi.yaml
@@ -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
|
||||
@@ -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")
|
||||
@@ -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"
|
||||
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"
|
||||
|
||||
@@ -8,4 +8,3 @@ rich
|
||||
typing-extensions
|
||||
toml
|
||||
uv
|
||||
chardet
|
||||
|
||||
10
scanner.py
10
scanner.py
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user