mirror of
https://git.datalinker.icu/ltdrdata/ComfyUI-Manager
synced 2025-12-08 21:54:26 +08:00
feat: component pack builder
- support drag & drop - add node if single component importing
This commit is contained in:
parent
58a2494715
commit
4bdf7aabe4
14
README.md
14
README.md
@ -223,9 +223,17 @@ NODE_CLASS_MAPPINGS.update({
|
||||
* `<current timestamp>` Ensure that the timestamp is always unique.
|
||||
* "components" should have the same structure as the content of the file stored in ComfyUI-Manager/components.
|
||||
* `<component name>`: The name should be in the format `<prefix>::<node name>`.
|
||||
* `<compnent nodeata>`: In the nodedata of the group node.
|
||||
|
||||
|
||||
* `<compnent nodeata>`: In the nodedata of the group node.
|
||||
* `<version>`: Only two formats are allowed: `major.minor.patch` or `major.minor`. (e.g. `1.0`, `2.2.1`)
|
||||
* `<datetime>`: Saved time
|
||||
* `<packname>`: If the packname is not empty, the category becomes packname/workflow, and it is saved in the <packname>.pack file in ComfyUI-Manager/components.
|
||||
* `<category>`: If there is neither a category nor a packname, it is saved in the components category.
|
||||
```
|
||||
"version":"1.0",
|
||||
"datetime": 1705390656516,
|
||||
"packname": "mypack",
|
||||
"category": "util/pipe",
|
||||
```
|
||||
|
||||
## Support of missing nodes installation
|
||||
|
||||
|
||||
11
__init__.py
11
__init__.py
@ -1855,9 +1855,12 @@ async def save_component(request):
|
||||
if not os.path.exists(components_path):
|
||||
os.mkdir(components_path)
|
||||
|
||||
sanitized_name = sanitize_filename(name)
|
||||
if 'packname' in workflow and workflow['packname'] != '':
|
||||
sanitized_name = sanitize_filename(workflow['packname'])+'.pack'
|
||||
else:
|
||||
sanitized_name = sanitize_filename(name)+'.json'
|
||||
|
||||
filepath = os.path.join(components_path, sanitized_name+'.json')
|
||||
filepath = os.path.join(components_path, sanitized_name)
|
||||
components = {}
|
||||
if os.path.exists(filepath):
|
||||
with open(filepath) as f:
|
||||
@ -1876,12 +1879,14 @@ async def save_component(request):
|
||||
async def load_components(request):
|
||||
try:
|
||||
json_files = [f for f in os.listdir(components_path) if f.endswith('.json')]
|
||||
pack_files = [f for f in os.listdir(components_path) if f.endswith('.pack')]
|
||||
|
||||
components = {}
|
||||
for json_file in json_files:
|
||||
for json_file in json_files + pack_files:
|
||||
file_path = os.path.join(components_path, json_file)
|
||||
with open(file_path, 'r') as file:
|
||||
try:
|
||||
# When there is a conflict between the .pack and the .json, the pack takes precedence and overrides.
|
||||
components.update(json.load(file))
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[ComfyUI-Manager] Error decoding component file in file {json_file}: {e}")
|
||||
|
||||
1
components/.gitignore
vendored
1
components/.gitignore
vendored
@ -1 +1,2 @@
|
||||
*.json
|
||||
*.pack
|
||||
|
||||
@ -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 { load_components, save_as_component } from "./components-manager.js";
|
||||
import { ComponentBuilderDialog, load_components } from "./components-manager.js";
|
||||
|
||||
var docStyle = document.createElement('style');
|
||||
docStyle.innerHTML = `
|
||||
@ -27,6 +27,35 @@ docStyle.innerHTML = `
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.cb-widget {
|
||||
width: 400px;
|
||||
height: 25px;
|
||||
box-sizing: border-box;
|
||||
z-index: 10000;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.cb-widget-input {
|
||||
width: 305px;
|
||||
height: 25px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.cb-widget-input:disabled {
|
||||
background-color: #444444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cb-widget-input-label {
|
||||
width: 90px;
|
||||
height: 25px;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
text-align: right;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.cm-menu-container {
|
||||
column-gap: 20px;
|
||||
display: flex;
|
||||
@ -251,6 +280,16 @@ const style = `
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cb-node-label {
|
||||
width: 400px;
|
||||
height:28px;
|
||||
color: black;
|
||||
background-color: #777777;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#cm-close-button {
|
||||
width: calc(100% - 65px);
|
||||
bottom: 10px;
|
||||
@ -258,6 +297,16 @@ const style = `
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#cm-save-button {
|
||||
width: calc(100% - 65px);
|
||||
bottom:40px;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
#cm-save-button:disabled {
|
||||
background-color: #444444;
|
||||
}
|
||||
|
||||
.pysssss-workflow-arrow-2 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -873,7 +922,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
||||
})
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
createControlsRight() {
|
||||
const elts = [
|
||||
@ -1197,7 +1246,13 @@ app.registerExtension({
|
||||
if (node.comfyClass.startsWith('workflow/')) {
|
||||
options.push({
|
||||
content: "Save As Component",
|
||||
callback: (obj) => { save_as_component(node, app); }
|
||||
callback: (obj) => {
|
||||
if (!ComponentBuilderDialog.instance) {
|
||||
ComponentBuilderDialog.instance = new ComponentBuilderDialog();
|
||||
}
|
||||
ComponentBuilderDialog.instance.target_node = node;
|
||||
ComponentBuilderDialog.instance.show();
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
|
||||
11
js/common.js
11
js/common.js
@ -48,8 +48,7 @@ export async function install_checked_custom_node(grid_rows, target_i, caller, m
|
||||
});
|
||||
|
||||
if(response.status == 400) {
|
||||
app.ui.dialog.show(`${mode} failed: ${target.title}`);
|
||||
app.ui.dialog.element.style.zIndex = 10010;
|
||||
show_message(`${mode} failed: ${target.title}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -64,8 +63,7 @@ export async function install_checked_custom_node(grid_rows, target_i, caller, m
|
||||
}
|
||||
|
||||
if(failed != '') {
|
||||
app.ui.dialog.show(`${mode} failed: ${failed}`);
|
||||
app.ui.dialog.element.style.zIndex = 10010;
|
||||
show_message(`${mode} failed: ${failed}`);
|
||||
}
|
||||
|
||||
await caller.invalidateControl();
|
||||
@ -161,3 +159,8 @@ export async function free_models() {
|
||||
}
|
||||
app.ui.dialog.element.style.zIndex = 10010;
|
||||
}
|
||||
|
||||
export function show_message(msg) {
|
||||
app.ui.dialog.show(msg);
|
||||
app.ui.dialog.element.style.zIndex = 10010;
|
||||
}
|
||||
@ -1,7 +1,32 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js"
|
||||
import { sleep } from "./common.js";
|
||||
import { sleep, show_message } from "./common.js";
|
||||
import { GroupNodeConfig, GroupNodeHandler } from "../../extensions/core/groupNode.js";
|
||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||
|
||||
let pack_map = {};
|
||||
let rpack_map = {};
|
||||
|
||||
function isValidVersionString(version) {
|
||||
const versionPattern = /^(\d+)\.(\d+)(\.(\d+))?$/;
|
||||
|
||||
const match = version.match(versionPattern);
|
||||
|
||||
return match !== null &&
|
||||
parseInt(match[1], 10) >= 0 &&
|
||||
parseInt(match[2], 10) >= 0 &&
|
||||
(!match[3] || parseInt(match[4], 10) >= 0);
|
||||
}
|
||||
|
||||
function register_pack_map(name, data) {
|
||||
if(data.packname) {
|
||||
pack_map[data.packname] = name;
|
||||
rpack_map[name] = [data.packname, data.category, data.version, data.datetime];
|
||||
}
|
||||
else {
|
||||
rpack_map[name] = [data.packname, data.category, data.version, data.datetime];
|
||||
}
|
||||
}
|
||||
|
||||
function storeGroupNode(name, data) {
|
||||
let extra = app.graph.extra;
|
||||
@ -9,6 +34,8 @@ function storeGroupNode(name, data) {
|
||||
let groupNodes = extra.groupNodes;
|
||||
if (!groupNodes) extra.groupNodes = groupNodes = {};
|
||||
groupNodes[name] = data;
|
||||
|
||||
register_pack_map(name, data);
|
||||
}
|
||||
|
||||
export async function load_components() {
|
||||
@ -21,6 +48,22 @@ export async function load_components() {
|
||||
|
||||
for(let name in components) {
|
||||
if(app.graph.extra?.groupNodes?.[name]) {
|
||||
if(data) {
|
||||
let data = components[name];
|
||||
|
||||
let category = data.packname;
|
||||
if(data.category) {
|
||||
category += "/" + data.category;
|
||||
}
|
||||
if(category == '') {
|
||||
category = 'components';
|
||||
}
|
||||
|
||||
const config = new GroupNodeConfig(name, data);
|
||||
await config.registerType(category);
|
||||
|
||||
register_pack_map(name, data);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -32,7 +75,16 @@ export async function load_components() {
|
||||
while(!success) {
|
||||
var success = false;
|
||||
try {
|
||||
await config.registerType();
|
||||
let category = nodeData.packname;
|
||||
if(nodeData.category) {
|
||||
category += "/" + nodeData.category;
|
||||
}
|
||||
if(category == '') {
|
||||
category = 'components';
|
||||
}
|
||||
|
||||
await config.registerType(category);
|
||||
register_pack_map(name, nodeData);
|
||||
}
|
||||
catch {
|
||||
let elapsed_time = Date.now() - start_time;
|
||||
@ -44,8 +96,6 @@ export async function load_components() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const groupNode = LiteGraph.createNode(`workflow/${name}`);
|
||||
}
|
||||
|
||||
// fallback1
|
||||
@ -64,7 +114,16 @@ export async function load_components() {
|
||||
while(!success) {
|
||||
var success = false;
|
||||
try {
|
||||
await config.registerType();
|
||||
let category = nodeData.packname;
|
||||
if(nodeData.workflow.category) {
|
||||
category += "/" + nodeData.category;
|
||||
}
|
||||
if(category == '') {
|
||||
category = 'components';
|
||||
}
|
||||
|
||||
await config.registerType(category);
|
||||
register_pack_map(name, nodeData);
|
||||
}
|
||||
catch {
|
||||
let elapsed_time = Date.now() - start_time;
|
||||
@ -76,8 +135,6 @@ export async function load_components() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const groupNode = LiteGraph.createNode(`workflow/${name}`);
|
||||
}
|
||||
|
||||
// fallback2
|
||||
@ -91,8 +148,18 @@ export async function load_components() {
|
||||
const config = new GroupNodeConfig(name, nodeData);
|
||||
while(!success) {
|
||||
var success = false;
|
||||
|
||||
try {
|
||||
await config.registerType();
|
||||
let category = nodeData.workflow.packname;
|
||||
if(nodeData.workflow.category) {
|
||||
category += "/" + nodeData.category;
|
||||
}
|
||||
if(category == '') {
|
||||
category = 'components';
|
||||
}
|
||||
|
||||
await config.registerType(category);
|
||||
register_pack_map(name, nodeData);
|
||||
}
|
||||
catch {
|
||||
let elapsed_time = Date.now() - start_time;
|
||||
@ -104,72 +171,31 @@ export async function load_components() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
async function save_as_component(node, version, prefix, nodename, packname, category) {
|
||||
let component_name = `${prefix}::${nodename}`;
|
||||
|
||||
let subgraph = app.graph.extra?.groupNodes?.[component_name];
|
||||
if(!subgraph) {
|
||||
app.ui.dialog.show(`Failed to retrieve the group node '${pure_name}'.`);
|
||||
return;
|
||||
subgraph = app.graph.extra?.groupNodes?.[node.comfyClass.substring(9)];
|
||||
}
|
||||
|
||||
if(node.comfyClass.includes('::')) {
|
||||
let component_name = node.comfyClass.substring(9);
|
||||
subgraph.version = version;
|
||||
subgraph.datetime = Date.now();
|
||||
subgraph.packname = packname;
|
||||
subgraph.category = category;
|
||||
|
||||
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(component_name, subgraph);
|
||||
const config = new GroupNodeConfig(component_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
|
||||
};
|
||||
|
||||
pack_map[packname] = component_name;
|
||||
rpack_map[component_name] = [body.pack_name, body.category, body.version, body.datetime];
|
||||
|
||||
const res = await api.fetchApi('/manager/component/save', {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@ -181,21 +207,30 @@ export async function save_as_component(node, app) {
|
||||
if(res.status == 200) {
|
||||
storeGroupNode(component_name, subgraph);
|
||||
const config = new GroupNodeConfig(component_name, subgraph);
|
||||
await config.registerType();
|
||||
|
||||
let category = body.workflow.packname;
|
||||
if(body.workflow.category) {
|
||||
category += "/" + body.workflow.category;
|
||||
}
|
||||
if(category == '') {
|
||||
category = 'components';
|
||||
}
|
||||
|
||||
await config.registerType(category);
|
||||
|
||||
let path = await res.text();
|
||||
app.ui.dialog.show(`Component '${component_name}' is saved into:\n${path}`);
|
||||
show_message(`Component '${component_name}' is saved into:\n${path}`);
|
||||
}
|
||||
else
|
||||
app.ui.dialog.show(`Failed to save component.`);
|
||||
show_message(`Failed to save component.`);
|
||||
}
|
||||
|
||||
async function import_component(component_name, subgraph) {
|
||||
if(confirm("Will you save component?\n(If canceled, the component won't be saved and can only be used within the current workflow.)")) {
|
||||
async function import_component(component_name, component, mode) {
|
||||
if(mode) {
|
||||
let body =
|
||||
{
|
||||
name: component_name,
|
||||
workflow: subgraph
|
||||
workflow: component
|
||||
};
|
||||
|
||||
const res = await api.fetchApi('/manager/component/save', {
|
||||
@ -205,14 +240,142 @@ async function import_component(component_name, subgraph) {
|
||||
});
|
||||
}
|
||||
|
||||
storeGroupNode(component_name, subgraph);
|
||||
const config = new GroupNodeConfig(component_name, subgraph);
|
||||
await config.registerType();
|
||||
let category = component.packname;
|
||||
if(component.category) {
|
||||
category += "/" + component.category;
|
||||
}
|
||||
if(category == '') {
|
||||
category = 'components';
|
||||
}
|
||||
|
||||
storeGroupNode(component_name, component);
|
||||
const config = new GroupNodeConfig(component_name, component);
|
||||
await config.registerType(category);
|
||||
}
|
||||
|
||||
// Using a timestamp prevents duplicate pastes and ensures the prevention of re-deletion of litegrapheditor_clipboard.
|
||||
let last_paste_timestamp = null;
|
||||
|
||||
function versionCompare(v1, v2) {
|
||||
let ver1;
|
||||
let ver2;
|
||||
if(v1 && v1 != '') {
|
||||
ver1 = v1.split('.');
|
||||
ver1[0] = parseInt(ver1[0]);
|
||||
ver1[1] = parseInt(ver1[1]);
|
||||
if(ver1.length == 2)
|
||||
ver1.push(0);
|
||||
else
|
||||
ver1[2] = parseInt(ver2[2]);
|
||||
}
|
||||
else {
|
||||
ver1 = [0,0,0];
|
||||
}
|
||||
|
||||
if(v2 && v2 != '') {
|
||||
ver2 = v2.split('.');
|
||||
ver2[0] = parseInt(ver2[0]);
|
||||
ver2[1] = parseInt(ver2[1]);
|
||||
if(ver2.length == 2)
|
||||
ver2.push(0);
|
||||
else
|
||||
ver2[2] = parseInt(ver2[2]);
|
||||
}
|
||||
else {
|
||||
ver2 = [0,0,0];
|
||||
}
|
||||
|
||||
if(ver1[0] > ver2[0])
|
||||
return -1;
|
||||
else if(ver1[0] < ver2[0])
|
||||
return 1;
|
||||
|
||||
if(ver1[1] > ver2[1])
|
||||
return -1;
|
||||
else if(ver1[1] < ver2[1])
|
||||
return 1;
|
||||
|
||||
if(ver1[2] > ver2[2])
|
||||
return -1;
|
||||
else if(ver1[2] < ver2[2])
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function checkVersion(name, component) {
|
||||
let msg = '';
|
||||
if(rpack_map[name]) {
|
||||
let old_version = rpack_map[name][2];
|
||||
if(!old_version || old_version == '') {
|
||||
msg = ` '${name}' Upgrade (V0.0 -> V${component.version})`;
|
||||
}
|
||||
else {
|
||||
let c = versionCompare(old_version, component.version);
|
||||
if(c < 0) {
|
||||
msg = ` '${name}' Downgrade (V${old_version} -> V${component.version})`;
|
||||
}
|
||||
else if(c > 0) {
|
||||
msg = ` '${name}' Upgrade (V${old_version} -> V${component.version})`;
|
||||
}
|
||||
else {
|
||||
msg = ` '${name}' Same version (V${component.version})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
msg = `'${name}' NEW (V${component.version})`;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
function handle_import_components(components) {
|
||||
let msg = 'Components:\n';
|
||||
let cnt = 0;
|
||||
for(let name in components) {
|
||||
let component = components[name];
|
||||
let v = checkVersion(name, component);
|
||||
|
||||
if(cnt < 10) {
|
||||
msg += v + '\n';
|
||||
}
|
||||
else if (cnt == 10) {
|
||||
msg += '...\n';
|
||||
}
|
||||
else {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
cnt++;
|
||||
}
|
||||
|
||||
let last_name = null;
|
||||
msg += '\nWill you load components?\n';
|
||||
if(confirm(msg)) {
|
||||
let mode = confirm('\nWill you save components?\n(cancel=load without save)');
|
||||
|
||||
for(let name in components) {
|
||||
let component = components[name];
|
||||
import_component(name, component, mode);
|
||||
last_name = name;
|
||||
}
|
||||
|
||||
if(mode) {
|
||||
show_message('Components are saved.');
|
||||
}
|
||||
else {
|
||||
show_message('Components are loaded.');
|
||||
}
|
||||
}
|
||||
|
||||
if(cnt == 1 && last_name) {
|
||||
const node = LiteGraph.createNode(`workflow/${last_name}`);
|
||||
node.pos = [app.canvas.graph_mouse[0], app.canvas.graph_mouse[1]];
|
||||
app.canvas.graph.add(node, false);
|
||||
}
|
||||
}
|
||||
|
||||
function handlePaste(e) {
|
||||
let data = (e.clipboardData || window.clipboardData);
|
||||
const items = data.items;
|
||||
@ -223,14 +386,7 @@ function handlePaste(e) {
|
||||
let json_data = JSON.parse(data);
|
||||
if(json_data.kind == 'ComfyUI Components' && last_paste_timestamp != json_data.timestamp) {
|
||||
last_paste_timestamp = json_data.timestamp;
|
||||
|
||||
let msg = 'Components are added:\n';
|
||||
for(let name in json_data.components) {
|
||||
let subgraph = json_data.components[name];
|
||||
import_component(name, subgraph);
|
||||
msg += ' - ' + name + '\n';
|
||||
}
|
||||
app.ui.dialog.show(msg);
|
||||
handle_import_components(json_data.components);
|
||||
|
||||
// disable paste node
|
||||
localStorage.removeItem("litegrapheditor_clipboard", null);
|
||||
@ -249,3 +405,263 @@ function handlePaste(e) {
|
||||
document.addEventListener("paste", handlePaste);
|
||||
|
||||
|
||||
export class ComponentBuilderDialog extends ComfyDialog {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
clear() {
|
||||
while (this.element.children.length) {
|
||||
this.element.removeChild(this.element.children[0]);
|
||||
}
|
||||
}
|
||||
|
||||
show() {
|
||||
this.invalidateControl();
|
||||
|
||||
this.element.style.display = "block";
|
||||
this.element.style.zIndex = 10001;
|
||||
this.element.style.width = "500px";
|
||||
this.element.style.height = "450px";
|
||||
}
|
||||
|
||||
invalidateControl() {
|
||||
this.clear();
|
||||
|
||||
let self = this;
|
||||
|
||||
const close_button = $el("button", { id: "cm-close-button", type: "button", textContent: "Close", onclick: () => self.close() });
|
||||
this.save_button = $el("button",
|
||||
{ id: "cm-save-button", type: "button", textContent: "Save", onclick: () =>
|
||||
{
|
||||
save_as_component(self.target_node, self.version_string.value.trim(), self.node_prefix.value.trim(),
|
||||
self.getNodeName(), self.getPackName(), self.category.value.trim());
|
||||
}
|
||||
});
|
||||
|
||||
let default_nodename = this.target_node.comfyClass.substring(9).trim();
|
||||
|
||||
let default_packname = "";
|
||||
if(rpack_map[default_nodename]) {
|
||||
default_packname = rpack_map[default_nodename][0];
|
||||
}
|
||||
if(!default_packname) {
|
||||
default_packname = '';
|
||||
}
|
||||
|
||||
let default_category = "";
|
||||
if(rpack_map[default_nodename]) {
|
||||
default_category = rpack_map[default_nodename][1];
|
||||
}
|
||||
if(!default_category) {
|
||||
default_category = '';
|
||||
}
|
||||
|
||||
if(rpack_map[default_nodename]) {
|
||||
this.default_ver = rpack_map[default_nodename][2];
|
||||
}
|
||||
if(!this.default_ver) {
|
||||
this.default_ver = '0.0';
|
||||
}
|
||||
|
||||
let delimiterIndex = default_nodename.indexOf('::');
|
||||
let default_prefix = "";
|
||||
if(delimiterIndex != -1) {
|
||||
default_prefix = default_nodename.substring(0, delimiterIndex);
|
||||
default_nodename = default_nodename.substring(delimiterIndex + 2);
|
||||
}
|
||||
|
||||
if(!default_prefix) {
|
||||
this.save_button.disabled = true;
|
||||
}
|
||||
|
||||
this.pack_list = this.createPackListCombo();
|
||||
|
||||
let version_string = this.createLabeledInput('input version (e.g. 1.0)', '*Version : ', this.default_ver);
|
||||
this.version_string = version_string[1];
|
||||
this.version_string.disabled = true;
|
||||
|
||||
let node_prefix = this.createLabeledInput('input node prefix (e.g. mypack)', '*Prefix : ', default_prefix);
|
||||
this.node_prefix = node_prefix[1];
|
||||
|
||||
let manual_nodename = this.createLabeledInput('input node name (e.g. MAKE_BASIC_PIPE)', 'Nodename : ', default_nodename);
|
||||
this.manual_nodename = manual_nodename[1];
|
||||
|
||||
let manual_packname = this.createLabeledInput('input pack name (e.g. mypack)', 'Packname : ', default_packname);
|
||||
this.manual_packname = manual_packname[1];
|
||||
|
||||
let category = this.createLabeledInput('input category (e.g. util/pipe)', 'Category : ', default_category);
|
||||
this.category = category[1];
|
||||
|
||||
this.node_label = this.createNodeLabel();
|
||||
|
||||
let author_mode = this.createAuthorModeCheck();
|
||||
this.author_mode = author_mode[0];
|
||||
|
||||
const content =
|
||||
$el("div.comfy-modal-content",
|
||||
[
|
||||
$el("tr.cm-title", {}, [
|
||||
$el("font", {size:6, color:"white"}, [`ComfyUI-Manager: Component Builder`])]
|
||||
),
|
||||
$el("br", {}, []),
|
||||
$el("div.cm-menu-container",
|
||||
[
|
||||
author_mode[0],
|
||||
author_mode[1],
|
||||
category[0],
|
||||
node_prefix[0],
|
||||
manual_nodename[0],
|
||||
manual_packname[0],
|
||||
version_string[0],
|
||||
this.pack_list,
|
||||
$el("br", {}, []),
|
||||
this.node_label
|
||||
]),
|
||||
|
||||
$el("br", {}, []),
|
||||
this.save_button,
|
||||
close_button,
|
||||
]
|
||||
);
|
||||
|
||||
content.style.width = '100%';
|
||||
content.style.height = '100%';
|
||||
|
||||
this.element = $el("div.comfy-modal", { id:'cm-manager-dialog', parent: document.body }, [ content ]);
|
||||
}
|
||||
|
||||
validateInput() {
|
||||
let msg = "";
|
||||
|
||||
if(!isValidVersionString(this.version_string.value)) {
|
||||
msg += 'Invalid version string: '+event.value+"\n";
|
||||
}
|
||||
|
||||
if(this.node_prefix.value.trim() == '') {
|
||||
msg += 'Node prefix cannot be empty\n';
|
||||
}
|
||||
|
||||
if(this.manual_nodename.value.trim() == '') {
|
||||
msg += 'Node name cannot be empty\n';
|
||||
}
|
||||
|
||||
if(msg != '') {
|
||||
// alert(msg);
|
||||
}
|
||||
|
||||
this.save_button.disabled = msg != "";
|
||||
}
|
||||
|
||||
getPackName() {
|
||||
if(this.pack_list.selectedIndex == 0) {
|
||||
return this.manual_packname.value.trim();
|
||||
}
|
||||
|
||||
return this.pack_list.value.trim();
|
||||
}
|
||||
|
||||
getNodeName() {
|
||||
if(this.manual_nodename.value.trim() != '') {
|
||||
return this.manual_nodename.value.trim();
|
||||
}
|
||||
|
||||
return this.target_node.comfyClass.substring(9);
|
||||
}
|
||||
|
||||
createAuthorModeCheck() {
|
||||
let check = $el("input",{type:'checkbox', id:"author-mode"},[])
|
||||
const check_label = $el("label",{for:"author-mode"},["Enable author mode"]);
|
||||
check_label.style.color = "var(--fg-color)";
|
||||
check_label.style.cursor = "pointer";
|
||||
check.checked = false;
|
||||
|
||||
let self = this;
|
||||
check.onchange = () => {
|
||||
self.version_string.disabled = !check.checked;
|
||||
|
||||
if(!check.checked) {
|
||||
self.version_string.value = self.default_ver;
|
||||
}
|
||||
else {
|
||||
alert('If you are not the author, it is not recommended to change the version, as it may cause component update issues.');
|
||||
}
|
||||
};
|
||||
|
||||
return [check, check_label];
|
||||
}
|
||||
|
||||
createNodeLabel() {
|
||||
let label = $el('p');
|
||||
label.className = 'cb-node-label';
|
||||
label.textContent = " _::" + this.target_node.comfyClass.substring(9);
|
||||
return label;
|
||||
}
|
||||
|
||||
createLabeledInput(placeholder, label, value) {
|
||||
let textbox = $el('input.cb-widget-input', {type:'text', placeholder:placeholder, value:value}, []);
|
||||
|
||||
let self = this;
|
||||
textbox.onchange = () => {
|
||||
this.validateInput.call(self);
|
||||
this.node_label.textContent = this.node_prefix.value + "::" + this.manual_nodename.value;
|
||||
}
|
||||
let row = $el('span.cb-widget', {}, [ $el('span.cb-widget-input-label', label), textbox]);
|
||||
|
||||
return [row, textbox];
|
||||
}
|
||||
|
||||
createPackListCombo() {
|
||||
let combo = document.createElement("select");
|
||||
combo.className = "cb-widget";
|
||||
let default_packname_option = { value: '##manual', text: 'Packname: Manual' };
|
||||
|
||||
combo.appendChild($el('option', default_packname_option, []));
|
||||
for(let name in pack_map) {
|
||||
combo.appendChild($el('option', { value: name, text: 'Packname: '+ name }, []));
|
||||
}
|
||||
|
||||
let self = this;
|
||||
combo.onchange = function () {
|
||||
if(combo.selectedIndex == 0) {
|
||||
self.manual_packname.disabled = false;
|
||||
}
|
||||
else {
|
||||
self.manual_packname.disabled = true;
|
||||
}
|
||||
};
|
||||
|
||||
return combo;
|
||||
}
|
||||
}
|
||||
|
||||
let orig_handleFile = app.handleFile;
|
||||
|
||||
function handleFile(file) {
|
||||
if (file.name?.endsWith(".json") || file.name?.endsWith(".pack")) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async () => {
|
||||
let is_component = false;
|
||||
const jsonContent = JSON.parse(reader.result);
|
||||
for(let name in jsonContent) {
|
||||
let cand = jsonContent[name];
|
||||
is_component = cand.datetime && cand.version;
|
||||
break;
|
||||
}
|
||||
|
||||
if(is_component) {
|
||||
handle_import_components(jsonContent);
|
||||
}
|
||||
else {
|
||||
orig_handleFile.call(app, file);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
orig_handleFile.call(app, file);
|
||||
}
|
||||
|
||||
app.handleFile = handleFile;
|
||||
|
||||
444
misc/Impact.pack
Normal file
444
misc/Impact.pack
Normal file
@ -0,0 +1,444 @@
|
||||
{
|
||||
"Impact::MAKE_BASIC_PIPE": {
|
||||
"category": "",
|
||||
"config": {
|
||||
"1": {
|
||||
"input": {
|
||||
"text": {
|
||||
"name": "Positive prompt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"input": {
|
||||
"text": {
|
||||
"name": "Negative prompt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"datetime": 1705418802481,
|
||||
"external": [],
|
||||
"links": [
|
||||
[
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
1,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
4,
|
||||
4,
|
||||
"CONDITIONING"
|
||||
]
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"flags": {},
|
||||
"index": 0,
|
||||
"mode": 0,
|
||||
"order": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "MODEL",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "MODEL"
|
||||
},
|
||||
{
|
||||
"links": [],
|
||||
"name": "CLIP",
|
||||
"shape": 3,
|
||||
"slot_index": 1,
|
||||
"type": "CLIP"
|
||||
},
|
||||
{
|
||||
"links": [],
|
||||
"name": "VAE",
|
||||
"shape": 3,
|
||||
"slot_index": 2,
|
||||
"type": "VAE"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
550,
|
||||
360
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"widgets_values": [
|
||||
"SDXL/sd_xl_base_1.0_0.9vae.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 1,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "clip",
|
||||
"type": "CLIP"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 1,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "CONDITIONING",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "CONDITIONING"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
940,
|
||||
480
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"size": {
|
||||
"0": 263,
|
||||
"1": 99
|
||||
},
|
||||
"title": "Positive",
|
||||
"type": "CLIPTextEncode",
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 2,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "clip",
|
||||
"type": "CLIP"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 2,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "CONDITIONING",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "CONDITIONING"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
940,
|
||||
640
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"size": {
|
||||
"0": 263,
|
||||
"1": 99
|
||||
},
|
||||
"title": "Negative",
|
||||
"type": "CLIPTextEncode",
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 3,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "model",
|
||||
"type": "MODEL"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "clip",
|
||||
"type": "CLIP"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "vae",
|
||||
"type": "VAE"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 3,
|
||||
"outputs": [
|
||||
{
|
||||
"links": null,
|
||||
"name": "basic_pipe",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "BASIC_PIPE"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
1320,
|
||||
360
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ToBasicPipe"
|
||||
},
|
||||
"size": {
|
||||
"0": 241.79998779296875,
|
||||
"1": 106
|
||||
},
|
||||
"type": "ToBasicPipe"
|
||||
}
|
||||
],
|
||||
"packname": "Impact",
|
||||
"version": "1.0"
|
||||
},
|
||||
"Impact::SIMPLE_DETAILER_PIPE": {
|
||||
"category": "",
|
||||
"config": {
|
||||
"0": {
|
||||
"output": {
|
||||
"0": {
|
||||
"visible": false
|
||||
},
|
||||
"1": {
|
||||
"visible": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"input": {
|
||||
"Select to add LoRA": {
|
||||
"visible": false
|
||||
},
|
||||
"Select to add Wildcard": {
|
||||
"visible": false
|
||||
},
|
||||
"wildcard": {
|
||||
"visible": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"datetime": 1705419147116,
|
||||
"external": [],
|
||||
"links": [
|
||||
[
|
||||
null,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
6,
|
||||
"BASIC_PIPE"
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
13,
|
||||
"BBOX_DETECTOR"
|
||||
],
|
||||
[
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
15,
|
||||
"SAM_MODEL"
|
||||
]
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"flags": {},
|
||||
"index": 0,
|
||||
"mode": 0,
|
||||
"order": 2,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "BBOX_DETECTOR",
|
||||
"shape": 3,
|
||||
"type": "BBOX_DETECTOR"
|
||||
},
|
||||
{
|
||||
"links": null,
|
||||
"name": "SEGM_DETECTOR",
|
||||
"shape": 3,
|
||||
"type": "SEGM_DETECTOR"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
590,
|
||||
830
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "UltralyticsDetectorProvider"
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 78
|
||||
},
|
||||
"type": "UltralyticsDetectorProvider",
|
||||
"widgets_values": [
|
||||
"bbox/Eyeful_v1.pt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 1,
|
||||
"mode": 0,
|
||||
"order": 3,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "SAM_MODEL",
|
||||
"shape": 3,
|
||||
"type": "SAM_MODEL"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
590,
|
||||
960
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SAMLoader"
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"type": "SAMLoader",
|
||||
"widgets_values": [
|
||||
"sam_vit_b_01ec64.pth",
|
||||
"AUTO"
|
||||
]
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 2,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "basic_pipe",
|
||||
"type": "BASIC_PIPE"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "bbox_detector",
|
||||
"slot_index": 1,
|
||||
"type": "BBOX_DETECTOR"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "sam_model_opt",
|
||||
"slot_index": 2,
|
||||
"type": "SAM_MODEL"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "segm_detector_opt",
|
||||
"type": "SEGM_DETECTOR"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "detailer_hook",
|
||||
"type": "DETAILER_HOOK"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 5,
|
||||
"outputs": [
|
||||
{
|
||||
"links": null,
|
||||
"name": "detailer_pipe",
|
||||
"shape": 3,
|
||||
"type": "DETAILER_PIPE"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
1044,
|
||||
812
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "BasicPipeToDetailerPipe"
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 204
|
||||
},
|
||||
"type": "BasicPipeToDetailerPipe",
|
||||
"widgets_values": [
|
||||
"",
|
||||
"Select the LoRA to add to the text",
|
||||
"Select the Wildcard to add to the text"
|
||||
]
|
||||
}
|
||||
],
|
||||
"packname": "Impact",
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user