Compare commits

...

46 Commits

Author SHA1 Message Date
jfcantu
1f50823c36
Merge 1bdcd1bdbf into 9ad16c0188 2026-05-19 12:29:42 +09:00
Dr.Lt.Data
9ad16c0188 update DB
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
2026-05-18 05:39:11 +09:00
Dr.Lt.Data
a2c41a2a21 update DB
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-05-17 10:10:59 +09:00
Dr.Lt.Data
9690eed4d1 fix(custom-node-list): place ComfyUI-Pixal3D entry after last git-clone block 2026-05-17 10:10:47 +09:00
Dr.Lt.Data
5d1d287735
Merge pull request #2874 from dreamrec/add-comfyui-pixal3d
Add ComfyUI-Pixal3D to custom-node-list.json
2026-05-17 10:10:18 +09:00
Dr.Lt.Data
65789a6c9d update DB
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
2026-05-17 03:47:43 +09:00
Dr.Lt.Data
871a646fd7 update DB 2026-05-16 22:10:31 +09:00
Dr.Lt.Data
27f81a058e fix(custom-node-list): place PlagueKind-Nodes entry after last git-clone block 2026-05-16 22:10:16 +09:00
Dr.Lt.Data
af11571dd7
Merge pull request #2875 from PlagueKind/main
Add PlagueKind Nodes to ComfyUI-Manager registry
2026-05-16 22:09:39 +09:00
Dr.Lt.Data
f59dd4bdc6 update DB 2026-05-16 21:42:09 +09:00
Dr.Lt.Data
90b87db05c fix(custom-node-list): dedup nicehero/comfyui-conditioning-saver (#2883), keep PR entry with id field and proper-case title 2026-05-16 21:41:10 +09:00
Dr.Lt.Data
d6a9d6544d
Merge pull request #2883 from nicehero/main
Add [comfyui-conditioning-saver](https://github.com/nicehero/comfyui-conditioning-saver) to the custom node list.
2026-05-16 21:40:50 +09:00
Dr.Lt.Data
42b028362e
Merge pull request #2882 from ketle-man/add-model-and-prompt-from-metadata
Add Model and Prompt from Metadata node
2026-05-16 21:40:17 +09:00
Dr.Lt.Data
dcf04578b8 fix(custom-node-list): place ComfyUI-Magos-Nodes entry after last git-clone block 2026-05-16 21:39:55 +09:00
Dr.Lt.Data
3eceec5bfa
Merge pull request #2878 from MagosDigitalStudio/main
Add ComfyUI-Magos-Nodes
2026-05-16 21:39:19 +09:00
Dr.Lt.Data
2c0d665929 fix(custom-node-list): place BangtrixToolkit entry after last git-clone block 2026-05-16 21:38:56 +09:00
Dr.Lt.Data
7fa009e9da
Merge pull request #2877 from Anonymzx/main
add BangtrixToolkit to custom nodes list
2026-05-16 21:38:24 +09:00
Dr.Lt.Data
7cf155b2fe
Merge pull request #2868 from pmarmotte2/register-comfyui-pick-any
Add Comfyui-Pick_Any to custom node list
2026-05-16 21:37:55 +09:00
Dr.Lt.Data
350ff2d044 fix(custom-node-list): place XB_ToolBox entry after last git-clone block 2026-05-16 21:37:31 +09:00
Dr.Lt.Data
f64b73fc57
Merge pull request #2807 from wjluoxiao/main
Add custom node: XB_ToolBox
2026-05-16 21:36:47 +09:00
Dr.Lt.Data
1958957d36 fix(custom-node-list): place ComfyUI-PuLID-Flux-Chroma entry after last git-clone block 2026-05-16 21:35:49 +09:00
Dr.Lt.Data
f3236c3031
Merge pull request #1935 from PaoloC68/main
Add ComfyUI-PuLID-Flux-Chroma - First PuLID implementation for Chroma models
2026-05-16 21:32:03 +09:00
Dr.Lt.Data
fcbad506bb
Merge pull request #1913 from flrngel/patch-1
Add ComfyUI_rgbx_xrgb_Wrapper
2026-05-16 21:31:21 +09:00
ketle-man
5de92e4f92 Merge upstream/main - resolve conflict 2026-05-16 14:36:37 +09:00
nicehero
8ea1cf7aa7 add comfyui-conditioning-saver 2026-05-15 13:55:48 +08:00
ketle-man
936dc09f22 Add Model and Prompt from Metadata (ketle-man)
- Add new entry: model-and-prompt-from-metadata
- Remove duplicate comfyui-2dpose-editor entry (leftover from previous closed PR)
- Add node mappings to extension-node-map.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 11:03:50 +09:00
ketle-man
5a19558c01
Merge branch 'Comfy-Org:main' into main 2026-05-15 10:58:06 +09:00
MagosDigitalStudio
2513b3bff1
Add ComfyUI-Magos-Nodes 2026-05-14 12:58:28 +03:00
Anonymzx
919c07f1e1 add BangtrixToolkit to custom nodes list 2026-05-14 16:20:30 +07:00
PlagueKind
8f646e92a0 Add PlagueKind Nodes 2026-05-14 02:09:09 +02:00
silviu
8d9eeb93f4 Add ComfyUI-Pixal3D — Pixal3D image-to-3D for Windows + RTX 30/40/50
ComfyUI custom node wrapping Tencent's SIGGRAPH 2026 Pixal3D pipeline.
Registers 3 nodes (Pixal3DLoadPipeline, Pixal3DImageToMesh, Pixal3DFreePipeline)
in extension-node-map.json so the Manager's "Install Missing Custom Nodes"
flow can find them.

Repo: https://github.com/dreamrec/ComfyUI-Pixal3D
2026-05-13 22:47:29 +03:00
ketle-man
299976330f
Merge branch 'Comfy-Org:main' into main 2026-05-12 13:04:58 +09:00
Pmarmotte
ea3dcde0ca Add Comfyui-Pick_Any custom node 2026-05-11 14:37:29 +02:00
ketle-man
5162e1d601
Merge branch 'Comfy-Org:main' into main 2026-05-02 21:23:26 +09:00
wjluoxiao
a129cc6321
style: finalize internationalization for title and description 2026-04-22 06:52:05 +08:00
wjluoxiao
1fc7b34f4a
Add files via upload 2026-04-13 03:35:02 +08:00
wjluoxiao
ba33ebe2dc
Delete custom-node-list.json 2026-04-13 03:34:43 +08:00
wjluoxiao
d98ca19ef5
Add files via upload 2026-04-13 03:32:02 +08:00
ketle-man
04e12f4b3b
Merge branch 'Comfy-Org:main' into main 2026-03-18 22:13:29 +09:00
ketle-man
23056a0eb8
Add ComfyUI 2D Pose Editor node 2026-03-14 22:45:52 +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
Paolo Calvi
6d87c3e981 Add ComfyUI-PuLID-Flux-Chroma - First PuLID implementation for Chroma models. 2025-06-16 00:20:47 +02:00
Derrick
acc11e1bda
Add ComfyUI_rgbx_xrgb_Wrapper 2025-06-07 20:55:40 -07:00
16 changed files with 8014 additions and 5525 deletions

View File

@ -4504,6 +4504,16 @@
"install_type": "git-clone",
"description": "This is the rgb2x wrapper node for ComfyUI. The required models are automatically downloaded on the first run.\noriginal project : [a/https://github.com/zheng95z/rgbx](original project : https://github.com/zheng95z/rgbx)"
},
{
"author": "flrngel",
"title": "ComfyUI_rgbx_xrgb_Wrapper",
"reference": "https://github.com/flrngel/ComfyUI_rgbx_xrgb_Wrapper",
"files": [
"https://github.com/flrngel/ComfyUI_rgbx_xrgb_Wrapper"
],
"install_type": "git-clone",
"description": "This is the rgb2x and x2rgb wrapper node for ComfyUI. The required models are automatically downloaded on the first run.\noriginal project : [a/https://github.com/zheng95z/rgbx](original project : https://github.com/zheng95z/rgbx)"
},
{
"author": "thecooltechguy",
"title": "ComfyUI Stable Video Diffusion",
@ -17469,13 +17479,14 @@
},
{
"author": "nicehero",
"title": "comfyui-conditioning-saver",
"title": "ComfyUI Conditioning Saver",
"id": "conditioningsaver",
"reference": "https://github.com/nicehero/comfyui-conditioning-saver",
"files": [
"https://github.com/nicehero/comfyui-conditioning-saver"
],
"install_type": "git-clone",
"description": "Save and load CONDITIONING tensors to/from files, enabling reuse of encoded prompts and caching expensive CLIP encodings across workflows."
"description": "Save and load CONDITIONING tensors to or from files, enabling reuse of encoded prompts and caching expensive CLIP encodings across workflows."
},
{
"author": "sakura1bgx",
@ -31361,6 +31372,16 @@
"install_type": "git-clone",
"description": "A custom node for ComfyUI that performs speaker diarization to isolate individual speaker audio tracks from a single audio source."
},
{
"author": "pmarmotte2",
"title": "Comfyui-Pick_Any",
"reference": "https://github.com/pmarmotte2/Comfyui_Pick_Any",
"files": [
"https://github.com/pmarmotte2/Comfyui_Pick_Any"
],
"install_type": "git-clone",
"description": "Advanced chooser nodes for ComfyUI that let workflows pause for image, audio, or text selection, with optional automatic picking and series helper nodes."
},
{
"author": "IIEleven11",
"title": "ComfyUI-FairyTaler",
@ -45473,6 +45494,17 @@
"install_type": "git-clone",
"description": "Workflow management and generation UI plugin. Browse, organize, and execute workflows with auto-generated parameter editing, AI prompt assistant (Ollama), JSON syntax highlighting, Eagle integration, and multi-language support (EN/JA/ZH)."
},
{
"author": "ketle-man",
"title": "Model and Prompt from Metadata",
"id": "model-and-prompt-from-metadata",
"reference": "https://github.com/ketle-man/model-and-prompt-from-metadata",
"files": [
"https://github.com/ketle-man/model-and-prompt-from-metadata"
],
"install_type": "git-clone",
"description": "Drop a ComfyUI PNG/WebP/JSON onto the node to instantly extract and apply checkpoint, VAE, and prompts from embedded metadata. Supports ComfyUI, SD WebUI, SD Forge neo, Fooocus, SDXLPromptStyler, and Lora Loader (LoraManager). Includes LoRA from Metadata and CLIP Text Encode edit+ nodes."
},
{
"author": "senjinthedragon",
"title": "ComfyUI Gender Tag Filter",
@ -47462,6 +47494,89 @@
"install_type": "git-clone",
"description": "Simple ComfyUI node that determines the image size by a keyword in the prompt, optionally replacing the keyword"
},
{
"author": "PaoloC68",
"title": "ComfyUI-PuLID-Flux-Chroma",
"id": "comfyui-pulid-flux-chroma",
"reference": "https://github.com/PaoloC68/ComfyUI-PuLID-Flux-Chroma",
"files": [
"https://github.com/PaoloC68/ComfyUI-PuLID-Flux-Chroma"
],
"install_type": "git-clone",
"description": "The first PuLID (face identity preservation) implementation with full Chroma model support for ComfyUI. Automatically detects and adapts to both FLUX and Chroma models. Fork of PuLID-Flux-Enhanced with comprehensive Chroma compatibility fixes.",
"stars": 0,
"last_update": "2025-06-15 00:00:00",
"preemptions": [
"ApplyPulidFlux",
"PulidFluxModelLoader",
"PulidFluxInsightFaceLoader",
"PulidFluxEvaClipLoader"
],
"nodename_pattern": "PulidFlux",
"badges": [
"Chroma",
"Face Identity"
],
"dependencies": [
"insightface",
"onnxruntime"
],
"pip": [
"insightface",
"onnxruntime"
]
},
{
"author": "WJLUOXIAO",
"title": "XB_ToolBox",
"reference": "https://github.com/WJLUOXIAO/XB_ToolBox",
"files": [
"https://github.com/WJLUOXIAO/XB_ToolBox"
],
"install_type": "git-clone",
"description": "A comprehensive ComfyUI extension suite encompassing both front-end interaction and low-level memory scheduling, designed to help beginners master workflows and simplify local deployment."
},
{
"author": "Anonymzx",
"title": "BangtrixToolkit",
"reference": "https://github.com/Anonymzx/BangtrixToolkit",
"files": [
"https://github.com/Anonymzx/BangtrixToolkit"
],
"install_type": "git-clone",
"description": "Advanced ComfyUI toolkit with translation, AMD ROCm utilities, optimization, and workflow enhance"
},
{
"author": "Magos Digital Studio",
"title": "ComfyUI-Magos-Nodes",
"reference": "https://github.com/MagosDigitalStudio/ComfyUI-Magos-Nodes",
"files": [
"https://github.com/MagosDigitalStudio/ComfyUI-Magos-Nodes"
],
"install_type": "git-clone",
"description": "DWPose & NLF skeleton editor, retargeter, and renderer for ComfyUI. Extract body/hand/face keypoints (+ optional NLF 3D) from video, edit them frame-by-frame in a full-screen timeline + dope sheet + graph + 3D orbit editor with animated camera, retarget DWPose proportions per-cluster, and render clean pose images. Outputs standard ComfyUI types — drives WanAnimate, ControlNet, SCAIL, LTX-Video, UniAnimate, and any other pose-driven pipeline."
},
{
"author": "PlagueKind",
"title": "PlagueKind-Nodes",
"reference": "https://github.com/PlagueKind/ComfyUI-PlagueKind-Nodes",
"files": [
"https://github.com/PlagueKind/ComfyUI-PlagueKind-Nodes"
],
"install_type": "git-clone",
"description": "Custom ComfyUI nodes providing unified image and mask resizing with multiple scaling modes, aspect-ratio preservation, center crop alignment, and stable tensor-based mask transformations."
},
{
"author": "dreamrec",
"title": "ComfyUI-Pixal3D",
"id": "comfyui-pixal3d",
"reference": "https://github.com/dreamrec/ComfyUI-Pixal3D",
"files": [
"https://github.com/dreamrec/ComfyUI-Pixal3D"
],
"install_type": "git-clone",
"description": "ComfyUI wrapper for [a/Pixal3D](https://github.com/TencentARC/Pixal3D) — Tencent's SIGGRAPH 2026 single-image to PBR-textured-3D pipeline. Windows + NVIDIA RTX 30/40/50 (≥ 16 GB VRAM). One image → textured PBR mesh in ~3-5 min. Built on top of [a/ComfyUI-Trellis2](https://github.com/visualbruno/ComfyUI-Trellis2). Note: Pixal3D itself is academic / non-commercial only and NOT for EU use — see NOTICE.md."
},
@ -47854,4 +47969,4 @@
"description": "This is a node to convert an image into a CMYK Halftone dot image."
}
]
}
}

View File

@ -2956,6 +2956,14 @@
"title_aux": "ComfyUI Qwen Prompt Expander"
}
],
"https://github.com/Anonymzx/BangtrixToolkit": [
[
"BangtrixTranslateUniversal"
],
{
"title_aux": "BangtrixToolkit"
}
],
"https://github.com/Anzhc/Anima-Mod-Guidance-ComfyUI-Node": [
[
"AnimaModGuidance"
@ -6779,7 +6787,8 @@
"DenoLTXSequencer",
"DenoMultiImageLoader",
"DenoRTXVFXEasyUpscale",
"DenoResolutionSetup"
"DenoResolutionSetup",
"DenoVideoCompare"
],
{
"title_aux": "Deno Custom Nodes"
@ -14863,6 +14872,19 @@
"title_aux": "comfyui-vram-overlay"
}
],
"https://github.com/MagosDigitalStudio/ComfyUI-Magos-Nodes": [
[
"DWPoseTEEditor",
"DWPoseTEExtractor",
"DWPoseTERenderer",
"MagosPoseRetargeter",
"WanAnimateSamplerPresets",
"WanRatioAndFPS"
],
{
"title_aux": "ComfyUI-Magos-Nodes"
}
],
"https://github.com/MajoorWaldi/ComfyUI-Majoor-AssetsManager": [
[
"MajoorSaveImage",
@ -17478,6 +17500,24 @@
"title_aux": "ComfyUI-Gallery"
}
],
"https://github.com/PaoloC68/ComfyUI-PuLID-Flux-Chroma": [
[
"ApplyPulidFlux",
"PulidFluxEvaClipLoader",
"PulidFluxInsightFaceLoader",
"PulidFluxModelLoader"
],
{
"nodename_pattern": "PulidFlux",
"preemptions": [
"ApplyPulidFlux",
"PulidFluxModelLoader",
"PulidFluxInsightFaceLoader",
"PulidFluxEvaClipLoader"
],
"title_aux": "ComfyUI-PuLID-Flux-Chroma"
}
],
"https://github.com/Parameshvadivel/ComfyUI-SVGview": [
[
"SVGPreview"
@ -17769,6 +17809,14 @@
"title_aux": "Joy Caption Two - PixelaiLabs Edition"
}
],
"https://github.com/PlagueKind/ComfyUI-PlagueKind-Nodes": [
[
"UnifiedResizeImageMask"
],
{
"title_aux": "PlagueKind-Nodes"
}
],
"https://github.com/PnthrLeo/comfyUI-PL-data-tools": [
[
"AreasGenerator",
@ -18165,6 +18213,10 @@
],
"https://github.com/PozzettiAndrea/ComfyUI-TurntableGSViewer": [
[
"GaussianAnalysis",
"GaussianExport",
"GaussianMerge",
"LoadPLY",
"PreviewGaussians"
],
{
@ -24083,6 +24135,35 @@
"title_aux": "WAS Affine"
}
],
"https://github.com/WJLUOXIAO/XB_ToolBox": [
[
"XB_BatchFolderLoader",
"XB_CLIPNameBroadcaster",
"XB_CheckpointBlockSwap",
"XB_ChunkVisualization",
"XB_Dashboard_Zen",
"XB_DynamicBus",
"XB_ImageParamsMaster",
"XB_MasterParameter",
"XB_SageAttentionAccelerator",
"XB_SamplerChunkMaster",
"XB_StoryboardSlicer",
"XB_UNetBlockSwap",
"XB_UNetNameBroadcaster",
"XB_VRAM_Calculator",
"XB_VideoParamsMaster",
"XB_Video_Merger",
"XB_WanFirstLastFrameToVideo",
"XB_WanImageToVideo",
"XB_Wan_ParamBus",
"XB_Wan_RelayNode",
"XTX_Data_Radar",
"XTX_VRAM_Cleaner"
],
{
"title_aux": "XB_ToolBox"
}
],
"https://github.com/WUYUDING2583/ComfyUI-Save-Image-Callback": [
[
"Save Image With Callback"
@ -30977,7 +31058,6 @@
"IO_ImageSaveOverwrite",
"IO_LoadAudioBatch",
"IO_LoadImgBatch",
"IO_LoadImgList",
"IO_LoadShotBatch",
"IO_LoadTextBatch",
"IO_LoadVideoBatch",
@ -31205,6 +31285,7 @@
"mask_sam_detctor",
"math_Remap_data",
"math_calculate",
"math_text_compare",
"model_tool_assy",
"pack_Pack",
"pack_Unpack",
@ -36177,6 +36258,16 @@
"title_aux": "ComfyUI_show_seed"
}
],
"https://github.com/dreamrec/ComfyUI-Pixal3D": [
[
"Pixal3DFreePipeline",
"Pixal3DImageToMesh",
"Pixal3DLoadPipeline"
],
{
"title_aux": "ComfyUI-Pixal3D"
}
],
"https://github.com/drmbt/comfyui-dreambait-nodes": [
[
"AudioInfoPlus",
@ -38258,6 +38349,15 @@
"title_aux": "ComfyUI-Flowty-TripoSR"
}
],
"https://github.com/flrngel/ComfyUI_rgbx_xrgb_Wrapper": [
[
"rgb2x",
"x2rgb"
],
{
"title_aux": "ComfyUI_rgbx_xrgb_Wrapper"
}
],
"https://github.com/fluffydiveX/ComfyUI-hvBlockswap": [
[
"hvBlockSwap"
@ -40444,6 +40544,7 @@
"Double_Prompt_Encode",
"FiveRandomLines",
"Prompt_Extender",
"Prompt_Library",
"RandomLine",
"Simple_Prompt_Library"
],
@ -44338,6 +44439,17 @@
"title_aux": "ComfyUI VRM Pose Editor 3D"
}
],
"https://github.com/ketle-man/model-and-prompt-from-metadata": [
[
"CLIPTextEncodeEditPlus",
"ImageMetadataCheckpointLoader",
"ImageMetadataLoRALoader",
"ImageMetadataPromptLoader"
],
{
"title_aux": "Model and Prompt from Metadata"
}
],
"https://github.com/kevinmcmahondev/comfyui-kmcdev-image-filter-adjustments": [
[
"ImageBlankAlpha",
@ -44768,6 +44880,7 @@
"GetLatentSizeAndCount",
"GetLatentsFromBatchIndexed",
"GetMaskSizeAndCount",
"GetPreviewOverrideFramesKJ",
"GetTrackRange",
"GradientToFloat",
"GrowMaskWithBlur",
@ -44813,7 +44926,6 @@
"InsertImagesToBatchIndexed",
"InsertLatentToIndexed",
"InterpolateCoords",
"Intrinsic_lora_sampling",
"JoinStringMulti",
"JoinStrings",
"LTX2AttentionTunerPatch",
@ -44844,6 +44956,7 @@
"ModelMemoryUseReportPatch",
"ModelPassThrough",
"ModelPatchTorchSettings",
"ModelPreviewOverrideKJ",
"ModelSaveKJ",
"NABLA_AttentionKJ",
"NormalizedAmplitudeToFloatList",
@ -51455,7 +51568,7 @@
"SaveConditioning"
],
{
"title_aux": "comfyui-conditioning-saver"
"title_aux": "ComfyUI Conditioning Saver"
}
],
"https://github.com/nickve28/ComfyUI-Nich-Utils": [
@ -52440,6 +52553,7 @@
"FunPackAdvisorLLM",
"FunPackApplyLoraWeights",
"FunPackClipVisionOutputCombine",
"FunPackConditioningAdjust",
"FunPackContextTransitionWindows",
"FunPackContinueVideo",
"FunPackDistilledFlowSampler",
@ -52453,6 +52567,7 @@
"FunPackStoryMemKeyframeExtractor",
"FunPackStoryMemLastFrameExtractor",
"FunPackStoryWriter",
"FunPackStudio",
"FunPackVideoRefinerV2",
"FunPackVideoStitch"
],
@ -53666,6 +53781,19 @@
"title_aux": "ComfyUI-Speaker-Isolation"
}
],
"https://github.com/pmarmotte2/Comfyui_Pick_Any": [
[
"ADVAudioSeries",
"ADVAudioSeries4",
"ADVImageSeries4",
"ADVPickAnyChooser",
"ADVTextSeries",
"ADVTextSeries4"
],
{
"title_aux": "Comfyui-Pick_Any"
}
],
"https://github.com/pnikolic-amd/ComfyUI_MIGraphX": [
[
"CompileDiffusersMIGraphX"
@ -55328,9 +55456,11 @@
[
"RikanHiddenBase64ImageLoader",
"RikanHiddenBase64ImageSaver",
"RikanMultiLoraLoader",
"RikanPromptRelayEncodeTimeline",
"RikanPromptRelayMultiLoraGate",
"RikanQwenCustomImageSize",
"RikanRTXResolutionSettings",
"RikanWanSpatioTemporalTiledVAEDecode",
"rikan-i2vpainter",
"rikan-i2vpainter-tiled-vae"

File diff suppressed because it is too large Load Diff

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
@ -816,6 +819,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.post("/manager/queue/update_all")

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) {
@ -1918,7 +1852,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];
@ -1965,6 +1902,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;
@ -2034,6 +2013,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

@ -3826,6 +3826,7 @@
"MaskClampedCrop",
"MaskClampedCropSticky",
"SaveConditioning",
"SaveImagePlus",
"SaveInpaintCropCache"
],
{
@ -4735,16 +4736,28 @@
"CloseHost",
"CompileConfig",
"CreateHost",
"DDIMScheduler",
"DDPMScheduler",
"DEISMultistepScheduler",
"DPMSolverMultistepScheduler",
"DPMSolverSinglestepScheduler",
"EncodePromptWithCompel",
"EulerAncestralDiscreteScheduler",
"EulerDiscreteScheduler",
"FlowMatchScheduler",
"FlowMatchSchedulerConfig",
"FluxSampler",
"GroupOffloadConfig",
"HeunDiscreteScheduler",
"KDPM2AncestralDiscreteScheduler",
"KDPM2DiscreteScheduler",
"LMSDiscreteScheduler",
"LoraSelector",
"ModelSelector",
"MultiLoraJoiner",
"OffloadConfig",
"OffloadPipeline",
"PNDMScheduler",
"QuantizationConfig",
"QuantoQuantizationConfig",
"SDNQQuantizationConfig",
@ -4752,12 +4765,12 @@
"SDSamplerPrompt",
"SDUpscaleSampler",
"SVDSampler",
"SchedulerConfig",
"SchedulerSelector",
"SingleConfig",
"SleepHost",
"TCDScheduler",
"TorchAOQuantizationConfig",
"TorchConfig",
"UniPCMultistepScheduler",
"UnsafeModelSelector",
"WanSampler",
"ZImageSampler"
@ -7209,6 +7222,7 @@
"GeminiNanoBanana2V2",
"GeminiNode",
"GenerateTracks",
"GetICLoRAParameters",
"GetImageSize",
"GetVideoComponents",
"GrokImageEditNode",
@ -12459,6 +12473,7 @@
"Fast Groups Bypasser [Eclipse]",
"Fast Groups Muter [Eclipse]",
"Fast Mode Switcher [Eclipse]",
"Fast Mode Toggle [Eclipse]",
"Fast Muter [Eclipse]",
"Float Passer [Eclipse]",
"Float [Eclipse]",

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,129 @@
{
"custom_nodes": [
{
"author": "dreamrec",
"title": "ComfyUI-Pixal3D",
"id": "comfyui-pixal3d",
"reference": "https://github.com/dreamrec/ComfyUI-Pixal3D",
"files": [
"https://github.com/dreamrec/ComfyUI-Pixal3D"
],
"install_type": "git-clone",
"description": "ComfyUI wrapper for [a/Pixal3D](https://github.com/TencentARC/Pixal3D) — Tencent's SIGGRAPH 2026 single-image to PBR-textured-3D pipeline. Windows + NVIDIA RTX 30/40/50 (≥ 16 GB VRAM). One image → textured PBR mesh in ~3-5 min. Built on top of [a/ComfyUI-Trellis2](https://github.com/visualbruno/ComfyUI-Trellis2). Note: Pixal3D itself is academic / non-commercial only and NOT for EU use — see NOTICE.md."
},
{
"author": "PlagueKind",
"title": "PlagueKind-Nodes",
"reference": "https://github.com/PlagueKind/ComfyUI-PlagueKind-Nodes",
"files": [
"https://github.com/PlagueKind/ComfyUI-PlagueKind-Nodes"
],
"install_type": "git-clone",
"description": "Custom ComfyUI nodes providing unified image and mask resizing with multiple scaling modes, aspect-ratio preservation, center crop alignment, and stable tensor-based mask transformations."
},
{
"author": "ketle-man",
"title": "Model and Prompt from Metadata",
"id": "model-and-prompt-from-metadata",
"reference": "https://github.com/ketle-man/model-and-prompt-from-metadata",
"files": [
"https://github.com/ketle-man/model-and-prompt-from-metadata"
],
"install_type": "git-clone",
"description": "Drop a ComfyUI PNG/WebP/JSON onto the node to instantly extract and apply checkpoint, VAE, and prompts from embedded metadata. Supports ComfyUI, SD WebUI, SD Forge neo, Fooocus, SDXLPromptStyler, and Lora Loader (LoraManager). Includes LoRA from Metadata and CLIP Text Encode edit+ nodes."
},
{
"author": "Magos Digital Studio",
"title": "ComfyUI-Magos-Nodes",
"reference": "https://github.com/MagosDigitalStudio/ComfyUI-Magos-Nodes",
"files": [
"https://github.com/MagosDigitalStudio/ComfyUI-Magos-Nodes"
],
"install_type": "git-clone",
"description": "DWPose & NLF skeleton editor, retargeter, and renderer for ComfyUI. Extract body/hand/face keypoints (+ optional NLF 3D) from video, edit them frame-by-frame in a full-screen timeline + dope sheet + graph + 3D orbit editor with animated camera, retarget DWPose proportions per-cluster, and render clean pose images. Outputs standard ComfyUI types — drives WanAnimate, ControlNet, SCAIL, LTX-Video, UniAnimate, and any other pose-driven pipeline."
},
{
"author": "Anonymzx",
"title": "BangtrixToolkit",
"reference": "https://github.com/Anonymzx/BangtrixToolkit",
"files": [
"https://github.com/Anonymzx/BangtrixToolkit"
],
"install_type": "git-clone",
"description": "Advanced ComfyUI toolkit with translation, AMD ROCm utilities, optimization, and workflow enhance"
},
{
"author": "pmarmotte2",
"title": "Comfyui-Pick_Any",
"reference": "https://github.com/pmarmotte2/Comfyui_Pick_Any",
"files": [
"https://github.com/pmarmotte2/Comfyui_Pick_Any"
],
"install_type": "git-clone",
"description": "Advanced chooser nodes for ComfyUI that let workflows pause for image, audio, or text selection, with optional automatic picking and series helper nodes."
},
{
"author": "WJLUOXIAO",
"title": "XB_ToolBox",
"reference": "https://github.com/WJLUOXIAO/XB_ToolBox",
"files": [
"https://github.com/WJLUOXIAO/XB_ToolBox"
],
"install_type": "git-clone",
"description": "A comprehensive ComfyUI extension suite encompassing both front-end interaction and low-level memory scheduling, designed to help beginners master workflows and simplify local deployment."
},
{
"author": "PaoloC68",
"title": "ComfyUI-PuLID-Flux-Chroma",
"id": "comfyui-pulid-flux-chroma",
"reference": "https://github.com/PaoloC68/ComfyUI-PuLID-Flux-Chroma",
"files": [
"https://github.com/PaoloC68/ComfyUI-PuLID-Flux-Chroma"
],
"install_type": "git-clone",
"description": "The first PuLID (face identity preservation) implementation with full Chroma model support for ComfyUI. Automatically detects and adapts to both FLUX and Chroma models. Fork of PuLID-Flux-Enhanced with comprehensive Chroma compatibility fixes.",
"stars": 0,
"last_update": "2025-06-15 00:00:00",
"preemptions": [
"ApplyPulidFlux",
"PulidFluxModelLoader",
"PulidFluxInsightFaceLoader",
"PulidFluxEvaClipLoader"
],
"nodename_pattern": "PulidFlux",
"badges": [
"Chroma",
"Face Identity"
],
"dependencies": [
"insightface",
"onnxruntime"
],
"pip": [
"insightface",
"onnxruntime"
]
},
{
"author": "flrngel",
"title": "ComfyUI_rgbx_xrgb_Wrapper",
"reference": "https://github.com/flrngel/ComfyUI_rgbx_xrgb_Wrapper",
"files": [
"https://github.com/flrngel/ComfyUI_rgbx_xrgb_Wrapper"
],
"install_type": "git-clone",
"description": "This is the rgb2x and x2rgb wrapper node for ComfyUI. The required models are automatically downloaded on the first run.\noriginal project : [a/https://github.com/zheng95z/rgbx](original project : https://github.com/zheng95z/rgbx)"
},
{
"author": "nicehero",
"title": "comfyui-conditioning-saver",
"title": "ComfyUI Conditioning Saver",
"id": "conditioningsaver",
"reference": "https://github.com/nicehero/comfyui-conditioning-saver",
"files": [
"https://github.com/nicehero/comfyui-conditioning-saver"
],
"install_type": "git-clone",
"description": "Save and load CONDITIONING tensors to/from files, enabling reuse of encoded prompts and caching expensive CLIP encodings across workflows."
"description": "Save and load CONDITIONING tensors to or from files, enabling reuse of encoded prompts and caching expensive CLIP encodings across workflows."
},
{
"author": "randyhaylor",

View File

@ -2956,6 +2956,14 @@
"title_aux": "ComfyUI Qwen Prompt Expander"
}
],
"https://github.com/Anonymzx/BangtrixToolkit": [
[
"BangtrixTranslateUniversal"
],
{
"title_aux": "BangtrixToolkit"
}
],
"https://github.com/Anzhc/Anima-Mod-Guidance-ComfyUI-Node": [
[
"AnimaModGuidance"
@ -6779,7 +6787,8 @@
"DenoLTXSequencer",
"DenoMultiImageLoader",
"DenoRTXVFXEasyUpscale",
"DenoResolutionSetup"
"DenoResolutionSetup",
"DenoVideoCompare"
],
{
"title_aux": "Deno Custom Nodes"
@ -14863,6 +14872,19 @@
"title_aux": "comfyui-vram-overlay"
}
],
"https://github.com/MagosDigitalStudio/ComfyUI-Magos-Nodes": [
[
"DWPoseTEEditor",
"DWPoseTEExtractor",
"DWPoseTERenderer",
"MagosPoseRetargeter",
"WanAnimateSamplerPresets",
"WanRatioAndFPS"
],
{
"title_aux": "ComfyUI-Magos-Nodes"
}
],
"https://github.com/MajoorWaldi/ComfyUI-Majoor-AssetsManager": [
[
"MajoorSaveImage",
@ -17478,6 +17500,24 @@
"title_aux": "ComfyUI-Gallery"
}
],
"https://github.com/PaoloC68/ComfyUI-PuLID-Flux-Chroma": [
[
"ApplyPulidFlux",
"PulidFluxEvaClipLoader",
"PulidFluxInsightFaceLoader",
"PulidFluxModelLoader"
],
{
"nodename_pattern": "PulidFlux",
"preemptions": [
"ApplyPulidFlux",
"PulidFluxModelLoader",
"PulidFluxInsightFaceLoader",
"PulidFluxEvaClipLoader"
],
"title_aux": "ComfyUI-PuLID-Flux-Chroma"
}
],
"https://github.com/Parameshvadivel/ComfyUI-SVGview": [
[
"SVGPreview"
@ -17769,6 +17809,14 @@
"title_aux": "Joy Caption Two - PixelaiLabs Edition"
}
],
"https://github.com/PlagueKind/ComfyUI-PlagueKind-Nodes": [
[
"UnifiedResizeImageMask"
],
{
"title_aux": "PlagueKind-Nodes"
}
],
"https://github.com/PnthrLeo/comfyUI-PL-data-tools": [
[
"AreasGenerator",
@ -18165,6 +18213,10 @@
],
"https://github.com/PozzettiAndrea/ComfyUI-TurntableGSViewer": [
[
"GaussianAnalysis",
"GaussianExport",
"GaussianMerge",
"LoadPLY",
"PreviewGaussians"
],
{
@ -24083,6 +24135,35 @@
"title_aux": "WAS Affine"
}
],
"https://github.com/WJLUOXIAO/XB_ToolBox": [
[
"XB_BatchFolderLoader",
"XB_CLIPNameBroadcaster",
"XB_CheckpointBlockSwap",
"XB_ChunkVisualization",
"XB_Dashboard_Zen",
"XB_DynamicBus",
"XB_ImageParamsMaster",
"XB_MasterParameter",
"XB_SageAttentionAccelerator",
"XB_SamplerChunkMaster",
"XB_StoryboardSlicer",
"XB_UNetBlockSwap",
"XB_UNetNameBroadcaster",
"XB_VRAM_Calculator",
"XB_VideoParamsMaster",
"XB_Video_Merger",
"XB_WanFirstLastFrameToVideo",
"XB_WanImageToVideo",
"XB_Wan_ParamBus",
"XB_Wan_RelayNode",
"XTX_Data_Radar",
"XTX_VRAM_Cleaner"
],
{
"title_aux": "XB_ToolBox"
}
],
"https://github.com/WUYUDING2583/ComfyUI-Save-Image-Callback": [
[
"Save Image With Callback"
@ -30977,7 +31058,6 @@
"IO_ImageSaveOverwrite",
"IO_LoadAudioBatch",
"IO_LoadImgBatch",
"IO_LoadImgList",
"IO_LoadShotBatch",
"IO_LoadTextBatch",
"IO_LoadVideoBatch",
@ -31205,6 +31285,7 @@
"mask_sam_detctor",
"math_Remap_data",
"math_calculate",
"math_text_compare",
"model_tool_assy",
"pack_Pack",
"pack_Unpack",
@ -36177,6 +36258,16 @@
"title_aux": "ComfyUI_show_seed"
}
],
"https://github.com/dreamrec/ComfyUI-Pixal3D": [
[
"Pixal3DFreePipeline",
"Pixal3DImageToMesh",
"Pixal3DLoadPipeline"
],
{
"title_aux": "ComfyUI-Pixal3D"
}
],
"https://github.com/drmbt/comfyui-dreambait-nodes": [
[
"AudioInfoPlus",
@ -38258,6 +38349,15 @@
"title_aux": "ComfyUI-Flowty-TripoSR"
}
],
"https://github.com/flrngel/ComfyUI_rgbx_xrgb_Wrapper": [
[
"rgb2x",
"x2rgb"
],
{
"title_aux": "ComfyUI_rgbx_xrgb_Wrapper"
}
],
"https://github.com/fluffydiveX/ComfyUI-hvBlockswap": [
[
"hvBlockSwap"
@ -40444,6 +40544,7 @@
"Double_Prompt_Encode",
"FiveRandomLines",
"Prompt_Extender",
"Prompt_Library",
"RandomLine",
"Simple_Prompt_Library"
],
@ -44338,6 +44439,17 @@
"title_aux": "ComfyUI VRM Pose Editor 3D"
}
],
"https://github.com/ketle-man/model-and-prompt-from-metadata": [
[
"CLIPTextEncodeEditPlus",
"ImageMetadataCheckpointLoader",
"ImageMetadataLoRALoader",
"ImageMetadataPromptLoader"
],
{
"title_aux": "Model and Prompt from Metadata"
}
],
"https://github.com/kevinmcmahondev/comfyui-kmcdev-image-filter-adjustments": [
[
"ImageBlankAlpha",
@ -44768,6 +44880,7 @@
"GetLatentSizeAndCount",
"GetLatentsFromBatchIndexed",
"GetMaskSizeAndCount",
"GetPreviewOverrideFramesKJ",
"GetTrackRange",
"GradientToFloat",
"GrowMaskWithBlur",
@ -44813,7 +44926,6 @@
"InsertImagesToBatchIndexed",
"InsertLatentToIndexed",
"InterpolateCoords",
"Intrinsic_lora_sampling",
"JoinStringMulti",
"JoinStrings",
"LTX2AttentionTunerPatch",
@ -44844,6 +44956,7 @@
"ModelMemoryUseReportPatch",
"ModelPassThrough",
"ModelPatchTorchSettings",
"ModelPreviewOverrideKJ",
"ModelSaveKJ",
"NABLA_AttentionKJ",
"NormalizedAmplitudeToFloatList",
@ -51455,7 +51568,7 @@
"SaveConditioning"
],
{
"title_aux": "comfyui-conditioning-saver"
"title_aux": "ComfyUI Conditioning Saver"
}
],
"https://github.com/nickve28/ComfyUI-Nich-Utils": [
@ -52440,6 +52553,7 @@
"FunPackAdvisorLLM",
"FunPackApplyLoraWeights",
"FunPackClipVisionOutputCombine",
"FunPackConditioningAdjust",
"FunPackContextTransitionWindows",
"FunPackContinueVideo",
"FunPackDistilledFlowSampler",
@ -52453,6 +52567,7 @@
"FunPackStoryMemKeyframeExtractor",
"FunPackStoryMemLastFrameExtractor",
"FunPackStoryWriter",
"FunPackStudio",
"FunPackVideoRefinerV2",
"FunPackVideoStitch"
],
@ -53666,6 +53781,19 @@
"title_aux": "ComfyUI-Speaker-Isolation"
}
],
"https://github.com/pmarmotte2/Comfyui_Pick_Any": [
[
"ADVAudioSeries",
"ADVAudioSeries4",
"ADVImageSeries4",
"ADVPickAnyChooser",
"ADVTextSeries",
"ADVTextSeries4"
],
{
"title_aux": "Comfyui-Pick_Any"
}
],
"https://github.com/pnikolic-amd/ComfyUI_MIGraphX": [
[
"CompileDiffusersMIGraphX"
@ -55328,9 +55456,11 @@
[
"RikanHiddenBase64ImageLoader",
"RikanHiddenBase64ImageSaver",
"RikanMultiLoraLoader",
"RikanPromptRelayEncodeTimeline",
"RikanPromptRelayMultiLoraGate",
"RikanQwenCustomImageSize",
"RikanRTXResolutionSettings",
"RikanWanSpatioTemporalTiledVAEDecode",
"rikan-i2vpainter",
"rikan-i2vpainter-tiled-vae"

View File

@ -259,6 +259,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: