diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index d059fb14..da066cf8 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -16,7 +16,8 @@ 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 { ComponentBuilderDialog, load_components, set_component_policy, getPureName } from "./components-manager.js"; +import { ComponentBuilderDialog, load_components, set_component_policy, getPureName } from "./component-builder.js"; +import { ComponentsManager } from "./components-manager.js"; var docStyle = document.createElement('style'); docStyle.innerHTML = ` @@ -172,6 +173,10 @@ docStyle.innerHTML = ` overflow-x: hidden; overflow: auto; } + +#components-grid tr { + height: 50px; +} `; document.head.appendChild(docStyle); @@ -955,7 +960,17 @@ class ManagerMenuDialog extends ComfyDialog { type: "button", textContent: "Unload models", onclick: () => { free_models(); } - }) + }), + $el("button.cm-experimental-button", { + type: "button", + textContent: "Manage Components", + onclick: + () => { + if(!ComponentsManager.instance) + ComponentsManager.instance = new ComponentsManager(app, self); + ComponentsManager.instance.show(); + } + }), ]), ]; } diff --git a/js/component-builder.js b/js/component-builder.js new file mode 100644 index 00000000..c9dc4ef5 --- /dev/null +++ b/js/component-builder.js @@ -0,0 +1,816 @@ +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 isInstalled(name) { + return rpack_map[name] != undefined; +} + +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); + 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, author, prefix, nodename, packname, category) { + let component_name = nodenam; + + if(prefix) { + 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.author = author; + 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 = "480px"; + } + + 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.author.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 default_author = groupNode.author; + if(!default_author) { + default_author = ''; + } + + 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 author = this.createLabeledInput('input author (e.g. Dr.Lt.Data)', 'Author : ', default_author); + this.author = author[1]; + + 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], + author[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; +} diff --git a/js/components-manager.js b/js/components-manager.js index 248a74d2..a3a72f32 100644 --- a/js/components-manager.js +++ b/js/components-manager.js @@ -1,809 +1,447 @@ 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"; +import { isInstalled } from "./component-builder.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); - 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, author, 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.author = author; - 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(); - } +export class ComponentsManager extends ComfyDialog { + static instance = null; clear() { + + } + + constructor(app, manager_dialog) { + super(); + this.manager_dialog = manager_dialog; + this.search_keyword = ''; + this.element = $el("div.comfy-modal", { parent: document.body }, []); + this.install_buttons = []; + } + + apply_searchbox(data) { + let keyword = this.search_box.value.toLowerCase(); + for(let i in this.grid_rows) { + let data = this.grid_rows[i].data; + let content = data.author.toLowerCase() + data.packname.toLowerCase() + data.category.toLowerCase(); + + if(this.filter && this.filter != '*') { + if(this.filter == 'Installed') { + this.grid_rows[i].control.style.display = null; + } + else { + this.grid_rows[i].control.style.display = 'none'; + } + } + + if(keyword == "") + this.grid_rows[i].control.style.display = null; + else if(content.includes(keyword)) { + this.grid_rows[i].control.style.display = null; + } + else { + this.grid_rows[i].control.style.display = 'none'; + } + } + } + + getNodes() { + let groupNodes = app.graph.extra?.groupNodes; + let res = []; + + if(groupNodes) { + for(let name in groupNodes) { + let item = groupNodes[name]; + + let state = "Installed"; + if(!isInstalled(name)) { + if(item.prefix) + state = "NotInstalled"; + else { +// state = "NotComponent"; + continue; + } + } + + let data = + { + "version": item.version, + "packname": item.packname, + "author": item.author, + "name": name, + "category": item.category, + "node": item, + "state": state + }; + + if(!data.author) + data.author = ''; + + res.push(data); + } + } + + return res; + } + + invalidate_check(self, checked, installed) { + + } + + async invalidateControl() { + this.clear(); + + // invalidate + this.data = this.getNodes(); + while (this.element.children.length) { this.element.removeChild(this.element.children[0]); } + + this.createHeaderControls(); + await this.createGrid(); + this.apply_searchbox(this.data); + this.createBottomControls(); } - show() { - this.invalidateControl(); + void uninstall_components(name) { - this.element.style.display = "block"; - this.element.style.zIndex = 10001; - this.element.style.width = "500px"; - this.element.style.height = "480px"; } - invalidateControl() { - this.clear(); + void install_components(data) { + save_as_component(data.node, ) + } + + async createGrid() { + var grid = document.createElement('table'); + grid.setAttribute('id', 'components-grid'); + + this.grid_rows = {}; 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.author.value.trim(), self.node_prefix.value.trim(), - self.getNodeName(), self.getPackName(), self.category.value.trim()); - } + var thead = document.createElement('thead'); + var tbody = document.createElement('tbody'); + + var headerRow = document.createElement('tr'); + thead.style.position = "sticky"; + thead.style.top = "0px"; + thead.style.width = "100%"; + thead.style.borderCollapse = "collapse"; + + var header0 = document.createElement('th'); + header0.style.width = "20px"; + this.checkbox_all = $el("input",{type:'checkbox', id:'check_all'},[]); + header0.appendChild(this.checkbox_all); + this.checkbox_all.checked = false; + this.checkbox_all.disabled = true; + this.checkbox_all.addEventListener('change', function() { self.check_all.call(self, self.checkbox_all.checked); }); + + var header1 = document.createElement('th'); + header1.innerHTML = '  ID  '; + header1.style.width = "20px"; + var header2 = document.createElement('th'); + header2.innerHTML = 'Packname'; + header2.style.width = "150px"; + var header3 = document.createElement('th'); + header3.innerHTML = 'Author'; + header3.style.width = "150px"; + var header4 = document.createElement('th'); + header4.innerHTML = 'Name'; + header4.style.width = "calc(100% - 450px)"; + var header5 = document.createElement('th'); + header5.innerHTML = 'Action'; + header5.style.width = "130px"; + header4.style.maxWidth = "500px"; + + header0.style.position = "sticky"; + header0.style.top = "0px"; + header1.style.position = "sticky"; + header1.style.top = "0px"; + header2.style.position = "sticky"; + header2.style.top = "0px"; + header3.style.position = "sticky"; + header3.style.top = "0px"; + header4.style.position = "sticky"; + header4.style.top = "0px"; + header5.style.position = "sticky"; + header5.style.top = "0px"; + + thead.appendChild(headerRow); + headerRow.appendChild(header0); + headerRow.appendChild(header1); + headerRow.appendChild(header2); + headerRow.appendChild(header3); + headerRow.appendChild(header4); + headerRow.appendChild(header5); + + headerRow.style.backgroundColor = "Black"; + headerRow.style.color = "White"; + headerRow.style.textAlign = "center"; + headerRow.style.width = "100%"; + headerRow.style.padding = "0"; + + grid.appendChild(thead); + grid.appendChild(tbody); + + if(this.data) + for (var i = 0; i < this.data.length; i++) { + const data = this.data[i]; + let dataRow = document.createElement('tr'); + dataRow.style.lineHeight = '50px'; + + let data0 = document.createElement('td'); + let checkbox = $el("input",{type:'checkbox', id:`check_${i}`},[]); + data0.appendChild(checkbox); + checkbox.checked = false; + checkbox.addEventListener('change', function() { self.invalidate_checks.call(self, checkbox.checked, data.state); }); + + var data1 = document.createElement('td'); + data1.style.textAlign = "center"; + data1.innerHTML = i+1; + + var data2 = document.createElement('td'); + data2.style.maxWidth = "150px"; + data2.className = "cm-component-packname" + data2.textContent = `${data.packname}`; + data2.style.whiteSpace = "nowrap"; + data2.style.overflow = "hidden"; + data2.style.textOverflow = "ellipsis"; + + var data3 = document.createElement('td'); + data3.style.maxWidth = "150px"; + data3.style.wordWrap = "break-word"; + data3.className = "cm-component-author" + data3.textContent = `${data.packname}`; + + var data4 = document.createElement('td'); + data4.style.wordWrap = "break-word"; + data4.className = "cm-component-name" + data4.textContent = `${data.name}`; + data4.style.width = "calc(100% - 450px)"; + + var data5 = document.createElement('td'); + data5.style.maxWidth = "130px"; + data5.style.textAlign = "center"; + data5.style.wordWrap = "break-word"; + data5.className = "cm-component-action"; + + var installBtn = document.createElement('button'); + installBtn.className = "cm-btn-install"; + var installBtn2 = null; + var installBtn3 = null; + var installBtn4 = null; + + this.install_buttons.push(installBtn); + + switch(data.state) { + case 'HasUpdate': + installBtn2 = document.createElement('button'); + installBtn2.innerHTML = 'Update'; + installBtn2.className = "cm-btn-update"; + installBtn2.style.backgroundColor = 'blue'; + installBtn2.style.color = 'white'; + this.install_buttons.push(installBtn2); + + installBtn.innerHTML = 'Uninstall'; + installBtn.style.backgroundColor = 'red'; + data5.appendChild(installBtn); + break; + + case 'Installed': + installBtn.innerHTML = 'Uninstall'; + installBtn.style.backgroundColor = 'red'; + data5.appendChild(installBtn); + break; + + case 'NotInstalled': + installBtn.innerHTML = 'Install'; + installBtn.style.backgroundColor = 'black'; + installBtn.style.color = 'white'; + data5.appendChild(installBtn); + break; + + case 'NotComponent': + break; + + default: + break; + } + + let j = i; + if(installBtn2 != null) { + installBtn2.style.width = "120px"; + installBtn2.addEventListener('click', function() { + // todo }); - let default_nodename = getPureName(this.target_node).trim(); + data5.appendChild(installBtn2); + } - let groupNode = app.graph.extra.groupNodes[default_nodename]; - let default_packname = groupNode.packname; - if(!default_packname) { - default_packname = ''; - } + installBtn.style.width = "120px"; + installBtn.addEventListener('click', function() { + if(this.innerHTML == 'Uninstall') { + this.uninstall_components(data.name); + } + else { + this.install_components(data.name); + } + }); - let default_category = groupNode.category; - if(!default_category) { - default_category = ''; - } + if(data.state == 'Fail') + dataRow.style.backgroundColor = "#880000"; + else + dataRow.style.backgroundColor = "var(--bg-color)"; + dataRow.style.color = "var(--fg-color)"; + dataRow.style.textAlign = "left"; - this.default_ver = groupNode.version; - if(!this.default_ver) { - this.default_ver = '0.0'; - } + dataRow.appendChild(data0); + dataRow.appendChild(data1); + dataRow.appendChild(data2); + dataRow.appendChild(data3); + dataRow.appendChild(data4); + dataRow.appendChild(data5); + tbody.appendChild(dataRow); - let default_author = groupNode.author; - if(!default_author) { - default_author = ''; - } + let buttons = []; + if(installBtn) { + buttons.push(installBtn); + } + if(installBtn2) { + buttons.push(installBtn2); + } - 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 author = this.createLabeledInput('input author (e.g. Dr.Lt.Data)', 'Author : ', default_author); - this.author = author[1]; - - 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], - author[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; + this.grid_rows[i] = {data:data, buttons:buttons, checkbox:checkbox, control:dataRow}; } - 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]; - } + const panel = document.createElement('div'); + panel.style.width = "100%"; + panel.appendChild(grid); - 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; - } + function handleResize() { + const parentHeight = self.element.clientHeight; + const gridHeight = parentHeight - 200; - 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; + grid.style.height = gridHeight + "px"; } - let row = $el('span.cb-widget', {}, [ $el('span.cb-widget-input-label', label), textbox]); + window.addEventListener("resize", handleResize); - return [row, textbox]; + grid.style.position = "relative"; + grid.style.display = "inline-block"; + grid.style.tableLayout = "fixed"; + grid.style.width = "100%"; + grid.style.height = "100%"; + grid.style.overflowY = "scroll"; + this.element.style.height = "85%"; + this.element.style.width = "80%"; + this.element.appendChild(panel); + + handleResize(); } - createPackListCombo() { + createFilterCombo() { 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 }, [])); - } + combo.style.cssFloat = "left"; + combo.style.fontSize = "14px"; + combo.style.padding = "4px"; + combo.style.background = "black"; + combo.style.marginLeft = "2px"; + combo.style.width = "199px"; + combo.id = `combo-manger-filter`; + combo.style.borderRadius = "15px"; + + let items = + [ + { value:'*', text:'Filter: all' }, + { value:'Installed', text:'Filter: installed' }, + { value:'NotInstalled', text:'Filter: not installed' }, + { value:'NotComponent', text:'Filter: not component' }, + ]; + + items.forEach(item => { + const option = document.createElement("option"); + option.value = item.value; + option.text = item.text; + combo.appendChild(option); + }); let self = this; - combo.onchange = function () { - if(combo.selectedIndex == 0) { - self.manual_packname.disabled = false; - } - else { - self.manual_packname.disabled = true; - } - }; + combo.addEventListener('change', function(event) { + self.filter = event.target.value; + self.apply_searchbox(); + }); + + if(self.filter) { + combo.value = self.filter; + } return combo; } -} -let orig_handleFile = app.handleFile; + createHeaderControls() { + let self = this; + this.search_box = $el('input.cm-search-filter', {type:'text', id:'manager-components-search-box', placeholder:'input search keyword', value:this.search_keyword}, []); + this.search_box.style.height = "25px"; + this.search_box.onkeydown = (event) => { + if (event.key === 'Enter') { + self.search_keyword = self.search_box.value; + self.apply_searchbox(); + } + if (event.key === 'Escape') { + self.search_keyword = self.search_box.value; + self.apply_searchbox(); + } + }; -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); - } + let search_button = document.createElement("button"); + search_button.className = "cm-small-button"; + search_button.innerHTML = "Search"; + search_button.onclick = () => { + self.search_keyword = self.search_box.value; + self.apply_searchbox(); }; - reader.readAsText(file); + search_button.style.display = "inline-block"; - return; + let filter_control = this.createFilterCombo(); + filter_control.style.display = "inline-block"; + + let cell = $el('td', {width:'100%'}, [filter_control, this.search_box, ' ', search_button]); + let search_control = $el('table', {width:'100%'}, + [ + $el('tr', {}, [cell]) + ] + ); + + cell.style.textAlign = "right"; + + this.element.appendChild(search_control); } - orig_handleFile.call(app, file); -} + async createBottomControls() { + var close_button = document.createElement("button"); + close_button.className = "cm-small-button"; + close_button.innerHTML = "Close"; + close_button.onclick = () => { this.close(); } + close_button.style.display = "inline-block"; -app.handleFile = handleFile; + this.message_box = $el('div', {id:'components-installer-message'}, [$el('br'), '']); + this.message_box.style.height = '60px'; + this.message_box.style.verticalAlign = 'middle'; -let current_component_policy = 'workflow'; -try { - api.fetchApi('/manager/component/policy') - .then(response => response.text()) - .then(data => { current_component_policy = data; }); -} -catch {} + this.element.appendChild(this.message_box); + this.element.appendChild(close_button); + } -function getChangedVersion(groupNodes) { - if(!Object.keys(pack_map).length || !groupNodes) - return null; + async show(show_mode) { + try { + this.invalidateControl(); - 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; + this.element.style.display = "block"; + this.element.style.zIndex = 10001; + } + catch(exception) { + app.ui.dialog.show(`Failed to get component list. / ${exception}`); } } - - 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; -} +} \ No newline at end of file diff --git a/js/custom-nodes-downloader.js b/js/custom-nodes-downloader.js index 10d893e9..78428245 100644 --- a/js/custom-nodes-downloader.js +++ b/js/custom-nodes-downloader.js @@ -1,7 +1,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; -import { install_checked_custom_node, manager_instance, rebootAPI } from "./common.js"; +import { install_checked_custom_node, manager_instance, rebootAPI } from "./common.js"; async function getCustomNodes() { @@ -93,9 +93,9 @@ export class CustomNodesInstaller extends ComfyDialog { data = null; static ShowMode = { - NORMAL: 0, - MISSING_NODES: 1, - UPDATE: 2, + NORMAL: 0, + MISSING_NODES: 1, + UPDATE: 2, }; clear() { @@ -651,10 +651,10 @@ export class CustomNodesInstaller extends ComfyDialog { panel.appendChild(grid); function handleResize() { - const parentHeight = self.element.clientHeight; - const gridHeight = parentHeight - 200; + const parentHeight = self.element.clientHeight; + const gridHeight = parentHeight - 200; - grid.style.height = gridHeight + "px"; + grid.style.height = gridHeight + "px"; } window.addEventListener("resize", handleResize);