mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2025-12-15 01:27:05 +08:00
support CNR
This commit is contained in:
parent
3c2eb84602
commit
b3be556837
660
cm-cli.py
660
cm-cli.py
@ -4,9 +4,9 @@ import traceback
|
|||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
|
||||||
import concurrent
|
import concurrent
|
||||||
import threading
|
import threading
|
||||||
|
import yaml
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
@ -17,10 +17,12 @@ import git
|
|||||||
|
|
||||||
sys.path.append(os.path.dirname(__file__))
|
sys.path.append(os.path.dirname(__file__))
|
||||||
sys.path.append(os.path.join(os.path.dirname(__file__), "glob"))
|
sys.path.append(os.path.join(os.path.dirname(__file__), "glob"))
|
||||||
import manager_core as core
|
|
||||||
import cm_global
|
import cm_global
|
||||||
|
import manager_core as core
|
||||||
|
from manager_core import unified_manager
|
||||||
|
import cnr_utils
|
||||||
|
|
||||||
comfyui_manager_path = os.path.dirname(__file__)
|
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
|
||||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
comfy_path = os.environ.get('COMFYUI_PATH')
|
||||||
|
|
||||||
if comfy_path is None:
|
if comfy_path is None:
|
||||||
@ -78,9 +80,7 @@ read_downgrade_blacklist() # This is a preparation step for manager_core
|
|||||||
class Ctx:
|
class Ctx:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.channel = 'default'
|
self.channel = 'default'
|
||||||
self.mode = 'remote'
|
self.mode = 'cache'
|
||||||
self.processed_install = set()
|
|
||||||
self.custom_node_map_cache = None
|
|
||||||
|
|
||||||
def set_channel_mode(self, channel, mode):
|
def set_channel_mode(self, channel, mode):
|
||||||
if mode is not None:
|
if mode is not None:
|
||||||
@ -97,196 +97,143 @@ class Ctx:
|
|||||||
if channel is not None:
|
if channel is not None:
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
|
|
||||||
def post_install(self, url):
|
asyncio.run(unified_manager.reload(cache_mode=self.mode == 'cache'))
|
||||||
try:
|
asyncio.run(unified_manager.load_nightly(self.channel, self.mode))
|
||||||
repository_name = url.split("/")[-1].strip()
|
|
||||||
repo_path = os.path.join(custom_nodes_path, repository_name)
|
|
||||||
repo_path = os.path.abspath(repo_path)
|
|
||||||
|
|
||||||
requirements_path = os.path.join(repo_path, 'requirements.txt')
|
|
||||||
install_script_path = os.path.join(repo_path, 'install.py')
|
|
||||||
|
|
||||||
if os.path.exists(requirements_path):
|
|
||||||
with (open(requirements_path, 'r', encoding="UTF-8", errors="ignore") as file):
|
|
||||||
for line in file:
|
|
||||||
package_name = core.remap_pip_package(line.strip())
|
|
||||||
if package_name and not core.is_installed(package_name):
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", package_name]
|
|
||||||
output = subprocess.check_output(install_cmd, cwd=repo_path, text=True)
|
|
||||||
for msg_line in output.split('\n'):
|
|
||||||
if 'Requirement already satisfied:' in msg_line:
|
|
||||||
print('.', end='')
|
|
||||||
else:
|
|
||||||
print(msg_line)
|
|
||||||
|
|
||||||
if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in self.processed_install:
|
|
||||||
self.processed_install.add(f'{repo_path}/install.py')
|
|
||||||
install_cmd = [sys.executable, install_script_path]
|
|
||||||
output = subprocess.check_output(install_cmd, cwd=repo_path, text=True)
|
|
||||||
for msg_line in output.split('\n'):
|
|
||||||
if 'Requirement already satisfied:' in msg_line:
|
|
||||||
print('.', end='')
|
|
||||||
else:
|
|
||||||
print(msg_line)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
print(f"ERROR: Restoring '{url}' is failed.")
|
|
||||||
|
|
||||||
def restore_dependencies(self):
|
|
||||||
node_paths = [os.path.join(custom_nodes_path, name) for name in os.listdir(custom_nodes_path)
|
|
||||||
if os.path.isdir(os.path.join(custom_nodes_path, name)) and not name.endswith('.disabled')]
|
|
||||||
|
|
||||||
total = len(node_paths)
|
|
||||||
i = 1
|
|
||||||
for x in node_paths:
|
|
||||||
print(f"----------------------------------------------------------------------------------------------------")
|
|
||||||
print(f"Restoring [{i}/{total}]: {x}")
|
|
||||||
self.post_install(x)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
def load_custom_nodes(self):
|
|
||||||
channel_dict = core.get_channel_dict()
|
|
||||||
if self.channel not in channel_dict:
|
|
||||||
print(f"[bold red]ERROR: Invalid channel is specified `--channel {self.channel}`[/bold red]", file=sys.stderr)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if self.mode not in ['remote', 'local', 'cache']:
|
|
||||||
print(f"[bold red]ERROR: Invalid mode is specified `--mode {self.mode}`[/bold red]", file=sys.stderr)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
channel_url = channel_dict[self.channel]
|
|
||||||
|
|
||||||
res = {}
|
|
||||||
json_obj = asyncio.run(core.get_data_by_mode(self.mode, 'custom-node-list.json', channel_url=channel_url))
|
|
||||||
for x in json_obj['custom_nodes']:
|
|
||||||
for y in x['files']:
|
|
||||||
if 'github.com' in y and not (y.endswith('.py') or y.endswith('.js')):
|
|
||||||
repo_name = y.split('/')[-1]
|
|
||||||
res[repo_name] = (x, False)
|
|
||||||
|
|
||||||
if 'id' in x:
|
|
||||||
if x['id'] not in res:
|
|
||||||
res[x['id']] = (x, True)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_custom_node_map(self):
|
|
||||||
if self.custom_node_map_cache is not None:
|
|
||||||
return self.custom_node_map_cache
|
|
||||||
|
|
||||||
self.custom_node_map_cache = self.load_custom_nodes()
|
|
||||||
|
|
||||||
return self.custom_node_map_cache
|
|
||||||
|
|
||||||
def lookup_node_path(self, node_name, robust=False):
|
|
||||||
if '..' in node_name:
|
|
||||||
print(f"\n[bold red]ERROR: Invalid node name '{node_name}'[/bold red]\n")
|
|
||||||
exit(2)
|
|
||||||
|
|
||||||
custom_node_map = self.get_custom_node_map()
|
|
||||||
if node_name in custom_node_map:
|
|
||||||
node_url = custom_node_map[node_name][0]['files'][0]
|
|
||||||
repo_name = node_url.split('/')[-1]
|
|
||||||
node_path = os.path.join(custom_nodes_path, repo_name)
|
|
||||||
return node_path, custom_node_map[node_name][0]
|
|
||||||
elif robust:
|
|
||||||
node_path = os.path.join(custom_nodes_path, node_name)
|
|
||||||
return node_path, None
|
|
||||||
|
|
||||||
print(f"\n[bold red]ERROR: Invalid node name '{node_name}'[/bold red]\n")
|
|
||||||
exit(2)
|
|
||||||
|
|
||||||
|
|
||||||
cm_ctx = Ctx()
|
channel_ctx = Ctx()
|
||||||
|
|
||||||
|
|
||||||
def install_node(node_name, is_all=False, cnt_msg=''):
|
def install_node(node_spec_str, is_all=False, cnt_msg=''):
|
||||||
if core.is_valid_url(node_name):
|
if core.is_valid_url(node_spec_str):
|
||||||
# install via urls
|
# install via urls
|
||||||
res = core.gitclone_install([node_name])
|
res = asyncio.run(core.gitclone_install(node_spec_str))
|
||||||
if not res:
|
if not res.result:
|
||||||
print(f"[bold red]ERROR: An error occurred while installing '{node_name}'.[/bold red]")
|
print(res.msg)
|
||||||
|
print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]")
|
||||||
else:
|
else:
|
||||||
print(f"{cnt_msg} [INSTALLED] {node_name:50}")
|
print(f"{cnt_msg} [INSTALLED] {node_spec_str:50}")
|
||||||
else:
|
else:
|
||||||
node_path, node_item = cm_ctx.lookup_node_path(node_name)
|
node_spec = unified_manager.resolve_node_spec(node_spec_str)
|
||||||
|
|
||||||
if os.path.exists(node_path):
|
if node_spec is None:
|
||||||
if not is_all:
|
return
|
||||||
print(f"{cnt_msg} [ SKIPPED ] {node_name:50} => Already installed")
|
|
||||||
elif os.path.exists(node_path + '.disabled'):
|
node_name, version_spec, is_specified = node_spec
|
||||||
enable_node(node_name)
|
|
||||||
|
# NOTE: install node doesn't allow update if version is not specified
|
||||||
|
if not is_specified:
|
||||||
|
version_spec = None
|
||||||
|
|
||||||
|
res = asyncio.run(unified_manager.install_by_id(node_name, version_spec, channel_ctx.channel, channel_ctx.mode, instant_execution=True))
|
||||||
|
|
||||||
|
if res.action == 'skip':
|
||||||
|
print(f"{cnt_msg} [ SKIP ] {node_name:50} => Already installed")
|
||||||
|
elif res.action == 'enable':
|
||||||
|
print(f"{cnt_msg} [ ENABLED ] {node_name:50}")
|
||||||
|
elif res.action == 'install-git' and res.target == 'nightly':
|
||||||
|
print(f"{cnt_msg} [INSTALLED] {node_name:50}[NIGHTLY]")
|
||||||
|
elif res.action == 'install-git' and res.target == 'unknown':
|
||||||
|
print(f"{cnt_msg} [INSTALLED] {node_name:50}[UNKNOWN]")
|
||||||
|
elif res.action == 'install-cnr' and res.result:
|
||||||
|
print(f"{cnt_msg} [INSTALLED] {node_name:50}[{res.target}]")
|
||||||
|
elif res.action == 'switch-cnr' and res.result:
|
||||||
|
print(f"{cnt_msg} [INSTALLED] {node_name:50}[{res.target}]")
|
||||||
|
elif (res.action == 'switch-cnr' or res.action == 'install-cnr') and not res.result and node_name in unified_manager.cnr_map:
|
||||||
|
print(f"\nAvailable version of '{node_name}'")
|
||||||
|
show_versions(node_name)
|
||||||
|
print("")
|
||||||
else:
|
else:
|
||||||
res = core.gitclone_install(node_item['files'], instant_execution=True, msg_prefix=f"[{cnt_msg}] ")
|
print(f"[bold red]ERROR: An error occurred while installing '{node_name}'.\n{res.msg}[/bold red]")
|
||||||
if not res:
|
|
||||||
print(f"[bold red]ERROR: An error occurred while installing '{node_name}'.[/bold red]")
|
|
||||||
else:
|
|
||||||
print(f"{cnt_msg} [INSTALLED] {node_name:50}")
|
|
||||||
|
|
||||||
|
|
||||||
def reinstall_node(node_name, is_all=False, cnt_msg=''):
|
def reinstall_node(node_spec_str, is_all=False, cnt_msg=''):
|
||||||
node_path, node_item = cm_ctx.lookup_node_path(node_name)
|
node_spec = unified_manager.resolve_node_spec(node_spec_str)
|
||||||
|
|
||||||
if os.path.exists(node_path):
|
node_name, version_spec, _ = node_spec
|
||||||
shutil.rmtree(node_path)
|
|
||||||
if os.path.exists(node_path + '.disabled'):
|
|
||||||
shutil.rmtree(node_path + '.disabled')
|
|
||||||
|
|
||||||
|
unified_manager.unified_uninstall(node_name, version_spec == 'unknown')
|
||||||
install_node(node_name, is_all=is_all, cnt_msg=cnt_msg)
|
install_node(node_name, is_all=is_all, cnt_msg=cnt_msg)
|
||||||
|
|
||||||
|
|
||||||
def fix_node(node_name, is_all=False, cnt_msg=''):
|
def fix_node(node_spec_str, is_all=False, cnt_msg=''):
|
||||||
node_path, node_item = cm_ctx.lookup_node_path(node_name, robust=True)
|
node_spec = unified_manager.resolve_node_spec(node_spec_str, guess_mode='active')
|
||||||
|
|
||||||
files = node_item['files'] if node_item is not None else [node_path]
|
if node_spec is None:
|
||||||
|
if not is_all:
|
||||||
|
if unified_manager.resolve_node_spec(node_spec_str, guess_mode='inactive') is not None:
|
||||||
|
print(f"{cnt_msg} [ SKIPPED ]: {node_spec_str:50} => Disabled")
|
||||||
|
else:
|
||||||
|
print(f"{cnt_msg} [ SKIPPED ]: {node_spec_str:50} => Not installed")
|
||||||
|
|
||||||
if os.path.exists(node_path):
|
return
|
||||||
print(f"{cnt_msg} [ FIXING ]: {node_name:50} => Disabled")
|
|
||||||
res = core.gitclone_fix(files, instant_execution=True)
|
node_name, version_spec, _ = node_spec
|
||||||
if not res:
|
|
||||||
print(f"ERROR: An error occurred while fixing '{node_name}'.")
|
print(f"{cnt_msg} [ FIXING ]: {node_name:50}[{version_spec}]")
|
||||||
elif not is_all and os.path.exists(node_path + '.disabled'):
|
res = unified_manager.unified_fix(node_name, version_spec)
|
||||||
print(f"{cnt_msg} [ SKIPPED ]: {node_name:50} => Disabled")
|
|
||||||
elif not is_all:
|
if not res.result:
|
||||||
print(f"{cnt_msg} [ SKIPPED ]: {node_name:50} => Not installed")
|
print(f"ERROR: f{res.msg}")
|
||||||
|
|
||||||
|
|
||||||
def uninstall_node(node_name, is_all=False, cnt_msg=''):
|
def uninstall_node(node_spec_str, is_all=False, cnt_msg=''):
|
||||||
node_path, node_item = cm_ctx.lookup_node_path(node_name, robust=True)
|
spec = node_spec_str.split('@')
|
||||||
|
if len(spec) == 2 and spec[1] == 'unknown':
|
||||||
files = node_item['files'] if node_item is not None else [node_path]
|
node_name = spec[0]
|
||||||
|
is_unknown = True
|
||||||
if os.path.exists(node_path) or os.path.exists(node_path + '.disabled'):
|
|
||||||
res = core.gitclone_uninstall(files)
|
|
||||||
if not res:
|
|
||||||
print(f"ERROR: An error occurred while uninstalling '{node_name}'.")
|
|
||||||
else:
|
|
||||||
print(f"{cnt_msg} [UNINSTALLED] {node_name:50}")
|
|
||||||
else:
|
else:
|
||||||
|
node_name = spec[0]
|
||||||
|
is_unknown = False
|
||||||
|
|
||||||
|
res = unified_manager.unified_uninstall(node_name, is_unknown)
|
||||||
|
if len(spec) == 1 and res.action == 'skip' and not is_unknown:
|
||||||
|
res = unified_manager.unified_uninstall(node_name, True)
|
||||||
|
|
||||||
|
if res.action == 'skip':
|
||||||
print(f"{cnt_msg} [ SKIPPED ]: {node_name:50} => Not installed")
|
print(f"{cnt_msg} [ SKIPPED ]: {node_name:50} => Not installed")
|
||||||
|
|
||||||
|
elif res.result:
|
||||||
|
print(f"{cnt_msg} [UNINSTALLED] {node_name:50}")
|
||||||
|
else:
|
||||||
|
print(f"ERROR: An error occurred while uninstalling '{node_name}'.")
|
||||||
|
|
||||||
def update_node(node_name, is_all=False, cnt_msg=''):
|
|
||||||
node_path, node_item = cm_ctx.lookup_node_path(node_name, robust=True)
|
|
||||||
|
|
||||||
files = node_item['files'] if node_item is not None else [node_path]
|
def update_node(node_spec_str, is_all=False, cnt_msg=''):
|
||||||
|
node_spec = unified_manager.resolve_node_spec(node_spec_str, 'active')
|
||||||
|
|
||||||
res = core.gitclone_update(files, skip_script=True, msg_prefix=f"[{cnt_msg}] ")
|
if node_spec is None:
|
||||||
|
if unified_manager.resolve_node_spec(node_spec_str, 'inactive'):
|
||||||
if not res:
|
print(f"{cnt_msg} [ SKIPPED ]: {node_spec_str:50} => Disabled")
|
||||||
print(f"ERROR: An error occurred while updating '{node_name}'.")
|
else:
|
||||||
|
print(f"{cnt_msg} [ SKIPPED ]: {node_spec_str:50} => Not installed")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return node_path
|
node_name, version_spec, _ = node_spec
|
||||||
|
|
||||||
|
res = unified_manager.unified_update(node_name, version_spec, return_postinstall=True)
|
||||||
|
|
||||||
|
if not res.result:
|
||||||
|
print(f"ERROR: An error occurred while updating '{node_name}'.")
|
||||||
|
elif res.action == 'skip':
|
||||||
|
print(f"{cnt_msg} [ SKIPPED ]: {node_name:50} => {res.msg}")
|
||||||
|
else:
|
||||||
|
print(f"{cnt_msg} [ UPDATED ]: {node_name:50} => ({version_spec} -> {res.target})")
|
||||||
|
|
||||||
|
return res.with_target(f'{node_name}@{res.target}')
|
||||||
|
|
||||||
|
|
||||||
def update_parallel(nodes):
|
def update_parallel(nodes):
|
||||||
is_all = False
|
is_all = False
|
||||||
if 'all' in nodes:
|
if 'all' in nodes:
|
||||||
is_all = True
|
is_all = True
|
||||||
nodes = [x for x in cm_ctx.get_custom_node_map().keys() if os.path.exists(os.path.join(custom_nodes_path, x)) or os.path.exists(os.path.join(custom_nodes_path, x) + '.disabled')]
|
nodes = []
|
||||||
|
for x in unified_manager.active_nodes.keys():
|
||||||
nodes = [x for x in nodes if x.lower() not in ['comfy', 'comfyui', 'all']]
|
nodes.append(x)
|
||||||
|
for x in unified_manager.unknown_active_nodes.keys():
|
||||||
|
nodes.append(x+"@unknown")
|
||||||
|
else:
|
||||||
|
nodes = [x for x in nodes if x.lower() not in ['comfy', 'comfyui']]
|
||||||
|
|
||||||
total = len(nodes)
|
total = len(nodes)
|
||||||
|
|
||||||
@ -303,9 +250,9 @@ def update_parallel(nodes):
|
|||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
node_path = update_node(x, is_all=is_all, cnt_msg=f'{i}/{total}')
|
res = update_node(x, is_all=is_all, cnt_msg=f'{i}/{total}')
|
||||||
with lock:
|
with lock:
|
||||||
processed.append(node_path)
|
processed.append(res)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: {e}")
|
print(f"ERROR: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -315,12 +262,11 @@ def update_parallel(nodes):
|
|||||||
executor.submit(process_custom_node, item)
|
executor.submit(process_custom_node, item)
|
||||||
|
|
||||||
i = 1
|
i = 1
|
||||||
for node_path in processed:
|
for res in processed:
|
||||||
if node_path is None:
|
if res is not None:
|
||||||
print(f"[{i}/{total}] Post update: ERROR")
|
print(f"[{i}/{total}] Post update: {res.target}")
|
||||||
else:
|
if res.postinstall is not None:
|
||||||
print(f"[{i}/{total}] Post update: {node_path}")
|
res.postinstall()
|
||||||
cm_ctx.post_install(node_path)
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
@ -334,100 +280,158 @@ def update_comfyui():
|
|||||||
print("ComfyUI is already up to date.")
|
print("ComfyUI is already up to date.")
|
||||||
|
|
||||||
|
|
||||||
def enable_node(node_name, is_all=False, cnt_msg=''):
|
def enable_node(node_spec_str, is_all=False, cnt_msg=''):
|
||||||
if node_name == 'ComfyUI-Manager':
|
if unified_manager.resolve_node_spec(node_spec_str, guess_mode='active') is not None:
|
||||||
|
print(f"{cnt_msg} [ SKIP ] {node_spec_str:50} => Already enabled")
|
||||||
return
|
return
|
||||||
|
|
||||||
node_path, node_item = cm_ctx.lookup_node_path(node_name, robust=True)
|
node_spec = unified_manager.resolve_node_spec(node_spec_str, guess_mode='inactive')
|
||||||
|
|
||||||
if os.path.exists(node_path + '.disabled'):
|
if node_spec is None:
|
||||||
current_name = node_path + '.disabled'
|
print(f"{cnt_msg} [ SKIP ] {node_spec_str:50} => Not found")
|
||||||
os.rename(current_name, node_path)
|
return
|
||||||
|
|
||||||
|
node_name, version_spec, _ = node_spec
|
||||||
|
|
||||||
|
res = unified_manager.unified_enable(node_name, version_spec)
|
||||||
|
|
||||||
|
if res.action == 'skip':
|
||||||
|
print(f"{cnt_msg} [ SKIP ] {node_name:50} => {res.msg}")
|
||||||
|
elif res.result:
|
||||||
print(f"{cnt_msg} [ENABLED] {node_name:50}")
|
print(f"{cnt_msg} [ENABLED] {node_name:50}")
|
||||||
elif os.path.exists(node_path):
|
else:
|
||||||
print(f"{cnt_msg} [SKIPPED] {node_name:50} => Already enabled")
|
print(f"{cnt_msg} [ FAIL ] {node_name:50} => {res.msg}")
|
||||||
elif not is_all:
|
|
||||||
print(f"{cnt_msg} [SKIPPED] {node_name:50} => Not installed")
|
|
||||||
|
|
||||||
|
|
||||||
def disable_node(node_name, is_all=False, cnt_msg=''):
|
def disable_node(node_spec_str: str, is_all=False, cnt_msg=''):
|
||||||
if node_name == 'ComfyUI-Manager':
|
if 'comfyui-manager' in node_spec_str.lower():
|
||||||
return
|
return
|
||||||
|
|
||||||
node_path, node_item = cm_ctx.lookup_node_path(node_name, robust=True)
|
node_spec = unified_manager.resolve_node_spec(node_spec_str, guess_mode='active')
|
||||||
|
|
||||||
if os.path.exists(node_path):
|
if node_spec is None:
|
||||||
current_name = node_path
|
if unified_manager.resolve_node_spec(node_spec_str, guess_mode='inactive') is not None:
|
||||||
new_name = node_path + '.disabled'
|
print(f"{cnt_msg} [ SKIP ] {node_spec_str:50} => Already disabled")
|
||||||
os.rename(current_name, new_name)
|
else:
|
||||||
|
print(f"{cnt_msg} [ SKIP ] {node_spec_str:50} => Not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
node_name, version_spec, _ = node_spec
|
||||||
|
|
||||||
|
res = unified_manager.unified_disable(node_name, version_spec == 'unknown')
|
||||||
|
|
||||||
|
if res.action == 'skip':
|
||||||
|
print(f"{cnt_msg} [ SKIP ] {node_name:50} => {res.msg}")
|
||||||
|
elif res.result:
|
||||||
print(f"{cnt_msg} [DISABLED] {node_name:50}")
|
print(f"{cnt_msg} [DISABLED] {node_name:50}")
|
||||||
elif os.path.exists(node_path + '.disabled'):
|
else:
|
||||||
print(f"{cnt_msg} [ SKIPPED] {node_name:50} => Already disabled")
|
print(f"{cnt_msg} [ FAIL ] {node_name:50} => {res.msg}")
|
||||||
elif not is_all:
|
|
||||||
print(f"{cnt_msg} [ SKIPPED] {node_name:50} => Not installed")
|
|
||||||
|
|
||||||
|
|
||||||
def show_list(kind, simple=False):
|
def show_list(kind, simple=False):
|
||||||
for k, v in cm_ctx.get_custom_node_map().items():
|
custom_nodes = asyncio.run(unified_manager.get_custom_nodes(channel=channel_ctx.channel, mode=channel_ctx.mode))
|
||||||
if v[1]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
node_path = os.path.join(custom_nodes_path, k)
|
# collect not-installed unknown nodes
|
||||||
|
not_installed_unknown_nodes = []
|
||||||
|
repo_unknown = {}
|
||||||
|
|
||||||
states = set()
|
for k, v in custom_nodes.items():
|
||||||
if os.path.exists(node_path):
|
if 'cnr_latest' not in v:
|
||||||
prefix = '[ ENABLED ] '
|
if len(v['files']) == 1:
|
||||||
states.add('installed')
|
repo_url = v['files'][0]
|
||||||
states.add('enabled')
|
node_name = repo_url.split('/')[-1]
|
||||||
states.add('all')
|
if node_name not in unified_manager.unknown_inactive_nodes and node_name not in unified_manager.unknown_active_nodes:
|
||||||
elif os.path.exists(node_path + '.disabled'):
|
not_installed_unknown_nodes.append(v)
|
||||||
prefix = '[ DISABLED ] '
|
|
||||||
states.add('installed')
|
|
||||||
states.add('disabled')
|
|
||||||
states.add('all')
|
|
||||||
else:
|
|
||||||
prefix = '[ NOT INSTALLED ] '
|
|
||||||
states.add('not-installed')
|
|
||||||
states.add('all')
|
|
||||||
|
|
||||||
if kind in states:
|
|
||||||
if simple:
|
|
||||||
print(f"{k:50}")
|
|
||||||
else:
|
|
||||||
short_id = v[0].get('id', "")
|
|
||||||
print(f"{prefix} {k:50} {short_id:20} (author: {v[0]['author']})")
|
|
||||||
|
|
||||||
# unregistered nodes
|
|
||||||
candidates = os.listdir(os.path.realpath(custom_nodes_path))
|
|
||||||
|
|
||||||
for k in candidates:
|
|
||||||
fullpath = os.path.join(custom_nodes_path, k)
|
|
||||||
|
|
||||||
if os.path.isfile(fullpath):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if k in ['__pycache__']:
|
|
||||||
continue
|
|
||||||
|
|
||||||
states = set()
|
|
||||||
if k.endswith('.disabled'):
|
|
||||||
prefix = '[ DISABLED ] '
|
|
||||||
states.add('installed')
|
|
||||||
states.add('disabled')
|
|
||||||
states.add('all')
|
|
||||||
k = k[:-9]
|
|
||||||
else:
|
|
||||||
prefix = '[ ENABLED ] '
|
|
||||||
states.add('installed')
|
|
||||||
states.add('enabled')
|
|
||||||
states.add('all')
|
|
||||||
|
|
||||||
if k not in cm_ctx.get_custom_node_map():
|
|
||||||
if kind in states:
|
|
||||||
if simple:
|
|
||||||
print(f"{k:50}")
|
|
||||||
else:
|
else:
|
||||||
print(f"{prefix} {k:50} {'':20} (author: N/A)")
|
repo_unknown[node_name] = v
|
||||||
|
|
||||||
|
processed = {}
|
||||||
|
unknown_processed = []
|
||||||
|
|
||||||
|
flag = kind in ['all', 'cnr', 'installed', 'enabled']
|
||||||
|
for k, v in unified_manager.active_nodes.items():
|
||||||
|
if flag:
|
||||||
|
cnr = unified_manager.cnr_map[k]
|
||||||
|
processed[k] = "[ ENABLED ] ", cnr['name'], k, cnr['publisher']['name'], v[0]
|
||||||
|
else:
|
||||||
|
processed[k] = None
|
||||||
|
|
||||||
|
if flag and kind != 'cnr':
|
||||||
|
for k, v in unified_manager.unknown_active_nodes.items():
|
||||||
|
item = repo_unknown.get(k)
|
||||||
|
|
||||||
|
if item is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
log_item = "[ ENABLED ] ", item['title'], k, item['author']
|
||||||
|
unknown_processed.append(log_item)
|
||||||
|
|
||||||
|
flag = kind in ['all', 'cnr', 'installed', 'disabled']
|
||||||
|
for k, v in unified_manager.cnr_inactive_nodes.items():
|
||||||
|
if k in processed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if flag:
|
||||||
|
cnr = unified_manager.cnr_map[k]
|
||||||
|
processed[k] = "[ DISABLED ] ", cnr['name'], k, cnr['publisher']['name'], ", ".join(list(v.keys()))
|
||||||
|
else:
|
||||||
|
processed[k] = None
|
||||||
|
|
||||||
|
for k, v in unified_manager.nightly_inactive_nodes.items():
|
||||||
|
if k in processed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if flag:
|
||||||
|
cnr = unified_manager.cnr_map[k]
|
||||||
|
processed[k] = "[ DISABLED ] ", cnr['name'], k, cnr['publisher']['name'], 'nightly'
|
||||||
|
else:
|
||||||
|
processed[k] = None
|
||||||
|
|
||||||
|
if flag and kind != 'cnr':
|
||||||
|
for k, v in unified_manager.unknown_inactive_nodes.items():
|
||||||
|
item = repo_unknown.get(k)
|
||||||
|
|
||||||
|
if item is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
log_item = "[ DISABLED ] ", item['title'], k, item['author']
|
||||||
|
unknown_processed.append(log_item)
|
||||||
|
|
||||||
|
flag = kind in ['all', 'cnr', 'not-installed']
|
||||||
|
for k, v in unified_manager.cnr_map.items():
|
||||||
|
if k in processed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if flag:
|
||||||
|
cnr = unified_manager.cnr_map[k]
|
||||||
|
ver_spec = v['latest_version']['version'] if 'latest_version' in v else '0.0.0'
|
||||||
|
processed[k] = "[ NOT INSTALLED ] ", cnr['name'], k, cnr['publisher']['name'], ver_spec
|
||||||
|
else:
|
||||||
|
processed[k] = None
|
||||||
|
|
||||||
|
if flag and kind != 'cnr':
|
||||||
|
for x in not_installed_unknown_nodes:
|
||||||
|
if len(x['files']) == 1:
|
||||||
|
node_id = os.path.basename(x['files'][0])
|
||||||
|
log_item = "[ NOT INSTALLED ] ", x['title'], node_id, x['author']
|
||||||
|
unknown_processed.append(log_item)
|
||||||
|
|
||||||
|
for x in processed.values():
|
||||||
|
if x is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
prefix, title, short_id, author, ver_spec = x
|
||||||
|
if simple:
|
||||||
|
print(title+'@'+ver_spec)
|
||||||
|
else:
|
||||||
|
print(f"{prefix} {title:50} {short_id:30} (author: {author:20}) \\[{ver_spec}]")
|
||||||
|
|
||||||
|
for x in unknown_processed:
|
||||||
|
prefix, title, short_id, author = x
|
||||||
|
if simple:
|
||||||
|
print(title+'@unknown')
|
||||||
|
else:
|
||||||
|
print(f"{prefix} {title:50} {short_id:30} (author: {author:20}) [UNKNOWN]")
|
||||||
|
|
||||||
|
|
||||||
def show_snapshot(simple_mode=False):
|
def show_snapshot(simple_mode=False):
|
||||||
@ -467,13 +471,48 @@ def auto_save_snapshot():
|
|||||||
print(f"Current snapshot is saved as `{path}`")
|
print(f"Current snapshot is saved as `{path}`")
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_installed_node_specs():
|
||||||
|
res = []
|
||||||
|
processed = set()
|
||||||
|
for k, v in unified_manager.active_nodes.items():
|
||||||
|
node_spec_str = f"{k}@{v[0]}"
|
||||||
|
res.append(node_spec_str)
|
||||||
|
processed.add(k)
|
||||||
|
|
||||||
|
for k, _ in unified_manager.cnr_inactive_nodes.keys():
|
||||||
|
if k in processed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
latest = unified_manager.get_from_cnr_inactive_nodes(k)
|
||||||
|
if latest is not None:
|
||||||
|
node_spec_str = f"{k}@{latest}"
|
||||||
|
res.append(node_spec_str)
|
||||||
|
|
||||||
|
for k, _ in unified_manager.nightly_inactive_nodes.keys():
|
||||||
|
if k in processed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
node_spec_str = f"{k}@nightly"
|
||||||
|
res.append(node_spec_str)
|
||||||
|
|
||||||
|
for k in unified_manager.unknown_active_nodes.keys():
|
||||||
|
node_spec_str = f"{k}@unknown"
|
||||||
|
res.append(node_spec_str)
|
||||||
|
|
||||||
|
for k in unified_manager.unknown_inactive_nodes.keys():
|
||||||
|
node_spec_str = f"{k}@unknown"
|
||||||
|
res.append(node_spec_str)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
def for_each_nodes(nodes, act, allow_all=True):
|
def for_each_nodes(nodes, act, allow_all=True):
|
||||||
is_all = False
|
is_all = False
|
||||||
if allow_all and 'all' in nodes:
|
if allow_all and 'all' in nodes:
|
||||||
is_all = True
|
is_all = True
|
||||||
nodes = [x for x in cm_ctx.get_custom_node_map().keys() if os.path.exists(os.path.join(custom_nodes_path, x)) or os.path.exists(os.path.join(custom_nodes_path, x) + '.disabled')]
|
nodes = get_all_installed_node_specs()
|
||||||
|
else:
|
||||||
nodes = [x for x in nodes if x.lower() not in ['comfy', 'comfyui', 'all']]
|
nodes = [x for x in nodes if x.lower() not in ['comfy', 'comfyui', 'all']]
|
||||||
|
|
||||||
total = len(nodes)
|
total = len(nodes)
|
||||||
i = 1
|
i = 1
|
||||||
@ -510,9 +549,9 @@ def install(
|
|||||||
mode: str = typer.Option(
|
mode: str = typer.Option(
|
||||||
None,
|
None,
|
||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
),
|
)
|
||||||
):
|
):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
for_each_nodes(nodes, act=install_node)
|
for_each_nodes(nodes, act=install_node)
|
||||||
|
|
||||||
|
|
||||||
@ -533,7 +572,7 @@ def reinstall(
|
|||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
for_each_nodes(nodes, act=reinstall_node)
|
for_each_nodes(nodes, act=reinstall_node)
|
||||||
|
|
||||||
|
|
||||||
@ -554,7 +593,7 @@ def uninstall(
|
|||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
for_each_nodes(nodes, act=uninstall_node)
|
for_each_nodes(nodes, act=uninstall_node)
|
||||||
|
|
||||||
|
|
||||||
@ -576,7 +615,7 @@ def update(
|
|||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
|
|
||||||
if 'all' in nodes:
|
if 'all' in nodes:
|
||||||
auto_save_snapshot()
|
auto_save_snapshot()
|
||||||
@ -607,7 +646,7 @@ def disable(
|
|||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
|
|
||||||
if 'all' in nodes:
|
if 'all' in nodes:
|
||||||
auto_save_snapshot()
|
auto_save_snapshot()
|
||||||
@ -633,7 +672,7 @@ def enable(
|
|||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
|
|
||||||
if 'all' in nodes:
|
if 'all' in nodes:
|
||||||
auto_save_snapshot()
|
auto_save_snapshot()
|
||||||
@ -659,7 +698,7 @@ def fix(
|
|||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
|
|
||||||
if 'all' in nodes:
|
if 'all' in nodes:
|
||||||
auto_save_snapshot()
|
auto_save_snapshot()
|
||||||
@ -667,10 +706,20 @@ def fix(
|
|||||||
for_each_nodes(nodes, fix_node, allow_all=True)
|
for_each_nodes(nodes, fix_node, allow_all=True)
|
||||||
|
|
||||||
|
|
||||||
@app.command("show", help="Show node list (simple mode)")
|
@app.command("show-versions", help="Show all available versions of the node")
|
||||||
|
def show_versions(node_name: str):
|
||||||
|
versions = cnr_utils.all_versions_of_node(node_name)
|
||||||
|
if versions is None:
|
||||||
|
print(f"Node not found in Comfy Registry: {node_name}")
|
||||||
|
|
||||||
|
for x in versions:
|
||||||
|
print(f"[{x['createdAt'][:10]}] {x['version']} -- {x['changelog']}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.command("show", help="Show node list")
|
||||||
def show(
|
def show(
|
||||||
arg: str = typer.Argument(
|
arg: str = typer.Argument(
|
||||||
help="[installed|enabled|not-installed|disabled|all|snapshot|snapshot-list]"
|
help="[installed|enabled|not-installed|disabled|all|cnr|snapshot|snapshot-list]"
|
||||||
),
|
),
|
||||||
channel: Annotated[
|
channel: Annotated[
|
||||||
str,
|
str,
|
||||||
@ -690,6 +739,7 @@ def show(
|
|||||||
"not-installed",
|
"not-installed",
|
||||||
"disabled",
|
"disabled",
|
||||||
"all",
|
"all",
|
||||||
|
"cnr",
|
||||||
"snapshot",
|
"snapshot",
|
||||||
"snapshot-list",
|
"snapshot-list",
|
||||||
]
|
]
|
||||||
@ -697,7 +747,7 @@ def show(
|
|||||||
typer.echo(f"Invalid command: `show {arg}`", err=True)
|
typer.echo(f"Invalid command: `show {arg}`", err=True)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
if arg == 'snapshot':
|
if arg == 'snapshot':
|
||||||
show_snapshot()
|
show_snapshot()
|
||||||
elif arg == 'snapshot-list':
|
elif arg == 'snapshot-list':
|
||||||
@ -736,7 +786,7 @@ def simple_show(
|
|||||||
typer.echo(f"[bold red]Invalid command: `show {arg}`[/bold red]", err=True)
|
typer.echo(f"[bold red]Invalid command: `show {arg}`[/bold red]", err=True)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
if arg == 'snapshot':
|
if arg == 'snapshot':
|
||||||
show_snapshot(True)
|
show_snapshot(True)
|
||||||
elif arg == 'snapshot-list':
|
elif arg == 'snapshot-list':
|
||||||
@ -786,7 +836,7 @@ def deps_in_workflow(
|
|||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
|
|
||||||
input_path = workflow
|
input_path = workflow
|
||||||
output_path = output
|
output_path = output
|
||||||
@ -795,7 +845,7 @@ def deps_in_workflow(
|
|||||||
print(f"[bold red]File not found: {input_path}[/bold red]")
|
print(f"[bold red]File not found: {input_path}[/bold red]")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
used_exts, unknown_nodes = asyncio.run(core.extract_nodes_from_workflow(input_path, mode=cm_ctx.mode, channel_url=cm_ctx.channel))
|
used_exts, unknown_nodes = asyncio.run(core.extract_nodes_from_workflow(input_path, mode=channel_ctx.mode, channel_url=channel_ctx.channel))
|
||||||
|
|
||||||
custom_nodes = {}
|
custom_nodes = {}
|
||||||
for x in used_exts:
|
for x in used_exts:
|
||||||
@ -870,53 +920,7 @@ def restore_snapshot(
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cloned_repos = []
|
asyncio.run(core.restore_snapshot(snapshot_path, extras))
|
||||||
checkout_repos = []
|
|
||||||
skipped_repos = []
|
|
||||||
enabled_repos = []
|
|
||||||
disabled_repos = []
|
|
||||||
is_failed = False
|
|
||||||
|
|
||||||
def extract_infos(msg):
|
|
||||||
nonlocal is_failed
|
|
||||||
|
|
||||||
for x in msg:
|
|
||||||
if x.startswith("CLONE: "):
|
|
||||||
cloned_repos.append(x[7:])
|
|
||||||
elif x.startswith("CHECKOUT: "):
|
|
||||||
checkout_repos.append(x[10:])
|
|
||||||
elif x.startswith("SKIPPED: "):
|
|
||||||
skipped_repos.append(x[9:])
|
|
||||||
elif x.startswith("ENABLE: "):
|
|
||||||
enabled_repos.append(x[8:])
|
|
||||||
elif x.startswith("DISABLE: "):
|
|
||||||
disabled_repos.append(x[9:])
|
|
||||||
elif 'APPLY SNAPSHOT: False' in x:
|
|
||||||
is_failed = True
|
|
||||||
|
|
||||||
print(f"Restore snapshot.")
|
|
||||||
cmd_str = [sys.executable, git_script_path, '--apply-snapshot', snapshot_path] + extras
|
|
||||||
output = subprocess.check_output(cmd_str, cwd=custom_nodes_path, text=True)
|
|
||||||
msg_lines = output.split('\n')
|
|
||||||
extract_infos(msg_lines)
|
|
||||||
|
|
||||||
for url in cloned_repos:
|
|
||||||
cm_ctx.post_install(url)
|
|
||||||
|
|
||||||
# print summary
|
|
||||||
for x in cloned_repos:
|
|
||||||
print(f"[ INSTALLED ] {x}")
|
|
||||||
for x in checkout_repos:
|
|
||||||
print(f"[ CHECKOUT ] {x}")
|
|
||||||
for x in enabled_repos:
|
|
||||||
print(f"[ ENABLED ] {x}")
|
|
||||||
for x in disabled_repos:
|
|
||||||
print(f"[ DISABLED ] {x}")
|
|
||||||
|
|
||||||
if is_failed:
|
|
||||||
print(output)
|
|
||||||
print("[bold red]ERROR: Failed to restore snapshot.[/bold red]")
|
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
print("[bold red]ERROR: Failed to restore snapshot.[/bold red]")
|
print("[bold red]ERROR: Failed to restore snapshot.[/bold red]")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -935,7 +939,7 @@ def restore_dependencies():
|
|||||||
for x in node_paths:
|
for x in node_paths:
|
||||||
print(f"----------------------------------------------------------------------------------------------------")
|
print(f"----------------------------------------------------------------------------------------------------")
|
||||||
print(f"Restoring [{i}/{total}]: {x}")
|
print(f"Restoring [{i}/{total}]: {x}")
|
||||||
cm_ctx.post_install(x)
|
unified_manager.execute_install_script('', x, instant_execution=True)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
@ -947,7 +951,7 @@ def post_install(
|
|||||||
help="path to custom node",
|
help="path to custom node",
|
||||||
)):
|
)):
|
||||||
path = os.path.expanduser(path)
|
path = os.path.expanduser(path)
|
||||||
cm_ctx.post_install(path)
|
unified_manager.execute_install_script('', path, instant_execution=True)
|
||||||
|
|
||||||
|
|
||||||
@app.command(
|
@app.command(
|
||||||
@ -970,7 +974,7 @@ def install_deps(
|
|||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
auto_save_snapshot()
|
auto_save_snapshot()
|
||||||
|
|
||||||
if not os.path.exists(deps):
|
if not os.path.exists(deps):
|
||||||
@ -989,7 +993,7 @@ def install_deps(
|
|||||||
if state == 'installed':
|
if state == 'installed':
|
||||||
continue
|
continue
|
||||||
elif state == 'not-installed':
|
elif state == 'not-installed':
|
||||||
core.gitclone_install([k], instant_execution=True)
|
asyncio.run(core.gitclone_install(k, instant_execution=True))
|
||||||
else: # disabled
|
else: # disabled
|
||||||
core.gitclone_set_active([k], False)
|
core.gitclone_set_active([k], False)
|
||||||
|
|
||||||
@ -1015,15 +1019,35 @@ def export_custom_node_ids(
|
|||||||
None,
|
None,
|
||||||
help="[remote|local|cache]"
|
help="[remote|local|cache]"
|
||||||
)):
|
)):
|
||||||
cm_ctx.set_channel_mode(channel, mode)
|
channel_ctx.set_channel_mode(channel, mode)
|
||||||
|
|
||||||
with open(path, "w", encoding='utf-8') as output_file:
|
with open(path, "w", encoding='utf-8') as output_file:
|
||||||
for x in cm_ctx.get_custom_node_map().keys():
|
for x in unified_manager.cnr_map.keys():
|
||||||
print(x, file=output_file)
|
print(x, file=output_file)
|
||||||
|
|
||||||
|
custom_nodes = asyncio.run(unified_manager.get_custom_nodes(channel=channel_ctx.channel, mode=channel_ctx.mode))
|
||||||
|
for x in custom_nodes.values():
|
||||||
|
if 'cnr_latest' not in x:
|
||||||
|
if len(x['files']) == 1:
|
||||||
|
repo_url = x['files'][0]
|
||||||
|
node_id = repo_url.split('/')[-1]
|
||||||
|
print(f"{node_id}@unknown", file=output_file)
|
||||||
|
|
||||||
|
if 'id' in x:
|
||||||
|
print(f"{x['id']}@unknown", file=output_file)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(
|
||||||
|
"migrate",
|
||||||
|
help="Migrate legacy node system to new node system",
|
||||||
|
)
|
||||||
|
def migrate():
|
||||||
|
asyncio.run(unified_manager.migrate_unmanaged_nodes())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
sys.exit(app())
|
sys.exit(app())
|
||||||
|
|
||||||
|
|
||||||
print(f"")
|
print(f"")
|
||||||
|
|||||||
@ -5,13 +5,20 @@ import traceback
|
|||||||
|
|
||||||
import git
|
import git
|
||||||
import configparser
|
import configparser
|
||||||
import re
|
|
||||||
import json
|
import json
|
||||||
import yaml
|
import yaml
|
||||||
from torchvision.datasets.utils import download_url
|
from torchvision.datasets.utils import download_url
|
||||||
from tqdm.auto import tqdm
|
from tqdm.auto import tqdm
|
||||||
from git.remote import RemoteProgress
|
from git.remote import RemoteProgress
|
||||||
|
|
||||||
|
|
||||||
|
comfy_path = os.environ.get('COMFYUI_PATH')
|
||||||
|
|
||||||
|
if comfy_path is None:
|
||||||
|
print(f"\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(os.path.dirname(__file__), '..', '..'))
|
||||||
|
|
||||||
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
||||||
nodelist_path = os.path.join(os.path.dirname(__file__), "custom-node-list.json")
|
nodelist_path = os.path.join(os.path.dirname(__file__), "custom-node-list.json")
|
||||||
working_directory = os.getcwd()
|
working_directory = os.getcwd()
|
||||||
@ -35,9 +42,11 @@ class GitProgress(RemoteProgress):
|
|||||||
self.pbar.refresh()
|
self.pbar.refresh()
|
||||||
|
|
||||||
|
|
||||||
def gitclone(custom_nodes_path, url, target_hash=None):
|
def gitclone(custom_nodes_path, url, target_hash=None, repo_path=None):
|
||||||
repo_name = os.path.splitext(os.path.basename(url))[0]
|
repo_name = os.path.splitext(os.path.basename(url))[0]
|
||||||
repo_path = os.path.join(custom_nodes_path, repo_name)
|
|
||||||
|
if repo_path is None:
|
||||||
|
repo_path = os.path.join(custom_nodes_path, repo_name)
|
||||||
|
|
||||||
# Clone the repository from the remote URL
|
# Clone the repository from the remote URL
|
||||||
repo = git.Repo.clone_from(url, repo_path, recursive=True, progress=GitProgress())
|
repo = git.Repo.clone_from(url, repo_path, recursive=True, progress=GitProgress())
|
||||||
@ -70,7 +79,12 @@ def gitcheck(path, do_fetch=False):
|
|||||||
|
|
||||||
# Get the current commit hash and the commit hash of the remote branch
|
# Get the current commit hash and the commit hash of the remote branch
|
||||||
commit_hash = repo.head.commit.hexsha
|
commit_hash = repo.head.commit.hexsha
|
||||||
remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
|
|
||||||
|
if f'{remote_name}/{branch_name}' in repo.refs:
|
||||||
|
remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
|
||||||
|
else:
|
||||||
|
print("CUSTOM NODE CHECK: True") # non default branch is treated as updatable
|
||||||
|
return
|
||||||
|
|
||||||
# Compare the commit hashes to determine if the local repository is behind the remote repository
|
# Compare the commit hashes to determine if the local repository is behind the remote repository
|
||||||
if commit_hash != remote_commit_hash:
|
if commit_hash != remote_commit_hash:
|
||||||
@ -89,11 +103,8 @@ def gitcheck(path, do_fetch=False):
|
|||||||
|
|
||||||
|
|
||||||
def switch_to_default_branch(repo):
|
def switch_to_default_branch(repo):
|
||||||
show_result = repo.git.remote("show", "origin")
|
default_branch = repo.git.symbolic_ref('refs/remotes/origin/HEAD').replace('refs/remotes/origin/', '')
|
||||||
matches = re.search(r"\s*HEAD branch:\s*(.*)", show_result)
|
repo.git.checkout(default_branch)
|
||||||
if matches:
|
|
||||||
default_branch = matches.group(1)
|
|
||||||
repo.git.checkout(default_branch)
|
|
||||||
|
|
||||||
|
|
||||||
def gitpull(path):
|
def gitpull(path):
|
||||||
@ -117,6 +128,11 @@ def gitpull(path):
|
|||||||
remote_name = current_branch.tracking_branch().remote_name
|
remote_name = current_branch.tracking_branch().remote_name
|
||||||
remote = repo.remote(name=remote_name)
|
remote = repo.remote(name=remote_name)
|
||||||
|
|
||||||
|
if f'{remote_name}/{branch_name}' not in repo.refs:
|
||||||
|
switch_to_default_branch(repo)
|
||||||
|
current_branch = repo.active_branch
|
||||||
|
branch_name = current_branch.name
|
||||||
|
|
||||||
remote.fetch()
|
remote.fetch()
|
||||||
remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
|
remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
|
||||||
|
|
||||||
@ -142,9 +158,7 @@ def gitpull(path):
|
|||||||
|
|
||||||
|
|
||||||
def checkout_comfyui_hash(target_hash):
|
def checkout_comfyui_hash(target_hash):
|
||||||
repo_path = os.path.abspath(os.path.join(working_directory, '..')) # ComfyUI dir
|
repo = git.Repo(comfy_path)
|
||||||
|
|
||||||
repo = git.Repo(repo_path)
|
|
||||||
commit_hash = repo.head.commit.hexsha
|
commit_hash = repo.head.commit.hexsha
|
||||||
|
|
||||||
if commit_hash != target_hash:
|
if commit_hash != target_hash:
|
||||||
@ -167,7 +181,7 @@ def checkout_custom_node_hash(git_custom_node_infos):
|
|||||||
repo_name_to_url[repo_name] = url
|
repo_name_to_url[repo_name] = url
|
||||||
|
|
||||||
for path in os.listdir(working_directory):
|
for path in os.listdir(working_directory):
|
||||||
if path.endswith("ComfyUI-Manager"):
|
if '@' in path or path.endswith("ComfyUI-Manager"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
fullpath = os.path.join(working_directory, path)
|
fullpath = os.path.join(working_directory, path)
|
||||||
@ -226,6 +240,9 @@ def checkout_custom_node_hash(git_custom_node_infos):
|
|||||||
|
|
||||||
# clone missing
|
# clone missing
|
||||||
for k, v in git_custom_node_infos.items():
|
for k, v in git_custom_node_infos.items():
|
||||||
|
if 'ComfyUI-Manager' in k:
|
||||||
|
continue
|
||||||
|
|
||||||
if not v['disabled']:
|
if not v['disabled']:
|
||||||
repo_name = k.split('/')[-1]
|
repo_name = k.split('/')[-1]
|
||||||
if repo_name.endswith('.git'):
|
if repo_name.endswith('.git'):
|
||||||
@ -234,7 +251,7 @@ def checkout_custom_node_hash(git_custom_node_infos):
|
|||||||
path = os.path.join(working_directory, repo_name)
|
path = os.path.join(working_directory, repo_name)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
print(f"CLONE: {path}")
|
print(f"CLONE: {path}")
|
||||||
gitclone(working_directory, k, v['hash'])
|
gitclone(working_directory, k, target_hash=v['hash'])
|
||||||
|
|
||||||
|
|
||||||
def invalidate_custom_node_file(file_custom_node_infos):
|
def invalidate_custom_node_file(file_custom_node_infos):
|
||||||
@ -286,6 +303,7 @@ def invalidate_custom_node_file(file_custom_node_infos):
|
|||||||
|
|
||||||
def apply_snapshot(target):
|
def apply_snapshot(target):
|
||||||
try:
|
try:
|
||||||
|
# todo: should be if target is not in snapshots dir
|
||||||
path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{target}")
|
path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{target}")
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
if not target.endswith('.json') and not target.endswith('.yaml'):
|
if not target.endswith('.json') and not target.endswith('.yaml'):
|
||||||
@ -401,7 +419,11 @@ setup_environment()
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if sys.argv[1] == "--clone":
|
if sys.argv[1] == "--clone":
|
||||||
gitclone(sys.argv[2], sys.argv[3])
|
repo_path = None
|
||||||
|
if len(sys.argv) > 4:
|
||||||
|
repo_path = sys.argv[4]
|
||||||
|
|
||||||
|
gitclone(sys.argv[2], sys.argv[3], repo_path=repo_path)
|
||||||
elif sys.argv[1] == "--check":
|
elif sys.argv[1] == "--check":
|
||||||
gitcheck(sys.argv[2], False)
|
gitcheck(sys.argv[2], False)
|
||||||
elif sys.argv[1] == "--fetch":
|
elif sys.argv[1] == "--fetch":
|
||||||
|
|||||||
112
glob/cnr_utils.py
Normal file
112
glob/cnr_utils.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
from manager_util import *
|
||||||
|
import zipfile
|
||||||
|
import requests
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
base_url = "https://api.comfy.org"
|
||||||
|
|
||||||
|
|
||||||
|
async def get_cnr_data(page=1, limit=1000, cache_mode=True):
|
||||||
|
try:
|
||||||
|
uri = f'{base_url}/nodes?page={page}&limit={limit}'
|
||||||
|
json_obj = await get_data_with_cache(uri, cache_mode=cache_mode)
|
||||||
|
|
||||||
|
for v in json_obj['nodes']:
|
||||||
|
if 'latest_version' not in v:
|
||||||
|
v['latest_version'] = dict(version='nightly')
|
||||||
|
|
||||||
|
return json_obj['nodes']
|
||||||
|
except:
|
||||||
|
res = {}
|
||||||
|
print(f"Cannot connect to comfyregistry.")
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NodeVersion:
|
||||||
|
changelog: str
|
||||||
|
dependencies: List[str]
|
||||||
|
deprecated: bool
|
||||||
|
id: str
|
||||||
|
version: str
|
||||||
|
download_url: str
|
||||||
|
|
||||||
|
|
||||||
|
def map_node_version(api_node_version):
|
||||||
|
"""
|
||||||
|
Maps node version data from API response to NodeVersion dataclass.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_data (dict): The 'node_version' part of the API response.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
NodeVersion: An instance of NodeVersion dataclass populated with data from the API.
|
||||||
|
"""
|
||||||
|
return NodeVersion(
|
||||||
|
changelog=api_node_version.get(
|
||||||
|
"changelog", ""
|
||||||
|
), # Provide a default value if 'changelog' is missing
|
||||||
|
dependencies=api_node_version.get(
|
||||||
|
"dependencies", []
|
||||||
|
), # Provide a default empty list if 'dependencies' is missing
|
||||||
|
deprecated=api_node_version.get(
|
||||||
|
"deprecated", False
|
||||||
|
), # Assume False if 'deprecated' is not specified
|
||||||
|
id=api_node_version[
|
||||||
|
"id"
|
||||||
|
], # 'id' should be mandatory; raise KeyError if missing
|
||||||
|
version=api_node_version[
|
||||||
|
"version"
|
||||||
|
], # 'version' should be mandatory; raise KeyError if missing
|
||||||
|
download_url=api_node_version.get(
|
||||||
|
"downloadUrl", ""
|
||||||
|
), # Provide a default value if 'downloadUrl' is missing
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def install_node(node_id, version=None):
|
||||||
|
"""
|
||||||
|
Retrieves the node version for installation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node_id (str): The unique identifier of the node.
|
||||||
|
version (str, optional): Specific version of the node to retrieve. If omitted, the latest version is returned.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
NodeVersion: Node version data or error message.
|
||||||
|
"""
|
||||||
|
if version is None:
|
||||||
|
url = f"{base_url}/nodes/{node_id}/install"
|
||||||
|
else:
|
||||||
|
url = f"{base_url}/nodes/{node_id}/install?version={version}"
|
||||||
|
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
# Convert the API response to a NodeVersion object
|
||||||
|
return map_node_version(response.json())
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def all_versions_of_node(node_id):
|
||||||
|
url = f"https://api.comfy.org/nodes/{node_id}/versions"
|
||||||
|
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_package_as_zip(file_path, extract_path):
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(file_path, "r") as zip_ref:
|
||||||
|
zip_ref.extractall(extract_path)
|
||||||
|
extracted_files = zip_ref.namelist()
|
||||||
|
print(f"Extracted zip file to {extract_path}")
|
||||||
|
return extracted_files
|
||||||
|
except zipfile.BadZipFile:
|
||||||
|
print(f"File '{file_path}' is not a zip or is corrupted.")
|
||||||
|
return None
|
||||||
1690
glob/manager_core.py
1690
glob/manager_core.py
File diff suppressed because it is too large
Load Diff
@ -16,12 +16,15 @@ import git
|
|||||||
|
|
||||||
from server import PromptServer
|
from server import PromptServer
|
||||||
import manager_core as core
|
import manager_core as core
|
||||||
|
import manager_util
|
||||||
import cm_global
|
import cm_global
|
||||||
|
|
||||||
print(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
print(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
||||||
|
|
||||||
comfy_ui_hash = "-"
|
comfy_ui_hash = "-"
|
||||||
|
|
||||||
|
routes = PromptServer.instance.routes
|
||||||
|
|
||||||
|
|
||||||
def handle_stream(stream, prefix):
|
def handle_stream(stream, prefix):
|
||||||
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
||||||
@ -59,7 +62,7 @@ def is_allowed_security_level(level):
|
|||||||
|
|
||||||
async def get_risky_level(files):
|
async def get_risky_level(files):
|
||||||
json_data1 = await core.get_data_by_mode('local', 'custom-node-list.json')
|
json_data1 = await core.get_data_by_mode('local', 'custom-node-list.json')
|
||||||
json_data2 = await core.get_data_by_mode('cache', 'custom-node-list.json', channel_url='https://github.com/ltdrdata/ComfyUI-Manager/raw/main/custom-node-list.json')
|
json_data2 = await core.get_data_by_mode('cache', 'custom-node-list.json', channel_url='https://github.com/ltdrdata/ComfyUI-Manager/raw/main')
|
||||||
|
|
||||||
all_urls = set()
|
all_urls = set()
|
||||||
for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:
|
for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:
|
||||||
@ -201,19 +204,6 @@ def print_comfyui_version():
|
|||||||
print_comfyui_version()
|
print_comfyui_version()
|
||||||
|
|
||||||
|
|
||||||
async def populate_github_stats(json_obj, json_obj_github):
|
|
||||||
if 'custom_nodes' in json_obj:
|
|
||||||
for i, node in enumerate(json_obj['custom_nodes']):
|
|
||||||
url = node['reference']
|
|
||||||
if url in json_obj_github:
|
|
||||||
json_obj['custom_nodes'][i]['stars'] = json_obj_github[url]['stars']
|
|
||||||
json_obj['custom_nodes'][i]['last_update'] = json_obj_github[url]['last_update']
|
|
||||||
json_obj['custom_nodes'][i]['trust'] = json_obj_github[url]['author_account_age_days'] > 180
|
|
||||||
else:
|
|
||||||
json_obj['custom_nodes'][i]['stars'] = -1
|
|
||||||
json_obj['custom_nodes'][i]['last_update'] = -1
|
|
||||||
json_obj['custom_nodes'][i]['trust'] = False
|
|
||||||
return json_obj
|
|
||||||
|
|
||||||
|
|
||||||
def setup_environment():
|
def setup_environment():
|
||||||
@ -280,7 +270,7 @@ def get_model_path(data):
|
|||||||
return os.path.join(base_model, data['filename'])
|
return os.path.join(base_model, data['filename'])
|
||||||
|
|
||||||
|
|
||||||
def check_custom_nodes_installed(json_obj, do_fetch=False, do_update_check=True, do_update=False):
|
def check_state_of_git_node_pack(node_packs, do_fetch=False, do_update_check=True, do_update=False):
|
||||||
if do_fetch:
|
if do_fetch:
|
||||||
print("Start fetching...", end="")
|
print("Start fetching...", end="")
|
||||||
elif do_update:
|
elif do_update:
|
||||||
@ -289,16 +279,17 @@ def check_custom_nodes_installed(json_obj, do_fetch=False, do_update_check=True,
|
|||||||
print("Start update check...", end="")
|
print("Start update check...", end="")
|
||||||
|
|
||||||
def process_custom_node(item):
|
def process_custom_node(item):
|
||||||
core.check_a_custom_node_installed(item, do_fetch, do_update_check, do_update)
|
core.check_state_of_git_node_pack_single(item, do_fetch, do_update_check, do_update)
|
||||||
|
|
||||||
with concurrent.futures.ThreadPoolExecutor(4) as executor:
|
with concurrent.futures.ThreadPoolExecutor(4) as executor:
|
||||||
for item in json_obj['custom_nodes']:
|
for k, v in node_packs.items():
|
||||||
executor.submit(process_custom_node, item)
|
if v.get('active_version') in ['unknown', 'nightly']:
|
||||||
|
executor.submit(process_custom_node, v)
|
||||||
|
|
||||||
if do_fetch:
|
if do_fetch:
|
||||||
print(f"\x1b[2K\rFetching done.")
|
print(f"\x1b[2K\rFetching done.")
|
||||||
elif do_update:
|
elif do_update:
|
||||||
update_exists = any(item['installed'] == 'Update' for item in json_obj['custom_nodes'])
|
update_exists = any(item.get('updatable', False) for item in node_packs.values())
|
||||||
if update_exists:
|
if update_exists:
|
||||||
print(f"\x1b[2K\rUpdate done.")
|
print(f"\x1b[2K\rUpdate done.")
|
||||||
else:
|
else:
|
||||||
@ -335,8 +326,11 @@ def nickname_filter(json_obj):
|
|||||||
return json_obj
|
return json_obj
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/customnode/getmappings")
|
@routes.get("/customnode/getmappings")
|
||||||
async def fetch_customnode_mappings(request):
|
async def fetch_customnode_mappings(request):
|
||||||
|
"""
|
||||||
|
provide unified (node -> node pack) mapping list
|
||||||
|
"""
|
||||||
mode = request.rel_url.query["mode"]
|
mode = request.rel_url.query["mode"]
|
||||||
|
|
||||||
nickname_mode = False
|
nickname_mode = False
|
||||||
@ -345,6 +339,7 @@ async def fetch_customnode_mappings(request):
|
|||||||
nickname_mode = True
|
nickname_mode = True
|
||||||
|
|
||||||
json_obj = await core.get_data_by_mode(mode, 'extension-node-map.json')
|
json_obj = await core.get_data_by_mode(mode, 'extension-node-map.json')
|
||||||
|
json_obj = core.map_to_unified_keys(json_obj)
|
||||||
|
|
||||||
if nickname_mode:
|
if nickname_mode:
|
||||||
json_obj = nickname_filter(json_obj)
|
json_obj = nickname_filter(json_obj)
|
||||||
@ -367,25 +362,34 @@ async def fetch_customnode_mappings(request):
|
|||||||
return web.json_response(json_obj, content_type='application/json')
|
return web.json_response(json_obj, content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/customnode/fetch_updates")
|
@routes.get("/customnode/fetch_updates")
|
||||||
async def fetch_updates(request):
|
async def fetch_updates(request):
|
||||||
try:
|
try:
|
||||||
json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
|
if request.rel_url.query["mode"] == "local":
|
||||||
|
channel = 'local'
|
||||||
|
else:
|
||||||
|
channel = core.get_config()['channel_url']
|
||||||
|
|
||||||
check_custom_nodes_installed(json_obj, True)
|
await core.unified_manager.reload(request.rel_url.query["mode"])
|
||||||
|
await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])
|
||||||
|
|
||||||
update_exists = any('custom_nodes' in json_obj and 'installed' in node and node['installed'] == 'Update' for node in
|
res = core.unified_manager.fetch_or_pull_git_repo(is_pull=False)
|
||||||
json_obj['custom_nodes'])
|
|
||||||
|
|
||||||
if update_exists:
|
for x in res['failed']:
|
||||||
|
print(f"FETCH FAILED: {x}")
|
||||||
|
|
||||||
|
print("\nDone.")
|
||||||
|
|
||||||
|
if len(res['updated']) > 0:
|
||||||
return web.Response(status=201)
|
return web.Response(status=201)
|
||||||
|
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
except:
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/customnode/update_all")
|
@routes.get("/customnode/update_all")
|
||||||
async def update_all(request):
|
async def update_all(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
||||||
@ -394,22 +398,37 @@ async def update_all(request):
|
|||||||
try:
|
try:
|
||||||
core.save_snapshot_with_postfix('autosave')
|
core.save_snapshot_with_postfix('autosave')
|
||||||
|
|
||||||
json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
|
if request.rel_url.query["mode"] == "local":
|
||||||
|
channel = 'local'
|
||||||
|
else:
|
||||||
|
channel = core.get_config()['channel_url']
|
||||||
|
|
||||||
check_custom_nodes_installed(json_obj, do_update=True)
|
await core.unified_manager.reload(request.rel_url.query["mode"])
|
||||||
|
await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])
|
||||||
|
|
||||||
updated = [item['title'] for item in json_obj['custom_nodes'] if item['installed'] == 'Update']
|
updated_cnr = []
|
||||||
failed = [item['title'] for item in json_obj['custom_nodes'] if item['installed'] == 'Fail']
|
for k, v in core.unified_manager.active_nodes.items():
|
||||||
|
if v[0] != 'nightly':
|
||||||
|
res = core.unified_manager.unified_update(k, v[0])
|
||||||
|
if res.action == 'switch-cnr' and res:
|
||||||
|
updated_cnr.append(k)
|
||||||
|
|
||||||
res = {'updated': updated, 'failed': failed}
|
res = core.unified_manager.fetch_or_pull_git_repo(is_pull=True)
|
||||||
|
|
||||||
if len(updated) == 0 and len(failed) == 0:
|
res['updated'] += updated_cnr
|
||||||
|
|
||||||
|
for x in res['failed']:
|
||||||
|
print(f"PULL FAILED: {x}")
|
||||||
|
|
||||||
|
if len(res['updated']) == 0 and len(res['failed']) == 0:
|
||||||
status = 200
|
status = 200
|
||||||
else:
|
else:
|
||||||
status = 201
|
status = 201
|
||||||
|
|
||||||
|
print(f"\nDone.")
|
||||||
return web.json_response(res, status=status, content_type='application/json')
|
return web.json_response(res, status=status, content_type='application/json')
|
||||||
except:
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
finally:
|
finally:
|
||||||
core.clear_pip_cache()
|
core.clear_pip_cache()
|
||||||
@ -450,17 +469,20 @@ def convert_markdown_to_html(input_text):
|
|||||||
|
|
||||||
def populate_markdown(x):
|
def populate_markdown(x):
|
||||||
if 'description' in x:
|
if 'description' in x:
|
||||||
x['description'] = convert_markdown_to_html(x['description'])
|
x['description'] = convert_markdown_to_html(manager_util.sanitize_tag(x['description']))
|
||||||
|
|
||||||
if 'name' in x:
|
if 'name' in x:
|
||||||
x['name'] = x['name'].replace('<', '<').replace('>', '>')
|
x['name'] = manager_util.sanitize_tag(x['name'])
|
||||||
|
|
||||||
if 'title' in x:
|
if 'title' in x:
|
||||||
x['title'] = x['title'].replace('<', '<').replace('>', '>')
|
x['title'] = manager_util.sanitize_tag(x['title'])
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/customnode/getlist")
|
@routes.get("/customnode/getlist")
|
||||||
async def fetch_customnode_list(request):
|
async def fetch_customnode_list(request):
|
||||||
|
"""
|
||||||
|
provide unified custom node list
|
||||||
|
"""
|
||||||
if "skip_update" in request.rel_url.query and request.rel_url.query["skip_update"] == "true":
|
if "skip_update" in request.rel_url.query and request.rel_url.query["skip_update"] == "true":
|
||||||
skip_update = True
|
skip_update = True
|
||||||
else:
|
else:
|
||||||
@ -471,26 +493,14 @@ async def fetch_customnode_list(request):
|
|||||||
else:
|
else:
|
||||||
channel = core.get_config()['channel_url']
|
channel = core.get_config()['channel_url']
|
||||||
|
|
||||||
json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
|
node_packs = await core.get_unified_total_nodes(channel, request.rel_url.query["mode"])
|
||||||
json_obj_github = await core.get_data_by_mode(request.rel_url.query["mode"], 'github-stats.json', 'default')
|
json_obj_github = await core.get_data_by_mode(request.rel_url.query["mode"], 'github-stats.json', 'default')
|
||||||
json_obj = await populate_github_stats(json_obj, json_obj_github)
|
core.populate_github_stats(node_packs, json_obj_github)
|
||||||
|
|
||||||
def is_ignored_notice(code):
|
check_state_of_git_node_pack(node_packs, False, do_update_check=not skip_update)
|
||||||
if code is not None and code.startswith('#NOTICE_'):
|
|
||||||
try:
|
|
||||||
notice_version = [int(x) for x in code[8:].split('.')]
|
|
||||||
return notice_version[0] < core.version[0] or (notice_version[0] == core.version[0] and notice_version[1] <= core.version[1])
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
json_obj['custom_nodes'] = [record for record in json_obj['custom_nodes'] if not is_ignored_notice(record.get('author'))]
|
for v in node_packs.values():
|
||||||
|
populate_markdown(v)
|
||||||
check_custom_nodes_installed(json_obj, False, not skip_update)
|
|
||||||
|
|
||||||
for x in json_obj['custom_nodes']:
|
|
||||||
populate_markdown(x)
|
|
||||||
|
|
||||||
if channel != 'local':
|
if channel != 'local':
|
||||||
found = 'custom'
|
found = 'custom'
|
||||||
@ -502,48 +512,24 @@ async def fetch_customnode_list(request):
|
|||||||
|
|
||||||
channel = found
|
channel = found
|
||||||
|
|
||||||
json_obj['channel'] = channel
|
result = dict(channel=channel, node_packs=node_packs)
|
||||||
|
|
||||||
return web.json_response(json_obj, content_type='application/json')
|
return web.json_response(result, content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/customnode/alternatives")
|
@routes.get("/customnode/alternatives")
|
||||||
async def fetch_customnode_alternatives(request):
|
async def fetch_customnode_alternatives(request):
|
||||||
alter_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json')
|
alter_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json')
|
||||||
|
|
||||||
|
res = {}
|
||||||
|
|
||||||
for item in alter_json['items']:
|
for item in alter_json['items']:
|
||||||
populate_markdown(item)
|
populate_markdown(item)
|
||||||
|
res[item['id']] = item
|
||||||
return web.json_response(alter_json, content_type='application/json')
|
|
||||||
|
|
||||||
|
res = core.map_to_unified_keys(res)
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/alternatives/getlist")
|
return web.json_response(res, content_type='application/json')
|
||||||
async def fetch_alternatives_list(request):
|
|
||||||
if "skip_update" in request.rel_url.query and request.rel_url.query["skip_update"] == "true":
|
|
||||||
skip_update = True
|
|
||||||
else:
|
|
||||||
skip_update = False
|
|
||||||
|
|
||||||
alter_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json')
|
|
||||||
custom_node_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
|
|
||||||
|
|
||||||
fileurl_to_custom_node = {}
|
|
||||||
|
|
||||||
for item in custom_node_json['custom_nodes']:
|
|
||||||
for fileurl in item['files']:
|
|
||||||
fileurl_to_custom_node[fileurl] = item
|
|
||||||
|
|
||||||
for item in alter_json['items']:
|
|
||||||
fileurl = item['id']
|
|
||||||
if fileurl in fileurl_to_custom_node:
|
|
||||||
custom_node = fileurl_to_custom_node[fileurl]
|
|
||||||
core.check_a_custom_node_installed(custom_node, not skip_update)
|
|
||||||
|
|
||||||
populate_markdown(item)
|
|
||||||
populate_markdown(custom_node)
|
|
||||||
item['custom_node'] = custom_node
|
|
||||||
|
|
||||||
return web.json_response(alter_json, content_type='application/json')
|
|
||||||
|
|
||||||
|
|
||||||
def check_model_installed(json_obj):
|
def check_model_installed(json_obj):
|
||||||
@ -567,7 +553,7 @@ def check_model_installed(json_obj):
|
|||||||
executor.submit(process_model, item)
|
executor.submit(process_model, item)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/externalmodel/getlist")
|
@routes.get("/externalmodel/getlist")
|
||||||
async def fetch_externalmodel_list(request):
|
async def fetch_externalmodel_list(request):
|
||||||
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')
|
||||||
|
|
||||||
@ -587,7 +573,7 @@ async def get_snapshot_list(request):
|
|||||||
return web.json_response({'items': items}, content_type='application/json')
|
return web.json_response({'items': items}, content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/snapshot/remove")
|
@routes.get("/snapshot/remove")
|
||||||
async def remove_snapshot(request):
|
async def remove_snapshot(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
||||||
@ -605,7 +591,7 @@ async def remove_snapshot(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/snapshot/restore")
|
@routes.get("/snapshot/restore")
|
||||||
async def remove_snapshot(request):
|
async def remove_snapshot(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
||||||
@ -631,7 +617,7 @@ async def remove_snapshot(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/snapshot/get_current")
|
@routes.get("/snapshot/get_current")
|
||||||
async def get_current_snapshot_api(request):
|
async def get_current_snapshot_api(request):
|
||||||
try:
|
try:
|
||||||
return web.json_response(core.get_current_snapshot(), content_type='application/json')
|
return web.json_response(core.get_current_snapshot(), content_type='application/json')
|
||||||
@ -639,7 +625,7 @@ async def get_current_snapshot_api(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/snapshot/save")
|
@routes.get("/snapshot/save")
|
||||||
async def save_snapshot(request):
|
async def save_snapshot(request):
|
||||||
try:
|
try:
|
||||||
core.save_snapshot_with_postfix('snapshot')
|
core.save_snapshot_with_postfix('snapshot')
|
||||||
@ -774,7 +760,34 @@ def copy_set_active(files, is_disable, js_path_name='.'):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/customnode/install")
|
@routes.get("/customnode/versions/{node_name}")
|
||||||
|
async def get_cnr_versions(request):
|
||||||
|
node_name = request.match_info.get("node_name", None)
|
||||||
|
versions = core.cnr_utils.all_versions_of_node(node_name)
|
||||||
|
|
||||||
|
if versions:
|
||||||
|
return web.json_response(versions, content_type='application/json')
|
||||||
|
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/customnode/disabled_versions/{node_name}")
|
||||||
|
async def get_disabled_versions(request):
|
||||||
|
node_name = request.match_info.get("node_name", None)
|
||||||
|
versions = []
|
||||||
|
if node_name in core.unified_manager.nightly_inactive_nodes:
|
||||||
|
versions.append(dict(version='nightly'))
|
||||||
|
|
||||||
|
for v in core.unified_manager.cnr_inactive_nodes.get(node_name, {}).keys():
|
||||||
|
versions.append(dict(version=v))
|
||||||
|
|
||||||
|
if versions:
|
||||||
|
return web.json_response(versions, content_type='application/json')
|
||||||
|
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/customnode/install")
|
||||||
async def install_custom_node(request):
|
async def install_custom_node(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
||||||
@ -782,46 +795,47 @@ async def install_custom_node(request):
|
|||||||
|
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
|
|
||||||
risky_level = await get_risky_level(json_data['files'])
|
# non-nightly cnr is safe
|
||||||
|
risky_level = None
|
||||||
|
cnr_id = json_data.get('id')
|
||||||
|
skip_post_install = json_data.get('skip_post_install')
|
||||||
|
|
||||||
|
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}"
|
||||||
|
else:
|
||||||
|
node_spec_str = f"{cnr_id}@nightly"
|
||||||
|
else:
|
||||||
|
# unknown
|
||||||
|
unknown_name = os.path.basename(json_data['files'][0])
|
||||||
|
node_spec_str = f"{unknown_name}@unknown"
|
||||||
|
|
||||||
|
# apply security policy if not cnr node (nightly isn't regarded as cnr node)
|
||||||
|
if risky_level is None:
|
||||||
|
risky_level = await get_risky_level(json_data['files'])
|
||||||
|
|
||||||
if not is_allowed_security_level(risky_level):
|
if not is_allowed_security_level(risky_level):
|
||||||
print(f"ERROR: This installation is not allowed in this security_level. Please contact the administrator.")
|
print(f"ERROR: This installation is not allowed in this security_level. Please contact the administrator.")
|
||||||
return web.Response(status=404)
|
return web.Response(status=404)
|
||||||
|
|
||||||
install_type = json_data['install_type']
|
node_spec = core.unified_manager.resolve_node_spec(node_spec_str)
|
||||||
|
|
||||||
print(f"Install custom node '{json_data['title']}'")
|
if node_spec is None:
|
||||||
|
return
|
||||||
|
|
||||||
res = False
|
node_name, version_spec, is_specified = node_spec
|
||||||
|
res = await core.unified_manager.install_by_id(node_name, version_spec, json_data['channel'], json_data['mode'], return_postinstall=skip_post_install)
|
||||||
|
# discard post install if skip_post_install mode
|
||||||
|
|
||||||
if len(json_data['files']) == 0:
|
if res not in ['skip', 'enable', 'install-git', 'install-cnr', 'switch-cnr']:
|
||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
if install_type == "unzip":
|
return web.Response(status=200)
|
||||||
res = unzip_install(json_data['files'])
|
|
||||||
|
|
||||||
if install_type == "copy":
|
|
||||||
js_path_name = json_data['js_path'] if 'js_path' in json_data else '.'
|
|
||||||
res = copy_install(json_data['files'], js_path_name)
|
|
||||||
|
|
||||||
elif install_type == "git-clone":
|
|
||||||
res = core.gitclone_install(json_data['files'])
|
|
||||||
|
|
||||||
if 'pip' in json_data:
|
|
||||||
for pname in json_data['pip']:
|
|
||||||
pkg = core.remap_pip_package(pname)
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", pkg]
|
|
||||||
core.try_install_script(json_data['files'][0], ".", install_cmd)
|
|
||||||
|
|
||||||
core.clear_pip_cache()
|
|
||||||
|
|
||||||
if res:
|
|
||||||
print(f"After restarting ComfyUI, please refresh the browser.")
|
|
||||||
return web.json_response({}, content_type='application/json')
|
|
||||||
|
|
||||||
return web.Response(status=400)
|
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/customnode/fix")
|
@routes.post("/customnode/fix")
|
||||||
async def fix_custom_node(request):
|
async def fix_custom_node(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
||||||
@ -829,49 +843,45 @@ async def fix_custom_node(request):
|
|||||||
|
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
|
|
||||||
install_type = json_data['install_type']
|
node_id = json_data.get('id')
|
||||||
|
node_ver = json_data['version']
|
||||||
print(f"Install custom node '{json_data['title']}'")
|
if node_ver != 'unknown':
|
||||||
|
node_name = node_id
|
||||||
res = False
|
|
||||||
|
|
||||||
if len(json_data['files']) == 0:
|
|
||||||
return web.Response(status=400)
|
|
||||||
|
|
||||||
if install_type == "git-clone":
|
|
||||||
res = core.gitclone_fix(json_data['files'])
|
|
||||||
else:
|
else:
|
||||||
return web.Response(status=400)
|
# unknown
|
||||||
|
node_name = os.path.basename(json_data['files'][0])
|
||||||
|
|
||||||
if 'pip' in json_data:
|
res = core.unified_manager.unified_fix(node_name, node_ver)
|
||||||
for pname in json_data['pip']:
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", '-U', pname]
|
|
||||||
core.try_install_script(json_data['files'][0], ".", install_cmd)
|
|
||||||
|
|
||||||
if res:
|
if res.result:
|
||||||
print(f"After restarting ComfyUI, please refresh the browser.")
|
print(f"After restarting ComfyUI, please refresh the browser.")
|
||||||
return web.json_response({}, content_type='application/json')
|
return web.json_response({}, content_type='application/json')
|
||||||
|
|
||||||
|
print(f"ERROR: An error occurred while fixing '{node_name}@{node_ver}'.")
|
||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/customnode/install/git_url")
|
@routes.post("/customnode/install/git_url")
|
||||||
async def install_custom_node_git_url(request):
|
async def install_custom_node_git_url(request):
|
||||||
if not is_allowed_security_level('high'):
|
if not is_allowed_security_level('high'):
|
||||||
print(f"ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.")
|
print(f"ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.")
|
||||||
return web.Response(status=403)
|
return web.Response(status=403)
|
||||||
|
|
||||||
url = await request.text()
|
url = await request.text()
|
||||||
res = core.gitclone_install([url])
|
res = await core.gitclone_install(url)
|
||||||
|
|
||||||
if res:
|
if res.action == 'skip':
|
||||||
|
print(f"Already installed: '{res.target}'")
|
||||||
|
return web.Response(status=200)
|
||||||
|
elif res.result:
|
||||||
print(f"After restarting ComfyUI, please refresh the browser.")
|
print(f"After restarting ComfyUI, please refresh the browser.")
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
print(res.msg)
|
||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/customnode/install/pip")
|
@routes.post("/customnode/install/pip")
|
||||||
async def install_custom_node_git_url(request):
|
async def install_custom_node_git_url(request):
|
||||||
if not is_allowed_security_level('high'):
|
if not is_allowed_security_level('high'):
|
||||||
print(f"ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.")
|
print(f"ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.")
|
||||||
@ -883,7 +893,7 @@ async def install_custom_node_git_url(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/customnode/uninstall")
|
@routes.post("/customnode/uninstall")
|
||||||
async def uninstall_custom_node(request):
|
async def uninstall_custom_node(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
||||||
@ -891,27 +901,26 @@ async def uninstall_custom_node(request):
|
|||||||
|
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
|
|
||||||
install_type = json_data['install_type']
|
node_id = json_data.get('id')
|
||||||
|
if json_data['version'] != 'unknown':
|
||||||
|
is_unknown = False
|
||||||
|
node_name = node_id
|
||||||
|
else:
|
||||||
|
# unknown
|
||||||
|
is_unknown = True
|
||||||
|
node_name = os.path.basename(json_data['files'][0])
|
||||||
|
|
||||||
print(f"Uninstall custom node '{json_data['title']}'")
|
res = core.unified_manager.unified_uninstall(node_name, is_unknown)
|
||||||
|
|
||||||
res = False
|
if res.result:
|
||||||
|
|
||||||
if install_type == "copy":
|
|
||||||
js_path_name = json_data['js_path'] if 'js_path' in json_data else '.'
|
|
||||||
res = copy_uninstall(json_data['files'], js_path_name)
|
|
||||||
|
|
||||||
elif install_type == "git-clone":
|
|
||||||
res = core.gitclone_uninstall(json_data['files'])
|
|
||||||
|
|
||||||
if res:
|
|
||||||
print(f"After restarting ComfyUI, please refresh the browser.")
|
print(f"After restarting ComfyUI, please refresh the browser.")
|
||||||
return web.json_response({}, content_type='application/json')
|
return web.json_response({}, content_type='application/json')
|
||||||
|
|
||||||
|
print(f"ERROR: An error occurred while uninstalling '{node_name}'.")
|
||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/customnode/update")
|
@routes.post("/customnode/update")
|
||||||
async def update_custom_node(request):
|
async def update_custom_node(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
||||||
@ -919,25 +928,26 @@ async def update_custom_node(request):
|
|||||||
|
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
|
|
||||||
install_type = json_data['install_type']
|
node_id = json_data.get('id')
|
||||||
|
if json_data['version'] != 'unknown':
|
||||||
|
node_name = node_id
|
||||||
|
else:
|
||||||
|
# unknown
|
||||||
|
node_name = os.path.basename(json_data['files'][0])
|
||||||
|
|
||||||
print(f"Update custom node '{json_data['title']}'")
|
res = core.unified_manager.unified_update(node_name, json_data['version'])
|
||||||
|
|
||||||
res = False
|
|
||||||
|
|
||||||
if install_type == "git-clone":
|
|
||||||
res = core.gitclone_update(json_data['files'])
|
|
||||||
|
|
||||||
core.clear_pip_cache()
|
core.clear_pip_cache()
|
||||||
|
|
||||||
if res:
|
if res.result:
|
||||||
print(f"After restarting ComfyUI, please refresh the browser.")
|
print(f"After restarting ComfyUI, please refresh the browser.")
|
||||||
return web.json_response({}, content_type='application/json')
|
return web.json_response({}, content_type='application/json')
|
||||||
|
|
||||||
|
print(f"ERROR: An error occurred while updating '{node_name}'.")
|
||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/comfyui_manager/update_comfyui")
|
@routes.get("/comfyui_manager/update_comfyui")
|
||||||
async def update_comfyui(request):
|
async def update_comfyui(request):
|
||||||
print(f"Update ComfyUI")
|
print(f"Update ComfyUI")
|
||||||
|
|
||||||
@ -957,21 +967,20 @@ async def update_comfyui(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/customnode/toggle_active")
|
@routes.post("/customnode/disable")
|
||||||
async def toggle_active(request):
|
async def disable_node(request):
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
|
|
||||||
install_type = json_data['install_type']
|
node_id = json_data.get('id')
|
||||||
is_disabled = json_data['installed'] == "Disabled"
|
if json_data['version'] != 'unknown':
|
||||||
|
is_unknown = False
|
||||||
|
node_name = node_id
|
||||||
|
else:
|
||||||
|
# unknown
|
||||||
|
is_unknown = True
|
||||||
|
node_name = os.path.basename(json_data['files'][0])
|
||||||
|
|
||||||
print(f"Update custom node '{json_data['title']}'")
|
res = core.unified_manager.unified_disable(node_name, is_unknown)
|
||||||
|
|
||||||
res = False
|
|
||||||
|
|
||||||
if install_type == "git-clone":
|
|
||||||
res = core.gitclone_set_active(json_data['files'], not is_disabled)
|
|
||||||
elif install_type == "copy":
|
|
||||||
res = copy_set_active(json_data['files'], not is_disabled, json_data.get('js_path', None))
|
|
||||||
|
|
||||||
if res:
|
if res:
|
||||||
return web.json_response({}, content_type='application/json')
|
return web.json_response({}, content_type='application/json')
|
||||||
@ -979,7 +988,20 @@ async def toggle_active(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/model/install")
|
@routes.get("/manager/migrate_unmanaged_nodes")
|
||||||
|
async def migrate_unmanaged_nodes(request):
|
||||||
|
print(f"[ComfyUI-Manager] Migrating unmanaged nodes...")
|
||||||
|
await core.unified_manager.migrate_unmanaged_nodes()
|
||||||
|
print("Done.")
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@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("/model/install")
|
||||||
async def install_model(request):
|
async def install_model(request):
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
|
|
||||||
@ -1046,7 +1068,7 @@ class ManagerTerminalHook:
|
|||||||
manager_terminal_hook = ManagerTerminalHook()
|
manager_terminal_hook = ManagerTerminalHook()
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/terminal")
|
@routes.get("/manager/terminal")
|
||||||
async def terminal_mode(request):
|
async def terminal_mode(request):
|
||||||
if not is_allowed_security_level('high'):
|
if not is_allowed_security_level('high'):
|
||||||
print(f"ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.")
|
print(f"ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.")
|
||||||
@ -1061,7 +1083,7 @@ async def terminal_mode(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/preview_method")
|
@routes.get("/manager/preview_method")
|
||||||
async def preview_method(request):
|
async def preview_method(request):
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
set_preview_method(request.rel_url.query['value'])
|
set_preview_method(request.rel_url.query['value'])
|
||||||
@ -1072,7 +1094,7 @@ async def preview_method(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/badge_mode")
|
@routes.get("/manager/badge_mode")
|
||||||
async def badge_mode(request):
|
async def badge_mode(request):
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
set_badge_mode(request.rel_url.query['value'])
|
set_badge_mode(request.rel_url.query['value'])
|
||||||
@ -1083,7 +1105,7 @@ async def badge_mode(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/default_ui")
|
@routes.get("/manager/default_ui")
|
||||||
async def default_ui_mode(request):
|
async def default_ui_mode(request):
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
set_default_ui_mode(request.rel_url.query['value'])
|
set_default_ui_mode(request.rel_url.query['value'])
|
||||||
@ -1094,7 +1116,7 @@ async def default_ui_mode(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/component/policy")
|
@routes.get("/manager/component/policy")
|
||||||
async def component_policy(request):
|
async def component_policy(request):
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
set_component_policy(request.rel_url.query['value'])
|
set_component_policy(request.rel_url.query['value'])
|
||||||
@ -1105,7 +1127,7 @@ async def component_policy(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/dbl_click/policy")
|
@routes.get("/manager/dbl_click/policy")
|
||||||
async def dbl_click_policy(request):
|
async def dbl_click_policy(request):
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
set_double_click_policy(request.rel_url.query['value'])
|
set_double_click_policy(request.rel_url.query['value'])
|
||||||
@ -1116,7 +1138,7 @@ async def dbl_click_policy(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/channel_url_list")
|
@routes.get("/manager/channel_url_list")
|
||||||
async def channel_url_list(request):
|
async def channel_url_list(request):
|
||||||
channels = core.get_channel_dict()
|
channels = core.get_channel_dict()
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
@ -1153,7 +1175,7 @@ def add_target_blank(html_text):
|
|||||||
return modified_html
|
return modified_html
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/notice")
|
@routes.get("/manager/notice")
|
||||||
async def get_notice(request):
|
async def get_notice(request):
|
||||||
url = "github.com"
|
url = "github.com"
|
||||||
path = "/ltdrdata/ltdrdata.github.io/wiki/News"
|
path = "/ltdrdata/ltdrdata.github.io/wiki/News"
|
||||||
@ -1188,7 +1210,7 @@ async def get_notice(request):
|
|||||||
return web.Response(text="Unable to retrieve Notice", status=200)
|
return web.Response(text="Unable to retrieve Notice", status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/reboot")
|
@routes.get("/manager/reboot")
|
||||||
def restart(self):
|
def restart(self):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
|
||||||
@ -1214,12 +1236,11 @@ def restart(self):
|
|||||||
|
|
||||||
|
|
||||||
def sanitize_filename(input_string):
|
def sanitize_filename(input_string):
|
||||||
# 알파벳, 숫자, 및 밑줄 이외의 문자를 밑줄로 대체
|
|
||||||
result_string = re.sub(r'[^a-zA-Z0-9_]', '_', input_string)
|
result_string = re.sub(r'[^a-zA-Z0-9_]', '_', input_string)
|
||||||
return result_string
|
return result_string
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/component/save")
|
@routes.post("/manager/component/save")
|
||||||
async def save_component(request):
|
async def save_component(request):
|
||||||
try:
|
try:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
@ -1249,7 +1270,7 @@ async def save_component(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/component/loads")
|
@routes.post("/manager/component/loads")
|
||||||
async def load_components(request):
|
async def load_components(request):
|
||||||
try:
|
try:
|
||||||
json_files = [f for f in os.listdir(components_path) if f.endswith('.json')]
|
json_files = [f for f in os.listdir(components_path) if f.endswith('.json')]
|
||||||
@ -1271,7 +1292,7 @@ async def load_components(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/share_option")
|
@routes.get("/manager/share_option")
|
||||||
async def share_option(request):
|
async def share_option(request):
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
core.get_config()['share_option'] = request.rel_url.query['value']
|
core.get_config()['share_option'] = request.rel_url.query['value']
|
||||||
@ -1340,7 +1361,7 @@ def set_youml_settings(settings):
|
|||||||
f.write(settings)
|
f.write(settings)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/get_openart_auth")
|
@routes.get("/manager/get_openart_auth")
|
||||||
async def api_get_openart_auth(request):
|
async def api_get_openart_auth(request):
|
||||||
# print("Getting stored Matrix credentials...")
|
# print("Getting stored Matrix credentials...")
|
||||||
openart_key = get_openart_auth()
|
openart_key = get_openart_auth()
|
||||||
@ -1349,7 +1370,7 @@ async def api_get_openart_auth(request):
|
|||||||
return web.json_response({"openart_key": openart_key})
|
return web.json_response({"openart_key": openart_key})
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/set_openart_auth")
|
@routes.post("/manager/set_openart_auth")
|
||||||
async def api_set_openart_auth(request):
|
async def api_set_openart_auth(request):
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
openart_key = json_data['openart_key']
|
openart_key = json_data['openart_key']
|
||||||
@ -1358,7 +1379,7 @@ async def api_set_openart_auth(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/get_matrix_auth")
|
@routes.get("/manager/get_matrix_auth")
|
||||||
async def api_get_matrix_auth(request):
|
async def api_get_matrix_auth(request):
|
||||||
# print("Getting stored Matrix credentials...")
|
# print("Getting stored Matrix credentials...")
|
||||||
matrix_auth = get_matrix_auth()
|
matrix_auth = get_matrix_auth()
|
||||||
@ -1367,7 +1388,7 @@ async def api_get_matrix_auth(request):
|
|||||||
return web.json_response(matrix_auth)
|
return web.json_response(matrix_auth)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/youml/settings")
|
@routes.get("/manager/youml/settings")
|
||||||
async def api_get_youml_settings(request):
|
async def api_get_youml_settings(request):
|
||||||
youml_settings = get_youml_settings()
|
youml_settings = get_youml_settings()
|
||||||
if not youml_settings:
|
if not youml_settings:
|
||||||
@ -1375,14 +1396,14 @@ async def api_get_youml_settings(request):
|
|||||||
return web.json_response(json.loads(youml_settings))
|
return web.json_response(json.loads(youml_settings))
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/youml/settings")
|
@routes.post("/manager/youml/settings")
|
||||||
async def api_set_youml_settings(request):
|
async def api_set_youml_settings(request):
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
set_youml_settings(json.dumps(json_data))
|
set_youml_settings(json.dumps(json_data))
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/get_comfyworkflows_auth")
|
@routes.get("/manager/get_comfyworkflows_auth")
|
||||||
async def api_get_comfyworkflows_auth(request):
|
async def api_get_comfyworkflows_auth(request):
|
||||||
# Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken'
|
# Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken'
|
||||||
# in the same directory as the ComfyUI base folder
|
# in the same directory as the ComfyUI base folder
|
||||||
@ -1400,7 +1421,7 @@ if hasattr(PromptServer.instance, "app"):
|
|||||||
app.middlewares.append(cors_middleware)
|
app.middlewares.append(cors_middleware)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/set_esheep_workflow_and_images")
|
@routes.post("/manager/set_esheep_workflow_and_images")
|
||||||
async def set_esheep_workflow_and_images(request):
|
async def set_esheep_workflow_and_images(request):
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
current_workflow = json_data['workflow']
|
current_workflow = json_data['workflow']
|
||||||
@ -1410,7 +1431,7 @@ async def set_esheep_workflow_and_images(request):
|
|||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/get_esheep_workflow_and_images")
|
@routes.get("/manager/get_esheep_workflow_and_images")
|
||||||
async def get_esheep_workflow_and_images(request):
|
async def get_esheep_workflow_and_images(request):
|
||||||
with open(os.path.join(core.comfyui_manager_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file:
|
with open(os.path.join(core.comfyui_manager_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file:
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
@ -1481,7 +1502,7 @@ def compute_sha256_checksum(filepath):
|
|||||||
return sha256.hexdigest()
|
return sha256.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/share")
|
@routes.post("/manager/share")
|
||||||
async def share_art(request):
|
async def share_art(request):
|
||||||
# get json data
|
# get json data
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
@ -1654,15 +1675,11 @@ async def share_art(request):
|
|||||||
}, content_type='application/json', status=200)
|
}, content_type='application/json', status=200)
|
||||||
|
|
||||||
|
|
||||||
def sanitize(data):
|
|
||||||
return data.replace("<", "<").replace(">", ">")
|
|
||||||
|
|
||||||
|
|
||||||
async def _confirm_try_install(sender, custom_node_url, msg):
|
async def _confirm_try_install(sender, custom_node_url, msg):
|
||||||
json_obj = await core.get_data_by_mode('default', 'custom-node-list.json')
|
json_obj = await core.get_data_by_mode('default', 'custom-node-list.json')
|
||||||
|
|
||||||
sender = sanitize(sender)
|
sender = manager_util.sanitize_tag(sender)
|
||||||
msg = sanitize(msg)
|
msg = manager_util.sanitize_tag(msg)
|
||||||
target = core.lookup_customnode_by_url(json_obj, custom_node_url)
|
target = core.lookup_customnode_by_url(json_obj, custom_node_url)
|
||||||
|
|
||||||
if target is not None:
|
if target is not None:
|
||||||
@ -1684,10 +1701,10 @@ import asyncio
|
|||||||
async def default_cache_update():
|
async def default_cache_update():
|
||||||
async def get_cache(filename):
|
async def get_cache(filename):
|
||||||
uri = 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/' + filename
|
uri = 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/' + filename
|
||||||
cache_uri = str(core.simple_hash(uri)) + '_' + filename
|
cache_uri = str(manager_util.simple_hash(uri)) + '_' + filename
|
||||||
cache_uri = os.path.join(core.cache_dir, cache_uri)
|
cache_uri = os.path.join(core.cache_dir, cache_uri)
|
||||||
|
|
||||||
json_obj = await core.get_data(uri, True)
|
json_obj = await manager_util.get_data(uri, True)
|
||||||
|
|
||||||
with core.cache_lock:
|
with core.cache_lock:
|
||||||
with open(cache_uri, "w", encoding='utf-8') as file:
|
with open(cache_uri, "w", encoding='utf-8') as file:
|
||||||
@ -1700,7 +1717,7 @@ async def default_cache_update():
|
|||||||
d = get_cache("alter-list.json")
|
d = get_cache("alter-list.json")
|
||||||
e = get_cache("github-stats.json")
|
e = get_cache("github-stats.json")
|
||||||
|
|
||||||
await asyncio.gather(a, b, c, d, e)
|
await asyncio.gather(a, b, c, d, e, core.check_need_to_migrate())
|
||||||
|
|
||||||
|
|
||||||
threading.Thread(target=lambda: asyncio.run(default_cache_update())).start()
|
threading.Thread(target=lambda: asyncio.run(default_cache_update())).start()
|
||||||
|
|||||||
@ -1,3 +1,18 @@
|
|||||||
|
import traceback
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
cache_lock = threading.Lock()
|
||||||
|
|
||||||
|
comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
cache_dir = os.path.join(comfyui_manager_path, '.cache')
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
except:
|
except:
|
||||||
@ -61,3 +76,64 @@ except:
|
|||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|
||||||
|
|
||||||
|
def simple_hash(input_string):
|
||||||
|
hash_value = 0
|
||||||
|
for char in input_string:
|
||||||
|
hash_value = (hash_value * 31 + ord(char)) % (2**32)
|
||||||
|
|
||||||
|
return hash_value
|
||||||
|
|
||||||
|
|
||||||
|
def is_file_created_within_one_day(file_path):
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
file_creation_time = os.path.getctime(file_path)
|
||||||
|
current_time = datetime.now().timestamp()
|
||||||
|
time_difference = current_time - file_creation_time
|
||||||
|
|
||||||
|
return time_difference <= 86400
|
||||||
|
|
||||||
|
|
||||||
|
async def get_data(uri, silent=False):
|
||||||
|
if not silent:
|
||||||
|
print(f"FETCH DATA from: {uri}", end="")
|
||||||
|
|
||||||
|
if uri.startswith("http"):
|
||||||
|
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||||
|
async with session.get(uri) as resp:
|
||||||
|
json_text = await resp.text()
|
||||||
|
else:
|
||||||
|
with cache_lock:
|
||||||
|
with open(uri, "r", encoding="utf-8") as f:
|
||||||
|
json_text = f.read()
|
||||||
|
|
||||||
|
json_obj = json.loads(json_text)
|
||||||
|
|
||||||
|
if not silent:
|
||||||
|
print(f" [DONE]")
|
||||||
|
|
||||||
|
return json_obj
|
||||||
|
|
||||||
|
|
||||||
|
async def get_data_with_cache(uri, silent=False, cache_mode=True):
|
||||||
|
cache_uri = str(simple_hash(uri)) + '_' + os.path.basename(uri).replace('&', "_").replace('?', "_").replace('=', "_")
|
||||||
|
cache_uri = os.path.join(cache_dir, cache_uri+'.json')
|
||||||
|
|
||||||
|
if cache_mode and is_file_created_within_one_day(cache_uri):
|
||||||
|
json_obj = await get_data(cache_uri, silent=silent)
|
||||||
|
else:
|
||||||
|
json_obj = await get_data(uri, silent=silent)
|
||||||
|
|
||||||
|
with cache_lock:
|
||||||
|
with open(cache_uri, "w", encoding='utf-8') as file:
|
||||||
|
json.dump(json_obj, file, indent=4, sort_keys=True)
|
||||||
|
if not silent:
|
||||||
|
print(f"[ComfyUI-Manager] default cache updated: {uri}")
|
||||||
|
|
||||||
|
return json_obj
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_tag(x):
|
||||||
|
return x.replace('<', '<').replace('>', '>')
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
showYouMLShareDialog
|
showYouMLShareDialog
|
||||||
} from "./comfyui-share-common.js";
|
} from "./comfyui-share-common.js";
|
||||||
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
|
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
|
||||||
import { free_models, install_pip, install_via_git_url, manager_instance, rebootAPI, setManagerInstance, show_message } from "./common.js";
|
import { free_models, install_pip, install_via_git_url, manager_instance, rebootAPI, migrateAPI, setManagerInstance, show_message } from "./common.js";
|
||||||
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
|
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
|
||||||
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
||||||
import { ModelManager } from "./model-manager.js";
|
import { ModelManager } from "./model-manager.js";
|
||||||
@ -253,6 +253,18 @@ const style = `
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.cm-button-orange {
|
||||||
|
width: 310px;
|
||||||
|
height: 30px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 17px !important;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: orange !important;
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-experimental-button {
|
.cm-experimental-button {
|
||||||
width: 290px;
|
width: 290px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
@ -804,6 +816,28 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let migration_btn =
|
||||||
|
$el("button.cm-button-orange", {
|
||||||
|
type: "button",
|
||||||
|
textContent: "Migrate to New Node System",
|
||||||
|
onclick: () => migrateAPI()
|
||||||
|
});
|
||||||
|
|
||||||
|
migration_btn.style.display = 'none';
|
||||||
|
|
||||||
|
res.push(migration_btn);
|
||||||
|
|
||||||
|
api.fetchApi('/manager/need_to_migrate')
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(text => {
|
||||||
|
if (text === 'True') {
|
||||||
|
migration_btn.style.display = 'block';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error checking migration status:', error);
|
||||||
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
js/common.js
17
js/common.js
@ -25,6 +25,23 @@ export function rebootAPI() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function migrateAPI() {
|
||||||
|
if (confirm("When performing a migration, existing installed custom nodes will be renamed and the server will be restarted. Are you sure you want to apply this?\n\n(If you don't perform the migration, ComfyUI-Manager's start-up time will be longer each time due to re-checking during startup.)")) {
|
||||||
|
try {
|
||||||
|
await api.fetchApi("/manager/migrate_unmanaged_nodes");
|
||||||
|
api.fetchApi("/manager/reboot");
|
||||||
|
}
|
||||||
|
catch(exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export var manager_instance = null;
|
export var manager_instance = null;
|
||||||
|
|
||||||
export function setManagerInstance(obj) {
|
export function setManagerInstance(obj) {
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { $el } from "../../scripts/ui.js";
|
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||||
import {
|
import { api } from "../../scripts/api.js";
|
||||||
manager_instance, rebootAPI, install_via_git_url,
|
|
||||||
|
import {
|
||||||
|
manager_instance, rebootAPI, install_via_git_url,
|
||||||
fetchData, md5, icons
|
fetchData, md5, icons
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
|
|
||||||
@ -28,11 +30,11 @@ const pageCss = `
|
|||||||
.cn-manager button {
|
.cn-manager button {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--input-text);
|
color: var(--input-text);
|
||||||
background-color: var(--comfy-input-bg);
|
background-color: var(--comfy-input-bg);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-color: var(--border-color);
|
border-color: var(--border-color);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
@ -124,7 +126,7 @@ const pageCss = `
|
|||||||
|
|
||||||
.cn-manager-grid .cn-node-desc a {
|
.cn-manager-grid .cn-node-desc a {
|
||||||
color: #5555FF;
|
color: #5555FF;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +193,7 @@ const pageCss = `
|
|||||||
.cn-tag-list > div {
|
.cn-tag-list > div {
|
||||||
background-color: var(--border-color);
|
background-color: var(--border-color);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-install-buttons {
|
.cn-install-buttons {
|
||||||
@ -200,8 +202,8 @@ const pageCss = `
|
|||||||
gap: 3px;
|
gap: 3px;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-selected-buttons {
|
.cn-selected-buttons {
|
||||||
@ -212,17 +214,17 @@ const pageCss = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-btn-enable {
|
.cn-manager .cn-btn-enable {
|
||||||
background-color: blue;
|
background-color: #333399;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-btn-disable {
|
.cn-manager .cn-btn-disable {
|
||||||
background-color: MediumSlateBlue;
|
background-color: #442277;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-btn-update {
|
.cn-manager .cn-btn-update {
|
||||||
background-color: blue;
|
background-color: #1155AA;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,41 +249,47 @@ const pageCss = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-btn-uninstall {
|
.cn-manager .cn-btn-uninstall {
|
||||||
background-color: red;
|
background-color: #993333;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-switch {
|
||||||
|
background-color: #448833;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes cn-btn-loading-bg {
|
@keyframes cn-btn-loading-bg {
|
||||||
0% {
|
0% {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
left: -105px;
|
left: -105px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager button.cn-btn-loading {
|
.cn-manager button.cn-btn-loading {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-color: rgb(0 119 207 / 80%);
|
border-color: rgb(0 119 207 / 80%);
|
||||||
background-color: var(--comfy-input-bg);
|
background-color: var(--comfy-input-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager button.cn-btn-loading::after {
|
.cn-manager button.cn-btn-loading::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
content: "";
|
content: "";
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-image: repeating-linear-gradient(
|
background-image: repeating-linear-gradient(
|
||||||
-45deg,
|
-45deg,
|
||||||
rgb(0 119 207 / 30%),
|
rgb(0 119 207 / 30%),
|
||||||
rgb(0 119 207 / 30%) 10px,
|
rgb(0 119 207 / 30%) 10px,
|
||||||
transparent 10px,
|
transparent 10px,
|
||||||
transparent 15px
|
transparent 15px
|
||||||
);
|
);
|
||||||
animation: cn-btn-loading-bg 2s linear infinite;
|
animation: cn-btn-loading-bg 2s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager-light .cn-node-name a {
|
.cn-manager-light .cn-node-name a {
|
||||||
@ -356,7 +364,6 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
||||||
if (!document.querySelector(`style[context="${this.id}"]`)) {
|
if (!document.querySelector(`style[context="${this.id}"]`)) {
|
||||||
const $style = document.createElement("style");
|
const $style = document.createElement("style");
|
||||||
$style.setAttribute("context", this.id);
|
$style.setAttribute("context", this.id);
|
||||||
@ -374,6 +381,130 @@ export class CustomNodesManager {
|
|||||||
this.initGrid();
|
this.initGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showVersionSelectorDialog(versions, onSelect) {
|
||||||
|
const dialog = new ComfyDialog();
|
||||||
|
dialog.element.style.zIndex = 100003;
|
||||||
|
dialog.element.style.width = "300px";
|
||||||
|
dialog.element.style.padding = "0";
|
||||||
|
dialog.element.style.backgroundColor = "#2a2a2a";
|
||||||
|
dialog.element.style.border = "1px solid #3a3a3a";
|
||||||
|
dialog.element.style.borderRadius = "8px";
|
||||||
|
dialog.element.style.boxSizing = "border-box";
|
||||||
|
dialog.element.style.overflow = "hidden";
|
||||||
|
|
||||||
|
const contentStyle = {
|
||||||
|
width: "300px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "20px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
gap: "15px"
|
||||||
|
};
|
||||||
|
|
||||||
|
let selectedVersion = versions[0];
|
||||||
|
|
||||||
|
const versionList = $el("select", {
|
||||||
|
multiple: true,
|
||||||
|
size: Math.min(10, versions.length),
|
||||||
|
style: {
|
||||||
|
width: "260px",
|
||||||
|
height: "auto",
|
||||||
|
backgroundColor: "#383838",
|
||||||
|
color: "#ffffff",
|
||||||
|
border: "1px solid #4a4a4a",
|
||||||
|
borderRadius: "4px",
|
||||||
|
padding: "5px",
|
||||||
|
boxSizing: "border-box"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
versions.map((v, index) => $el("option", {
|
||||||
|
value: v,
|
||||||
|
textContent: v,
|
||||||
|
selected: index === 0
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
versionList.addEventListener('change', (e) => {
|
||||||
|
selectedVersion = e.target.value;
|
||||||
|
Array.from(e.target.options).forEach(opt => {
|
||||||
|
opt.selected = opt.value === selectedVersion;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = $el("div", {
|
||||||
|
style: contentStyle
|
||||||
|
}, [
|
||||||
|
$el("h3", {
|
||||||
|
textContent: "Select Version",
|
||||||
|
style: {
|
||||||
|
color: "#ffffff",
|
||||||
|
backgroundColor: "#1a1a1a",
|
||||||
|
padding: "10px 15px",
|
||||||
|
margin: "0 0 10px 0",
|
||||||
|
width: "260px",
|
||||||
|
textAlign: "center",
|
||||||
|
borderRadius: "4px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
versionList,
|
||||||
|
$el("div", {
|
||||||
|
style: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "260px",
|
||||||
|
gap: "10px"
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
$el("button", {
|
||||||
|
textContent: "Cancel",
|
||||||
|
onclick: () => dialog.close(),
|
||||||
|
style: {
|
||||||
|
flex: "1",
|
||||||
|
padding: "8px",
|
||||||
|
backgroundColor: "#4a4a4a",
|
||||||
|
color: "#ffffff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
$el("button", {
|
||||||
|
textContent: "Select",
|
||||||
|
onclick: () => {
|
||||||
|
if (selectedVersion) {
|
||||||
|
onSelect(selectedVersion);
|
||||||
|
dialog.close();
|
||||||
|
} else {
|
||||||
|
alert("Please select a version.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
flex: "1",
|
||||||
|
padding: "8px",
|
||||||
|
backgroundColor: "#4CAF50",
|
||||||
|
color: "#ffffff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
dialog.show(content);
|
||||||
|
}
|
||||||
|
|
||||||
initFilter() {
|
initFilter() {
|
||||||
const $filter = this.element.querySelector(".cn-manager-filter");
|
const $filter = this.element.querySelector(".cn-manager-filter");
|
||||||
const filterList = [{
|
const filterList = [{
|
||||||
@ -382,23 +513,31 @@ export class CustomNodesManager {
|
|||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Installed",
|
label: "Installed",
|
||||||
value: "True",
|
value: "installed",
|
||||||
|
hasData: true
|
||||||
|
}, {
|
||||||
|
label: "Enabled",
|
||||||
|
value: "enabled",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Disabled",
|
label: "Disabled",
|
||||||
value: "Disabled",
|
value: "disabled",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Import Failed",
|
label: "Import Failed",
|
||||||
value: "Fail",
|
value: "import-fail",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Not Installed",
|
label: "Not Installed",
|
||||||
value: "False",
|
value: "not-installed",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Unknown",
|
label: "ComfyRegistry",
|
||||||
value: "None",
|
value: "cnr",
|
||||||
|
hasData: true
|
||||||
|
}, {
|
||||||
|
label: "Non-ComfyRegistry",
|
||||||
|
value: "unknown",
|
||||||
hasData: true
|
hasData: true
|
||||||
}, {
|
}, {
|
||||||
label: "Update",
|
label: "Update",
|
||||||
@ -423,16 +562,15 @@ export class CustomNodesManager {
|
|||||||
return this.filterList.find(it => it.value === filter)
|
return this.filterList.find(it => it.value === filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
getInstallButtons(installed, title) {
|
getActionButtons(action, rowItem, is_selected_button) {
|
||||||
|
|
||||||
const buttons = {
|
const buttons = {
|
||||||
"enable": {
|
"enable": {
|
||||||
label: "Enable",
|
label: "Enable",
|
||||||
mode: "toggle_active"
|
mode: "enable"
|
||||||
},
|
},
|
||||||
"disable": {
|
"disable": {
|
||||||
label: "Disable",
|
label: "Disable",
|
||||||
mode: "toggle_active"
|
mode: "disable"
|
||||||
},
|
},
|
||||||
|
|
||||||
"update": {
|
"update": {
|
||||||
@ -460,34 +598,47 @@ export class CustomNodesManager {
|
|||||||
"uninstall": {
|
"uninstall": {
|
||||||
label: "Uninstall",
|
label: "Uninstall",
|
||||||
mode: "uninstall"
|
mode: "uninstall"
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
label: "Switch",
|
||||||
|
mode: "switch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const installGroups = {
|
const installGroups = {
|
||||||
"Disabled": ["enable", "uninstall"],
|
"disabled": ["enable", "switch", "uninstall"],
|
||||||
"Update": ["update", "disable", "uninstall"],
|
"updatable": ["update", "switch", "disable", "uninstall"],
|
||||||
"Fail": ["try-fix", "uninstall"],
|
"import-fail": ["try-fix", "switch", "disable", "uninstall"],
|
||||||
"True": ["try-update", "disable", "uninstall"],
|
"enabled": ["try-update", "switch", "disable", "uninstall"],
|
||||||
"False": ["install"],
|
"not-installed": ["install"],
|
||||||
'None': ["try-install"]
|
'unknown': ["try-install"]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!manager_instance.update_check_checkbox.checked) {
|
if (!manager_instance.update_check_checkbox.checked) {
|
||||||
installGroups.True = installGroups.True.filter(it => it !== "try-update");
|
installGroups.enabled = installGroups.enabled.filter(it => it !== "try-update");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (title === "ComfyUI-Manager") {
|
if (rowItem?.title === "ComfyUI-Manager") {
|
||||||
installGroups.True = installGroups.True.filter(it => it !== "disable");
|
installGroups.enabled = installGroups.enabled.filter(it => it !== "disable");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowItem?.version === "unknown") {
|
||||||
|
installGroups.enabled = installGroups.enabled.filter(it => it !== "switch");
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = installGroups[action];
|
||||||
|
|
||||||
|
if(is_selected_button) {
|
||||||
|
list = list.filter(it => it !== "switch");
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = installGroups[installed];
|
|
||||||
if (!list) {
|
if (!list) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return list.map(id => {
|
return list.map(id => {
|
||||||
const bt = buttons[id];
|
const bt = buttons[id];
|
||||||
return `<button class="cn-btn-${id}" group="${installed}" mode="${bt.mode}">${bt.label}</button>`;
|
return `<button class="cn-btn-${id}" group="${action}" mode="${bt.mode}">${bt.label}</button>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,18 +772,27 @@ export class CustomNodesManager {
|
|||||||
this.showStatus(`${prevViewRowsLength.toLocaleString()} custom nodes`);
|
this.showStatus(`${prevViewRowsLength.toLocaleString()} custom nodes`);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.bind('onSelectChanged', (e, changes) => {
|
grid.bind('onSelectChanged', (e, changes) => {
|
||||||
this.renderSelected();
|
this.renderSelected();
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.bind('onClick', (e, d) => {
|
grid.bind('onClick', (e, d) => {
|
||||||
const btn = this.getButton(d.e.target);
|
const btn = this.getButton(d.e.target);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
this.installNodes([d.rowItem.hash], btn, d.rowItem.title);
|
const item = this.grid.getRowItemBy("hash", d.rowItem.hash);
|
||||||
|
|
||||||
|
const { target, label, mode} = btn;
|
||||||
|
if((mode === "install" || mode === "switch" || mode == "enable") && item.originalData.version != 'unknown') {
|
||||||
|
// install after select version via dialog if item is cnr node
|
||||||
|
this.installNodeWithVersion(d.rowItem, btn, mode == 'enable');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.installNodes([d.rowItem.hash], btn, d.rowItem.title);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.setOption({
|
grid.setOption({
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
@ -651,7 +811,7 @@ export class CustomNodesManager {
|
|||||||
bindContainerResize: true,
|
bindContainerResize: true,
|
||||||
|
|
||||||
cellResizeObserver: (rowItem, columnItem) => {
|
cellResizeObserver: (rowItem, columnItem) => {
|
||||||
const autoHeightColumns = ['title', 'installed', 'description', "alternatives"];
|
const autoHeightColumns = ['title', 'action', 'description', "alternatives"];
|
||||||
return autoHeightColumns.includes(columnItem.id)
|
return autoHeightColumns.includes(columnItem.id)
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -696,11 +856,11 @@ export class CustomNodesManager {
|
|||||||
theme: colorPalette === "light" ? "" : "dark"
|
theme: colorPalette === "light" ? "" : "dark"
|
||||||
};
|
};
|
||||||
|
|
||||||
const rows = this.custom_nodes || [];
|
const rows = this.custom_nodes || {};
|
||||||
rows.forEach((item, i) => {
|
for(let nodeKey in rows) {
|
||||||
item.id = i + 1;
|
let item = rows[nodeKey];
|
||||||
const nodeKey = item.files[0];
|
|
||||||
const extensionInfo = this.extension_mappings[nodeKey];
|
const extensionInfo = this.extension_mappings[nodeKey];
|
||||||
|
|
||||||
if(extensionInfo) {
|
if(extensionInfo) {
|
||||||
const { extensions, conflicts } = extensionInfo;
|
const { extensions, conflicts } = extensionInfo;
|
||||||
if (extensions.length) {
|
if (extensions.length) {
|
||||||
@ -712,7 +872,7 @@ export class CustomNodesManager {
|
|||||||
item.conflictsList = conflicts;
|
item.conflictsList = conflicts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
id: 'id',
|
id: 'id',
|
||||||
@ -727,22 +887,47 @@ export class CustomNodesManager {
|
|||||||
maxWidth: 500,
|
maxWidth: 500,
|
||||||
classMap: 'cn-node-name',
|
classMap: 'cn-node-name',
|
||||||
formatter: (title, rowItem, columnItem) => {
|
formatter: (title, rowItem, columnItem) => {
|
||||||
return `${rowItem.installed === 'Fail' ? '<font color="red"><B>(IMPORT FAILED)</B></font>' : ''}
|
return `${rowItem.action === 'import-fail' ? '<font color="red"><B>(IMPORT FAILED)</B></font>' : ''}
|
||||||
<a href=${rowItem.reference} target="_blank"><b>${title}</b></a>`;
|
<a href=${rowItem.reference} target="_blank"><b>${title}</b></a>`;
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
id: 'installed',
|
id: 'version',
|
||||||
name: 'Install',
|
name: 'Version',
|
||||||
|
width: 200,
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 500,
|
||||||
|
classMap: 'cn-node-desc',
|
||||||
|
formatter: (version, rowItem, columnItem) => {
|
||||||
|
if(version == undefined) {
|
||||||
|
return `undef`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(rowItem.cnr_latest && version != rowItem.cnr_latest) {
|
||||||
|
if(version == 'nightly') {
|
||||||
|
return `${version} [${rowItem.cnr_latest}]`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `${version} [↑${rowItem.cnr_latest}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return `${version}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
id: 'action',
|
||||||
|
name: 'Action',
|
||||||
width: 130,
|
width: 130,
|
||||||
minWidth: 110,
|
minWidth: 110,
|
||||||
maxWidth: 200,
|
maxWidth: 200,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: (installed, rowItem, columnItem) => {
|
formatter: (action, rowItem, columnItem) => {
|
||||||
if (rowItem.restart) {
|
if (rowItem.restart) {
|
||||||
return `<font color="red">Restart Required</span>`;
|
return `<font color="red">Restart Required</span>`;
|
||||||
}
|
}
|
||||||
const buttons = this.getInstallButtons(installed, rowItem.title);
|
const buttons = this.getActionButtons(action, rowItem);
|
||||||
return `<div class="cn-install-buttons">${buttons}</div>`;
|
return `<div class="cn-install-buttons">${buttons}</div>`;
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@ -845,14 +1030,35 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
let rows_values = Object.keys(rows).map(key => rows[key]);
|
||||||
|
|
||||||
|
rows_values =
|
||||||
|
rows_values.sort((a, b) => {
|
||||||
|
if (a.version == 'unknown' && b.version != 'unknown') return 1;
|
||||||
|
if (a.version != 'unknown' && b.version == 'unknown') return -1;
|
||||||
|
|
||||||
|
if (a.stars !== b.stars) {
|
||||||
|
return b.stars - a.stars;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.last_update !== b.last_update) {
|
||||||
|
return new Date(b.last_update) - new Date(a.last_update);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
this.grid.setData({
|
this.grid.setData({
|
||||||
options,
|
options: options,
|
||||||
rows,
|
rows: rows_values,
|
||||||
columns
|
columns: columns
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for(let i=0; i<rows_values.length; i++) {
|
||||||
|
rows_values[i].id = i+1;
|
||||||
|
}
|
||||||
|
|
||||||
this.grid.render();
|
this.grid.render();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGrid() {
|
updateGrid() {
|
||||||
@ -877,7 +1083,7 @@ export class CustomNodesManager {
|
|||||||
|
|
||||||
const selectedMap = {};
|
const selectedMap = {};
|
||||||
selectedList.forEach(item => {
|
selectedList.forEach(item => {
|
||||||
let type = item.installed;
|
let type = item.action;
|
||||||
if (item.restart) {
|
if (item.restart) {
|
||||||
type = "Restart Required";
|
type = "Restart Required";
|
||||||
}
|
}
|
||||||
@ -895,7 +1101,7 @@ export class CustomNodesManager {
|
|||||||
const filterItem = this.getFilterItem(v);
|
const filterItem = this.getFilterItem(v);
|
||||||
list.push(`<div class="cn-selected-buttons">
|
list.push(`<div class="cn-selected-buttons">
|
||||||
<span>Selected <b>${selectedMap[v].length}</b> ${filterItem ? filterItem.label : v}</span>
|
<span>Selected <b>${selectedMap[v].length}</b> ${filterItem ? filterItem.label : v}</span>
|
||||||
${this.grid.hasMask ? "" : this.getInstallButtons(v)}
|
${this.grid.hasMask ? "" : this.getActionButtons(v, null, true)}
|
||||||
</div>`);
|
</div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -913,8 +1119,67 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async installNodes(list, btn, title) {
|
async installNodeWithVersion(rowItem, btn, is_enable) {
|
||||||
|
let hash = rowItem.hash;
|
||||||
|
let title = rowItem.title;
|
||||||
|
|
||||||
|
const item = this.grid.getRowItemBy("hash", hash);
|
||||||
|
|
||||||
|
let node_id = item.originalData.id;
|
||||||
|
|
||||||
|
this.showLoading();
|
||||||
|
let res;
|
||||||
|
if(is_enable) {
|
||||||
|
res = await api.fetchApi(`/customnode/disabled_versions/${node_id}`, { cache: "no-store" });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res = await api.fetchApi(`/customnode/versions/${node_id}`, { cache: "no-store" });
|
||||||
|
}
|
||||||
|
this.hideLoading();
|
||||||
|
|
||||||
|
if(res.status == 200) {
|
||||||
|
let obj = await res.json();
|
||||||
|
|
||||||
|
let versions = [];
|
||||||
|
let default_version;
|
||||||
|
let version_cnt = 0;
|
||||||
|
|
||||||
|
if(!is_enable) {
|
||||||
|
if(rowItem.cnr_latest != rowItem.originalData.active_version) {
|
||||||
|
versions.push('latest');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rowItem.originalData.active_version != 'nightly') {
|
||||||
|
versions.push('nightly');
|
||||||
|
default_version = 'nightly';
|
||||||
|
version_cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let v of obj) {
|
||||||
|
if(rowItem.originalData.active_version != v.version) {
|
||||||
|
default_version = v.version;
|
||||||
|
versions.push(v.version);
|
||||||
|
version_cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(version_cnt == 1) {
|
||||||
|
// if only one version is available
|
||||||
|
this.installNodes([hash], btn, title, default_version);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.showVersionSelectorDialog(versions, (selected_version) => {
|
||||||
|
this.installNodes([hash], btn, title, selected_version);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
show_message('Failed to fetch versions from ComfyRegistry.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async installNodes(list, btn, title, selected_version) {
|
||||||
const { target, label, mode} = btn;
|
const { target, label, mode} = btn;
|
||||||
|
|
||||||
if(mode === "uninstall") {
|
if(mode === "uninstall") {
|
||||||
@ -925,13 +1190,11 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
target.classList.add("cn-btn-loading");
|
target.classList.add("cn-btn-loading");
|
||||||
this.showLoading();
|
|
||||||
this.showError("");
|
this.showError("");
|
||||||
|
|
||||||
let needRestart = false;
|
let needRestart = false;
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
for (const hash of list) {
|
for (const hash of list) {
|
||||||
|
|
||||||
const item = this.grid.getRowItemBy("hash", hash);
|
const item = this.grid.getRowItemBy("hash", hash);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
errorMsg = `Not found custom node: ${hash}`;
|
errorMsg = `Not found custom node: ${hash}`;
|
||||||
@ -949,9 +1212,24 @@ export class CustomNodesManager {
|
|||||||
this.showStatus(`${label} ${item.title} ...`);
|
this.showStatus(`${label} ${item.title} ...`);
|
||||||
|
|
||||||
const data = item.originalData;
|
const data = item.originalData;
|
||||||
const res = await fetchData(`/customnode/${mode}`, {
|
data.selected_version = selected_version;
|
||||||
|
data.channel = this.channel;
|
||||||
|
data.mode = this.mode;
|
||||||
|
|
||||||
|
let install_mode = mode;
|
||||||
|
if(mode == 'switch') {
|
||||||
|
install_mode = 'install';
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't post install if install_mode == 'enable'
|
||||||
|
data.skip_post_install = install_mode == 'enable';
|
||||||
|
let api_mode = install_mode;
|
||||||
|
if(install_mode == 'enable') {
|
||||||
|
api_mode = 'install';
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.fetchApi(`/customnode/${api_mode}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -974,13 +1252,12 @@ export class CustomNodesManager {
|
|||||||
this.grid.setRowSelected(item, false);
|
this.grid.setRowSelected(item, false);
|
||||||
item.restart = true;
|
item.restart = true;
|
||||||
this.restartMap[item.hash] = true;
|
this.restartMap[item.hash] = true;
|
||||||
this.grid.updateCell(item, "installed");
|
this.grid.updateCell(item, "action");
|
||||||
|
|
||||||
//console.log(res.data);
|
//console.log(res.data);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hideLoading();
|
|
||||||
target.classList.remove("cn-btn-loading");
|
target.classList.remove("cn-btn-loading");
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
@ -1064,26 +1341,28 @@ export class CustomNodesManager {
|
|||||||
const mappings = res.data;
|
const mappings = res.data;
|
||||||
|
|
||||||
// build regex->url map
|
// build regex->url map
|
||||||
const regex_to_url = [];
|
const regex_to_pack = [];
|
||||||
this.custom_nodes.forEach(node => {
|
for(let k in this.custom_nodes) {
|
||||||
|
let node = this.custom_nodes[k];
|
||||||
|
|
||||||
if(node.nodename_pattern) {
|
if(node.nodename_pattern) {
|
||||||
regex_to_url.push({
|
regex_to_pack.push({
|
||||||
regex: new RegExp(node.nodename_pattern),
|
regex: new RegExp(node.nodename_pattern),
|
||||||
url: node.files[0]
|
url: node.files[0]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// build name->url map
|
// build name->url map
|
||||||
const name_to_urls = {};
|
const name_to_packs = {};
|
||||||
for (const url in mappings) {
|
for (const url in mappings) {
|
||||||
const names = mappings[url];
|
const names = mappings[url];
|
||||||
|
|
||||||
for(const name in names[0]) {
|
for(const name in names[0]) {
|
||||||
let v = name_to_urls[names[0][name]];
|
let v = name_to_packs[names[0][name]];
|
||||||
if(v == undefined) {
|
if(v == undefined) {
|
||||||
v = [];
|
v = [];
|
||||||
name_to_urls[names[0][name]] = v;
|
name_to_packs[names[0][name]] = v;
|
||||||
}
|
}
|
||||||
v.push(url);
|
v.push(url);
|
||||||
}
|
}
|
||||||
@ -1110,15 +1389,15 @@ export class CustomNodesManager {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!registered_nodes.has(node_type)) {
|
if (!registered_nodes.has(node_type)) {
|
||||||
const urls = name_to_urls[node_type.trim()];
|
const packs = name_to_packs[node_type.trim()];
|
||||||
if(urls)
|
if(packs)
|
||||||
urls.forEach(url => {
|
packs.forEach(url => {
|
||||||
missing_nodes.add(url);
|
missing_nodes.add(url);
|
||||||
});
|
});
|
||||||
else {
|
else {
|
||||||
for(let j in regex_to_url) {
|
for(let j in regex_to_pack) {
|
||||||
if(regex_to_url[j].regex.test(node_type)) {
|
if(regex_to_pack[j].regex.test(node_type)) {
|
||||||
missing_nodes.add(regex_to_url[j].url);
|
missing_nodes.add(regex_to_pack[j].url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1129,19 +1408,27 @@ export class CustomNodesManager {
|
|||||||
const unresolved = resUnresolved.data;
|
const unresolved = resUnresolved.data;
|
||||||
if (unresolved && unresolved.nodes) {
|
if (unresolved && unresolved.nodes) {
|
||||||
unresolved.nodes.forEach(node_type => {
|
unresolved.nodes.forEach(node_type => {
|
||||||
const url = name_to_urls[node_type];
|
const packs = name_to_packs[node_type];
|
||||||
if(url) {
|
if(packs) {
|
||||||
missing_nodes.add(url);
|
packs.forEach(url => {
|
||||||
|
missing_nodes.add(url);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashMap = {};
|
const hashMap = {};
|
||||||
this.custom_nodes.forEach(item => {
|
for(let k in this.custom_nodes) {
|
||||||
if (item.files.some(file => missing_nodes.has(file))) {
|
let item = this.custom_nodes[k];
|
||||||
|
|
||||||
|
if(missing_nodes.has(item.id)) {
|
||||||
hashMap[item.hash] = true;
|
hashMap[item.hash] = true;
|
||||||
}
|
}
|
||||||
});
|
else if (item.files?.some(file => missing_nodes.has(file))) {
|
||||||
|
hashMap[item.hash] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return hashMap;
|
return hashMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1156,27 +1443,28 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hashMap = {};
|
const hashMap = {};
|
||||||
const { items } = res.data;
|
const items = res.data;
|
||||||
|
|
||||||
items.forEach(item => {
|
for(let i in items) {
|
||||||
|
let item = items[i];
|
||||||
|
let custom_node = this.custom_nodes[i];
|
||||||
|
|
||||||
const custom_node = this.custom_nodes.find(node => node.files.find(file => file === item.id));
|
|
||||||
if (!custom_node) {
|
if (!custom_node) {
|
||||||
console.log(`Not found custom node: ${item.id}`);
|
console.log(`Not found custom node: ${item.id}`);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = `${item.tags}`.split(",").map(tag => {
|
const tags = `${item.tags}`.split(",").map(tag => {
|
||||||
return `<div>${tag.trim()}</div>`;
|
return `<div>${tag.trim()}</div>`;
|
||||||
}).join("")
|
}).join("");
|
||||||
|
|
||||||
hashMap[custom_node.hash] = {
|
hashMap[custom_node.hash] = {
|
||||||
alternatives: `<div class="cn-tag-list">${tags}</div> ${item.description}`
|
alternatives: `<div class="cn-tag-list">${tags}</div> ${item.description}`
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
}
|
||||||
|
|
||||||
return hashMap
|
return hashMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadData(show_mode = ShowMode.NORMAL) {
|
async loadData(show_mode = ShowMode.NORMAL) {
|
||||||
@ -1198,18 +1486,19 @@ export class CustomNodesManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { channel, custom_nodes} = res.data;
|
const { channel, node_packs } = res.data;
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.custom_nodes = custom_nodes;
|
this.mode = mode;
|
||||||
|
this.custom_nodes = node_packs;
|
||||||
|
|
||||||
if(this.channel !== 'default') {
|
if(this.channel !== 'default') {
|
||||||
this.element.querySelector(".cn-manager-channel").innerHTML = `Channel: ${this.channel} (Incomplete list)`;
|
this.element.querySelector(".cn-manager-channel").innerHTML = `Channel: ${this.channel} (Incomplete list)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of custom_nodes) {
|
for (const k in node_packs) {
|
||||||
|
let item = node_packs[k];
|
||||||
item.originalData = JSON.parse(JSON.stringify(item));
|
item.originalData = JSON.parse(JSON.stringify(item));
|
||||||
const message = item.title + item.files[0];
|
item.hash = md5(k);
|
||||||
item.hash = md5(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterItem = this.getFilterItem(this.show_mode);
|
const filterItem = this.getFilterItem(this.show_mode);
|
||||||
@ -1217,11 +1506,12 @@ export class CustomNodesManager {
|
|||||||
let hashMap;
|
let hashMap;
|
||||||
if(this.show_mode == ShowMode.UPDATE) {
|
if(this.show_mode == ShowMode.UPDATE) {
|
||||||
hashMap = {};
|
hashMap = {};
|
||||||
custom_nodes.forEach(it => {
|
for (const k in node_packs) {
|
||||||
if (it.installed === "Update") {
|
let it = node_packs[k];
|
||||||
|
if (it['update-state'] === "true") {
|
||||||
hashMap[it.hash] = true;
|
hashMap[it.hash] = true;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else if(this.show_mode == ShowMode.MISSING) {
|
} else if(this.show_mode == ShowMode.MISSING) {
|
||||||
hashMap = await this.getMissingNodes();
|
hashMap = await this.getMissingNodes();
|
||||||
} else if(this.show_mode == ShowMode.ALTERNATIVES) {
|
} else if(this.show_mode == ShowMode.ALTERNATIVES) {
|
||||||
@ -1231,10 +1521,23 @@ export class CustomNodesManager {
|
|||||||
filterItem.hasData = true;
|
filterItem.hasData = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_nodes.forEach(nodeItem => {
|
for(let k in node_packs) {
|
||||||
|
let nodeItem = node_packs[k];
|
||||||
|
|
||||||
if (this.restartMap[nodeItem.hash]) {
|
if (this.restartMap[nodeItem.hash]) {
|
||||||
nodeItem.restart = true;
|
nodeItem.restart = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(nodeItem['update-state'] == "true") {
|
||||||
|
nodeItem.action = 'updatable';
|
||||||
|
}
|
||||||
|
else if(nodeItem['import-fail']) {
|
||||||
|
nodeItem.action = 'import-fail';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nodeItem.action = nodeItem.state;
|
||||||
|
}
|
||||||
|
|
||||||
const filterTypes = new Set();
|
const filterTypes = new Set();
|
||||||
this.filterList.forEach(filterItem => {
|
this.filterList.forEach(filterItem => {
|
||||||
const { value, hashMap } = filterItem;
|
const { value, hashMap } = filterItem;
|
||||||
@ -1243,29 +1546,51 @@ export class CustomNodesManager {
|
|||||||
if (hashData) {
|
if (hashData) {
|
||||||
filterTypes.add(value);
|
filterTypes.add(value);
|
||||||
if (value === ShowMode.UPDATE) {
|
if (value === ShowMode.UPDATE) {
|
||||||
nodeItem.installed = "Update";
|
nodeItem['update-state'] = "true";
|
||||||
|
}
|
||||||
|
if (value === ShowMode.MISSING) {
|
||||||
|
nodeItem['missing-node'] = "true";
|
||||||
}
|
}
|
||||||
if (typeof hashData === "object") {
|
if (typeof hashData === "object") {
|
||||||
Object.assign(nodeItem, hashData);
|
Object.assign(nodeItem, hashData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (nodeItem.installed === value) {
|
if (nodeItem.state === value) {
|
||||||
filterTypes.add(value);
|
filterTypes.add(value);
|
||||||
}
|
}
|
||||||
const map = {
|
|
||||||
"Update": "True",
|
switch(nodeItem.state) {
|
||||||
"Disabled": "True",
|
case "enabled":
|
||||||
"Fail": "True",
|
filterTypes.add("enabled");
|
||||||
"None": "False"
|
case "disabled":
|
||||||
|
filterTypes.add("installed");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "not-installed":
|
||||||
|
filterTypes.add("not-installed");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (map[nodeItem.installed]) {
|
|
||||||
filterTypes.add(map[nodeItem.installed]);
|
if(nodeItem.version != 'unknown') {
|
||||||
|
filterTypes.add("cnr");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
filterTypes.add("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nodeItem['update-state'] == 'true') {
|
||||||
|
filterTypes.add("updatable");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nodeItem['import-fail']) {
|
||||||
|
filterTypes.add("import-fail");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
nodeItem.filterTypes = Array.from(filterTypes);
|
nodeItem.filterTypes = Array.from(filterTypes);
|
||||||
});
|
}
|
||||||
|
|
||||||
this.renderGrid();
|
this.renderGrid();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import datetime
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@ -70,11 +69,12 @@ cm_global.register_api('cm.register_message_collapse', register_message_collapse
|
|||||||
cm_global.register_api('cm.is_import_failed_extension', is_import_failed_extension)
|
cm_global.register_api('cm.is_import_failed_extension', is_import_failed_extension)
|
||||||
|
|
||||||
|
|
||||||
comfyui_manager_path = os.path.dirname(__file__)
|
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
|
||||||
custom_nodes_path = os.path.abspath(os.path.join(comfyui_manager_path, ".."))
|
custom_nodes_path = os.path.abspath(os.path.join(comfyui_manager_path, ".."))
|
||||||
startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts")
|
startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts")
|
||||||
restore_snapshot_path = os.path.join(startup_script_path, "restore-snapshot.json")
|
restore_snapshot_path = os.path.join(startup_script_path, "restore-snapshot.json")
|
||||||
git_script_path = os.path.join(comfyui_manager_path, "git_helper.py")
|
git_script_path = os.path.join(comfyui_manager_path, "git_helper.py")
|
||||||
|
cm_cli_path = os.path.join(comfyui_manager_path, "cm-cli.py")
|
||||||
pip_overrides_path = os.path.join(comfyui_manager_path, "pip_overrides.json")
|
pip_overrides_path = os.path.join(comfyui_manager_path, "pip_overrides.json")
|
||||||
|
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ try:
|
|||||||
write_stderr = wrapper_stderr
|
write_stderr = wrapper_stderr
|
||||||
|
|
||||||
pat_tqdm = r'\d+%.*\[(.*?)\]'
|
pat_tqdm = r'\d+%.*\[(.*?)\]'
|
||||||
pat_import_fail = r'seconds \(IMPORT FAILED\):.*[/\\]custom_nodes[/\\](.*)$'
|
pat_import_fail = r'seconds \(IMPORT FAILED\):(.*)$'
|
||||||
|
|
||||||
is_start_mode = True
|
is_start_mode = True
|
||||||
|
|
||||||
@ -233,7 +233,7 @@ try:
|
|||||||
if is_start_mode:
|
if is_start_mode:
|
||||||
match = re.search(pat_import_fail, message)
|
match = re.search(pat_import_fail, message)
|
||||||
if match:
|
if match:
|
||||||
import_failed_extensions.add(match.group(1))
|
import_failed_extensions.add(match.group(1).strip())
|
||||||
|
|
||||||
if 'Starting server' in message:
|
if 'Starting server' in message:
|
||||||
is_start_mode = False
|
is_start_mode = False
|
||||||
@ -255,7 +255,7 @@ try:
|
|||||||
|
|
||||||
def sync_write(self, message, file_only=False):
|
def sync_write(self, message, file_only=False):
|
||||||
with log_lock:
|
with log_lock:
|
||||||
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')[:-3]
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')[:-3]
|
||||||
if self.last_char != '\n':
|
if self.last_char != '\n':
|
||||||
log_file.write(message)
|
log_file.write(message)
|
||||||
else:
|
else:
|
||||||
@ -321,7 +321,7 @@ try:
|
|||||||
if is_start_mode:
|
if is_start_mode:
|
||||||
match = re.search(pat_import_fail, message)
|
match = re.search(pat_import_fail, message)
|
||||||
if match:
|
if match:
|
||||||
import_failed_extensions.add(match.group(1))
|
import_failed_extensions.add(match.group(1).strip())
|
||||||
|
|
||||||
if 'Starting server' in message:
|
if 'Starting server' in message:
|
||||||
is_start_mode = False
|
is_start_mode = False
|
||||||
@ -361,7 +361,7 @@ except:
|
|||||||
print(f"## [ERROR] ComfyUI-Manager: GitPython package seems to be installed, but failed to load somehow. Make sure you have a working git client installed")
|
print(f"## [ERROR] ComfyUI-Manager: GitPython package seems to be installed, but failed to load somehow. Make sure you have a working git client installed")
|
||||||
|
|
||||||
|
|
||||||
print("** ComfyUI startup time:", datetime.datetime.now())
|
print("** ComfyUI startup time:", datetime.now())
|
||||||
print("** Platform:", platform.system())
|
print("** Platform:", platform.system())
|
||||||
print("** Python version:", sys.version)
|
print("** Python version:", sys.version)
|
||||||
print("** Python executable:", sys.executable)
|
print("** Python executable:", sys.executable)
|
||||||
@ -507,49 +507,12 @@ if os.path.exists(restore_snapshot_path):
|
|||||||
print(prefix, msg, end="")
|
print(prefix, msg, end="")
|
||||||
|
|
||||||
print(f"[ComfyUI-Manager] Restore snapshot.")
|
print(f"[ComfyUI-Manager] Restore snapshot.")
|
||||||
cmd_str = [sys.executable, git_script_path, '--apply-snapshot', restore_snapshot_path]
|
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
new_env = os.environ.copy()
|
||||||
new_env["COMFYUI_PATH"] = comfy_path
|
new_env["COMFYUI_PATH"] = comfy_path
|
||||||
|
|
||||||
|
cmd_str = [sys.executable, cm_cli_path, 'restore-snapshot', restore_snapshot_path]
|
||||||
exit_code = process_wrap(cmd_str, custom_nodes_path, handler=msg_capture, env=new_env)
|
exit_code = process_wrap(cmd_str, custom_nodes_path, handler=msg_capture, env=new_env)
|
||||||
|
|
||||||
repository_name = ''
|
|
||||||
for url in cloned_repos:
|
|
||||||
try:
|
|
||||||
repository_name = url.split("/")[-1].strip()
|
|
||||||
repo_path = os.path.join(custom_nodes_path, repository_name)
|
|
||||||
repo_path = os.path.abspath(repo_path)
|
|
||||||
|
|
||||||
requirements_path = os.path.join(repo_path, 'requirements.txt')
|
|
||||||
install_script_path = os.path.join(repo_path, 'install.py')
|
|
||||||
|
|
||||||
this_exit_code = 0
|
|
||||||
|
|
||||||
if os.path.exists(requirements_path):
|
|
||||||
with open(requirements_path, 'r', encoding="UTF-8", errors="ignore") as file:
|
|
||||||
for line in file:
|
|
||||||
package_name = remap_pip_package(line.strip())
|
|
||||||
if package_name and not is_installed(package_name):
|
|
||||||
if not package_name.startswith('#'):
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", package_name]
|
|
||||||
this_exit_code += process_wrap(install_cmd, repo_path)
|
|
||||||
|
|
||||||
if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in processed_install:
|
|
||||||
processed_install.add(f'{repo_path}/install.py')
|
|
||||||
install_cmd = [sys.executable, install_script_path]
|
|
||||||
print(f">>> {install_cmd} / {repo_path}")
|
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
|
||||||
new_env["COMFYUI_PATH"] = comfy_path
|
|
||||||
this_exit_code += process_wrap(install_cmd, repo_path, env=new_env)
|
|
||||||
|
|
||||||
if this_exit_code != 0:
|
|
||||||
print(f"[ComfyUI-Manager] Restoring '{repository_name}' is failed.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print(f"[ComfyUI-Manager] Restoring '{repository_name}' is failed.")
|
|
||||||
|
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
print(f"[ComfyUI-Manager] Restore snapshot failed.")
|
print(f"[ComfyUI-Manager] Restore snapshot failed.")
|
||||||
else:
|
else:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user