diff --git a/__init__.py b/__init__.py index 69499083..9ebf8c77 100644 --- a/__init__.py +++ b/__init__.py @@ -28,7 +28,7 @@ except: print(f"[WARN] ComfyUI-Manager: Your ComfyUI version is outdated. Please update to the latest version.") -version = [1, 26, 1] +version = [2, 0] version_str = f"V{version[0]}.{version[1]}" + (f'.{version[2]}' if len(version) > 2 else '') print(f"### Loading: ComfyUI-Manager ({version_str})") @@ -118,6 +118,7 @@ local_db_alter = os.path.join(comfyui_manager_path, "alter-list.json") local_db_custom_node_list = os.path.join(comfyui_manager_path, "custom-node-list.json") local_db_extension_node_mappings = os.path.join(comfyui_manager_path, "extension-node-map.json") git_script_path = os.path.join(os.path.dirname(__file__), "git_helper.py") +components_path = os.path.join(comfyui_manager_path, 'components') startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts") config_path = os.path.join(os.path.dirname(__file__), "config.ini") @@ -1838,6 +1839,59 @@ def restart(self): 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 + + +@server.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) + + sanitized_name = sanitize_filename(name) + + filepath = os.path.join(components_path, sanitized_name+'.json') + 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) + + +@server.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')] + + components = {} + for json_file in json_files: + file_path = os.path.join(components_path, json_file) + with open(file_path, 'r') as file: + try: + 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) + + @server.PromptServer.instance.routes.get("/manager/share_option") async def share_option(request): if "value" in request.rel_url.query: diff --git a/components/.gitignore b/components/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/components/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index b0dd5355..191a9695 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -16,7 +16,7 @@ import { AlternativesInstaller } from "./a1111-alter-downloader.js"; import { SnapshotManager } from "./snapshot.js"; import { ModelInstaller } from "./model-downloader.js"; import { manager_instance, setManagerInstance, install_via_git_url, install_pip, rebootAPI, free_models } from "./common.js"; -import { save_as_component } from "./components-manager.js"; +import { load_components, save_as_component } from "./components-manager.js"; var docStyle = document.createElement('style'); docStyle.innerHTML = ` @@ -1111,6 +1111,14 @@ app.registerExtension({ }); }, async setup() { + let orig_clear = app.graph.clear; + app.graph.clear = function () { + orig_clear.call(app.graph); + load_components(); + }; + + load_components(); + const menu = document.querySelector(".comfy-menu"); const separator = document.createElement("hr"); diff --git a/js/common.js b/js/common.js index f118e145..8d0997d1 100644 --- a/js/common.js +++ b/js/common.js @@ -1,5 +1,5 @@ import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js" +import { api } from "../../scripts/api.js"; export async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); diff --git a/js/components-manager.js b/js/components-manager.js new file mode 100644 index 00000000..c510dc03 --- /dev/null +++ b/js/components-manager.js @@ -0,0 +1,191 @@ +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js" +import { sleep } from "./common.js"; +import { GroupNodeConfig, GroupNodeHandler } from "../../extensions/core/groupNode.js"; + +function storeGroupNode(name, data) { + let extra = app.graph.extra; + if (!extra) app.graph.extra = extra = {}; + let groupNodes = extra.groupNodes; + if (!groupNodes) extra.groupNodes = groupNodes = {}; + groupNodes[name] = data; +} + +export async function load_components() { + let data = await api.fetchApi('/manager/component/loads', {method: "POST"}); + let components = await data.json(); + +// while(!app.graph) { +// await sleep(100); +// } + + let start_time = Date.now(); + let failed = []; + let failed2 = []; + + for(let name in components) { + if(app.graph.extra?.groupNodes?.[name]) { + continue; + } + + let nodeData = components[name]; + + storeGroupNode(name, nodeData); + + const config = new GroupNodeConfig(name, nodeData); + while(!success) { + var success = false; + try { + await config.registerType(); + } + catch { + let elapsed_time = Date.now() - start_time; + if (elapsed_time > 5000) { + failed.push(name); + success = true; + } else { + await sleep(100); + } + } + } + + const groupNode = LiteGraph.createNode(`workflow/${name}`); + } + + // fallback1 + for(let i in failed) { + let name = failed[i]; + + if(app.graph.extra?.groupNodes?.[name]) { + continue; + } + + let nodeData = components[name]; + + storeGroupNode(name, nodeData); + + const config = new GroupNodeConfig(name, nodeData); + while(!success) { + var success = false; + try { + await config.registerType(); + } + catch { + let elapsed_time = Date.now() - start_time; + if (elapsed_time > 10000) { + failed2.push(name); + success = true; + } else { + await sleep(100); + } + } + } + + const groupNode = LiteGraph.createNode(`workflow/${name}`); + } + + // fallback2 + for(let name in failed2) { + let name = failed2[i]; + + let nodeData = components[name]; + + storeGroupNode(name, nodeData); + + const config = new GroupNodeConfig(name, nodeData); + while(!success) { + var success = false; + try { + await config.registerType(); + } + catch { + let elapsed_time = Date.now() - start_time; + if (elapsed_time > 30000) { + failed.push(name); + success = true; + } else { + await sleep(100); + } + } + } + + const groupNode = LiteGraph.createNode(`workflow/${name}`); + } +} + +export async function save_as_component(node, app) { + let pure_name = node.comfyClass.substring(9); + let subgraph = app.graph.extra?.groupNodes?.[pure_name]; + + if(!subgraph) { + app.ui.dialog.show(`Failed to retrieve the group node '${pure_name}'.`); + return; + } + + if(node.comfyClass.includes('::')) { + let component_name = node.comfyClass.substring(9); + + if(confirm(`Will you save/overwrite component '${component_name}'?`)) { + let subgraph = app.graph.extra?.groupNodes?.[component_name]; + let body = + { + name: component_name, + workflow: subgraph + }; + + const res = await api.fetchApi('/manager/component/save', { + method: "POST", + headers: { "Content-Type": "application/json", }, + body: JSON.stringify(body) + }); + + if(res.status == 200) { + storeGroupNode(name, subgraph); + const config = new GroupNodeConfig(name, subgraph); + await config.registerType(); + + let path = await res.text(); + app.ui.dialog.show(`Component '${component_name}' is saved into:\n${path}`); + } + else + app.ui.dialog.show(`Failed to save component.`); + } + + return; + } + + var prefix = prompt("To save as a component, a unique prefix is required. (e.g., the 'Impact' in Impact::MAKE_BASIC_PIPE)", "PREFIX"); + + if(!prefix) { + return; + } + + prefix = prefix.trim(); + + if(prefix == 'PREFIX') { + app.ui.dialog.show(`The placeholder 'PREFIX' isn't allowed for component prefix.`); + return; + } + + let component_name = prefix+'::'+pure_name; + let body = + { + name: component_name, + workflow: subgraph + }; + + const res = await api.fetchApi('/manager/component/save', { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if(res.status == 200) { + let path = await res.text(); + app.ui.dialog.show(`Component '${component_name}' is saved into:\n${path}`); + } + else + app.ui.dialog.show(`Failed to save component.`); +} \ No newline at end of file