|
|
|
import { app } from "../../scripts/app.js";
|
|
|
|
import { api } from "../../scripts/api.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 = {};
|
|
|
|
|
|
|
|
export function getPureName(node) {
|
|
|
|
// group nodes/
|
|
|
|
let category = null;
|
|
|
|
if(node.category) {
|
|
|
|
category = node.category.substring(12);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
category = node.constructor.category?.substring(12);
|
|
|
|
}
|
|
|
|
if(category) {
|
|
|
|
let purename = node.comfyClass.substring(category.length+1);
|
|
|
|
return purename;
|
|
|
|
}
|
|
|
|
else if(node.comfyClass.startsWith('workflow/')) {
|
|
|
|
return node.comfyClass.substring(9);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return node.comfyClass;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rpack_map[name] = data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function storeGroupNode(name, data, register=true) {
|
|
|
|
let extra = app.graph.extra;
|
|
|
|
if (!extra) app.graph.extra = extra = {};
|
|
|
|
let groupNodes = extra.groupNodes;
|
|
|
|
if (!groupNodes) extra.groupNodes = groupNodes = {};
|
|
|
|
groupNodes[name] = data;
|
|
|
|
|
|
|
|
if(register) {
|
|
|
|
register_pack_map(name, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function load_components() {
|
|
|
|
let data = await api.fetchApi('/manager/component/loads', {method: "POST"});
|
|
|
|
let components = await data.json();
|
|
|
|
|
|
|
|
let start_time = Date.now();
|
|
|
|
let failed = [];
|
|
|
|
let failed2 = [];
|
|
|
|
|
|
|
|
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);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let nodeData = components[name];
|
|
|
|
|
|
|
|
storeGroupNode(name, nodeData);
|
|
|
|
|
|
|
|
const config = new GroupNodeConfig(name, nodeData);
|
|
|
|
|
|
|
|
while(true) {
|
|
|
|
try {
|
|
|
|
let category = nodeData.packname;
|
|
|
|
if(nodeData.category) {
|
|
|
|
category += "/" + nodeData.category;
|
|
|
|
}
|
|
|
|
if(category == '') {
|
|
|
|
category = 'components';
|
|
|
|
}
|
|
|
|
|
|
|
|
await config.registerType(category);
|
|
|
|
register_pack_map(name, nodeData);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
catch {
|
|
|
|
let elapsed_time = Date.now() - start_time;
|
|
|
|
if (elapsed_time > 5000) {
|
|
|
|
failed.push(name);
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
await sleep(100);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(true) {
|
|
|
|
try {
|
|
|
|
let category = nodeData.packname;
|
|
|
|
if(nodeData.workflow.category) {
|
|
|
|
category += "/" + nodeData.category;
|
|
|
|
}
|
|
|
|
if(category == '') {
|
|
|
|
category = 'components';
|
|
|
|
}
|
|
|
|
|
|
|
|
await config.registerType(category);
|
|
|
|
register_pack_map(name, nodeData);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
catch {
|
|
|
|
let elapsed_time = Date.now() - start_time;
|
|
|
|
if (elapsed_time > 10000) {
|
|
|
|
failed2.push(name);
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
await sleep(100);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// fallback2
|
|
|
|
for(let name in failed2) {
|
|
|
|
let name = failed2[i];
|
|
|
|
|
|
|
|
let nodeData = components[name];
|
|
|
|
|
|
|
|
storeGroupNode(name, nodeData);
|
|
|
|
|
|
|
|
const config = new GroupNodeConfig(name, nodeData);
|
|
|
|
while(true) {
|
|
|
|
try {
|
|
|
|
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);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
catch {
|
|
|
|
let elapsed_time = Date.now() - start_time;
|
|
|
|
if (elapsed_time > 30000) {
|
|
|
|
failed.push(name);
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
await sleep(100);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
subgraph = app.graph.extra?.groupNodes?.[getPureName(node)];
|
|
|
|
}
|
|
|
|
|
|
|
|
subgraph.version = version;
|
|
|
|
subgraph.datetime = Date.now();
|
|
|
|
subgraph.packname = packname;
|
|
|
|
subgraph.category = category;
|
|
|
|
|
|
|
|
let body =
|
|
|
|
{
|
|
|
|
name: component_name,
|
|
|
|
workflow: subgraph
|
|
|
|
};
|
|
|
|
|
|
|
|
pack_map[packname] = component_name;
|
|
|
|
rpack_map[component_name] = 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);
|
|
|
|
|
|
|
|
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();
|
|
|
|
show_message(`Component '${component_name}' is saved into:\n${path}`);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
show_message(`Failed to save component.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function import_component(component_name, component, mode) {
|
|
|
|
if(mode) {
|
|
|
|
let body =
|
|
|
|
{
|
|
|
|
name: component_name,
|
|
|
|
workflow: component
|
|
|
|
};
|
|
|
|
|
|
|
|
const res = await api.fetchApi('/manager/component/save', {
|
|
|
|
method: "POST",
|
|
|
|
headers: { "Content-Type": "application/json", },
|
|
|
|
body: JSON.stringify(body)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
function restore_to_loaded_component(component_name) {
|
|
|
|
if(rpack_map[component_name]) {
|
|
|
|
let component = rpack_map[component_name];
|
|
|
|
storeGroupNode(component_name, component, false);
|
|
|
|
const config = new GroupNodeConfig(component_name, component);
|
|
|
|
config.registerType(component.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].version;
|
|
|
|
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;
|
|
|
|
for(const item of items) {
|
|
|
|
if(item.kind == 'string' && item.type == 'text/plain') {
|
|
|
|
data = data.getData("text/plain");
|
|
|
|
try {
|
|
|
|
let json_data = JSON.parse(data);
|
|
|
|
if(json_data.kind == 'ComfyUI Components' && last_paste_timestamp != json_data.timestamp) {
|
|
|
|
last_paste_timestamp = json_data.timestamp;
|
|
|
|
handle_import_components(json_data.components);
|
|
|
|
|
|
|
|
// disable paste node
|
|
|
|
localStorage.removeItem("litegrapheditor_clipboard", null);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
console.log('This components are already pasted: ignored');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch {
|
|
|
|
// nothing to do
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = getPureName(this.target_node).trim();
|
|
|
|
|
|
|
|
let groupNode = app.graph.extra.groupNodes[default_nodename];
|
|
|
|
let default_packname = groupNode.packname;
|
|
|
|
|
|
|
|
if(!default_packname) {
|
|
|
|
default_packname = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
let default_category = groupNode.category;
|
|
|
|
if(!default_category) {
|
|
|
|
default_category = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
this.default_ver = groupNode.version;
|
|
|
|
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 getPureName(this.target_node);
|
|
|
|
}
|
|
|
|
|
|
|
|
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';
|
|
|
|
if(this.target_node.comfyClass.includes('::'))
|
|
|
|
label.textContent = getPureName(this.target_node);
|
|
|
|
else
|
|
|
|
label.textContent = " _::" + getPureName(this.target_node);
|
|
|
|
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;
|
|
|
|
|
|
|
|
let current_component_policy = 'workflow';
|
|
|
|
try {
|
|
|
|
api.fetchApi('/manager/component/policy')
|
|
|
|
.then(response => response.text())
|
|
|
|
.then(data => { current_component_policy = data; });
|
|
|
|
}
|
|
|
|
catch {}
|
|
|
|
|
|
|
|
function getChangedVersion(groupNodes) {
|
|
|
|
if(!Object.keys(pack_map).length || !groupNodes)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
let res = {};
|
|
|
|
for(let component_name in groupNodes) {
|
|
|
|
let data = groupNodes[component_name];
|
|
|
|
|
|
|
|
if(rpack_map[component_name]) {
|
|
|
|
let v = versionCompare(data.version, rpack_map[component_name].version);
|
|
|
|
res[component_name] = v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
const loadGraphData = app.loadGraphData;
|
|
|
|
app.loadGraphData = async function () {
|
|
|
|
if(arguments.length == 0)
|
|
|
|
return await loadGraphData.apply(this, arguments);
|
|
|
|
|
|
|
|
let graphData = arguments[0];
|
|
|
|
let groupNodes = graphData.extra?.groupNodes;
|
|
|
|
let res = getChangedVersion(groupNodes);
|
|
|
|
|
|
|
|
if(res) {
|
|
|
|
let target_components = null;
|
|
|
|
switch(current_component_policy) {
|
|
|
|
case 'higher':
|
|
|
|
target_components = Object.keys(res).filter(key => res[key] == 1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'mine':
|
|
|
|
target_components = Object.keys(res);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
if(target_components) {
|
|
|
|
for(let i in target_components) {
|
|
|
|
let component_name = target_components[i];
|
|
|
|
let component = rpack_map[component_name];
|
|
|
|
if(component && graphData.extra?.groupNodes) {
|
|
|
|
graphData.extra.groupNodes[component_name] = component;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
console.log('Empty components: policy ignored');
|
|
|
|
}
|
|
|
|
|
|
|
|
arguments[0] = graphData;
|
|
|
|
return await loadGraphData.apply(this, arguments);
|
|
|
|
};
|
|
|
|
|
|
|
|
export function set_component_policy(v) {
|
|
|
|
current_component_policy = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
let graphToPrompt = app.graphToPrompt;
|
|
|
|
app.graphToPrompt = async function () {
|
|
|
|
let p = await graphToPrompt.call(app);
|
|
|
|
try {
|
|
|
|
let groupNodes = p.workflow.extra?.groupNodes;
|
|
|
|
if(groupNodes) {
|
|
|
|
p.workflow.extra = { ... p.workflow.extra};
|
|
|
|
|
|
|
|
// get used group nodes
|
|
|
|
let used_group_nodes = new Set();
|
|
|
|
for(let node of p.workflow.nodes) {
|
|
|
|
if(node.type.startsWith('workflow/')) {
|
|
|
|
used_group_nodes.add(node.type.substring(9));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove unused group nodes
|
|
|
|
let new_groupNodes = {};
|
|
|
|
for (let key in p.workflow.extra.groupNodes) {
|
|
|
|
if (used_group_nodes.has(key)) {
|
|
|
|
new_groupNodes[key] = p.workflow.extra.groupNodes[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p.workflow.extra.groupNodes = new_groupNodes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(e) {
|
|
|
|
console.log(`Failed to filtering group nodes: ${e}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|