import mimetypes import traceback import folder_paths import locale import subprocess # don't remove this import concurrent import nodes import hashlib import os import sys import threading import re import shutil import git from server import PromptServer import manager_core as core import cm_global print(f"### Loading: ComfyUI-Manager ({core.version_str})") comfy_ui_hash = "-" 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.') 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'] 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/custom-node-list.json') 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" class ManagerFuncsInComfyUI(core.ManagerFuncs): 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" def run_script(self, cmd, cwd='.'): if len(cmd) > 0 and cmd[0].startswith("#"): print(f"[ComfyUI-Manager] Unexpected behavior: `{cmd}`") return 0 process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1) stdout_thread = threading.Thread(target=handle_stream, args=(process.stdout, "")) stderr_thread = threading.Thread(target=handle_stream, args=(process.stderr, "[!]")) stdout_thread.start() stderr_thread.start() stdout_thread.join() stderr_thread.join() return process.wait() core.manager_funcs = ManagerFuncsInComfyUI() sys.path.append('../..') from torchvision.datasets.utils import download_url core.comfy_path = os.path.dirname(folder_paths.__file__) core.js_path = os.path.join(core.comfy_path, "web", "extensions") local_db_model = os.path.join(core.comfyui_manager_path, "model-list.json") local_db_alter = os.path.join(core.comfyui_manager_path, "alter-list.json") local_db_custom_node_list = os.path.join(core.comfyui_manager_path, "custom-node-list.json") local_db_extension_node_mappings = os.path.join(core.comfyui_manager_path, "extension-node-map.json") 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_badge_mode(mode): core.get_config()['badge_mode'] = mode 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 print_comfyui_version(): global comfy_ui_hash is_detached = False try: repo = git.Repo(os.path.dirname(folder_paths.__file__)) core.comfy_ui_revision = len(list(repo.iter_commits('HEAD'))) comfy_ui_hash = repo.head.commit.hexsha cm_global.variables['comfyui.revision'] = core.comfy_ui_revision core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime cm_global.variables['comfyui.commit_datetime'] = core.comfy_ui_commit_datetime is_detached = repo.head.is_detached current_branch = repo.active_branch.name try: if core.comfy_ui_commit_datetime.date() < core.comfy_ui_required_commit_datetime.date(): print(f"\n\n## [WARN] ComfyUI-Manager: Your ComfyUI version ({core.comfy_ui_revision})[{core.comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version. ##\n\n") except: pass # process on_revision_detected --> if 'cm.on_revision_detected_handler' in cm_global.variables: for k, f in cm_global.variables['cm.on_revision_detected_handler']: try: f(core.comfy_ui_revision) except Exception: print(f"[ERROR] '{k}' on_revision_detected_handler") traceback.print_exc() del cm_global.variables['cm.on_revision_detected_handler'] else: print(f"[ComfyUI-Manager] Some features are restricted due to your ComfyUI being outdated.") # <-- if current_branch == "master": print(f"### ComfyUI Revision: {core.comfy_ui_revision} [{comfy_ui_hash[:8]}] | Released on '{core.comfy_ui_commit_datetime.date()}'") else: print(f"### ComfyUI Revision: {core.comfy_ui_revision} on '{current_branch}' [{comfy_ui_hash[:8]}] | Released on '{core.comfy_ui_commit_datetime.date()}'") except: if is_detached: print(f"### ComfyUI Revision: {core.comfy_ui_revision} [{comfy_ui_hash[:8]}] *DETACHED | Released on '{core.comfy_ui_commit_datetime.date()}'") else: print("### ComfyUI Revision: UNKNOWN (The currently installed ComfyUI is not a Git repository)") print_comfyui_version() async def populate_github_stats(json_obj, json_obj_github): if 'custom_nodes' in json_obj: for i, node in enumerate(json_obj['custom_nodes']): url = node['reference'] if url in json_obj_github: json_obj['custom_nodes'][i]['stars'] = json_obj_github[url]['stars'] json_obj['custom_nodes'][i]['last_update'] = json_obj_github[url]['last_update'] else: json_obj['custom_nodes'][i]['stars'] = -1 json_obj['custom_nodes'][i]['last_update'] = -1 return json_obj 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 = "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": 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 == "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] else: base_model = "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_custom_nodes_installed(json_obj, 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_a_custom_node_installed(item, do_fetch, do_update_check, do_update) with concurrent.futures.ThreadPoolExecutor(4) as executor: for item in json_obj['custom_nodes']: executor.submit(process_custom_node, item) if do_fetch: print(f"\x1b[2K\rFetching done.") elif do_update: update_exists = any(item['installed'] == 'Update' for item in json_obj['custom_nodes']) 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 @PromptServer.instance.routes.get("/customnode/getmappings") async def fetch_customnode_mappings(request): 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') 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') @PromptServer.instance.routes.get("/customnode/fetch_updates") async def fetch_updates(request): try: json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json') check_custom_nodes_installed(json_obj, True) update_exists = any('custom_nodes' in json_obj and 'installed' in node and node['installed'] == 'Update' for node in json_obj['custom_nodes']) if update_exists: return web.Response(status=201) return web.Response(status=200) except: return web.Response(status=400) @PromptServer.instance.routes.get("/customnode/update_all") async def update_all(request): if not is_allowed_security_level('middle'): print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.") return web.Response(status=403) try: core.save_snapshot_with_postfix('autosave') json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json') check_custom_nodes_installed(json_obj, do_update=True) updated = [item['title'] for item in json_obj['custom_nodes'] if item['installed'] == 'Update'] failed = [item['title'] for item in json_obj['custom_nodes'] if item['installed'] == 'Fail'] res = {'updated': updated, 'failed': failed} if len(updated) == 0 and len(failed) == 0: status = 200 else: status = 201 return web.json_response(res, status=status, content_type='application/json') except: 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"{match.group(1)}" def replace_w(match): return f"
{match.group(1)}
" def replace_i(match): return f"{match.group(1)}
" def replace_bold(match): return f"{match.group(1)}" def replace_white(match): return f"{match.group(1)}" 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", "Your ComfyUI is too OUTDATED!!!
' + 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) @PromptServer.instance.routes.get("/manager/reboot") def restart(self): if not is_allowed_security_level('middle'): print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.") 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 @PromptServer.instance.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) @PromptServer.instance.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) @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}) 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) @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() 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.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) def sanitize(data): return data.replace("<", "<").replace(">", ">") async def _confirm_try_install(sender, custom_node_url, msg): json_obj = await core.get_data_by_mode('default', 'custom-node-list.json') sender = sanitize(sender) msg = sanitize(msg) target = core.lookup_customnode_by_url(json_obj, custom_node_url) if target is not None: PromptServer.instance.send_sync("cm-api-try-install-customnode", {"sender": sender, "target": target, "msg": msg}) else: print(f"[ComfyUI Manager API] Failed to try install - Unknown custom node url '{custom_node_url}'") def confirm_try_install(sender, custom_node_url, msg): asyncio.run(_confirm_try_install(sender, custom_node_url, msg)) cm_global.register_api('cm.try-install-custom-node', confirm_try_install) import asyncio async def default_cache_update(): async def get_cache(filename): uri = 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/' + filename cache_uri = str(core.simple_hash(uri)) + '_' + filename cache_uri = os.path.join(core.cache_dir, cache_uri) json_obj = await core.get_data(uri, True) with core.cache_lock: with open(cache_uri, "w", encoding='utf-8') as file: json.dump(json_obj, file, indent=4, sort_keys=True) print(f"[ComfyUI-Manager] default cache updated: {uri}") a = get_cache("custom-node-list.json") b = get_cache("extension-node-map.json") c = get_cache("model-list.json") d = get_cache("alter-list.json") e = get_cache("github-stats.json") await asyncio.gather(a, b, c, d, e) threading.Thread(target=lambda: asyncio.run(default_cache_update())).start() 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', 'nodes': {'Terminal Log //CM'}, 'description': 'It provides the ability to manage custom nodes in ComfyUI.', })