mirror of
https://git.datalinker.icu/ltdrdata/ComfyUI-Manager
synced 2025-12-08 21:54:26 +08:00
improved: advanced missing node detection based on embedded info
https://github.com/ltdrdata/ComfyUI-Manager/issues/1445 feat: Custom Nodes In Workflow https://github.com/ltdrdata/ComfyUI-Manager/issues/990 https://github.com/ltdrdata/ComfyUI-Manager/issues/127 improved: show version on main dialog modified: aux_id - use github_id if possible removed: `fetch updates` button
This commit is contained in:
parent
76d2206058
commit
4fd17b0bf5
@ -52,6 +52,14 @@ def git_url(fullpath):
|
||||
|
||||
|
||||
def normalize_url(url) -> str:
|
||||
github_id = normalize_to_github_id(url)
|
||||
if github_id is not None:
|
||||
url = f"https://github.com/{github_id}"
|
||||
|
||||
return url
|
||||
|
||||
|
||||
def normalize_to_github_id(url) -> str:
|
||||
if 'github' in url or (GITHUB_ENDPOINT is not None and GITHUB_ENDPOINT in url):
|
||||
author = os.path.basename(os.path.dirname(url))
|
||||
|
||||
@ -62,9 +70,9 @@ def normalize_url(url) -> str:
|
||||
if repo_name.endswith('.git'):
|
||||
repo_name = repo_name[:-4]
|
||||
|
||||
url = f"https://github.com/{author}/{repo_name}"
|
||||
return f"{author}/{repo_name}"
|
||||
|
||||
return url
|
||||
return None
|
||||
|
||||
|
||||
def get_url_for_clone(url):
|
||||
|
||||
@ -42,7 +42,7 @@ import manager_downloader
|
||||
from node_package import InstalledNodePackage
|
||||
|
||||
|
||||
version_code = [3, 25, 1]
|
||||
version_code = [3, 26]
|
||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
|
||||
@ -1472,7 +1472,7 @@ def identify_node_pack_from_path(fullpath):
|
||||
# cnr
|
||||
cnr = cnr_utils.read_cnr_info(fullpath)
|
||||
if cnr is not None:
|
||||
return module_name, cnr['version'], cnr['id']
|
||||
return module_name, cnr['version'], cnr['id'], None
|
||||
|
||||
return None
|
||||
else:
|
||||
@ -1480,10 +1480,18 @@ def identify_node_pack_from_path(fullpath):
|
||||
cnr_id = cnr_utils.read_cnr_id(fullpath)
|
||||
commit_hash = git_utils.get_commit_hash(fullpath)
|
||||
|
||||
github_id = git_utils.normalize_to_github_id(repo_url)
|
||||
if github_id is None:
|
||||
try:
|
||||
github_id = os.path.basename(repo_url)
|
||||
except:
|
||||
logging.warning(f"[ComfyUI-Manager] unexpected repo url: {repo_url}")
|
||||
github_id = module_name
|
||||
|
||||
if cnr_id is not None:
|
||||
return module_name, commit_hash, cnr_id
|
||||
return module_name, commit_hash, cnr_id, github_id
|
||||
else:
|
||||
return module_name, commit_hash, ''
|
||||
return module_name, commit_hash, '', github_id
|
||||
|
||||
|
||||
def get_installed_node_packs():
|
||||
@ -1501,7 +1509,7 @@ def get_installed_node_packs():
|
||||
|
||||
is_disabled = not y.endswith('.disabled')
|
||||
|
||||
res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'enabled': is_disabled }
|
||||
res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'aux_id': info[3], 'enabled': is_disabled }
|
||||
|
||||
disabled_dirs = os.path.join(x, '.disabled')
|
||||
if os.path.exists(disabled_dirs):
|
||||
@ -1514,7 +1522,7 @@ def get_installed_node_packs():
|
||||
if info is None:
|
||||
continue
|
||||
|
||||
res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'enabled': False }
|
||||
res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'aux_id': info[3], 'enabled': False }
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@ -21,6 +21,8 @@ import { CustomNodesManager } from "./custom-nodes-manager.js";
|
||||
import { ModelManager } from "./model-manager.js";
|
||||
import { SnapshotManager } from "./snapshot.js";
|
||||
|
||||
let manager_version = await getVersion();
|
||||
|
||||
var docStyle = document.createElement('style');
|
||||
docStyle.innerHTML = `
|
||||
.comfy-toast {
|
||||
@ -42,7 +44,7 @@ docStyle.innerHTML = `
|
||||
|
||||
#cm-manager-dialog {
|
||||
width: 1000px;
|
||||
height: 450px;
|
||||
height: 455px;
|
||||
box-sizing: content-box;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
@ -139,7 +141,7 @@ docStyle.innerHTML = `
|
||||
|
||||
.cm-notice-board {
|
||||
width: 290px;
|
||||
height: 210px;
|
||||
height: 230px;
|
||||
overflow: auto;
|
||||
color: var(--input-text);
|
||||
border: 1px solid var(--descrip-text);
|
||||
@ -958,7 +960,19 @@ class ManagerMenuDialog extends ComfyDialog {
|
||||
}
|
||||
}),
|
||||
|
||||
$el("button.cm-button", {
|
||||
type: "button",
|
||||
textContent: "Custom Nodes In Workflow",
|
||||
onclick:
|
||||
() => {
|
||||
if(!CustomNodesManager.instance) {
|
||||
CustomNodesManager.instance = new CustomNodesManager(app, self);
|
||||
}
|
||||
CustomNodesManager.instance.show(CustomNodesManager.ShowMode.IN_WORKFLOW);
|
||||
}
|
||||
}),
|
||||
|
||||
$el("br", {}, []),
|
||||
$el("button.cm-button", {
|
||||
type: "button",
|
||||
textContent: "Model Manager",
|
||||
@ -987,7 +1001,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
||||
update_all_button,
|
||||
update_comfyui_button,
|
||||
switch_comfyui_button,
|
||||
fetch_updates_button,
|
||||
// fetch_updates_button,
|
||||
|
||||
$el("br", {}, []),
|
||||
restart_stop_button,
|
||||
@ -1318,7 +1332,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
||||
$el("div.comfy-modal-content",
|
||||
[
|
||||
$el("tr.cm-title", {}, [
|
||||
$el("font", {size:6, color:"white"}, [`ComfyUI Manager Menu`])]
|
||||
$el("font", {size:6, color:"white"}, [`ComfyUI Manager ${manager_version}`])]
|
||||
),
|
||||
$el("br", {}, []),
|
||||
$el("div.cm-menu-container",
|
||||
@ -1460,13 +1474,12 @@ async function getVersion() {
|
||||
return await version.text();
|
||||
}
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.ManagerMenu",
|
||||
|
||||
aboutPageBadges: [
|
||||
{
|
||||
label: `ComfyUI-Manager ${await getVersion()}`,
|
||||
label: `ComfyUI-Manager ${manager_version}`,
|
||||
url: 'https://github.com/ltdrdata/ComfyUI-Manager',
|
||||
icon: 'pi pi-th-large'
|
||||
}
|
||||
|
||||
@ -363,6 +363,7 @@ const pageHtml = `
|
||||
<button class="cn-manager-restart">Restart</button>
|
||||
<button class="cn-manager-stop">Stop</button>
|
||||
<div class="cn-flex-auto"></div>
|
||||
<button class="cn-manager-used-in-workflow">Used In Workflow</button>
|
||||
<button class="cn-manager-check-update">Check Update</button>
|
||||
<button class="cn-manager-check-missing">Check Missing</button>
|
||||
<button class="cn-manager-install-url">Install via Git URL</button>
|
||||
@ -374,7 +375,8 @@ const ShowMode = {
|
||||
UPDATE: "Update",
|
||||
MISSING: "Missing",
|
||||
FAVORITES: "Favorites",
|
||||
ALTERNATIVES: "Alternatives"
|
||||
ALTERNATIVES: "Alternatives",
|
||||
IN_WORKFLOW: "In Workflow",
|
||||
};
|
||||
|
||||
export class CustomNodesManager {
|
||||
@ -586,6 +588,10 @@ export class CustomNodesManager {
|
||||
label: "Update",
|
||||
value: ShowMode.UPDATE,
|
||||
hasData: false
|
||||
}, {
|
||||
label: "In Workflow",
|
||||
value: ShowMode.IN_WORKFLOW,
|
||||
hasData: false
|
||||
}, {
|
||||
label: "Missing",
|
||||
value: ShowMode.MISSING,
|
||||
@ -726,7 +732,7 @@ export class CustomNodesManager {
|
||||
const value = e.target.value
|
||||
this.filter = value;
|
||||
const item = this.getFilterItem(value);
|
||||
if (item && !item.hasData) {
|
||||
if (item && (!item.hasData)) {
|
||||
this.loadData(value);
|
||||
return;
|
||||
}
|
||||
@ -779,6 +785,14 @@ export class CustomNodesManager {
|
||||
}
|
||||
},
|
||||
|
||||
".cn-manager-used-in-workflow": {
|
||||
click: (e) => {
|
||||
e.target.classList.add("cn-btn-loading");
|
||||
this.setFilter(ShowMode.IN_WORKFLOW);
|
||||
this.loadData(ShowMode.IN_WORKFLOW);
|
||||
}
|
||||
},
|
||||
|
||||
".cn-manager-check-update": {
|
||||
click: (e) => {
|
||||
e.target.classList.add("cn-btn-loading");
|
||||
@ -1529,7 +1543,110 @@ export class CustomNodesManager {
|
||||
return extension_mappings;
|
||||
}
|
||||
|
||||
getNodesInWorkflow() {
|
||||
let usedGroupNodes = new Set();
|
||||
let allUsedNodes = {};
|
||||
|
||||
for(let k in app.graph._nodes) {
|
||||
let node = app.graph._nodes[k];
|
||||
|
||||
if(node.type.startsWith('workflow>')) {
|
||||
usedGroupNodes.add(node.type.slice(9));
|
||||
continue;
|
||||
}
|
||||
|
||||
allUsedNodes[node.type] = node;
|
||||
}
|
||||
|
||||
for(let k of usedGroupNodes) {
|
||||
let subnodes = app.graph.extra.groupNodes[k]?.nodes;
|
||||
|
||||
if(subnodes) {
|
||||
for(let k2 in subnodes) {
|
||||
let node = subnodes[k2];
|
||||
allUsedNodes[node.type] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allUsedNodes;
|
||||
}
|
||||
|
||||
async getMissingNodes() {
|
||||
let unresolved_missing_nodes = new Set();
|
||||
let hashMap = {};
|
||||
let allUsedNodes = this.getNodesInWorkflow();
|
||||
|
||||
const registered_nodes = new Set();
|
||||
for (let i in LiteGraph.registered_node_types) {
|
||||
registered_nodes.add(LiteGraph.registered_node_types[i].type);
|
||||
}
|
||||
|
||||
let unresolved_aux_ids = {};
|
||||
let outdated_comfyui = false;
|
||||
|
||||
for(let k in allUsedNodes) {
|
||||
let node = allUsedNodes[k];
|
||||
|
||||
if(!registered_nodes.has(node.type)) {
|
||||
// missing node
|
||||
if(node.properties.cnr_id) {
|
||||
if(node.properties.cnr_id == 'comfy-core') {
|
||||
outdated_comfyui = true;
|
||||
}
|
||||
|
||||
let item = this.custom_nodes[node.properties.cnr_id];
|
||||
hashMap[item.hash] = true;
|
||||
}
|
||||
else if(node.properties.aux_id) {
|
||||
unresolved_aux_ids[node.properties.aux_id] = node.type;
|
||||
}
|
||||
else {
|
||||
unresolved_missing_nodes.add(node.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(outdated_comfyui) {
|
||||
customAlert('ComfyUI is outdated, so some built-in nodes cannot be used.');
|
||||
}
|
||||
|
||||
if(Object.keys(unresolved_aux_ids).length > 0) {
|
||||
// building aux_id to nodepack map
|
||||
let aux_id_to_pack = {};
|
||||
for(let k in this.custom_nodes) {
|
||||
let nodepack = this.custom_nodes[k];
|
||||
let aux_id;
|
||||
if(nodepack.repository?.startsWith('https://github.com')) {
|
||||
aux_id = nodepack.repository.split('/').slice(-2).join('/');
|
||||
aux_id_to_pack[aux_id] = nodepack;
|
||||
}
|
||||
else if(nodepack.repository) {
|
||||
aux_id = nodepack.repository.split('/').slice(-1);
|
||||
aux_id_to_pack[aux_id] = nodepack;
|
||||
}
|
||||
}
|
||||
|
||||
// resolving aux_id
|
||||
for(let k in unresolved_aux_ids) {
|
||||
let nodepack = aux_id_to_pack[k];
|
||||
if(nodepack) {
|
||||
hashMap[nodepack.hash] = true;
|
||||
}
|
||||
else {
|
||||
unresolved_missing_nodes.add(unresolved_aux_ids[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(unresolved_missing_nodes.size > 0) {
|
||||
await this.getMissingNodesLegacy(hashMap, unresolved_missing_nodes, registered_nodes);
|
||||
}
|
||||
|
||||
return hashMap;
|
||||
}
|
||||
|
||||
async getMissingNodesLegacy(hashMap, missing_nodes, registered_nodes) {
|
||||
const mode = manager_instance.datasrc_combo.value;
|
||||
this.showStatus(`Loading missing nodes (${mode}) ...`);
|
||||
const res = await fetchData(`/customnode/getmappings?mode=${mode}`);
|
||||
@ -1568,23 +1685,8 @@ export class CustomNodesManager {
|
||||
}
|
||||
}
|
||||
|
||||
const registered_nodes = new Set();
|
||||
for (let i in LiteGraph.registered_node_types) {
|
||||
registered_nodes.add(LiteGraph.registered_node_types[i].type);
|
||||
}
|
||||
|
||||
const missing_nodes = new Set();
|
||||
const workflow = app.graph.serialize();
|
||||
const group_nodes = workflow.extra && workflow.extra.groupNodes ? workflow.extra.groupNodes : [];
|
||||
let nodes = workflow.nodes;
|
||||
|
||||
for (let i in group_nodes) {
|
||||
let group_node = group_nodes[i];
|
||||
nodes = nodes.concat(group_node.nodes);
|
||||
}
|
||||
|
||||
for (let i in nodes) {
|
||||
const node_type = nodes[i].type;
|
||||
let unresolved_missing_nodes = new Set();
|
||||
for (let node_type of missing_nodes) {
|
||||
if(node_type.startsWith('workflow/') || node_type.startsWith('workflow>'))
|
||||
continue;
|
||||
|
||||
@ -1592,26 +1694,25 @@ export class CustomNodesManager {
|
||||
const packs = name_to_packs[node_type.trim()];
|
||||
if(packs)
|
||||
packs.forEach(url => {
|
||||
missing_nodes.add(url);
|
||||
unresolved_missing_nodes.add(url);
|
||||
});
|
||||
else {
|
||||
for(let j in regex_to_pack) {
|
||||
if(regex_to_pack[j].regex.test(node_type)) {
|
||||
missing_nodes.add(regex_to_pack[j].url);
|
||||
unresolved_missing_nodes.add(regex_to_pack[j].url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hashMap = {};
|
||||
for(let k in this.custom_nodes) {
|
||||
let item = this.custom_nodes[k];
|
||||
|
||||
if(missing_nodes.has(item.id)) {
|
||||
if(unresolved_missing_nodes.has(item.id)) {
|
||||
hashMap[item.hash] = true;
|
||||
}
|
||||
else if (item.files?.some(file => missing_nodes.has(file))) {
|
||||
else if (item.files?.some(file => unresolved_missing_nodes.has(file))) {
|
||||
hashMap[item.hash] = true;
|
||||
}
|
||||
}
|
||||
@ -1630,6 +1731,41 @@ export class CustomNodesManager {
|
||||
return hashMap;
|
||||
}
|
||||
|
||||
async getNodepackInWorkflow() {
|
||||
let allUsedNodes = this.getNodesInWorkflow();
|
||||
|
||||
// building aux_id to nodepack map
|
||||
let aux_id_to_pack = {};
|
||||
for(let k in this.custom_nodes) {
|
||||
let nodepack = this.custom_nodes[k];
|
||||
let aux_id;
|
||||
if(nodepack.repository?.startsWith('https://github.com')) {
|
||||
aux_id = nodepack.repository.split('/').slice(-2).join('/');
|
||||
aux_id_to_pack[aux_id] = nodepack;
|
||||
}
|
||||
else if(nodepack.repository) {
|
||||
aux_id = nodepack.repository.split('/').slice(-1);
|
||||
aux_id_to_pack[aux_id] = nodepack;
|
||||
}
|
||||
}
|
||||
|
||||
const hashMap = {};
|
||||
for(let k in allUsedNodes) {
|
||||
var item;
|
||||
if(allUsedNodes[k].properties.cnr_id) {
|
||||
item = this.custom_nodes[allUsedNodes[k].properties.cnr_id];
|
||||
}
|
||||
else if(allUsedNodes[k].properties.aux_id) {
|
||||
item = aux_id_to_pack[allUsedNodes[k].properties.aux_id];
|
||||
}
|
||||
|
||||
if(item)
|
||||
hashMap[item.hash] = true;
|
||||
}
|
||||
|
||||
return hashMap;
|
||||
}
|
||||
|
||||
async getAlternatives() {
|
||||
const mode = manager_instance.datasrc_combo.value;
|
||||
this.showStatus(`Loading alternatives (${mode}) ...`);
|
||||
@ -1725,10 +1861,15 @@ export class CustomNodesManager {
|
||||
hashMap = await this.getAlternatives();
|
||||
} else if(this.show_mode == ShowMode.FAVORITES) {
|
||||
hashMap = await this.getFavorites();
|
||||
} else if(this.show_mode == ShowMode.IN_WORKFLOW) {
|
||||
hashMap = await this.getNodepackInWorkflow();
|
||||
}
|
||||
filterItem.hashMap = hashMap;
|
||||
|
||||
if(this.show_mode != ShowMode.IN_WORKFLOW) {
|
||||
filterItem.hasData = true;
|
||||
}
|
||||
}
|
||||
|
||||
for(let k in node_packs) {
|
||||
let nodeItem = node_packs[k];
|
||||
@ -1779,7 +1920,6 @@ export class CustomNodesManager {
|
||||
case "disabled":
|
||||
filterTypes.add("installed");
|
||||
break;
|
||||
|
||||
case "not-installed":
|
||||
filterTypes.add("not-installed");
|
||||
break;
|
||||
|
||||
@ -62,13 +62,14 @@ class WorkflowMetadataExtension {
|
||||
|
||||
if (moduleType === "custom_nodes") {
|
||||
const nodePackageName = modules[1];
|
||||
const { cnr_id, ver } =
|
||||
const { cnr_id, aux_id, ver } =
|
||||
this.installedNodes[nodePackageName] ??
|
||||
this.installedNodes[nodePackageName.toLowerCase()] ??
|
||||
{};
|
||||
|
||||
if (cnr_id === "comfy-core") return; // don't allow hijacking comfy-core name
|
||||
if (cnr_id) nodeProperties.cnr_id = cnr_id;
|
||||
else nodeProperties.aux_id = aux_id;
|
||||
if (ver) nodeProperties.ver = ver;
|
||||
} else if (["nodes", "comfy_extras"].includes(moduleType)) {
|
||||
nodeProperties.cnr_id = "comfy-core";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-manager"
|
||||
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
||||
version = "3.25.1"
|
||||
version = "3.26"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user