Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14b82d97ae | ||
|
|
c3eed981c0 | ||
|
|
bbb54d4a08 | ||
|
|
4566c585db | ||
|
|
a946338a18 | ||
|
|
0a60a44478 | ||
|
|
cef0ad6707 | ||
|
|
7176f0837a | ||
|
|
6b1f2b2d9d | ||
|
|
38a1a9b320 | ||
|
|
402e2c384f |
@@ -18573,6 +18573,16 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "ComfyUI nodes for PhotoDoodle model."
|
"description": "ComfyUI nodes for PhotoDoodle model."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"author": "Yuan-ManX",
|
||||||
|
"title": "ComfyUI-StyleStudio",
|
||||||
|
"reference": "https://github.com/Yuan-ManX/ComfyUI-StyleStudio",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Yuan-ManX/ComfyUI-StyleStudio"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI nodes for StyleStudio model."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"author": "Starnodes2024",
|
"author": "Starnodes2024",
|
||||||
"title": "ComfyUI_StarNodes",
|
"title": "ComfyUI_StarNodes",
|
||||||
@@ -21974,6 +21984,16 @@
|
|||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "A ComfyUI custom node that provides dynamic text substitution using wildcards and CSV files. Perfect for creating varied prompts with consistent relationships between terms."
|
"description": "A ComfyUI custom node that provides dynamic text substitution using wildcards and CSV files. Perfect for creating varied prompts with consistent relationships between terms."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"author": "finegrain",
|
||||||
|
"title": "comfyui-finegrain",
|
||||||
|
"reference": "https://github.com/finegrain-ai/comfyui-finegrain",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/finegrain-ai/comfyui-finegrain"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI custom nodes to interact with the Finegrain API."
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
|||||||
else:
|
else:
|
||||||
form_factor = 'other'
|
form_factor = 'other'
|
||||||
|
|
||||||
|
print(f"form_factor: {form_factor}")
|
||||||
while remained:
|
while remained:
|
||||||
# Add comfyui_version and form_factor to the API request
|
# 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_uri = f'{base_url}/nodes?page={page}&limit=30&comfyui_version={comfyui_ver}&form_factor={form_factor}'
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import manager_downloader
|
|||||||
from node_package import InstalledNodePackage
|
from node_package import InstalledNodePackage
|
||||||
|
|
||||||
|
|
||||||
version_code = [3, 30, 3]
|
version_code = [3, 30, 9]
|
||||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||||
|
|
||||||
|
|
||||||
@@ -53,6 +53,11 @@ DEFAULT_CHANNEL = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/ma
|
|||||||
default_custom_nodes_path = None
|
default_custom_nodes_path = None
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidChannel(Exception):
|
||||||
|
def __init__(self, channel):
|
||||||
|
self.channel = channel
|
||||||
|
super().__init__(channel)
|
||||||
|
|
||||||
def get_default_custom_nodes_path():
|
def get_default_custom_nodes_path():
|
||||||
global default_custom_nodes_path
|
global default_custom_nodes_path
|
||||||
if default_custom_nodes_path is None:
|
if default_custom_nodes_path is None:
|
||||||
@@ -251,6 +256,7 @@ comfy_ui_revision = "Unknown"
|
|||||||
comfy_ui_commit_datetime = datetime(1900, 1, 1, 0, 0, 0)
|
comfy_ui_commit_datetime = datetime(1900, 1, 1, 0, 0, 0)
|
||||||
|
|
||||||
channel_dict = None
|
channel_dict = None
|
||||||
|
valid_channels = set()
|
||||||
channel_list = None
|
channel_list = None
|
||||||
|
|
||||||
|
|
||||||
@@ -355,7 +361,7 @@ def normalize_channel(channel):
|
|||||||
if channel_url:
|
if channel_url:
|
||||||
return channel_url
|
return channel_url
|
||||||
|
|
||||||
raise Exception(f"Invalid channel name '{channel}'")
|
raise InvalidChannel(channel)
|
||||||
|
|
||||||
|
|
||||||
class ManagedResult:
|
class ManagedResult:
|
||||||
@@ -770,6 +776,11 @@ class UnifiedManager:
|
|||||||
print(f"[bold red]ERROR: Invalid mode is specified `--mode {mode}`[/bold red]", file=sys.stderr)
|
print(f"[bold red]ERROR: Invalid mode is specified `--mode {mode}`[/bold red]", file=sys.stderr)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
# validate channel - only the channel set by the user is allowed.
|
||||||
|
if channel_url not in valid_channels:
|
||||||
|
logging.error(f'[ComfyUI-Manager] An invalid channel was used: {channel_url}')
|
||||||
|
raise InvalidChannel(channel_url)
|
||||||
|
|
||||||
json_obj = await get_data_by_mode(mode, 'custom-node-list.json', channel_url=channel_url)
|
json_obj = await get_data_by_mode(mode, 'custom-node-list.json', channel_url=channel_url)
|
||||||
for x in json_obj['custom_nodes']:
|
for x in json_obj['custom_nodes']:
|
||||||
try:
|
try:
|
||||||
@@ -841,6 +852,7 @@ class UnifiedManager:
|
|||||||
install_script_path = os.path.join(repo_path, "install.py")
|
install_script_path = os.path.join(repo_path, "install.py")
|
||||||
requirements_path = os.path.join(repo_path, "requirements.txt")
|
requirements_path = os.path.join(repo_path, "requirements.txt")
|
||||||
|
|
||||||
|
res = True
|
||||||
if lazy_mode:
|
if lazy_mode:
|
||||||
install_cmd = ["#LAZY-INSTALL-SCRIPT", sys.executable]
|
install_cmd = ["#LAZY-INSTALL-SCRIPT", sys.executable]
|
||||||
return try_install_script(url, repo_path, install_cmd)
|
return try_install_script(url, repo_path, install_cmd)
|
||||||
@@ -848,7 +860,6 @@ class UnifiedManager:
|
|||||||
if os.path.exists(requirements_path) and not no_deps:
|
if os.path.exists(requirements_path) and not no_deps:
|
||||||
print("Install: pip packages")
|
print("Install: pip packages")
|
||||||
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(), comfy_path, manager_files_path)
|
||||||
res = True
|
|
||||||
lines = manager_util.robust_readlines(requirements_path)
|
lines = manager_util.robust_readlines(requirements_path)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
package_name = remap_pip_package(line.strip())
|
package_name = remap_pip_package(line.strip())
|
||||||
@@ -859,15 +870,14 @@ class UnifiedManager:
|
|||||||
res = res and try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution)
|
res = res and try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution)
|
||||||
|
|
||||||
pip_fixer.fix_broken()
|
pip_fixer.fix_broken()
|
||||||
return res
|
|
||||||
|
|
||||||
if os.path.exists(install_script_path) and install_script_path not in self.processed_install:
|
if os.path.exists(install_script_path) and install_script_path not in self.processed_install:
|
||||||
self.processed_install.add(install_script_path)
|
self.processed_install.add(install_script_path)
|
||||||
print("Install: install script")
|
print("Install: install script")
|
||||||
install_cmd = [sys.executable, "install.py"]
|
install_cmd = [sys.executable, "install.py"]
|
||||||
return try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution)
|
return res and try_install_script(url, repo_path, install_cmd, instant_execution=instant_execution)
|
||||||
|
|
||||||
return True
|
return res
|
||||||
|
|
||||||
def reserve_cnr_switch(self, target, zip_url, from_path, to_path, no_deps):
|
def reserve_cnr_switch(self, target, zip_url, from_path, to_path, no_deps):
|
||||||
script_path = os.path.join(manager_startup_script_path, "install-scripts.txt")
|
script_path = os.path.join(manager_startup_script_path, "install-scripts.txt")
|
||||||
@@ -1415,7 +1425,11 @@ class UnifiedManager:
|
|||||||
version_spec = self.resolve_unspecified_version(node_id)
|
version_spec = self.resolve_unspecified_version(node_id)
|
||||||
|
|
||||||
if version_spec == 'unknown' or version_spec == 'nightly':
|
if version_spec == 'unknown' or version_spec == 'nightly':
|
||||||
custom_nodes = await self.get_custom_nodes(channel, mode)
|
try:
|
||||||
|
custom_nodes = await self.get_custom_nodes(channel, mode)
|
||||||
|
except InvalidChannel as e:
|
||||||
|
return ManagedResult('fail').fail(f'Invalid channel is used: {e.channel}')
|
||||||
|
|
||||||
the_node = custom_nodes.get(node_id)
|
the_node = custom_nodes.get(node_id)
|
||||||
if the_node is not None:
|
if the_node is not None:
|
||||||
if version_spec == 'unknown':
|
if version_spec == 'unknown':
|
||||||
@@ -1473,28 +1487,6 @@ class UnifiedManager:
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
async def migrate_unmanaged_nodes(self):
|
|
||||||
"""
|
|
||||||
fix path for nightly and unknown nodes of unmanaged nodes
|
|
||||||
"""
|
|
||||||
await self.reload('cache')
|
|
||||||
await self.get_custom_nodes('default', 'cache')
|
|
||||||
|
|
||||||
print("Migration: STAGE 1")
|
|
||||||
moves = []
|
|
||||||
|
|
||||||
# migrate nightly inactive
|
|
||||||
for x, v in self.nightly_inactive_nodes.items():
|
|
||||||
if v.endswith('@nightly'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_path = os.path.join(get_default_custom_nodes_path(), '.disabled', f"{x}@nightly")
|
|
||||||
moves.append((v, new_path))
|
|
||||||
|
|
||||||
self.reserve_migration(moves)
|
|
||||||
|
|
||||||
print("DONE (Migration reserved)")
|
|
||||||
|
|
||||||
|
|
||||||
unified_manager = UnifiedManager()
|
unified_manager = UnifiedManager()
|
||||||
|
|
||||||
@@ -1564,8 +1556,14 @@ def get_installed_node_packs():
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_channel_dict():
|
||||||
|
if channel_dict is None:
|
||||||
|
get_channel_dict()
|
||||||
|
|
||||||
|
|
||||||
def get_channel_dict():
|
def get_channel_dict():
|
||||||
global channel_dict
|
global channel_dict
|
||||||
|
global valid_channels
|
||||||
|
|
||||||
if channel_dict is None:
|
if channel_dict is None:
|
||||||
channel_dict = {}
|
channel_dict = {}
|
||||||
@@ -1579,6 +1577,7 @@ def get_channel_dict():
|
|||||||
channel_info = x.split("::")
|
channel_info = x.split("::")
|
||||||
if len(channel_info) == 2:
|
if len(channel_info) == 2:
|
||||||
channel_dict[channel_info[0]] = channel_info[1]
|
channel_dict[channel_info[0]] = channel_info[1]
|
||||||
|
valid_channels.add(channel_info[1])
|
||||||
|
|
||||||
return channel_dict
|
return channel_dict
|
||||||
|
|
||||||
|
|||||||
@@ -273,14 +273,23 @@ import zipfile
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
def get_model_dir(data, show_log=False):
|
def get_model_dir(data, show_log=False) -> str | None:
|
||||||
if 'download_model_base' in folder_paths.folder_names_and_paths:
|
if 'download_model_base' in folder_paths.folder_names_and_paths:
|
||||||
models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0]
|
models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0]
|
||||||
else:
|
else:
|
||||||
models_base = folder_paths.models_dir
|
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):
|
def resolve_custom_node(save_path):
|
||||||
save_path = save_path[13:] # remove 'custom_nodes/'
|
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
|
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.
|
# NOTE: The creation of files within the custom node path should be removed in the future.
|
||||||
@@ -399,7 +408,6 @@ async def task_worker():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
node_spec = core.unified_manager.resolve_node_spec(node_spec_str)
|
node_spec = core.unified_manager.resolve_node_spec(node_spec_str)
|
||||||
|
|
||||||
if node_spec is None:
|
if node_spec is None:
|
||||||
logging.error(f"Cannot resolve install target: '{node_spec_str}'")
|
logging.error(f"Cannot resolve install target: '{node_spec_str}'")
|
||||||
return f"Cannot resolve install target: '{node_spec_str}'"
|
return f"Cannot resolve install target: '{node_spec_str}'"
|
||||||
@@ -929,6 +937,7 @@ def check_model_installed(json_obj):
|
|||||||
|
|
||||||
@routes.get("/externalmodel/getlist")
|
@routes.get("/externalmodel/getlist")
|
||||||
async def fetch_externalmodel_list(request):
|
async def fetch_externalmodel_list(request):
|
||||||
|
# The model list is only allowed in the default channel, yet.
|
||||||
json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'model-list.json')
|
json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'model-list.json')
|
||||||
|
|
||||||
check_model_installed(json_obj)
|
check_model_installed(json_obj)
|
||||||
@@ -1197,9 +1206,8 @@ async def install_custom_node(request):
|
|||||||
|
|
||||||
git_url = None
|
git_url = None
|
||||||
|
|
||||||
if json_data['version'] != 'unknown':
|
selected_version = json_data.get('selected_version')
|
||||||
selected_version = json_data.get('selected_version')
|
if json_data['version'] != 'unknown' and selected_version != 'unknown':
|
||||||
|
|
||||||
if skip_post_install:
|
if skip_post_install:
|
||||||
if cnr_id in core.unified_manager.nightly_inactive_nodes or cnr_id in core.unified_manager.cnr_inactive_nodes:
|
if cnr_id in core.unified_manager.nightly_inactive_nodes or cnr_id in core.unified_manager.cnr_inactive_nodes:
|
||||||
core.unified_manager.unified_enable(cnr_id)
|
core.unified_manager.unified_enable(cnr_id)
|
||||||
@@ -1216,6 +1224,9 @@ async def install_custom_node(request):
|
|||||||
if git_url is None:
|
if git_url is None:
|
||||||
logging.error(f"[ComfyUI-Manager] Following node pack doesn't provide `nightly` version: ${git_url}")
|
logging.error(f"[ComfyUI-Manager] Following node pack doesn't provide `nightly` version: ${git_url}")
|
||||||
return web.Response(status=404, text=f"Following node pack doesn't provide `nightly` version: ${git_url}")
|
return web.Response(status=404, text=f"Following node pack doesn't provide `nightly` version: ${git_url}")
|
||||||
|
elif json_data['version'] != 'unknown' and selected_version == 'unknown':
|
||||||
|
logging.error(f"[ComfyUI-Manager] Invalid installation request: {json_data}")
|
||||||
|
return web.Response(status=400, text="Invalid installation request")
|
||||||
else:
|
else:
|
||||||
# unknown
|
# unknown
|
||||||
unknown_name = os.path.basename(json_data['files'][0])
|
unknown_name = os.path.basename(json_data['files'][0])
|
||||||
@@ -1407,17 +1418,14 @@ async def disable_node(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/migrate_unmanaged_nodes")
|
async def check_whitelist_for_model(item):
|
||||||
async def migrate_unmanaged_nodes(request):
|
json_obj = await core.get_data_by_mode('cache', 'model-list.json')
|
||||||
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
|
||||||
|
|
||||||
@routes.get("/manager/need_to_migrate")
|
return False
|
||||||
async def need_to_migrate(request):
|
|
||||||
return web.Response(text=str(core.need_to_migrate), status=200)
|
|
||||||
|
|
||||||
|
|
||||||
@routes.post("/manager/queue/install_model")
|
@routes.post("/manager/queue/install_model")
|
||||||
@@ -1428,6 +1436,11 @@ async def install_model(request):
|
|||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||||
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
||||||
|
|
||||||
|
# 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'):
|
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')
|
models_json = await core.get_data_by_mode('cache', 'model-list.json', 'default')
|
||||||
|
|
||||||
@@ -1695,6 +1708,7 @@ cm_global.register_api('cm.try-install-custom-node', confirm_try_install)
|
|||||||
|
|
||||||
|
|
||||||
async def default_cache_update():
|
async def default_cache_update():
|
||||||
|
core.refresh_channel_dict()
|
||||||
channel_url = core.get_config()['channel_url']
|
channel_url = core.get_config()['channel_url']
|
||||||
async def get_cache(filename):
|
async def get_cache(filename):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -9,6 +9,26 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"author": "finegrain",
|
||||||
|
"title": "comfyui-finegrain",
|
||||||
|
"reference": "https://github.com/finegrain-ai/comfyui-finegrain",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/finegrain-ai/comfyui-finegrain"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI custom nodes to interact with the Finegrain API."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Yuan-ManX",
|
||||||
|
"title": "ComfyUI-StyleStudio",
|
||||||
|
"reference": "https://github.com/Yuan-ManX/ComfyUI-StyleStudio",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Yuan-ManX/ComfyUI-StyleStudio"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI nodes for StyleStudio model."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"author": "Jerome Bacquet",
|
"author": "Jerome Bacquet",
|
||||||
"title": "ComfyUI XenoFlow",
|
"title": "ComfyUI XenoFlow",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-manager"
|
name = "comfyui-manager"
|
||||||
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
||||||
version = "3.30.3"
|
version = "3.30.9"
|
||||||
license = { file = "LICENSE.txt" }
|
license = { file = "LICENSE.txt" }
|
||||||
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user