diff --git a/README.md b/README.md index fe07c816..1f4c33fe 100644 --- a/README.md +++ b/README.md @@ -193,14 +193,14 @@ NODE_CLASS_MAPPINGS.update({ ## Roadmap -- [ ] System displaying information about failed custom nodes import. -- [ ] Guide for missing nodes in ComfyUI vanilla nodes. +- [x] System displaying information about failed custom nodes import. +- [x] Guide for missing nodes in ComfyUI vanilla nodes. +- [x] Collision checking system for nodes with the same ID across extensions. - [ ] Auto migration for custom nodes with changed structures. - [ ] Version control feature for nodes. - [ ] List of currently used custom nodes. - [ ] Template sharing system. - [ ] 3rd party API system. -- [ ] Collision checking system for nodes with the same ID across extensions. # Disclaimer diff --git a/__init__.py b/__init__.py index e32a4717..755adaa6 100644 --- a/__init__.py +++ b/__init__.py @@ -13,8 +13,10 @@ from tqdm.auto import tqdm import concurrent import ssl from urllib.parse import urlparse +import http.client +import re -version = "V1.3" +version = "V1.4" print(f"### Loading: ComfyUI-Manager ({version})") @@ -84,6 +86,7 @@ from torchvision.datasets.utils import download_url comfy_ui_required_revision = 1240 comfy_ui_revision = "Unknown" +comfy_ui_commit_date = "" comfy_path = os.path.dirname(folder_paths.__file__) custom_nodes_path = os.path.join(comfy_path, 'custom_nodes') @@ -247,6 +250,8 @@ def try_install_script(url, repo_path, install_cmd): def print_comfyui_version(): global comfy_ui_revision + global comfy_ui_commit_date + try: repo = git.Repo(os.path.dirname(folder_paths.__file__)) @@ -260,10 +265,11 @@ def print_comfyui_version(): except: pass + comfy_ui_commit_date = repo.head.commit.committed_datetime.date() if current_branch == "master": - print(f"### ComfyUI Revision: {comfy_ui_revision} [{git_hash[:8]}] | Released on '{repo.head.commit.committed_datetime.date()}'") + print(f"### ComfyUI Revision: {comfy_ui_revision} [{git_hash[:8]}] | Released on '{comfy_ui_commit_date}'") else: - print(f"### ComfyUI Revision: {comfy_ui_revision} on '{current_branch}' [{git_hash[:8]}] | Released on '{repo.head.commit.committed_datetime.date()}'") + print(f"### ComfyUI Revision: {comfy_ui_revision} on '{current_branch}' [{git_hash[:8]}] | Released on '{comfy_ui_commit_date}'") except: print("### ComfyUI Revision: UNKNOWN (The currently installed ComfyUI is not a Git repository)") @@ -413,7 +419,7 @@ def git_pull(path): origin = repo.remote(name='origin') origin.pull() repo.git.submodule('update', '--init', '--recursive') - + repo.close() return True @@ -534,10 +540,15 @@ def check_a_custom_node_installed(item, do_fetch=False, do_update_check=True, do try: if do_update_check and git_repo_has_updates(dir_path, do_fetch, do_update): item['installed'] = 'Update' + elif sys.__comfyui_manager_is_import_failed_extension(dir_name): + item['installed'] = 'Fail' else: item['installed'] = 'True' except: - item['installed'] = 'True' + if sys.__comfyui_manager_is_import_failed_extension(dir_name): + item['installed'] = 'Fail' + else: + item['installed'] = 'True' elif os.path.exists(dir_path + ".disabled"): item['installed'] = 'Disabled' @@ -557,7 +568,10 @@ def check_a_custom_node_installed(item, do_fetch=False, do_update_check=True, do file_path = os.path.join(base_path, dir_name) if os.path.exists(file_path): - item['installed'] = 'True' + if sys.__comfyui_manager_is_import_failed_extension(dir_name): + item['installed'] = 'Fail' + else: + item['installed'] = 'True' elif os.path.exists(file_path + ".disabled"): item['installed'] = 'Disabled' else: @@ -826,14 +840,14 @@ def get_current_snapshot(): def save_snapshot_with_postfix(postfix): - now = datetime.datetime.now() + now = datetime.datetime.now() - date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S") - file_name = f"{date_time_format}_{postfix}" + date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S") + file_name = f"{date_time_format}_{postfix}" - path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{file_name}.json") - with open(path, "w") as json_file: - json.dump(get_current_snapshot(), json_file, indent=4) + path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{file_name}.json") + with open(path, "w") as json_file: + json.dump(get_current_snapshot(), json_file, indent=4) @server.PromptServer.instance.routes.get("/snapshot/get_current") @@ -1368,7 +1382,7 @@ async def install_model(request): if json_data['url'].startswith('https://github.com') or json_data['url'].startswith('https://huggingface.co'): model_dir = get_model_dir(json_data) download_url(json_data['url'], model_dir) - + return web.json_response({}, content_type='application/json') else: res = download_url_with_agent(json_data['url'], model_path) @@ -1433,6 +1447,37 @@ async def channel_url_list(request): return web.Response(status=200) +@server.PromptServer.instance.routes.get("/manager/notice") +async def get_notice(request): + url = "github.com" + path = "/ltdrdata/ltdrdata.github.io/wiki/News" + + conn = http.client.HTTPSConnection(url) + conn.request("GET", path) + + response = conn.getresponse() + + try: + if response.status == 200: + html_content = response.read().decode('utf-8') + + pattern = re.compile(r'
([\s\S]*?)
') + match = pattern.search(html_content) + + if match: + markdown_content = match.group(1) + markdown_content += f"
ComfyUI: {comfy_ui_revision} ({comfy_ui_commit_date})" + markdown_content += f"
Manager: {version}" + 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) + finally: + conn.close() + + + @server.PromptServer.instance.routes.get("/manager/share_option") async def share_option(request): if "value" in request.rel_url.query: @@ -1554,13 +1599,13 @@ async def share_art(request): 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() @@ -1585,7 +1630,7 @@ async def share_art(request): 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( @@ -1655,7 +1700,7 @@ async def share_art(request): 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']) @@ -1667,7 +1712,7 @@ async def share_art(request): 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 = "" @@ -1684,7 +1729,7 @@ async def share_art(request): 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}", diff --git a/extension-node-map.json b/extension-node-map.json index 1e874ed0..9ba5faa3 100644 --- a/extension-node-map.json +++ b/extension-node-map.json @@ -2565,6 +2565,138 @@ "title_aux": "comfy-nodes" } ], + "https://github.com/comfyanonymous/ComfyUI": [ + [ + "BasicScheduler", + "CLIPLoader", + "CLIPMergeSimple", + "CLIPSave", + "CLIPSetLastLayer", + "CLIPTextEncode", + "CLIPTextEncodeSDXL", + "CLIPTextEncodeSDXLRefiner", + "CLIPVisionEncode", + "CLIPVisionLoader", + "Canny", + "CheckpointLoader", + "CheckpointLoaderSimple", + "CheckpointSave", + "ConditioningAverage", + "ConditioningCombine", + "ConditioningConcat", + "ConditioningSetArea", + "ConditioningSetAreaPercentage", + "ConditioningSetMask", + "ConditioningSetTimestepRange", + "ConditioningZeroOut", + "ControlNetApply", + "ControlNetApplyAdvanced", + "ControlNetLoader", + "CropMask", + "DiffControlNetLoader", + "DiffusersLoader", + "DualCLIPLoader", + "EmptyImage", + "EmptyLatentImage", + "ExponentialScheduler", + "FeatherMask", + "FlipSigmas", + "FreeU", + "FreeU_V2", + "GLIGENLoader", + "GLIGENTextBoxApply", + "GrowMask", + "HyperTile", + "HypernetworkLoader", + "ImageBatch", + "ImageBlend", + "ImageBlur", + "ImageColorToMask", + "ImageCompositeMasked", + "ImageCrop", + "ImageInvert", + "ImageOnlyCheckpointLoader", + "ImagePadForOutpaint", + "ImageQuantize", + "ImageScale", + "ImageScaleBy", + "ImageScaleToTotalPixels", + "ImageSharpen", + "ImageToMask", + "ImageUpscaleWithModel", + "InvertMask", + "JoinImageWithAlpha", + "KSampler", + "KSamplerAdvanced", + "KSamplerSelect", + "KarrasScheduler", + "LatentAdd", + "LatentBlend", + "LatentComposite", + "LatentCompositeMasked", + "LatentCrop", + "LatentFlip", + "LatentFromBatch", + "LatentInterpolate", + "LatentMultiply", + "LatentRotate", + "LatentSubtract", + "LatentUpscale", + "LatentUpscaleBy", + "LoadImage", + "LoadImageMask", + "LoadLatent", + "LoraLoader", + "LoraLoaderModelOnly", + "MaskComposite", + "MaskToImage", + "ModelMergeAdd", + "ModelMergeBlocks", + "ModelMergeSimple", + "ModelMergeSubtract", + "ModelSamplingContinuousEDM", + "ModelSamplingDiscrete", + "PatchModelAddDownscale", + "PolyexponentialScheduler", + "PorterDuffImageComposite", + "PreviewImage", + "RebatchLatents", + "RepeatImageBatch", + "RepeatLatentBatch", + "RescaleCFG", + "SVD_img2vid_Conditioning", + "SamplerCustom", + "SamplerDPMPP_2M_SDE", + "SamplerDPMPP_SDE", + "SaveAnimatedPNG", + "SaveAnimatedWEBP", + "SaveImage", + "SaveLatent", + "SetLatentNoiseMask", + "SolidMask", + "SplitImageWithAlpha", + "SplitSigmas", + "StyleModelApply", + "StyleModelLoader", + "TomePatchModel", + "UNETLoader", + "UpscaleModelLoader", + "VAEDecode", + "VAEDecodeTiled", + "VAEEncode", + "VAEEncodeForInpaint", + "VAEEncodeTiled", + "VAELoader", + "VAESave", + "VPScheduler", + "VideoLinearCFGGuidance", + "unCLIPCheckpointLoader", + "unCLIPConditioning" + ], + { + "title_aux": "ComfyUI" + } + ], "https://github.com/comfyanonymous/ComfyUI_experiments": [ [ "ModelMergeBlockNumber", @@ -4274,6 +4406,7 @@ [ "Auto Merge Block Weighted", "CLIPMergeSimple", + "CheckpointSave", "ModelMergeBlocks", "ModelMergeSimple" ], diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 091185c5..c31d0ecf 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -29,6 +29,16 @@ docStyle.innerHTML = ` text-align: center; height: 45px; } + +.cm-notice-board { + width: 250px; + height: 160px; + overflow: auto; + color: var(--input-text); + border: 1px solid #ccc; + padding: 10px; + overflow-x: hidden; +}; `; document.head.appendChild(docStyle); @@ -89,6 +99,14 @@ async function init_share_option() { }); } +async function init_notice(notice) { + api.fetchApi('/manager/notice') + .then(response => response.text()) + .then(data => { + notice.innerHTML = data; + }) +} + await init_badge_mode(); await init_share_option(); @@ -492,7 +510,7 @@ class ManagerMenuDialog extends ComfyDialog { } createControlsRight() { - return [ + const elts = [ $el("button", { type: "button", textContent: "ComfyUI Community Manual", @@ -570,7 +588,16 @@ class ManagerMenuDialog extends ComfyDialog { textContent: "ComfyUI Nodes Info", onclick: () => { window.open("https://ltdrdata.github.io/", "comfyui-node-info"); } }), + $el("br", {}, []), ]; + + var textarea = document.createElement("div"); + textarea.className = "cm-notice-board"; + elts.push(textarea); + + init_notice(textarea); + + return elts; } constructor() { @@ -603,7 +630,7 @@ class ManagerMenuDialog extends ComfyDialog { this.element = $el("div.comfy-modal", { parent: document.body }, [ content ]); this.element.style.width = '1000px'; - this.element.style.height = '400px'; + this.element.style.height = '420px'; this.element.style.zIndex = 10000; } @@ -638,6 +665,7 @@ app.registerExtension({ } menu.append(managerButton); + const shareButton = document.createElement("button"); shareButton.id = "shareButton"; shareButton.textContent = "Share"; @@ -680,6 +708,10 @@ app.registerExtension({ if (nicknames[nodeData.name.trim()]) { let nick = nicknames[nodeData.name.trim()]; + if (nick == 'ComfyUI') { + nick = "🦊" + } + if (nick.length > 25) { text += nick.substring(0, 23) + ".."; } @@ -726,6 +758,10 @@ app.registerExtension({ if (nicknames[node.type.trim()]) { let nick = nicknames[node.type.trim()]; + if (nick == 'ComfyUI') { + nick = "🦊" + } + if (nick.length > 25) { text += nick.substring(0, 23) + ".."; } diff --git a/js/custom-nodes-downloader.js b/js/custom-nodes-downloader.js index fd8553e5..a2f3f5a3 100644 --- a/js/custom-nodes-downloader.js +++ b/js/custom-nodes-downloader.js @@ -29,6 +29,53 @@ async function getCustomnodeMappings() { return data; } +async function getConflictMappings() { + var mode = "url"; + if(manager_instance.local_mode_checkbox.checked) + mode = "local"; + + const response = await api.fetchApi(`/customnode/getmappings?mode=${mode}`); + + const data = await response.json(); + + let node_to_extensions_map = {}; + + for(let k in data) { + for(let i in data[k][0]) { + let node = data[k][0][i]; + let l = node_to_extensions_map[node]; + if(!l) { + l = []; + node_to_extensions_map[node] = l; + } + l.push(k); + } + } + + let conflict_map = {}; + for(let node in node_to_extensions_map) { + if(node_to_extensions_map[node].length > 1) { + for(let i in node_to_extensions_map[node]) { + let extension = node_to_extensions_map[node][i]; + let l = conflict_map[extension]; + + if(!l) { + l = []; + conflict_map[extension] = l; + } + + for(let j in node_to_extensions_map[node]) { + let extension2 = node_to_extensions_map[node][j]; + if(extension != extension2) + l.push([node, extension2]); + } + } + } + } + + return conflict_map; +} + async function getUnresolvedNodesInComponent() { try { var mode = "url"; @@ -180,6 +227,8 @@ export class CustomNodesInstaller extends ComfyDialog { // invalidate this.data = (await getCustomNodes()).custom_nodes; + this.conflict_mappings = await getConflictMappings(); + if(this.is_missing_node_mode) this.data = await this.filter_missing_node(this.data); @@ -369,6 +418,7 @@ export class CustomNodesInstaller extends ComfyDialog { var data1 = document.createElement('td'); data1.style.textAlign = "center"; data1.innerHTML = i+1; + var data2 = document.createElement('td'); data2.style.maxWidth = "100px"; data2.className = "cm-node-author" @@ -376,14 +426,43 @@ export class CustomNodesInstaller extends ComfyDialog { data2.style.whiteSpace = "nowrap"; data2.style.overflow = "hidden"; data2.style.textOverflow = "ellipsis"; + var data3 = document.createElement('td'); data3.style.maxWidth = "200px"; data3.style.wordWrap = "break-word"; data3.className = "cm-node-name" data3.innerHTML = ` ${data.title}`; + if(data.installed == 'Fail') + data3.innerHTML = ' (IMPORT FAILED)' + data3.innerHTML; + var data4 = document.createElement('td'); data4.innerHTML = data.description; data4.className = "cm-node-desc" + + let conflicts = this.conflict_mappings[data.files[0]]; + if(conflicts) { + let buf = '

Conflicted Nodes:
'; + for(let k in conflicts) { + let node_name = conflicts[k][0]; + + let extension_name = conflicts[k][1].split('/').pop(); + if(extension_name.endsWith('/')) { + extension_name = extension_name.slice(0, -1); + } + if(node_name.endsWith('.git')) { + extension_name = extension_name.slice(0, -4); + } + + buf += `${node_name} [${extension_name}], `; + } + + if(buf.endsWith(', ')) { + buf = buf.slice(0, -2); + } + buf += "
"; + data4.innerHTML += buf; + } + var data5 = document.createElement('td'); data5.style.textAlign = "center"; @@ -424,6 +503,7 @@ export class CustomNodesInstaller extends ComfyDialog { installBtn.innerHTML = 'Uninstall'; installBtn.style.backgroundColor = 'red'; break; + case 'Fail': case 'True': installBtn3 = document.createElement('button'); installBtn3.innerHTML = 'Disable'; @@ -441,7 +521,7 @@ export class CustomNodesInstaller extends ComfyDialog { installBtn.style.color = 'white'; break; default: - installBtn.innerHTML = 'Try Install'; + installBtn.innerHTML = `Try Install${data.installed}`; installBtn.style.backgroundColor = 'Gray'; installBtn.style.color = 'white'; } @@ -479,7 +559,10 @@ export class CustomNodesInstaller extends ComfyDialog { data5.appendChild(installBtn); - dataRow.style.backgroundColor = "var(--bg-color)"; + if(data.installed == 'Fail') + dataRow.style.backgroundColor = "#880000"; + else + dataRow.style.backgroundColor = "var(--bg-color)"; dataRow.style.color = "var(--fg-color)"; dataRow.style.textAlign = "left"; @@ -548,6 +631,7 @@ export class CustomNodesInstaller extends ComfyDialog { { value:'Update', text:'Filter: update' }, { value:'True', text:'Filter: installed' }, { value:'False', text:'Filter: not-installed' }, + { value:'Fail', text:'Filter: import failed' }, ]; items.forEach(item => { diff --git a/node_db/new/extension-node-map.json b/node_db/new/extension-node-map.json index 1e874ed0..9ba5faa3 100644 --- a/node_db/new/extension-node-map.json +++ b/node_db/new/extension-node-map.json @@ -2565,6 +2565,138 @@ "title_aux": "comfy-nodes" } ], + "https://github.com/comfyanonymous/ComfyUI": [ + [ + "BasicScheduler", + "CLIPLoader", + "CLIPMergeSimple", + "CLIPSave", + "CLIPSetLastLayer", + "CLIPTextEncode", + "CLIPTextEncodeSDXL", + "CLIPTextEncodeSDXLRefiner", + "CLIPVisionEncode", + "CLIPVisionLoader", + "Canny", + "CheckpointLoader", + "CheckpointLoaderSimple", + "CheckpointSave", + "ConditioningAverage", + "ConditioningCombine", + "ConditioningConcat", + "ConditioningSetArea", + "ConditioningSetAreaPercentage", + "ConditioningSetMask", + "ConditioningSetTimestepRange", + "ConditioningZeroOut", + "ControlNetApply", + "ControlNetApplyAdvanced", + "ControlNetLoader", + "CropMask", + "DiffControlNetLoader", + "DiffusersLoader", + "DualCLIPLoader", + "EmptyImage", + "EmptyLatentImage", + "ExponentialScheduler", + "FeatherMask", + "FlipSigmas", + "FreeU", + "FreeU_V2", + "GLIGENLoader", + "GLIGENTextBoxApply", + "GrowMask", + "HyperTile", + "HypernetworkLoader", + "ImageBatch", + "ImageBlend", + "ImageBlur", + "ImageColorToMask", + "ImageCompositeMasked", + "ImageCrop", + "ImageInvert", + "ImageOnlyCheckpointLoader", + "ImagePadForOutpaint", + "ImageQuantize", + "ImageScale", + "ImageScaleBy", + "ImageScaleToTotalPixels", + "ImageSharpen", + "ImageToMask", + "ImageUpscaleWithModel", + "InvertMask", + "JoinImageWithAlpha", + "KSampler", + "KSamplerAdvanced", + "KSamplerSelect", + "KarrasScheduler", + "LatentAdd", + "LatentBlend", + "LatentComposite", + "LatentCompositeMasked", + "LatentCrop", + "LatentFlip", + "LatentFromBatch", + "LatentInterpolate", + "LatentMultiply", + "LatentRotate", + "LatentSubtract", + "LatentUpscale", + "LatentUpscaleBy", + "LoadImage", + "LoadImageMask", + "LoadLatent", + "LoraLoader", + "LoraLoaderModelOnly", + "MaskComposite", + "MaskToImage", + "ModelMergeAdd", + "ModelMergeBlocks", + "ModelMergeSimple", + "ModelMergeSubtract", + "ModelSamplingContinuousEDM", + "ModelSamplingDiscrete", + "PatchModelAddDownscale", + "PolyexponentialScheduler", + "PorterDuffImageComposite", + "PreviewImage", + "RebatchLatents", + "RepeatImageBatch", + "RepeatLatentBatch", + "RescaleCFG", + "SVD_img2vid_Conditioning", + "SamplerCustom", + "SamplerDPMPP_2M_SDE", + "SamplerDPMPP_SDE", + "SaveAnimatedPNG", + "SaveAnimatedWEBP", + "SaveImage", + "SaveLatent", + "SetLatentNoiseMask", + "SolidMask", + "SplitImageWithAlpha", + "SplitSigmas", + "StyleModelApply", + "StyleModelLoader", + "TomePatchModel", + "UNETLoader", + "UpscaleModelLoader", + "VAEDecode", + "VAEDecodeTiled", + "VAEEncode", + "VAEEncodeForInpaint", + "VAEEncodeTiled", + "VAELoader", + "VAESave", + "VPScheduler", + "VideoLinearCFGGuidance", + "unCLIPCheckpointLoader", + "unCLIPConditioning" + ], + { + "title_aux": "ComfyUI" + } + ], "https://github.com/comfyanonymous/ComfyUI_experiments": [ [ "ModelMergeBlockNumber", @@ -4274,6 +4406,7 @@ [ "Auto Merge Block Weighted", "CLIPMergeSimple", + "CheckpointSave", "ModelMergeBlocks", "ModelMergeSimple" ], diff --git a/prestartup_script.py b/prestartup_script.py index 94230b3d..c86a17c3 100644 --- a/prestartup_script.py +++ b/prestartup_script.py @@ -9,6 +9,7 @@ import locale message_collapses = [] +import_failed_extensions = set() def register_message_collapse(f): @@ -16,10 +17,16 @@ def register_message_collapse(f): message_collapses.append(f) +def is_import_failed_extension(x): + global import_failed_extensions + return x in import_failed_extensions + + sys.__comfyui_manager_register_message_collapse = register_message_collapse +sys.__comfyui_manager_is_import_failed_extension = is_import_failed_extension comfyui_manager_path = os.path.dirname(__file__) -custom_nodes_path = os.path.join(comfyui_manager_path, "..") +custom_nodes_path = os.path.abspath(os.path.join(comfyui_manager_path, "..")) startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts") restore_snapshot_path = os.path.join(startup_script_path, "restore-snapshot.json") git_script_path = os.path.join(comfyui_manager_path, "git_helper.py") @@ -78,7 +85,12 @@ try: original_stdout = sys.stdout original_stderr = sys.stderr - tqdm = r'\d+%.*\[(.*?)\]' + pat_tqdm = r'\d+%.*\[(.*?)\]' + pat_import_fail = r'seconds \(IMPORT FAILED\):' + pat_custom_node = r'[/\\]custom_nodes[/\\](.*)$' + + is_start_mode = True + is_import_fail_mode = False log_file = open(f"comfyui{postfix}.log", "w", encoding="utf-8") log_lock = threading.Lock() @@ -99,11 +111,30 @@ try: raise ValueError("The object does not have a fileno method") def write(self, message): + global is_start_mode + global is_import_fail_mode + if any(f(message) for f in message_collapses): return + if is_start_mode: + if is_import_fail_mode: + match = re.search(pat_custom_node, message) + if match: + import_failed_extensions.add(match.group(1)) + is_import_fail_mode = False + else: + match = re.search(pat_import_fail, message) + if match: + is_import_fail_mode = True + else: + is_import_fail_mode = False + + if 'Starting server' in message: + is_start_mode = False + if not self.is_stdout: - match = re.search(tqdm, message) + match = re.search(pat_tqdm, message) if match: message = re.sub(r'([#|])\d', r'\1▌', message) message = re.sub('#', '█', message) diff --git a/scanner.py b/scanner.py index 37664b9a..8ac6a925 100644 --- a/scanner.py +++ b/scanner.py @@ -5,9 +5,12 @@ from git import Repo from torchvision.datasets.utils import download_url import concurrent -builtin_nodes = ["KSampler", "CheckpointSave"] +builtin_nodes = set() + + +def scan_in_file(filename, is_builtin=False): + global builtin_nodes -def scan_in_file(filename): try: with open(filename, encoding='utf-8') as file: code = file.read() @@ -63,9 +66,12 @@ def scan_in_file(filename): key, value = line[1:].strip().split(':') metadata[key.strip()] = value.strip() - for x in builtin_nodes: - if x in nodes: - nodes.remove(x) + if is_builtin: + builtin_nodes += set(nodes) + else: + for x in builtin_nodes: + if x in nodes: + nodes.remove(x) return nodes, metadata @@ -113,7 +119,9 @@ def get_git_urls_from_json(json_file): if node.get('install_type') == 'git-clone': files = node.get('files', []) if files: - git_clone_files.append((files[0],node.get('title'))) + git_clone_files.append((files[0], node.get('title'))) + + git_clone_files.append(("https://github.com/comfyanonymous/ComfyUI", "ComfyUI")) return git_clone_files @@ -190,7 +198,7 @@ def update_custom_nodes(): with concurrent.futures.ThreadPoolExecutor(10) as executor: executor.map(download_and_store_info, py_url_titles) - + return node_info @@ -198,6 +206,10 @@ def gen_json(node_info): # scan from .py file node_files, node_dirs = get_nodes(".tmp") + comfyui_path = os.path.abspath(os.path.join('.tmp', "ComfyUI")) + node_dirs.remove(comfyui_path) + node_dirs = [comfyui_path] + node_dirs + data = {} for dirname in node_dirs: py_files = get_py_file_paths(dirname) @@ -205,7 +217,7 @@ def gen_json(node_info): nodes = set() for py in py_files: - nodes_in_file, metadata_in_file = scan_in_file(py) + nodes_in_file, metadata_in_file = scan_in_file(py, dirname == "ComfyUI") nodes.update(nodes_in_file) metadata.update(metadata_in_file)