mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-01-31 00:10:16 +08:00
Compare commits
3 Commits
120cb3d0c3
...
a2d9d07ab4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2d9d07ab4 | ||
|
|
74d1e9d296 | ||
|
|
af1c698117 |
@ -24136,6 +24136,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI-TinyBreaker is a collection of custom nodes specifically designed to generate images using the TinyBreaker model. It's actively developed with ongoing improvements. Although still in progress, these nodes are functional and allow you to explore the potential of the model."
|
||||
},
|
||||
{
|
||||
"author": "martin-rizzo",
|
||||
"title": "ComfyUI-ZImagePowerNodes",
|
||||
"reference": "https://github.com/martin-rizzo/ComfyUI-ZImagePowerNodes",
|
||||
"files": [
|
||||
"https://github.com/martin-rizzo/ComfyUI-ZImagePowerNodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A set of ComfyUI nodes designed and fine-tuned specifically for the Z-Image model. Pushing the best image generation model to its limits!"
|
||||
},
|
||||
{
|
||||
"author": "Arkanun",
|
||||
"title": "ReadCSV_ComfyUI",
|
||||
@ -40636,6 +40646,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A visual dashboard for managing, versioning, and batch-processing JSON configuration files for AI video generation workflows with Streamlit web interface and ComfyUI custom nodes."
|
||||
},
|
||||
{
|
||||
"author": "ethanfel",
|
||||
"title": "ComfyUI-Sharp-Selector",
|
||||
"reference": "https://github.com/ethanfel/ComfyUI-Sharp-Selector",
|
||||
"files": [
|
||||
"https://github.com/ethanfel/ComfyUI-Sharp-Selector"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom nodes for sharp frame selection and analysis. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "shin131002",
|
||||
"title": "RandomLoRALoader",
|
||||
@ -41338,6 +41358,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Provide a simple interface to simplify prompt writing."
|
||||
},
|
||||
{
|
||||
"author": "0nikod",
|
||||
"title": "ComfyUI-Metadata-Tools",
|
||||
"reference": "https://github.com/0nikod/ComfyUI-Metadata-Tools",
|
||||
"files": [
|
||||
"https://github.com/0nikod/ComfyUI-Metadata-Tools"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Tools about metadata."
|
||||
},
|
||||
{
|
||||
"author": "Suzu008",
|
||||
"title": "ComfyUI-CryptIO",
|
||||
@ -41670,7 +41700,46 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Comprehensive single-node solution for ComfyUI integrating base generation, latent refinement, neural upscaling, and post-processing."
|
||||
},
|
||||
|
||||
{
|
||||
"author": "ato-zen",
|
||||
"title": "ComfyUI-VIBE",
|
||||
"reference": "https://github.com/ato-zen/ComfyUI-VIBE",
|
||||
"files": [
|
||||
"https://github.com/ato-zen/ComfyUI-VIBE"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Implementation of VIBE (Visual Instruction Based Editor) as a custom node for ComfyUI that enables image editing using natural language instructions leveraging Sana1.5 and Qwen3-VL models."
|
||||
},
|
||||
{
|
||||
"author": "FallenIncursio",
|
||||
"title": "arcenciel-link-comfyui",
|
||||
"reference": "https://github.com/FallenIncursio/arcenciel-link-comfyui",
|
||||
"files": [
|
||||
"https://github.com/FallenIncursio/arcenciel-link-comfyui"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Bring your ArcEnCiel models straight into ComfyUI with one click. Includes Link Key support, remote worker control, inventory sync, and sidecar generation."
|
||||
},
|
||||
{
|
||||
"author": "0dot77",
|
||||
"title": "comfyui-annotations",
|
||||
"reference": "https://github.com/0dot77/comfyui-annotations",
|
||||
"files": [
|
||||
"https://github.com/0dot77/comfyui-annotations"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A lightweight annotation overlay for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "isala404",
|
||||
"title": "comfy-workflow-api",
|
||||
"reference": "https://github.com/isala404/comfy-workflow-api",
|
||||
"files": [
|
||||
"https://github.com/isala404/comfy-workflow-api"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "HTTP API for ComfyUI with webhook-based workflow execution."
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
@ -18,6 +18,17 @@
|
||||
"title_aux": "mmaker/Color Enhance"
|
||||
}
|
||||
],
|
||||
"https://github.com/0nikod/ComfyUI-Metadata-Tools": [
|
||||
[
|
||||
"ImageGetMetadata",
|
||||
"ImageSetMetadata",
|
||||
"LoadImageWithMetadata",
|
||||
"SaveImageWithMetadata"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-Metadata-Tools"
|
||||
}
|
||||
],
|
||||
"https://github.com/0nikod/ComfyUI-Simple-Prompt": [
|
||||
[
|
||||
"SimplePrompt"
|
||||
@ -2149,6 +2160,7 @@
|
||||
"INPAINT_MaskedBlur",
|
||||
"INPAINT_MaskedFill",
|
||||
"INPAINT_ShrinkMask",
|
||||
"INPAINT_StabilizeMask",
|
||||
"INPAINT_VAEEncodeInpaintConditioning"
|
||||
],
|
||||
{
|
||||
@ -6007,6 +6019,7 @@
|
||||
"DiffusionModelSelectorNode",
|
||||
"LORASelectorNode",
|
||||
"ModelGeneratorNode",
|
||||
"PonyPrefixesNode",
|
||||
"SamplerGeneratorNode",
|
||||
"SchedulerGeneratorNode",
|
||||
"StringToFloatNode",
|
||||
@ -22579,6 +22592,7 @@
|
||||
"Qwen3VLAdvanced",
|
||||
"Qwen3VLBasic",
|
||||
"Qwen3VLExtraOptions",
|
||||
"ResourceCleaner",
|
||||
"Sa2VAAdvanced",
|
||||
"Sa2VASegmentationPreset",
|
||||
"ShowText",
|
||||
@ -25024,6 +25038,14 @@
|
||||
"title_aux": "ComfyUI NPNet (Golden Noise)"
|
||||
}
|
||||
],
|
||||
"https://github.com/asagi4/comfyui-dynamic-anynode": [
|
||||
[
|
||||
"AnyNode"
|
||||
],
|
||||
{
|
||||
"title_aux": "comfyui-dynamic-anynode"
|
||||
}
|
||||
],
|
||||
"https://github.com/asagi4/comfyui-prompt-control": [
|
||||
[
|
||||
"PCAddMaskToCLIP",
|
||||
@ -25143,6 +25165,14 @@
|
||||
"title_aux": "comfyui_arcane_style_trans"
|
||||
}
|
||||
],
|
||||
"https://github.com/ato-zen/ComfyUI-VIBE": [
|
||||
[
|
||||
"VIBE_Editor"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-VIBE"
|
||||
}
|
||||
],
|
||||
"https://github.com/ato321/ComfyUI-LTXVGuideRebase": [
|
||||
[
|
||||
"LTXVRebaseGuides"
|
||||
@ -29792,11 +29822,13 @@
|
||||
"ExtendIntermediateSigmas",
|
||||
"FeatherMask",
|
||||
"FlipSigmas",
|
||||
"Flux2ProImageNode",
|
||||
"Flux2Scheduler",
|
||||
"FluxDisableGuidance",
|
||||
"FluxGuidance",
|
||||
"FluxKontextImageScale",
|
||||
"FluxKontextMultiReferenceLatentMethod",
|
||||
"FluxKontextProImageNode",
|
||||
"FluxProExpandNode",
|
||||
"FluxProFillNode",
|
||||
"FluxProUltraImageNode",
|
||||
@ -29840,6 +29872,7 @@
|
||||
"ImageOnlyCheckpointLoader",
|
||||
"ImageOnlyCheckpointSave",
|
||||
"ImagePadForOutpaint",
|
||||
"ImageProcessingNode",
|
||||
"ImageQuantize",
|
||||
"ImageRGBToYUV",
|
||||
"ImageRotate",
|
||||
@ -30201,6 +30234,7 @@
|
||||
"TextEncodeHunyuanVideo_ImageToVideo",
|
||||
"TextEncodeQwenImageEdit",
|
||||
"TextEncodeQwenImageEditPlus",
|
||||
"TextProcessingNode",
|
||||
"ThresholdMask",
|
||||
"TomePatchModel",
|
||||
"TopazImageEnhance",
|
||||
@ -32779,6 +32813,15 @@
|
||||
"title_aux": "comfyui-videoframenode"
|
||||
}
|
||||
],
|
||||
"https://github.com/ethanfel/ComfyUI-Sharp-Selector": [
|
||||
[
|
||||
"SharpFrameSelector",
|
||||
"SharpnessAnalyzer"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-Sharp-Selector"
|
||||
}
|
||||
],
|
||||
"https://github.com/ethanfel/Comfyui-JSON-Manager": [
|
||||
[
|
||||
"JSONLoaderBatchI2V",
|
||||
@ -35414,6 +35457,8 @@
|
||||
"AIIA_CosyVoice_VoiceConversion",
|
||||
"AIIA_Dialogue_TTS",
|
||||
"AIIA_E2E_Speaker_Diarization",
|
||||
"AIIA_EchoMimicLoader",
|
||||
"AIIA_EchoMimicSampler",
|
||||
"AIIA_FloatProcess_InMemory",
|
||||
"AIIA_FloatProcess_ToDisk",
|
||||
"AIIA_GenerateSpeakerSegments",
|
||||
@ -37110,6 +37155,16 @@
|
||||
"title_aux": "ComfyUI-DSD"
|
||||
}
|
||||
],
|
||||
"https://github.com/isala404/comfy-workflow-api": [
|
||||
[
|
||||
"WebhookReceiver",
|
||||
"WebhookSend",
|
||||
"WebhookTransformer"
|
||||
],
|
||||
{
|
||||
"title_aux": "comfy-workflow-api"
|
||||
}
|
||||
],
|
||||
"https://github.com/iwanders/ComfyUI_nodes": [
|
||||
[
|
||||
"IW_JsonPickItem",
|
||||
@ -40706,14 +40761,27 @@
|
||||
],
|
||||
"https://github.com/latentastronaut/comfyui-latent-astronaut-suite": [
|
||||
[
|
||||
"BatchLastImage",
|
||||
"ChatterBoxTTS",
|
||||
"ChatterBoxTTSLoader",
|
||||
"ChatterBoxTTSLoaderAuto",
|
||||
"ChatterBoxTTSSimple",
|
||||
"ChatterBoxVC",
|
||||
"ChatterBoxVCLoader",
|
||||
"ChatterBoxVCLoaderAuto",
|
||||
"ChatterBoxVCSimple",
|
||||
"ForLoopEnd",
|
||||
"ForLoopStart",
|
||||
"ImageResizeToTotalPixels",
|
||||
"LLMConfig",
|
||||
"LLMPromptEnhancer",
|
||||
"LoraLoaderModelOnlySelector",
|
||||
"LoraLoaderSelector",
|
||||
"SizeSelector",
|
||||
"StringListCombine",
|
||||
"StringListFromText",
|
||||
"StringListIndex"
|
||||
"StringListIndex",
|
||||
"VideoLengthFromBatch"
|
||||
],
|
||||
{
|
||||
"title_aux": "comfyui-latent-astronaut-suite"
|
||||
@ -43540,6 +43608,18 @@
|
||||
"title_aux": "comfyui-previewlatent"
|
||||
}
|
||||
],
|
||||
"https://github.com/martin-rizzo/ComfyUI-ZImagePowerNodes": [
|
||||
[
|
||||
"EmptyZImageLatentImage",
|
||||
"IllustrationStylePromptEncoder",
|
||||
"PhotoStylePromptEncoder",
|
||||
"SaveImage",
|
||||
"ZSamplerTurbo"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-ZImagePowerNodes"
|
||||
}
|
||||
],
|
||||
"https://github.com/massao000/ComfyUI_aspect_ratios": [
|
||||
[
|
||||
"Aspect Ratios Node"
|
||||
@ -53304,8 +53384,8 @@
|
||||
],
|
||||
"https://github.com/transcendedhacker/Mode_personal_node": [
|
||||
[
|
||||
"NegativeNode",
|
||||
"PromptNode"
|
||||
"NegativePromptNode",
|
||||
"PromptComposerNode"
|
||||
],
|
||||
{
|
||||
"title_aux": "Mode_personal_node"
|
||||
@ -53629,7 +53709,9 @@
|
||||
"LatentSmokeSimulation",
|
||||
"LatentSwirlNoise",
|
||||
"LatentWorleyNoise",
|
||||
"SplitLatentPhaseMagnitude"
|
||||
"PatchifyFlux2Latent",
|
||||
"SplitLatentPhaseMagnitude",
|
||||
"UnpatchifyFlux2Latent"
|
||||
],
|
||||
{
|
||||
"title_aux": "Skoogeer-Noise"
|
||||
@ -57251,6 +57333,7 @@
|
||||
],
|
||||
"https://github.com/zhangp365/ComfyUI-utils-nodes": [
|
||||
[
|
||||
"AspectRatioSizeNodeOfUtils",
|
||||
"BooleanControlOutput",
|
||||
"CheckpointLoaderSimpleWithSwitch",
|
||||
"ColorCorrectOfUtils",
|
||||
|
||||
3975
github-stats.json
3975
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
|
||||
@ -1968,7 +1979,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")
|
||||
@ -1980,6 +1991,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
|
||||
@ -1990,6 +2008,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('#'):
|
||||
|
||||
@ -443,7 +443,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)
|
||||
@ -452,7 +457,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']:
|
||||
@ -1303,7 +1308,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)
|
||||
@ -1383,6 +1390,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("");
|
||||
|
||||
@ -1505,6 +2047,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';
|
||||
|
||||
@ -1,5 +1,85 @@
|
||||
{
|
||||
"custom_nodes": [
|
||||
{
|
||||
"author": "ah-kun",
|
||||
"title": "ComfyUI-OTP-Auth",
|
||||
"reference": "https://github.com/ah-kun/ComfyUI-OTP-Auth",
|
||||
"files": [
|
||||
"https://github.com/ah-kun/ComfyUI-OTP-Auth"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for ComfyUI that adds simple One-Time Password (OTP) authentication using Google Authenticator to prevent unauthorized use on publicly accessible servers."
|
||||
},
|
||||
{
|
||||
"author": "pollockjj",
|
||||
"title": "ComfyUI-StabilityTest",
|
||||
"reference": "https://github.com/pollockjj/ComfyUI-StabilityTest",
|
||||
"files": [
|
||||
"https://github.com/pollockjj/ComfyUI-StabilityTest"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI stability testing node. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "Tr1dae",
|
||||
"title": "[WIP] ComfyUI-MobileSAM",
|
||||
"reference": "https://github.com/Tr1dae/ComfyUI-MobileSAM",
|
||||
"files": [
|
||||
"https://github.com/Tr1dae/ComfyUI-MobileSAM"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A ComfyUI custom node for text-guided image segmentation using GroundingDINO and MobileSAM to segment objects in images using natural language prompts.\nNOTE: The files in the repo are not organized."
|
||||
},
|
||||
{
|
||||
"author": "Vov1ch",
|
||||
"title": "ComfyUI_GLMImage",
|
||||
"reference": "https://github.com/Vov1ch/ComfyUI_GLMImage",
|
||||
"files": [
|
||||
"https://github.com/Vov1ch/ComfyUI_GLMImage"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI nodes for GLM image generation, image-to-image translation, and flexible input handling. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "DailyMok",
|
||||
"title": "ComfyUI-PromptMixerNode",
|
||||
"reference": "https://github.com/DailyMok/ComfyUI-PromptMixerNode",
|
||||
"files": [
|
||||
"https://github.com/DailyMok/ComfyUI-PromptMixerNode"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom node for prompt mixing with PromptMixerDaily node. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "shin131002",
|
||||
"title": "[WIP] ComfyUI-Prompt-Preset-Selector",
|
||||
"reference": "https://github.com/shin131002/ComfyUI-Prompt-Preset-Selector",
|
||||
"files": [
|
||||
"https://github.com/shin131002/ComfyUI-Prompt-Preset-Selector"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Flexible preset selector with YAML support, advanced keyword filtering, and hierarchical key search\nNOTE: The files in the repo are not organized."
|
||||
},
|
||||
{
|
||||
"author": "fogyisland",
|
||||
"title": "Comfy_Show_StringText [WIP]",
|
||||
"reference": "https://github.com/fogyisland/Comfy_Show_StringText",
|
||||
"files": [
|
||||
"https://github.com/fogyisland/Comfy_Show_StringText"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI node for text display that implements string data output. (Description by CC)\nNOTE: The files in the repo are not organized."
|
||||
},
|
||||
{
|
||||
"author": "Leecoahs",
|
||||
"title": "ComfyUI_LeeNodes",
|
||||
"reference": "https://github.com/Leecoahs/ComfyUI_LeeNodes",
|
||||
"files": [
|
||||
"https://github.com/Leecoahs/ComfyUI_LeeNodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI nodes for image processing including grayscale combining, overlaying, texture inpainting. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "cosmicbuffalo",
|
||||
"title": "comfyui-mobile-frontend",
|
||||
|
||||
@ -274,6 +274,7 @@
|
||||
"SimpleIntMathHandle",
|
||||
"SimpleJsonArrayHandle",
|
||||
"SimpleJsonObjectHandle",
|
||||
"TestExtendComfyNode",
|
||||
"VideoFrameSize",
|
||||
"VideoSizeAndFps",
|
||||
"VideoTimeAndFPS",
|
||||
@ -1523,6 +1524,14 @@
|
||||
"title_aux": "ComfyUI-VideoStream"
|
||||
}
|
||||
],
|
||||
"https://github.com/DailyMok/ComfyUI-PromptMixerNode": [
|
||||
[
|
||||
"PromptMixerDaily"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-PromptMixerNode"
|
||||
}
|
||||
],
|
||||
"https://github.com/DataCTE/ComfyUI-DataVoid-nodes": [
|
||||
[
|
||||
"IPAAdapterFaceIDBatch",
|
||||
@ -2817,6 +2826,17 @@
|
||||
"title_aux": "ComfyUI-ModelUnloader"
|
||||
}
|
||||
],
|
||||
"https://github.com/Leecoahs/ComfyUI_LeeNodes": [
|
||||
[
|
||||
"CombineGrayscaleMRH",
|
||||
"ImageOverlaySimple",
|
||||
"TextureInpaintingPrep",
|
||||
"TextureInpaintingRestore"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI_LeeNodes"
|
||||
}
|
||||
],
|
||||
"https://github.com/Letz-AI/ComfyUI-LetzAI": [
|
||||
[
|
||||
"LetzAI Generator"
|
||||
@ -3728,6 +3748,14 @@
|
||||
"title_aux": "DoomFLUX Nodes [WIP]"
|
||||
}
|
||||
],
|
||||
"https://github.com/PladsElsker/comfyui-krita": [
|
||||
[
|
||||
"KritaSaveImage"
|
||||
],
|
||||
{
|
||||
"title_aux": "comfyui-krita [WIP]"
|
||||
}
|
||||
],
|
||||
"https://github.com/Polygoningenieur/ComfyUI-Diffusion-SDXL-Video": [
|
||||
[
|
||||
"DiffusionSDXLFrameByFrame"
|
||||
@ -4986,17 +5014,29 @@
|
||||
"https://github.com/ThatGlennD/ComfyUI-Image-Analysis-Tools": [
|
||||
[
|
||||
"Blur Detection",
|
||||
"BlurDetection",
|
||||
"Clipping Analysis",
|
||||
"ClippingAnalysis",
|
||||
"Color Cast Detector",
|
||||
"Color Harmony Analyzer",
|
||||
"Color Temperature Estimator",
|
||||
"ColorCastDetector",
|
||||
"ColorHarmonyAnalyzer",
|
||||
"ColorTemperatureEstimator",
|
||||
"Contrast Analysis",
|
||||
"ContrastAnalysis",
|
||||
"Defocus Analysis",
|
||||
"DefocusAnalysis",
|
||||
"Edge Density Analysis",
|
||||
"EdgeDensityAnalysis",
|
||||
"Entropy Analysis",
|
||||
"EntropyAnalysis",
|
||||
"Noise Estimation",
|
||||
"NoiseEstimation",
|
||||
"RGB Histogram Renderer",
|
||||
"Sharpness / Focus Score"
|
||||
"RGBHistogramRenderer",
|
||||
"Sharpness / Focus Score",
|
||||
"SharpnessFocusScore"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI Image Analysis Toolkit [WIP]"
|
||||
@ -5106,6 +5146,14 @@
|
||||
"title_aux": "ComfyUI-CustomNodes-MVM"
|
||||
}
|
||||
],
|
||||
"https://github.com/Tr1dae/ComfyUI-MobileSAM": [
|
||||
[
|
||||
"EasyMobileSAM"
|
||||
],
|
||||
{
|
||||
"title_aux": "[WIP] ComfyUI-MobileSAM"
|
||||
}
|
||||
],
|
||||
"https://github.com/UltraNoob-NazoGiken/ComfyUI-TOML-Tools": [
|
||||
[
|
||||
"CreateTomlData",
|
||||
@ -5203,6 +5251,23 @@
|
||||
"title_aux": "ComfyUI Custom Nodes: OpenRouter & Ollama [UNSAFE]"
|
||||
}
|
||||
],
|
||||
"https://github.com/Vov1ch/ComfyUI_GLMImage": [
|
||||
[
|
||||
"GLMImageGenerate",
|
||||
"GLMImageImageToImage",
|
||||
"GLMImageSDNQ_FlexibleInput",
|
||||
"GLMImageSDNQ_Generate",
|
||||
"GLMImageSDNQ_I2I_Standalone",
|
||||
"GLMImageSDNQ_ImageToImage",
|
||||
"GLMImageSDNQ_LoadPipe",
|
||||
"GLMImageSDNQ_MultiI2I_Standalone",
|
||||
"GLMImageSDNQ_MultiImageToImage",
|
||||
"GLMImageSDNQ_T2I_Standalone"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI_GLMImage"
|
||||
}
|
||||
],
|
||||
"https://github.com/Vsolon/ComfyUI-CBZ-Pack": [
|
||||
[
|
||||
"CBZ Preview Any",
|
||||
@ -6763,6 +6828,7 @@
|
||||
[
|
||||
"AliyunDriveCloudUploadNode",
|
||||
"AliyunDriveOptimizedUploadNode",
|
||||
"AliyunDriveUploadNode",
|
||||
"SimpleUploadToAliyunDrive",
|
||||
"UploadTo115",
|
||||
"UploadToAliyunDrive",
|
||||
@ -6972,11 +7038,13 @@
|
||||
"ExtendIntermediateSigmas",
|
||||
"FeatherMask",
|
||||
"FlipSigmas",
|
||||
"Flux2ProImageNode",
|
||||
"Flux2Scheduler",
|
||||
"FluxDisableGuidance",
|
||||
"FluxGuidance",
|
||||
"FluxKontextImageScale",
|
||||
"FluxKontextMultiReferenceLatentMethod",
|
||||
"FluxKontextProImageNode",
|
||||
"FluxProExpandNode",
|
||||
"FluxProFillNode",
|
||||
"FluxProUltraImageNode",
|
||||
@ -7020,6 +7088,7 @@
|
||||
"ImageOnlyCheckpointLoader",
|
||||
"ImageOnlyCheckpointSave",
|
||||
"ImagePadForOutpaint",
|
||||
"ImageProcessingNode",
|
||||
"ImageQuantize",
|
||||
"ImageRGBToYUV",
|
||||
"ImageRotate",
|
||||
@ -7381,6 +7450,7 @@
|
||||
"TextEncodeHunyuanVideo_ImageToVideo",
|
||||
"TextEncodeQwenImageEdit",
|
||||
"TextEncodeQwenImageEditPlus",
|
||||
"TextProcessingNode",
|
||||
"ThresholdMask",
|
||||
"TomePatchModel",
|
||||
"TopazImageEnhance",
|
||||
@ -8245,6 +8315,14 @@
|
||||
"title_aux": "comfyui-cem-tools"
|
||||
}
|
||||
],
|
||||
"https://github.com/fogyisland/Comfy_Show_StringText": [
|
||||
[
|
||||
"ComfyUIShowText"
|
||||
],
|
||||
{
|
||||
"title_aux": "Comfy_Show_StringText [WIP]"
|
||||
}
|
||||
],
|
||||
"https://github.com/franklydegenerate/ComfyUI-WAN-Resolution-Helper": [
|
||||
[
|
||||
"WANResolutionHelperV2"
|
||||
@ -8286,6 +8364,43 @@
|
||||
"title_aux": "ComfyUI-LLM-Utils [WIP]"
|
||||
}
|
||||
],
|
||||
"https://github.com/frost-byte/fbTools": [
|
||||
[
|
||||
"FBTextEncodeQwenImageEditPlus",
|
||||
"LibberApply",
|
||||
"LibberManager",
|
||||
"MaskProcessor",
|
||||
"NodeInputSelect",
|
||||
"OpaqueAlpha",
|
||||
"PromptComposer",
|
||||
"QwenAspectRatio",
|
||||
"SAMPreprocessNHWC",
|
||||
"SceneCreate",
|
||||
"SceneInput",
|
||||
"SceneOutput",
|
||||
"ScenePromptManager",
|
||||
"SceneSave",
|
||||
"SceneSelect",
|
||||
"SceneUpdate",
|
||||
"SceneView",
|
||||
"SceneWanVideoLoraMultiSave",
|
||||
"StoryCreate",
|
||||
"StoryEdit",
|
||||
"StoryLoad",
|
||||
"StorySave",
|
||||
"StorySceneBatch",
|
||||
"StorySceneImageSave",
|
||||
"StoryScenePick",
|
||||
"StoryVideoBatch",
|
||||
"StoryView",
|
||||
"SubdirLister",
|
||||
"TailEnhancePro",
|
||||
"TailSplit"
|
||||
],
|
||||
{
|
||||
"title_aux": "fb-tools"
|
||||
}
|
||||
],
|
||||
"https://github.com/ftechmax/ComfyUI-NovaKit-Pack": [
|
||||
[
|
||||
"CountTokens"
|
||||
@ -8548,6 +8663,7 @@
|
||||
],
|
||||
"https://github.com/grinlau18/ComfyUI_XISER_Nodes": [
|
||||
[
|
||||
"BaseFromListGetOneV3",
|
||||
"XISER_Canvas",
|
||||
"XIS_BooleanSwitch",
|
||||
"XIS_CanvasConfig",
|
||||
@ -12388,6 +12504,14 @@
|
||||
"title_aux": "ComfyUI-JsonViewer [WIP]"
|
||||
}
|
||||
],
|
||||
"https://github.com/shin131002/ComfyUI-Prompt-Preset-Selector": [
|
||||
[
|
||||
"PromptPresetSelector"
|
||||
],
|
||||
{
|
||||
"title_aux": "[WIP] ComfyUI-Prompt-Preset-Selector"
|
||||
}
|
||||
],
|
||||
"https://github.com/shinich39/comfyui-run-js": [
|
||||
[
|
||||
"RunJS"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,76 @@
|
||||
{
|
||||
"custom_nodes": [
|
||||
{
|
||||
"author": "martin-rizzo",
|
||||
"title": "ComfyUI-ZImagePowerNodes",
|
||||
"reference": "https://github.com/martin-rizzo/ComfyUI-ZImagePowerNodes",
|
||||
"files": [
|
||||
"https://github.com/martin-rizzo/ComfyUI-ZImagePowerNodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A set of ComfyUI nodes designed and fine-tuned specifically for the Z-Image model. Pushing the best image generation model to its limits!"
|
||||
},
|
||||
{
|
||||
"author": "0dot77",
|
||||
"title": "comfyui-annotations",
|
||||
"reference": "https://github.com/0dot77/comfyui-annotations",
|
||||
"files": [
|
||||
"https://github.com/0dot77/comfyui-annotations"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A lightweight annotation overlay for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "isala404",
|
||||
"title": "comfy-workflow-api",
|
||||
"reference": "https://github.com/isala404/comfy-workflow-api",
|
||||
"files": [
|
||||
"https://github.com/isala404/comfy-workflow-api"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "HTTP API for ComfyUI with webhook-based workflow execution."
|
||||
},
|
||||
{
|
||||
"author": "0nikod",
|
||||
"title": "ComfyUI-Metadata-Tools",
|
||||
"reference": "https://github.com/0nikod/ComfyUI-Metadata-Tools",
|
||||
"files": [
|
||||
"https://github.com/0nikod/ComfyUI-Metadata-Tools"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Tools about metadata."
|
||||
},
|
||||
{
|
||||
"author": "ethanfel",
|
||||
"title": "ComfyUI-Sharp-Selector",
|
||||
"reference": "https://github.com/ethanfel/ComfyUI-Sharp-Selector",
|
||||
"files": [
|
||||
"https://github.com/ethanfel/ComfyUI-Sharp-Selector"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom nodes for sharp frame selection and analysis. (Description by CC)"
|
||||
},
|
||||
{
|
||||
"author": "ato-zen",
|
||||
"title": "ComfyUI-VIBE",
|
||||
"reference": "https://github.com/ato-zen/ComfyUI-VIBE",
|
||||
"files": [
|
||||
"https://github.com/ato-zen/ComfyUI-VIBE"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Implementation of VIBE (Visual Instruction Based Editor) as a custom node for ComfyUI that enables image editing using natural language instructions leveraging Sana1.5 and Qwen3-VL models."
|
||||
},
|
||||
{
|
||||
"author": "FallenIncursio",
|
||||
"title": "arcenciel-link-comfyui",
|
||||
"reference": "https://github.com/FallenIncursio/arcenciel-link-comfyui",
|
||||
"files": [
|
||||
"https://github.com/FallenIncursio/arcenciel-link-comfyui"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Bring your ArcEnCiel models straight into ComfyUI with one click. Includes Link Key support, remote worker control, inventory sync, and sidecar generation."
|
||||
},
|
||||
|
||||
{
|
||||
"author": "capitan01R",
|
||||
"title": "ComfyUI-CapitanFlowMatch",
|
||||
@ -635,57 +706,6 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A dedicated ComfyUI custom node designed to streamline the process of converting transparent images (RGBA) into masks and high-quality mask previews."
|
||||
},
|
||||
{
|
||||
"author": "wallen0322",
|
||||
"title": "ComfyUI-qwenmultianglelight",
|
||||
"reference": "https://github.com/wallen0322/ComfyUI-qwenmultianglelight",
|
||||
"files": [
|
||||
"https://github.com/wallen0322/ComfyUI-qwenmultianglelight"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A ComfyUI node for 3D lighting angle control, outputs lighting prompts for relighting image generation"
|
||||
},
|
||||
|
||||
{
|
||||
"author": "patientx",
|
||||
"title": "CFZ-SwitchMenu",
|
||||
"reference": "https://github.com/patientx/CFZ-SwitchMenu",
|
||||
"files": [
|
||||
"https://github.com/patientx/CFZ-SwitchMenu"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Adds a menu button that switches between old and new style ComfyUI menus."
|
||||
},
|
||||
{
|
||||
"author": "Lumiyumi",
|
||||
"title": "LumyINTP",
|
||||
"reference": "https://github.com/Lumiyumi/LumyINTP",
|
||||
"files": [
|
||||
"https://github.com/Lumiyumi/LumyINTP"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "INT+ node for ComfyUI with control after generate style functionality for signal-controlled increment/decrement/randomize operations."
|
||||
},
|
||||
{
|
||||
"author": "MajoorWaldi",
|
||||
"title": "ComfyUI-Majoor-ImageOps",
|
||||
"reference": "https://github.com/MajoorWaldi/ComfyUI-Majoor-ImageOps",
|
||||
"files": [
|
||||
"https://github.com/MajoorWaldi/ComfyUI-Majoor-ImageOps"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Essential Nodes Pack for Images Processing for ComfyUI, with a live embedded preview inside the node (no queue) for supported chains."
|
||||
},
|
||||
{
|
||||
"author": "fujitea40",
|
||||
"title": "comfy_exp_preset",
|
||||
"reference": "https://github.com/fujitea40/comfy_exp_preset",
|
||||
"files": [
|
||||
"https://github.com/fujitea40/comfy_exp_preset"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom node for expression preset management in ComfyUI, allowing users to preselect expressions and parameters for character animation. (Description by CC)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -18,6 +18,17 @@
|
||||
"title_aux": "mmaker/Color Enhance"
|
||||
}
|
||||
],
|
||||
"https://github.com/0nikod/ComfyUI-Metadata-Tools": [
|
||||
[
|
||||
"ImageGetMetadata",
|
||||
"ImageSetMetadata",
|
||||
"LoadImageWithMetadata",
|
||||
"SaveImageWithMetadata"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-Metadata-Tools"
|
||||
}
|
||||
],
|
||||
"https://github.com/0nikod/ComfyUI-Simple-Prompt": [
|
||||
[
|
||||
"SimplePrompt"
|
||||
@ -2149,6 +2160,7 @@
|
||||
"INPAINT_MaskedBlur",
|
||||
"INPAINT_MaskedFill",
|
||||
"INPAINT_ShrinkMask",
|
||||
"INPAINT_StabilizeMask",
|
||||
"INPAINT_VAEEncodeInpaintConditioning"
|
||||
],
|
||||
{
|
||||
@ -6007,6 +6019,7 @@
|
||||
"DiffusionModelSelectorNode",
|
||||
"LORASelectorNode",
|
||||
"ModelGeneratorNode",
|
||||
"PonyPrefixesNode",
|
||||
"SamplerGeneratorNode",
|
||||
"SchedulerGeneratorNode",
|
||||
"StringToFloatNode",
|
||||
@ -22579,6 +22592,7 @@
|
||||
"Qwen3VLAdvanced",
|
||||
"Qwen3VLBasic",
|
||||
"Qwen3VLExtraOptions",
|
||||
"ResourceCleaner",
|
||||
"Sa2VAAdvanced",
|
||||
"Sa2VASegmentationPreset",
|
||||
"ShowText",
|
||||
@ -25024,6 +25038,14 @@
|
||||
"title_aux": "ComfyUI NPNet (Golden Noise)"
|
||||
}
|
||||
],
|
||||
"https://github.com/asagi4/comfyui-dynamic-anynode": [
|
||||
[
|
||||
"AnyNode"
|
||||
],
|
||||
{
|
||||
"title_aux": "comfyui-dynamic-anynode"
|
||||
}
|
||||
],
|
||||
"https://github.com/asagi4/comfyui-prompt-control": [
|
||||
[
|
||||
"PCAddMaskToCLIP",
|
||||
@ -25143,6 +25165,14 @@
|
||||
"title_aux": "comfyui_arcane_style_trans"
|
||||
}
|
||||
],
|
||||
"https://github.com/ato-zen/ComfyUI-VIBE": [
|
||||
[
|
||||
"VIBE_Editor"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-VIBE"
|
||||
}
|
||||
],
|
||||
"https://github.com/ato321/ComfyUI-LTXVGuideRebase": [
|
||||
[
|
||||
"LTXVRebaseGuides"
|
||||
@ -29792,11 +29822,13 @@
|
||||
"ExtendIntermediateSigmas",
|
||||
"FeatherMask",
|
||||
"FlipSigmas",
|
||||
"Flux2ProImageNode",
|
||||
"Flux2Scheduler",
|
||||
"FluxDisableGuidance",
|
||||
"FluxGuidance",
|
||||
"FluxKontextImageScale",
|
||||
"FluxKontextMultiReferenceLatentMethod",
|
||||
"FluxKontextProImageNode",
|
||||
"FluxProExpandNode",
|
||||
"FluxProFillNode",
|
||||
"FluxProUltraImageNode",
|
||||
@ -29840,6 +29872,7 @@
|
||||
"ImageOnlyCheckpointLoader",
|
||||
"ImageOnlyCheckpointSave",
|
||||
"ImagePadForOutpaint",
|
||||
"ImageProcessingNode",
|
||||
"ImageQuantize",
|
||||
"ImageRGBToYUV",
|
||||
"ImageRotate",
|
||||
@ -30201,6 +30234,7 @@
|
||||
"TextEncodeHunyuanVideo_ImageToVideo",
|
||||
"TextEncodeQwenImageEdit",
|
||||
"TextEncodeQwenImageEditPlus",
|
||||
"TextProcessingNode",
|
||||
"ThresholdMask",
|
||||
"TomePatchModel",
|
||||
"TopazImageEnhance",
|
||||
@ -32779,6 +32813,15 @@
|
||||
"title_aux": "comfyui-videoframenode"
|
||||
}
|
||||
],
|
||||
"https://github.com/ethanfel/ComfyUI-Sharp-Selector": [
|
||||
[
|
||||
"SharpFrameSelector",
|
||||
"SharpnessAnalyzer"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-Sharp-Selector"
|
||||
}
|
||||
],
|
||||
"https://github.com/ethanfel/Comfyui-JSON-Manager": [
|
||||
[
|
||||
"JSONLoaderBatchI2V",
|
||||
@ -35414,6 +35457,8 @@
|
||||
"AIIA_CosyVoice_VoiceConversion",
|
||||
"AIIA_Dialogue_TTS",
|
||||
"AIIA_E2E_Speaker_Diarization",
|
||||
"AIIA_EchoMimicLoader",
|
||||
"AIIA_EchoMimicSampler",
|
||||
"AIIA_FloatProcess_InMemory",
|
||||
"AIIA_FloatProcess_ToDisk",
|
||||
"AIIA_GenerateSpeakerSegments",
|
||||
@ -37110,6 +37155,16 @@
|
||||
"title_aux": "ComfyUI-DSD"
|
||||
}
|
||||
],
|
||||
"https://github.com/isala404/comfy-workflow-api": [
|
||||
[
|
||||
"WebhookReceiver",
|
||||
"WebhookSend",
|
||||
"WebhookTransformer"
|
||||
],
|
||||
{
|
||||
"title_aux": "comfy-workflow-api"
|
||||
}
|
||||
],
|
||||
"https://github.com/iwanders/ComfyUI_nodes": [
|
||||
[
|
||||
"IW_JsonPickItem",
|
||||
@ -40706,14 +40761,27 @@
|
||||
],
|
||||
"https://github.com/latentastronaut/comfyui-latent-astronaut-suite": [
|
||||
[
|
||||
"BatchLastImage",
|
||||
"ChatterBoxTTS",
|
||||
"ChatterBoxTTSLoader",
|
||||
"ChatterBoxTTSLoaderAuto",
|
||||
"ChatterBoxTTSSimple",
|
||||
"ChatterBoxVC",
|
||||
"ChatterBoxVCLoader",
|
||||
"ChatterBoxVCLoaderAuto",
|
||||
"ChatterBoxVCSimple",
|
||||
"ForLoopEnd",
|
||||
"ForLoopStart",
|
||||
"ImageResizeToTotalPixels",
|
||||
"LLMConfig",
|
||||
"LLMPromptEnhancer",
|
||||
"LoraLoaderModelOnlySelector",
|
||||
"LoraLoaderSelector",
|
||||
"SizeSelector",
|
||||
"StringListCombine",
|
||||
"StringListFromText",
|
||||
"StringListIndex"
|
||||
"StringListIndex",
|
||||
"VideoLengthFromBatch"
|
||||
],
|
||||
{
|
||||
"title_aux": "comfyui-latent-astronaut-suite"
|
||||
@ -43540,6 +43608,18 @@
|
||||
"title_aux": "comfyui-previewlatent"
|
||||
}
|
||||
],
|
||||
"https://github.com/martin-rizzo/ComfyUI-ZImagePowerNodes": [
|
||||
[
|
||||
"EmptyZImageLatentImage",
|
||||
"IllustrationStylePromptEncoder",
|
||||
"PhotoStylePromptEncoder",
|
||||
"SaveImage",
|
||||
"ZSamplerTurbo"
|
||||
],
|
||||
{
|
||||
"title_aux": "ComfyUI-ZImagePowerNodes"
|
||||
}
|
||||
],
|
||||
"https://github.com/massao000/ComfyUI_aspect_ratios": [
|
||||
[
|
||||
"Aspect Ratios Node"
|
||||
@ -53304,8 +53384,8 @@
|
||||
],
|
||||
"https://github.com/transcendedhacker/Mode_personal_node": [
|
||||
[
|
||||
"NegativeNode",
|
||||
"PromptNode"
|
||||
"NegativePromptNode",
|
||||
"PromptComposerNode"
|
||||
],
|
||||
{
|
||||
"title_aux": "Mode_personal_node"
|
||||
@ -53629,7 +53709,9 @@
|
||||
"LatentSmokeSimulation",
|
||||
"LatentSwirlNoise",
|
||||
"LatentWorleyNoise",
|
||||
"SplitLatentPhaseMagnitude"
|
||||
"PatchifyFlux2Latent",
|
||||
"SplitLatentPhaseMagnitude",
|
||||
"UnpatchifyFlux2Latent"
|
||||
],
|
||||
{
|
||||
"title_aux": "Skoogeer-Noise"
|
||||
@ -57251,6 +57333,7 @@
|
||||
],
|
||||
"https://github.com/zhangp365/ComfyUI-utils-nodes": [
|
||||
[
|
||||
"AspectRatioSizeNodeOfUtils",
|
||||
"BooleanControlOutput",
|
||||
"CheckpointLoaderSimpleWithSwitch",
|
||||
"ColorCorrectOfUtils",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user