mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-07-20 21:38:18 +08:00
Compare commits
13 Commits
571233fcc7
...
17c0923cfc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17c0923cfc | ||
|
|
037987b917 | ||
|
|
8076c200fc | ||
|
|
69bad851e0 | ||
|
|
47df9c702c | ||
|
|
d6df033a21 | ||
|
|
d72007d1a8 | ||
|
|
79a5258dd2 | ||
|
|
16a212aa70 | ||
|
|
396308854d | ||
|
|
2757728935 | ||
|
|
bb46126fbd | ||
|
|
af1c698117 |
@ -24207,6 +24207,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom utility nodes for ComfyUI, providing helpers for tasks like video frame manipulation and advanced audio saving. Part of the 'Burgstall Enabling The Awesomeness' suite."
|
||||
},
|
||||
{
|
||||
"author": "burgstall-labs",
|
||||
"title": "ComfyUI-Domemaster-Outpaint",
|
||||
"reference": "https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint",
|
||||
"files": [
|
||||
"https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Outpaint flat video into fulldome / planetarium shows: domemaster rendering with tilted-venue presets for ComfyUI, on top of the Burgstall Labs 360 outpaint pipeline."
|
||||
},
|
||||
{
|
||||
"author": "Kidev",
|
||||
"title": "ComfyUI Fisheye Effects Nodes",
|
||||
@ -40985,6 +40995,46 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Click-to-refine sampler. Generate, then click or paint regions to refine them in place. First-class support for FLUX 2 Klein 9B and Qwen-Image-Edit (Smart Inpaint / Smart Guided Inpaint / Xtra-Fine), works with any sampler-compatible model for Refine + Area Prompt. Optional SAM 3 Detect for text-prompted segmentation, and a companion Overrides node for custom samplers (power-sigma, Flux 2 scheduler, NAG)."
|
||||
},
|
||||
{
|
||||
"author": "shootthesound",
|
||||
"title": "Lighthouse",
|
||||
"reference": "https://github.com/shootthesound/comfyui-lighthouse",
|
||||
"files": [
|
||||
"https://github.com/shootthesound/comfyui-lighthouse"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A non-destructive overlay that lights up every node by graph distance from the one you click. Diagnostic for big workflows, educational for understanding workflows others built, and surgical when you need to focus on one neighbourhood at a time."
|
||||
},
|
||||
{
|
||||
"author": "shootthesound",
|
||||
"title": "CleanFreak",
|
||||
"reference": "https://github.com/shootthesound/comfyui-CleanFreak",
|
||||
"files": [
|
||||
"https://github.com/shootthesound/comfyui-CleanFreak"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "One-click tidy-by-role for any ComfyUI workflow — loaders, encoders, samplers, decoders, and outputs each sorted into their own column with coloured group cards and connections preserved. 1200+ node classes pre-classified out of the box."
|
||||
},
|
||||
{
|
||||
"author": "shootthesound",
|
||||
"title": "Finding LoRA",
|
||||
"reference": "https://github.com/shootthesound/comfyui-lora-FindingLora",
|
||||
"files": [
|
||||
"https://github.com/shootthesound/comfyui-lora-FindingLora"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A LoRA loader with bookmarks, trigger-word storage and click-to-copy, fuzzy search, and a one-click button to drop another loader inline and wire it into the model chain automatically."
|
||||
},
|
||||
{
|
||||
"author": "shootthesound",
|
||||
"title": "Image of the Day",
|
||||
"reference": "https://github.com/shootthesound/comfyui-image-of-the-day",
|
||||
"files": [
|
||||
"https://github.com/shootthesound/comfyui-image-of-the-day"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Fetch a fresh image straight into your workflow from 8 daily/random sources. Useful for prompt testing, style references, daily-image automations, or just kicking off a workflow with something interesting."
|
||||
},
|
||||
{
|
||||
"author": "HackAfterDark",
|
||||
"title": "AfterDark Film AR Selector",
|
||||
@ -46306,6 +46356,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom nodes for scoped, graph-based string template variables."
|
||||
},
|
||||
{
|
||||
"author": "boobkake22",
|
||||
"title": "Sampling Planner",
|
||||
"reference": "https://github.com/boobkake22/ComfyUI-SamplingPlanner",
|
||||
"files": [
|
||||
"https://github.com/boobkake22/ComfyUI-SamplingPlanner"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Task-aware Wan 2.2 sampling plans and sampler-specific breakout nodes for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "wobba",
|
||||
"title": "ComfyUI-ChatterBox-Turbo",
|
||||
@ -54053,6 +54113,66 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "One-node studio UI package for Z-Image Turbo, Flux.2 Klein, Qwen Image Edit 2511, and Krea 2 workflows, including T2I, I2I, inpaint, outpaint, edit, faceswap, angle, and upscale modes."
|
||||
},
|
||||
{
|
||||
"author": "depth-layer-rasterizer",
|
||||
"title": "Depth Layer Rasterizer",
|
||||
"reference": "https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer",
|
||||
"files": [
|
||||
"https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Depth-map paper-cut and parallax layer rasterizer for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "iljung1106",
|
||||
"title": "Anima Safe PAG",
|
||||
"reference": "https://github.com/iljung1106/comfyui-anima-safe-pag",
|
||||
"files": [
|
||||
"https://github.com/iljung1106/comfyui-anima-safe-pag"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A soft, Anima-friendly Perturbed Attention Guidance node for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "TechnoWarrior2",
|
||||
"title": "ComfyUI Image Picker",
|
||||
"reference": "https://github.com/TechnoWarrior2/comfyui-image-picker",
|
||||
"files": [
|
||||
"https://github.com/TechnoWarrior2/comfyui-image-picker"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Pauses your workflow and opens a popup window to manually pick which generated images pass through to the rest of the graph."
|
||||
},
|
||||
{
|
||||
"author": "TechnoWarrior2",
|
||||
"title": "ComfyUI Live Preview",
|
||||
"reference": "https://github.com/TechnoWarrior2/comfyui-live-preview",
|
||||
"files": [
|
||||
"https://github.com/TechnoWarrior2/comfyui-live-preview"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Displays a large draggable floating window with live denoising step previews as your image generates, similar to A1111 and Forge."
|
||||
},
|
||||
{
|
||||
"author": "Moon-NE",
|
||||
"title": "MoonNe_Nodes",
|
||||
"reference": "https://github.com/Moon-NE/MoonNe_Nodes",
|
||||
"files": [
|
||||
"https://github.com/Moon-NE/MoonNe_Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom nodes for text operations, including concatenation, horizontal-to-vertical conversion, and batch processing."
|
||||
},
|
||||
{
|
||||
"author": "younestft",
|
||||
"title": "Comfy EverAnimate",
|
||||
"reference": "https://github.com/younestft/Comfy_EverAnimate",
|
||||
"files": [
|
||||
"https://github.com/younestft/Comfy_EverAnimate"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Native ComfyUI nodes for EverAnimate-style chunked WanAnimate workflows, including master/chunk settings, chunk calculation, image carry-over, and boundary color correction."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1217,6 +1217,15 @@
|
||||
"title_aux": "Pixel3DMM ComfyUI Nodes"
|
||||
}
|
||||
],
|
||||
"https://github.com/A1-multiply/ComfyUI-LatentSaver": [
|
||||
[
|
||||
"A1LoadLatent",
|
||||
"A1SaveLatent"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI LatentSaver"
|
||||
}
|
||||
],
|
||||
"https://github.com/A4P7J1N7M05OT/ComfyUI-InlineVariableWildcards": [
|
||||
[
|
||||
"InlineWildcardGenerator",
|
||||
@ -3867,6 +3876,7 @@
|
||||
"Beaufort",
|
||||
"Bifid",
|
||||
"Binary2String",
|
||||
"BitsCounter",
|
||||
"BitwiseAND",
|
||||
"BitwiseLS",
|
||||
"BitwiseNOT",
|
||||
@ -3888,6 +3898,11 @@
|
||||
"ConcatKDFHash_Derive",
|
||||
"ConcatKDFHash_Verify",
|
||||
"ConstantTimeCompare",
|
||||
"DERPrivateKey",
|
||||
"DERPublicKey",
|
||||
"ECPrivateKey",
|
||||
"ECPublicKey",
|
||||
"ECSign",
|
||||
"EdDSAPrivateKeyFormat",
|
||||
"EdDSAPublicKeyFormat",
|
||||
"EdDSASignature",
|
||||
@ -3912,6 +3927,8 @@
|
||||
"Nihilist",
|
||||
"PBKDF2HMAC_Derive",
|
||||
"PBKDF2HMAC_Verify",
|
||||
"PEMPrivateKey",
|
||||
"PEMPublicKey",
|
||||
"Padding",
|
||||
"Playfair",
|
||||
"Polybius",
|
||||
@ -3947,6 +3964,7 @@
|
||||
"X963KDF_Derive",
|
||||
"X963KDF_Verify",
|
||||
"XExchange",
|
||||
"XOFHash",
|
||||
"XPrivateKeyFormat",
|
||||
"XPublicKeyFormat",
|
||||
"Zigzag"
|
||||
@ -5057,6 +5075,14 @@
|
||||
"title_aux": "ComfyUI-BS_Kokoro-onnx"
|
||||
}
|
||||
],
|
||||
"https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint": [
|
||||
[
|
||||
"EquirectToDomemaster"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-Domemaster-Outpaint"
|
||||
}
|
||||
],
|
||||
"https://github.com/Burve/ComfyUI_Burve_Tools": [
|
||||
[
|
||||
"BurveGoogleImageGen",
|
||||
@ -6307,6 +6333,15 @@
|
||||
"title_aux": "ComfyUI-CRZnodes"
|
||||
}
|
||||
],
|
||||
"https://github.com/CoreyCorza/comfyui-lora-loader": [
|
||||
[
|
||||
"CorzaCleanAppliedLoRAs",
|
||||
"CorzaLoRAClean"
|
||||
],
|
||||
{
|
||||
"title_aux": "Corza LoRA Loader (Clean)"
|
||||
}
|
||||
],
|
||||
"https://github.com/Cornmeister/ComfyUI-CornmeisterNL-PowerPack": [
|
||||
[
|
||||
"CornmeisterNL_PowerDiffusionModelLoader",
|
||||
@ -6905,6 +6940,7 @@
|
||||
],
|
||||
"https://github.com/DailyMok/ComfyUI-dAIly": [
|
||||
[
|
||||
"PromptMixerV2dAIly",
|
||||
"PromptMixerdAIly",
|
||||
"TokenCounterdAIly"
|
||||
],
|
||||
@ -17399,26 +17435,24 @@
|
||||
"KSampler Advanced (Dual Output)",
|
||||
"Load Folder",
|
||||
"Load VACE Module",
|
||||
"Loop SCAIL Pose Frames",
|
||||
"Match Color Palette",
|
||||
"Native Wan Pose Strength",
|
||||
"Pixelation Dimensions",
|
||||
"Preview (webm)",
|
||||
"PreviewAsMarkdown",
|
||||
"PreviewImageAlpha",
|
||||
"Remove Background",
|
||||
"ReplaceAlpha",
|
||||
"Resize Image and Mask by Side",
|
||||
"SCAIL Pose Frames",
|
||||
"Save Folder as ZIP",
|
||||
"Save To Zip",
|
||||
"Shift Image Batch",
|
||||
"Snap to Divisible",
|
||||
"Sprite Scale Calculator",
|
||||
"Spritesheet Builder",
|
||||
"Spritesheet Preview",
|
||||
"String to List",
|
||||
"Temporal Mask Cropper",
|
||||
"Trim Conditioning",
|
||||
"VACE Sampler",
|
||||
"WAN Frame Calculator",
|
||||
"WAN Frames to Add & Cut"
|
||||
],
|
||||
@ -25218,6 +25252,17 @@
|
||||
"TTP_Expand_And_Mask",
|
||||
"TTP_Image_Assy",
|
||||
"TTP_Image_Tile_Batch",
|
||||
"TTP_QwenVL3_Local_Loader_Experimental",
|
||||
"TTP_Smart_Tile_Assemble_Experimental",
|
||||
"TTP_Smart_Tile_Image_Upscale_Prep_Experimental",
|
||||
"TTP_Smart_Tile_Interactive_Crop_Experimental",
|
||||
"TTP_Smart_Tile_Loop_Collect_Experimental",
|
||||
"TTP_Smart_Tile_Loop_Source_Experimental",
|
||||
"TTP_Smart_Tile_Output_Size_Estimate_Experimental",
|
||||
"TTP_Smart_Tile_QwenVL_Prompt_Set_Builder_Experimental",
|
||||
"TTP_Smart_Tile_Save_Final_Image_Experimental",
|
||||
"TTP_Smart_Tile_Semantic_Rank_Experimental",
|
||||
"TTP_Smart_Tile_Set_Preview_Experimental",
|
||||
"TTP_Tile_image_size",
|
||||
"TTP_condsetarea_merge",
|
||||
"TTP_condsetarea_merge_test",
|
||||
@ -26780,6 +26825,7 @@
|
||||
"XB_AutoBubbleTextRenderer",
|
||||
"XB_BatchFolderLoader",
|
||||
"XB_BerniniConditioning",
|
||||
"XB_BerniniPromptRouter",
|
||||
"XB_CLIPNameBroadcaster",
|
||||
"XB_CanvasLabel",
|
||||
"XB_CheckpointBlockSwap",
|
||||
@ -26791,6 +26837,7 @@
|
||||
"XB_HumanSegModelLoader",
|
||||
"XB_HumanSegmentation",
|
||||
"XB_ImageParamsMaster",
|
||||
"XB_K2StyleRouter",
|
||||
"XB_KSampler",
|
||||
"XB_KSamplerAdvanced",
|
||||
"XB_MSR",
|
||||
@ -30488,6 +30535,8 @@
|
||||
"BFSHeadSwapLoRADebug",
|
||||
"FaceSequenceBatch",
|
||||
"FrameRangedFaceLoader",
|
||||
"LTXIdentityCAN",
|
||||
"LTXIdentityGemmaVision",
|
||||
"LTXIdentityOverlapConditioning",
|
||||
"LTXIdentityTransfer",
|
||||
"LTXVApplyNeutralMask",
|
||||
@ -33599,6 +33648,29 @@
|
||||
"title_aux": "ComfyUI-ColorCorrectGPU"
|
||||
}
|
||||
],
|
||||
"https://github.com/boobkake22/ComfyUI-SamplingPlanner": [
|
||||
[
|
||||
"AccelerationModelPair",
|
||||
"AccelerationSelector",
|
||||
"KSamplerBreakout",
|
||||
"ModelPairBreakout",
|
||||
"MoeSamplerBreakout",
|
||||
"PrioritySelector",
|
||||
"ProgressiveSigmaControl",
|
||||
"RangeSplitOverrideLegacy",
|
||||
"SamplerSelector",
|
||||
"SamplingPlanWan22",
|
||||
"SchedulerSelector",
|
||||
"ShiftOverride",
|
||||
"SigmaBreakout",
|
||||
"StepBudget",
|
||||
"StepSplitOverride",
|
||||
"TaskSelector"
|
||||
],
|
||||
{
|
||||
"title_aux": "Sampling Planner"
|
||||
}
|
||||
],
|
||||
"https://github.com/boobkake22/ComfyUI-SimpleSwitch": [
|
||||
[
|
||||
"SimpleAudioLatentSwitch",
|
||||
@ -39580,6 +39652,18 @@
|
||||
"title_aux": "demofusion-comfyui"
|
||||
}
|
||||
],
|
||||
"https://github.com/designloves2/ComfyUI-TJ_NODE_STUDIO_ONE": [
|
||||
[
|
||||
"Flux2KleinOneTJNode",
|
||||
"Krea2OneTJNode",
|
||||
"QwenImageEdit2511OneTJNode",
|
||||
"SDXLOneTJNode",
|
||||
"ZImageTurboOneNode"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-TJ_NODE_STUDIO_ONE"
|
||||
}
|
||||
],
|
||||
"https://github.com/destinyfaux/Z-Fuse": [
|
||||
[
|
||||
"ZFuseBake",
|
||||
@ -43141,10 +43225,12 @@
|
||||
"LTXAttentionTimestepEvolution",
|
||||
"LTXAttentionZoneAnalysis",
|
||||
"LTXLatentDims",
|
||||
"LTXQKVMultiplier",
|
||||
"LTXQKVStoreInspect",
|
||||
"LTXQKVTransfer",
|
||||
"LTXRFForwardSampler",
|
||||
"LTXRFReverseSampler",
|
||||
"LTXResetPatches",
|
||||
"LTXStoreDump",
|
||||
"LTXStoreLoad"
|
||||
],
|
||||
@ -46545,6 +46631,14 @@
|
||||
"title_aux": "ComfyUI-iterationNode"
|
||||
}
|
||||
],
|
||||
"https://github.com/iljung1106/comfyui-anima-safe-pag": [
|
||||
[
|
||||
"AnimaSafePAG"
|
||||
],
|
||||
{
|
||||
"title_aux": "Anima Safe PAG"
|
||||
}
|
||||
],
|
||||
"https://github.com/illuminatianon/comfyui-csvwildcards": [
|
||||
[
|
||||
"CSVWildcardNode",
|
||||
@ -54478,6 +54572,14 @@
|
||||
"title_aux": "h4 ToolKit"
|
||||
}
|
||||
],
|
||||
"https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer": [
|
||||
[
|
||||
"DepthRasterizeLayers"
|
||||
],
|
||||
{
|
||||
"title_aux": "Depth Layer Rasterizer"
|
||||
}
|
||||
],
|
||||
"https://github.com/maartenharms/comfyui-flowpath": [
|
||||
[
|
||||
"FlowPath"
|
||||
@ -59986,6 +60088,7 @@
|
||||
[
|
||||
"PJ_Image_Handler",
|
||||
"PJ_Latent_Generator",
|
||||
"PJ_Lora_Loader",
|
||||
"PJ_Text_Translator",
|
||||
"PJ_Video_Latent_Generator"
|
||||
],
|
||||
@ -67885,6 +67988,15 @@
|
||||
"title_aux": "ComfyUI-API-DockerCPU"
|
||||
}
|
||||
],
|
||||
"https://github.com/trx7111/ComfyUI-TrixNodes": [
|
||||
[
|
||||
"TrixBypasser",
|
||||
"TrixBypasserSimple"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-TrixNodes"
|
||||
}
|
||||
],
|
||||
"https://github.com/tsogzark/ComfyUI-load-image-from-url": [
|
||||
[
|
||||
"LoadImageFromUrlOrPath"
|
||||
@ -69744,7 +69856,10 @@
|
||||
],
|
||||
"https://github.com/wearekhepri/comfyui-kepri-nodes-pack": [
|
||||
[
|
||||
"KepriAspectRatio",
|
||||
"KepriFramingAnalysis",
|
||||
"KepriImageFinalize",
|
||||
"KepriMaskAnalysis",
|
||||
"KepriMaskMerge"
|
||||
],
|
||||
{
|
||||
|
||||
10933
github-stats.json
10933
github-stats.json
File diff suppressed because it is too large
Load Diff
@ -921,7 +921,7 @@ class UnifiedManager:
|
||||
except:
|
||||
return version.parse("0.0.0")
|
||||
|
||||
def execute_install_script(self, url, repo_path, instant_execution=False, lazy_mode=False, no_deps=False):
|
||||
def execute_install_script(self, url, repo_path, instant_execution=False, lazy_mode=False, no_deps=False, selected_dependencies=None):
|
||||
install_script_path = os.path.join(repo_path, "install.py")
|
||||
requirements_path = os.path.join(repo_path, "requirements.txt")
|
||||
|
||||
@ -933,8 +933,19 @@ class UnifiedManager:
|
||||
if os.path.exists(requirements_path) and not no_deps:
|
||||
print("Install: pip packages")
|
||||
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
|
||||
|
||||
# Create a set of selected dependency lines for quick lookup
|
||||
selected_lines = set()
|
||||
if selected_dependencies:
|
||||
for dep in selected_dependencies:
|
||||
selected_lines.add(dep.get('line', '').strip())
|
||||
|
||||
lines = manager_util.robust_readlines(requirements_path)
|
||||
for line in lines:
|
||||
# Skip if selected_dependencies is provided and this line is not in the selected list
|
||||
if selected_dependencies is not None and line.strip() not in selected_lines:
|
||||
continue
|
||||
|
||||
package_name = remap_pip_package(line.strip())
|
||||
if package_name and not package_name.startswith('#') and package_name not in self.processed_install:
|
||||
self.processed_install.add(package_name)
|
||||
@ -1342,7 +1353,7 @@ class UnifiedManager:
|
||||
|
||||
return result
|
||||
|
||||
def repo_install(self, url: str, repo_path: str, instant_execution=False, no_deps=False, return_postinstall=False):
|
||||
def repo_install(self, url: str, repo_path: str, instant_execution=False, no_deps=False, return_postinstall=False, selected_dependencies=None):
|
||||
result = ManagedResult('install-git')
|
||||
result.append(url)
|
||||
|
||||
@ -1369,7 +1380,7 @@ class UnifiedManager:
|
||||
repo.close()
|
||||
|
||||
def postinstall():
|
||||
return self.execute_install_script(url, repo_path, instant_execution=instant_execution, no_deps=no_deps)
|
||||
return self.execute_install_script(url, repo_path, instant_execution=instant_execution, no_deps=no_deps, selected_dependencies=selected_dependencies)
|
||||
|
||||
if return_postinstall:
|
||||
return result.with_postinstall(postinstall)
|
||||
@ -1468,7 +1479,7 @@ class UnifiedManager:
|
||||
else:
|
||||
return self.cnr_switch_version(node_id, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall).with_ver('cnr')
|
||||
|
||||
async def install_by_id(self, node_id: str, version_spec=None, channel=None, mode=None, instant_execution=False, no_deps=False, return_postinstall=False):
|
||||
async def install_by_id(self, node_id: str, version_spec=None, channel=None, mode=None, instant_execution=False, no_deps=False, return_postinstall=False, selected_dependencies=None):
|
||||
"""
|
||||
priority if version_spec == None
|
||||
1. CNR latest
|
||||
@ -1519,7 +1530,7 @@ class UnifiedManager:
|
||||
self.unified_disable(node_id, False)
|
||||
|
||||
to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), node_id))
|
||||
res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall)
|
||||
res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall, selected_dependencies=selected_dependencies)
|
||||
if res.result:
|
||||
if version_spec == 'unknown':
|
||||
self.unknown_active_nodes[node_id] = repo_url, to_path
|
||||
@ -1974,7 +1985,7 @@ def __win_check_git_pull(path):
|
||||
process.wait()
|
||||
|
||||
|
||||
def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=False, no_deps=False):
|
||||
def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=False, no_deps=False, selected_dependencies=None):
|
||||
# import ipdb; ipdb.set_trace()
|
||||
install_script_path = os.path.join(repo_path, "install.py")
|
||||
requirements_path = os.path.join(repo_path, "requirements.txt")
|
||||
@ -1986,6 +1997,13 @@ def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=Fa
|
||||
if os.path.exists(requirements_path) and not no_deps:
|
||||
print("Install: pip packages")
|
||||
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
|
||||
|
||||
# Create a set of selected dependency lines for quick lookup
|
||||
selected_lines = set()
|
||||
if selected_dependencies:
|
||||
for dep in selected_dependencies:
|
||||
selected_lines.add(dep.get('line', '').strip())
|
||||
|
||||
with open(requirements_path, "r") as requirements_file:
|
||||
for line in requirements_file:
|
||||
#handle comments
|
||||
@ -1996,6 +2014,10 @@ def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=Fa
|
||||
else:
|
||||
line = line.split('#')[0].strip()
|
||||
|
||||
# Skip if selected_dependencies is provided and this line is not in the selected list
|
||||
if selected_dependencies is not None and line.strip() not in selected_lines:
|
||||
continue
|
||||
|
||||
package_name = remap_pip_package(line.strip())
|
||||
|
||||
if package_name and not package_name.startswith('#'):
|
||||
|
||||
@ -517,7 +517,12 @@ async def task_worker():
|
||||
global tasks_in_progress
|
||||
|
||||
async def do_install(item) -> str:
|
||||
ui_id, node_spec_str, channel, mode, skip_post_install = item
|
||||
if len(item) == 6:
|
||||
ui_id, node_spec_str, channel, mode, skip_post_install, selected_dependencies = item
|
||||
else:
|
||||
# Backward compatibility
|
||||
ui_id, node_spec_str, channel, mode, skip_post_install = item
|
||||
selected_dependencies = []
|
||||
|
||||
try:
|
||||
node_spec = core.unified_manager.resolve_node_spec(node_spec_str)
|
||||
@ -526,7 +531,7 @@ async def task_worker():
|
||||
return f"Cannot resolve install target: '{node_spec_str}'"
|
||||
|
||||
node_name, version_spec, is_specified = node_spec
|
||||
res = await core.unified_manager.install_by_id(node_name, version_spec, channel, mode, return_postinstall=skip_post_install)
|
||||
res = await core.unified_manager.install_by_id(node_name, version_spec, channel, mode, return_postinstall=skip_post_install, selected_dependencies=selected_dependencies)
|
||||
# discard post install if skip_post_install mode
|
||||
|
||||
if res.action not in ['skip', 'enable', 'install-git', 'install-cnr', 'switch-cnr']:
|
||||
@ -1421,7 +1426,9 @@ async def install_custom_node(request):
|
||||
logging.error(SECURITY_MESSAGE_GENERAL)
|
||||
return web.Response(status=404, text="A security error has occurred. Please check the terminal logs")
|
||||
|
||||
install_item = json_data.get('ui_id'), node_spec_str, json_data['channel'], json_data['mode'], skip_post_install
|
||||
# Get selected dependencies if provided
|
||||
selected_dependencies = json_data.get('selectedDependencies', [])
|
||||
install_item = json_data.get('ui_id'), node_spec_str, json_data['channel'], json_data['mode'], skip_post_install, selected_dependencies
|
||||
task_queue.put(("install", install_item))
|
||||
|
||||
return web.Response(status=200)
|
||||
@ -1521,6 +1528,272 @@ async def install_custom_node_pip(request):
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@routes.post("/customnode/analyze_dependencies")
|
||||
async def analyze_dependencies(request):
|
||||
"""
|
||||
Analyze dependencies for a custom node from git URL.
|
||||
Fetches requirements.txt, checks installed packages, and returns dependency list with status.
|
||||
"""
|
||||
try:
|
||||
json_data = await request.json()
|
||||
url = json_data.get('url')
|
||||
commit_id = json_data.get('commitId')
|
||||
branch = json_data.get('branch')
|
||||
|
||||
if not url:
|
||||
return web.json_response({'error': 'URL is required'}, status=400)
|
||||
|
||||
# Fetch requirements.txt from git repository
|
||||
requirements_content = await fetch_requirements_from_git(url, commit_id, branch)
|
||||
|
||||
if requirements_content is None:
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'requirements': None,
|
||||
'dependencies': [],
|
||||
'noRequirementsFile': True
|
||||
})
|
||||
|
||||
# Parse requirements
|
||||
dependencies = parse_requirements(requirements_content)
|
||||
|
||||
# Get installed packages
|
||||
installed_packages = manager_util.get_installed_packages()
|
||||
|
||||
# Analyze each dependency with subdependencies
|
||||
analyzed_dependencies = []
|
||||
for dep_line in dependencies:
|
||||
if not dep_line.strip() or dep_line.strip().startswith('#'):
|
||||
continue
|
||||
|
||||
# Parse dependency line
|
||||
parsed = manager_util.parse_requirement_line(dep_line)
|
||||
if not parsed:
|
||||
continue
|
||||
|
||||
package_name = parsed.get('package')
|
||||
if not package_name:
|
||||
# Fallback: extract from line if package is missing
|
||||
import re
|
||||
match = re.match(r'^([a-zA-Z0-9_.-]+)', dep_line.strip())
|
||||
package_name = match.group(1) if match else "Unknown"
|
||||
|
||||
version_spec = parsed.get('version')
|
||||
# Convert version_spec to string if it's a StrictVersion object
|
||||
if version_spec is not None:
|
||||
version_spec = str(version_spec)
|
||||
|
||||
normalized_name = package_name.lower().replace('-', '_')
|
||||
|
||||
# Check if already installed
|
||||
installed_version = installed_packages.get(normalized_name)
|
||||
|
||||
status = 'new'
|
||||
if installed_version:
|
||||
status = 'installed'
|
||||
|
||||
# Convert version to string if it's not already (handle StrictVersion objects)
|
||||
current_version_str = str(installed_version) if installed_version else None
|
||||
|
||||
# Get subdependencies using pip install --dry-run
|
||||
# This is optional and failures should not block the main flow
|
||||
subdependencies = []
|
||||
# Skip subdependency analysis for already installed packages (not needed)
|
||||
if status != 'installed':
|
||||
try:
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# Run pip install --dry-run to get subdependencies
|
||||
# Some packages like pymeshlab can take longer due to complex dependency resolution
|
||||
# Use a reasonable timeout - if it times out, we'll continue without subdependencies
|
||||
result = subprocess.run(
|
||||
[sys.executable, '-m', 'pip', 'install', '--dry-run', dep_line.strip()],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=45 # Increased timeout to 45 seconds
|
||||
)
|
||||
|
||||
output = result.stdout + result.stderr
|
||||
if output:
|
||||
subdependencies = parse_dry_run_output(output, package_name, installed_packages)
|
||||
except subprocess.TimeoutExpired:
|
||||
# Timeout is not critical - continue without subdependencies
|
||||
logging.debug(f"Subdependency analysis timed out for {package_name} (skipping subdependencies)")
|
||||
subdependencies = []
|
||||
except Exception as e:
|
||||
# Any other error is not critical - continue without subdependencies
|
||||
logging.debug(f"Failed to analyze subdependencies for {package_name}: {e}")
|
||||
subdependencies = []
|
||||
|
||||
# Add main dependency (always add, even if subdependency analysis failed)
|
||||
# Ensure all fields are properly set and clean
|
||||
clean_package_name = str(package_name).strip() if package_name else "Unknown"
|
||||
# Remove any None/null strings that might have been concatenated
|
||||
clean_package_name = clean_package_name.replace('None', '').replace('null', '').strip()
|
||||
if not clean_package_name:
|
||||
clean_package_name = "Unknown"
|
||||
|
||||
analyzed_dependencies.append({
|
||||
'name': clean_package_name,
|
||||
'version': str(version_spec) if version_spec else None,
|
||||
'line': dep_line.strip(),
|
||||
'status': status,
|
||||
'currentVersion': current_version_str,
|
||||
'selected': status != 'installed', # Deselect if already installed
|
||||
'subdependencies': subdependencies
|
||||
})
|
||||
|
||||
return web.json_response({
|
||||
'success': True,
|
||||
'requirements': requirements_content,
|
||||
'dependencies': analyzed_dependencies,
|
||||
'noRequirementsFile': False
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error analyzing dependencies: {e}")
|
||||
traceback.print_exc()
|
||||
return web.json_response({'error': str(e)}, status=500)
|
||||
|
||||
|
||||
def parse_requirements(content):
|
||||
"""Parse requirements.txt content into list of dependency lines."""
|
||||
lines = []
|
||||
for line in content.split('\n'):
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
|
||||
def parse_dry_run_output(output, parent_name, installed_packages):
|
||||
"""Parse pip install --dry-run output to extract subdependencies."""
|
||||
import re
|
||||
subdependencies = []
|
||||
subdeps_map = {}
|
||||
|
||||
lines = output.split('\n')
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
# Look for "Collecting package==version" lines
|
||||
if 'Collecting ' in line and 'Using cached' not in line:
|
||||
# Match: "Collecting package==version" or "Collecting package"
|
||||
match = re.search(r'Collecting\s+([a-zA-Z0-9_.-]+(?:\[[^\]]+\])?)(?:\s*==\s*([^\s\(]+))?', line)
|
||||
if match:
|
||||
dep_name = match.group(1).split('[')[0].strip()
|
||||
# Clean the name - remove any None/null strings
|
||||
if dep_name:
|
||||
dep_name = dep_name.replace('None', '').replace('null', '').strip()
|
||||
dep_version = match.group(2).strip() if match.group(2) else None
|
||||
# Clean version too
|
||||
if dep_version:
|
||||
dep_version = dep_version.replace('None', '').replace('null', '').strip() or None
|
||||
|
||||
# Skip the parent package itself
|
||||
if dep_name.lower() == parent_name.lower():
|
||||
continue
|
||||
|
||||
# Normalize name
|
||||
normalized_name = dep_name.lower().replace('-', '_')
|
||||
|
||||
# Check if already in map (avoid duplicates)
|
||||
if normalized_name not in subdeps_map:
|
||||
# Check if already installed
|
||||
installed_version = installed_packages.get(normalized_name)
|
||||
status = 'installed' if installed_version else 'new'
|
||||
current_version_str = str(installed_version) if installed_version else None
|
||||
|
||||
# Ensure name is always a string, not None
|
||||
if not dep_name:
|
||||
dep_name = "Unknown"
|
||||
|
||||
# Clean the name - remove any None/null strings
|
||||
clean_dep_name = str(dep_name).strip().replace('None', '').replace('null', '').strip()
|
||||
if not clean_dep_name:
|
||||
clean_dep_name = "Unknown"
|
||||
|
||||
subdeps_map[normalized_name] = {
|
||||
'name': clean_dep_name,
|
||||
'version': str(dep_version) if dep_version else None,
|
||||
'status': status,
|
||||
'currentVersion': current_version_str,
|
||||
'selected': status != 'installed'
|
||||
}
|
||||
|
||||
# Also look for "Would install" lines which have more accurate version info
|
||||
if 'Would install' in line:
|
||||
# Match: "Would install package-version"
|
||||
match = re.search(r'Would install\s+([a-zA-Z0-9_.-]+)-([\d.]+)', line)
|
||||
if match:
|
||||
dep_name = match.group(1)
|
||||
dep_version = match.group(2)
|
||||
normalized_name = dep_name.lower().replace('-', '_')
|
||||
|
||||
if normalized_name in subdeps_map:
|
||||
# Update with more accurate version
|
||||
subdeps_map[normalized_name]['version'] = dep_version
|
||||
|
||||
# Convert map to list
|
||||
for normalized_name, dep_info in subdeps_map.items():
|
||||
subdependencies.append(dep_info)
|
||||
|
||||
return subdependencies
|
||||
|
||||
|
||||
async def fetch_requirements_from_git(url, commit_id=None, branch=None):
|
||||
"""
|
||||
Fetch requirements.txt from a git repository URL.
|
||||
Supports GitHub URLs by converting to raw.githubusercontent.com.
|
||||
"""
|
||||
try:
|
||||
# Extract GitHub repo info
|
||||
if 'github.com' in url:
|
||||
# Convert to raw GitHub URL
|
||||
url = url.rstrip('/')
|
||||
if url.endswith('.git'):
|
||||
url = url[:-4]
|
||||
|
||||
# Extract owner/repo
|
||||
match = re.search(r'github\.com[:/]([^/]+)/([^/]+)', url)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
owner = match.group(1)
|
||||
repo = match.group(2)
|
||||
|
||||
# Build raw URL
|
||||
if commit_id:
|
||||
raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{commit_id}/requirements.txt"
|
||||
elif branch:
|
||||
raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/requirements.txt"
|
||||
else:
|
||||
raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/main/requirements.txt"
|
||||
|
||||
# Try to fetch using aiohttp
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(raw_url) as response:
|
||||
if response.status == 200:
|
||||
return await response.text()
|
||||
# Try with master branch if main fails
|
||||
if 'main' in raw_url:
|
||||
raw_url = raw_url.replace('/main/', '/master/')
|
||||
async with session.get(raw_url) as response2:
|
||||
if response2.status == 200:
|
||||
return await response2.text()
|
||||
else:
|
||||
# For non-GitHub URLs, we'd need to clone temporarily
|
||||
# For now, return None (can be enhanced later)
|
||||
logging.warning(f"Non-GitHub URL not fully supported for dependency analysis: {url}")
|
||||
return None
|
||||
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error(f"Error fetching requirements from git: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@routes.post("/manager/queue/uninstall")
|
||||
async def uninstall_custom_node(request):
|
||||
if not is_allowed_security_level('middle'):
|
||||
|
||||
@ -67,6 +67,7 @@ export class CustomNodesManager {
|
||||
this.filter = '';
|
||||
this.keywords = '';
|
||||
this.restartMap = {};
|
||||
this.analyzeDependenciesBeforeInstall = false; // Default: false
|
||||
|
||||
this.init();
|
||||
|
||||
@ -77,6 +78,36 @@ export class CustomNodesManager {
|
||||
}
|
||||
|
||||
init() {
|
||||
// Create checkbox for dependency analysis
|
||||
const analyzeDepsCheckbox = $el("input", {
|
||||
type: "checkbox",
|
||||
id: "cn-analyze-deps-checkbox",
|
||||
checked: this.analyzeDependenciesBeforeInstall,
|
||||
onchange: (e) => {
|
||||
this.analyzeDependenciesBeforeInstall = e.target.checked;
|
||||
},
|
||||
style: {
|
||||
marginRight: "6px",
|
||||
cursor: "pointer"
|
||||
}
|
||||
});
|
||||
|
||||
const analyzeDepsLabel = $el("label", {
|
||||
for: "cn-analyze-deps-checkbox",
|
||||
style: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
color: "#fff",
|
||||
fontSize: "12px",
|
||||
marginRight: "10px",
|
||||
whiteSpace: "nowrap"
|
||||
}
|
||||
}, [
|
||||
analyzeDepsCheckbox,
|
||||
$el("span", { textContent: "Analyse dependencies before node installation" })
|
||||
]);
|
||||
|
||||
const header = $el("div.cn-manager-header.px-2", {}, [
|
||||
// $el("label", {}, [
|
||||
// $el("span", { textContent: "Filter" }),
|
||||
@ -84,6 +115,7 @@ export class CustomNodesManager {
|
||||
// ]),
|
||||
createSettingsCombo("Filter", $el("select.cn-manager-filter")),
|
||||
$el("input.cn-manager-keywords.p-inputtext.p-component", { type: "search", placeholder: "Search" }),
|
||||
analyzeDepsLabel,
|
||||
$el("div.cn-manager-status"),
|
||||
$el("div.cn-flex-auto"),
|
||||
$el("div.cn-manager-channel")
|
||||
@ -105,6 +137,421 @@ export class CustomNodesManager {
|
||||
this.initGrid();
|
||||
}
|
||||
|
||||
showDependencySelectorDialog(dependencies, onSelect) {
|
||||
const dialog = new ComfyDialog();
|
||||
dialog.element.style.zIndex = 1100;
|
||||
dialog.element.style.width = "900px";
|
||||
dialog.element.style.maxHeight = "80vh";
|
||||
dialog.element.style.padding = "0";
|
||||
dialog.element.style.backgroundColor = "#2a2a2a";
|
||||
dialog.element.style.border = "1px solid #3a3a3a";
|
||||
dialog.element.style.borderRadius = "8px";
|
||||
dialog.element.style.boxSizing = "border-box";
|
||||
dialog.element.style.overflow = "hidden";
|
||||
|
||||
const contentStyle = {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: "20px",
|
||||
boxSizing: "border-box",
|
||||
gap: "15px"
|
||||
};
|
||||
|
||||
// Create scrollable table container with sticky header
|
||||
const tableContainer = $el("div", {
|
||||
style: {
|
||||
maxHeight: "500px",
|
||||
overflowY: "auto",
|
||||
border: "1px solid #4a4a4a",
|
||||
borderRadius: "4px",
|
||||
backgroundColor: "#1a1a1a",
|
||||
position: "relative"
|
||||
}
|
||||
});
|
||||
|
||||
// Create table
|
||||
const table = $el("table", {
|
||||
style: {
|
||||
width: "100%",
|
||||
borderCollapse: "separate",
|
||||
borderSpacing: "0",
|
||||
fontSize: "14px"
|
||||
}
|
||||
});
|
||||
|
||||
// Create table header with sticky positioning
|
||||
const thead = $el("thead", {
|
||||
style: {
|
||||
position: "sticky",
|
||||
top: "0",
|
||||
zIndex: "10",
|
||||
backgroundColor: "#2a2a2a",
|
||||
boxShadow: "0 2px 4px rgba(0,0,0,0.3)"
|
||||
}
|
||||
}, [
|
||||
$el("tr", {
|
||||
style: {
|
||||
backgroundColor: "#2a2a2a",
|
||||
borderBottom: "2px solid #4a4a4a"
|
||||
}
|
||||
}, [
|
||||
$el("th", {
|
||||
textContent: "",
|
||||
style: {
|
||||
padding: "10px",
|
||||
textAlign: "left",
|
||||
width: "40px",
|
||||
color: "#fff"
|
||||
}
|
||||
}),
|
||||
$el("th", {
|
||||
textContent: "Dependency Name",
|
||||
style: {
|
||||
padding: "10px",
|
||||
textAlign: "left",
|
||||
color: "#fff",
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}),
|
||||
$el("th", {
|
||||
textContent: "Current Version",
|
||||
style: {
|
||||
padding: "10px",
|
||||
textAlign: "left",
|
||||
color: "#fff",
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}),
|
||||
$el("th", {
|
||||
textContent: "Incoming Version",
|
||||
style: {
|
||||
padding: "10px",
|
||||
textAlign: "left",
|
||||
color: "#fff",
|
||||
fontWeight: "bold"
|
||||
}
|
||||
})
|
||||
])
|
||||
]);
|
||||
|
||||
// Create table body
|
||||
const tbody = $el("tbody", {});
|
||||
|
||||
// Create table rows for each dependency and its subdependencies
|
||||
let rowIndex = 0;
|
||||
dependencies.forEach((dep) => {
|
||||
// Ensure name is not null/undefined and clean it
|
||||
let depName = dep.name;
|
||||
if (!depName || depName === 'null' || depName === 'None') {
|
||||
// Fallback: extract from line
|
||||
if (dep.line) {
|
||||
depName = dep.line.split(/[=<>!~]/)[0].trim();
|
||||
} else {
|
||||
depName = "Unknown";
|
||||
}
|
||||
}
|
||||
// Remove any "null" suffix that might have been appended
|
||||
depName = String(depName).replace(/null$/i, '').trim();
|
||||
|
||||
const isInstalled = dep.status === 'installed';
|
||||
const incomingVersion = dep.version || "NA";
|
||||
const currentVersion = dep.currentVersion || "NA";
|
||||
|
||||
// Main dependency row
|
||||
const row = $el("tr", {
|
||||
style: {
|
||||
backgroundColor: rowIndex % 2 === 0 ? "#1a1a1a" : "#222222",
|
||||
borderBottom: "1px solid #3a3a3a"
|
||||
}
|
||||
}, [
|
||||
$el("td", {
|
||||
style: {
|
||||
padding: "10px",
|
||||
textAlign: "center"
|
||||
}
|
||||
}, [
|
||||
$el("input", {
|
||||
type: "checkbox",
|
||||
checked: dep.selected,
|
||||
onchange: (e) => {
|
||||
dep.selected = e.target.checked;
|
||||
},
|
||||
style: {
|
||||
cursor: "pointer",
|
||||
width: "18px",
|
||||
height: "18px"
|
||||
}
|
||||
})
|
||||
]),
|
||||
$el("td", {
|
||||
style: {
|
||||
padding: "10px",
|
||||
color: isInstalled ? "#888" : "#fff"
|
||||
}
|
||||
}, [
|
||||
$el("span", {
|
||||
textContent: depName,
|
||||
style: {
|
||||
fontWeight: "500",
|
||||
marginRight: isInstalled ? "8px" : "0"
|
||||
}
|
||||
}),
|
||||
isInstalled ? $el("span", {
|
||||
textContent: "Installed",
|
||||
style: {
|
||||
display: "inline-block",
|
||||
backgroundColor: "#2a4a2a",
|
||||
color: "#4a9",
|
||||
padding: "2px 6px",
|
||||
borderRadius: "3px",
|
||||
fontSize: "10px",
|
||||
fontWeight: "bold",
|
||||
border: "1px solid #4a9"
|
||||
}
|
||||
}) : ''
|
||||
]),
|
||||
$el("td", {
|
||||
textContent: currentVersion,
|
||||
style: {
|
||||
padding: "10px",
|
||||
color: isInstalled ? "#4a9" : "#aaa",
|
||||
fontFamily: "monospace"
|
||||
}
|
||||
}),
|
||||
$el("td", {
|
||||
textContent: incomingVersion,
|
||||
style: {
|
||||
padding: "10px",
|
||||
color: "#fff",
|
||||
fontFamily: "monospace"
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
tbody.appendChild(row);
|
||||
rowIndex++;
|
||||
|
||||
// Add subdependencies as indented rows
|
||||
if(dep.subdependencies && dep.subdependencies.length > 0) {
|
||||
dep.subdependencies.forEach((subdep) => {
|
||||
// Ensure subdependency name is not null/undefined and clean it
|
||||
let subdepName = subdep.name;
|
||||
if (!subdepName || subdepName === 'null' || subdepName === 'None') {
|
||||
subdepName = "Unknown";
|
||||
}
|
||||
// Remove any "null" suffix that might have been appended
|
||||
subdepName = String(subdepName).replace(/null$/i, '').trim();
|
||||
|
||||
const subIsInstalled = subdep.status === 'installed';
|
||||
const subIncomingVersion = subdep.version || "NA";
|
||||
const subCurrentVersion = subdep.currentVersion || "NA";
|
||||
|
||||
const subRow = $el("tr", {
|
||||
style: {
|
||||
backgroundColor: rowIndex % 2 === 0 ? "#1a1a1a" : "#222222",
|
||||
borderBottom: "1px solid #3a3a3a"
|
||||
}
|
||||
}, [
|
||||
$el("td", {
|
||||
style: {
|
||||
padding: "10px",
|
||||
textAlign: "center"
|
||||
}
|
||||
}, [
|
||||
$el("input", {
|
||||
type: "checkbox",
|
||||
checked: subdep.selected,
|
||||
onchange: (e) => {
|
||||
subdep.selected = e.target.checked;
|
||||
},
|
||||
style: {
|
||||
cursor: "pointer",
|
||||
width: "18px",
|
||||
height: "18px"
|
||||
}
|
||||
})
|
||||
]),
|
||||
$el("td", {
|
||||
style: {
|
||||
padding: "10px 10px 10px 30px",
|
||||
color: subIsInstalled ? "#888" : "#aaa",
|
||||
fontSize: "13px"
|
||||
}
|
||||
}, [
|
||||
$el("span", {
|
||||
textContent: "└─ " + subdepName,
|
||||
style: {
|
||||
fontWeight: "400",
|
||||
marginRight: subIsInstalled ? "8px" : "0"
|
||||
}
|
||||
}),
|
||||
subIsInstalled ? $el("span", {
|
||||
textContent: "Installed",
|
||||
style: {
|
||||
display: "inline-block",
|
||||
backgroundColor: "#2a4a2a",
|
||||
color: "#4a9",
|
||||
padding: "2px 6px",
|
||||
borderRadius: "3px",
|
||||
fontSize: "10px",
|
||||
fontWeight: "bold",
|
||||
border: "1px solid #4a9"
|
||||
}
|
||||
}) : ''
|
||||
]),
|
||||
$el("td", {
|
||||
textContent: subCurrentVersion,
|
||||
style: {
|
||||
padding: "10px",
|
||||
color: subIsInstalled ? "#4a9" : "#666",
|
||||
fontFamily: "monospace",
|
||||
fontSize: "13px"
|
||||
}
|
||||
}),
|
||||
$el("td", {
|
||||
textContent: subIncomingVersion,
|
||||
style: {
|
||||
padding: "10px",
|
||||
color: "#aaa",
|
||||
fontFamily: "monospace",
|
||||
fontSize: "13px"
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
tbody.appendChild(subRow);
|
||||
rowIndex++;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
table.appendChild(thead);
|
||||
table.appendChild(tbody);
|
||||
tableContainer.appendChild(table);
|
||||
|
||||
const content = $el("div", {
|
||||
style: contentStyle
|
||||
}, [
|
||||
$el("h3", {
|
||||
textContent: "Select Dependencies to Install",
|
||||
style: {
|
||||
color: "#ffffff",
|
||||
backgroundColor: "#1a1a1a",
|
||||
padding: "10px 15px",
|
||||
margin: "0 0 10px 0",
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
borderRadius: "4px",
|
||||
boxSizing: "border-box"
|
||||
}
|
||||
}),
|
||||
$el("div", {
|
||||
textContent: `${dependencies.filter(d => d.status === 'installed').length} already installed, ${dependencies.filter(d => d.status !== 'installed').length} to install`,
|
||||
style: {
|
||||
color: "#aaa",
|
||||
fontSize: "12px",
|
||||
marginBottom: "5px"
|
||||
}
|
||||
}),
|
||||
tableContainer,
|
||||
$el("div", {
|
||||
style: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
gap: "10px",
|
||||
marginTop: "10px"
|
||||
}
|
||||
}, [
|
||||
$el("button", {
|
||||
textContent: "Cancel",
|
||||
onclick: () => {
|
||||
onSelect(null); // Pass null to indicate cancellation
|
||||
dialog.close();
|
||||
},
|
||||
style: {
|
||||
flex: "1",
|
||||
padding: "8px",
|
||||
backgroundColor: "#4a4a4a",
|
||||
color: "#ffffff",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer"
|
||||
}
|
||||
}),
|
||||
$el("button", {
|
||||
textContent: "Select All",
|
||||
onclick: () => {
|
||||
dependencies.forEach(dep => {
|
||||
if (dep.status !== 'installed') {
|
||||
dep.selected = true;
|
||||
}
|
||||
// Also select subdependencies
|
||||
if(dep.subdependencies) {
|
||||
dep.subdependencies.forEach(subdep => {
|
||||
if(subdep.status !== 'installed') {
|
||||
subdep.selected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// Update checkboxes in the table
|
||||
const checkboxes = tableContainer.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach((checkbox) => {
|
||||
if(!checkbox.disabled) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
style: {
|
||||
padding: "8px 15px",
|
||||
backgroundColor: "#4a6a4a",
|
||||
color: "#ffffff",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer"
|
||||
}
|
||||
}),
|
||||
$el("button", {
|
||||
textContent: "Install Selected",
|
||||
onclick: () => {
|
||||
// Collect all selected dependencies (main + subdependencies)
|
||||
const selected = [];
|
||||
dependencies.forEach(d => {
|
||||
if(d.selected) {
|
||||
selected.push(d);
|
||||
}
|
||||
// Also include selected subdependencies
|
||||
if(d.subdependencies) {
|
||||
d.subdependencies.forEach(subdep => {
|
||||
if(subdep.selected) {
|
||||
selected.push(subdep);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
onSelect(selected);
|
||||
dialog.close();
|
||||
},
|
||||
style: {
|
||||
flex: "1",
|
||||
padding: "8px",
|
||||
backgroundColor: "#4CAF50",
|
||||
color: "#ffffff",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer"
|
||||
}
|
||||
}),
|
||||
])
|
||||
]);
|
||||
|
||||
console.log('[Dependency Dialog] Showing dialog with', dependencies.length, 'dependencies');
|
||||
dialog.show(content);
|
||||
console.log('[Dependency Dialog] Dialog shown');
|
||||
}
|
||||
|
||||
showVersionSelectorDialog(versions, onSelect) {
|
||||
const dialog = new ComfyDialog();
|
||||
dialog.element.style.zIndex = 1100;
|
||||
@ -1470,6 +1917,101 @@ export class CustomNodesManager {
|
||||
}
|
||||
}
|
||||
|
||||
// For install mode, analyze dependencies BEFORE starting installation
|
||||
let selectedDependencies = [];
|
||||
let dependencyDialogShown = false; // Track if dialog was shown
|
||||
if(mode === "install" && this.analyzeDependenciesBeforeInstall) {
|
||||
// Analyze dependencies for all items first (only if checkbox is enabled)
|
||||
for (const hash of list) {
|
||||
const item = this.grid.getRowItemBy("hash", hash);
|
||||
if (!item) {
|
||||
console.log('[Dependency Analysis] Item not found for hash:', hash);
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = item.originalData;
|
||||
console.log('[Dependency Analysis] Item data:', {
|
||||
title: item.title,
|
||||
files: data.files,
|
||||
repository: data.repository,
|
||||
hasFiles: !!data.files,
|
||||
filesLength: data.files ? data.files.length : 0
|
||||
});
|
||||
|
||||
// Try multiple ways to get the git URL
|
||||
let gitUrl = null;
|
||||
if(data.files && data.files.length > 0) {
|
||||
gitUrl = data.files[0];
|
||||
} else if(data.repository) {
|
||||
gitUrl = data.repository;
|
||||
}
|
||||
|
||||
if(gitUrl && (gitUrl.includes('github.com') || gitUrl.includes('.git'))) {
|
||||
try {
|
||||
this.showStatus(`Analyzing dependencies for ${item.title}...`);
|
||||
console.log('[Dependency Analysis] Fetching dependencies for:', gitUrl);
|
||||
|
||||
const analyzeRes = await api.fetchApi('/customnode/analyze_dependencies', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url: gitUrl,
|
||||
commitId: data.commit_id,
|
||||
branch: data.branch
|
||||
})
|
||||
});
|
||||
|
||||
console.log('[Dependency Analysis] Response status:', analyzeRes.status);
|
||||
|
||||
if(analyzeRes.status === 200) {
|
||||
const analyzeData = await analyzeRes.json();
|
||||
console.log('[Dependency Analysis] Response data:', {
|
||||
success: analyzeData.success,
|
||||
hasDependencies: !!analyzeData.dependencies,
|
||||
dependenciesCount: analyzeData.dependencies ? analyzeData.dependencies.length : 0,
|
||||
noRequirementsFile: analyzeData.noRequirementsFile
|
||||
});
|
||||
|
||||
if(analyzeData.success && analyzeData.dependencies && analyzeData.dependencies.length > 0) {
|
||||
console.log('[Dependency Analysis] Showing dialog with', analyzeData.dependencies.length, 'dependencies');
|
||||
dependencyDialogShown = true;
|
||||
|
||||
// Show dependency selection dialog and wait for user
|
||||
const userSelection = await new Promise((resolve) => {
|
||||
this.showDependencySelectorDialog(analyzeData.dependencies, (selected) => {
|
||||
console.log('[Dependency Analysis] User selected:', selected);
|
||||
resolve(selected);
|
||||
});
|
||||
});
|
||||
|
||||
// If user cancelled (null), stop installation
|
||||
if(userSelection === null) {
|
||||
console.log('[Dependency Analysis] User cancelled installation');
|
||||
this.showStatus("Installation cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
selectedDependencies = userSelection || [];
|
||||
console.log('[Dependency Analysis] Selected dependencies:', selectedDependencies.length);
|
||||
} else if(analyzeData.noRequirementsFile) {
|
||||
console.log('[Dependency Analysis] No requirements.txt file found');
|
||||
} else {
|
||||
console.log('[Dependency Analysis] No dependencies to show');
|
||||
}
|
||||
} else {
|
||||
const errorText = await analyzeRes.text();
|
||||
console.error('[Dependency Analysis] API error:', analyzeRes.status, errorText);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('[Dependency Analysis] Exception:', e);
|
||||
// Continue with installation even if dependency analysis fails
|
||||
}
|
||||
} else {
|
||||
console.log('[Dependency Analysis] Not a GitHub URL or no URL found:', gitUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target.classList.add("cn-btn-loading");
|
||||
this.showError("");
|
||||
|
||||
@ -1511,6 +2053,46 @@ export class CustomNodesManager {
|
||||
data.mode = this.mode;
|
||||
data.ui_id = hash;
|
||||
|
||||
// Add selected dependencies to data (including subdependencies)
|
||||
// Only install selected dependencies - respect user's selection
|
||||
const allSelected = [];
|
||||
if(selectedDependencies.length > 0) {
|
||||
selectedDependencies.forEach(d => {
|
||||
// Add main dependency if selected
|
||||
if(d.selected) {
|
||||
allSelected.push({
|
||||
name: d.name,
|
||||
version: d.version,
|
||||
line: d.line
|
||||
});
|
||||
}
|
||||
// Add selected subdependencies
|
||||
if(d.subdependencies) {
|
||||
d.subdependencies.forEach(subdep => {
|
||||
if(subdep.selected) {
|
||||
allSelected.push({
|
||||
name: subdep.name,
|
||||
version: subdep.version,
|
||||
line: `${subdep.name}${subdep.version ? '==' + subdep.version : ''}`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set selectedDependencies:
|
||||
// - If dialog was shown: always set (even if empty) to respect user's selection
|
||||
// - If dialog was not shown: don't set (install all dependencies - original behavior)
|
||||
if(dependencyDialogShown) {
|
||||
// User saw the dialog, respect their selection (even if empty)
|
||||
data.selectedDependencies = allSelected;
|
||||
} else if(allSelected.length > 0) {
|
||||
// Dialog wasn't shown but we have selections (shouldn't happen, but just in case)
|
||||
data.selectedDependencies = allSelected;
|
||||
}
|
||||
// If dialog wasn't shown and no selections, don't set selectedDependencies
|
||||
// This means backend will install all dependencies (original behavior)
|
||||
|
||||
let install_mode = mode;
|
||||
if(mode == 'switch') {
|
||||
install_mode = 'install';
|
||||
|
||||
@ -1874,9 +1874,14 @@
|
||||
"DonutSave",
|
||||
"DonutTiledUpscale",
|
||||
"DonutUniversalDetailer",
|
||||
"DonutWeightVectorScale",
|
||||
"DonutWidenMergeCLIP",
|
||||
"DonutWidenMergeUNet",
|
||||
"DonutZitConditioningRebalance",
|
||||
"DonutZitHybridApply",
|
||||
"DonutZitHybridLoader",
|
||||
"DonutZitHybridOverride",
|
||||
"DonutZitHybridSave",
|
||||
"DonutZitLayerBlendEncode",
|
||||
"LoadLBW //Inspire",
|
||||
"LoraBlockInfo //Inspire",
|
||||
@ -3484,6 +3489,7 @@
|
||||
"MpiReroute",
|
||||
"MpiRoundToMultiple",
|
||||
"MpiRoundToMultipleRes",
|
||||
"MpiSaveVideo",
|
||||
"MpiScaledDimensions",
|
||||
"MpiSimpleBoolean",
|
||||
"MpiString",
|
||||
@ -7217,7 +7223,10 @@
|
||||
"WanMoveSVI_FLF_Mask",
|
||||
"WanMoveSVI_FLF_Stitch",
|
||||
"WanMoveSVI_FLF_Visualize",
|
||||
"WanMoveSVI_FLF_v2"
|
||||
"WanMoveSVI_FLF_v2",
|
||||
"Wan_Float",
|
||||
"Wan_Integer",
|
||||
"Wan_Length"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-WanMoveSVI_FLF_v2"
|
||||
@ -8889,6 +8898,7 @@
|
||||
"ETNodes-Color-Selector",
|
||||
"ETNodes-Gemini-API-Image",
|
||||
"ETNodes-Gemini-API-Text",
|
||||
"ETNodes-Gemini-API-Video",
|
||||
"ETNodes-List-Items",
|
||||
"ETNodes-List-Selector",
|
||||
"ETNodes-Text-Preview"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,125 @@
|
||||
{
|
||||
"custom_nodes": [
|
||||
{
|
||||
"author": "younestft",
|
||||
"title": "Comfy EverAnimate",
|
||||
"reference": "https://github.com/younestft/Comfy_EverAnimate",
|
||||
"files": [
|
||||
"https://github.com/younestft/Comfy_EverAnimate"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Native ComfyUI nodes for EverAnimate-style chunked WanAnimate workflows, including master/chunk settings, chunk calculation, image carry-over, and boundary color correction."
|
||||
},
|
||||
{
|
||||
"author": "Moon-NE",
|
||||
"title": "MoonNe_Nodes",
|
||||
"reference": "https://github.com/Moon-NE/MoonNe_Nodes",
|
||||
"files": [
|
||||
"https://github.com/Moon-NE/MoonNe_Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom nodes for text operations, including concatenation, horizontal-to-vertical conversion, and batch processing."
|
||||
},
|
||||
{
|
||||
"author": "TechnoWarrior2",
|
||||
"title": "ComfyUI Image Picker",
|
||||
"reference": "https://github.com/TechnoWarrior2/comfyui-image-picker",
|
||||
"files": [
|
||||
"https://github.com/TechnoWarrior2/comfyui-image-picker"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Pauses your workflow and opens a popup window to manually pick which generated images pass through to the rest of the graph."
|
||||
},
|
||||
{
|
||||
"author": "TechnoWarrior2",
|
||||
"title": "ComfyUI Live Preview",
|
||||
"reference": "https://github.com/TechnoWarrior2/comfyui-live-preview",
|
||||
"files": [
|
||||
"https://github.com/TechnoWarrior2/comfyui-live-preview"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Displays a large draggable floating window with live denoising step previews as your image generates, similar to A1111 and Forge."
|
||||
},
|
||||
{
|
||||
"author": "shootthesound",
|
||||
"title": "Lighthouse",
|
||||
"reference": "https://github.com/shootthesound/comfyui-lighthouse",
|
||||
"files": [
|
||||
"https://github.com/shootthesound/comfyui-lighthouse"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A non-destructive overlay that lights up every node by graph distance from the one you click. Diagnostic for big workflows, educational for understanding workflows others built, and surgical when you need to focus on one neighbourhood at a time."
|
||||
},
|
||||
{
|
||||
"author": "shootthesound",
|
||||
"title": "CleanFreak",
|
||||
"reference": "https://github.com/shootthesound/comfyui-CleanFreak",
|
||||
"files": [
|
||||
"https://github.com/shootthesound/comfyui-CleanFreak"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "One-click tidy-by-role for any ComfyUI workflow — loaders, encoders, samplers, decoders, and outputs each sorted into their own column with coloured group cards and connections preserved. 1200+ node classes pre-classified out of the box."
|
||||
},
|
||||
{
|
||||
"author": "shootthesound",
|
||||
"title": "Finding LoRA",
|
||||
"reference": "https://github.com/shootthesound/comfyui-lora-FindingLora",
|
||||
"files": [
|
||||
"https://github.com/shootthesound/comfyui-lora-FindingLora"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A LoRA loader with bookmarks, trigger-word storage and click-to-copy, fuzzy search, and a one-click button to drop another loader inline and wire it into the model chain automatically."
|
||||
},
|
||||
{
|
||||
"author": "shootthesound",
|
||||
"title": "Image of the Day",
|
||||
"reference": "https://github.com/shootthesound/comfyui-image-of-the-day",
|
||||
"files": [
|
||||
"https://github.com/shootthesound/comfyui-image-of-the-day"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Fetch a fresh image straight into your workflow from 8 daily/random sources. Useful for prompt testing, style references, daily-image automations, or just kicking off a workflow with something interesting."
|
||||
},
|
||||
{
|
||||
"author": "boobkake22",
|
||||
"title": "Sampling Planner",
|
||||
"reference": "https://github.com/boobkake22/ComfyUI-SamplingPlanner",
|
||||
"files": [
|
||||
"https://github.com/boobkake22/ComfyUI-SamplingPlanner"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Task-aware Wan 2.2 sampling plans and sampler-specific breakout nodes for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "iljung1106",
|
||||
"title": "Anima Safe PAG",
|
||||
"reference": "https://github.com/iljung1106/comfyui-anima-safe-pag",
|
||||
"files": [
|
||||
"https://github.com/iljung1106/comfyui-anima-safe-pag"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A soft, Anima-friendly Perturbed Attention Guidance node for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "burgstall-labs",
|
||||
"title": "ComfyUI-Domemaster-Outpaint",
|
||||
"reference": "https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint",
|
||||
"files": [
|
||||
"https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Outpaint flat video into fulldome / planetarium shows: domemaster rendering with tilted-venue presets for ComfyUI, on top of the Burgstall Labs 360 outpaint pipeline."
|
||||
},
|
||||
{
|
||||
"author": "depth-layer-rasterizer",
|
||||
"title": "Depth Layer Rasterizer",
|
||||
"reference": "https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer",
|
||||
"files": [
|
||||
"https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Depth-map paper-cut and parallax layer rasterizer for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "artokun",
|
||||
"title": "ComfyUI Agent Panel",
|
||||
|
||||
@ -1217,6 +1217,15 @@
|
||||
"title_aux": "Pixel3DMM ComfyUI Nodes"
|
||||
}
|
||||
],
|
||||
"https://github.com/A1-multiply/ComfyUI-LatentSaver": [
|
||||
[
|
||||
"A1LoadLatent",
|
||||
"A1SaveLatent"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI LatentSaver"
|
||||
}
|
||||
],
|
||||
"https://github.com/A4P7J1N7M05OT/ComfyUI-InlineVariableWildcards": [
|
||||
[
|
||||
"InlineWildcardGenerator",
|
||||
@ -3867,6 +3876,7 @@
|
||||
"Beaufort",
|
||||
"Bifid",
|
||||
"Binary2String",
|
||||
"BitsCounter",
|
||||
"BitwiseAND",
|
||||
"BitwiseLS",
|
||||
"BitwiseNOT",
|
||||
@ -3888,6 +3898,11 @@
|
||||
"ConcatKDFHash_Derive",
|
||||
"ConcatKDFHash_Verify",
|
||||
"ConstantTimeCompare",
|
||||
"DERPrivateKey",
|
||||
"DERPublicKey",
|
||||
"ECPrivateKey",
|
||||
"ECPublicKey",
|
||||
"ECSign",
|
||||
"EdDSAPrivateKeyFormat",
|
||||
"EdDSAPublicKeyFormat",
|
||||
"EdDSASignature",
|
||||
@ -3912,6 +3927,8 @@
|
||||
"Nihilist",
|
||||
"PBKDF2HMAC_Derive",
|
||||
"PBKDF2HMAC_Verify",
|
||||
"PEMPrivateKey",
|
||||
"PEMPublicKey",
|
||||
"Padding",
|
||||
"Playfair",
|
||||
"Polybius",
|
||||
@ -3947,6 +3964,7 @@
|
||||
"X963KDF_Derive",
|
||||
"X963KDF_Verify",
|
||||
"XExchange",
|
||||
"XOFHash",
|
||||
"XPrivateKeyFormat",
|
||||
"XPublicKeyFormat",
|
||||
"Zigzag"
|
||||
@ -5057,6 +5075,14 @@
|
||||
"title_aux": "ComfyUI-BS_Kokoro-onnx"
|
||||
}
|
||||
],
|
||||
"https://github.com/Burgstall-labs/ComfyUI-Domemaster-Outpaint": [
|
||||
[
|
||||
"EquirectToDomemaster"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-Domemaster-Outpaint"
|
||||
}
|
||||
],
|
||||
"https://github.com/Burve/ComfyUI_Burve_Tools": [
|
||||
[
|
||||
"BurveGoogleImageGen",
|
||||
@ -6307,6 +6333,15 @@
|
||||
"title_aux": "ComfyUI-CRZnodes"
|
||||
}
|
||||
],
|
||||
"https://github.com/CoreyCorza/comfyui-lora-loader": [
|
||||
[
|
||||
"CorzaCleanAppliedLoRAs",
|
||||
"CorzaLoRAClean"
|
||||
],
|
||||
{
|
||||
"title_aux": "Corza LoRA Loader (Clean)"
|
||||
}
|
||||
],
|
||||
"https://github.com/Cornmeister/ComfyUI-CornmeisterNL-PowerPack": [
|
||||
[
|
||||
"CornmeisterNL_PowerDiffusionModelLoader",
|
||||
@ -6905,6 +6940,7 @@
|
||||
],
|
||||
"https://github.com/DailyMok/ComfyUI-dAIly": [
|
||||
[
|
||||
"PromptMixerV2dAIly",
|
||||
"PromptMixerdAIly",
|
||||
"TokenCounterdAIly"
|
||||
],
|
||||
@ -17399,26 +17435,24 @@
|
||||
"KSampler Advanced (Dual Output)",
|
||||
"Load Folder",
|
||||
"Load VACE Module",
|
||||
"Loop SCAIL Pose Frames",
|
||||
"Match Color Palette",
|
||||
"Native Wan Pose Strength",
|
||||
"Pixelation Dimensions",
|
||||
"Preview (webm)",
|
||||
"PreviewAsMarkdown",
|
||||
"PreviewImageAlpha",
|
||||
"Remove Background",
|
||||
"ReplaceAlpha",
|
||||
"Resize Image and Mask by Side",
|
||||
"SCAIL Pose Frames",
|
||||
"Save Folder as ZIP",
|
||||
"Save To Zip",
|
||||
"Shift Image Batch",
|
||||
"Snap to Divisible",
|
||||
"Sprite Scale Calculator",
|
||||
"Spritesheet Builder",
|
||||
"Spritesheet Preview",
|
||||
"String to List",
|
||||
"Temporal Mask Cropper",
|
||||
"Trim Conditioning",
|
||||
"VACE Sampler",
|
||||
"WAN Frame Calculator",
|
||||
"WAN Frames to Add & Cut"
|
||||
],
|
||||
@ -25218,6 +25252,17 @@
|
||||
"TTP_Expand_And_Mask",
|
||||
"TTP_Image_Assy",
|
||||
"TTP_Image_Tile_Batch",
|
||||
"TTP_QwenVL3_Local_Loader_Experimental",
|
||||
"TTP_Smart_Tile_Assemble_Experimental",
|
||||
"TTP_Smart_Tile_Image_Upscale_Prep_Experimental",
|
||||
"TTP_Smart_Tile_Interactive_Crop_Experimental",
|
||||
"TTP_Smart_Tile_Loop_Collect_Experimental",
|
||||
"TTP_Smart_Tile_Loop_Source_Experimental",
|
||||
"TTP_Smart_Tile_Output_Size_Estimate_Experimental",
|
||||
"TTP_Smart_Tile_QwenVL_Prompt_Set_Builder_Experimental",
|
||||
"TTP_Smart_Tile_Save_Final_Image_Experimental",
|
||||
"TTP_Smart_Tile_Semantic_Rank_Experimental",
|
||||
"TTP_Smart_Tile_Set_Preview_Experimental",
|
||||
"TTP_Tile_image_size",
|
||||
"TTP_condsetarea_merge",
|
||||
"TTP_condsetarea_merge_test",
|
||||
@ -26780,6 +26825,7 @@
|
||||
"XB_AutoBubbleTextRenderer",
|
||||
"XB_BatchFolderLoader",
|
||||
"XB_BerniniConditioning",
|
||||
"XB_BerniniPromptRouter",
|
||||
"XB_CLIPNameBroadcaster",
|
||||
"XB_CanvasLabel",
|
||||
"XB_CheckpointBlockSwap",
|
||||
@ -26791,6 +26837,7 @@
|
||||
"XB_HumanSegModelLoader",
|
||||
"XB_HumanSegmentation",
|
||||
"XB_ImageParamsMaster",
|
||||
"XB_K2StyleRouter",
|
||||
"XB_KSampler",
|
||||
"XB_KSamplerAdvanced",
|
||||
"XB_MSR",
|
||||
@ -30488,6 +30535,8 @@
|
||||
"BFSHeadSwapLoRADebug",
|
||||
"FaceSequenceBatch",
|
||||
"FrameRangedFaceLoader",
|
||||
"LTXIdentityCAN",
|
||||
"LTXIdentityGemmaVision",
|
||||
"LTXIdentityOverlapConditioning",
|
||||
"LTXIdentityTransfer",
|
||||
"LTXVApplyNeutralMask",
|
||||
@ -33599,6 +33648,29 @@
|
||||
"title_aux": "ComfyUI-ColorCorrectGPU"
|
||||
}
|
||||
],
|
||||
"https://github.com/boobkake22/ComfyUI-SamplingPlanner": [
|
||||
[
|
||||
"AccelerationModelPair",
|
||||
"AccelerationSelector",
|
||||
"KSamplerBreakout",
|
||||
"ModelPairBreakout",
|
||||
"MoeSamplerBreakout",
|
||||
"PrioritySelector",
|
||||
"ProgressiveSigmaControl",
|
||||
"RangeSplitOverrideLegacy",
|
||||
"SamplerSelector",
|
||||
"SamplingPlanWan22",
|
||||
"SchedulerSelector",
|
||||
"ShiftOverride",
|
||||
"SigmaBreakout",
|
||||
"StepBudget",
|
||||
"StepSplitOverride",
|
||||
"TaskSelector"
|
||||
],
|
||||
{
|
||||
"title_aux": "Sampling Planner"
|
||||
}
|
||||
],
|
||||
"https://github.com/boobkake22/ComfyUI-SimpleSwitch": [
|
||||
[
|
||||
"SimpleAudioLatentSwitch",
|
||||
@ -39580,6 +39652,18 @@
|
||||
"title_aux": "demofusion-comfyui"
|
||||
}
|
||||
],
|
||||
"https://github.com/designloves2/ComfyUI-TJ_NODE_STUDIO_ONE": [
|
||||
[
|
||||
"Flux2KleinOneTJNode",
|
||||
"Krea2OneTJNode",
|
||||
"QwenImageEdit2511OneTJNode",
|
||||
"SDXLOneTJNode",
|
||||
"ZImageTurboOneNode"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-TJ_NODE_STUDIO_ONE"
|
||||
}
|
||||
],
|
||||
"https://github.com/destinyfaux/Z-Fuse": [
|
||||
[
|
||||
"ZFuseBake",
|
||||
@ -43141,10 +43225,12 @@
|
||||
"LTXAttentionTimestepEvolution",
|
||||
"LTXAttentionZoneAnalysis",
|
||||
"LTXLatentDims",
|
||||
"LTXQKVMultiplier",
|
||||
"LTXQKVStoreInspect",
|
||||
"LTXQKVTransfer",
|
||||
"LTXRFForwardSampler",
|
||||
"LTXRFReverseSampler",
|
||||
"LTXResetPatches",
|
||||
"LTXStoreDump",
|
||||
"LTXStoreLoad"
|
||||
],
|
||||
@ -46545,6 +46631,14 @@
|
||||
"title_aux": "ComfyUI-iterationNode"
|
||||
}
|
||||
],
|
||||
"https://github.com/iljung1106/comfyui-anima-safe-pag": [
|
||||
[
|
||||
"AnimaSafePAG"
|
||||
],
|
||||
{
|
||||
"title_aux": "Anima Safe PAG"
|
||||
}
|
||||
],
|
||||
"https://github.com/illuminatianon/comfyui-csvwildcards": [
|
||||
[
|
||||
"CSVWildcardNode",
|
||||
@ -54478,6 +54572,14 @@
|
||||
"title_aux": "h4 ToolKit"
|
||||
}
|
||||
],
|
||||
"https://github.com/maDcaDDie2000/comfyui_depth_layer_rasterizer": [
|
||||
[
|
||||
"DepthRasterizeLayers"
|
||||
],
|
||||
{
|
||||
"title_aux": "Depth Layer Rasterizer"
|
||||
}
|
||||
],
|
||||
"https://github.com/maartenharms/comfyui-flowpath": [
|
||||
[
|
||||
"FlowPath"
|
||||
@ -59986,6 +60088,7 @@
|
||||
[
|
||||
"PJ_Image_Handler",
|
||||
"PJ_Latent_Generator",
|
||||
"PJ_Lora_Loader",
|
||||
"PJ_Text_Translator",
|
||||
"PJ_Video_Latent_Generator"
|
||||
],
|
||||
@ -67885,6 +67988,15 @@
|
||||
"title_aux": "ComfyUI-API-DockerCPU"
|
||||
}
|
||||
],
|
||||
"https://github.com/trx7111/ComfyUI-TrixNodes": [
|
||||
[
|
||||
"TrixBypasser",
|
||||
"TrixBypasserSimple"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-TrixNodes"
|
||||
}
|
||||
],
|
||||
"https://github.com/tsogzark/ComfyUI-load-image-from-url": [
|
||||
[
|
||||
"LoadImageFromUrlOrPath"
|
||||
@ -69744,7 +69856,10 @@
|
||||
],
|
||||
"https://github.com/wearekhepri/comfyui-kepri-nodes-pack": [
|
||||
[
|
||||
"KepriAspectRatio",
|
||||
"KepriFramingAnalysis",
|
||||
"KepriImageFinalize",
|
||||
"KepriMaskAnalysis",
|
||||
"KepriMaskMerge"
|
||||
],
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user