fred-dev/comfy_ui_ali
0
1import traceback2 3import folder_paths4import locale5import subprocess # don't remove this6import concurrent7import nodes8import os9import sys10import threading11import re12import shutil13import git14from datetime import datetime15 16from server import PromptServer17import manager_core as core18import manager_util19import cm_global20import logging21import asyncio22import queue23 24import manager_downloader25 26 27logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")28logging.info("[ComfyUI-Manager] network_mode: " + core.get_config()['network_mode'])29 30comfy_ui_hash = "-"31comfyui_tag = None32 33SECURITY_MESSAGE_MIDDLE_OR_BELOW = "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"34SECURITY_MESSAGE_NORMAL_MINUS = "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"35SECURITY_MESSAGE_GENERAL = "ERROR: This installation is not allowed in this security_level. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"36SECURITY_MESSAGE_NORMAL_MINUS_MODEL = "ERROR: Downloading models that are not in '.safetensors' format is only allowed for models registered in the 'default' channel at this security level. If you want to download this model, set the security level to 'normal-' or lower."37 38routes = PromptServer.instance.routes39 40def handle_stream(stream, prefix):41 stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')42 for msg in stream:43 if prefix == '[!]' and ('it/s]' in msg or 's/it]' in msg) and ('%|' in msg or 'it [' in msg):44 if msg.startswith('100%'):45 print('\r' + msg, end="", file=sys.stderr),46 else:47 print('\r' + msg[:-1], end="", file=sys.stderr),48 else:49 if prefix == '[!]':50 print(prefix, msg, end="", file=sys.stderr)51 else:52 print(prefix, msg, end="")53 54 55from comfy.cli_args import args56import latent_preview57 58def is_loopback(address):59 import ipaddress60 try:61 return ipaddress.ip_address(address).is_loopback62 except ValueError:63 return False64 65is_local_mode = is_loopback(args.listen)66 67 68model_dir_name_map = {69 "checkpoints": "checkpoints",70 "checkpoint": "checkpoints",71 "unclip": "checkpoints",72 "text_encoders": "text_encoders",73 "clip": "text_encoders",74 "vae": "vae",75 "lora": "loras",76 "t2i-adapter": "controlnet",77 "t2i-style": "controlnet",78 "controlnet": "controlnet",79 "clip_vision": "clip_vision",80 "gligen": "gligen",81 "upscale": "upscale_models",82 "embedding": "embeddings",83 "embeddings": "embeddings",84 "unet": "diffusion_models",85 "diffusion_model": "diffusion_models",86}87 88 89def is_allowed_security_level(level):90 if level == 'block':91 return False92 elif level == 'high':93 if is_local_mode:94 return core.get_config()['security_level'] in ['weak', 'normal-']95 else:96 return core.get_config()['security_level'] == 'weak'97 elif level == 'middle':98 return core.get_config()['security_level'] in ['weak', 'normal', 'normal-']99 else:100 return True101 102 103async def get_risky_level(files, pip_packages):104 json_data1 = await core.get_data_by_mode('local', 'custom-node-list.json')105 json_data2 = await core.get_data_by_mode('cache', 'custom-node-list.json', channel_url='https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main')106 107 all_urls = set()108 for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:109 all_urls.update(x.get('files', []))110 111 for x in files:112 if x not in all_urls:113 return "high"114 115 all_pip_packages = set()116 for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:117 all_pip_packages.update(x.get('pip', []))118 119 for p in pip_packages:120 if p not in all_pip_packages:121 return "block"122 123 return "middle"124 125 126class ManagerFuncsInComfyUI(core.ManagerFuncs):127 def get_current_preview_method(self):128 if args.preview_method == latent_preview.LatentPreviewMethod.Auto:129 return "auto"130 elif args.preview_method == latent_preview.LatentPreviewMethod.Latent2RGB:131 return "latent2rgb"132 elif args.preview_method == latent_preview.LatentPreviewMethod.TAESD:133 return "taesd"134 else:135 return "none"136 137 def run_script(self, cmd, cwd='.'):138 if len(cmd) > 0 and cmd[0].startswith("#"):139 logging.error(f"[ComfyUI-Manager] Unexpected behavior: `{cmd}`")140 return 0141 142 process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, env=core.get_script_env())143 144 stdout_thread = threading.Thread(target=handle_stream, args=(process.stdout, ""))145 stderr_thread = threading.Thread(target=handle_stream, args=(process.stderr, "[!]"))146 147 stdout_thread.start()148 stderr_thread.start()149 150 stdout_thread.join()151 stderr_thread.join()152 153 return process.wait()154 155 156core.manager_funcs = ManagerFuncsInComfyUI()157 158sys.path.append('../..')159 160from manager_downloader import download_url, download_url_with_agent161 162core.comfy_path = os.path.dirname(folder_paths.__file__)163core.js_path = os.path.join(core.comfy_path, "web", "extensions")164 165local_db_model = os.path.join(manager_util.comfyui_manager_path, "model-list.json")166local_db_alter = os.path.join(manager_util.comfyui_manager_path, "alter-list.json")167local_db_custom_node_list = os.path.join(manager_util.comfyui_manager_path, "custom-node-list.json")168local_db_extension_node_mappings = os.path.join(manager_util.comfyui_manager_path, "extension-node-map.json")169 170 171def set_preview_method(method):172 if method == 'auto':173 args.preview_method = latent_preview.LatentPreviewMethod.Auto174 elif method == 'latent2rgb':175 args.preview_method = latent_preview.LatentPreviewMethod.Latent2RGB176 elif method == 'taesd':177 args.preview_method = latent_preview.LatentPreviewMethod.TAESD178 else:179 args.preview_method = latent_preview.LatentPreviewMethod.NoPreviews180 181 core.get_config()['preview_method'] = method182 183 184set_preview_method(core.get_config()['preview_method'])185 186 187def set_component_policy(mode):188 core.get_config()['component_policy'] = mode189 190def set_update_policy(mode):191 core.get_config()['update_policy'] = mode192 193def set_db_mode(mode):194 core.get_config()['db_mode'] = mode195 196def print_comfyui_version():197 global comfy_ui_hash198 global comfyui_tag199 200 is_detached = False201 try:202 repo = git.Repo(os.path.dirname(folder_paths.__file__))203 core.comfy_ui_revision = len(list(repo.iter_commits('HEAD')))204 205 comfy_ui_hash = repo.head.commit.hexsha206 cm_global.variables['comfyui.revision'] = core.comfy_ui_revision207 208 core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime209 cm_global.variables['comfyui.commit_datetime'] = core.comfy_ui_commit_datetime210 211 is_detached = repo.head.is_detached212 current_branch = repo.active_branch.name213 214 comfyui_tag = core.get_comfyui_tag()215 216 try:217 if not os.environ.get('__COMFYUI_DESKTOP_VERSION__') and core.comfy_ui_commit_datetime.date() < core.comfy_ui_required_commit_datetime.date():218 logging.warning(f"\n\n## [WARN] ComfyUI-Manager: Your ComfyUI version ({core.comfy_ui_revision})[{core.comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version. ##\n\n")219 except:220 pass221 222 # process on_revision_detected -->223 if 'cm.on_revision_detected_handler' in cm_global.variables:224 for k, f in cm_global.variables['cm.on_revision_detected_handler']:225 try:226 f(core.comfy_ui_revision)227 except Exception:228 logging.error(f"[ERROR] '{k}' on_revision_detected_handler")229 traceback.print_exc()230 231 del cm_global.variables['cm.on_revision_detected_handler']232 else:233 logging.warning("[ComfyUI-Manager] Some features are restricted due to your ComfyUI being outdated.")234 # <--235 236 if current_branch == "master":237 if comfyui_tag:238 logging.info(f"### ComfyUI Version: {comfyui_tag} | Released on '{core.comfy_ui_commit_datetime.date()}'")239 else:240 logging.info(f"### ComfyUI Revision: {core.comfy_ui_revision} [{comfy_ui_hash[:8]}] | Released on '{core.comfy_ui_commit_datetime.date()}'")241 else:242 if comfyui_tag:243 logging.info(f"### ComfyUI Version: {comfyui_tag} on '{current_branch}' | Released on '{core.comfy_ui_commit_datetime.date()}'")244 else:245 logging.info(f"### ComfyUI Revision: {core.comfy_ui_revision} on '{current_branch}' [{comfy_ui_hash[:8]}] | Released on '{core.comfy_ui_commit_datetime.date()}'")246 except:247 if is_detached:248 logging.info(f"### ComfyUI Revision: {core.comfy_ui_revision} [{comfy_ui_hash[:8]}] *DETACHED | Released on '{core.comfy_ui_commit_datetime.date()}'")249 else:250 logging.info("### ComfyUI Revision: UNKNOWN (The currently installed ComfyUI is not a Git repository)")251 252 253print_comfyui_version()254core.check_invalid_nodes()255 256 257 258def setup_environment():259 git_exe = core.get_config()['git_exe']260 261 if git_exe != '':262 git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=git_exe)263 264 265setup_environment()266 267# Expand Server api268 269from aiohttp import web270import aiohttp271import json272import zipfile273import urllib.request274 275 276def get_model_dir(data, show_log=False):277 if 'download_model_base' in folder_paths.folder_names_and_paths:278 models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0]279 else:280 models_base = folder_paths.models_dir281 282 # NOTE: Validate to prevent path traversal.283 if any(char in data['filename'] for char in {'/', '\\', ':'}):284 return None285 286 def resolve_custom_node(save_path):287 save_path = save_path[13:] # remove 'custom_nodes/'288 289 # NOTE: Validate to prevent path traversal.290 if save_path.startswith(os.path.sep) or ':' in save_path:291 return None292 293 repo_name = save_path.replace('\\','/').split('/')[0] # get custom node repo name294 295 # NOTE: The creation of files within the custom node path should be removed in the future.296 repo_path = core.lookup_installed_custom_nodes_legacy(repo_name)297 if repo_path is not None and repo_path[0]:298 # Returns the retargeted path based on the actually installed repository299 return os.path.join(os.path.dirname(repo_path[1]), save_path)300 else:301 return None302 303 if data['save_path'] != 'default':304 if '..' in data['save_path'] or data['save_path'].startswith('/'):305 if show_log:306 logging.info(f"[WARN] '{data['save_path']}' is not allowed path. So it will be saved into 'models/etc'.")307 base_model = os.path.join(models_base, "etc")308 else:309 if data['save_path'].startswith("custom_nodes"):310 base_model = resolve_custom_node(data['save_path'])311 if base_model is None:312 if show_log:313 logging.info(f"[ComfyUI-Manager] The target custom node for model download is not installed: {data['save_path']}")314 return None315 else:316 base_model = os.path.join(models_base, data['save_path'])317 else:318 model_dir_name = model_dir_name_map.get(data['type'].lower())319 if model_dir_name is not None:320 base_model = folder_paths.folder_names_and_paths[model_dir_name][0][0]321 else:322 base_model = os.path.join(models_base, "etc")323 324 return base_model325 326 327def get_model_path(data, show_log=False):328 base_model = get_model_dir(data, show_log)329 if base_model is None:330 return None331 else:332 if data['filename'] == '<huggingface>':333 return os.path.join(base_model, os.path.basename(data['url']))334 else:335 return os.path.join(base_model, data['filename'])336 337 338def check_state_of_git_node_pack(node_packs, do_fetch=False, do_update_check=True, do_update=False):339 if do_fetch:340 print("Start fetching...", end="")341 elif do_update:342 print("Start updating...", end="")343 elif do_update_check:344 print("Start update check...", end="")345 346 def process_custom_node(item):347 core.check_state_of_git_node_pack_single(item, do_fetch, do_update_check, do_update)348 349 with concurrent.futures.ThreadPoolExecutor(4) as executor:350 for k, v in node_packs.items():351 if v.get('active_version') in ['unknown', 'nightly']:352 executor.submit(process_custom_node, v)353 354 if do_fetch:355 print("\x1b[2K\rFetching done.")356 elif do_update:357 update_exists = any(item.get('updatable', False) for item in node_packs.values())358 if update_exists:359 print("\x1b[2K\rUpdate done.")360 else:361 print("\x1b[2K\rAll extensions are already up-to-date.")362 elif do_update_check:363 print("\x1b[2K\rUpdate check done.")364 365 366def nickname_filter(json_obj):367 preemptions_map = {}368 369 for k, x in json_obj.items():370 if 'preemptions' in x[1]:371 for y in x[1]['preemptions']:372 preemptions_map[y] = k373 elif k.endswith("/ComfyUI"):374 for y in x[0]:375 preemptions_map[y] = k376 377 updates = {}378 for k, x in json_obj.items():379 removes = set()380 for y in x[0]:381 k2 = preemptions_map.get(y)382 if k2 is not None and k != k2:383 removes.add(y)384 385 if len(removes) > 0:386 updates[k] = [y for y in x[0] if y not in removes]387 388 for k, v in updates.items():389 json_obj[k][0] = v390 391 return json_obj392 393 394task_queue = queue.Queue()395nodepack_result = {}396model_result = {}397tasks_in_progress = set()398task_worker_lock = threading.Lock()399 400async def task_worker():401 global task_queue402 global nodepack_result403 global model_result404 global tasks_in_progress405 406 async def do_install(item) -> str:407 ui_id, node_spec_str, channel, mode, skip_post_install = item408 409 try:410 node_spec = core.unified_manager.resolve_node_spec(node_spec_str)411 if node_spec is None:412 logging.error(f"Cannot resolve install target: '{node_spec_str}'")413 return f"Cannot resolve install target: '{node_spec_str}'"414 415 node_name, version_spec, is_specified = node_spec416 res = await core.unified_manager.install_by_id(node_name, version_spec, channel, mode, return_postinstall=skip_post_install)417 # discard post install if skip_post_install mode418 419 if res.action not in ['skip', 'enable', 'install-git', 'install-cnr', 'switch-cnr']:420 logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}")421 return res.msg422 423 elif not res.result:424 logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}")425 return res.msg426 427 return 'success'428 except Exception:429 traceback.print_exc()430 return f"Installation failed:\n{node_spec_str}"431 432 async def do_update(item):433 ui_id, node_name, node_ver = item434 435 try:436 res = core.unified_manager.unified_update(node_name, node_ver)437 438 if res.ver == 'unknown':439 url = core.unified_manager.unknown_active_nodes[node_name][0]440 title = os.path.basename(url)441 else:442 url = core.unified_manager.cnr_map[node_name].get('repository')443 title = core.unified_manager.cnr_map[node_name]['name']444 445 manager_util.clear_pip_cache()446 447 if url is not None:448 base_res = {'url': url, 'title': title}449 else:450 base_res = {'title': title}451 452 if res.result:453 if res.action == 'skip':454 base_res['msg'] = 'skip'455 return base_res456 else:457 base_res['msg'] = 'success'458 return base_res459 460 base_res['msg'] = f"An error occurred while updating '{node_name}'."461 logging.error(f"\nERROR: An error occurred while updating '{node_name}'. (res.result={res.result}, res.action={res.action})")462 return base_res463 except Exception:464 traceback.print_exc()465 466 return {'msg':f"An error occurred while updating '{node_name}'."}467 468 async def do_update_comfyui(is_stable) -> str:469 try:470 repo_path = os.path.dirname(folder_paths.__file__)471 latest_tag = None472 if is_stable:473 res, latest_tag = core.update_to_stable_comfyui(repo_path)474 else:475 res = core.update_path(repo_path)476 477 if res == "fail":478 logging.error("ComfyUI update failed")479 return "fail"480 elif res == "updated":481 if is_stable:482 logging.info("ComfyUI is updated to latest stable version.")483 return "success-stable-"+latest_tag484 else:485 logging.info("ComfyUI is updated to latest nightly version.")486 return "success-nightly"487 else: # skipped488 logging.info("ComfyUI is up-to-date.")489 return "skip"490 491 except Exception:492 traceback.print_exc()493 494 return "An error occurred while updating 'comfyui'."495 496 async def do_fix(item) -> str:497 ui_id, node_name, node_ver = item498 499 try:500 res = core.unified_manager.unified_fix(node_name, node_ver)501 502 if res.result:503 return 'success'504 else:505 logging.error(res.msg)506 507 logging.error(f"\nERROR: An error occurred while fixing '{node_name}@{node_ver}'.")508 except Exception:509 traceback.print_exc()510 511 return f"An error occurred while fixing '{node_name}@{node_ver}'."512 513 async def do_uninstall(item) -> str:514 ui_id, node_name, is_unknown = item515 516 try:517 res = core.unified_manager.unified_uninstall(node_name, is_unknown)518 519 if res.result:520 return 'success'521 522 logging.error(f"\nERROR: An error occurred while uninstalling '{node_name}'.")523 except Exception:524 traceback.print_exc()525 526 return f"An error occurred while uninstalling '{node_name}'."527 528 async def do_disable(item) -> str:529 ui_id, node_name, is_unknown = item530 531 try:532 res = core.unified_manager.unified_disable(node_name, is_unknown)533 534 if res:535 return 'success'536 537 except Exception:538 traceback.print_exc()539 540 return f"Failed to disable: '{node_name}'"541 542 async def do_install_model(item) -> str:543 ui_id, json_data = item544 545 model_path = get_model_path(json_data)546 model_url = json_data['url']547 548 res = False549 550 try:551 if model_path is not None:552 logging.info(f"Install model '{json_data['name']}' from '{model_url}' into '{model_path}'")553 554 if json_data['filename'] == '<huggingface>':555 if os.path.exists(os.path.join(model_path, os.path.dirname(json_data['url']))):556 logging.error(f"[ComfyUI-Manager] the model path already exists: {model_path}")557 return f"The model path already exists: {model_path}"558 559 logging.info(f"[ComfyUI-Manager] Downloading '{model_url}' into '{model_path}'")560 manager_downloader.download_repo_in_bytes(repo_id=model_url, local_dir=model_path)561 562 return 'success'563 564 elif not core.get_config()['model_download_by_agent'] and (565 model_url.startswith('https://github.com') or model_url.startswith('https://huggingface.co') or model_url.startswith('https://heibox.uni-heidelberg.de')):566 model_dir = get_model_dir(json_data, True)567 download_url(model_url, model_dir, filename=json_data['filename'])568 if model_path.endswith('.zip'):569 res = core.unzip(model_path)570 else:571 res = True572 573 if res:574 return 'success'575 else:576 res = download_url_with_agent(model_url, model_path)577 if res and model_path.endswith('.zip'):578 res = core.unzip(model_path)579 else:580 logging.error(f"[ComfyUI-Manager] Model installation error: invalid model type - {json_data['type']}")581 582 if res:583 return 'success'584 585 except Exception as e:586 logging.error(f"[ComfyUI-Manager] ERROR: {e}", file=sys.stderr)587 588 return f"Model installation error: {model_url}"589 590 stats = {}591 592 while True:593 done_count = len(nodepack_result) + len(model_result)594 total_count = done_count + task_queue.qsize()595 596 if task_queue.empty():597 logging.info(f"\n[ComfyUI-Manager] Queued works are completed.\n{stats}")598 599 logging.info("\nAfter restarting ComfyUI, please refresh the browser.")600 PromptServer.instance.send_sync("cm-queue-status",601 {'status': 'done',602 'nodepack_result': nodepack_result, 'model_result': model_result,603 'total_count': total_count, 'done_count': done_count})604 nodepack_result = {}605 task_queue = queue.Queue()606 return # terminate worker thread607 608 with task_worker_lock:609 kind, item = task_queue.get()610 tasks_in_progress.add((kind, item[0]))611 612 try:613 if kind == 'install':614 msg = await do_install(item)615 elif kind == 'install-model':616 msg = await do_install_model(item)617 elif kind == 'update':618 msg = await do_update(item)619 elif kind == 'update-main':620 msg = await do_update(item)621 elif kind == 'update-comfyui':622 msg = await do_update_comfyui(item[1])623 elif kind == 'fix':624 msg = await do_fix(item)625 elif kind == 'uninstall':626 msg = await do_uninstall(item)627 elif kind == 'disable':628 msg = await do_disable(item)629 else:630 msg = "Unexpected kind: " + kind631 except Exception:632 traceback.print_exc()633 msg = f"Exception: {(kind, item)}"634 635 with task_worker_lock:636 tasks_in_progress.remove((kind, item[0]))637 638 ui_id = item[0]639 if kind == 'install-model':640 model_result[ui_id] = msg641 ui_target = "model_manager"642 elif kind == 'update-main':643 nodepack_result[ui_id] = msg644 ui_target = "main"645 elif kind == 'update-comfyui':646 nodepack_result['comfyui'] = msg647 ui_target = "main"648 elif kind == 'update':649 nodepack_result[ui_id] = msg['msg']650 ui_target = "nodepack_manager"651 else:652 nodepack_result[ui_id] = msg653 ui_target = "nodepack_manager"654 655 stats[kind] = stats.get(kind, 0) + 1656 657 PromptServer.instance.send_sync("cm-queue-status",658 {'status': 'in_progress', 'target': item[0], 'ui_target': ui_target,659 'total_count': total_count, 'done_count': done_count})660 661 662@routes.get("/customnode/getmappings")663async def fetch_customnode_mappings(request):664 """665 provide unified (node -> node pack) mapping list666 """667 mode = request.rel_url.query["mode"]668 669 nickname_mode = False670 if mode == "nickname":671 mode = "local"672 nickname_mode = True673 674 json_obj = await core.get_data_by_mode(mode, 'extension-node-map.json')675 json_obj = core.map_to_unified_keys(json_obj)676 677 if nickname_mode:678 json_obj = nickname_filter(json_obj)679 680 all_nodes = set()681 patterns = []682 for k, x in json_obj.items():683 all_nodes.update(set(x[0]))684 685 if 'nodename_pattern' in x[1]:686 patterns.append((x[1]['nodename_pattern'], x[0]))687 688 missing_nodes = set(nodes.NODE_CLASS_MAPPINGS.keys()) - all_nodes689 690 for x in missing_nodes:691 for pat, item in patterns:692 if re.match(pat, x):693 item.append(x)694 695 return web.json_response(json_obj, content_type='application/json')696 697 698@routes.get("/customnode/fetch_updates")699async def fetch_updates(request):700 try:701 if request.rel_url.query["mode"] == "local":702 channel = 'local'703 else:704 channel = core.get_config()['channel_url']705 706 await core.unified_manager.reload(request.rel_url.query["mode"])707 await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])708 709 res = core.unified_manager.fetch_or_pull_git_repo(is_pull=False)710 711 for x in res['failed']:712 logging.error(f"FETCH FAILED: {x}")713 714 logging.info("\nDone.")715 716 if len(res['updated']) > 0:717 return web.Response(status=201)718 719 return web.Response(status=200)720 except:721 traceback.print_exc()722 return web.Response(status=400)723 724 725@routes.get("/manager/queue/update_all")726async def update_all(request):727 if not is_allowed_security_level('middle'):728 logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)729 return web.Response(status=403)730 731 with task_worker_lock:732 is_processing = task_worker_thread is not None and task_worker_thread.is_alive()733 if is_processing:734 return web.Response(status=401)735 736 await core.save_snapshot_with_postfix('autosave')737 738 if request.rel_url.query["mode"] == "local":739 channel = 'local'740 else:741 channel = core.get_config()['channel_url']742 743 await core.unified_manager.reload(request.rel_url.query["mode"])744 await core.unified_manager.get_custom_nodes(channel, request.rel_url.query["mode"])745 746 for k, v in core.unified_manager.active_nodes.items():747 if k == 'comfyui-manager':748 # skip updating comfyui-manager if desktop version749 if os.environ.get('__COMFYUI_DESKTOP_VERSION__'):750 continue751 752 update_item = k, k, v[0]753 task_queue.put(("update-main", update_item))754 755 for k, v in core.unified_manager.unknown_active_nodes.items():756 if k == 'comfyui-manager':757 # skip updating comfyui-manager if desktop version758 if os.environ.get('__COMFYUI_DESKTOP_VERSION__'):759 continue760 761 update_item = k, k, 'unknown'762 task_queue.put(("update-main", update_item))763 764 return web.Response(status=200)765 766 767def convert_markdown_to_html(input_text):768 pattern_a = re.compile(r'\[a/([^]]+)]\(([^)]+)\)')769 pattern_w = re.compile(r'\[w/([^]]+)]')770 pattern_i = re.compile(r'\[i/([^]]+)]')771 pattern_bold = re.compile(r'\*\*([^*]+)\*\*')772 pattern_white = re.compile(r'%%([^*]+)%%')773 774 def replace_a(match):775 return f"<a href='{match.group(2)}' target='blank'>{match.group(1)}</a>"776 777 def replace_w(match):778 return f"<p class='cm-warn-note'>{match.group(1)}</p>"779 780 def replace_i(match):781 return f"<p class='cm-info-note'>{match.group(1)}</p>"782 783 def replace_bold(match):784 return f"<B>{match.group(1)}</B>"785 786 def replace_white(match):787 return f"<font color='white'>{match.group(1)}</font>"788 789 input_text = input_text.replace('\\[', '[').replace('\\]', ']').replace('<', '<').replace('>', '>')790 791 result_text = re.sub(pattern_a, replace_a, input_text)792 result_text = re.sub(pattern_w, replace_w, result_text)793 result_text = re.sub(pattern_i, replace_i, result_text)794 result_text = re.sub(pattern_bold, replace_bold, result_text)795 result_text = re.sub(pattern_white, replace_white, result_text)796 797 return result_text.replace("\n", "<BR>")798 799 800def populate_markdown(x):801 if 'description' in x:802 x['description'] = convert_markdown_to_html(manager_util.sanitize_tag(x['description']))803 804 if 'name' in x:805 x['name'] = manager_util.sanitize_tag(x['name'])806 807 if 'title' in x:808 x['title'] = manager_util.sanitize_tag(x['title'])809 810 811# freeze imported version812startup_time_installed_node_packs = core.get_installed_node_packs()813@routes.get("/customnode/installed")814async def installed_list(request):815 mode = request.query.get('mode', 'default')816 817 if mode == 'imported':818 res = startup_time_installed_node_packs819 else:820 res = core.get_installed_node_packs()821 822 return web.json_response(res, content_type='application/json')823 824 825@routes.get("/customnode/getlist")826async def fetch_customnode_list(request):827 """828 provide unified custom node list829 """830 if request.rel_url.query.get("skip_update", '').lower() == "true":831 skip_update = True832 else:833 skip_update = False834 835 if request.rel_url.query["mode"] == "local":836 channel = 'local'837 else:838 channel = core.get_config()['channel_url']839 840 node_packs = await core.get_unified_total_nodes(channel, request.rel_url.query["mode"], 'cache')841 json_obj_github = core.get_data_by_mode(request.rel_url.query["mode"], 'github-stats.json', 'default')842 json_obj_extras = core.get_data_by_mode(request.rel_url.query["mode"], 'extras.json', 'default')843 844 core.populate_github_stats(node_packs, await json_obj_github)845 core.populate_favorites(node_packs, await json_obj_extras)846 847 check_state_of_git_node_pack(node_packs, not skip_update, do_update_check=not skip_update)848 849 for v in node_packs.values():850 populate_markdown(v)851 852 if channel != 'local':853 found = 'custom'854 855 for name, url in core.get_channel_dict().items():856 if url == channel:857 found = name858 break859 860 channel = found861 862 result = dict(channel=channel, node_packs=node_packs)863 864 return web.json_response(result, content_type='application/json')865 866 867@routes.get("/customnode/alternatives")868async def fetch_customnode_alternatives(request):869 alter_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json')870 871 res = {}872 873 for item in alter_json['items']:874 populate_markdown(item)875 res[item['id']] = item876 877 res = core.map_to_unified_keys(res)878 879 return web.json_response(res, content_type='application/json')880 881 882def check_model_installed(json_obj):883 def is_exists(model_dir_name, filename, url):884 if filename == '<huggingface>':885 filename = os.path.basename(url)886 887 dirs = folder_paths.get_folder_paths(model_dir_name)888 889 for x in dirs:890 if os.path.exists(os.path.join(x, filename)):891 return True892 893 return False894 895 model_dir_names = ['checkpoints', 'loras', 'vae', 'text_encoders', 'diffusion_models', 'clip_vision', 'embeddings',896 'diffusers', 'vae_approx', 'controlnet', 'gligen', 'upscale_models', 'hypernetworks',897 'photomaker', 'classifiers']898 899 total_models_files = set()900 for x in model_dir_names:901 for y in folder_paths.get_filename_list(x):902 total_models_files.add(y)903 904 def process_model_phase(item):905 if 'diffusion' not in item['filename'] and 'pytorch' not in item['filename'] and 'model' not in item['filename']:906 # non-general name case907 if item['filename'] in total_models_files:908 item['installed'] = 'True'909 return910 911 if item['save_path'] == 'default':912 model_dir_name = model_dir_name_map.get(item['type'].lower())913 if model_dir_name is not None:914 item['installed'] = str(is_exists(model_dir_name, item['filename'], item['url']))915 else:916 item['installed'] = 'False'917 else:918 model_dir_name = item['save_path'].split('/')[0]919 if model_dir_name in folder_paths.folder_names_and_paths:920 if is_exists(model_dir_name, item['filename'], item['url']):921 item['installed'] = 'True'922 923 if 'installed' not in item:924 if item['filename'] == '<huggingface>':925 filename = os.path.basename(item['url'])926 else:927 filename = item['filename']928 929 fullpath = os.path.join(folder_paths.models_dir, item['save_path'], filename)930 931 item['installed'] = 'True' if os.path.exists(fullpath) else 'False'932 933 with concurrent.futures.ThreadPoolExecutor(8) as executor:934 for item in json_obj['models']:935 executor.submit(process_model_phase, item)936 937 938@routes.get("/externalmodel/getlist")939async def fetch_externalmodel_list(request):940 # The model list is only allowed in the default channel, yet.941 json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'model-list.json')942 943 check_model_installed(json_obj)944 945 for x in json_obj['models']:946 populate_markdown(x)947 948 return web.json_response(json_obj, content_type='application/json')949 950 951@PromptServer.instance.routes.get("/snapshot/getlist")952async def get_snapshot_list(request):953 items = [f[:-5] for f in os.listdir(core.manager_snapshot_path) if f.endswith('.json')]954 items.sort(reverse=True)955 return web.json_response({'items': items}, content_type='application/json')956 957 958@routes.get("/snapshot/remove")959async def remove_snapshot(request):960 if not is_allowed_security_level('middle'):961 logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)962 return web.Response(status=403)963 964 try:965 target = request.rel_url.query["target"]966 967 path = os.path.join(core.manager_snapshot_path, f"{target}.json")968 if os.path.exists(path):969 os.remove(path)970 971 return web.Response(status=200)972 except:973 return web.Response(status=400)974 975 976@routes.get("/snapshot/restore")977async def restore_snapshot(request):978 if not is_allowed_security_level('middle'):979 logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)980 return web.Response(status=403)981 982 try:983 target = request.rel_url.query["target"]984 985 path = os.path.join(core.manager_snapshot_path, f"{target}.json")986 if os.path.exists(path):987 if not os.path.exists(core.manager_startup_script_path):988 os.makedirs(core.manager_startup_script_path)989 990 target_path = os.path.join(core.manager_startup_script_path, "restore-snapshot.json")991 shutil.copy(path, target_path)992 993 logging.info(f"Snapshot restore scheduled: `{target}`")994 return web.Response(status=200)995 996 logging.error(f"Snapshot file not found: `{path}`")997 return web.Response(status=400)998 except:999 return web.Response(status=400)1000 1001 1002@routes.get("/snapshot/get_current")1003async def get_current_snapshot_api(request):1004 try:1005 return web.json_response(await core.get_current_snapshot(), content_type='application/json')1006 except:1007 return web.Response(status=400)1008 1009 1010@routes.get("/snapshot/save")1011async def save_snapshot(request):1012 try:1013 await core.save_snapshot_with_postfix('snapshot')1014 return web.Response(status=200)1015 except:1016 return web.Response(status=400)1017 1018 1019def unzip_install(files):1020 temp_filename = 'manager-temp.zip'1021 for url in files:1022 if url.endswith("/"):1023 url = url[:-1]1024 try:1025 headers = {1026 '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'}1027 1028 req = urllib.request.Request(url, headers=headers)1029 response = urllib.request.urlopen(req)1030 data = response.read()1031 1032 with open(temp_filename, 'wb') as f:1033 f.write(data)1034 1035 with zipfile.ZipFile(temp_filename, 'r') as zip_ref:1036 zip_ref.extractall(core.get_default_custom_nodes_path())1037 1038 os.remove(temp_filename)1039 except Exception as e:1040 logging.error(f"Install(unzip) error: {url} / {e}", file=sys.stderr)1041 return False1042 1043 logging.info("Installation was successful.")1044 return True1045 1046 1047def copy_install(files, js_path_name=None):1048 for url in files:1049 if url.endswith("/"):1050 url = url[:-1]1051 try:1052 filename = os.path.basename(url)1053 if url.endswith(".py"):1054 download_url(url, core.get_default_custom_nodes_path(), filename)1055 else:1056 path = os.path.join(core.js_path, js_path_name) if js_path_name is not None else core.js_path1057 if not os.path.exists(path):1058 os.makedirs(path)1059 download_url(url, path, filename)1060 1061 except Exception as e:1062 logging.error(f"Install(copy) error: {url} / {e}", file=sys.stderr)1063 return False1064 1065 logging.info("Installation was successful.")1066 return True1067 1068 1069def copy_uninstall(files, js_path_name='.'):1070 for url in files:1071 if url.endswith("/"):1072 url = url[:-1]1073 dir_name = os.path.basename(url)1074 base_path = core.get_default_custom_nodes_path() if url.endswith('.py') else os.path.join(core.js_path, js_path_name)1075 file_path = os.path.join(base_path, dir_name)1076 1077 try:1078 if os.path.exists(file_path):1079 os.remove(file_path)1080 elif os.path.exists(file_path + ".disabled"):1081 os.remove(file_path + ".disabled")1082 except Exception as e:1083 logging.error(f"Uninstall(copy) error: {url} / {e}", file=sys.stderr)1084 return False1085 1086 logging.info("Uninstallation was successful.")1087 return True1088 1089 1090def copy_set_active(files, is_disable, js_path_name='.'):1091 if is_disable:1092 action_name = "Disable"1093 else:1094 action_name = "Enable"1095 1096 for url in files:1097 if url.endswith("/"):1098 url = url[:-1]1099 dir_name = os.path.basename(url)1100 base_path = core.get_default_custom_nodes_path() if url.endswith('.py') else os.path.join(core.js_path, js_path_name)1101 file_path = os.path.join(base_path, dir_name)1102 1103 try:1104 if is_disable:1105 current_name = file_path1106 new_name = file_path + ".disabled"1107 else:1108 current_name = file_path + ".disabled"1109 new_name = file_path1110 1111 os.rename(current_name, new_name)1112 1113 except Exception as e:1114 logging.error(f"{action_name}(copy) error: {url} / {e}", file=sys.stderr)1115 1116 return False1117 1118 logging.info(f"{action_name} was successful.")1119 return True1120 1121 1122@routes.get("/customnode/versions/{node_name}")1123async def get_cnr_versions(request):1124 node_name = request.match_info.get("node_name", None)1125 versions = core.cnr_utils.all_versions_of_node(node_name)1126 1127 if versions is not None:1128 return web.json_response(versions, content_type='application/json')1129 1130 return web.Response(status=400)1131 1132 1133@routes.get("/customnode/disabled_versions/{node_name}")1134async def get_disabled_versions(request):1135 node_name = request.match_info.get("node_name", None)1136 versions = []1137 if node_name in core.unified_manager.nightly_inactive_nodes:1138 versions.append(dict(version='nightly'))1139 1140 for v in core.unified_manager.cnr_inactive_nodes.get(node_name, {}).keys():1141 versions.append(dict(version=v))1142 1143 if versions:1144 return web.json_response(versions, content_type='application/json')1145 1146 return web.Response(status=400)1147 1148 1149@routes.post("/customnode/import_fail_info")1150async def import_fail_info(request):1151 json_data = await request.json()1152 1153 if 'cnr_id' in json_data:1154 module_name = core.unified_manager.get_module_name(json_data['cnr_id'])1155 else:1156 module_name = core.unified_manager.get_module_name(json_data['url'])1157 1158 if module_name is not None:1159 info = cm_global.error_dict.get(module_name)1160 if info is not None:1161 return web.json_response(info)1162 1163 return web.Response(status=400)1164 1165 1166@routes.post("/manager/queue/reinstall")1167async def reinstall_custom_node(request):1168 await uninstall_custom_node(request)1169 await install_custom_node(request)1170 1171 1172@routes.get("/manager/queue/reset")1173async def reset_queue(request):1174 global task_queue1175 task_queue = queue.Queue()1176 return web.Response(status=200)1177 1178 1179@routes.get("/manager/queue/status")1180async def queue_count(request):1181 global task_queue1182 1183 with task_worker_lock:1184 done_count = len(nodepack_result) + len(model_result)1185 in_progress_count = len(tasks_in_progress)1186 total_count = done_count + in_progress_count + task_queue.qsize()1187 is_processing = task_worker_thread is not None and task_worker_thread.is_alive()1188 1189 return web.json_response({1190 'total_count': total_count, 'done_count': done_count, 'in_progress_count': in_progress_count,1191 'is_processing': is_processing})1192 1193 1194@routes.post("/manager/queue/install")1195async def install_custom_node(request):1196 if not is_allowed_security_level('middle'):1197 logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)1198 return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")1199 1200 json_data = await request.json()