diff --git a/__init__.py b/__init__.py index 83cc099..e4bf8a1 100644 --- a/__init__.py +++ b/__init__.py @@ -28,7 +28,7 @@ except: print(f"[WARN] ComfyUI-Manager: Your ComfyUI version is outdated. Please update to the latest version.") -version = [2, 1] +version = [2, 2] version_str = f"V{version[0]}.{version[1]}" + (f'.{version[2]}' if len(version) > 2 else '') print(f"### Loading: ComfyUI-Manager ({version_str})") @@ -172,6 +172,7 @@ def write_config(): 'share_option': get_config()['share_option'], 'bypass_ssl': get_config()['bypass_ssl'], 'default_ui': get_config()['default_ui'], + 'component_policy': get_config()['component_policy'], } with open(config_path, 'w') as configfile: config.write(configfile) @@ -191,6 +192,7 @@ def read_config(): 'share_option': default_conf['share_option'] if 'share_option' in default_conf else 'all', 'bypass_ssl': default_conf['bypass_ssl'] if 'bypass_ssl' in default_conf else False, 'default_ui': default_conf['default_ui'] if 'default_ui' in default_conf else 'none', + 'component_policy': default_conf['component_policy'] if 'component_policy' in default_conf else 'workflow', } except Exception: @@ -201,7 +203,8 @@ def read_config(): 'channel_url': 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main', 'share_option': 'all', 'bypass_ssl': False, - 'default_ui': 'none' + 'default_ui': 'none', + 'component_policy': 'workflow' } @@ -249,6 +252,10 @@ def set_default_ui_mode(mode): get_config()['default_ui'] = mode +def set_component_policy(mode): + get_config()['component_policy'] = mode + + def try_install_script(url, repo_path, install_cmd): int_comfyui_revision = 0 @@ -1767,6 +1774,18 @@ async def default_ui_mode(request): return web.Response(status=200) + +@server.PromptServer.instance.routes.get("/manager/component/policy") +async def component_policy(request): + if "value" in request.rel_url.query: + set_component_policy(request.rel_url.query['value']) + write_config() + else: + return web.Response(text=get_config()['component_policy'], status=200) + + return web.Response(status=200) + + @server.PromptServer.instance.routes.get("/manager/channel_url_list") async def channel_url_list(request): channels = get_channel_dict() diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index fe1233d..0dbce9d 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -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 { ComponentBuilderDialog, load_components } from "./components-manager.js"; +import { ComponentBuilderDialog, load_components, set_component_policy } from "./components-manager.js"; var docStyle = document.createElement('style'); docStyle.innerHTML = ` @@ -721,6 +721,18 @@ class ManagerMenuDialog extends ComfyDialog { } }), + $el("button.cm-button", { + type: "button", + textContent: "Install via Git URL", + onclick: () => { + var url = prompt("Please enter the URL of the Git repository to install", ""); + + if (url !== null) { + install_via_git_url(url, self); + } + } + }), + $el("br", {}, []), update_all_button, update_comfyui_button, @@ -753,6 +765,7 @@ class ManagerMenuDialog extends ComfyDialog { // db mode this.datasrc_combo = document.createElement("select"); + this.datasrc_combo.setAttribute("title", "Configure where to retrieve node/model information. If set to 'local,' the channel is ignored, and if set to 'channel (remote),' it fetches the latest information each time the list is opened."); this.datasrc_combo.className = "cm-menu-combo"; this.datasrc_combo.appendChild($el('option', { value: 'cache', text: 'DB: Channel (1day cache)' }, [])); this.datasrc_combo.appendChild($el('option', { value: 'local', text: 'DB: Local' }, [])); @@ -760,6 +773,7 @@ class ManagerMenuDialog extends ComfyDialog { // preview method let preview_combo = document.createElement("select"); + preview_combo.setAttribute("title", "Configure how latent variables will be decoded during preview in the sampling process."); preview_combo.className = "cm-menu-combo"; preview_combo.appendChild($el('option', { value: 'auto', text: 'Preview method: Auto' }, [])); preview_combo.appendChild($el('option', { value: 'taesd', text: 'Preview method: TAESD (slow)' }, [])); @@ -776,6 +790,7 @@ class ManagerMenuDialog extends ComfyDialog { // nickname let badge_combo = document.createElement("select"); + badge_combo.setAttribute("title", "Configure the content to be displayed on the badge at the top right corner of the node. The ID is the identifier of the node. If 'hide built-in' is selected, both unknown nodes and built-in nodes will be omitted, making them indistinguishable"); badge_combo.className = "cm-menu-combo"; badge_combo.appendChild($el('option', { value: 'none', text: 'Badge: None' }, [])); badge_combo.appendChild($el('option', { value: 'nick', text: 'Badge: Nickname' }, [])); @@ -795,6 +810,7 @@ class ManagerMenuDialog extends ComfyDialog { // channel let channel_combo = document.createElement("select"); + channel_combo.setAttribute("title", "Configure the channel for retrieving data from the Custom Node list (including missing nodes) or the Model list. Note that the badge utilizes local information."); channel_combo.className = "cm-menu-combo"; api.fetchApi('/manager/channel_url_list') .then(response => response.json()) @@ -821,6 +837,7 @@ class ManagerMenuDialog extends ComfyDialog { // default ui state let default_ui_combo = document.createElement("select"); + default_ui_combo.setAttribute("title", "Set the default state to be displayed in the main menu when the browser starts."); default_ui_combo.className = "cm-menu-combo"; default_ui_combo.appendChild($el('option', { value: 'none', text: 'Default UI: None' }, [])); default_ui_combo.appendChild($el('option', { value: 'history', text: 'Default UI: History' }, [])); @@ -836,6 +853,7 @@ class ManagerMenuDialog extends ComfyDialog { // share let share_combo = document.createElement("select"); + share_combo.setAttribute("title", "Hide the share button in the main menu or set the default action upon clicking it. Additionally, configure the default share site when sharing via the context menu's share button."); share_combo.className = "cm-menu-combo"; const share_options = [ ['none', 'None'], @@ -849,6 +867,25 @@ class ManagerMenuDialog extends ComfyDialog { share_combo.appendChild($el('option', { value: option[0], text: `Share: ${option[1]}` }, [])); } + // default ui state + let component_policy_combo = document.createElement("select"); + component_policy_combo.setAttribute("title", "When loading the workflow, configure which version of the component to use."); + component_policy_combo.className = "cm-menu-combo"; + component_policy_combo.appendChild($el('option', { value: 'workflow', text: 'Component: Use workflow version' }, [])); + component_policy_combo.appendChild($el('option', { value: 'higher', text: 'Component: Use higher version' }, [])); + component_policy_combo.appendChild($el('option', { value: 'mine', text: 'Component: Use my version' }, [])); + api.fetchApi('/manager/component/policy') + .then(response => response.text()) + .then(data => { + component_policy_combo.value = data; + set_component_policy(data); + }); + + component_policy_combo.addEventListener('change', function (event) { + api.fetchApi(`/manager/component/policy?value=${event.target.value}`); + set_component_policy(event.target.value); + }); + api.fetchApi('/manager/share_option') .then(response => response.text()) .then(data => { @@ -877,18 +914,8 @@ class ManagerMenuDialog extends ComfyDialog { badge_combo, default_ui_combo, share_combo, + component_policy_combo, $el("br", {}, []), - $el("button.cm-button", { - type: "button", - textContent: "Install via Git URL", - onclick: () => { - var url = prompt("Please enter the URL of the Git repository to install", ""); - - if (url !== null) { - install_via_git_url(url, self); - } - } - }), $el("br", {}, []), $el("filedset.cm-experimental", {}, [ @@ -1220,6 +1247,7 @@ app.registerExtension({ async beforeRegisterNodeDef(nodeType, nodeData, app) { this._addExtraNodeContextMenu(nodeType, app); }, + async nodeCreated(node, app) { if(!node.badge_enabled) { node.getNickname = function () { return getNickname(node, node.comfyClass.trim()) }; @@ -1230,6 +1258,7 @@ app.registerExtension({ node.badge_enabled = true; } }, + async loadedGraphNode(node, app) { if(!node.badge_enabled) { const orig = node.onDrawForeground; @@ -1240,10 +1269,11 @@ app.registerExtension({ _addExtraNodeContextMenu(node, app) { const origGetExtraMenuOptions = node.prototype.getExtraMenuOptions; + node.prototype.cm_menu_added = true; node.prototype.getExtraMenuOptions = function (_, options) { origGetExtraMenuOptions?.apply?.(this, arguments); - if (node.comfyClass.startsWith('workflow/')) { + if (node.category.startsWith('group nodes/')) { options.push({ content: "Save As Component", callback: (obj) => { diff --git a/js/components-manager.js b/js/components-manager.js index 4c7e69a..031196a 100644 --- a/js/components-manager.js +++ b/js/components-manager.js @@ -7,6 +7,13 @@ import { ComfyDialog, $el } from "../../scripts/ui.js"; let pack_map = {}; let rpack_map = {}; +function getPureName(node) { + // group nodes/ + let category = node.category.substring(12); + let purename = node.comfyClass.substring(category.length+1); + return purename; +} + function isValidVersionString(version) { const versionPattern = /^(\d+)\.(\d+)(\.(\d+))?$/; @@ -21,21 +28,23 @@ function isValidVersionString(version) { 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]; + rpack_map[name] = data; } else { - rpack_map[name] = [data.packname, data.category, data.version, data.datetime]; + rpack_map[name] = data; } } -function storeGroupNode(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; - register_pack_map(name, data); + if(register) { + register_pack_map(name, data); + } } export async function load_components() { @@ -179,7 +188,7 @@ async function save_as_component(node, version, prefix, nodename, packname, cate let subgraph = app.graph.extra?.groupNodes?.[component_name]; if(!subgraph) { - subgraph = app.graph.extra?.groupNodes?.[node.comfyClass.substring(9)]; + subgraph = app.graph.extra?.groupNodes?.[getPureName(node)]; } subgraph.version = version; @@ -194,7 +203,7 @@ async function save_as_component(node, version, prefix, nodename, packname, cate }; pack_map[packname] = component_name; - rpack_map[component_name] = [body.pack_name, body.category, body.version, body.datetime]; + rpack_map[component_name] = subgraph; const res = await api.fetchApi('/manager/component/save', { method: "POST", @@ -253,6 +262,15 @@ async function import_component(component_name, component, mode) { 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; @@ -306,7 +324,7 @@ function versionCompare(v1, v2) { function checkVersion(name, component) { let msg = ''; if(rpack_map[name]) { - let old_version = rpack_map[name][2]; + let old_version = rpack_map[name].version; if(!old_version || old_version == '') { msg = ` '${name}' Upgrade (V0.0 -> V${component.version})`; } @@ -439,27 +457,21 @@ export class ComponentBuilderDialog extends ComfyDialog { } }); - let default_nodename = this.target_node.comfyClass.substring(9).trim(); + let default_nodename = getPureName(this.target_node).trim(); + + let groupNode = app.graph.extra.groupNodes[default_nodename]; + let default_packname = groupNode.packname; - 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]; - } + let default_category = groupNode.category; if(!default_category) { default_category = ''; } - if(rpack_map[default_nodename]) { - this.default_ver = rpack_map[default_nodename][2]; - } + this.default_ver = groupNode.version; if(!this.default_ver) { this.default_ver = '0.0'; } @@ -566,7 +578,7 @@ export class ComponentBuilderDialog extends ComfyDialog { return this.manual_nodename.value.trim(); } - return this.target_node.comfyClass.substring(9); + return getPureName(this.target_node); } createAuthorModeCheck() { @@ -595,9 +607,9 @@ export class ComponentBuilderDialog extends ComfyDialog { let label = $el('p'); label.className = 'cb-node-label'; if(this.target_node.comfyClass.includes('::')) - label.textContent = this.target_node.comfyClass.substring(9); + label.textContent = getPureName(this.target_node); else - label.textContent = " _::" + this.target_node.comfyClass.substring(9); + label.textContent = " _::" + getPureName(this.target_node); return label; } @@ -668,3 +680,71 @@ function handleFile(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 () { + const v = await loadGraphData.apply(this, arguments); + + if(arguments.length == 0) + return v; + + let groupNodes = arguments[0].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]; + restore_to_loaded_component(component_name); + } + } + } + else { + console.log('Empty components: policy ignored'); + } + + return v; +}; + +export function set_component_policy(v) { + current_component_policy = v; +} \ No newline at end of file