wip: remove manager core code
This commit is contained in:
72
modules/manager_ext_core.py
Normal file
72
modules/manager_ext_core.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import os
|
||||
import sys
|
||||
import configparser
|
||||
import manager_core as core
|
||||
import cm_global
|
||||
from manager_util import *
|
||||
|
||||
|
||||
version_code = [3, 0]
|
||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
DEFAULT_CHANNEL = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main"
|
||||
|
||||
config_path = os.path.join(comfyui_manager_path, "config.ini")
|
||||
cached_config = None
|
||||
|
||||
def write_config():
|
||||
config = configparser.ConfigParser()
|
||||
config['default'] = {
|
||||
'share_option': get_config()['share_option'],
|
||||
"file_logging": get_config()['file_logging'],
|
||||
'default_ui': get_config()['default_ui'],
|
||||
'component_policy': get_config()['component_policy'],
|
||||
'double_click_policy': get_config()['double_click_policy'],
|
||||
'model_download_by_agent': get_config()['model_download_by_agent'],
|
||||
'security_level': get_config()['security_level'],
|
||||
}
|
||||
with open(config_path, 'w') as configfile:
|
||||
config.write(configfile)
|
||||
|
||||
|
||||
def read_config():
|
||||
try:
|
||||
config = configparser.ConfigParser()
|
||||
config.read(config_path)
|
||||
default_conf = config['default']
|
||||
|
||||
# policy migration: disable_unsecure_features -> security_level
|
||||
security_level = default_conf['security_level'] if 'security_level' in default_conf else 'normal'
|
||||
|
||||
return {
|
||||
'share_option': default_conf['share_option'] if 'share_option' in default_conf else 'all',
|
||||
'default_ui': default_conf['default_ui'] if 'default_ui' in default_conf else 'none',
|
||||
'component_policy': default_conf['component_policy'] if 'component_policy' in default_conf else 'workflow',
|
||||
'double_click_policy': default_conf['double_click_policy'] if 'double_click_policy' in default_conf else 'copy-all',
|
||||
'model_download_by_agent': default_conf['model_download_by_agent'].lower() == 'true' if 'model_download_by_agent' in default_conf else False,
|
||||
'security_level': security_level
|
||||
}
|
||||
|
||||
except Exception:
|
||||
return {
|
||||
'share_option': 'all',
|
||||
'default_ui': 'none',
|
||||
'component_policy': 'workflow',
|
||||
'double_click_policy': 'copy-all',
|
||||
'model_download_by_agent': False,
|
||||
'security_level': 'normal',
|
||||
}
|
||||
|
||||
|
||||
def get_config():
|
||||
global cached_config
|
||||
|
||||
if cached_config is None:
|
||||
cached_config = read_config()
|
||||
|
||||
return cached_config
|
||||
|
||||
|
||||
def pip_install(packages):
|
||||
install_cmd = ['#FORCE', sys.executable, "-m", "pip", "install", '-U'] + packages
|
||||
core.try_install_script('pip install via manager', '..', install_cmd)
|
||||
948
modules/manager_ext_server.py
Normal file
948
modules/manager_ext_server.py
Normal file
@@ -0,0 +1,948 @@
|
||||
import traceback
|
||||
|
||||
import folder_paths
|
||||
import locale
|
||||
import concurrent
|
||||
import nodes
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import git
|
||||
|
||||
from server import PromptServer
|
||||
import manager_core as core
|
||||
import cm_global
|
||||
|
||||
from . import manager_ext_core as ext_core
|
||||
from . import manager_ext_util
|
||||
|
||||
print(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
||||
|
||||
comfy_ui_hash = "-"
|
||||
comfyui_tag = None
|
||||
|
||||
SECURITY_MESSAGE_MIDDLE_OR_BELOW = f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||
SECURITY_MESSAGE_NORMAL_MINUS = 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.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||
SECURITY_MESSAGE_GENERAL = f"ERROR: This installation is not allowed in this security_level. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||
|
||||
routes = PromptServer.instance.routes
|
||||
|
||||
|
||||
def handle_stream(stream, prefix):
|
||||
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
||||
for msg in stream:
|
||||
if prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg):
|
||||
if msg.startswith('100%'):
|
||||
print('\r' + msg, end="", file=sys.stderr),
|
||||
else:
|
||||
print('\r' + msg[:-1], end="", file=sys.stderr),
|
||||
else:
|
||||
if prefix == '[!]':
|
||||
print(prefix, msg, end="", file=sys.stderr)
|
||||
else:
|
||||
print(prefix, msg, end="")
|
||||
|
||||
|
||||
from comfy.cli_args import args
|
||||
import latent_preview
|
||||
|
||||
|
||||
is_local_mode = args.listen.startswith('127.') or args.listen.startswith('local.')
|
||||
|
||||
|
||||
def is_allowed_security_level(level):
|
||||
if level == 'high':
|
||||
if is_local_mode:
|
||||
return core.get_config()['security_level'].lower() in ['weak', 'normal-']
|
||||
else:
|
||||
return core.get_config()['security_level'].lower() == 'weak'
|
||||
elif level == 'middle':
|
||||
return core.get_config()['security_level'].lower() in ['weak', 'normal', 'normal-']
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
async def get_risky_level(files):
|
||||
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')
|
||||
|
||||
all_urls = set()
|
||||
for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:
|
||||
all_urls.update(x['files'])
|
||||
|
||||
for x in files:
|
||||
if x not in all_urls:
|
||||
return "high"
|
||||
|
||||
return "middle"
|
||||
|
||||
|
||||
def get_current_preview_method(self):
|
||||
if args.preview_method == latent_preview.LatentPreviewMethod.Auto:
|
||||
return "auto"
|
||||
elif args.preview_method == latent_preview.LatentPreviewMethod.Latent2RGB:
|
||||
return "latent2rgb"
|
||||
elif args.preview_method == latent_preview.LatentPreviewMethod.TAESD:
|
||||
return "taesd"
|
||||
else:
|
||||
return "none"
|
||||
|
||||
|
||||
from manager_downloader import download_url
|
||||
|
||||
core.comfy_path = os.path.dirname(folder_paths.__file__)
|
||||
components_path = os.path.join(core.comfyui_manager_path, 'components')
|
||||
|
||||
|
||||
def set_preview_method(method):
|
||||
if method == 'auto':
|
||||
args.preview_method = latent_preview.LatentPreviewMethod.Auto
|
||||
elif method == 'latent2rgb':
|
||||
args.preview_method = latent_preview.LatentPreviewMethod.Latent2RGB
|
||||
elif method == 'taesd':
|
||||
args.preview_method = latent_preview.LatentPreviewMethod.TAESD
|
||||
else:
|
||||
args.preview_method = latent_preview.LatentPreviewMethod.NoPreviews
|
||||
|
||||
core.get_config()['preview_method'] = args.preview_method
|
||||
|
||||
|
||||
set_preview_method(core.get_config()['preview_method'])
|
||||
|
||||
|
||||
def set_default_ui_mode(mode):
|
||||
core.get_config()['default_ui'] = mode
|
||||
|
||||
|
||||
def set_component_policy(mode):
|
||||
core.get_config()['component_policy'] = mode
|
||||
|
||||
|
||||
def set_double_click_policy(mode):
|
||||
core.get_config()['double_click_policy'] = mode
|
||||
|
||||
|
||||
def setup_environment():
|
||||
git_exe = core.get_config()['git_exe']
|
||||
|
||||
if git_exe != '':
|
||||
git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=git_exe)
|
||||
|
||||
|
||||
setup_environment()
|
||||
|
||||
# Expand Server api
|
||||
|
||||
import server
|
||||
from aiohttp import web
|
||||
import aiohttp
|
||||
import json
|
||||
import zipfile
|
||||
import urllib.request
|
||||
|
||||
|
||||
def get_model_dir(data):
|
||||
if data['save_path'] != 'default':
|
||||
if '..' in data['save_path'] or data['save_path'].startswith('/'):
|
||||
print(f"[WARN] '{data['save_path']}' is not allowed path. So it will be saved into 'models/etc'.")
|
||||
base_model = os.path.join(folder_paths.models_dir, "etc")
|
||||
else:
|
||||
if data['save_path'].startswith("custom_nodes"):
|
||||
base_model = os.path.join(core.comfy_path, data['save_path'])
|
||||
else:
|
||||
base_model = os.path.join(folder_paths.models_dir, data['save_path'])
|
||||
else:
|
||||
model_type = data['type']
|
||||
if model_type == "checkpoints" or model_type == "checkpoint":
|
||||
base_model = folder_paths.folder_names_and_paths["checkpoints"][0][0]
|
||||
elif model_type == "unclip":
|
||||
base_model = folder_paths.folder_names_and_paths["checkpoints"][0][0]
|
||||
elif model_type == "clip":
|
||||
base_model = folder_paths.folder_names_and_paths["clip"][0][0]
|
||||
elif model_type == "VAE":
|
||||
base_model = folder_paths.folder_names_and_paths["vae"][0][0]
|
||||
elif model_type == "lora":
|
||||
base_model = folder_paths.folder_names_and_paths["loras"][0][0]
|
||||
elif model_type == "T2I-Adapter":
|
||||
base_model = folder_paths.folder_names_and_paths["controlnet"][0][0]
|
||||
elif model_type == "T2I-Style":
|
||||
base_model = folder_paths.folder_names_and_paths["controlnet"][0][0]
|
||||
elif model_type == "controlnet":
|
||||
base_model = folder_paths.folder_names_and_paths["controlnet"][0][0]
|
||||
elif model_type == "clip_vision":
|
||||
base_model = folder_paths.folder_names_and_paths["clip_vision"][0][0]
|
||||
elif model_type == "gligen":
|
||||
base_model = folder_paths.folder_names_and_paths["gligen"][0][0]
|
||||
elif model_type == "upscale":
|
||||
base_model = folder_paths.folder_names_and_paths["upscale_models"][0][0]
|
||||
elif model_type == "embeddings":
|
||||
base_model = folder_paths.folder_names_and_paths["embeddings"][0][0]
|
||||
elif model_type == "unet" or model_type == "diffusion_model":
|
||||
if folder_paths.folder_names_and_paths.get("diffusion_models"):
|
||||
base_model = folder_paths.folder_names_and_paths["diffusion_models"][0][1]
|
||||
else:
|
||||
print(f"[ComfyUI-Manager] Your ComfyUI is outdated version.")
|
||||
base_model = folder_paths.folder_names_and_paths["unet"][0][0] # outdated version
|
||||
else:
|
||||
base_model = os.path.join(folder_paths.models_dir, "etc")
|
||||
|
||||
return base_model
|
||||
|
||||
|
||||
def get_model_path(data):
|
||||
base_model = get_model_dir(data)
|
||||
return os.path.join(base_model, data['filename'])
|
||||
|
||||
|
||||
def check_state_of_git_node_pack(node_packs, do_fetch=False, do_update_check=True, do_update=False):
|
||||
if do_fetch:
|
||||
print("Start fetching...", end="")
|
||||
elif do_update:
|
||||
print("Start updating...", end="")
|
||||
elif do_update_check:
|
||||
print("Start update check...", end="")
|
||||
|
||||
def process_custom_node(item):
|
||||
core.check_state_of_git_node_pack_single(item, do_fetch, do_update_check, do_update)
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(4) as executor:
|
||||
for k, v in node_packs.items():
|
||||
if v.get('active_version') in ['unknown', 'nightly']:
|
||||
executor.submit(process_custom_node, v)
|
||||
|
||||
if do_fetch:
|
||||
print(f"\x1b[2K\rFetching done.")
|
||||
elif do_update:
|
||||
update_exists = any(item.get('updatable', False) for item in node_packs.values())
|
||||
if update_exists:
|
||||
print(f"\x1b[2K\rUpdate done.")
|
||||
else:
|
||||
print(f"\x1b[2K\rAll extensions are already up-to-date.")
|
||||
elif do_update_check:
|
||||
print(f"\x1b[2K\rUpdate check done.")
|
||||
|
||||
|
||||
def nickname_filter(json_obj):
|
||||
preemptions_map = {}
|
||||
|
||||
for k, x in json_obj.items():
|
||||
if 'preemptions' in x[1]:
|
||||
for y in x[1]['preemptions']:
|
||||
preemptions_map[y] = k
|
||||
elif k.endswith("/ComfyUI"):
|
||||
for y in x[0]:
|
||||
preemptions_map[y] = k
|
||||
|
||||
updates = {}
|
||||
for k, x in json_obj.items():
|
||||
removes = set()
|
||||
for y in x[0]:
|
||||
k2 = preemptions_map.get(y)
|
||||
if k2 is not None and k != k2:
|
||||
removes.add(y)
|
||||
|
||||
if len(removes) > 0:
|
||||
updates[k] = [y for y in x[0] if y not in removes]
|
||||
|
||||
for k, v in updates.items():
|
||||
json_obj[k][0] = v
|
||||
|
||||
return json_obj
|
||||
|
||||
|
||||
@routes.get("/customnode/getmappings")
|
||||
async def fetch_customnode_mappings(request):
|
||||
"""
|
||||
provide unified (node -> node pack) mapping list
|
||||
"""
|
||||
mode = request.rel_url.query["mode"]
|
||||
|
||||
nickname_mode = False
|
||||
if mode == "nickname":
|
||||
mode = "local"
|
||||
nickname_mode = True
|
||||
|
||||
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:
|
||||
json_obj = nickname_filter(json_obj)
|
||||
|
||||
all_nodes = set()
|
||||
patterns = []
|
||||
for k, x in json_obj.items():
|
||||
all_nodes.update(set(x[0]))
|
||||
|
||||
if 'nodename_pattern' in x[1]:
|
||||
patterns.append((x[1]['nodename_pattern'], x[0]))
|
||||
|
||||
missing_nodes = set(nodes.NODE_CLASS_MAPPINGS.keys()) - all_nodes
|
||||
|
||||
for x in missing_nodes:
|
||||
for pat, item in patterns:
|
||||
if re.match(pat, x):
|
||||
item.append(x)
|
||||
|
||||
return web.json_response(json_obj, content_type='application/json')
|
||||
|
||||
|
||||
@routes.get("/customnode/fetch_updates")
|
||||
async def fetch_updates(request):
|
||||
try:
|
||||
if request.rel_url.query["mode"] == "local":
|
||||
channel = 'local'
|
||||
else:
|
||||
channel = core.get_config()['channel_url']
|
||||
|
||||
await core.unified_manager.reload(request.rel_url.query["mode"])
|
||||
await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])
|
||||
|
||||
res = core.unified_manager.fetch_or_pull_git_repo(is_pull=False)
|
||||
|
||||
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=200)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@routes.get("/customnode/update_all")
|
||||
async def update_all(request):
|
||||
if not is_allowed_security_level('middle'):
|
||||
print(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||
return web.Response(status=403)
|
||||
|
||||
try:
|
||||
core.save_snapshot_with_postfix('autosave')
|
||||
|
||||
if request.rel_url.query["mode"] == "local":
|
||||
channel = 'local'
|
||||
else:
|
||||
channel = core.get_config()['channel_url']
|
||||
|
||||
await core.unified_manager.reload(request.rel_url.query["mode"])
|
||||
await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])
|
||||
|
||||
updated_cnr = []
|
||||
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 = core.unified_manager.fetch_or_pull_git_repo(is_pull=True)
|
||||
|
||||
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
|
||||
else:
|
||||
status = 201
|
||||
|
||||
print(f"\nDone.")
|
||||
return web.json_response(res, status=status, content_type='application/json')
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return web.Response(status=400)
|
||||
finally:
|
||||
core.clear_pip_cache()
|
||||
|
||||
|
||||
def convert_markdown_to_html(input_text):
|
||||
pattern_a = re.compile(r'\[a/([^]]+)\]\(([^)]+)\)')
|
||||
pattern_w = re.compile(r'\[w/([^]]+)\]')
|
||||
pattern_i = re.compile(r'\[i/([^]]+)\]')
|
||||
pattern_bold = re.compile(r'\*\*([^*]+)\*\*')
|
||||
pattern_white = re.compile(r'%%([^*]+)%%')
|
||||
|
||||
def replace_a(match):
|
||||
return f"<a href='{match.group(2)}' target='blank'>{match.group(1)}</a>"
|
||||
|
||||
def replace_w(match):
|
||||
return f"<p class='cm-warn-note'>{match.group(1)}</p>"
|
||||
|
||||
def replace_i(match):
|
||||
return f"<p class='cm-info-note'>{match.group(1)}</p>"
|
||||
|
||||
def replace_bold(match):
|
||||
return f"<B>{match.group(1)}</B>"
|
||||
|
||||
def replace_white(match):
|
||||
return f"<font color='white'>{match.group(1)}</font>"
|
||||
|
||||
input_text = input_text.replace('\\[', '[').replace('\\]', ']').replace('<', '<').replace('>', '>')
|
||||
|
||||
result_text = re.sub(pattern_a, replace_a, input_text)
|
||||
result_text = re.sub(pattern_w, replace_w, result_text)
|
||||
result_text = re.sub(pattern_i, replace_i, result_text)
|
||||
result_text = re.sub(pattern_bold, replace_bold, result_text)
|
||||
result_text = re.sub(pattern_white, replace_white, result_text)
|
||||
|
||||
return result_text.replace("\n", "<BR>")
|
||||
|
||||
|
||||
def populate_markdown(x):
|
||||
if 'description' in x:
|
||||
x['description'] = convert_markdown_to_html(manager_ext_util.sanitize_tag(x['description']))
|
||||
|
||||
if 'name' in x:
|
||||
x['name'] = manager_ext_util.sanitize_tag(x['name'])
|
||||
|
||||
if 'title' in x:
|
||||
x['title'] = manager_ext_util.sanitize_tag(x['title'])
|
||||
|
||||
|
||||
@routes.get("/customnode/getlist")
|
||||
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":
|
||||
skip_update = True
|
||||
else:
|
||||
skip_update = False
|
||||
|
||||
if request.rel_url.query["mode"] == "local":
|
||||
channel = 'local'
|
||||
else:
|
||||
channel = core.get_config()['channel_url']
|
||||
|
||||
node_packs = await core.get_unified_total_nodes(channel, request.rel_url.query["mode"])
|
||||
json_obj_github = core.get_data_by_mode(request.rel_url.query["mode"], 'github-stats.json', 'default')
|
||||
json_obj_extras = core.get_data_by_mode(request.rel_url.query["mode"], 'extras.json', 'default')
|
||||
|
||||
core.populate_github_stats(node_packs, await json_obj_github)
|
||||
core.populate_favorites(node_packs, await json_obj_extras)
|
||||
|
||||
check_state_of_git_node_pack(node_packs, False, do_update_check=not skip_update)
|
||||
|
||||
for v in node_packs.values():
|
||||
populate_markdown(v)
|
||||
|
||||
if channel != 'local':
|
||||
found = 'custom'
|
||||
|
||||
for name, url in core.get_channel_dict().items():
|
||||
if url == channel:
|
||||
found = name
|
||||
break
|
||||
|
||||
channel = found
|
||||
|
||||
result = dict(channel=channel, node_packs=node_packs)
|
||||
|
||||
return web.json_response(result, content_type='application/json')
|
||||
|
||||
|
||||
@routes.get("/customnode/alternatives")
|
||||
async def fetch_customnode_alternatives(request):
|
||||
alter_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json')
|
||||
|
||||
res = {}
|
||||
|
||||
for item in alter_json['items']:
|
||||
populate_markdown(item)
|
||||
res[item['id']] = item
|
||||
|
||||
res = core.map_to_unified_keys(res)
|
||||
|
||||
return web.json_response(res, content_type='application/json')
|
||||
|
||||
|
||||
def check_model_installed(json_obj):
|
||||
def process_model(item):
|
||||
model_path = get_model_path(item)
|
||||
item['installed'] = 'None'
|
||||
|
||||
if model_path is not None:
|
||||
if model_path.endswith('.zip'):
|
||||
if os.path.exists(model_path[:-4]):
|
||||
item['installed'] = 'True'
|
||||
else:
|
||||
item['installed'] = 'False'
|
||||
elif os.path.exists(model_path):
|
||||
item['installed'] = 'True'
|
||||
else:
|
||||
item['installed'] = 'False'
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(8) as executor:
|
||||
for item in json_obj['models']:
|
||||
executor.submit(process_model, item)
|
||||
|
||||
|
||||
@routes.get("/externalmodel/getlist")
|
||||
async def fetch_externalmodel_list(request):
|
||||
json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'model-list.json')
|
||||
|
||||
check_model_installed(json_obj)
|
||||
|
||||
for x in json_obj['models']:
|
||||
populate_markdown(x)
|
||||
|
||||
return web.json_response(json_obj, content_type='application/json')
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/snapshot/getlist")
|
||||
async def get_snapshot_list(request):
|
||||
snapshots_directory = os.path.join(core.comfyui_manager_path, 'snapshots')
|
||||
items = [f[:-5] for f in os.listdir(snapshots_directory) if f.endswith('.json')]
|
||||
items.sort(reverse=True)
|
||||
return web.json_response({'items': items}, content_type='application/json')
|
||||
|
||||
|
||||
@routes.get("/snapshot/remove")
|
||||
async def remove_snapshot(request):
|
||||
if not is_allowed_security_level('middle'):
|
||||
print(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||
return web.Response(status=403)
|
||||
|
||||
try:
|
||||
target = request.rel_url.query["target"]
|
||||
|
||||
path = os.path.join(core.comfyui_manager_path, 'snapshots', f"{target}.json")
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
return web.Response(status=200)
|
||||
except:
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@routes.get("/snapshot/get_current")
|
||||
async def get_current_snapshot_api(request):
|
||||
try:
|
||||
return web.json_response(core.get_current_snapshot(), content_type='application/json')
|
||||
except:
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
def unzip_install(files):
|
||||
temp_filename = 'manager-temp.zip'
|
||||
for url in files:
|
||||
if url.endswith("/"):
|
||||
url = url[:-1]
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
|
||||
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
response = urllib.request.urlopen(req)
|
||||
data = response.read()
|
||||
|
||||
with open(temp_filename, 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
with zipfile.ZipFile(temp_filename, 'r') as zip_ref:
|
||||
zip_ref.extractall(core.custom_nodes_path)
|
||||
|
||||
os.remove(temp_filename)
|
||||
except Exception as e:
|
||||
print(f"Install(unzip) error: {url} / {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
print("Installation was successful.")
|
||||
return True
|
||||
|
||||
|
||||
def download_url_with_agent(url, save_path):
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
|
||||
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
response = urllib.request.urlopen(req)
|
||||
data = response.read()
|
||||
|
||||
if not os.path.exists(os.path.dirname(save_path)):
|
||||
os.makedirs(os.path.dirname(save_path))
|
||||
|
||||
with open(save_path, 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Download error: {url} / {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
print("Installation was successful.")
|
||||
return True
|
||||
|
||||
|
||||
@routes.post("/customnode/install/git_url")
|
||||
async def install_custom_node_git_url(request):
|
||||
if not is_allowed_security_level('high'):
|
||||
print(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||
return web.Response(status=403)
|
||||
|
||||
url = await request.text()
|
||||
res = await core.gitclone_install(url)
|
||||
|
||||
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.")
|
||||
return web.Response(status=200)
|
||||
|
||||
print(res.msg)
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@routes.post("/customnode/install/pip")
|
||||
async def install_pip(request):
|
||||
if not is_allowed_security_level('high'):
|
||||
print(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||
return web.Response(status=403)
|
||||
|
||||
packages = await request.text()
|
||||
core.pip_install(packages.split(' '))
|
||||
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@routes.get("/comfyui_manager/update_comfyui")
|
||||
async def update_comfyui(request):
|
||||
print(f"Update ComfyUI")
|
||||
|
||||
try:
|
||||
repo_path = os.path.dirname(folder_paths.__file__)
|
||||
res = core.update_path(repo_path)
|
||||
if res == "fail":
|
||||
print(f"ComfyUI update fail: The installed ComfyUI does not have a Git repository.")
|
||||
return web.Response(status=400)
|
||||
elif res == "updated":
|
||||
return web.Response(status=201)
|
||||
else: # skipped
|
||||
return web.Response(status=200)
|
||||
except Exception as e:
|
||||
print(f"ComfyUI update fail: {e}", file=sys.stderr)
|
||||
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@routes.get("/comfyui_manager/comfyui_switch_version")
|
||||
async def comfyui_switch_version(request):
|
||||
try:
|
||||
if "ver" in request.rel_url.query:
|
||||
core.switch_comfyui(request.rel_url.query['ver'])
|
||||
|
||||
return web.Response(status=200)
|
||||
except Exception as e:
|
||||
print(f"ComfyUI update fail: {e}", file=sys.stderr)
|
||||
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@routes.post("/model/install")
|
||||
async def install_model(request):
|
||||
json_data = await request.json()
|
||||
|
||||
model_path = get_model_path(json_data)
|
||||
|
||||
if not is_allowed_security_level('middle'):
|
||||
print(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||
return web.Response(status=403)
|
||||
|
||||
if not json_data['filename'].endswith('.safetensors') and not is_allowed_security_level('high'):
|
||||
models_json = await core.get_data_by_mode('cache', 'model-list.json')
|
||||
|
||||
is_belongs_to_whitelist = False
|
||||
for x in models_json['models']:
|
||||
if x.get('url') == json_data['url']:
|
||||
is_belongs_to_whitelist = True
|
||||
break
|
||||
|
||||
if not is_belongs_to_whitelist:
|
||||
print(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||
return web.Response(status=403)
|
||||
|
||||
res = False
|
||||
|
||||
try:
|
||||
if model_path is not None:
|
||||
print(f"Install model '{json_data['name']}' into '{model_path}'")
|
||||
|
||||
model_url = json_data['url']
|
||||
if not core.get_config()['model_download_by_agent'] and (
|
||||
model_url.startswith('https://github.com') or model_url.startswith('https://huggingface.co') or model_url.startswith('https://heibox.uni-heidelberg.de')):
|
||||
model_dir = get_model_dir(json_data)
|
||||
download_url(model_url, model_dir, filename=json_data['filename'])
|
||||
if model_path.endswith('.zip'):
|
||||
res = core.unzip(model_path)
|
||||
else:
|
||||
res = True
|
||||
|
||||
if res:
|
||||
return web.json_response({}, content_type='application/json')
|
||||
else:
|
||||
res = download_url_with_agent(model_url, model_path)
|
||||
if res and model_path.endswith('.zip'):
|
||||
res = core.unzip(model_path)
|
||||
else:
|
||||
print(f"Model installation error: invalid model type - {json_data['type']}")
|
||||
|
||||
if res:
|
||||
return web.json_response({}, content_type='application/json')
|
||||
except Exception as e:
|
||||
print(f"[ERROR] {e}", file=sys.stderr)
|
||||
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
class ManagerTerminalHook:
|
||||
def write_stderr(self, msg):
|
||||
PromptServer.instance.send_sync("manager-terminal-feedback", {"data": msg})
|
||||
|
||||
def write_stdout(self, msg):
|
||||
PromptServer.instance.send_sync("manager-terminal-feedback", {"data": msg})
|
||||
|
||||
|
||||
manager_terminal_hook = ManagerTerminalHook()
|
||||
|
||||
|
||||
@routes.get("/manager/terminal")
|
||||
async def terminal_mode(request):
|
||||
if not is_allowed_security_level('high'):
|
||||
print(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||
return web.Response(status=403)
|
||||
|
||||
if "mode" in request.rel_url.query:
|
||||
if request.rel_url.query['mode'] == 'true':
|
||||
sys.__comfyui_manager_terminal_hook.add_hook('cm', manager_terminal_hook)
|
||||
else:
|
||||
sys.__comfyui_manager_terminal_hook.remove_hook('cm')
|
||||
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@routes.get("/manager/preview_method")
|
||||
async def preview_method(request):
|
||||
if "value" in request.rel_url.query:
|
||||
set_preview_method(request.rel_url.query['value'])
|
||||
core.write_config()
|
||||
else:
|
||||
return web.Response(text=core.manager_funcs.get_current_preview_method(), status=200)
|
||||
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@routes.get("/manager/default_ui")
|
||||
async def default_ui_mode(request):
|
||||
if "value" in request.rel_url.query:
|
||||
set_default_ui_mode(request.rel_url.query['value'])
|
||||
core.write_config()
|
||||
else:
|
||||
return web.Response(text=core.get_config()['default_ui'], status=200)
|
||||
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@routes.get("/manager/component/policy")
|
||||
async def component_policy(request):
|
||||
if "value" in request.rel_url.query:
|
||||
set_component_policy(request.rel_url.query['value'])
|
||||
core.write_config()
|
||||
else:
|
||||
return web.Response(text=core.get_config()['component_policy'], status=200)
|
||||
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@routes.get("/manager/dbl_click/policy")
|
||||
async def dbl_click_policy(request):
|
||||
if "value" in request.rel_url.query:
|
||||
set_double_click_policy(request.rel_url.query['value'])
|
||||
core.write_config()
|
||||
else:
|
||||
return web.Response(text=core.get_config()['double_click_policy'], status=200)
|
||||
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@routes.get("/manager/channel_url_list")
|
||||
async def channel_url_list(request):
|
||||
channels = core.get_channel_dict()
|
||||
if "value" in request.rel_url.query:
|
||||
channel_url = channels.get(request.rel_url.query['value'])
|
||||
if channel_url is not None:
|
||||
core.get_config()['channel_url'] = channel_url
|
||||
core.write_config()
|
||||
else:
|
||||
selected = 'custom'
|
||||
selected_url = core.get_config()['channel_url']
|
||||
|
||||
for name, url in channels.items():
|
||||
if url == selected_url:
|
||||
selected = name
|
||||
break
|
||||
|
||||
res = {'selected': selected,
|
||||
'list': core.get_channel_list()}
|
||||
return web.json_response(res, status=200)
|
||||
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
def add_target_blank(html_text):
|
||||
pattern = r'(<a\s+href="[^"]*"\s*[^>]*)(>)'
|
||||
|
||||
def add_target(match):
|
||||
if 'target=' not in match.group(1):
|
||||
return match.group(1) + ' target="_blank"' + match.group(2)
|
||||
return match.group(0)
|
||||
|
||||
modified_html = re.sub(pattern, add_target, html_text)
|
||||
|
||||
return modified_html
|
||||
|
||||
|
||||
@routes.get("/manager/notice")
|
||||
async def get_notice(request):
|
||||
url = "github.com"
|
||||
path = "/ltdrdata/ltdrdata.github.io/wiki/News"
|
||||
|
||||
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||
async with session.get(f"https://{url}{path}") as response:
|
||||
if response.status == 200:
|
||||
# html_content = response.read().decode('utf-8')
|
||||
html_content = await response.text()
|
||||
|
||||
pattern = re.compile(r'<div class="markdown-body">([\s\S]*?)</div>')
|
||||
match = pattern.search(html_content)
|
||||
|
||||
if match:
|
||||
markdown_content = match.group(1)
|
||||
if comfyui_tag:
|
||||
markdown_content += f"<HR>ComfyUI: {comfyui_tag}<BR>Commit Date: {core.comfy_ui_commit_datetime.date()}"
|
||||
else:
|
||||
markdown_content += f"<HR>ComfyUI: {core.comfy_ui_revision}[{comfy_ui_hash[:6]}]({core.comfy_ui_commit_datetime.date()})"
|
||||
# markdown_content += f"<BR> ()"
|
||||
markdown_content += f"<BR>Manager: {core.version_str}"
|
||||
|
||||
markdown_content = add_target_blank(markdown_content)
|
||||
|
||||
try:
|
||||
if core.comfy_ui_required_commit_datetime.date() > core.comfy_ui_commit_datetime.date():
|
||||
markdown_content = f'<P style="text-align: center; color:red; background-color:white; font-weight:bold">Your ComfyUI is too OUTDATED!!!</P>' + markdown_content
|
||||
except:
|
||||
pass
|
||||
|
||||
return web.Response(text=markdown_content, status=200)
|
||||
else:
|
||||
return web.Response(text="Unable to retrieve Notice", status=200)
|
||||
else:
|
||||
return web.Response(text="Unable to retrieve Notice", status=200)
|
||||
|
||||
|
||||
@routes.get("/manager/reboot")
|
||||
def restart(self):
|
||||
if not is_allowed_security_level('middle'):
|
||||
print(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
||||
return web.Response(status=403)
|
||||
|
||||
try:
|
||||
sys.stdout.close_log()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if '__COMFY_CLI_SESSION__' in os.environ:
|
||||
with open(os.path.join(os.environ['__COMFY_CLI_SESSION__'] + '.reboot'), 'w') as file:
|
||||
pass
|
||||
|
||||
print(f"\nRestarting...\n\n")
|
||||
exit(0)
|
||||
|
||||
print(f"\nRestarting... [Legacy Mode]\n\n")
|
||||
if sys.platform.startswith('win32'):
|
||||
return os.execv(sys.executable, ['"' + sys.executable + '"', '"' + sys.argv[0] + '"'] + sys.argv[1:])
|
||||
else:
|
||||
return os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||
|
||||
|
||||
def sanitize_filename(input_string):
|
||||
result_string = re.sub(r'[^a-zA-Z0-9_]', '_', input_string)
|
||||
return result_string
|
||||
|
||||
|
||||
@routes.post("/manager/component/save")
|
||||
async def save_component(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
name = data['name']
|
||||
workflow = data['workflow']
|
||||
|
||||
if not os.path.exists(components_path):
|
||||
os.mkdir(components_path)
|
||||
|
||||
if 'packname' in workflow and workflow['packname'] != '':
|
||||
sanitized_name = sanitize_filename(workflow['packname']) + '.pack'
|
||||
else:
|
||||
sanitized_name = sanitize_filename(name) + '.json'
|
||||
|
||||
filepath = os.path.join(components_path, sanitized_name)
|
||||
components = {}
|
||||
if os.path.exists(filepath):
|
||||
with open(filepath) as f:
|
||||
components = json.load(f)
|
||||
|
||||
components[name] = workflow
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
json.dump(components, f, indent=4, sort_keys=True)
|
||||
return web.Response(text=filepath, status=200)
|
||||
except:
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@routes.post("/manager/component/loads")
|
||||
async def load_components(request):
|
||||
try:
|
||||
json_files = [f for f in os.listdir(components_path) if f.endswith('.json')]
|
||||
pack_files = [f for f in os.listdir(components_path) if f.endswith('.pack')]
|
||||
|
||||
components = {}
|
||||
for json_file in json_files + pack_files:
|
||||
file_path = os.path.join(components_path, json_file)
|
||||
with open(file_path, 'r') as file:
|
||||
try:
|
||||
# When there is a conflict between the .pack and the .json, the pack takes precedence and overrides.
|
||||
components.update(json.load(file))
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[ComfyUI-Manager] Error decoding component file in file {json_file}: {e}")
|
||||
|
||||
return web.json_response(components)
|
||||
except Exception as e:
|
||||
print(f"[ComfyUI-Manager] failed to load components\n{e}")
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
args.enable_cors_header = "*"
|
||||
if hasattr(PromptServer.instance, "app"):
|
||||
app = PromptServer.instance.app
|
||||
cors_middleware = server.create_cors_middleware(args.enable_cors_header)
|
||||
app.middlewares.append(cors_middleware)
|
||||
|
||||
|
||||
def sanitize(data):
|
||||
return data.replace("<", "<").replace(">", ">")
|
||||
|
||||
if not os.path.exists(core.config_path):
|
||||
core.get_config()
|
||||
core.write_config()
|
||||
|
||||
cm_global.register_extension('ComfyUI-Manager',
|
||||
{'version': core.version,
|
||||
'name': 'ComfyUI Manager (Extension)',
|
||||
'nodes': {'Terminal Log //CM'},
|
||||
'description': 'ComfyUI-Manager (Extension)', })
|
||||
|
||||
cm_global.variables['manager-core.show_menu'] = False
|
||||
174
modules/manager_ext_util.py
Normal file
174
modules/manager_ext_util.py
Normal file
@@ -0,0 +1,174 @@
|
||||
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')
|
||||
|
||||
|
||||
# DON'T USE StrictVersion - cannot handle pre_release version
|
||||
# try:
|
||||
# from distutils.version import StrictVersion
|
||||
# except:
|
||||
# print(f"[ComfyUI-Manager] 'distutils' package not found. Activating fallback mode for compatibility.")
|
||||
class StrictVersion:
|
||||
def __init__(self, version_string):
|
||||
self.version_string = version_string
|
||||
self.major = 0
|
||||
self.minor = 0
|
||||
self.patch = 0
|
||||
self.pre_release = None
|
||||
self.parse_version_string()
|
||||
|
||||
def parse_version_string(self):
|
||||
parts = self.version_string.split('.')
|
||||
if not parts:
|
||||
raise ValueError("Version string must not be empty")
|
||||
|
||||
self.major = int(parts[0])
|
||||
self.minor = int(parts[1]) if len(parts) > 1 else 0
|
||||
self.patch = int(parts[2]) if len(parts) > 2 else 0
|
||||
|
||||
# Handling pre-release versions if present
|
||||
if len(parts) > 3:
|
||||
self.pre_release = parts[3]
|
||||
|
||||
def __str__(self):
|
||||
version = f"{self.major}.{self.minor}.{self.patch}"
|
||||
if self.pre_release:
|
||||
version += f"-{self.pre_release}"
|
||||
return version
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.major, self.minor, self.patch, self.pre_release) == \
|
||||
(other.major, other.minor, other.patch, other.pre_release)
|
||||
|
||||
def __lt__(self, other):
|
||||
if (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch):
|
||||
return self.pre_release_compare(self.pre_release, other.pre_release) < 0
|
||||
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
|
||||
|
||||
@staticmethod
|
||||
def pre_release_compare(pre1, pre2):
|
||||
if pre1 == pre2:
|
||||
return 0
|
||||
if pre1 is None:
|
||||
return 1
|
||||
if pre2 is None:
|
||||
return -1
|
||||
return -1 if pre1 < pre2 else 1
|
||||
|
||||
def __le__(self, other):
|
||||
return self == other or self < other
|
||||
|
||||
def __gt__(self, other):
|
||||
return not self <= other
|
||||
|
||||
def __ge__(self, other):
|
||||
return not self < other
|
||||
|
||||
def __ne__(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('>', '>')
|
||||
|
||||
|
||||
def download_url(url, dest_folder, filename):
|
||||
import requests
|
||||
|
||||
# Ensure the destination folder exists
|
||||
if not os.path.exists(dest_folder):
|
||||
os.makedirs(dest_folder)
|
||||
|
||||
# Full path to save the file
|
||||
dest_path = os.path.join(dest_folder, filename)
|
||||
|
||||
# Download the file
|
||||
response = requests.get(url, stream=True)
|
||||
if response.status_code == 200:
|
||||
with open(dest_path, 'wb') as file:
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
if chunk:
|
||||
file.write(chunk)
|
||||
else:
|
||||
raise Exception(f"Failed to download file from {url}")
|
||||
|
||||
|
||||
def extract_package_as_zip(file_path, extract_path):
|
||||
import zipfile
|
||||
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
|
||||
386
modules/share_3rdparty.py
Normal file
386
modules/share_3rdparty.py
Normal file
@@ -0,0 +1,386 @@
|
||||
import mimetypes
|
||||
import manager_core as core
|
||||
import os
|
||||
from aiohttp import web
|
||||
import aiohttp
|
||||
import json
|
||||
import hashlib
|
||||
|
||||
import folder_paths
|
||||
from server import PromptServer
|
||||
|
||||
|
||||
def extract_model_file_names(json_data):
|
||||
"""Extract unique file names from the input JSON data."""
|
||||
file_names = set()
|
||||
model_filename_extensions = {'.safetensors', '.ckpt', '.pt', '.pth', '.bin'}
|
||||
|
||||
# Recursively search for file names in the JSON data
|
||||
def recursive_search(data):
|
||||
if isinstance(data, dict):
|
||||
for value in data.values():
|
||||
recursive_search(value)
|
||||
elif isinstance(data, list):
|
||||
for item in data:
|
||||
recursive_search(item)
|
||||
elif isinstance(data, str) and '.' in data:
|
||||
file_names.add(os.path.basename(data)) # file_names.add(data)
|
||||
|
||||
recursive_search(json_data)
|
||||
return [f for f in list(file_names) if os.path.splitext(f)[1] in model_filename_extensions]
|
||||
|
||||
|
||||
def find_file_paths(base_dir, file_names):
|
||||
"""Find the paths of the files in the base directory."""
|
||||
file_paths = {}
|
||||
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
# Exclude certain directories
|
||||
dirs[:] = [d for d in dirs if d not in ['.git']]
|
||||
|
||||
for file in files:
|
||||
if file in file_names:
|
||||
file_paths[file] = os.path.join(root, file)
|
||||
return file_paths
|
||||
|
||||
|
||||
def compute_sha256_checksum(filepath):
|
||||
"""Compute the SHA256 checksum of a file, in chunks"""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(4096), b''):
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/manager/share_option")
|
||||
async def share_option(request):
|
||||
if "value" in request.rel_url.query:
|
||||
core.get_config()['share_option'] = request.rel_url.query['value']
|
||||
core.write_config()
|
||||
else:
|
||||
return web.Response(text=core.get_config()['share_option'], status=200)
|
||||
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
def get_openart_auth():
|
||||
if not os.path.exists(os.path.join(core.comfyui_manager_path, ".openart_key")):
|
||||
return None
|
||||
try:
|
||||
with open(os.path.join(core.comfyui_manager_path, ".openart_key"), "r") as f:
|
||||
openart_key = f.read().strip()
|
||||
return openart_key if openart_key else None
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def get_matrix_auth():
|
||||
if not os.path.exists(os.path.join(core.comfyui_manager_path, "matrix_auth")):
|
||||
return None
|
||||
try:
|
||||
with open(os.path.join(core.comfyui_manager_path, "matrix_auth"), "r") as f:
|
||||
matrix_auth = f.read()
|
||||
homeserver, username, password = matrix_auth.strip().split("\n")
|
||||
if not homeserver or not username or not password:
|
||||
return None
|
||||
return {
|
||||
"homeserver": homeserver,
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def get_comfyworkflows_auth():
|
||||
if not os.path.exists(os.path.join(core.comfyui_manager_path, "comfyworkflows_sharekey")):
|
||||
return None
|
||||
try:
|
||||
with open(os.path.join(core.comfyui_manager_path, "comfyworkflows_sharekey"), "r") as f:
|
||||
share_key = f.read()
|
||||
if not share_key.strip():
|
||||
return None
|
||||
return share_key
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def get_youml_settings():
|
||||
if not os.path.exists(os.path.join(core.comfyui_manager_path, ".youml")):
|
||||
return None
|
||||
try:
|
||||
with open(os.path.join(core.comfyui_manager_path, ".youml"), "r") as f:
|
||||
youml_settings = f.read().strip()
|
||||
return youml_settings if youml_settings else None
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def set_youml_settings(settings):
|
||||
with open(os.path.join(core.comfyui_manager_path, ".youml"), "w") as f:
|
||||
f.write(settings)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/manager/get_openart_auth")
|
||||
async def api_get_openart_auth(request):
|
||||
# print("Getting stored Matrix credentials...")
|
||||
openart_key = get_openart_auth()
|
||||
if not openart_key:
|
||||
return web.Response(status=404)
|
||||
return web.json_response({"openart_key": openart_key})
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/manager/set_openart_auth")
|
||||
async def api_set_openart_auth(request):
|
||||
json_data = await request.json()
|
||||
openart_key = json_data['openart_key']
|
||||
with open(os.path.join(core.comfyui_manager_path, ".openart_key"), "w") as f:
|
||||
f.write(openart_key)
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/manager/get_matrix_auth")
|
||||
async def api_get_matrix_auth(request):
|
||||
# print("Getting stored Matrix credentials...")
|
||||
matrix_auth = get_matrix_auth()
|
||||
if not matrix_auth:
|
||||
return web.Response(status=404)
|
||||
return web.json_response(matrix_auth)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/manager/youml/settings")
|
||||
async def api_get_youml_settings(request):
|
||||
youml_settings = get_youml_settings()
|
||||
if not youml_settings:
|
||||
return web.Response(status=404)
|
||||
return web.json_response(json.loads(youml_settings))
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/manager/youml/settings")
|
||||
async def api_set_youml_settings(request):
|
||||
json_data = await request.json()
|
||||
set_youml_settings(json.dumps(json_data))
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/manager/get_comfyworkflows_auth")
|
||||
async def api_get_comfyworkflows_auth(request):
|
||||
# Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken'
|
||||
# in the same directory as the ComfyUI base folder
|
||||
# print("Getting stored Comfyworkflows.com auth...")
|
||||
comfyworkflows_auth = get_comfyworkflows_auth()
|
||||
if not comfyworkflows_auth:
|
||||
return web.Response(status=404)
|
||||
return web.json_response({"comfyworkflows_sharekey": comfyworkflows_auth})
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/manager/set_esheep_workflow_and_images")
|
||||
async def set_esheep_workflow_and_images(request):
|
||||
json_data = await request.json()
|
||||
current_workflow = json_data['workflow']
|
||||
images = json_data['images']
|
||||
with open(os.path.join(core.comfyui_manager_path, "esheep_share_message.json"), "w", encoding='utf-8') as file:
|
||||
json.dump(json_data, file, indent=4)
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/manager/get_esheep_workflow_and_images")
|
||||
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:
|
||||
data = json.load(file)
|
||||
return web.Response(status=200, text=json.dumps(data))
|
||||
|
||||
|
||||
def set_matrix_auth(json_data):
|
||||
homeserver = json_data['homeserver']
|
||||
username = json_data['username']
|
||||
password = json_data['password']
|
||||
with open(os.path.join(core.comfyui_manager_path, "matrix_auth"), "w") as f:
|
||||
f.write("\n".join([homeserver, username, password]))
|
||||
|
||||
|
||||
def set_comfyworkflows_auth(comfyworkflows_sharekey):
|
||||
with open(os.path.join(core.comfyui_manager_path, "comfyworkflows_sharekey"), "w") as f:
|
||||
f.write(comfyworkflows_sharekey)
|
||||
|
||||
|
||||
def has_provided_matrix_auth(matrix_auth):
|
||||
return matrix_auth['homeserver'].strip() and matrix_auth['username'].strip() and matrix_auth['password'].strip()
|
||||
|
||||
|
||||
def has_provided_comfyworkflows_auth(comfyworkflows_sharekey):
|
||||
return comfyworkflows_sharekey.strip()
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/manager/share")
|
||||
async def share_art(request):
|
||||
# get json data
|
||||
json_data = await request.json()
|
||||
|
||||
matrix_auth = json_data['matrix_auth']
|
||||
comfyworkflows_sharekey = json_data['cw_auth']['cw_sharekey']
|
||||
|
||||
set_matrix_auth(matrix_auth)
|
||||
set_comfyworkflows_auth(comfyworkflows_sharekey)
|
||||
|
||||
share_destinations = json_data['share_destinations']
|
||||
credits = json_data['credits']
|
||||
title = json_data['title']
|
||||
description = json_data['description']
|
||||
is_nsfw = json_data['is_nsfw']
|
||||
prompt = json_data['prompt']
|
||||
potential_outputs = json_data['potential_outputs']
|
||||
selected_output_index = json_data['selected_output_index']
|
||||
|
||||
try:
|
||||
output_to_share = potential_outputs[int(selected_output_index)]
|
||||
except:
|
||||
# for now, pick the first output
|
||||
output_to_share = potential_outputs[0]
|
||||
|
||||
assert output_to_share['type'] in ('image', 'output')
|
||||
output_dir = folder_paths.get_output_directory()
|
||||
|
||||
if output_to_share['type'] == 'image':
|
||||
asset_filename = output_to_share['image']['filename']
|
||||
asset_subfolder = output_to_share['image']['subfolder']
|
||||
|
||||
if output_to_share['image']['type'] == 'temp':
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
else:
|
||||
asset_filename = output_to_share['output']['filename']
|
||||
asset_subfolder = output_to_share['output']['subfolder']
|
||||
|
||||
if asset_subfolder:
|
||||
asset_filepath = os.path.join(output_dir, asset_subfolder, asset_filename)
|
||||
else:
|
||||
asset_filepath = os.path.join(output_dir, asset_filename)
|
||||
|
||||
# get the mime type of the asset
|
||||
assetFileType = mimetypes.guess_type(asset_filepath)[0]
|
||||
|
||||
share_website_host = "UNKNOWN"
|
||||
if "comfyworkflows" in share_destinations:
|
||||
share_website_host = "https://comfyworkflows.com"
|
||||
share_endpoint = f"{share_website_host}/api"
|
||||
|
||||
# get presigned urls
|
||||
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||
async with session.post(
|
||||
f"{share_endpoint}/get_presigned_urls",
|
||||
json={
|
||||
"assetFileName": asset_filename,
|
||||
"assetFileType": assetFileType,
|
||||
"workflowJsonFileName": 'workflow.json',
|
||||
"workflowJsonFileType": 'application/json',
|
||||
},
|
||||
) as resp:
|
||||
assert resp.status == 200
|
||||
presigned_urls_json = await resp.json()
|
||||
assetFilePresignedUrl = presigned_urls_json["assetFilePresignedUrl"]
|
||||
assetFileKey = presigned_urls_json["assetFileKey"]
|
||||
workflowJsonFilePresignedUrl = presigned_urls_json["workflowJsonFilePresignedUrl"]
|
||||
workflowJsonFileKey = presigned_urls_json["workflowJsonFileKey"]
|
||||
|
||||
# upload asset
|
||||
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||
async with session.put(assetFilePresignedUrl, data=open(asset_filepath, "rb")) as resp:
|
||||
assert resp.status == 200
|
||||
|
||||
# upload workflow json
|
||||
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||
async with session.put(workflowJsonFilePresignedUrl, data=json.dumps(prompt['workflow']).encode('utf-8')) as resp:
|
||||
assert resp.status == 200
|
||||
|
||||
model_filenames = extract_model_file_names(prompt['workflow'])
|
||||
model_file_paths = find_file_paths(folder_paths.base_path, model_filenames)
|
||||
|
||||
models_info = {}
|
||||
for filename, filepath in model_file_paths.items():
|
||||
models_info[filename] = {
|
||||
"filename": filename,
|
||||
"sha256_checksum": compute_sha256_checksum(filepath),
|
||||
"relative_path": os.path.relpath(filepath, folder_paths.base_path),
|
||||
}
|
||||
|
||||
# make a POST request to /api/upload_workflow with form data key values
|
||||
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||
form = aiohttp.FormData()
|
||||
if comfyworkflows_sharekey:
|
||||
form.add_field("shareKey", comfyworkflows_sharekey)
|
||||
form.add_field("source", "comfyui_manager")
|
||||
form.add_field("assetFileKey", assetFileKey)
|
||||
form.add_field("assetFileType", assetFileType)
|
||||
form.add_field("workflowJsonFileKey", workflowJsonFileKey)
|
||||
form.add_field("sharedWorkflowWorkflowJsonString", json.dumps(prompt['workflow']))
|
||||
form.add_field("sharedWorkflowPromptJsonString", json.dumps(prompt['output']))
|
||||
form.add_field("shareWorkflowCredits", credits)
|
||||
form.add_field("shareWorkflowTitle", title)
|
||||
form.add_field("shareWorkflowDescription", description)
|
||||
form.add_field("shareWorkflowIsNSFW", str(is_nsfw).lower())
|
||||
form.add_field("currentSnapshot", json.dumps(core.get_current_snapshot()))
|
||||
form.add_field("modelsInfo", json.dumps(models_info))
|
||||
|
||||
async with session.post(
|
||||
f"{share_endpoint}/upload_workflow",
|
||||
data=form,
|
||||
) as resp:
|
||||
assert resp.status == 200
|
||||
upload_workflow_json = await resp.json()
|
||||
workflowId = upload_workflow_json["workflowId"]
|
||||
|
||||
# check if the user has provided Matrix credentials
|
||||
if "matrix" in share_destinations:
|
||||
comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org'
|
||||
filename = os.path.basename(asset_filepath)
|
||||
content_type = assetFileType
|
||||
|
||||
try:
|
||||
from matrix_client.api import MatrixHttpApi
|
||||
from matrix_client.client import MatrixClient
|
||||
|
||||
homeserver = 'matrix.org'
|
||||
if matrix_auth:
|
||||
homeserver = matrix_auth.get('homeserver', 'matrix.org')
|
||||
homeserver = homeserver.replace("http://", "https://")
|
||||
if not homeserver.startswith("https://"):
|
||||
homeserver = "https://" + homeserver
|
||||
|
||||
client = MatrixClient(homeserver)
|
||||
try:
|
||||
token = client.login(username=matrix_auth['username'], password=matrix_auth['password'])
|
||||
if not token:
|
||||
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
||||
except:
|
||||
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
||||
|
||||
matrix = MatrixHttpApi(homeserver, token=token)
|
||||
with open(asset_filepath, 'rb') as f:
|
||||
mxc_url = matrix.media_upload(f.read(), content_type, filename=filename)['content_uri']
|
||||
|
||||
workflow_json_mxc_url = matrix.media_upload(prompt['workflow'], 'application/json', filename='workflow.json')['content_uri']
|
||||
|
||||
text_content = ""
|
||||
if title:
|
||||
text_content += f"{title}\n"
|
||||
if description:
|
||||
text_content += f"{description}\n"
|
||||
if credits:
|
||||
text_content += f"\ncredits: {credits}\n"
|
||||
response = matrix.send_message(comfyui_share_room_id, text_content)
|
||||
response = matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image')
|
||||
response = matrix.send_content(comfyui_share_room_id, workflow_json_mxc_url, 'workflow.json', 'm.file')
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return web.json_response({"error": "An error occurred when sharing your art to Matrix."}, content_type='application/json', status=500)
|
||||
|
||||
return web.json_response({
|
||||
"comfyworkflows": {
|
||||
"url": None if "comfyworkflows" not in share_destinations else f"{share_website_host}/workflows/{workflowId}",
|
||||
},
|
||||
"matrix": {
|
||||
"success": None if "matrix" not in share_destinations else True
|
||||
}
|
||||
}, content_type='application/json', status=200)
|
||||
Reference in New Issue
Block a user