Browse Source

feat: A functionality to save a group node as a component.

pull/329/head
Dr.Lt.Data 10 months ago
parent
commit
7f10374a9f
  1. 56
      __init__.py
  2. 1
      components/.gitignore
  3. 10
      js/comfyui-manager.js
  4. 2
      js/common.js
  5. 191
      js/components-manager.js

56
__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:

1
components/.gitignore vendored

@ -0,0 +1 @@
*.json

10
js/comfyui-manager.js vendored

@ -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");

2
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));

191
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.`);
}
Loading…
Cancel
Save