mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2025-12-17 02:12:58 +08:00
feat: A functionality to save a group node as a component.
This commit is contained in:
parent
019897eae2
commit
7f10374a9f
56
__init__.py
56
__init__.py
@ -28,7 +28,7 @@ except:
|
|||||||
print(f"[WARN] ComfyUI-Manager: Your ComfyUI version is outdated. Please update to the latest version.")
|
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 '')
|
version_str = f"V{version[0]}.{version[1]}" + (f'.{version[2]}' if len(version) > 2 else '')
|
||||||
print(f"### Loading: ComfyUI-Manager ({version_str})")
|
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_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")
|
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")
|
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")
|
startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts")
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
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)
|
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")
|
@server.PromptServer.instance.routes.get("/manager/share_option")
|
||||||
async def share_option(request):
|
async def share_option(request):
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
|
|||||||
1
components/.gitignore
vendored
Normal file
1
components/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.json
|
||||||
@ -16,7 +16,7 @@ import { AlternativesInstaller } from "./a1111-alter-downloader.js";
|
|||||||
import { SnapshotManager } from "./snapshot.js";
|
import { SnapshotManager } from "./snapshot.js";
|
||||||
import { ModelInstaller } from "./model-downloader.js";
|
import { ModelInstaller } from "./model-downloader.js";
|
||||||
import { manager_instance, setManagerInstance, install_via_git_url, install_pip, rebootAPI, free_models } from "./common.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');
|
var docStyle = document.createElement('style');
|
||||||
docStyle.innerHTML = `
|
docStyle.innerHTML = `
|
||||||
@ -1111,6 +1111,14 @@ app.registerExtension({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
async setup() {
|
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 menu = document.querySelector(".comfy-menu");
|
||||||
const separator = document.createElement("hr");
|
const separator = document.createElement("hr");
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { api } from "../../scripts/api.js"
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
export async function sleep(ms) {
|
export async function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|||||||
191
js/components-manager.js
Normal file
191
js/components-manager.js
Normal file
@ -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…
Reference in New Issue
Block a user