mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-12 14:32:35 +08:00
Save group nodes to templates
This commit is contained in:
parent
288a1c6242
commit
bb4e65d4e1
@ -2,10 +2,22 @@ import { app } from "../../scripts/app.js";
|
|||||||
import { api } from "../../scripts/api.js";
|
import { api } from "../../scripts/api.js";
|
||||||
import { getWidgetType } from "../../scripts/widgets.js";
|
import { getWidgetType } from "../../scripts/widgets.js";
|
||||||
|
|
||||||
const IS_GROUP_NODE = Symbol();
|
export const IS_GROUP_NODE = Symbol();
|
||||||
const GROUP_DATA = Symbol();
|
export const GROUP_DATA = Symbol();
|
||||||
const GROUP_SLOTS = Symbol();
|
const GROUP_SLOTS = Symbol();
|
||||||
|
|
||||||
|
export async function registerGroupNodes(groupNodes, source, prefix) {
|
||||||
|
if (!groupNodes) return;
|
||||||
|
|
||||||
|
for (const g in groupNodes) {
|
||||||
|
const def = buildNodeDef(groupNodes[g], g, globalDefs, source);
|
||||||
|
if (prefix) {
|
||||||
|
def.display_name = prefix + "/" + def.display_name;
|
||||||
|
}
|
||||||
|
await app.registerNodeDef(source + "/" + g, def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getLinks(config) {
|
function getLinks(config) {
|
||||||
const linksFrom = {};
|
const linksFrom = {};
|
||||||
const linksTo = {};
|
const linksTo = {};
|
||||||
@ -30,7 +42,7 @@ function getLinks(config) {
|
|||||||
return { linksTo, linksFrom };
|
return { linksTo, linksFrom };
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildNodeDef(config, nodeName, defs, workflow) {
|
function buildNodeDef(config, nodeName, defs, source = "workflow") {
|
||||||
const slots = {
|
const slots = {
|
||||||
inputs: {},
|
inputs: {},
|
||||||
widgets: {},
|
widgets: {},
|
||||||
@ -43,7 +55,7 @@ function buildNodeDef(config, nodeName, defs, workflow) {
|
|||||||
output_is_list: [],
|
output_is_list: [],
|
||||||
name: nodeName,
|
name: nodeName,
|
||||||
display_name: nodeName,
|
display_name: nodeName,
|
||||||
category: "group nodes" + (workflow ? "/workflow" : ""),
|
category: "group nodes" + ("/" + source),
|
||||||
input: { required: {} },
|
input: { required: {} },
|
||||||
|
|
||||||
[IS_GROUP_NODE]: true,
|
[IS_GROUP_NODE]: true,
|
||||||
@ -221,7 +233,7 @@ class ConvertToGroupAction {
|
|||||||
link.push(type);
|
link.push(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
const def = buildNodeDef(config, name, globalDefs, true);
|
const def = buildNodeDef(config, name, globalDefs);
|
||||||
await app.registerNodeDef("workflow/" + name, def);
|
await app.registerNodeDef("workflow/" + name, def);
|
||||||
return { config, def };
|
return { config, def };
|
||||||
}
|
}
|
||||||
@ -380,13 +392,7 @@ const ext = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async beforeConfigureGraph(graphData) {
|
async beforeConfigureGraph(graphData) {
|
||||||
const groupNodes = graphData?.extra?.groupNodes;
|
registerGroupNodes(graphData?.extra?.groupNodes, "workflow");
|
||||||
if (!groupNodes) return;
|
|
||||||
|
|
||||||
for (const name in groupNodes) {
|
|
||||||
const def = buildNodeDef(groupNodes[name], name, globalDefs, true);
|
|
||||||
await app.registerNodeDef("workflow/" + name, def);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
addCustomNodeDefs(defs) {
|
addCustomNodeDefs(defs) {
|
||||||
globalDefs = defs;
|
globalDefs = defs;
|
||||||
@ -579,7 +585,7 @@ const ext = {
|
|||||||
|
|
||||||
for (const innerWidget of innerNode.widgets ?? []) {
|
for (const innerWidget of innerNode.widgets ?? []) {
|
||||||
const groupWidgetName = slots.widgets[i]?.[innerWidget.name];
|
const groupWidgetName = slots.widgets[i]?.[innerWidget.name];
|
||||||
if(!groupWidgetName) continue;
|
if (!groupWidgetName) continue;
|
||||||
const groupWidget = node.widgets.find((w) => w.name === groupWidgetName);
|
const groupWidget = node.widgets.find((w) => w.name === groupWidgetName);
|
||||||
if (groupWidget) {
|
if (groupWidget) {
|
||||||
innerWidget.value = groupWidget.value;
|
innerWidget.value = groupWidget.value;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||||
|
import { GROUP_DATA, IS_GROUP_NODE, registerGroupNodes } from "./groupNode.js";
|
||||||
|
|
||||||
// Adds the ability to save and add multiple nodes as a template
|
// Adds the ability to save and add multiple nodes as a template
|
||||||
// To save:
|
// To save:
|
||||||
@ -27,7 +28,7 @@ class ManageTemplates extends ComfyDialog {
|
|||||||
type: "file",
|
type: "file",
|
||||||
accept: ".json",
|
accept: ".json",
|
||||||
multiple: true,
|
multiple: true,
|
||||||
style: {display: "none"},
|
style: { display: "none" },
|
||||||
parent: document.body,
|
parent: document.body,
|
||||||
onchange: () => this.importAll(),
|
onchange: () => this.importAll(),
|
||||||
});
|
});
|
||||||
@ -124,13 +125,13 @@ class ManageTemplates extends ComfyDialog {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = JSON.stringify({templates: this.templates}, null, 2); // convert the data to a JSON string
|
const json = JSON.stringify({ templates: this.templates }, null, 2); // convert the data to a JSON string
|
||||||
const blob = new Blob([json], {type: "application/json"});
|
const blob = new Blob([json], { type: "application/json" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = $el("a", {
|
const a = $el("a", {
|
||||||
href: url,
|
href: url,
|
||||||
download: "node_templates.json",
|
download: "node_templates.json",
|
||||||
style: {display: "none"},
|
style: { display: "none" },
|
||||||
parent: document.body,
|
parent: document.body,
|
||||||
});
|
});
|
||||||
a.click();
|
a.click();
|
||||||
@ -168,48 +169,44 @@ class ManageTemplates extends ComfyDialog {
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
$el(
|
$el("div", {}, [
|
||||||
"div",
|
$el("button", {
|
||||||
{},
|
textContent: "Export",
|
||||||
[
|
style: {
|
||||||
$el("button", {
|
fontSize: "12px",
|
||||||
textContent: "Export",
|
fontWeight: "normal",
|
||||||
style: {
|
},
|
||||||
fontSize: "12px",
|
onclick: (e) => {
|
||||||
fontWeight: "normal",
|
const json = JSON.stringify({ templates: [t] }, null, 2); // convert the data to a JSON string
|
||||||
},
|
const blob = new Blob([json], { type: "application/json" });
|
||||||
onclick: (e) => {
|
const url = URL.createObjectURL(blob);
|
||||||
const json = JSON.stringify({templates: [t]}, null, 2); // convert the data to a JSON string
|
const a = $el("a", {
|
||||||
const blob = new Blob([json], {type: "application/json"});
|
href: url,
|
||||||
const url = URL.createObjectURL(blob);
|
download: (nameInput.value || t.name) + ".json",
|
||||||
const a = $el("a", {
|
style: { display: "none" },
|
||||||
href: url,
|
parent: document.body,
|
||||||
download: (nameInput.value || t.name) + ".json",
|
});
|
||||||
style: {display: "none"},
|
a.click();
|
||||||
parent: document.body,
|
setTimeout(function () {
|
||||||
});
|
a.remove();
|
||||||
a.click();
|
window.URL.revokeObjectURL(url);
|
||||||
setTimeout(function () {
|
}, 0);
|
||||||
a.remove();
|
},
|
||||||
window.URL.revokeObjectURL(url);
|
}),
|
||||||
}, 0);
|
$el("button", {
|
||||||
},
|
textContent: "Delete",
|
||||||
}),
|
style: {
|
||||||
$el("button", {
|
fontSize: "12px",
|
||||||
textContent: "Delete",
|
color: "red",
|
||||||
style: {
|
fontWeight: "normal",
|
||||||
fontSize: "12px",
|
},
|
||||||
color: "red",
|
onclick: (e) => {
|
||||||
fontWeight: "normal",
|
nameInput.value = "";
|
||||||
},
|
e.target.parentElement.style.display = "none";
|
||||||
onclick: (e) => {
|
e.target.parentElement.previousElementSibling.style.display = "none";
|
||||||
nameInput.value = "";
|
},
|
||||||
e.target.parentElement.style.display = "none";
|
}),
|
||||||
e.target.parentElement.previousElementSibling.style.display = "none";
|
]),
|
||||||
},
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -222,11 +219,11 @@ app.registerExtension({
|
|||||||
setup() {
|
setup() {
|
||||||
const manage = new ManageTemplates();
|
const manage = new ManageTemplates();
|
||||||
|
|
||||||
const clipboardAction = (cb) => {
|
const clipboardAction = async (cb) => {
|
||||||
// We use the clipboard functions but dont want to overwrite the current user clipboard
|
// We use the clipboard functions but dont want to overwrite the current user clipboard
|
||||||
// Restore it after we've run our callback
|
// Restore it after we've run our callback
|
||||||
const old = localStorage.getItem("litegrapheditor_clipboard");
|
const old = localStorage.getItem("litegrapheditor_clipboard");
|
||||||
cb();
|
await cb();
|
||||||
localStorage.setItem("litegrapheditor_clipboard", old);
|
localStorage.setItem("litegrapheditor_clipboard", old);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -240,13 +237,29 @@ app.registerExtension({
|
|||||||
disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
|
disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const name = prompt("Enter name");
|
const name = prompt("Enter name");
|
||||||
if (!name || !name.trim()) return;
|
if (!name?.trim()) return;
|
||||||
|
|
||||||
clipboardAction(() => {
|
clipboardAction(() => {
|
||||||
app.canvas.copyToClipboard();
|
app.canvas.copyToClipboard();
|
||||||
|
let data = localStorage.getItem("litegrapheditor_clipboard");
|
||||||
|
data = JSON.parse(data);
|
||||||
|
const nodeIds = Object.keys(app.canvas.selected_nodes);
|
||||||
|
for (let i = 0; i < nodeIds.length; i++) {
|
||||||
|
const node = app.graph.getNodeById(nodeIds[i]);
|
||||||
|
const nodeData = node?.constructor.nodeData;
|
||||||
|
if (nodeData?.[IS_GROUP_NODE]) {
|
||||||
|
const groupData = nodeData[GROUP_DATA];
|
||||||
|
if (!data.groupNodes) {
|
||||||
|
data.groupNodes = {};
|
||||||
|
}
|
||||||
|
data.groupNodes[nodeData.name] = groupData;
|
||||||
|
data.nodes[i].type = "workflow/" + nodeData.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
manage.templates.push({
|
manage.templates.push({
|
||||||
name,
|
name,
|
||||||
data: localStorage.getItem("litegrapheditor_clipboard"),
|
data: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
manage.store();
|
manage.store();
|
||||||
});
|
});
|
||||||
@ -254,15 +267,19 @@ app.registerExtension({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Map each template to a menu item
|
// Map each template to a menu item
|
||||||
const subItems = manage.templates.map((t) => ({
|
const subItems = manage.templates.map((t) => {
|
||||||
content: t.name,
|
return {
|
||||||
callback: () => {
|
content: t.name,
|
||||||
clipboardAction(() => {
|
callback: () => {
|
||||||
localStorage.setItem("litegrapheditor_clipboard", t.data);
|
clipboardAction(async () => {
|
||||||
app.canvas.pasteFromClipboard();
|
const data = JSON.parse(t.data);
|
||||||
});
|
await registerGroupNodes(data.groupNodes, "workflow", t.name);
|
||||||
},
|
localStorage.setItem("litegrapheditor_clipboard", t.data);
|
||||||
}));
|
app.canvas.pasteFromClipboard();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
subItems.push(null, {
|
subItems.push(null, {
|
||||||
content: "Manage",
|
content: "Manage",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user