Compare commits

...

42 Commits

Author SHA1 Message Date
jfcantu
aa09e57dbb
Merge 1bdcd1bdbf into f20f8549e6 2026-01-14 20:58:37 +03:00
Dr.Lt.Data
f20f8549e6 update DB
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
2026-01-14 00:45:38 +09:00
sinanzoo2nd
82135ab168
Add ComfyUI-Seed-Wildcard-Pack (#2497) 2026-01-14 00:14:47 +09:00
Dr.Lt.Data
2cb7f021e9 update DB 2026-01-13 18:51:07 +09:00
Dr.Lt.Data
29a59595b9 update DB
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-01-13 12:59:33 +09:00
Dr.Lt.Data
58a1051c46 update DB
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-01-13 07:32:02 +09:00
Dr.Lt.Data
53bec8be40 update DB 2026-01-13 07:29:47 +09:00
zwaigani
162f25f570
Add ComfyUI-VRAM-watcher node with details (#2493) 2026-01-13 07:28:10 +09:00
Dr.Lt.Data
5d0112f768 update DB
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-01-12 01:59:22 +09:00
Jason Hoku
695d87cc0a
Add Ultimate Auto Sampler Suite (#2471) 2026-01-12 01:39:54 +09:00
Dr.Lt.Data
f2ba36ec3a update DB 2026-01-12 01:12:58 +09:00
Bulldog68
6b83a5a16e
Add ComfyUI_FMJ_SaveImageVersions node entry (#2475)
* Add ComfyUI_FMJ_SaveImageVersions node entry

Save images with full metadata (prompt, workflow, software versions, commit)

This repository provides ComfyUI nodes to:
- Save PNG images with embedded metadata (positive / negative prompts and other JSON fields),
- Generate and copy a snapshot describing the environment (commits, versions, GPU, etc.),
- Load an image and restore information from its metadata.

Important: this project runs a local script `snapshot.py` to capture the state of ComfyUI and custom nodes. Only run it in trusted environments.

* Update custom-node-list.json

---------

Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2026-01-12 01:11:35 +09:00
Dr.Lt.Data
10aff14af5 update DB 2026-01-12 01:06:25 +09:00
LiChunlin
7634a08073
Add Comfyui-Image-Concat custom node (#2480)
Added a new custom node for ComfyUI that supports image concatenation with advanced features.
2026-01-12 01:05:32 +09:00
Dr.Lt.Data
92cf872f1b update DB 2026-01-12 00:55:47 +09:00
vuhung3990
c916174499
Add NSFW Guard node to custom-node-list.json (#2489)
Added NSFW Guard node for ComfyUI with automatic detection.
2026-01-12 00:51:05 +09:00
Amit Pintz
4ad8735c89
Add LTX-2 models. (#2490) 2026-01-12 00:49:45 +09:00
Dr.Lt.Data
2416aa2fc9 update DB
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
2026-01-09 12:53:58 +09:00
Dr.Lt.Data
f4fa394e0f fix(security): add input sanitization and path traversal protection
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
- Sanitize config string values to prevent CRLF injection attacks
- Add get_safe_snapshot_path() helper to validate snapshot targets
- Block path traversal attempts in remove_snapshot and restore_snapshot endpoints
- Reject targets containing /, \, .., or null characters
2026-01-08 18:29:14 +09:00
Dr.Lt.Data
8d67702ab0 update DB
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-01-08 03:04:19 +09:00
SBCODE
b1d804a47e
Change author name from 'Sean-Bradley' to 'sbcode' (#2468)
Updated author names for all my custom nodes to match what i have in the registry.
Also added a node for my version of ComfyUI-Sonic
2026-01-08 02:57:57 +09:00
Dr.Lt.Data
db6ff690bf update DB 2026-01-08 02:55:50 +09:00
Dr.Lt.Data
adfbe8de75 update DB 2026-01-08 02:55:17 +09:00
BWDrum
8184430df5
Update custom-node-list.json (#2470)
Add ComfyUI Random Wildcard Loader
2026-01-08 02:54:34 +09:00
Dr.Lt.Data
88938a04cc update DB 2026-01-08 02:54:22 +09:00
Yifan Zhu
6a50229ef6
Add ComfyUI-Persona-Director to custom node list (#2467) 2026-01-08 02:53:12 +09:00
The Kraken
76e9ce9b5d
Add Kraken Tools custom node pack + fix author attribution (#2459)
- Add new kraken-tools entry for comfyui-kraken-tools repo
- Fix author attribution for existing ComfyUI-KrakenTools entry (thimpat -> KrakenUnbound)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: KrakenUnbound <thekraken@thekraken.net>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 02:50:48 +09:00
Dr.Lt.Data
c635591e16 update DB 2026-01-08 02:42:56 +09:00
Dr.Lt.Data
82cf838d28 update DB 2026-01-08 01:50:17 +09:00
mengqin
9d1bc43a58
Update DB. (#2473) 2026-01-08 01:49:07 +09:00
solidlime
4711e81cec
Update custom-node-list.json (#2474) 2026-01-08 01:46:49 +09:00
Dr.Lt.Data
09f0656139 update DB 2026-01-08 01:27:06 +09:00
LiChunlin
98cf72e0a2
Update custom-node-list.json (#2464) 2026-01-08 01:25:00 +09:00
Dr.Lt.Data
10f3b6551c bump version 2026-01-08 01:22:59 +09:00
Akhil Narayanan
1fe90867a2
Ignore Windows stderr flush errors (#2462) 2026-01-08 01:22:02 +09:00
Bulldog68
42aabcfec1
Add FMJ Save Image + Versions node entry (#2445)
* Add FMJ Save Image + Versions node entry

Sauvegarde d’images avec métadonnées complètes (prompt, seed, versions logicielles) + chargement intelligent.

* Update custom-node-list.json

---------

Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2026-01-08 01:18:36 +09:00
Dr.Lt.Data
ac122a1db0 update DB
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-01-07 12:37:25 +09:00
Dr.Lt.Data
5cff01eef3 update DB
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-01-06 18:59:31 +09:00
John Cantu
1bdcd1bdbf Merge branch 'main' of https://github.com/jfcantu/ComfyUI-Manager 2025-12-14 18:11:34 -08:00
John Cantu
e04ed0eda7 update README.md for node usage analyzeer 2025-09-14 23:10:10 -07:00
John Cantu
02aa67b541 Merge branch 'main' of https://github.com/jfcantu/ComfyUI-Manager 2025-09-14 22:52:41 -07:00
John Cantu
67d03530a3 Changes and new code for Node Usage Analyzer 2025-09-14 22:49:54 -07:00
24 changed files with 13203 additions and 31282 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ import manager_migration
from node_package import InstalledNodePackage
version_code = [3, 39]
version_code = [3, 39, 2]
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
@ -1701,6 +1701,11 @@ def write_config():
'db_mode': get_config()['db_mode'],
}
# Sanitize all string values to prevent CRLF injection attacks
for key, value in config['default'].items():
if isinstance(value, str):
config['default'][key] = value.replace('\r', '').replace('\n', '').replace('\x00', '')
directory = os.path.dirname(manager_config_path)
if not os.path.exists(directory):
os.makedirs(directory)

View File

@ -11,7 +11,10 @@ import threading
import re
import shutil
import git
import glob
import json
from datetime import datetime
from contextlib import contextmanager
from server import PromptServer
import manager_core as core
@ -762,6 +765,86 @@ async def fetch_updates(request):
except:
traceback.print_exc()
return web.Response(status=400)
@routes.get("/customnode/get_node_types_in_workflows")
async def get_node_types_in_workflows(request):
try:
# get our username from the request header
user_id = PromptServer.instance.user_manager.get_request_user_id(request)
# get the base workflow directory (TODO: figure out if non-standard directories are possible, and how to find them)
workflow_files_base_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), user_id, "workflows"))
logging.debug(f"workflows base path: {workflow_files_base_path}")
# workflow directory doesn't actually exist, return 204 (No Content)
if not os.path.isdir(workflow_files_base_path):
logging.debug("workflows base path doesn't exist - nothing to do...")
return web.Response(status=204)
# get all JSON files under the workflow directory
workflow_file_relative_paths: list[str] = glob.glob(pathname="**/*.json", root_dir=workflow_files_base_path, recursive=True)
logging.debug(f"found the following workflows: {workflow_file_relative_paths}")
# set up our list of workflow/node-lists
workflow_node_mappings: list[dict[str, str | list[str]]] = []
# iterate over each found JSON file
for workflow_file_path in workflow_file_relative_paths:
try:
workflow_file_absolute_path = os.path.abspath(os.path.join(workflow_files_base_path, workflow_file_path))
logging.debug(f"starting work on {workflow_file_absolute_path}")
# load the JSON file
workflow_file_data = json.load(open(workflow_file_absolute_path, "r"))
# make sure there's a nodes key (otherwise this might not actually be a workflow file)
if "nodes" not in workflow_file_data:
logging.warning(f"{workflow_file_path} has no 'nodes' key (possibly invalid?) - skipping...")
# skip to next file
continue
# now this looks like a valid file, so let's get to work
new_mapping = {"workflow_file_name": workflow_file_path}
# we can't use an actual set, because you can't use dicts as set members
node_set = []
# iterate over each node in the workflow
for node in workflow_file_data["nodes"]:
if "id" not in node:
logging.warning(f"Found a node with no ID - possibly corrupt/invalid workflow?")
continue
# if there's no type, throw a warning
if "type" not in node:
logging.warning(f"Node type not found in {workflow_file_path} for node ID {node['id']}")
# skip to next node
continue
node_data_to_return = {"type": node["type"]}
if "properties" not in node:
logging.warning(f"Node ${node['id']} has no properties field - can't determine cnr_id")
else:
for property_key in ["cnr_id", "ver"]:
if property_key in node["properties"]:
node_data_to_return[property_key] = node["properties"][property_key]
# add it to the list for this workflow
if not node_data_to_return in node_set:
node_set.append(node_data_to_return)
# annoyingly, Python can't serialize sets to JSON
new_mapping["node_types"] = list(node_set)
workflow_node_mappings.append(new_mapping)
except Exception as e:
logging.warning(f"Couldn't open {workflow_file_path}: {e}")
return web.json_response(workflow_node_mappings, content_type='application/json')
except:
traceback.print_exc()
return web.Response(status=500)
@routes.get("/manager/queue/update_all")
@ -997,6 +1080,15 @@ async def get_snapshot_list(request):
return web.json_response({'items': items}, content_type='application/json')
def get_safe_snapshot_path(target):
"""
Safely construct a snapshot file path, preventing path traversal attacks.
"""
if '/' in target or '\\' in target or '..' in target or '\x00' in target:
return None
return os.path.join(core.manager_snapshot_path, f"{target}.json")
@routes.get("/snapshot/remove")
async def remove_snapshot(request):
if not is_allowed_security_level('middle'):
@ -1005,8 +1097,12 @@ async def remove_snapshot(request):
try:
target = request.rel_url.query["target"]
path = get_safe_snapshot_path(target)
if path is None:
logging.error(f"[ComfyUI-Manager] Invalid snapshot target: {target}")
return web.Response(text="Invalid snapshot target", status=400)
path = os.path.join(core.manager_snapshot_path, f"{target}.json")
if os.path.exists(path):
os.remove(path)
@ -1023,8 +1119,12 @@ async def restore_snapshot(request):
try:
target = request.rel_url.query["target"]
path = get_safe_snapshot_path(target)
if path is None:
logging.error(f"[ComfyUI-Manager] Invalid snapshot target: {target}")
return web.Response(text="Invalid snapshot target", status=400)
path = os.path.join(core.manager_snapshot_path, f"{target}.json")
if os.path.exists(path):
if not os.path.exists(core.manager_startup_script_path):
os.makedirs(core.manager_startup_script_path)

View File

@ -9,6 +9,7 @@ This directory contains the JavaScript frontend implementation for ComfyUI-Manag
- **model-manager.js**: Handles the model management interface for downloading and organizing AI models.
- **components-manager.js**: Manages reusable workflow components system.
- **snapshot.js**: Implements the snapshot system for backing up and restoring installations.
- **node-usage-analyzer.js**: Implements the UI for analyzing node usage in workflows.
## Sharing Components
@ -46,5 +47,6 @@ The frontend follows a modular component-based architecture:
CSS files are included for specific components:
- **custom-nodes-manager.css**: Styling for the node management UI
- **model-manager.css**: Styling for the model management UI
- **node-usage-analyzer.css**: Styling for the node usage analyzer UI
This frontend implementation provides a comprehensive yet user-friendly interface for managing the ComfyUI ecosystem.

View File

@ -18,6 +18,7 @@ import {
} from "./common.js";
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
import { CustomNodesManager } from "./custom-nodes-manager.js";
import { NodeUsageAnalyzer } from "./node-usage-analyzer.js";
import { ModelManager } from "./model-manager.js";
import { SnapshotManager } from "./snapshot.js";
import { buildGuiFrame, createSettingsCombo } from "./comfyui-gui-builder.js";
@ -909,6 +910,17 @@ class ManagerMenuDialog extends ComfyDialog {
CustomNodesManager.instance.show(CustomNodesManager.ShowMode.IN_WORKFLOW);
}
}),
$el("button.cm-button", {
type: "button",
textContent: "Node Usage Analyzer",
onclick:
() => {
if(!NodeUsageAnalyzer.instance) {
NodeUsageAnalyzer.instance = new NodeUsageAnalyzer(app, self);
}
NodeUsageAnalyzer.instance.show(NodeUsageAnalyzer.SortMode.BY_PACKAGE);
}
}),
$el("div", {}, []),
$el("button.p-button.p-component.cm-button", {

View File

@ -122,9 +122,9 @@ export async function customConfirm(message) {
let res = await
window['app'].extensionManager.dialog
.confirm({
title: 'Confirm',
message: message
});
title: 'Confirm',
message: message
});
return res;
}
@ -164,9 +164,9 @@ export async function customPrompt(title, message) {
let res = await
window['app'].extensionManager.dialog
.prompt({
title: title,
message: message
});
title: title,
message: message
});
return res;
}
@ -667,4 +667,449 @@ function initTooltip () {
document.body.addEventListener('mouseleave', mouseleaveHandler, true);
}
export async function uninstallNodes(nodeList, options = {}) {
const {
title = `${nodeList.length} custom nodes`,
onProgress = () => {},
onError = () => {},
onSuccess = () => {},
channel = 'default',
mode = 'default'
} = options;
// Check if queue is busy
let stats = await api.fetchApi('/manager/queue/status');
stats = await stats.json();
if (stats.is_processing) {
customAlert(`[ComfyUI-Manager] There are already tasks in progress. Please try again after it is completed. (${stats.done_count}/${stats.total_count})`);
return { success: false, error: 'Queue is busy' };
}
// Confirmation dialog for uninstall
const confirmed = await customConfirm(`Are you sure uninstall ${title}?`);
if (!confirmed) {
return { success: false, error: 'User cancelled' };
}
let errorMsg = "";
let target_items = [];
await api.fetchApi('/manager/queue/reset');
for (const nodeItem of nodeList) {
target_items.push(nodeItem);
onProgress(`Uninstall ${nodeItem.title || nodeItem.name} ...`);
const data = nodeItem.originalData || nodeItem;
data.channel = channel;
data.mode = mode;
data.ui_id = nodeItem.hash || md5(nodeItem.name || nodeItem.title);
const res = await api.fetchApi(`/manager/queue/uninstall`, {
method: 'POST',
body: JSON.stringify(data)
});
if (res.status != 200) {
errorMsg = `'${nodeItem.title || nodeItem.name}': `;
if (res.status == 403) {
errorMsg += `This action is not allowed with this security level configuration.\n`;
} else if (res.status == 404) {
errorMsg += `With the current security level configuration, only custom nodes from the <B>"default channel"</B> can be uninstalled.\n`;
} else {
errorMsg += await res.text() + '\n';
}
break;
}
}
if (errorMsg) {
onError(errorMsg);
show_message("[Uninstall Errors]\n" + errorMsg);
return { success: false, error: errorMsg, targets: target_items };
} else {
await api.fetchApi('/manager/queue/start');
onSuccess(target_items);
showTerminal();
return { success: true, targets: target_items };
}
}
// ===========================================================================================
// Workflow Utilities Consolidation
export async function getWorkflowNodeTypes() {
try {
const res = await fetchData('/customnode/get_node_types_in_workflows');
if (res.status === 200) {
return { success: true, data: res.data };
} else if (res.status === 204) {
// No workflows found - return empty list
return { success: true, data: [] };
} else {
return { success: false, error: res.error };
}
} catch (error) {
return { success: false, error: error };
}
}
export function findPackageByCnrId(cnrId, nodePackages, installedOnly = true) {
if (!cnrId || !nodePackages) {
return null;
}
// Tier 1: Direct key match
if (nodePackages[cnrId]) {
const pack = nodePackages[cnrId];
if (!installedOnly || pack.state !== "not-installed") {
return { key: cnrId, pack: pack };
}
}
// Tier 2: Case-insensitive match
const cnrIdLower = cnrId.toLowerCase();
for (const packKey of Object.keys(nodePackages)) {
if (packKey.toLowerCase() === cnrIdLower) {
const pack = nodePackages[packKey];
if (!installedOnly || pack.state !== "not-installed") {
return { key: packKey, pack: pack };
}
}
}
// Tier 3: URL/reference contains match
for (const packKey of Object.keys(nodePackages)) {
const pack = nodePackages[packKey];
// Skip non-installed packages if installedOnly is true
if (installedOnly && pack.state === "not-installed") {
continue;
}
// Check if reference URL contains cnr_id
if (pack.reference && pack.reference.includes(cnrId)) {
return { key: packKey, pack: pack };
}
// Check if any file URL contains cnr_id
if (pack.files && Array.isArray(pack.files)) {
for (const fileUrl of pack.files) {
if (fileUrl.includes(cnrId)) {
return { key: packKey, pack: pack };
}
}
}
}
return null;
}
export async function analyzeWorkflowUsage(nodePackages) {
const result = await getWorkflowNodeTypes();
if (!result.success) {
return { success: false, error: result.error };
}
const workflowNodeList = result.data;
const usageMap = new Map();
const workflowDetailsMap = new Map();
if (workflowNodeList && Array.isArray(workflowNodeList)) {
const cnrIdCounts = new Map();
const cnrIdToWorkflows = new Map();
// Process each workflow
workflowNodeList.forEach((workflowObj, workflowIndex) => {
if (workflowObj.node_types && Array.isArray(workflowObj.node_types)) {
const workflowCnrIds = new Set();
// Get workflow filename
const workflowFilename = workflowObj.workflow_file_name ||
workflowObj.filename ||
workflowObj.file ||
workflowObj.name ||
workflowObj.path ||
`Workflow ${workflowIndex + 1}`;
// Count nodes per cnr_id in this workflow
const workflowCnrIdCounts = new Map();
workflowObj.node_types.forEach(nodeTypeObj => {
const cnrId = nodeTypeObj.cnr_id;
if (cnrId && cnrId !== "comfy-core") {
// Track unique cnr_ids per workflow
workflowCnrIds.add(cnrId);
// Count nodes per cnr_id in this specific workflow
const workflowNodeCount = workflowCnrIdCounts.get(cnrId) || 0;
workflowCnrIdCounts.set(cnrId, workflowNodeCount + 1);
}
});
// Record workflow details for each unique cnr_id found in this workflow
workflowCnrIds.forEach(cnrId => {
// Count occurrences of this cnr_id across all workflows
const currentCount = cnrIdCounts.get(cnrId) || 0;
cnrIdCounts.set(cnrId, currentCount + 1);
// Track workflow details
if (!cnrIdToWorkflows.has(cnrId)) {
cnrIdToWorkflows.set(cnrId, []);
}
cnrIdToWorkflows.get(cnrId).push({
filename: workflowFilename,
nodeCount: workflowCnrIdCounts.get(cnrId) || 0
});
});
}
});
// Map cnr_id to installed packages with workflow details
cnrIdCounts.forEach((count, cnrId) => {
const workflowDetails = cnrIdToWorkflows.get(cnrId) || [];
const foundPackage = findPackageByCnrId(cnrId, nodePackages, true);
if (foundPackage) {
usageMap.set(foundPackage.key, count);
workflowDetailsMap.set(foundPackage.key, workflowDetails);
}
});
}
return {
success: true,
usageMap: usageMap,
workflowDetailsMap: workflowDetailsMap
};
}
// Size formatting utilities - consolidated from model-manager.js and node-usage-analyzer.js
export function formatSize(v) {
const base = 1000;
const units = ['', 'K', 'M', 'G', 'T', 'P'];
const space = '';
const postfix = 'B';
if (v <= 0) {
return `0${space}${postfix}`;
}
for (let i = 0, l = units.length; i < l; i++) {
const min = Math.pow(base, i);
const max = Math.pow(base, i + 1);
if (v > min && v <= max) {
const unit = units[i];
if (unit) {
const n = v / min;
const nl = n.toString().split('.')[0].length;
const fl = Math.max(3 - nl, 1);
v = n.toFixed(fl);
}
v = v + space + unit + postfix;
break;
}
}
return v;
}
// for size sort
export function sizeToBytes(v) {
if (typeof v === "number") {
return v;
}
if (typeof v === "string") {
const n = parseFloat(v);
const unit = v.replace(/[0-9.B]+/g, "").trim().toUpperCase();
if (unit === "K") {
return n * 1000;
}
if (unit === "M") {
return n * 1000 * 1000;
}
if (unit === "G") {
return n * 1000 * 1000 * 1000;
}
if (unit === "T") {
return n * 1000 * 1000 * 1000 * 1000;
}
}
return v;
}
// Flyover component - consolidated from custom-nodes-manager.js and node-usage-analyzer.js
export function createFlyover(container, options = {}) {
const {
enableHover = false,
hoverHandler = null,
context = null
} = options;
const $flyover = document.createElement("div");
$flyover.className = "cn-flyover";
$flyover.innerHTML = `<div class="cn-flyover-header">
<div class="cn-flyover-close">${icons.arrowRight}</div>
<div class="cn-flyover-title"></div>
<div class="cn-flyover-close">${icons.close}</div>
</div>
<div class="cn-flyover-body"></div>`
container.appendChild($flyover);
const $flyoverTitle = $flyover.querySelector(".cn-flyover-title");
const $flyoverBody = $flyover.querySelector(".cn-flyover-body");
let width = '50%';
let visible = false;
let timeHide;
const closeHandler = (e) => {
if ($flyover === e.target || $flyover.contains(e.target)) {
return;
}
clearTimeout(timeHide);
timeHide = setTimeout(() => {
flyover.hide();
}, 100);
}
const displayHandler = () => {
if (visible) {
$flyover.classList.remove("cn-slide-in-right");
} else {
$flyover.classList.remove("cn-slide-out-right");
$flyover.style.width = '0px';
$flyover.style.display = "none";
}
}
const flyover = {
show: (titleHtml, bodyHtml) => {
clearTimeout(timeHide);
if (context && context.element) {
context.element.removeEventListener("click", closeHandler);
}
$flyoverTitle.innerHTML = titleHtml;
$flyoverBody.innerHTML = bodyHtml;
$flyover.style.display = "block";
$flyover.style.width = width;
if(!visible) {
$flyover.classList.add("cn-slide-in-right");
}
visible = true;
setTimeout(() => {
if (context && context.element) {
context.element.addEventListener("click", closeHandler);
}
}, 100);
},
hide: (now) => {
visible = false;
if (context && context.element) {
context.element.removeEventListener("click", closeHandler);
}
if(now) {
displayHandler();
return;
}
$flyover.classList.add("cn-slide-out-right");
}
}
$flyover.addEventListener("animationend", (e) => {
displayHandler();
});
// Add hover handlers if enabled
if (enableHover && hoverHandler) {
$flyover.addEventListener("mouseenter", hoverHandler, true);
$flyover.addEventListener("mouseleave", hoverHandler, true);
}
$flyover.addEventListener("click", (e) => {
if(e.target.classList.contains("cn-flyover-close")) {
flyover.hide();
return;
}
// Forward other click events to the provided handler or context
if (context && context.handleFlyoverClick) {
context.handleFlyoverClick(e);
}
});
return flyover;
}
// Shared UI State Methods - consolidated from multiple managers
export function createUIStateManager(element, selectors) {
return {
showSelection: (msg) => {
const el = element.querySelector(selectors.selection);
if (el) el.innerHTML = msg;
},
showError: (err) => {
const el = element.querySelector(selectors.message);
if (el) {
const msg = err ? `<font color="red">${err}</font>` : "";
el.innerHTML = msg;
}
},
showMessage: (msg, color) => {
const el = element.querySelector(selectors.message);
if (el) {
if (color) {
msg = `<font color="${color}">${msg}</font>`;
}
el.innerHTML = msg;
}
},
showStatus: (msg, color) => {
const el = element.querySelector(selectors.status);
if (el) {
if (color) {
msg = `<font color="${color}">${msg}</font>`;
}
el.innerHTML = msg;
}
},
showLoading: (grid) => {
if (grid) {
grid.showLoading();
grid.showMask({
opacity: 0.05
});
}
},
hideLoading: (grid) => {
if (grid) {
grid.hideLoading();
grid.hideMask();
}
},
showRefresh: () => {
const el = element.querySelector(selectors.refresh);
if (el) el.style.display = "block";
},
showStop: () => {
const el = element.querySelector(selectors.stop);
if (el) el.style.display = "block";
},
hideStop: () => {
const el = element.querySelector(selectors.stop);
if (el) el.style.display = "none";
}
};
}
initTooltip();

View File

@ -8,7 +8,7 @@ import {
fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt,
sanitizeHTML, infoToast, showTerminal, setNeedRestart,
storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss,
showPopover, hidePopover, handle403Response
showPopover, hidePopover, getWorkflowNodeTypes, findPackageByCnrId, analyzeWorkflowUsage, createFlyover
} from "./common.js";
// https://cenfun.github.io/turbogrid/api.html
@ -42,6 +42,8 @@ const ShowMode = {
FAVORITES: "Favorites",
ALTERNATIVES: "Alternatives",
IN_WORKFLOW: "In Workflow",
USED_IN_ANY_WORKFLOW: "Used In Any Workflow",
NOT_USED_IN_ANY_WORKFLOW: "Installed and Unused",
};
export class CustomNodesManager {
@ -271,6 +273,14 @@ export class CustomNodesManager {
label: "In Workflow",
value: ShowMode.IN_WORKFLOW,
hasData: false
}, {
label: "Used In Any Workflow",
value: ShowMode.USED_IN_ANY_WORKFLOW,
hasData: false
}, {
label: "Installed and Unused",
value: ShowMode.NOT_USED_IN_ANY_WORKFLOW,
hasData: false
}, {
label: "Missing",
value: ShowMode.MISSING,
@ -521,7 +531,11 @@ export class CustomNodesManager {
const grid = new TG.Grid(container);
this.grid = grid;
this.flyover = this.createFlyover(container);
this.flyover = createFlyover(container, {
enableHover: true,
hoverHandler: this.handleFlyoverHover.bind(this),
context: this
});
let prevViewRowsLength = -1;
grid.bind('onUpdated', (e, d) => {
@ -1063,143 +1077,63 @@ export class CustomNodesManager {
hidePopover();
}
createFlyover(container) {
const $flyover = document.createElement("div");
$flyover.className = "cn-flyover";
$flyover.innerHTML = `<div class="cn-flyover-header">
<div class="cn-flyover-close">${icons.arrowRight}</div>
<div class="cn-flyover-title"></div>
<div class="cn-flyover-close">${icons.close}</div>
</div>
<div class="cn-flyover-body"></div>`
container.appendChild($flyover);
const $flyoverTitle = $flyover.querySelector(".cn-flyover-title");
const $flyoverBody = $flyover.querySelector(".cn-flyover-body");
let width = '50%';
let visible = false;
let timeHide;
const closeHandler = (e) => {
if ($flyover === e.target || $flyover.contains(e.target)) {
return;
}
clearTimeout(timeHide);
timeHide = setTimeout(() => {
flyover.hide();
}, 100);
}
const hoverHandler = (e) => {
if(e.type === "mouseenter") {
if(e.target.classList.contains("cn-nodes-name")) {
this.showNodePreview(e.target);
}
return;
}
this.hideNodePreview();
}
const displayHandler = () => {
if (visible) {
$flyover.classList.remove("cn-slide-in-right");
} else {
$flyover.classList.remove("cn-slide-out-right");
$flyover.style.width = '0px';
$flyover.style.display = "none";
}
}
const flyover = {
show: (titleHtml, bodyHtml) => {
clearTimeout(timeHide);
this.element.removeEventListener("click", closeHandler);
$flyoverTitle.innerHTML = titleHtml;
$flyoverBody.innerHTML = bodyHtml;
$flyover.style.display = "block";
$flyover.style.width = width;
if(!visible) {
$flyover.classList.add("cn-slide-in-right");
}
visible = true;
setTimeout(() => {
this.element.addEventListener("click", closeHandler);
}, 100);
},
hide: (now) => {
visible = false;
this.element.removeEventListener("click", closeHandler);
if(now) {
displayHandler();
return;
}
$flyover.classList.add("cn-slide-out-right");
}
}
$flyover.addEventListener("animationend", (e) => {
displayHandler();
});
$flyover.addEventListener("mouseenter", hoverHandler, true);
$flyover.addEventListener("mouseleave", hoverHandler, true);
$flyover.addEventListener("click", (e) => {
handleFlyoverHover(e) {
if(e.type === "mouseenter") {
if(e.target.classList.contains("cn-nodes-name")) {
const nodeName = e.target.innerText;
const nodeItem = this.nodeMap[nodeName];
if (!nodeItem) {
copyText(nodeName).then((res) => {
if (res) {
e.target.setAttribute("action", "Copied");
e.target.classList.add("action");
setTimeout(() => {
e.target.classList.remove("action");
e.target.removeAttribute("action");
}, 1000);
}
});
return;
}
this.showNodePreview(e.target);
}
return;
}
this.hideNodePreview();
}
const [x, y, w, h] = app.canvas.ds.visible_area;
const dpi = Math.max(window.devicePixelRatio ?? 1, 1);
const node = window.LiteGraph?.createNode(
nodeItem.name,
nodeItem.display_name,
{
pos: [x + (w-300) / dpi / 2, y]
handleFlyoverClick(e) {
if(e.target.classList.contains("cn-nodes-name")) {
const nodeName = e.target.innerText;
const nodeItem = this.nodeMap[nodeName];
if (!nodeItem) {
copyText(nodeName).then((res) => {
if (res) {
e.target.setAttribute("action", "Copied");
e.target.classList.add("action");
setTimeout(() => {
e.target.classList.remove("action");
e.target.removeAttribute("action");
}, 1000);
}
);
if (node) {
app.graph.add(node);
e.target.setAttribute("action", "Added to Workflow");
e.target.classList.add("action");
setTimeout(() => {
e.target.classList.remove("action");
e.target.removeAttribute("action");
}, 1000);
}
});
return;
}
if(e.target.classList.contains("cn-nodes-pack")) {
const hash = e.target.getAttribute("hash");
const rowItem = this.grid.getRowItemBy("hash", hash);
//console.log(rowItem);
this.grid.scrollToRow(rowItem);
this.addHighlight(rowItem);
return;
}
if(e.target.classList.contains("cn-flyover-close")) {
flyover.hide();
return;
}
});
return flyover;
const [x, y, w, h] = app.canvas.ds.visible_area;
const dpi = Math.max(window.devicePixelRatio ?? 1, 1);
const node = window.LiteGraph?.createNode(
nodeItem.name,
nodeItem.display_name,
{
pos: [x + (w-300) / dpi / 2, y]
}
);
if (node) {
app.graph.add(node);
e.target.setAttribute("action", "Added to Workflow");
e.target.classList.add("action");
setTimeout(() => {
e.target.classList.remove("action");
e.target.removeAttribute("action");
}, 1000);
}
return;
}
if(e.target.classList.contains("cn-nodes-pack")) {
const hash = e.target.getAttribute("hash");
const rowItem = this.grid.getRowItemBy("hash", hash);
//console.log(rowItem);
this.grid.scrollToRow(rowItem);
this.addHighlight(rowItem);
return;
}
}
showNodes(d) {
@ -1874,7 +1808,10 @@ export class CustomNodesManager {
for(let k in allUsedNodes) {
var item;
if(allUsedNodes[k].properties.cnr_id) {
item = this.custom_nodes[allUsedNodes[k].properties.cnr_id];
const foundPackage = findPackageByCnrId(allUsedNodes[k].properties.cnr_id, this.custom_nodes, false);
if (foundPackage) {
item = foundPackage.pack;
}
}
else if(allUsedNodes[k].properties.aux_id) {
item = aux_id_to_pack[allUsedNodes[k].properties.aux_id];
@ -1921,6 +1858,48 @@ export class CustomNodesManager {
return hashMap;
}
async getUsedInAnyWorkflow() {
this.showStatus(`Loading workflow usage analysis ...`);
const result = await analyzeWorkflowUsage(this.custom_nodes);
if (!result.success) {
this.showError(`Failed to get workflow data: ${result.error}`);
return {};
}
const hashMap = {};
// Convert usage map keys to hash map
result.usageMap.forEach((count, packageKey) => {
const pack = this.custom_nodes[packageKey];
if (pack && pack.hash) {
hashMap[pack.hash] = true;
}
});
return hashMap;
}
async getNotUsedInAnyWorkflow() {
this.showStatus(`Loading workflow usage analysis ...`);
// Get the used packages first using common utility
const usedHashMap = await this.getUsedInAnyWorkflow();
const notUsedHashMap = {};
// Find all installed packages that are NOT in the used list
for(let k in this.custom_nodes) {
let nodepack = this.custom_nodes[k];
// Only consider installed packages
if (nodepack.state !== "not-installed" && !usedHashMap[nodepack.hash]) {
notUsedHashMap[nodepack.hash] = true;
}
}
return notUsedHashMap;
}
async loadData(show_mode = ShowMode.NORMAL) {
const isElectron = 'electronAPI' in window;
@ -1990,6 +1969,10 @@ export class CustomNodesManager {
hashMap = await this.getFavorites();
} else if(this.show_mode == ShowMode.IN_WORKFLOW) {
hashMap = await this.getNodepackInWorkflow();
} else if(this.show_mode == ShowMode.USED_IN_ANY_WORKFLOW) {
hashMap = await this.getUsedInAnyWorkflow();
} else if(this.show_mode == ShowMode.NOT_USED_IN_ANY_WORKFLOW) {
hashMap = await this.getNotUsedInAnyWorkflow();
}
filterItem.hashMap = hashMap;

View File

@ -3,7 +3,7 @@ import { $el } from "../../scripts/ui.js";
import {
manager_instance, rebootAPI,
fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal,
storeColumnWidth, restoreColumnWidth, loadCss, handle403Response
storeColumnWidth, restoreColumnWidth, loadCss, formatSize, sizeToBytes
} from "./common.js";
import { api } from "../../scripts/api.js";
@ -359,7 +359,7 @@ export class ModelManager {
width: 100,
formatter: (size) => {
if (typeof size === "number") {
return this.formatSize(size);
return formatSize(size);
}
return size;
}
@ -582,7 +582,7 @@ export class ModelManager {
models.forEach((item, i) => {
const { type, base, name, reference, installed } = item;
item.originalData = JSON.parse(JSON.stringify(item));
item.size = this.sizeToBytes(item.size);
item.size = sizeToBytes(item.size);
item.hash = md5(name + reference);
item.id = i + 1;
@ -659,7 +659,6 @@ export class ModelManager {
const { models } = res.data;
this.modelList = this.getModelList(models);
// console.log("models", this.modelList);
this.updateFilter();
@ -671,56 +670,6 @@ export class ModelManager {
// ===========================================================================================
formatSize(v) {
const base = 1000;
const units = ['', 'K', 'M', 'G', 'T', 'P'];
const space = '';
const postfix = 'B';
if (v <= 0) {
return `0${space}${postfix}`;
}
for (let i = 0, l = units.length; i < l; i++) {
const min = Math.pow(base, i);
const max = Math.pow(base, i + 1);
if (v > min && v <= max) {
const unit = units[i];
if (unit) {
const n = v / min;
const nl = n.toString().split('.')[0].length;
const fl = Math.max(3 - nl, 1);
v = n.toFixed(fl);
}
v = v + space + unit + postfix;
break;
}
}
return v;
}
// for size sort
sizeToBytes(v) {
if (typeof v === "number") {
return v;
}
if (typeof v === "string") {
const n = parseFloat(v);
const unit = v.replace(/[0-9.B]+/g, "").trim().toUpperCase();
if (unit === "K") {
return n * 1000;
}
if (unit === "M") {
return n * 1000 * 1000;
}
if (unit === "G") {
return n * 1000 * 1000 * 1000;
}
if (unit === "T") {
return n * 1000 * 1000 * 1000 * 1000;
}
}
return v;
}
showSelection(msg) {
this.element.querySelector(".cmm-manager-selection").innerHTML = msg;
}

699
js/node-usage-analyzer.css Normal file
View File

@ -0,0 +1,699 @@
.nu-manager {
--grid-font: -apple-system, BlinkMacSystemFont, "Segue UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
z-index: 1099;
width: 80%;
height: 80%;
display: flex;
flex-direction: column;
gap: 10px;
color: var(--fg-color);
font-family: arial, sans-serif;
text-underline-offset: 3px;
outline: none;
}
.nu-manager .nu-flex-auto {
flex: auto;
}
.nu-manager button {
font-size: 16px;
color: var(--input-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
margin: 0;
padding: 4px 8px;
min-width: 100px;
}
.nu-manager button:disabled,
.nu-manager input:disabled,
.nu-manager select:disabled {
color: gray;
}
.nu-manager button:disabled {
background-color: var(--comfy-input-bg);
}
.nu-manager .nu-manager-restart {
display: none;
background-color: #500000;
color: white;
}
.nu-manager .nu-manager-stop {
display: none;
background-color: #500000;
color: white;
}
.nu-manager .nu-manager-back {
align-items: center;
justify-content: center;
}
.arrow-icon {
height: 1em;
width: 1em;
margin-right: 5px;
transform: translateY(2px);
}
.cn-icon {
display: block;
width: 16px;
height: 16px;
}
.cn-icon svg {
display: block;
margin: 0;
pointer-events: none;
}
.nu-manager-header {
display: flex;
flex-wrap: wrap;
gap: 5px;
align-items: center;
padding: 0 5px;
}
.nu-manager-header label {
display: flex;
gap: 5px;
align-items: center;
}
.nu-manager-filter {
height: 28px;
line-height: 28px;
}
.nu-manager-keywords {
height: 28px;
line-height: 28px;
padding: 0 5px 0 26px;
background-size: 16px;
background-position: 5px center;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E");
}
.nu-manager-status {
padding-left: 10px;
}
.nu-manager-grid {
flex: auto;
border: 1px solid var(--border-color);
overflow: hidden;
position: relative;
}
.nu-manager-selection {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.nu-manager-message {
position: relative;
}
.nu-manager-footer {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.nu-manager-grid .tg-turbogrid {
font-family: var(--grid-font);
font-size: 15px;
background: var(--bg-color);
}
.nu-manager-grid .tg-turbogrid .tg-highlight::after {
position: absolute;
top: 0;
left: 0;
content: "";
display: block;
width: 100%;
height: 100%;
box-sizing: border-box;
background-color: #80bdff11;
pointer-events: none;
}
.nu-manager-grid .nu-pack-name a {
color: skyblue;
text-decoration: none;
word-break: break-word;
}
.nu-manager-grid .cn-pack-desc a {
color: #5555FF;
font-weight: bold;
text-decoration: none;
}
.nu-manager-grid .tg-cell a:hover {
text-decoration: underline;
}
.nu-manager-grid .cn-pack-version {
line-height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
gap: 5px;
}
.nu-manager-grid .cn-pack-nodes {
line-height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
gap: 5px;
cursor: pointer;
height: 100%;
}
.nu-manager-grid .cn-pack-nodes:hover {
text-decoration: underline;
}
.nu-manager-grid .cn-pack-conflicts {
color: orange;
}
.cn-popover {
position: fixed;
z-index: 10000;
padding: 20px;
color: #1e1e1e;
filter: drop-shadow(1px 5px 5px rgb(0 0 0 / 30%));
overflow: hidden;
}
.cn-flyover {
position: absolute;
top: 0;
right: 0;
z-index: 1000;
display: none;
width: 50%;
height: 100%;
background-color: var(--comfy-menu-bg);
animation-duration: 0.2s;
animation-fill-mode: both;
flex-direction: column;
}
.cn-flyover::before {
position: absolute;
top: 0;
content: "";
z-index: 10;
display: block;
width: 10px;
height: 100%;
pointer-events: none;
left: -10px;
background-image: linear-gradient(to left, rgb(0 0 0 / 20%), rgb(0 0 0 / 0%));
}
.cn-flyover-header {
height: 45px;
display: flex;
align-items: center;
gap: 5px;
border-bottom: 1px solid var(--border-color);
}
.cn-flyover-close {
display: flex;
align-items: center;
padding: 0 10px;
justify-content: center;
cursor: pointer;
opacity: 0.8;
height: 100%;
}
.cn-flyover-close:hover {
opacity: 1;
}
.cn-flyover-close svg {
display: block;
margin: 0;
pointer-events: none;
width: 20px;
height: 20px;
}
.cn-flyover-title {
display: flex;
align-items: center;
font-weight: bold;
gap: 10px;
flex: auto;
}
.cn-flyover-body {
height: calc(100% - 45px);
overflow-y: auto;
position: relative;
background-color: var(--comfy-menu-secondary-bg);
}
@keyframes cn-slide-in-right {
from {
visibility: visible;
transform: translate3d(100%, 0, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}
.cn-slide-in-right {
animation-name: cn-slide-in-right;
}
@keyframes cn-slide-out-right {
from {
transform: translate3d(0, 0, 0);
}
to {
visibility: hidden;
transform: translate3d(100%, 0, 0);
}
}
.cn-slide-out-right {
animation-name: cn-slide-out-right;
}
.cn-nodes-list {
width: 100%;
}
.cn-nodes-row {
display: flex;
align-items: center;
gap: 10px;
}
.cn-nodes-row:nth-child(odd) {
background-color: rgb(0 0 0 / 5%);
}
.cn-nodes-row:hover {
background-color: rgb(0 0 0 / 10%);
}
.cn-nodes-sn {
text-align: right;
min-width: 35px;
color: var(--drag-text);
flex-shrink: 0;
font-size: 12px;
padding: 8px 5px;
}
.cn-nodes-name {
cursor: pointer;
white-space: nowrap;
flex-shrink: 0;
position: relative;
padding: 8px 5px;
}
.cn-nodes-name::after {
content: attr(action);
position: absolute;
pointer-events: none;
top: 50%;
left: 100%;
transform: translate(5px, -50%);
font-size: 12px;
color: var(--drag-text);
background-color: var(--comfy-input-bg);
border-radius: 10px;
border: 1px solid var(--border-color);
padding: 3px 8px;
display: none;
}
.cn-nodes-name.action::after {
display: block;
}
.cn-nodes-name:hover {
text-decoration: underline;
}
.cn-nodes-conflict .cn-nodes-name,
.cn-nodes-conflict .cn-icon {
color: orange;
}
.cn-conflicts-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
align-items: center;
padding: 5px 0;
}
.cn-conflicts-list b {
font-weight: normal;
color: var(--descrip-text);
}
.cn-nodes-pack {
cursor: pointer;
color: skyblue;
}
.cn-nodes-pack:hover {
text-decoration: underline;
}
.cn-pack-badge {
font-size: 12px;
font-weight: normal;
background-color: var(--comfy-input-bg);
border-radius: 10px;
border: 1px solid var(--border-color);
padding: 3px 8px;
color: var(--error-text);
}
.cn-preview {
min-width: 300px;
max-width: 500px;
min-height: 120px;
overflow: hidden;
font-size: 12px;
pointer-events: none;
padding: 12px;
color: var(--fg-color);
}
.cn-preview-header {
display: flex;
gap: 8px;
align-items: center;
border-bottom: 1px solid var(--comfy-input-bg);
padding: 5px 10px;
}
.cn-preview-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: grey;
position: relative;
filter: drop-shadow(1px 2px 3px rgb(0 0 0 / 30%));
}
.cn-preview-dot.cn-preview-optional::after {
content: "";
position: absolute;
pointer-events: none;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--comfy-input-bg);
border-radius: 50%;
width: 3px;
height: 3px;
}
.cn-preview-dot.cn-preview-grid {
border-radius: 0;
}
.cn-preview-dot.cn-preview-grid::before {
content: '';
position: absolute;
border-left: 1px solid var(--comfy-input-bg);
border-right: 1px solid var(--comfy-input-bg);
width: 4px;
height: 100%;
left: 2px;
top: 0;
z-index: 1;
}
.cn-preview-dot.cn-preview-grid::after {
content: '';
position: absolute;
border-top: 1px solid var(--comfy-input-bg);
border-bottom: 1px solid var(--comfy-input-bg);
width: 100%;
height: 4px;
left: 0;
top: 2px;
z-index: 1;
}
.cn-preview-name {
flex: auto;
font-size: 14px;
}
.cn-preview-io {
display: flex;
justify-content: space-between;
padding: 10px 10px;
}
.cn-preview-column > div {
display: flex;
gap: 10px;
align-items: center;
height: 18px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.cn-preview-input {
justify-content: flex-start;
}
.cn-preview-output {
justify-content: flex-end;
}
.cn-preview-list {
display: flex;
flex-direction: column;
gap: 3px;
padding: 0 10px 10px 10px;
}
.cn-preview-switch {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
background: var(--bg-color);
border: 2px solid var(--border-color);
border-radius: 10px;
text-wrap: nowrap;
padding: 2px 20px;
gap: 10px;
}
.cn-preview-switch::before,
.cn-preview-switch::after {
position: absolute;
pointer-events: none;
top: 50%;
transform: translate(0, -50%);
color: var(--fg-color);
opacity: 0.8;
}
.cn-preview-switch::before {
content: "◀";
left: 5px;
}
.cn-preview-switch::after {
content: "▶";
right: 5px;
}
.cn-preview-value {
color: var(--descrip-text);
}
.cn-preview-string {
min-height: 30px;
max-height: 300px;
background: var(--bg-color);
color: var(--descrip-text);
border-radius: 3px;
padding: 3px 5px;
overflow-y: auto;
overflow-x: hidden;
}
.cn-preview-description {
margin: 0px 10px 10px 10px;
padding: 6px;
background: var(--border-color);
color: var(--descrip-text);
border-radius: 5px;
font-style: italic;
word-break: break-word;
}
.cn-tag-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
align-items: center;
margin-bottom: 5px;
}
.cn-tag-list > div {
background-color: var(--border-color);
border-radius: 5px;
padding: 0 5px;
}
.cn-install-buttons {
display: flex;
flex-direction: column;
gap: 3px;
padding: 3px;
align-items: center;
justify-content: center;
height: 100%;
}
.cn-selected-buttons {
display: flex;
gap: 5px;
align-items: center;
padding-right: 20px;
}
.nu-manager .cn-btn-enable {
background-color: #333399;
color: white;
}
.nu-manager .cn-btn-disable {
background-color: #442277;
color: white;
}
.nu-manager .cn-btn-update {
background-color: #1155AA;
color: white;
}
.nu-manager .cn-btn-try-update {
background-color: Gray;
color: white;
}
.nu-manager .cn-btn-try-fix {
background-color: #6495ED;
color: white;
}
.nu-manager .cn-btn-import-failed {
background-color: #AA1111;
font-size: 10px;
font-weight: bold;
color: white;
}
.nu-manager .cn-btn-install {
background-color: black;
color: white;
}
.nu-manager .cn-btn-try-install {
background-color: Gray;
color: white;
}
.nu-manager .cn-btn-uninstall {
background-color: #993333;
color: white;
}
.nu-manager .cn-btn-reinstall {
background-color: #993333;
color: white;
}
.nu-manager .cn-btn-switch {
background-color: #448833;
color: white;
}
@keyframes nu-btn-loading-bg {
0% {
left: 0;
}
100% {
left: -105px;
}
}
.nu-manager button.nu-btn-loading {
position: relative;
overflow: hidden;
border-color: rgb(0 119 207 / 80%);
background-color: var(--comfy-input-bg);
}
.nu-manager button.nu-btn-loading::after {
position: absolute;
top: 0;
left: 0;
content: "";
width: 500px;
height: 100%;
background-image: repeating-linear-gradient(
-45deg,
rgb(0 119 207 / 30%),
rgb(0 119 207 / 30%) 10px,
transparent 10px,
transparent 15px
);
animation: nu-btn-loading-bg 2s linear infinite;
}
.nu-manager-light .nu-pack-name a {
color: blue;
}
.nu-manager-light .cm-warn-note {
background-color: #ccc !important;
}
.nu-manager-light .cn-btn-install {
background-color: #333;
}

742
js/node-usage-analyzer.js Normal file
View File

@ -0,0 +1,742 @@
import { app } from "../../scripts/app.js";
import { $el } from "../../scripts/ui.js";
import {
manager_instance,
fetchData, md5, show_message, customAlert, infoToast, showTerminal,
storeColumnWidth, restoreColumnWidth, loadCss, uninstallNodes,
analyzeWorkflowUsage, sizeToBytes, createFlyover, createUIStateManager
} from "./common.js";
import { api } from "../../scripts/api.js";
// https://cenfun.github.io/turbogrid/api.html
import TG from "./turbogrid.esm.js";
loadCss("./node-usage-analyzer.css");
const gridId = "model";
const pageHtml = `
<div class="nu-manager-header">
<div class="nu-manager-status"></div>
<input type="text" class="nu-manager-keywords" placeholder="Filter keywords..." />
<div class="nu-flex-auto"></div>
</div>
<div class="nu-manager-grid"></div>
<div class="nu-manager-selection"></div>
<div class="nu-manager-message"></div>
<div class="nu-manager-footer">
<button class="nu-manager-back">
<svg class="arrow-icon" width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 8H18M2 8L8 2M2 8L8 14" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Back
</button>
<button class="nu-manager-refresh">Refresh</button>
<button class="nu-manager-stop">Stop</button>
<div class="nu-flex-auto"></div>
</div>
`;
export class NodeUsageAnalyzer {
static instance = null;
static SortMode = {
BY_PACKAGE: 'by_package'
};
constructor(app, manager_dialog) {
this.app = app;
this.manager_dialog = manager_dialog;
this.id = "nu-manager";
this.filter = '';
this.type = '';
this.base = '';
this.keywords = '';
this.init();
// Initialize shared UI state manager
this.ui = createUIStateManager(this.element, {
selection: ".nu-manager-selection",
message: ".nu-manager-message",
status: ".nu-manager-status",
refresh: ".nu-manager-refresh",
stop: ".nu-manager-stop"
});
api.addEventListener("cm-queue-status", this.onQueueStatus);
}
init() {
this.element = $el("div", {
parent: document.body,
className: "comfy-modal nu-manager"
});
this.element.innerHTML = pageHtml;
this.bindEvents();
this.initGrid();
}
bindEvents() {
const eventsMap = {
".nu-manager-selection": {
click: (e) => {
const target = e.target;
const mode = target.getAttribute("mode");
if (mode === "install") {
this.installModels(this.selectedModels, target);
} else if (mode === "uninstall") {
this.uninstallModels(this.selectedModels, target);
}
}
},
".nu-manager-refresh": {
click: () => {
app.refreshComboInNodes();
}
},
".nu-manager-stop": {
click: () => {
api.fetchApi('/manager/queue/reset');
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
}
},
".nu-manager-back": {
click: (e) => {
this.close()
manager_instance.show();
}
}
};
Object.keys(eventsMap).forEach(selector => {
const target = this.element.querySelector(selector);
if (target) {
const events = eventsMap[selector];
if (events) {
Object.keys(events).forEach(type => {
target.addEventListener(type, events[type]);
});
}
}
});
}
// ===========================================================================================
initGrid() {
const container = this.element.querySelector(".nu-manager-grid");
const grid = new TG.Grid(container);
this.grid = grid;
this.flyover = createFlyover(container, { context: this });
grid.bind('onUpdated', (e, d) => {
this.ui.showStatus(`${grid.viewRows.length.toLocaleString()} installed packages`);
});
grid.bind('onSelectChanged', (e, changes) => {
this.renderSelected();
});
grid.bind("onColumnWidthChanged", (e, columnItem) => {
storeColumnWidth(gridId, columnItem)
});
grid.bind('onClick', (e, d) => {
const { rowItem } = d;
const target = d.e.target;
const mode = target.getAttribute("mode");
if (mode === "install") {
this.installModels([rowItem], target);
return;
}
if (mode === "uninstall") {
this.uninstallModels([rowItem], target);
return;
}
// Handle click on usage count
if (d.columnItem.id === "used_in_count" && rowItem.used_in_count > 0) {
this.showUsageDetails(rowItem);
return;
}
});
grid.setOption({
theme: 'dark',
selectVisible: true,
selectMultiple: true,
selectAllVisible: true,
textSelectable: true,
scrollbarRound: true,
frozenColumn: 1,
rowNotFound: "No Results",
rowHeight: 40,
bindWindowResize: true,
bindContainerResize: true,
cellResizeObserver: (rowItem, columnItem) => {
const autoHeightColumns = ['name', 'description'];
return autoHeightColumns.includes(columnItem.id)
}
});
}
renderGrid() {
// update theme
const colorPalette = this.app.ui.settings.settingsValues['Comfy.ColorPalette'];
Array.from(this.element.classList).forEach(cn => {
if (cn.startsWith("nu-manager-")) {
this.element.classList.remove(cn);
}
});
this.element.classList.add(`nu-manager-${colorPalette}`);
const options = {
theme: colorPalette === "light" ? "" : "dark"
};
const rows = this.modelList || [];
const columns = [{
id: 'title',
name: 'Title',
width: 200,
minWidth: 100,
maxWidth: 500,
classMap: 'nu-pack-name',
formatter: function (name, rowItem, columnItem, cellNode) {
return `<a href=${rowItem.reference} target="_blank"><b>${name}</b></a>`;
}
}, {
id: 'used_in_count',
name: 'Used in',
width: 100,
formatter: function (usedCount, rowItem, columnItem) {
if (!usedCount || usedCount === 0) {
return '0';
}
const plural = usedCount > 1 ? 's' : '';
return `<div class="cn-pack-nodes" style="cursor: pointer;">${usedCount} workflow${plural}</div>`;
}
}, {
id: 'action',
name: 'Action',
width: 160,
minWidth: 140,
maxWidth: 200,
sortable: false,
align: 'center',
formatter: function (action, rowItem, columnItem) {
// Only show uninstall button for installed packages
if (rowItem.originalData && rowItem.originalData.state && rowItem.originalData.state !== "not-installed") {
return `<div class="cn-install-buttons"><button class="nu-btn-uninstall" mode="uninstall">Uninstall</button></div>`;
}
return '';
}
}];
restoreColumnWidth(gridId, columns);
this.grid.setData({
options,
rows,
columns
});
this.grid.render();
}
updateGrid() {
if (this.grid) {
this.grid.update();
}
}
showUsageDetails(rowItem) {
const workflowList = rowItem.workflowDetails;
if (!workflowList || workflowList.length === 0) {
return;
}
let titleHtml = `<div class="cn-nodes-pack">${rowItem.title}</div>`;
const list = [];
list.push(`<div class="cn-nodes-list">`);
workflowList.forEach((workflow, i) => {
list.push(`<div class="cn-nodes-row">`);
list.push(`<div class="cn-nodes-sn">${i + 1}</div>`);
list.push(`<div class="cn-nodes-name">${workflow.filename}</div>`);
list.push(`<div class="cn-nodes-details">${workflow.nodeCount} node${workflow.nodeCount > 1 ? 's' : ''}</div>`);
list.push(`</div>`);
});
list.push("</div>");
const bodyHtml = list.join("");
this.flyover.show(titleHtml, bodyHtml);
}
renderSelected() {
const selectedList = this.grid.getSelectedRows();
if (!selectedList.length) {
this.ui.showSelection("");
return;
}
const installedSelected = selectedList.filter(item =>
item.originalData && item.originalData.state && item.originalData.state !== "not-installed"
);
if (installedSelected.length === 0) {
this.ui.showSelection(`<span>Selected <b>${selectedList.length}</b> packages (none can be uninstalled)</span>`);
return;
}
this.selectedModels = installedSelected;
this.ui.showSelection(`
<div class="nu-selected-buttons">
<span>Selected <b>${installedSelected.length}</b> installed packages</span>
<button class="nu-btn-uninstall" mode="uninstall">Uninstall Selected</button>
</div>
`);
}
// ===========================================================================================
async installModels(list, btn) {
let stats = await api.fetchApi('/manager/queue/status');
stats = await stats.json();
if (stats.is_processing) {
customAlert(`[ComfyUI-Manager] There are already tasks in progress. Please try again after it is completed. (${stats.done_count}/${stats.total_count})`);
return;
}
btn.classList.add("nu-btn-loading");
this.ui.showError("");
let needRefresh = false;
let errorMsg = "";
await api.fetchApi('/manager/queue/reset');
let target_items = [];
for (const item of list) {
this.grid.scrollRowIntoView(item);
target_items.push(item);
this.ui.showStatus(`Install ${item.name} ...`);
const data = item.originalData;
data.ui_id = item.hash;
const res = await api.fetchApi(`/manager/queue/install_model`, {
method: 'POST',
body: JSON.stringify(data)
});
if (res.status != 200) {
errorMsg = `'${item.name}': `;
if (res.status == 403) {
errorMsg += `This action is not allowed with this security level configuration.\n`;
} else {
errorMsg += await res.text() + '\n';
}
break;
}
}
this.install_context = { btn: btn, targets: target_items };
if (errorMsg) {
this.ui.showError(errorMsg);
show_message("[Installation Errors]\n" + errorMsg);
// reset
for (let k in target_items) {
const item = target_items[k];
this.grid.updateCell(item, "installed");
}
}
else {
await api.fetchApi('/manager/queue/start');
this.ui.showStop();
showTerminal();
}
}
async uninstallModels(list, btn) {
btn.classList.add("nu-btn-loading");
this.ui.showError("");
const result = await uninstallNodes(list, {
title: list.length === 1 ? list[0].title || list[0].name : `${list.length} custom nodes`,
channel: 'default',
mode: 'default',
onProgress: (msg) => {
this.showStatus(msg);
},
onError: (errorMsg) => {
this.showError(errorMsg);
},
onSuccess: (targets) => {
this.showStatus(`Uninstalled ${targets.length} custom node(s) successfully`);
this.showMessage(`To apply the uninstalled custom nodes, please restart ComfyUI and refresh browser.`, "red");
// Update the grid to reflect changes
for (let item of targets) {
if (item.originalData) {
item.originalData.state = "not-installed";
}
this.grid.updateRow(item);
}
}
});
if (result.success) {
this.showStop();
}
btn.classList.remove("nu-btn-loading");
}
async onQueueStatus(event) {
let self = NodeUsageAnalyzer.instance;
if (event.detail.status == 'in_progress' && (event.detail.ui_target == 'model_manager' || event.detail.ui_target == 'nodepack_manager')) {
const hash = event.detail.target;
const item = self.grid.getRowItemBy("hash", hash);
if (item) {
item.refresh = true;
self.grid.setRowSelected(item, false);
item.selectable = false;
self.grid.updateRow(item);
}
}
else if (event.detail.status == 'done') {
self.hideStop();
self.onQueueCompleted(event.detail);
}
}
async onQueueCompleted(info) {
let result = info.model_result || info.nodepack_result;
if (!result || result.length == 0) {
return;
}
let self = NodeUsageAnalyzer.instance;
if (!self.install_context) {
return;
}
let btn = self.install_context.btn;
self.hideLoading();
btn.classList.remove("nu-btn-loading");
let errorMsg = "";
for (let hash in result) {
let v = result[hash];
if (v != 'success' && v != 'skip')
errorMsg += v + '\n';
}
for (let k in self.install_context.targets) {
let item = self.install_context.targets[k];
if (info.model_result) {
self.grid.updateCell(item, "installed");
} else if (info.nodepack_result) {
// Handle uninstall completion
if (item.originalData) {
item.originalData.state = "not-installed";
}
self.grid.updateRow(item);
}
}
if (errorMsg) {
self.showError(errorMsg);
show_message("Operation Error:\n" + errorMsg);
} else {
if (info.model_result) {
self.showStatus(`Install ${Object.keys(result).length} models successfully`);
self.showRefresh();
self.showMessage(`To apply the installed model, please click the 'Refresh' button.`, "red");
} else if (info.nodepack_result) {
self.showStatus(`Uninstall ${Object.keys(result).length} custom node(s) successfully`);
self.showMessage(`To apply the uninstalled custom nodes, please restart ComfyUI and refresh browser.`, "red");
}
}
infoToast('Tasks done', `[ComfyUI-Manager] All tasks in the queue have been completed.\n${info.done_count}/${info.total_count}`);
self.install_context = undefined;
}
getModelList(models) {
const typeMap = new Map();
const baseMap = new Map();
models.forEach((item, i) => {
const { type, base, name, reference, installed } = item;
// CRITICAL FIX: Do NOT overwrite originalData - it contains the needed state field!
item.size = sizeToBytes(item.size);
item.hash = md5(name + reference);
if (installed === "True") {
item.selectable = false;
}
typeMap.set(type, type);
baseMap.set(base, base);
});
const typeList = [];
typeMap.forEach(type => {
typeList.push({
label: type,
value: type
});
});
typeList.sort((a, b) => {
const au = a.label.toUpperCase();
const bu = b.label.toUpperCase();
if (au !== bu) {
return au > bu ? 1 : -1;
}
return 0;
});
this.typeList = [{
label: "All",
value: ""
}].concat(typeList);
const baseList = [];
baseMap.forEach(base => {
baseList.push({
label: base,
value: base
});
});
baseList.sort((a, b) => {
const au = a.label.toUpperCase();
const bu = b.label.toUpperCase();
if (au !== bu) {
return au > bu ? 1 : -1;
}
return 0;
});
this.baseList = [{
label: "All",
value: ""
}].concat(baseList);
return models;
}
// ===========================================================================================
async loadData() {
this.showLoading();
this.showStatus(`Analyzing node usage ...`);
const mode = manager_instance.datasrc_combo.value;
const nodeListRes = await fetchData(`/customnode/getlist?mode=${mode}&skip_update=true`);
if (nodeListRes.error) {
this.showError("Failed to get custom node list.");
this.hideLoading();
return;
}
const { channel, node_packs } = nodeListRes.data;
delete node_packs['comfyui-manager'];
this.installed_custom_node_packs = node_packs;
// Use the consolidated workflow analysis utility
const result = await analyzeWorkflowUsage(node_packs);
if (!result.success) {
if (result.error.toString().includes('204')) {
this.showMessage("No workflows were found for analysis.");
} else {
this.showError(result.error);
this.hideLoading();
return;
}
}
// Transform node_packs into models format - ONLY INSTALLED PACKAGES
const models = [];
Object.keys(node_packs).forEach((packKey, index) => {
const pack = node_packs[packKey];
// Only include installed packages (filter out "not-installed" packages)
if (pack.state === "not-installed") {
return; // Skip non-installed packages
}
const usedCount = result.usageMap?.get(packKey) || 0;
const workflowDetails = result.workflowDetailsMap?.get(packKey) || [];
models.push({
title: pack.title || packKey,
reference: pack.reference || pack.files?.[0] || '#',
used_in_count: usedCount,
workflowDetails: workflowDetails,
name: packKey,
originalData: pack
});
});
// Sort by usage count (descending) then by title
models.sort((a, b) => {
if (b.used_in_count !== a.used_in_count) {
return b.used_in_count - a.used_in_count;
}
return a.title.localeCompare(b.title);
});
this.modelList = this.getModelList(models);
this.renderGrid();
this.hideLoading();
}
// ===========================================================================================
showSelection(msg) {
this.element.querySelector(".nu-manager-selection").innerHTML = msg;
}
showError(err) {
this.showMessage(err, "red");
}
showMessage(msg, color) {
if (color) {
msg = `<font color="${color}">${msg}</font>`;
}
this.element.querySelector(".nu-manager-message").innerHTML = msg;
}
showStatus(msg, color) {
if (color) {
msg = `<font color="${color}">${msg}</font>`;
}
this.element.querySelector(".nu-manager-status").innerHTML = msg;
}
showLoading() {
// this.setDisabled(true);
if (this.grid) {
this.grid.showLoading();
this.grid.showMask({
opacity: 0.05
});
}
}
hideLoading() {
// this.setDisabled(false);
if (this.grid) {
this.grid.hideLoading();
this.grid.hideMask();
}
}
setDisabled(disabled) {
const $close = this.element.querySelector(".nu-manager-close");
const $refresh = this.element.querySelector(".nu-manager-refresh");
const $stop = this.element.querySelector(".nu-manager-stop");
const list = [
".nu-manager-header input",
".nu-manager-header select",
".nu-manager-footer button",
".nu-manager-selection button"
].map(s => {
return Array.from(this.element.querySelectorAll(s));
})
.flat()
.filter(it => {
return it !== $close && it !== $refresh && it !== $stop;
});
list.forEach($elem => {
if (disabled) {
$elem.setAttribute("disabled", "disabled");
} else {
$elem.removeAttribute("disabled");
}
});
Array.from(this.element.querySelectorAll(".nu-btn-loading")).forEach($elem => {
$elem.classList.remove("nu-btn-loading");
});
}
showRefresh() {
this.element.querySelector(".nu-manager-refresh").style.display = "block";
}
showStop() {
this.element.querySelector(".nu-manager-stop").style.display = "block";
}
hideStop() {
this.element.querySelector(".nu-manager-stop").style.display = "none";
}
setKeywords(keywords = "") {
this.keywords = keywords;
this.element.querySelector(".nu-manager-keywords").value = keywords;
}
show(sortMode) {
this.element.style.display = "flex";
this.setKeywords("");
this.showSelection("");
this.showMessage("");
this.loadData();
}
close() {
this.element.style.display = "none";
}
}

View File

@ -5180,6 +5180,204 @@
"size": "25.75GB"
},
{
"name": "LTX-2 19B Dev FP8",
"type": "checkpoint",
"base": "LTX-2",
"save_path": "checkpoints/LTX-2",
"description": "LTX-2 19B Dev FP8 model.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-dev-fp8.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-dev-fp8.safetensors",
"size": "27.1GB"
},
{
"name": "LTX-2 19B Distilled FP8",
"type": "checkpoint",
"base": "LTX-2",
"save_path": "checkpoints/LTX-2",
"description": "LTX-2 19B Distilled FP8 model.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-distilled-fp8.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-distilled-fp8.safetensors",
"size": "27.1GB"
},
{
"name": "LTX-2 19B Dev",
"type": "checkpoint",
"base": "LTX-2",
"save_path": "checkpoints/LTX-2",
"description": "LTX-2 19B Dev model.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-dev.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-dev.safetensors",
"size": "43.3GB"
},
{
"name": "LTX-2 19B Distilled",
"type": "checkpoint",
"base": "LTX-2",
"save_path": "checkpoints/LTX-2",
"description": "LTX-2 19B Distilled model.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-distilled.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-distilled.safetensors",
"size": "43.3GB"
},
{
"name": "LTX-2 Spatial Upscaler",
"type": "upscale",
"base": "upscale",
"save_path": "default",
"description": "Spatial upscaler model for LTX-2. This model enhances the spatial resolution of generated videos.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-spatial-upscaler-x2-1.0.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-spatial-upscaler-x2-1.0.safetensors",
"size": "996MB"
},
{
"name": "LTX-2 Temporal Upscaler",
"type": "upscale",
"base": "upscale",
"save_path": "default",
"description": "Temporal upscaler model for LTX-2. This model enhances the temporal resolution of generated videos.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-temporal-upscaler-x2-1.0.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-temporal-upscaler-x2-1.0.safetensors",
"size": "262MB"
},
{
"name": "LTX-2 19B Distilled LoRA",
"type": "lora",
"base": "LTX-2",
"save_path": "loras",
"description": "A LoRA adapter that transforms the standard LTX-2 19B model into a distilled version when loaded.",
"reference": "https://huggingface.co/Lightricks/LTX-2",
"filename": "ltx-2-19b-distilled-lora-384.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2/resolve/main/ltx-2-19b-distilled-lora-384.safetensors",
"size": "7.67GB"
},
{
"name": "LTX-2 19B IC LoRA - Canny Control",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for canny control on LTX-2 19B IC model. Intended for advanced edge control and guided generation.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Canny-Control",
"filename": "ltx-2-19b-ic-lora-canny-control.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Canny-Control/resolve/main/ltx-2-19b-ic-lora-canny-control.safetensors",
"size": "654MB"
},
{
"name": "LTX-2 19B IC LoRA - Depth Control",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for depth control on LTX-2 19B IC model. Adds depth-aware generation guidance.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Depth-Control",
"filename": "ltx-2-19b-ic-lora-depth-control.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Depth-Control/resolve/main/ltx-2-19b-ic-lora-depth-control.safetensors",
"size": "654MB"
},
{
"name": "LTX-2 19B IC LoRA - Detailer",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA detailer for LTX-2 19B IC. Improves fine details and sharpness in generated outputs.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Detailer",
"filename": "ltx-2-19b-ic-lora-detailer.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Detailer/resolve/main/ltx-2-19b-ic-lora-detailer.safetensors",
"size": "2.62GB"
},
{
"name": "LTX-2 19B IC LoRA - Pose Control",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for pose control on LTX-2 19B IC model. Enables pose-guided image/video generation.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Pose-Control",
"filename": "ltx-2-19b-ic-lora-pose-control.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-IC-LoRA-Pose-Control/resolve/main/ltx-2-19b-ic-lora-pose-control.safetensors",
"size": "654MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Dolly In",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for dolly-in camera control with LTX-2 19B. Simulates camera moving closer to subject.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-In",
"filename": "ltx-2-19b-lora-camera-control-dolly-in.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-In/resolve/main/ltx-2-19b-lora-camera-control-dolly-in.safetensors",
"size": "327MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Dolly Left",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for dolly-left camera control with LTX-2 19B. Simulates camera moving left.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Left",
"filename": "ltx-2-19b-lora-camera-control-dolly-left.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Left/resolve/main/ltx-2-19b-lora-camera-control-dolly-left.safetensors",
"size": "327MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Dolly Out",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for dolly-out camera control with LTX-2 19B. Simulates camera moving away from subject.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Out",
"filename": "ltx-2-19b-lora-camera-control-dolly-out.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Out/resolve/main/ltx-2-19b-lora-camera-control-dolly-out.safetensors",
"size": "327MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Dolly Right",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for dolly-right camera control with LTX-2 19B. Simulates camera moving right.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Right",
"filename": "ltx-2-19b-lora-camera-control-dolly-right.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Dolly-Right/resolve/main/ltx-2-19b-lora-camera-control-dolly-right.safetensors",
"size": "327MB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Jib Down",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for jib-down camera control with LTX-2 19B. Simulates vertical camera movement downwards.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Down",
"filename": "ltx-2-19b-lora-camera-control-jib-down.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Down/resolve/main/ltx-2-19b-lora-camera-control-jib-down.safetensors",
"size": "2.21GB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Jib Up",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for jib-up camera control with LTX-2 19B. Simulates vertical camera movement upwards.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Up",
"filename": "ltx-2-19b-lora-camera-control-jib-up.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Jib-Up/resolve/main/ltx-2-19b-lora-camera-control-jib-up.safetensors",
"size": "2.21GB"
},
{
"name": "LTX-2 19B LoRA - Camera Control Static",
"type": "lora",
"base": "LTX-2",
"save_path": "loras/LTX-2",
"description": "LoRA for static camera control with LTX-2 19B. Simulates stationary/static camera view.",
"reference": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Static",
"filename": "ltx-2-19b-lora-camera-control-static.safetensors",
"url": "https://huggingface.co/Lightricks/LTX-2-19b-LoRA-Camera-Control-Static/resolve/main/ltx-2-19b-lora-camera-control-static.safetensors",
"size": "2.21GB"
},
{
"name": "LTX-Video Spatial Upscaler v0.9.7",
"type": "upscale",

View File

@ -1,5 +1,286 @@
{
"custom_nodes": [
{
"author": "CarlMarkswx",
"title": "ComfyUI-Mirror-Download [UNSAFE]",
"reference": "https://github.com/CarlMarkswx/ComfyUI-Mirror-Download",
"files": [
"https://github.com/CarlMarkswx/ComfyUI-Mirror-Download"
],
"install_type": "git-clone",
"description": "Plugin that adds a mirror download button for accessing Hugging Face models via Chinese mirror, with automatic detection and progress tracking. (Description by CC)[w/This nodepack contains a node that has a vulnerability allowing write to arbitrary file paths.]"
},
{
"author": "nekotxt",
"title": "ComfyUI-NTX-support-nodes [WIP]",
"reference": "https://github.com/nekotxt/ComfyUI-NTX-support-nodes",
"files": [
"https://github.com/nekotxt/ComfyUI-NTX-support-nodes"
],
"install_type": "git-clone",
"description": "Utility nodes to support the creation of pipes\nNOTE: The files in the repo are not organized."
},
{
"author": "killshotttttt",
"title": "ComfyUI-killshotttttt [WIP]",
"reference": "https://github.com/killshotttttt/ComfyUI-killshotttttt",
"files": [
"https://github.com/killshotttttt/ComfyUI-killshotttttt"
],
"install_type": "git-clone",
"description": "A custom node for ComfyUI that integrates with the Meshy AI API for Image-to-Image transformations using nano-banana models.\nNOTE: The files in the repo are not organized."
},
{
"author": "bozkut",
"title": "ComfyUI-Prompt-Expander [NAME CONFLICT]",
"reference": "https://github.com/bozkut/ComfyUI-Prompt-Expander",
"files": [
"https://github.com/bozkut/ComfyUI-Prompt-Expander"
],
"install_type": "git-clone",
"description": "An LLM-powered prompt expansion node for ComfyUI. Transform simple prompts into detailed, high-quality image generation prompts."
},
{
"author": "my-xz-org",
"title": "comfyui_xz_nodes",
"reference": "https://github.com/my-xz-org/comfyui_xz_nodes",
"files": [
"https://github.com/my-xz-org/comfyui_xz_nodes"
],
"install_type": "git-clone",
"description": "NODES: XZImageToText"
},
{
"author": "tackcrypto1031",
"title": "[WIP] tk_comfyui_SimpleSize",
"reference": "https://github.com/tackcrypto1031/tk_comfyui_SimpleSize",
"files": [
"https://github.com/tackcrypto1031/tk_comfyui_SimpleSize"
],
"install_type": "git-clone",
"description": "A professional and intelligent aspect ratio and resolution selector for ComfyUI.\nNOTE: The files in the repo are not organized."
},
{
"author": "hashms0a",
"title": "ComfyUI-Qwen-Multi-Angle-Camera-Nodes [WIP]",
"reference": "https://github.com/hashms0a/ComfyUI-Qwen-Multi-Angle-Camera-Nodes",
"files": [
"https://github.com/hashms0a/ComfyUI-Qwen-Multi-Angle-Camera-Nodes"
],
"install_type": "git-clone",
"description": "Comprehensive custom nodes for controlling camera angles with Qwen-Image-Edit-2511-Multiple-Angles-LoRA, supporting all 96 camera positions with basic/advanced selection, orbital animation, elevation sweep, and pre-defined animation paths.\nNOTE: The files in the repo are not organized."
},
{
"author": "jceme",
"title": "Comfy_Extensions [WIP]",
"reference": "https://github.com/jceme/Comfy_Extensions",
"files": [
"https://github.com/jceme/Comfy_Extensions"
],
"install_type": "git-clone",
"description": "ComfyUI extension pack offering image dimension handling, aspect ratio presets, FPS utilities, inpainting, and prompt customization. (Description by CC)\nNOTE: The files in the repo are not organized."
},
{
"author": "saltchicken",
"title": "ComfyUI-Interactive-Cropper",
"reference": "https://github.com/saltchicken/ComfyUI-Interactive-Cropper",
"files": [
"https://github.com/saltchicken/ComfyUI-Interactive-Cropper"
],
"install_type": "git-clone",
"description": "Interactive image cropping node with visual interface. (Description by CC)"
},
{
"author": "shenymce",
"title": "ComfyUI-JsonViewer [WIP]",
"reference": "https://github.com/shenymce/ComfyUI-JsonViewer",
"files": [
"https://github.com/shenymce/ComfyUI-JsonViewer"
],
"install_type": "git-clone",
"description": "OpenPose JSON data visualization for displaying pose keypoint data exported from ComfyUI workflow nodes. (Description by CC)\nNOTE: The files in the repo are not organized."
},
{
"author": "qimi-dev",
"title": "ComfyUI-Qimi-Tiler",
"reference": "https://github.com/qimi-dev/ComfyUI-Qimi-Tiler",
"files": [
"https://github.com/qimi-dev/ComfyUI-Qimi-Tiler"
],
"install_type": "git-clone",
"description": "Image tiling utility nodes for splitting and combining image tiles in ComfyUI. (Description by CC)"
},
{
"author": "maTORIx",
"title": "ComfyUI-KeypointsToImage",
"reference": "https://github.com/maTORIx/ComfyUI-KeypointsToImage",
"files": [
"https://github.com/maTORIx/ComfyUI-KeypointsToImage"
],
"install_type": "git-clone",
"description": "Node for converting keypoints to images. (Description by CC)"
},
{
"author": "IO-AtelierTech",
"title": "comfyui-video-utils [NAME CONFLICT]",
"reference": "https://github.com/IO-AtelierTech/comfyui-video-utils",
"files": [
"https://github.com/IO-AtelierTech/comfyui-video-utils"
],
"install_type": "git-clone",
"description": "Provides utility nodes for video operations, particularly for FFmpeg integration and workflow connectivity."
},
{
"author": "PozzettiAndrea",
"title": "ComfyUI-TRELLIS2 [NAME CONFLICT]",
"reference": "https://github.com/PozzettiAndrea/ComfyUI-TRELLIS2",
"files": [
"https://github.com/PozzettiAndrea/ComfyUI-TRELLIS2"
],
"install_type": "git-clone",
"description": "ComfyUI custom nodes for TRELLIS.2 - Microsoft's image-to-3D generation model for creating high-quality 3D meshes with PBR materials."
},
{
"author": "colorAi",
"title": "comfyui-prompt-manager [NAME CONFLICT]",
"reference": "https://github.com/colorAi/comfyui-prompt-manager",
"files": [
"https://github.com/colorAi/comfyui-prompt-manager"
],
"install_type": "git-clone",
"description": "A modern, powerful, and easy-to-use Prompt Manager extension for ComfyUI."
},
{
"author": "Yukinoshita-Yukinoe",
"title": "ComfyUI-SenseVoice [NAME CONFLICT]",
"reference": "https://github.com/Yukinoshita-Yukinoe/ComfyUI-SenseVoice",
"files": [
"https://github.com/Yukinoshita-Yukinoe/ComfyUI-SenseVoice"
],
"install_type": "git-clone",
"description": "Audio processing node providing SenseVoice speech recognition, audio splitting, and SRT generation for ComfyUI. (Description by CC)"
},
{
"author": "satyam-fp",
"title": "ComfyUI-GeminiImage [NAME CONFLICT]",
"reference": "https://github.com/satyam-fp/ComfyUI-GeminiImage",
"files": [
"https://github.com/satyam-fp/ComfyUI-GeminiImage"
],
"install_type": "git-clone",
"description": "A custom ComfyUI node package that integrates Google Gemini API for AI-powered image generation and enhancement."
},
{
"author": "ai-joe-git",
"title": "ComfyUI-Chatterbox [NAME CONFLICT]",
"reference": "https://github.com/ai-joe-git/ComfyUI-Chatterbox",
"files": [
"https://github.com/ai-joe-git/ComfyUI-Chatterbox"
],
"install_type": "git-clone",
"description": "Chatterbox TTS integration for ComfyUI - Voice cloning with 3 model variants."
},
{
"author": "Sergey004",
"title": "ComfyUI-Telegram-Sender [UNSAFE/NAME CONFLICT]",
"reference": "https://github.com/Sergey004/ComfyUI-Telegram-Sender",
"files": [
"https://github.com/Sergey004/ComfyUI-Telegram-Sender"
],
"install_type": "git-clone",
"description": "Does the same thing as comfyui_image_metadata_extension but is slightly more modern, sending the output to Telegram and downloading unnecessary metadata at the same time.[w/This nodepack contains a path traversal vulnerability.]"
},
{
"author": "cedarconnor",
"title": "ComfyUI-DAP [UNSAFE/NAME CONFLICT]",
"reference": "https://github.com/cedarconnor/ComfyUI-DAP",
"files": [
"https://github.com/cedarconnor/ComfyUI-DAP"
],
"install_type": "git-clone",
"description": "ComfyUI nodes for DAP (Depth Any Panoramas) - panoramic depth estimation[w/This nodepack contains a path traversal vulnerability.]"
},
{
"author": "AhiruNeko",
"title": "ComfyUI-MiniTools [UNSAFE/NAME CONFLICT]",
"reference": "https://github.com/AhiruNeko/ComfyUI-MiniTools",
"files": [
"https://github.com/AhiruNeko/ComfyUI-MiniTools"
],
"install_type": "git-clone",
"description": "A ComfyUI plugins[w/This nodepack contains a path traversal vulnerability.]"
},
{
"author": "861289009",
"title": "comfyui_video_node [WIP]",
"reference": "https://github.com/861289009/comfyui_video_node",
"files": [
"https://github.com/861289009/comfyui_video_node"
],
"install_type": "git-clone",
"description": "A ComfyUI node for creating smooth fade transitions between two video segments using hex color effects. (Description by CC)\nNOTE: The files in the repo are not organized."
},
{
"author": "jluo-github",
"title": "comfyui-easy-resize [WIP]",
"reference": "https://github.com/jluo-github/comfyui-easy-resize",
"files": [
"https://github.com/jluo-github/comfyui-easy-resize"
],
"install_type": "git-clone",
"description": "ComfyUI custom nodes for quick image size selection with curated presets\nNOTE: The files in the repo are not organized."
},
{
"author": "w3rc",
"title": "lpips-similarity-comfyui",
"reference": "https://github.com/w3rc/lpips-similarity-comfyui",
"files": [
"https://github.com/w3rc/lpips-similarity-comfyui"
],
"install_type": "git-clone",
"description": "NODES: GetSimilarity, LPIPSSimilarity"
},
{
"author": "StevenBaby",
"title": "comfyui-tools",
"reference": "https://github.com/StevenBaby/comfyui-tools",
"files": [
"https://github.com/StevenBaby/comfyui-tools"
],
"install_type": "git-clone",
"description": "IntParameterNode: A Node contain 4 int parameters to quick switch input to other nodes."
},
{
"author": "zhu798542746",
"title": "comfyui_model [UNSAFE]",
"reference": "https://github.com/zhu798542746/comfyui_model",
"files": [
"https://github.com/zhu798542746/comfyui_model"
],
"install_type": "git-clone",
"description": "A powerful tool for managing and exporting ComfyUI plugins and models with an elegant side panel interface. (Description by CC) [w/This node pack has a vulnerability that allows it to access (or exfiltrate) data from custom nodes installed remotely.]"
},
{
"author": "simonri",
"title": "ComfyUI-SimonNodes",
"reference": "https://github.com/simonri/ComfyUI-SimonNodes",
"files": [
"https://github.com/simonri/ComfyUI-SimonNodes"
],
"install_type": "git-clone",
"description": "Provides seed upscaling and image cropping utilities for ComfyUI workflows. (Description by CC)"
},
{
"author": "j-pyxal",
"title": "ComfyUI-Lattice-Manim [WIP]",
"reference": "https://github.com/j-pyxal/ComfyUI-Lattice-Manim",
"files": [
"https://github.com/j-pyxal/ComfyUI-Lattice-Manim"
],
"install_type": "git-clone",
"description": "A ComfyUI extension integrating Manim for animation rendering.\nNOTE: The files in the repo are not organized."
},
{
"author": "kanttouchthis",
"title": "ComfyUI-SDNQ [NAME CONFLICT]",
@ -60,16 +341,6 @@
"install_type": "git-clone",
"description": "Personal utility tools for ComfyUI. (Description by CC)\nNOTE: The files in the repo are not organized."
},
{
"author": "EricRorich",
"title": "ComfyUI-Parametric-Face-Canvas [WIP]",
"reference": "https://github.com/EricRorich/ComfyUI-Parametric-Face-Canvas",
"files": [
"https://github.com/EricRorich/ComfyUI-Parametric-Face-Canvas"
],
"install_type": "git-clone",
"description": "Generates a parametric 3D face wireframe and renders it as a 2D image with adjustable facial proportions and camera orientation for use in AI pipelines.\nNOTE: The files in the repo are not organized."
},
{
"author": "HailXD",
"title": "comfyui-random-artist",
@ -2724,16 +2995,6 @@
"install_type": "git-clone",
"description": "NODES: Resize Frame, Pad Batch to 4n+1, Trim Padded Batch, Get Image Dimensions, Slot Frame, ..."
},
{
"author": "LSDJesus",
"title": "ComfyUI-Luna-Collection [WIP]",
"reference": "https://github.com/LSDJesus/ComfyUI-Luna-Collection",
"files": [
"https://github.com/LSDJesus/ComfyUI-Luna-Collection"
],
"install_type": "git-clone",
"description": "Collection of finetuned and custom nodes for Pyrite and I\nNOTE: The files in the repo are not organized."
},
{
"author": "nicolabergamascahkimkoohi",
"title": "ComfyUI-OSS-Upload [UNSAFE]",
@ -2885,16 +3146,6 @@
"install_type": "git-clone",
"description": "A ComfyUI custom node for saving and restoring random seeds. Useful for workflow reproducibility, experimentation, and quickly trying different variations.\nNOTE: The files in the repo are not organized."
},
{
"author": "LSDJesus",
"title": "ComfyUI-Luna-Collection [WIP]",
"reference": "https://github.com/LSDJesus/ComfyUI-Luna-Collection",
"files": [
"https://github.com/LSDJesus/ComfyUI-Luna-Collection"
],
"install_type": "git-clone",
"description": "This repository contains ComfyUI-Luna-Collection, a bespoke collection of custom nodes for ComfyUI, engineered for power, flexibility, and a efficient workflow. These tools are born from a collaborative project between a human architect and their AI muse, Luna.\nNOTE: The files in the repo are not organized."
},
{
"author": "Juste-Leo2",
"title": "ComfyUI-Arduino [WIP]",
@ -3115,16 +3366,6 @@
"install_type": "git-clone",
"description": "Custom ComfyUI nodes for downloading, converting, and previewing audio/video from YouTube and 1,000+ other platforms"
},
{
"author": "LSDJesus",
"title": "ComfyUI-Pyrite-Core [WIP]",
"reference": "https://github.com/LSDJesus/ComfyUI-Luna-Collection",
"files": [
"https://github.com/LSDJesus/ComfyUI-Luna-Collection"
],
"install_type": "git-clone",
"description": "This repository contains ComfyUI-Pyrite-Core, a bespoke collection of custom nodes for ComfyUI, engineered for power, flexibility, and a ruthlessly efficient workflow. These tools are born from a collaborative project between a human architect and their AI muse, Pyrite.\nNOTE: The files in the repo are not organized."
},
{
"author": "comfyscript",
"title": "ComfyUI-CloudClient",
@ -4536,16 +4777,6 @@
"install_type": "git-clone",
"description": "Change of resolutions for ComfyUI and Upscalers\nNOTE: The files in the repo are not organized."
},
{
"author": "pixixai",
"title": "ComfyUI_Pixix-Tools [UNSAFE/WIP]",
"reference": "https://github.com/pixixai/ComfyUI_pixixTools",
"files": [
"https://github.com/pixixai/ComfyUI_pixixTools"
],
"install_type": "git-clone",
"description": "Load Text (from folder)\nNOTE: The files in the repo are not organized.[w/The contents of files from arbitrary paths can be read remotely through this node.]"
},
{
"author": "PeterMikhai",
"title": "DoomFLUX Nodes [WIP]",

View File

@ -428,6 +428,14 @@
"title_aux": "comfyui-promptbymood [WIP]"
}
],
"https://github.com/861289009/comfyui_video_node": [
[
"VideoFadeHex2In"
],
{
"title_aux": "comfyui_video_node [WIP]"
}
],
"https://github.com/90cube/Comfyui-CBcanvas": [
[
"CBCanvasNode"
@ -1202,11 +1210,15 @@
],
"https://github.com/Brandelan/ComfyUI_bd_customNodes": [
[
"BD Float to String",
"BD Int to String",
"BD Random Range",
"BD Random Settings",
"BD Sequencer",
"BD Settings",
"bd_FloatRangeSlider",
"bd_FloatToString",
"bd_IntToString",
"bd_RandomizeSettings",
"bd_Sequencer"
],
@ -1679,15 +1691,18 @@
"DonutFillerClip",
"DonutFillerModel",
"DonutHotReload",
"DonutImageReporter",
"DonutLoRACivitAIInfo",
"DonutLoRAHashLookup",
"DonutLoRALibrary",
"DonutLoRAStack",
"DonutLoRAStackCivitAI",
"DonutLoraStackCombine",
"DonutManualCleanup",
"DonutModelSave",
"DonutMultiModelSampler",
"DonutOpenCivitAI",
"DonutPromptReceiver",
"DonutSDXLTeaCache",
"DonutSDXLTeaCacheStats",
"DonutSampler",
@ -1909,15 +1924,6 @@
"title_aux": "[WIP] ComfyUI-MegaTran-cutom-node"
}
],
"https://github.com/EricRorich/ComfyUI-Parametric-Face-Canvas": [
[
"Parametric Face Canvas",
"ParametricFaceCanvas"
],
{
"title_aux": "ComfyUI-Parametric-Face-Canvas [WIP]"
}
],
"https://github.com/Estanislao-Oviedo/ComfyUI-CustomNodes": [
[
"Attention couple",
@ -2286,6 +2292,15 @@
"title_aux": "comfyui-genai-connectors [WIP]"
}
],
"https://github.com/IO-AtelierTech/comfyui-video-utils": [
[
"GetVideoPath",
"SaveVideoGetPath"
],
{
"title_aux": "comfyui-video-utils [NAME CONFLICT]"
}
],
"https://github.com/IXIWORKS-KIMJUNGHO/comfyui-ixiworks": [
[
"BuildCharacterPromptNode",
@ -2769,77 +2784,6 @@
"title_aux": "HiggsfieldAPI_Node"
}
],
"https://github.com/LSDJesus/ComfyUI-Luna-Collection": [
[
"LunaBatchPromptExtractor",
"LunaBatchPromptLoader",
"LunaBatchUpscaleRefine",
"LunaCheckpointLoader",
"LunaCheckpointTunnel",
"LunaChessRefiner",
"LunaChessTileTest",
"LunaCivitaiBatchScraper",
"LunaCivitaiScraper",
"LunaConfigGateway",
"LunaConnectionEditor",
"LunaConnectionMatcher",
"LunaConnectionStats",
"LunaDaemonCLIPLoader",
"LunaDaemonVAELoader",
"LunaDimensionScaler",
"LunaDynamicModelLoader",
"LunaEmbeddingManager",
"LunaExpressionPromptBuilder",
"LunaExpressionSlicerSaver",
"LunaFBCacheOverride",
"LunaGGUFConverter",
"LunaINT8Loader",
"LunaKSampler",
"LunaKSamplerAdvanced",
"LunaKSamplerHeadless",
"LunaKSamplerScaffold",
"LunaLoRARandomizer",
"LunaLoRAStacker",
"LunaLoRATriggerInjector",
"LunaLoRAValidator",
"LunaModelRestore",
"LunaModelRouter",
"LunaMultiSaver",
"LunaNF4Loader",
"LunaNativeCanvasDownscale",
"LunaOptimizedWeightsManager",
"LunaPipeExpander",
"LunaPrepUpscaler",
"LunaPromptCraft",
"LunaPromptCraftDebug",
"LunaResetModelWeights",
"LunaSAM3Detector",
"LunaScaffoldUpscaler",
"LunaSecondaryModelLoader",
"LunaSemanticDetailer",
"LunaSmartLoRALinker",
"LunaSuperUpscaler",
"LunaSuperUpscalerSimple",
"LunaUNetTunnel",
"LunaUSDUClone",
"LunaVLMPromptGenerator",
"LunaVisionNode",
"LunaWildcardBuilder",
"LunaYAMLInjector",
"LunaYAMLPathExplorer",
"LunaYAMLWildcard",
"LunaYAMLWildcardBatch",
"LunaYAMLWildcardExplorer",
"LunaZImageEncoder",
"LunaZImageProcessor",
"Luna_Advanced_Upscaler",
"Luna_SimpleUpscaler",
"Luna_UltimateSDUpscale"
],
{
"title_aux": "ComfyUI-Pyrite-Core [WIP]"
}
],
"https://github.com/LZpenguin/ComfyUI-Text": [
[
"Add_text_by_mask"
@ -4031,15 +3975,35 @@
"title_aux": "SAM3DObjects [UNSAFE]"
}
],
"https://github.com/PozzettiAndrea/ComfyUI-TRELLIS2": [
[
"LoadTrellis2Models",
"Trellis2ExportGLB",
"Trellis2ExportTrimesh",
"Trellis2GetConditioning",
"Trellis2ImageToShape",
"Trellis2RasterizePBR",
"Trellis2RemoveBackground",
"Trellis2RenderPreview",
"Trellis2RenderVideo",
"Trellis2ShapeToTexturedMesh",
"Trellis2Simplify",
"Trellis2UVUnwrap"
],
{
"title_aux": "ComfyUI-TRELLIS2 [NAME CONFLICT]"
}
],
"https://github.com/PozzettiAndrea/ComfyUI-UniRig": [
[
"UniRigApplySkinningMLNew",
"MIAAutoRig",
"MIALoadModel",
"UniRigApplyAnimation",
"UniRigAutoRig",
"UniRigExportPosedFBX",
"UniRigExtractSkeletonNew",
"UniRigLoadMesh",
"UniRigLoadModel",
"UniRigLoadRiggedMesh",
"UniRigLoadSkeletonModel",
"UniRigLoadSkinningModel",
"UniRigPreviewRiggedMesh",
"UniRigSaveMesh",
"UniRigSaveSkeleton"
@ -4144,6 +4108,7 @@
"https://github.com/Randomwalkforest/Comfyui-Koi-Toolkit": [
[
"AliyunChat",
"AliyunConcurrentVLChat",
"AliyunVLChat",
"AnyToBoolean",
"CropImageByJson",
@ -4152,12 +4117,17 @@
"Florence2JsonShow",
"FreepikIconSearch",
"GeminiVision",
"IdealabAPINode",
"ImageDesaturateEdgeBinarize",
"ImageSubtraction",
"ImageSubtractionAdvanced",
"ImageToSVG_Potracer",
"JsonExtractTextList",
"KoiImageMarker",
"MaskBatchCombine",
"MaskExternalRectangle",
"MaskFilterByInclusion",
"MaskThresholdToWhite",
"PreviewSVG",
"QwenVLBboxVisualizer",
"QwenVLPointVisualizer",
@ -4966,6 +4936,14 @@
"title_aux": "Comfyui_leffa"
}
],
"https://github.com/StevenBaby/comfyui-tools": [
[
"IntParameterNode"
],
{
"title_aux": "comfyui-tools"
}
],
"https://github.com/StoryWalker/comfyui_flux_collection_advanced": [
[
"Example",
@ -5727,6 +5705,17 @@
"title_aux": "upload-to-azure"
}
],
"https://github.com/ai-joe-git/ComfyUI-Chatterbox": [
[
"ChatterboxLoadReferenceAudio",
"ChatterboxPresets",
"ChatterboxSaveAudio",
"ChatterboxTTSNode"
],
{
"title_aux": "ComfyUI-Chatterbox [NAME CONFLICT]"
}
],
"https://github.com/aiden1020/ComfyUI_Artcoder": [
[
"ArtCoder"
@ -6197,6 +6186,7 @@
"EnvVarNode",
"Eval",
"JMESPathSelect",
"RescaleToDimensions",
"WanVideoSize"
],
{
@ -6507,6 +6497,15 @@
"title_aux": "ComfyUI-ACES-EXR-OCIOr [UNSAFE]"
}
],
"https://github.com/bozkut/ComfyUI-Prompt-Expander": [
[
"PromptExpanderNode",
"PromptExpanderSimple"
],
{
"title_aux": "ComfyUI-Prompt-Expander [NAME CONFLICT]"
}
],
"https://github.com/brace-great/comfyui-eim": [
[
"EncryptImage"
@ -6525,15 +6524,67 @@
],
"https://github.com/brandonkish/comfyUI-extractable-text": [
[
"LoRA Testing Node",
"Load Image Easy",
"BK AI Text Cleaner",
"BK AI Text Parser",
"BK Add To JSON",
"BK Add To Path",
"BK Body Ratios",
"BK Caption File Reader",
"BK Caption FileParser",
"BK Combo Tag",
"BK Crop And Pad",
"BK Dynamic Checkpoints",
"BK Dynamic Checkpoints List",
"BK File Select Next Missing",
"BK File Select Next Unprocessed",
"BK File Selector",
"BK Get From JSON",
"BK Get Last Folder Name",
"BK Get Matching Mask",
"BK Get Next Img Without Caption",
"BK Get Next Missing Checkpoint",
"BK Is A Greater Than B INT",
"BK Is A Greater Than Or Equal To B INT",
"BK Is A Less Than B INT",
"BK Is A Less Than Or Equal To B INT",
"BK Is Vertical Image",
"BK Line Counter",
"BK LoRA Testing Node",
"BK Load Image",
"BK Load Image By Path",
"BK Loop Path Builder",
"BK Loop Status Text",
"BK Max Size",
"BK Move File",
"BK Multi Read Text File",
"BK Next Unprocessed File In Folder",
"BK Next Unprocessed Image In Folder",
"BK Path Builder",
"BK Print To Console",
"BK Print To Console With Boarder",
"BK Prompt Sync",
"BK Read Text File",
"BK Remove Any Sentences With Text",
"BK Remove Last Folder",
"BK Remove Mask At Idx",
"BK Remove Mask At Idx SAM3",
"BK Replace All Tags",
"BK Replace Each Tag Random",
"BK Sampler Options Selector",
"BK Save Image",
"BK Save Text File",
"BK String Splitter",
"BK TSV Header Formatter",
"BK TSV Loader",
"BK TSV Prompt Reader",
"BK TSV Random Prompt",
"BK TSV String Parser",
"BK TSV Tag Replacer",
"BK Write Text File",
"Convert To UTF8",
"Get Larger Value",
"Multi LoRA Test Node",
"Ollama Connectivity Data",
"Save Image Easy",
"Save\\Overwrite Image",
"Save\\Overwrite Text File",
"Saveverwrite Image",
"Saveverwrite Text File",
"Single LoRA Test Node"
],
{
@ -6634,6 +6685,17 @@
"title_aux": "ComfyUI-Pixelsmith [WIP]"
}
],
"https://github.com/cedarconnor/ComfyUI-DAP": [
[
"DAP_BatchFromFolder",
"DAP_Inference",
"DAP_SaveDepthBatch",
"DAP_Setup"
],
{
"title_aux": "ComfyUI-DAP [UNSAFE/NAME CONFLICT]"
}
],
"https://github.com/cedarconnor/ComfyUI-HunyuanWorld-Mirror": [
[
"HWMInference",
@ -6839,6 +6901,14 @@
"title_aux": "comfyui-boll-nodes"
}
],
"https://github.com/colorAi/comfyui-prompt-manager": [
[
"PromptManagerNode"
],
{
"title_aux": "comfyui-prompt-manager [NAME CONFLICT]"
}
],
"https://github.com/comfyanonymous/ComfyUI": [
[
"APG",
@ -6987,6 +7057,7 @@
"ImageBlend",
"ImageBlur",
"ImageColorToMask",
"ImageCompare",
"ImageCompositeMasked",
"ImageCrop",
"ImageFlip",
@ -7011,6 +7082,7 @@
"InstructPixToPixConditioning",
"InvertBooleanNode",
"InvertMask",
"JoinAudioChannels",
"JoinImageWithAlpha",
"KSampler",
"KSamplerAdvanced",
@ -7039,12 +7111,21 @@
"KlingTextToVideoWithAudio",
"KlingVideoExtendNode",
"KlingVirtualTryOnNode",
"LTXAVTextEncoderLoader",
"LTXVAddGuide",
"LTXVAudioVAEDecode",
"LTXVAudioVAEEncode",
"LTXVAudioVAELoader",
"LTXVConcatAVLatent",
"LTXVConditioning",
"LTXVCropGuides",
"LTXVEmptyLatentAudio",
"LTXVImgToVideo",
"LTXVImgToVideoInplace",
"LTXVLatentUpsampler",
"LTXVPreprocess",
"LTXVScheduler",
"LTXVSeparateAVLatent",
"LaplaceScheduler",
"LatentAdd",
"LatentApplyOperation",
@ -7377,6 +7458,10 @@
"VeoVideoGenerationNode",
"VideoLinearCFGGuidance",
"VideoTriangleCFGGuidance",
"Vidu2ImageToVideoNode",
"Vidu2ReferenceVideoNode",
"Vidu2StartEndToVideoNode",
"Vidu2TextToVideoNode",
"ViduImageToVideoNode",
"ViduReferenceVideoNode",
"ViduStartEndToVideoNode",
@ -7400,6 +7485,7 @@
"WanMoveTracksFromCoords",
"WanMoveVisualizeTracks",
"WanPhantomSubjectToVideo",
"WanReferenceVideoApi",
"WanSoundImageToVideo",
"WanSoundImageToVideoExtend",
"WanTextToImageApi",
@ -8166,7 +8252,9 @@
],
"https://github.com/flywhale-666/ComfyUI_pixel_snapping": [
[
"FaceHandCrop",
"PixelSnapping",
"PowerfulColorAlignment",
"PowerfulMaskCrop",
"PowerfulMaskRestore"
],
@ -8493,24 +8581,18 @@
"XIS_CoordinatePath",
"XIS_CropImage",
"XIS_CurveEditor",
"XIS_DynamicImageInputs",
"XIS_DynamicKSampler",
"XIS_DynamicPackImages",
"XIS_FloatSwitch",
"XIS_Float_Slider",
"XIS_FromListGet1Color",
"XIS_FromListGet1Cond",
"XIS_FromListGet1Float",
"XIS_FromListGet1Image",
"XIS_FromListGet1Int",
"XIS_FromListGet1Latent",
"XIS_FromListGet1Mask",
"XIS_FromListGet1Model",
"XIS_FromListGet1String",
"XIS_INT_Slider",
"XIS_IPAStyleSettings",
"XIS_IfDataIsNone",
"XIS_ImageAdjustAndBlend",
"XIS_ImageManager",
"XIS_ImageMaskMirror",
"XIS_ImagePreview",
"XIS_ImagePuzzle",
"XIS_ImageSwitch",
"XIS_IntSwitch",
@ -8533,7 +8615,6 @@
"XIS_PromptProcessor",
"XIS_PromptsWithSwitches",
"XIS_ReorderImageMaskGroups",
"XIS_ReorderImages",
"XIS_ResizeImageOrMask",
"XIS_ResizeToDivisible",
"XIS_ResolutionSelector",
@ -8550,6 +8631,7 @@
],
"https://github.com/grokuku/ComfyUI-Holaf": [
[
"HolafAutoSelectX2",
"HolafBundleCreator",
"HolafBundleExtractor",
"HolafBypasser",
@ -8565,6 +8647,7 @@
"HolafMaskToBoolean",
"HolafOverlayNode",
"HolafRemote",
"HolafRemoteSelector",
"HolafResolutionPreset",
"HolafSaveImage",
"HolafSaveVideo",
@ -8621,6 +8704,24 @@
"title_aux": "Comfyui-SadTalker"
}
],
"https://github.com/hashms0a/ComfyUI-Qwen-Multi-Angle-Camera-Nodes": [
[
"QwenMultiAngleBatchSelector",
"QwenMultiAngleCameraAdvanced",
"QwenMultiAngleCameraBasic",
"QwenMultiAngleCameraDistanceTransition",
"QwenMultiAngleCameraElevationSweep",
"QwenMultiAngleCameraFromIndex",
"QwenMultiAngleCameraInfo",
"QwenMultiAngleCameraOrbit",
"QwenMultiAngleCameraPath",
"QwenMultiAngleCameraRandom",
"QwenMultiAnglePromptCombiner"
],
{
"title_aux": "ComfyUI-Qwen-Multi-Angle-Camera-Nodes [WIP]"
}
],
"https://github.com/hay86/ComfyUI_AceNodes": [
[
"ACE_AnyInputSwitchBool",
@ -9178,6 +9279,17 @@
"title_aux": "ComfyUI-Lovis-Node [WIP]"
}
],
"https://github.com/j-pyxal/ComfyUI-Lattice-Manim": [
[
"ManimAudioCaptionNode",
"ManimDataVisualizationNode",
"ManimScriptNode",
"ManimTimelineSceneNode"
],
{
"title_aux": "ComfyUI-Lattice-Manim [WIP]"
}
],
"https://github.com/jKaarlehto/ComfyUI-GetWorkflowName": [
[
"GetWorkflowName"
@ -9198,6 +9310,23 @@
"title_aux": "ComfyUI-DreamO"
}
],
"https://github.com/jceme/Comfy_Extensions": [
[
"DimensionResize",
"FpsHelper",
"ImageAspectPresets",
"ImageAspectResize",
"ImageDim",
"ImageSizePresets",
"InpaintPrepare",
"InpaintRestore",
"NegativePromptCustomizer",
"PositivePromptCustomizer"
],
{
"title_aux": "Comfy_Extensions [WIP]"
}
],
"https://github.com/jchiotaka/ComfyUI-ClarityAI-Upscaler": [
[
"ClarityCreativeUpscaler",
@ -9334,6 +9463,15 @@
"title_aux": "ComfyUI-Midjourney"
}
],
"https://github.com/jluo-github/comfyui-easy-resize": [
[
"EasyImageSize",
"EasyImageSizeLatent"
],
{
"title_aux": "comfyui-easy-resize [WIP]"
}
],
"https://github.com/jn-jairo/jn_node_suite_comfyui": [
[
"JN_AreaInfo",
@ -9809,6 +9947,14 @@
"title_aux": "ComfyUI-WanAnimatePreprocess [WIP]"
}
],
"https://github.com/killshotttttt/ComfyUI-killshotttttt": [
[
"MeshyImageToImage"
],
{
"title_aux": "ComfyUI-killshotttttt [WIP]"
}
],
"https://github.com/kimara-ai/ComfyUI-Kimara-AI-Image-From-URL": [
[
"KimaraAIImageFromURL"
@ -9833,6 +9979,7 @@
"Replace Color By Palette",
"UltralyticsCheckCategory",
"UltralyticsInference",
"UltralyticsInference_face_detection",
"UltralyticsListIndexToNames",
"UltralyticsModelLoader",
"UltralyticsVisualization",
@ -10011,6 +10158,7 @@
"FluxAdaptiveInjector",
"GaussianNoisePadding",
"PresenceDirector",
"PresencePaddingTester",
"PresencePreview",
"PresenceSaver"
],
@ -10539,6 +10687,14 @@
"title_aux": "lyra-nodes"
}
],
"https://github.com/maTORIx/ComfyUI-KeypointsToImage": [
[
"KeypointsToImage"
],
{
"title_aux": "ComfyUI-KeypointsToImage"
}
],
"https://github.com/machinesarenotpeople/comfyui-energycost": [
[
"TimeCostEndNode",
@ -10698,7 +10854,9 @@
"AddSingleObject",
"AddSingleText",
"ColorNode",
"PromptSelector"
"Log",
"PromptSelector",
"SaveImageAndText"
],
{
"title_aux": "ComfyUI-glb-to-stl [WIP]"
@ -10938,6 +11096,14 @@
"title_aux": "ComfyUI-Crop-Border"
}
],
"https://github.com/my-xz-org/comfyui_xz_nodes": [
[
"XZImageToText"
],
{
"title_aux": "comfyui_xz_nodes"
}
],
"https://github.com/naderzare/comfyui-inodes": [
[
"IAzureAiApi",
@ -11454,17 +11620,6 @@
"title_aux": "ComfyUI LLM Prompt Enhancer [WIP]"
}
],
"https://github.com/pixixai/ComfyUI_pixixTools": [
[
"BaiduTranslateNode",
"ChatGLM4TranslateTextNode",
"ColorPicker",
"LoadTextFromFolderNode"
],
{
"title_aux": "ComfyUI_Pixix-Tools [UNSAFE/WIP]"
}
],
"https://github.com/pixuai/ComfyUI-PixuAI": [
[
"PromptSearch"
@ -11621,6 +11776,15 @@
"title_aux": "comfyui-sd3-simple-simpletuner"
}
],
"https://github.com/qimi-dev/ComfyUI-Qimi-Tiler": [
[
"Qimi-ImageToTiles",
"Qimi-TilesImage"
],
{
"title_aux": "ComfyUI-Qimi-Tiler"
}
],
"https://github.com/qlikpetersen/ComfyUI-AI_Tools": [
[
"CreateListJSON",
@ -12056,11 +12220,21 @@
"title_aux": "ComfyUI-Identity-Mixer"
}
],
"https://github.com/saltchicken/ComfyUI-Interactive-Cropper": [
[
"InteractiveCropNode"
],
{
"title_aux": "ComfyUI-Interactive-Cropper"
}
],
"https://github.com/saltchicken/ComfyUI-Local-Loader": [
[
"LoadImageFromDir",
"LoadImageFromOutput",
"LoadImageFromPath",
"LoadSingleImageFromPath"
"LoadSingleImageFromPath",
"LoadVideoFromOutput"
],
{
"title_aux": "ComfyUI-Local-Loader"
@ -12068,7 +12242,7 @@
],
"https://github.com/saltchicken/ComfyUI-Prompter": [
[
"CustomizablePromptGenerator"
"PromptTemplateManager"
],
{
"title_aux": "ComfyUI-Prompter"
@ -12098,6 +12272,15 @@
"title_aux": "ComfyUI-Video-Utils"
}
],
"https://github.com/satyam-fp/ComfyUI-GeminiImage": [
[
"GeminiImageEnhance",
"GeminiTextToImage"
],
{
"title_aux": "ComfyUI-GeminiImage [NAME CONFLICT]"
}
],
"https://github.com/satyasairazole/ComfyUI-Turbandetection": [
[
"TurbanDetectorNode"
@ -12204,6 +12387,14 @@
"title_aux": "comfyui-hydit"
}
],
"https://github.com/shenymce/ComfyUI-JsonViewer": [
[
"JsonViewer"
],
{
"title_aux": "ComfyUI-JsonViewer [WIP]"
}
],
"https://github.com/shinich39/comfyui-run-js": [
[
"RunJS"
@ -12358,6 +12549,16 @@
"title_aux": "ComfyUI-sjnodes"
}
],
"https://github.com/simonri/ComfyUI-SimonNodes": [
[
"SeedVRUpscale",
"UltralyticsCrop",
"UltralyticsModelLoader"
],
{
"title_aux": "ComfyUI-SimonNodes"
}
],
"https://github.com/siyonomicon/ComfyUI-Pin": [
[
"PinGridNode"
@ -12806,6 +13007,14 @@
"title_aux": "ComfyUI-GoogleFX"
}
],
"https://github.com/tackcrypto1031/tk_comfyui_SimpleSize": [
[
"TK_SimpleSize"
],
{
"title_aux": "[WIP] tk_comfyui_SimpleSize"
}
],
"https://github.com/takoyaki1118/ComfyUI-Kakimoji-Layer": [
[
"KakimojiEditor"
@ -13379,6 +13588,15 @@
"title_aux": "ComfyUI-ccsrv2 [WIP]"
}
],
"https://github.com/w3rc/lpips-similarity-comfyui": [
[
"GetSimilarity",
"LPIPSSimilarity"
],
{
"title_aux": "lpips-similarity-comfyui"
}
],
"https://github.com/wTechArtist/ComfyUI-VVL-Tools": [
[
"ApplyUrlsToJson",
@ -13781,6 +13999,7 @@
"FirstLastFrameXZ",
"ImageResizeKJ",
"ImageResizeXZ",
"SelfGuidance",
"TextEncodeQwenImageEditSimpleXZ",
"TextEncodeQwenImageEditXZ",
"TripleCLIPLoaderXZ",
@ -13937,7 +14156,8 @@
"YCFaceAlignToCanvas",
"YCFaceAlignToCanvasV2",
"YCFaceAlignToReference",
"YCFaceAnalysisModels"
"YCFaceAnalysisModels",
"ycFaceMaskCreator"
],
{
"title_aux": "ComfyUI-YCNodes_Advance"
@ -14089,6 +14309,14 @@
"title_aux": "Comfyui-Anything-Converter [UNSAFE]"
}
],
"https://github.com/zhu798542746/comfyui_model": [
[
"ModelManagerNode"
],
{
"title_aux": "comfyui_model [UNSAFE]"
}
],
"https://github.com/zhuanvi/ComfyUI-ZVNodes": [
[
"ApimartDownloadSavedTaskImageZV",

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,75 @@
{
"custom_nodes": [
{
"author": "LSDJesus",
"title": "ComfyUI-Luna-Collection [REMOVED]",
"reference": "https://github.com/LSDJesus/ComfyUI-Luna-Collection",
"files": [
"https://github.com/LSDJesus/ComfyUI-Luna-Collection"
],
"install_type": "git-clone",
"description": "Collection of finetuned and custom nodes for Pyrite and I\nNOTE: The files in the repo are not organized."
},
{
"author": "r3dial",
"title": "Redial Discomphy - Discord Integration for ComfyUI [REMOVED]",
"reference": "https://github.com/r3dial/redial-discomphy",
"files": [
"https://github.com/r3dial/redial-discomphy"
],
"install_type": "git-clone",
"description": "A custom node for ComfyUI that enables direct posting of images, videos, and messages to Discord channels. This node seamlessly integrates your ComfyUI workflows with Discord communication, allowing you to automatically share your generated content."
},
{
"author": "EricRorich",
"title": "ComfyUI-Parametric-Face-Canvas [REMOVED]",
"reference": "https://github.com/EricRorich/ComfyUI-Parametric-Face-Canvas",
"files": [
"https://github.com/EricRorich/ComfyUI-Parametric-Face-Canvas"
],
"install_type": "git-clone",
"description": "Generates a parametric 3D face wireframe and renders it as a 2D image with adjustable facial proportions and camera orientation for use in AI pipelines.\nNOTE: The files in the repo are not organized."
},
{
"author": "pixixai",
"title": "ComfyUI_Pixix-Tools [UNSAFE/REMOVED]",
"reference": "https://github.com/pixixai/ComfyUI_pixixTools",
"files": [
"https://github.com/pixixai/ComfyUI_pixixTools"
],
"install_type": "git-clone",
"description": "Load Text (from folder)\nNOTE: The files in the repo are not organized.[w/The contents of files from arbitrary paths can be read remotely through this node.]"
},
{
"author": "fllywaay",
"title": "Comfyui-TextLine-counter [REMOVED]",
"reference": "https://github.com/zpengcom/Comfyui-TextLine-counter",
"files": [
"https://github.com/zpengcom/Comfyui-TextLine-counter"
],
"install_type": "git-clone",
"description": "A simple multi-line text processing tool, such as line count statistics, ignoring blank lines, etc."
},
{
"author": "daveand",
"title": "ComfyUI-daveand-utils [REMOVED]",
"reference": "https://github.com/daveand/ComfyUI-daveand-utils",
"files": [
"https://github.com/daveand/ComfyUI-daveand-utils"
],
"install_type": "git-clone",
"description": "Utility nodes including ModelConfigSelector for saving checkpoint configurations and managing manual sampler overrides. (Description by CC)"
},
{
"author": "tristanvdb",
"title": "ComfyUI-toolset [REMOVED]",
"reference": "https://github.com/tristanvdb/ComfyUI-toolset",
"files": [
"https://github.com/tristanvdb/ComfyUI-toolset"
],
"install_type": "git-clone",
"description": "Human-in-the-loop image selection tool for ComfyUI workflows using a Flask web server, enabling users to pause workflows and interactively select images via a web browser interface."
},
{
"author": "chuchu114514",
"title": "comfyui_proportion_solver [REMOVED]",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -250,6 +250,30 @@ paths:
type: object
additionalProperties:
$ref: '#/components/schemas/NodePackageMetadata'
/customnode/get_node_types_in_workflows:
get:
summary: List node types used by all user workflows
description: Scan through all workflows in the Comfy user directory, and return a list of all node types used in each one.
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: array
items:
type: object
properties:
workflow_file_name:
type: string
node_types:
type: array
items:
type: string
'500':
description: Error occurred
/customnode/alternatives:
get:

View File

@ -370,10 +370,13 @@ try:
pass
with std_log_lock:
if self.is_stdout:
original_stdout.flush()
else:
original_stderr.flush()
try:
if self.is_stdout:
original_stdout.flush()
else:
original_stderr.flush()
except (OSError, ValueError):
pass
def close(self):
self.flush()

View File

@ -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.39"
version = "3.39.2"
license = { file = "LICENSE.txt" }
dependencies = ["GitPython", "PyGithub", "matrix-nio", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]