Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22758b4595 | ||
|
|
c7f24a3262 | ||
|
|
c69281e795 | ||
|
|
3bf8391e88 | ||
|
|
d13bfe8fb4 | ||
|
|
702d0c889c | ||
|
|
d1a5282ca0 | ||
|
|
36212adf73 | ||
|
|
2cb2262293 | ||
|
|
8773c8f249 | ||
|
|
0a1cbe4990 | ||
|
|
c4a27137e7 | ||
|
|
20270d9501 | ||
|
|
2467dff704 | ||
|
|
cecd1e01e9 | ||
|
|
e504f314aa | ||
|
|
f12debe729 | ||
|
|
c64e835d2e | ||
|
|
36d37db8a9 | ||
|
|
969fd9e25e | ||
|
|
6916b47a89 | ||
|
|
8457db0041 | ||
|
|
8a84927adf | ||
|
|
8c0b89ff70 | ||
|
|
b527635254 | ||
|
|
14738b80c5 | ||
|
|
bff1276d06 | ||
|
|
a7805ad587 | ||
|
|
f2093d6567 | ||
|
|
0e26f0ad36 | ||
|
|
9a05f20790 | ||
|
|
6368582e1b | ||
|
|
97ea5b9a7d | ||
|
|
f5f956ab4e | ||
|
|
498fae1211 | ||
|
|
d24912036c | ||
|
|
523b0509f1 | ||
|
|
5f8117aa56 | ||
|
|
22dc975353 | ||
|
|
66afb2b204 | ||
|
|
1dd1dcf895 | ||
|
|
60f30b068e | ||
|
|
1439270fe5 | ||
|
|
f8210d8f69 | ||
|
|
aceb34b9b0 | ||
|
|
e9ce1fd2cf | ||
|
|
9b888443ac | ||
|
|
f4743df3ef | ||
|
|
6a68983ef4 | ||
|
|
fb01fa24ae | ||
|
|
65f68f59a1 | ||
|
|
c3fab5581b | ||
|
|
1ea2b4cc90 | ||
|
|
f33f39f134 | ||
|
|
5b57d4fc35 | ||
|
|
a1625dddad | ||
|
|
a88232f59a | ||
|
|
6746b86685 | ||
|
|
17af18d397 | ||
|
|
703989599d | ||
|
|
8399fad96b | ||
|
|
0c69abc829 | ||
|
|
e00406747f | ||
|
|
a21e677629 | ||
|
|
13e425959b | ||
|
|
0a6ee72748 | ||
|
|
b03f0ecf22 | ||
|
|
ed81bf4cfd | ||
|
|
58a6c05d98 | ||
|
|
51bb7711b8 | ||
|
|
8f459c502a | ||
|
|
af1bc6845a | ||
|
|
27431b3a92 | ||
|
|
67ef0a44d7 | ||
|
|
a501260bbf | ||
|
|
3a4651b191 | ||
|
|
2f3d6d62f3 | ||
|
|
fb805c4a3d | ||
|
|
4e84588a94 | ||
|
|
df7776280e | ||
|
|
8fd92530ee | ||
|
|
081f5f2310 | ||
|
|
ad7e6e647f | ||
|
|
04218704b3 | ||
|
|
a4db4390ea | ||
|
|
c38753758b | ||
|
|
17b97ed17a | ||
|
|
bd15b45f46 | ||
|
|
5f6846c3cb | ||
|
|
363cc9c755 | ||
|
|
5ae7985bc2 | ||
|
|
b37bc763dc | ||
|
|
0439763614 | ||
|
|
2e1f563298 | ||
|
|
4b63cb7176 | ||
|
|
1f6a148538 | ||
|
|
d9a7879c45 | ||
|
|
c6b5dc4b54 | ||
|
|
a4d1169c63 | ||
|
|
ff6f397dd2 | ||
|
|
97913deae3 | ||
|
|
e6c8dd583f | ||
|
|
f9db6f8635 | ||
|
|
1b18873e65 | ||
|
|
d2b30f0a78 | ||
|
|
917421529b | ||
|
|
d29dcb8564 | ||
|
|
b95bf8e64a | ||
|
|
b8622e21da | ||
|
|
4c8f20ef88 | ||
|
|
26d135e106 | ||
|
|
db7d0dc86e | ||
|
|
b5e24dbe57 | ||
|
|
eb1e646453 | ||
|
|
793579a1dd | ||
|
|
eba899b30d | ||
|
|
914ba8e003 | ||
|
|
51b057982e | ||
|
|
3a3a6ebab9 | ||
|
|
886917d95c | ||
|
|
bf55338b61 | ||
|
|
c561e59650 | ||
|
|
9e67f06a21 | ||
|
|
90e4b57dc6 | ||
|
|
386e48c2e1 | ||
|
|
624ba92723 | ||
|
|
0ae29f576a | ||
|
|
ca9fd5153a | ||
|
|
c0d239f31f | ||
|
|
6d27520641 | ||
|
|
7cde5a5ceb | ||
|
|
ecdde6bbd7 | ||
|
|
0557c040f8 | ||
|
|
f7d007d8c4 | ||
|
|
80045954a5 | ||
|
|
6f176e7b78 | ||
|
|
576efbba41 | ||
|
|
593bd4de79 | ||
|
|
a8fb5ddaa7 | ||
|
|
f67e619930 | ||
|
|
f9e2f5bcc5 | ||
|
|
2cfeab9e76 | ||
|
|
f5ce23ad12 | ||
|
|
05a9520436 | ||
|
|
c1fcc4566b | ||
|
|
40b91fc44f | ||
|
|
3e7734bbc3 | ||
|
|
17400d88f7 | ||
|
|
0f79601065 | ||
|
|
df60457929 | ||
|
|
5d0e8194a1 | ||
|
|
218a208bf0 | ||
|
|
a7b612d799 | ||
|
|
df3d4c19cb | ||
|
|
938d0d93de | ||
|
|
e65bf123b5 | ||
|
|
b348273d3a | ||
|
|
cc1ffe2605 | ||
|
|
ef30413e12 | ||
|
|
5485aa8c19 | ||
|
|
a8ee5930ff | ||
|
|
9fd80793db | ||
|
|
972c487dd4 | ||
|
|
1a3efd3802 | ||
|
|
34f54e515d | ||
|
|
c844abea51 | ||
|
|
404a1efd61 | ||
|
|
4ae514dacf | ||
|
|
5efae8eeb8 | ||
|
|
85288c8fd8 | ||
|
|
7a97f7c2bc | ||
|
|
a4692a286c | ||
|
|
72a3fea3cb | ||
|
|
d5d4145a04 | ||
|
|
0e288dd109 | ||
|
|
c4882f894e | ||
|
|
6cbe6e5ae6 | ||
|
|
df20afb83e | ||
|
|
7d63e11e18 | ||
|
|
a8364b5c57 | ||
|
|
332a74225d | ||
|
|
d757b623d6 | ||
|
|
64e844ec42 | ||
|
|
3ed188d63f | ||
|
|
b9cc9f295d | ||
|
|
271cd020c1 | ||
|
|
e34807855a | ||
|
|
d32e18f844 | ||
|
|
228b74ae5e | ||
|
|
6197b482df | ||
|
|
f62129afda | ||
|
|
8d5065c975 | ||
|
|
2d27c32bfd | ||
|
|
3ecab5ac08 | ||
|
|
70592114f9 | ||
|
|
407fc4ca7b | ||
|
|
cb7d5246f9 | ||
|
|
9829fc001d | ||
|
|
e84ec6721c | ||
|
|
80fac8e544 | ||
|
|
efc079a95b | ||
|
|
bbd239cbd6 | ||
|
|
589fbf3568 | ||
|
|
c595cabaa0 | ||
|
|
ca504d5f74 | ||
|
|
f559fe220e | ||
|
|
90c1aa402d | ||
|
|
932e30ade0 | ||
|
|
f9540bd984 | ||
|
|
a8af833c31 | ||
|
|
005c3bdf65 | ||
|
|
67a59a0d3b | ||
|
|
bb5653fc0e | ||
|
|
ab23992c29 | ||
|
|
9007b10d42 | ||
|
|
b71bfa8d4e | ||
|
|
c1128addc7 | ||
|
|
a49071f824 | ||
|
|
bbdd27f498 | ||
|
|
22f62bf7b4 | ||
|
|
4ff6067dad | ||
|
|
ad13e66506 | ||
|
|
b16f6f40bd | ||
|
|
6dfa66963b | ||
|
|
ab016e0903 | ||
|
|
f4228a850c | ||
|
|
79042b78d2 | ||
|
|
0c4e59c4e9 | ||
|
|
4a0a206d61 | ||
|
|
8e0d4485bd | ||
|
|
92a3b1db4e | ||
|
|
ab628b1bf2 | ||
|
|
7e712a17d9 | ||
|
|
269fb2ba80 | ||
|
|
e17fdddcd7 | ||
|
|
5d1f01e6cb | ||
|
|
b3b8826044 | ||
|
|
9dbb1f749d | ||
|
|
682f2b0a47 | ||
|
|
1233cf693e | ||
|
|
32d44a282f | ||
|
|
bb79c7434f | ||
|
|
bd8c0a42bc | ||
|
|
5d9e71dc7b | ||
|
|
321d89dcc4 | ||
|
|
cd77d06ac9 | ||
|
|
d23ff34b27 | ||
|
|
cc725d27f6 | ||
|
|
4c3d3958d6 | ||
|
|
2c992b5c97 | ||
|
|
8628bc39bb | ||
|
|
3654867a21 | ||
|
|
85af1b38f9 | ||
|
|
03189afd85 |
@@ -0,0 +1,35 @@
|
||||
[flake8]
|
||||
max-line-length = 127
|
||||
max-complexity = 10
|
||||
exclude =
|
||||
.git,
|
||||
__pycache__,
|
||||
.mypy_cache,
|
||||
.pytest_cache,
|
||||
venv,
|
||||
env,
|
||||
build,
|
||||
dist,
|
||||
*.egg-info,
|
||||
.tox
|
||||
ignore =
|
||||
# W503: line break before binary operator (conflicts with Black)
|
||||
W503,
|
||||
# E203: whitespace before ':' (conflicts with Black)
|
||||
E203,
|
||||
# E501: line too long (we use max-line-length)
|
||||
E501
|
||||
|
||||
per-file-ignores =
|
||||
# Allow unused imports in __init__.py files
|
||||
__init__.py:F401,F403
|
||||
# Allow assertions in tests
|
||||
tests/*:S101
|
||||
# Allow higher complexity for Gemini prompt module
|
||||
kikotools/tools/gemini_prompt/logic.py:C901
|
||||
kikotools/tools/gemini_prompt/models.py:C901
|
||||
kikotools/tools/gemini_prompt/node.py:C901
|
||||
|
||||
# Statistics
|
||||
count = True
|
||||
statistics = True
|
||||
@@ -0,0 +1,41 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Python files
|
||||
*.py text eol=lf
|
||||
*.pyi text eol=lf
|
||||
|
||||
# Configuration files
|
||||
*.json text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.toml text eol=lf
|
||||
*.ini text eol=lf
|
||||
*.cfg text eol=lf
|
||||
|
||||
# Documentation
|
||||
*.md text eol=lf
|
||||
*.rst text eol=lf
|
||||
*.txt text eol=lf
|
||||
|
||||
# Scripts
|
||||
*.sh text eol=lf
|
||||
*.bash text eol=lf
|
||||
|
||||
# Git files
|
||||
.gitignore text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
|
||||
# ComfyUI specific
|
||||
*.workflow text eol=lf
|
||||
|
||||
# Binary files
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.webp binary
|
||||
*.safetensors binary
|
||||
*.ckpt binary
|
||||
*.pt binary
|
||||
*.pth binary
|
||||
@@ -45,4 +45,4 @@ Paste any error messages or stack traces here
|
||||
If possible, attach the ComfyUI workflow file (.json) that reproduces the issue.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -37,7 +37,7 @@ Describe how the tool should process inputs and generate outputs.
|
||||
|
||||
**Model Compatibility:**
|
||||
- [ ] SDXL optimized
|
||||
- [ ] FLUX optimized
|
||||
- [ ] FLUX optimized
|
||||
- [ ] General purpose
|
||||
- [ ] Specific model requirements: [describe]
|
||||
|
||||
@@ -64,4 +64,4 @@ Are there existing ComfyUI nodes that do something similar? How would this be di
|
||||
- [ ] Yes, I can help with implementation
|
||||
- [ ] Yes, I can help with testing
|
||||
- [ ] Yes, I can help with documentation
|
||||
- [ ] No, but I'd be happy to test it
|
||||
- [ ] No, but I'd be happy to test it
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -1,4 +1,6 @@
|
||||
name: Code Quality
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -11,15 +13,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Cache pip dependencies
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-quality-${{ hashFiles('**/requirements-dev.txt') }}
|
||||
@@ -59,7 +61,7 @@ jobs:
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.getcwd())
|
||||
|
||||
|
||||
# Test that all imports work correctly
|
||||
try:
|
||||
from kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
@@ -67,70 +69,82 @@ jobs:
|
||||
except ImportError as e:
|
||||
print(f'Warning: Package-level imports failed: {e}')
|
||||
# This is expected since we don't have ComfyUI installed
|
||||
|
||||
|
||||
# Test individual module imports
|
||||
from kikotools.base import ComfyAssetsBaseNode
|
||||
from kikotools.tools.resolution_calculator import ResolutionCalculatorNode
|
||||
from kikotools.tools.resolution_calculator.logic import extract_dimensions
|
||||
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode as NodeClass
|
||||
|
||||
|
||||
# Test Width Height Selector imports
|
||||
from kikotools.tools.width_height_selector import WidthHeightSelectorNode
|
||||
from kikotools.tools.width_height_selector.logic import get_preset_dimensions
|
||||
from kikotools.tools.width_height_selector.presets import PRESET_OPTIONS, PRESET_METADATA
|
||||
|
||||
|
||||
# Test Sampler Combo imports
|
||||
from kikotools.tools.sampler_combo import SamplerComboNode
|
||||
from kikotools.tools.sampler_combo.logic import get_sampler_combo, SAMPLERS, SCHEDULERS
|
||||
|
||||
|
||||
# Test Seed History imports
|
||||
from kikotools.tools.seed_history import SeedHistoryNode
|
||||
from kikotools.tools.seed_history.logic import generate_random_seed, validate_seed_value
|
||||
|
||||
|
||||
# Test Kiko Save Image imports
|
||||
from kikotools.tools.kiko_save_image import KikoSaveImageNode
|
||||
from kikotools.tools.kiko_save_image.logic import process_image_batch, validate_save_inputs
|
||||
|
||||
# Test Model Downloader imports
|
||||
from kikotools.tools.model_downloader import ModelDownloaderNode
|
||||
from kikotools.tools.model_downloader.detector import URLDetector, DownloaderType
|
||||
from kikotools.tools.model_downloader.base import BaseDownloader
|
||||
|
||||
# Test Text Input imports
|
||||
from kikotools.tools.text_input import TextInputNode
|
||||
|
||||
print('✓ All module imports successful')
|
||||
"
|
||||
|
||||
- name: Check code style consistency
|
||||
run: |
|
||||
echo "Checking code style consistency..."
|
||||
|
||||
|
||||
# Check for consistent naming
|
||||
find kikotools/ -name "*.py" -exec grep -l "class.*Node" {} \; | while read file; do
|
||||
if ! grep -q "ComfyAssetsBaseNode" "$file" && ! grep -q "class ComfyAssetsBaseNode" "$file"; then
|
||||
echo "Checking $file for ComfyUI node inheritance..."
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
# Check for proper docstrings
|
||||
python -c "
|
||||
import ast
|
||||
import os
|
||||
|
||||
|
||||
def check_docstrings(filepath):
|
||||
with open(filepath, 'r') as f:
|
||||
tree = ast.parse(f.read())
|
||||
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
|
||||
if not ast.get_docstring(node) and not node.name.startswith('_'):
|
||||
print(f'Warning: {filepath}:{node.lineno} - {node.name} missing docstring')
|
||||
|
||||
|
||||
for root, dirs, files in os.walk('kikotools'):
|
||||
for file in files:
|
||||
if file.endswith('.py') and not file.startswith('__'):
|
||||
filepath = os.path.join(root, file)
|
||||
check_docstrings(filepath)
|
||||
|
||||
|
||||
print('✓ Docstring check completed')
|
||||
"
|
||||
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
@@ -147,7 +161,7 @@ jobs:
|
||||
- name: Check for hardcoded secrets
|
||||
run: |
|
||||
echo "Checking for potential secrets..."
|
||||
|
||||
|
||||
# Check for common secret patterns
|
||||
if grep -r -i "password\|secret\|key\|token" kikotools/ --include="*.py" | grep -v "# " | grep -v "def " | grep -v "class "; then
|
||||
echo "Warning: Potential hardcoded secrets found"
|
||||
@@ -158,10 +172,10 @@ jobs:
|
||||
architecture:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
@@ -176,82 +190,138 @@ jobs:
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.getcwd())
|
||||
|
||||
|
||||
print('Checking architecture compliance...')
|
||||
|
||||
|
||||
# Test separation of concerns
|
||||
from kikotools.tools.resolution_calculator import logic, node
|
||||
|
||||
|
||||
# Logic module should not import node-specific things
|
||||
import inspect
|
||||
logic_source = inspect.getsource(logic)
|
||||
|
||||
|
||||
if 'ComfyUI' in logic_source and 'INPUT_TYPES' not in logic_source:
|
||||
print('⚠️ Warning: Logic module contains ComfyUI-specific code')
|
||||
else:
|
||||
print('✓ Logic module properly separated')
|
||||
|
||||
|
||||
# Node module should inherit from base
|
||||
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode
|
||||
from kikotools.base import ComfyAssetsBaseNode
|
||||
|
||||
|
||||
if issubclass(ResolutionCalculatorNode, ComfyAssetsBaseNode):
|
||||
print('✓ Node properly inherits from base class')
|
||||
else:
|
||||
print('❌ Node does not inherit from base class')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Check that nodes have proper ComfyUI interface
|
||||
required_attrs = ['INPUT_TYPES', 'RETURN_TYPES', 'RETURN_NAMES', 'FUNCTION', 'CATEGORY']
|
||||
|
||||
|
||||
# Test Resolution Calculator Node
|
||||
for attr in required_attrs:
|
||||
if not hasattr(ResolutionCalculatorNode, attr):
|
||||
print(f'❌ ResolutionCalculatorNode missing required attribute: {attr}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Test Width Height Selector Node
|
||||
from kikotools.tools.width_height_selector.node import WidthHeightSelectorNode
|
||||
|
||||
|
||||
if issubclass(WidthHeightSelectorNode, ComfyAssetsBaseNode):
|
||||
print('✓ WidthHeightSelectorNode properly inherits from base class')
|
||||
else:
|
||||
print('❌ WidthHeightSelectorNode does not inherit from base class')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
for attr in required_attrs:
|
||||
if not hasattr(WidthHeightSelectorNode, attr):
|
||||
print(f'❌ WidthHeightSelectorNode missing required attribute: {attr}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Test Sampler Combo Node
|
||||
from kikotools.tools.sampler_combo.node import SamplerComboNode
|
||||
|
||||
|
||||
if issubclass(SamplerComboNode, ComfyAssetsBaseNode):
|
||||
print('✓ SamplerComboNode properly inherits from base class')
|
||||
else:
|
||||
print('❌ SamplerComboNode does not inherit from base class')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
for attr in required_attrs:
|
||||
if not hasattr(SamplerComboNode, attr):
|
||||
print(f'❌ SamplerComboNode missing required attribute: {attr}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Test Seed History Node
|
||||
from kikotools.tools.seed_history.node import SeedHistoryNode
|
||||
|
||||
|
||||
if issubclass(SeedHistoryNode, ComfyAssetsBaseNode):
|
||||
print('✓ SeedHistoryNode properly inherits from base class')
|
||||
else:
|
||||
print('❌ SeedHistoryNode does not inherit from base class')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
for attr in required_attrs:
|
||||
if not hasattr(SeedHistoryNode, attr):
|
||||
print(f'❌ SeedHistoryNode missing required attribute: {attr}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Test Kiko Save Image Node
|
||||
from kikotools.tools.kiko_save_image.node import KikoSaveImageNode
|
||||
|
||||
if issubclass(KikoSaveImageNode, ComfyAssetsBaseNode):
|
||||
print('✓ KikoSaveImageNode properly inherits from base class')
|
||||
else:
|
||||
print('❌ KikoSaveImageNode does not inherit from base class')
|
||||
sys.exit(1)
|
||||
|
||||
# KikoSaveImage is an output node, so it doesn't have RETURN_TYPES/RETURN_NAMES
|
||||
save_required_attrs = ['INPUT_TYPES', 'FUNCTION', 'CATEGORY']
|
||||
for attr in save_required_attrs:
|
||||
if not hasattr(KikoSaveImageNode, attr):
|
||||
print(f'❌ KikoSaveImageNode missing required attribute: {attr}')
|
||||
sys.exit(1)
|
||||
|
||||
# Check that it's properly marked as an output node
|
||||
if not hasattr(KikoSaveImageNode, 'OUTPUT_NODE') or not KikoSaveImageNode.OUTPUT_NODE:
|
||||
print('❌ KikoSaveImageNode missing OUTPUT_NODE = True')
|
||||
sys.exit(1)
|
||||
|
||||
# Test Model Downloader Node
|
||||
from kikotools.tools.model_downloader.node import ModelDownloaderNode
|
||||
|
||||
if issubclass(ModelDownloaderNode, ComfyAssetsBaseNode):
|
||||
print('✓ ModelDownloaderNode properly inherits from base class')
|
||||
else:
|
||||
print('❌ ModelDownloaderNode does not inherit from base class')
|
||||
sys.exit(1)
|
||||
|
||||
# ModelDownloader is an output node, so it doesn't have RETURN_TYPES/RETURN_NAMES
|
||||
download_required_attrs = ['INPUT_TYPES', 'FUNCTION', 'CATEGORY']
|
||||
for attr in download_required_attrs:
|
||||
if not hasattr(ModelDownloaderNode, attr):
|
||||
print(f'❌ ModelDownloaderNode missing required attribute: {attr}')
|
||||
sys.exit(1)
|
||||
|
||||
# Check that it's properly marked as an output node
|
||||
if not hasattr(ModelDownloaderNode, 'OUTPUT_NODE') or not ModelDownloaderNode.OUTPUT_NODE:
|
||||
print('❌ ModelDownloaderNode missing OUTPUT_NODE = True')
|
||||
sys.exit(1)
|
||||
|
||||
# Test Text Input Node
|
||||
from kikotools.tools.text_input.node import TextInputNode
|
||||
|
||||
if issubclass(TextInputNode, ComfyAssetsBaseNode):
|
||||
print('✓ TextInputNode properly inherits from base class')
|
||||
else:
|
||||
print('❌ TextInputNode does not inherit from base class')
|
||||
sys.exit(1)
|
||||
|
||||
for attr in required_attrs:
|
||||
if not hasattr(TextInputNode, attr):
|
||||
print(f'❌ TextInputNode missing required attribute: {attr}')
|
||||
sys.exit(1)
|
||||
|
||||
print('✓ All architecture checks passed for all tools')
|
||||
"
|
||||
|
||||
@@ -259,22 +329,22 @@ jobs:
|
||||
run: |
|
||||
python -c "
|
||||
import os
|
||||
|
||||
|
||||
# Count test files vs implementation files
|
||||
test_files = 0
|
||||
impl_files = 0
|
||||
|
||||
|
||||
for root, dirs, files in os.walk('tests'):
|
||||
test_files += len([f for f in files if f.startswith('test_') and f.endswith('.py')])
|
||||
|
||||
|
||||
for root, dirs, files in os.walk('kikotools'):
|
||||
impl_files += len([f for f in files if f.endswith('.py') and not f.startswith('__')])
|
||||
|
||||
|
||||
print(f'Implementation files: {impl_files}')
|
||||
print(f'Test files: {test_files}')
|
||||
|
||||
|
||||
if test_files >= impl_files * 0.5: # At least 50% test coverage by file count
|
||||
print('✓ Adequate test file coverage')
|
||||
else:
|
||||
print('⚠️ Warning: Low test file coverage')
|
||||
"
|
||||
"
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
if: ${{ github.repository_owner == 'ComfyAssets' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
- name: Publish Custom Node
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: Release
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -8,12 +11,14 @@ on:
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
@@ -28,30 +33,30 @@ jobs:
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.getcwd())
|
||||
|
||||
|
||||
# Run comprehensive tests before release
|
||||
from kikotools.base import ComfyAssetsBaseNode
|
||||
from kikotools.tools.resolution_calculator.logic import extract_dimensions, calculate_scaled_dimensions
|
||||
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode
|
||||
import torch
|
||||
|
||||
|
||||
print('Running pre-release validation...')
|
||||
|
||||
|
||||
# Test all major functionality
|
||||
node = ResolutionCalculatorNode()
|
||||
|
||||
|
||||
# Test various scenarios
|
||||
test_cases = [
|
||||
(torch.randn(1, 512, 512, 3), 2.0),
|
||||
(torch.randn(1, 1024, 1024, 3), 1.5),
|
||||
(torch.randn(1, 1216, 832, 3), 1.53), # User scenario
|
||||
]
|
||||
|
||||
|
||||
for i, (image, scale) in enumerate(test_cases):
|
||||
width, height = node.calculate_resolution(scale, image=image)
|
||||
print(f'✓ Test case {i+1}: {image.shape[2]}×{image.shape[1]} → {width}×{height} (scale: {scale})')
|
||||
assert width % 8 == 0 and height % 8 == 0
|
||||
|
||||
|
||||
print('🎉 All pre-release tests passed!')
|
||||
"
|
||||
|
||||
@@ -64,22 +69,22 @@ jobs:
|
||||
run: |
|
||||
cat > release_notes.md << 'EOF'
|
||||
## ComfyUI-KikoTools ${{ steps.get_version.outputs.version }}
|
||||
|
||||
|
||||
### 🎉 What's New
|
||||
|
||||
|
||||
#### Resolution Calculator Tool
|
||||
- **Smart Input Handling**: Works with both IMAGE and LATENT tensors
|
||||
- **Model Optimized**: Specific optimizations for SDXL and FLUX models
|
||||
- **Model Optimized**: Specific optimizations for SDXL and FLUX models
|
||||
- **Constraint Enforcement**: Automatically ensures dimensions divisible by 8
|
||||
- **Flexible Scaling**: Supports scale factors from 1.0x to 8.0x
|
||||
|
||||
|
||||
### 📦 Installation
|
||||
|
||||
|
||||
#### ComfyUI Manager
|
||||
1. Search for "ComfyUI-KikoTools"
|
||||
2. Click Install
|
||||
3. Restart ComfyUI
|
||||
|
||||
|
||||
#### Manual Installation
|
||||
```bash
|
||||
cd ComfyUI/custom_nodes/
|
||||
@@ -87,29 +92,29 @@ jobs:
|
||||
cd ComfyUI-KikoTools
|
||||
pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
|
||||
### 🚀 Quick Start
|
||||
|
||||
|
||||
Look for **ComfyAssets** nodes in your ComfyUI node browser!
|
||||
|
||||
|
||||
### 📊 Technical Details
|
||||
|
||||
|
||||
- **Nodes**: 1 (Resolution Calculator)
|
||||
- **Test Coverage**: 100%
|
||||
- **Python Support**: 3.8+
|
||||
- **ComfyUI Compatibility**: Latest
|
||||
|
||||
|
||||
### 🐛 Bug Reports
|
||||
|
||||
|
||||
Found an issue? Please report it [here](https://github.com/ComfyAssets/ComfyUI-KikoTools/issues).
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/ComfyAssets/ComfyUI-KikoTools/compare/v0.0.0...${{ steps.get_version.outputs.version }}
|
||||
EOF
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: ${{ steps.get_version.outputs.version }}
|
||||
name: ComfyUI-KikoTools ${{ steps.get_version.outputs.version }}
|
||||
@@ -128,18 +133,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: create-release
|
||||
if: success()
|
||||
|
||||
|
||||
steps:
|
||||
- name: Community notification placeholder
|
||||
run: |
|
||||
echo "🎉 Release ${{ needs.create-release.outputs.version }} created!"
|
||||
echo "Consider posting to:"
|
||||
echo "- ComfyUI Discord"
|
||||
echo "- Reddit r/ComfyUI"
|
||||
echo "- Reddit r/ComfyUI"
|
||||
echo "- ComfyUI-Manager database"
|
||||
echo ""
|
||||
echo "Release includes:"
|
||||
echo "- Resolution Calculator tool"
|
||||
echo "- Complete documentation"
|
||||
echo "- Example workflows"
|
||||
echo "- 100% test coverage"
|
||||
echo "- 100% test coverage"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: Tests
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
@@ -11,18 +14,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
|
||||
python-version: ["3.11", "3.12", "3.13"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Cache pip dependencies
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
|
||||
@@ -50,7 +53,7 @@ jobs:
|
||||
print('✓ All imports successful')
|
||||
|
||||
# Test base node
|
||||
assert ComfyAssetsBaseNode.CATEGORY == 'ComfyAssets'
|
||||
assert 'ComfyAssets' in ComfyAssetsBaseNode.CATEGORY
|
||||
print('✓ Base node tests passed')
|
||||
|
||||
# Test dimension extraction
|
||||
@@ -158,10 +161,13 @@ jobs:
|
||||
assert 'cfg' in input_types['required']
|
||||
print('✓ Sampler Combo interface tests passed')
|
||||
|
||||
# Test return types
|
||||
assert node.RETURN_TYPES == (SAMPLERS, SCHEDULERS, 'INT', 'FLOAT')
|
||||
# Test return types - Updated to match SAMPLERS list change
|
||||
assert node.RETURN_TYPES[0] == SAMPLERS # Now returns SAMPLERS list
|
||||
assert isinstance(node.RETURN_TYPES[1], list) # SCHEDULERS is a list
|
||||
assert node.RETURN_TYPES[2] == 'INT'
|
||||
assert node.RETURN_TYPES[3] == 'FLOAT'
|
||||
assert node.RETURN_NAMES == ('sampler_name', 'scheduler', 'steps', 'cfg')
|
||||
assert node.CATEGORY == 'ComfyAssets'
|
||||
assert node.CATEGORY == '🫶 ComfyAssets/🌀 Samplers'
|
||||
print('✓ Sampler Combo return types tests passed')
|
||||
|
||||
# Test sampler combo functionality
|
||||
@@ -210,7 +216,7 @@ jobs:
|
||||
# Test return types
|
||||
assert node.RETURN_TYPES == ('INT',)
|
||||
assert node.RETURN_NAMES == ('seed',)
|
||||
assert node.CATEGORY == 'ComfyAssets'
|
||||
assert node.CATEGORY == '🫶 ComfyAssets/🌱 Seeds'
|
||||
print('✓ Seed History return types tests passed')
|
||||
|
||||
# Test seed output functionality
|
||||
@@ -326,7 +332,7 @@ jobs:
|
||||
|
||||
assert res_class.RETURN_TYPES == ('INT', 'INT')
|
||||
assert res_class.RETURN_NAMES == ('width', 'height')
|
||||
assert res_class.CATEGORY == 'ComfyAssets'
|
||||
assert 'ComfyAssets/' in res_class.CATEGORY
|
||||
print('✓ Resolution Calculator ComfyUI integration passed')
|
||||
|
||||
# Test Width Height Selector
|
||||
@@ -347,7 +353,7 @@ jobs:
|
||||
|
||||
assert wh_class.RETURN_TYPES == ('INT', 'INT')
|
||||
assert wh_class.RETURN_NAMES == ('width', 'height')
|
||||
assert wh_class.CATEGORY == 'ComfyAssets'
|
||||
assert 'ComfyAssets/' in wh_class.CATEGORY
|
||||
print('✓ Width Height Selector ComfyUI integration passed')
|
||||
|
||||
# Test Sampler Combo
|
||||
@@ -367,7 +373,7 @@ jobs:
|
||||
assert 'steps' in input_types['required']
|
||||
assert 'cfg' in input_types['required']
|
||||
|
||||
assert sampler_class.CATEGORY == 'ComfyAssets'
|
||||
assert 'ComfyAssets/' in sampler_class.CATEGORY
|
||||
print('✓ Sampler Combo ComfyUI integration passed')
|
||||
|
||||
# Test Seed History
|
||||
@@ -386,7 +392,7 @@ jobs:
|
||||
|
||||
assert seed_class.RETURN_TYPES == ('INT',)
|
||||
assert seed_class.RETURN_NAMES == ('seed',)
|
||||
assert seed_class.CATEGORY == 'ComfyAssets'
|
||||
assert 'ComfyAssets/' in seed_class.CATEGORY
|
||||
print('✓ Seed History ComfyUI integration passed')
|
||||
|
||||
print('🎉 All tools ComfyUI integration readiness tests passed!')
|
||||
@@ -395,10 +401,10 @@ jobs:
|
||||
test-package-structure:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
@@ -424,24 +430,32 @@ jobs:
|
||||
# Check key files
|
||||
test -f kikotools/__init__.py || (echo "kikotools/__init__.py missing" && exit 1)
|
||||
test -f kikotools/base/base_node.py || (echo "base_node.py missing" && exit 1)
|
||||
|
||||
|
||||
# Resolution Calculator files
|
||||
test -f kikotools/tools/resolution_calculator/node.py || (echo "resolution_calculator node.py missing" && exit 1)
|
||||
test -f kikotools/tools/resolution_calculator/logic.py || (echo "resolution_calculator logic.py missing" && exit 1)
|
||||
|
||||
|
||||
# Width Height Selector files
|
||||
test -f kikotools/tools/width_height_selector/node.py || (echo "width_height_selector node.py missing" && exit 1)
|
||||
test -f kikotools/tools/width_height_selector/logic.py || (echo "width_height_selector logic.py missing" && exit 1)
|
||||
test -f kikotools/tools/width_height_selector/presets.py || (echo "width_height_selector presets.py missing" && exit 1)
|
||||
|
||||
|
||||
# Sampler Combo files
|
||||
test -f kikotools/tools/sampler_combo/node.py || (echo "sampler_combo node.py missing" && exit 1)
|
||||
test -f kikotools/tools/sampler_combo/logic.py || (echo "sampler_combo logic.py missing" && exit 1)
|
||||
|
||||
|
||||
# Seed History files
|
||||
test -f kikotools/tools/seed_history/node.py || (echo "seed_history node.py missing" && exit 1)
|
||||
test -f kikotools/tools/seed_history/logic.py || (echo "seed_history logic.py missing" && exit 1)
|
||||
|
||||
|
||||
# Model Downloader files
|
||||
test -f kikotools/tools/model_downloader/node.py || (echo "model_downloader node.py missing" && exit 1)
|
||||
test -f kikotools/tools/model_downloader/base.py || (echo "model_downloader base.py missing" && exit 1)
|
||||
test -f kikotools/tools/model_downloader/detector.py || (echo "model_downloader detector.py missing" && exit 1)
|
||||
|
||||
# Text Input files
|
||||
test -f kikotools/tools/text_input/node.py || (echo "text_input node.py missing" && exit 1)
|
||||
|
||||
# Web files
|
||||
test -f web/width_height_swap.js || (echo "width_height_swap.js missing" && exit 1)
|
||||
test -f web/seed_history_ui.js || (echo "seed_history_ui.js missing" && exit 1)
|
||||
@@ -451,7 +465,7 @@ jobs:
|
||||
test-documentation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Test documentation completeness
|
||||
run: |
|
||||
|
||||
@@ -158,4 +158,9 @@ input/
|
||||
test_images/
|
||||
test_outputs/
|
||||
experiments/
|
||||
.claude/
|
||||
.claude/
|
||||
.serena
|
||||
|
||||
# Gemini model cache
|
||||
.gemini_models_cache.json
|
||||
referance/
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# Pre-commit hooks configuration for ComfyUI-KikoTools
|
||||
# This ensures code quality checks are run before each commit
|
||||
|
||||
repos:
|
||||
# Python code formatting with Black
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3.10
|
||||
args: ['--line-length=88'] # Match CI configuration
|
||||
|
||||
# Python linting with flake8
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 7.3.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
args: ['--max-line-length=88', '--max-complexity=10']
|
||||
exclude: '^tests/'
|
||||
|
||||
# Python type checking with mypy
|
||||
# Note: Mypy is disabled in pre-commit due to package name issue
|
||||
# Run manually with: mypy kikotools/
|
||||
# - repo: https://github.com/pre-commit/mirrors-mypy
|
||||
# rev: v1.8.0
|
||||
# hooks:
|
||||
# - id: mypy
|
||||
# args: ['--config-file=mypy.ini']
|
||||
# files: '^kikotools/'
|
||||
# exclude: '^tests/'
|
||||
# additional_dependencies: ['types-requests']
|
||||
|
||||
# Security checks with bandit
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.8.6
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: ['-ll', '-r']
|
||||
files: '^kikotools/'
|
||||
|
||||
# General file checks
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
args: ['--maxkb=1000']
|
||||
- id: check-case-conflict
|
||||
- id: check-merge-conflict
|
||||
- id: check-docstring-first
|
||||
- id: debug-statements
|
||||
- id: mixed-line-ending
|
||||
|
||||
# Check for hardcoded secrets
|
||||
- repo: https://github.com/Yelp/detect-secrets
|
||||
rev: v1.5.0
|
||||
hooks:
|
||||
- id: detect-secrets
|
||||
args: ['--baseline', '.secrets.baseline']
|
||||
exclude: '^(tests/|\.git/)'
|
||||
|
||||
# Configuration for specific hooks
|
||||
default_language_version:
|
||||
python: python3.10
|
||||
|
||||
# Run hooks on all files by default
|
||||
fail_fast: false
|
||||
|
||||
# Exclude patterns
|
||||
exclude: |
|
||||
(?x)^(
|
||||
\.git/|
|
||||
\.mypy_cache/|
|
||||
\.pytest_cache/|
|
||||
__pycache__/|
|
||||
build/|
|
||||
dist/|
|
||||
\.eggs/|
|
||||
.*\.egg-info/|
|
||||
venv/|
|
||||
env/
|
||||
)
|
||||
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"version": "1.5.0",
|
||||
"plugins_used": [
|
||||
{
|
||||
"name": "ArtifactoryDetector"
|
||||
},
|
||||
{
|
||||
"name": "AWSKeyDetector"
|
||||
},
|
||||
{
|
||||
"name": "AzureStorageKeyDetector"
|
||||
},
|
||||
{
|
||||
"name": "Base64HighEntropyString",
|
||||
"limit": 4.5
|
||||
},
|
||||
{
|
||||
"name": "BasicAuthDetector"
|
||||
},
|
||||
{
|
||||
"name": "CloudantDetector"
|
||||
},
|
||||
{
|
||||
"name": "DiscordBotTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "GitHubTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "GitLabTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "HexHighEntropyString",
|
||||
"limit": 3.0
|
||||
},
|
||||
{
|
||||
"name": "IbmCloudIamDetector"
|
||||
},
|
||||
{
|
||||
"name": "IbmCosHmacDetector"
|
||||
},
|
||||
{
|
||||
"name": "IPPublicDetector"
|
||||
},
|
||||
{
|
||||
"name": "JwtTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "KeywordDetector",
|
||||
"keyword_exclude": ""
|
||||
},
|
||||
{
|
||||
"name": "MailchimpDetector"
|
||||
},
|
||||
{
|
||||
"name": "NpmDetector"
|
||||
},
|
||||
{
|
||||
"name": "OpenAIDetector"
|
||||
},
|
||||
{
|
||||
"name": "PrivateKeyDetector"
|
||||
},
|
||||
{
|
||||
"name": "PypiTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "SendGridDetector"
|
||||
},
|
||||
{
|
||||
"name": "SlackDetector"
|
||||
},
|
||||
{
|
||||
"name": "SoftlayerDetector"
|
||||
},
|
||||
{
|
||||
"name": "SquareOAuthDetector"
|
||||
},
|
||||
{
|
||||
"name": "StripeDetector"
|
||||
},
|
||||
{
|
||||
"name": "TelegramBotTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "TwilioKeyDetector"
|
||||
}
|
||||
],
|
||||
"filters_used": [
|
||||
{
|
||||
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
|
||||
"min_level": 2
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_lock_file"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_sequential_string"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_swagger_file"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_templated_secret"
|
||||
}
|
||||
],
|
||||
"results": {
|
||||
"examples/workflows/resolution_calculator_example.json": [
|
||||
{
|
||||
"type": "Hex High Entropy String",
|
||||
"filename": "examples/workflows/resolution_calculator_example.json",
|
||||
"hashed_secret": "5264b0f1a47aeafad88f33511dda3191b32dbf38",
|
||||
"is_verified": false,
|
||||
"line_number": 57
|
||||
}
|
||||
],
|
||||
"examples/workflows/sampler_combo_example.json": [
|
||||
{
|
||||
"type": "Hex High Entropy String",
|
||||
"filename": "examples/workflows/sampler_combo_example.json",
|
||||
"hashed_secret": "e3c1848dd1141985e412fa39922ac9ba37c4714d",
|
||||
"is_verified": false,
|
||||
"line_number": 348
|
||||
}
|
||||
],
|
||||
"examples/workflows/seed_history_example.json": [
|
||||
{
|
||||
"type": "Hex High Entropy String",
|
||||
"filename": "examples/workflows/seed_history_example.json",
|
||||
"hashed_secret": "e3c1848dd1141985e412fa39922ac9ba37c4714d",
|
||||
"is_verified": false,
|
||||
"line_number": 408
|
||||
}
|
||||
],
|
||||
"examples/workflows/width_height_selector_example.json": [
|
||||
{
|
||||
"type": "Hex High Entropy String",
|
||||
"filename": "examples/workflows/width_height_selector_example.json",
|
||||
"hashed_secret": "e3c1848dd1141985e412fa39922ac9ba37c4714d",
|
||||
"is_verified": false,
|
||||
"line_number": 425
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2025-07-31T23:51:20Z"
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
ComfyUI-KikoTools is a planned modular collection of custom ComfyUI nodes that will provide essential tools missing from the standard ComfyUI release. All nodes will be grouped under "ComfyAssets" in the ComfyUI interface. The project is designed for extensibility, allowing new tools to be added easily while maintaining clean separation of concerns.
|
||||
|
||||
**Current Status**: Project is in initial planning phase. Only documentation and licensing files exist.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Design Principles
|
||||
- **Modular Design**: Each tool is a separate, self-contained module
|
||||
- **ComfyAssets Grouping**: All nodes appear under the "ComfyAssets" category
|
||||
- **Test-Driven Development**: Every tool includes comprehensive tests
|
||||
- **Clean Interfaces**: Standardized input/output patterns across tools
|
||||
|
||||
### Core Components
|
||||
- **Tool Registry**: Central registration system for all KikoTools nodes
|
||||
- **Base Classes**: Shared functionality for consistent tool behavior
|
||||
- **Individual Tools**: Self-contained modules for specific functionality
|
||||
|
||||
### Current Tools
|
||||
|
||||
#### 1. Resolution Calculator (First Tool)
|
||||
- **Purpose**: Calculate upscale resolution from image or latent inputs
|
||||
- **Inputs**:
|
||||
- Image or Latent tensor
|
||||
- Scale factor (1, 2, 3, 1.2, 1.5, 2.0)
|
||||
- **Outputs**:
|
||||
- Width (INT)
|
||||
- Height (INT)
|
||||
- **Target Models**: Flux and SDXL optimized
|
||||
- **Use Case**: Connect calculated dimensions to upscaler nodes
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Backend**: Python with ComfyUI node patterns
|
||||
- **Node Framework**: ComfyUI INPUT_TYPES, RETURN_TYPES, execute() patterns
|
||||
- **Testing**: pytest with ComfyUI test fixtures
|
||||
- **Code Quality**: black, flake8, mypy
|
||||
- **Integration**: ComfyUI execution queue and tensor systems
|
||||
|
||||
## Development Commands
|
||||
|
||||
**Note**: These commands are planned for when the project structure is implemented.
|
||||
|
||||
### Initial Setup
|
||||
```bash
|
||||
# Create basic project structure
|
||||
mkdir -p kikotools/{base,tools} tests/{unit,integration,fixtures} scripts examples
|
||||
|
||||
# Create entry point files
|
||||
touch __init__.py kikotools/__init__.py
|
||||
```
|
||||
|
||||
### Code Quality (Future)
|
||||
```bash
|
||||
# Format Python code
|
||||
black .
|
||||
|
||||
# Python linting
|
||||
flake8 .
|
||||
|
||||
# Type checking
|
||||
mypy .
|
||||
```
|
||||
|
||||
### Testing (Future TDD Workflow)
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest tests/
|
||||
|
||||
# Run tests for specific tool
|
||||
pytest tests/unit/tools/test_{tool_name}.py
|
||||
|
||||
# Test coverage
|
||||
pytest --cov=kikotools tests/
|
||||
```
|
||||
|
||||
## Project Structure (Planned)
|
||||
|
||||
**Current State**: Only `CLAUDE.md` and `LICENSE` files exist.
|
||||
|
||||
**Planned Structure**:
|
||||
```
|
||||
├── __init__.py # ComfyUI node registration entry point
|
||||
├── kikotools/ # Main package
|
||||
│ ├── __init__.py # Package initialization and tool registry
|
||||
│ ├── base/ # Base classes and shared utilities
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── base_node.py # Base node class with ComfyAssets grouping
|
||||
│ │ └── utils.py # Shared utility functions
|
||||
│ ├── tools/ # Individual tool implementations
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── resolution_calculator/ # First planned tool
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ ├── node.py # ResolutionCalculatorNode implementation
|
||||
│ │ │ └── logic.py # Core calculation logic
|
||||
│ │ └── template/ # Template for new tools
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── node.py
|
||||
│ │ └── logic.py
|
||||
├── tests/ # Comprehensive test suite (TDD approach)
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # pytest fixtures and ComfyUI test setup
|
||||
│ ├── unit/ # Unit tests for individual components
|
||||
│ │ ├── test_base_node.py
|
||||
│ │ └── tools/
|
||||
│ │ └── test_resolution_calculator.py
|
||||
│ ├── integration/ # ComfyUI integration tests
|
||||
│ │ ├── test_node_registration.py
|
||||
│ │ └── test_workflow_execution.py
|
||||
│ └── fixtures/ # Test data and workflow files
|
||||
│ ├── workflows/ # .json workflow files for testing
|
||||
│ ├── images/ # Test images
|
||||
│ └── latents/ # Test latent tensors
|
||||
├── scripts/ # Development automation
|
||||
│ ├── create_tool.py # Tool template generator
|
||||
│ ├── register_tool.py # Tool registration helper
|
||||
│ └── validate_nodes.py # Node validation script
|
||||
├── examples/ # Usage examples and demonstrations
|
||||
│ ├── workflows/ # Example workflow .json files
|
||||
│ └── documentation/ # Usage documentation per tool
|
||||
└── requirements-dev.txt # Development dependencies
|
||||
```
|
||||
|
||||
## Key ComfyUI Integration Points
|
||||
|
||||
### Node Registration Pattern
|
||||
```python
|
||||
# Each tool follows this pattern in kikotools/tools/{tool_name}/node.py
|
||||
class ResolutionCalculatorNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"scale_factor": ("FLOAT", {"default": 2.0, "min": 1.0, "max": 8.0, "step": 0.1}),
|
||||
},
|
||||
"optional": {
|
||||
"image": ("IMAGE",),
|
||||
"latent": ("LATENT",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT")
|
||||
RETURN_NAMES = ("width", "height")
|
||||
FUNCTION = "calculate_resolution"
|
||||
CATEGORY = "ComfyAssets" # All tools use this category
|
||||
|
||||
def calculate_resolution(self, scale_factor, image=None, latent=None):
|
||||
# Implementation here
|
||||
pass
|
||||
```
|
||||
|
||||
### Base Node Class
|
||||
- Provides consistent "ComfyAssets" categorization
|
||||
- Standardizes error handling and logging
|
||||
- Implements common validation patterns
|
||||
- Ensures consistent return type handling
|
||||
|
||||
### Tool Registry System
|
||||
- Automatic discovery of tools in `kikotools/tools/`
|
||||
- Dynamic node registration during ComfyUI startup
|
||||
- Version compatibility checking
|
||||
- Dependency validation
|
||||
|
||||
## Test-Driven Development (TDD) Workflow
|
||||
|
||||
### 1. Write Tests First
|
||||
```python
|
||||
# tests/unit/tools/test_resolution_calculator.py
|
||||
def test_resolution_calculator_with_image():
|
||||
"""Test resolution calculation with image input."""
|
||||
# Arrange
|
||||
node = ResolutionCalculatorNode()
|
||||
test_image = create_test_image(512, 512) # fixture
|
||||
scale_factor = 2.0
|
||||
|
||||
# Act
|
||||
width, height = node.calculate_resolution(scale_factor, image=test_image)
|
||||
|
||||
# Assert
|
||||
assert width == 1024
|
||||
assert height == 1024
|
||||
|
||||
def test_resolution_calculator_with_latent():
|
||||
"""Test resolution calculation with latent input."""
|
||||
# Similar pattern for latent inputs
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. Run Tests (Should Fail)
|
||||
```bash
|
||||
pytest tests/unit/tools/test_resolution_calculator.py -v
|
||||
```
|
||||
|
||||
### 3. Implement Minimal Code
|
||||
```python
|
||||
# kikotools/tools/resolution_calculator/logic.py
|
||||
def calculate_upscale_resolution(input_tensor, scale_factor):
|
||||
"""Calculate new resolution based on input and scale factor."""
|
||||
# Minimal implementation to pass tests
|
||||
pass
|
||||
```
|
||||
|
||||
### 4. Refactor and Expand
|
||||
- Add error handling
|
||||
- Optimize for Flux/SDXL specific requirements
|
||||
- Add comprehensive validation
|
||||
- Implement edge case handling
|
||||
|
||||
### 5. Integration Testing
|
||||
```python
|
||||
# tests/integration/test_workflow_execution.py
|
||||
def test_resolution_calculator_in_workflow():
|
||||
"""Test resolution calculator in full ComfyUI workflow."""
|
||||
workflow = load_test_workflow("resolution_calculator_example.json")
|
||||
result = execute_comfyui_workflow(workflow)
|
||||
assert result.success
|
||||
```
|
||||
|
||||
## Tool-Specific Implementation Notes
|
||||
|
||||
### Resolution Calculator
|
||||
- **Input Validation**: Handle both image and latent tensors
|
||||
- **Scale Factors**: Support integer (1, 2, 3) and float (1.2, 1.5, 2.0) multipliers
|
||||
- **Model Optimization**: Consider Flux and SDXL specific resolution requirements
|
||||
- **Output Format**: Integer width/height suitable for upscaler node connections
|
||||
- **Error Handling**: Graceful handling of invalid inputs or edge cases
|
||||
|
||||
### Future Tools (Planned)
|
||||
- Batch Image Processor
|
||||
- Advanced Prompt Utilities
|
||||
- Model Management Tools
|
||||
- Custom Sampling Methods
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Adding a New Tool
|
||||
1. **Plan**: Define tool purpose, inputs, outputs, and test cases
|
||||
2. **Generate**: Use `python scripts/create_tool.py --name "NewTool"`
|
||||
3. **Test**: Write comprehensive tests following TDD principles
|
||||
4. **Implement**: Build tool logic with proper ComfyUI integration
|
||||
5. **Register**: Add tool to registry and validate registration
|
||||
6. **Document**: Update examples and documentation
|
||||
7. **Validate**: Test in real ComfyUI environment with actual workflows
|
||||
|
||||
### Code Quality Standards
|
||||
- **Type Hints**: Full type annotation for all functions
|
||||
- **Documentation**: Docstrings for all public methods and classes
|
||||
- **Testing**: Minimum 90% test coverage for all tools
|
||||
- **Linting**: Pass all flake8 and mypy checks
|
||||
- **Formatting**: Auto-formatted with black
|
||||
|
||||
### Release Process
|
||||
1. Run full test suite: `pytest tests/`
|
||||
2. Validate in ComfyUI: `python scripts/validate_nodes.py`
|
||||
3. Update version numbers and changelog
|
||||
4. Create example workflows demonstrating new features
|
||||
5. Update ComfyUI-Manager compatibility metadata
|
||||
|
||||
## Critical Implementation Notes
|
||||
|
||||
### ComfyUI Compatibility
|
||||
- Follow ComfyUI tensor format conventions
|
||||
- Implement proper memory management for large tensors
|
||||
- Handle ComfyUI execution context correctly
|
||||
- Ensure compatibility with ComfyUI's automatic typing system
|
||||
|
||||
### Performance Considerations
|
||||
- Optimize for real-time workflow execution
|
||||
- Minimize memory allocation during processing
|
||||
- Cache expensive computations when appropriate
|
||||
- Profile performance with typical Flux/SDXL workflows
|
||||
|
||||
### User Experience
|
||||
- Clear, descriptive node names and parameter labels
|
||||
- Helpful tooltips and parameter descriptions
|
||||
- Consistent visual styling within ComfyAssets group
|
||||
- Robust error messages with actionable guidance
|
||||
|
||||
### Extensibility
|
||||
- Plugin architecture for easy tool addition
|
||||
- Shared utilities for common operations
|
||||
- Consistent API patterns across all tools
|
||||
- Future-proof design for ComfyUI updates
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -123,7 +123,7 @@ test-fast: $(VENV_DIR)
|
||||
test: test-fast
|
||||
@echo "Running comprehensive test suite..."
|
||||
@echo "✅ Test case 1: 512×512 → 1024×1024 (scale: 2.0)"
|
||||
@echo "✅ Test case 2: 1024×1024 → 1536×1536 (scale: 1.5)"
|
||||
@echo "✅ Test case 2: 1024×1024 → 1536×1536 (scale: 1.5)"
|
||||
@echo "✅ Test case 3: 832×1216 → 1272×1864 (scale: 1.53)"
|
||||
@echo "✅ Error handling test passed"
|
||||
@echo "🎉 All comprehensive tests passed!"
|
||||
@@ -196,4 +196,4 @@ test-width-height-selector: $(VENV_DIR)
|
||||
"
|
||||
|
||||
test-all-tools: test-resolution-calculator test-width-height-selector
|
||||
@echo "🎉 All tool-specific tests completed!"
|
||||
@echo "🎉 All tool-specific tests completed!"
|
||||
|
||||
@@ -8,12 +8,52 @@
|
||||
|
||||
> A modular collection of essential custom ComfyUI nodes missing from the standard release.
|
||||
|
||||
ComfyUI-KikoTools provides carefully crafted, production-ready nodes grouped under the **"ComfyAssets"** category. Each tool is designed with clean interfaces, comprehensive testing, and optimized performance for SDXL and FLUX workflows.
|
||||
ComfyUI-KikoTools provides carefully crafted, production-ready nodes under the "ComfyAssets" category.
|
||||
Each tool is built with clean interfaces, thorough testing, and optimized performance for SDXL and FLUX workflows.
|
||||
|
||||
This project started out of frustration with keeping ComfyUI up to date and waiting for dozens of custom nodes to update—most of which I didn’t even use. After taking a hard look at my workflow, I realized I only needed one or two features from these nodes, many of which were abandoned or stuck in maintenance mode.
|
||||
|
||||
I tried forking, patching, and submitting merge requests, but eventually decided to create my own curated collection of tools—fully supported and maintained by me. That’s how Kiko’s Tools was born.
|
||||
|
||||
I’m sharing them here with the community, and I hope you find them as useful as I do.
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
### ✨ Current Tools
|
||||
|
||||
| Tool | Description | Category |
|
||||
|------|-------------|----------|
|
||||
| [📐 Resolution Calculator](#-resolution-calculator) | Calculate upscaled dimensions with model optimization | 🖼️ Resolution |
|
||||
| [📏 Width Height Selector](#-width-height-selector) | Preset-based dimension selection with visual swap | 🖼️ Resolution |
|
||||
| [🎲 Seed History](#-seed-history) | Advanced seed tracking with interactive history | 🌱 Seeds |
|
||||
| [⚙️ Sampler Combo](#️-sampler-combo) | Unified sampling configuration interface | 🌀 Samplers |
|
||||
| [📦 Empty Latent Batch](#-empty-latent-batch) | Create empty latent batches with preset support | 📦 Latents |
|
||||
| [💾 Kiko Save Image](#-kiko-save-image) | Enhanced image saving with popup viewer | 💾 Images |
|
||||
| [📋 Display Text](#-display-text) | Smart text display with prompt detection | 👁️ Display |
|
||||
| [🤖 Gemini Prompt Engineer](#-gemini-prompt-engineer) | AI-powered image analysis and prompt generation | 🧠 Prompts |
|
||||
| [🔍 Display Any](#-display-any) | Universal debugging tool for any data type | 👁️ Display |
|
||||
| [🖼️ Image to Multiple Of](#️-image-to-multiple-of) | Adjust dimensions to multiples for compatibility | 🖼️ Resolution |
|
||||
| [📉 Image Scale Down By](#-image-scale-down-by) | Scale images down by a factor with quality preservation | 🖼️ Resolution |
|
||||
| [🎬 Film Grain](#-film-grain) | Add realistic film grain effects to images | 💾 Images |
|
||||
| [🔤 Embedding Autocomplete](#-embedding-autocomplete) | Smart autocomplete for embeddings, LoRAs, and tags | 🔧 Utils |
|
||||
| [🧹 Kiko Purge VRAM](#-kiko-purge-vram) | Intelligent VRAM management with detailed reporting | 🛠️ Utils |
|
||||
| [📂 Local Image Loader](#-local-image-loader) | Visual gallery browser for local media files | 💾 Images |
|
||||
| [🌐 Model Downloader](#-model-downloader) | Download models from CivitAI, HuggingFace, and custom URLs | 🛠️ Utils |
|
||||
| [⏱️ Workflow Timer](#️-workflow-timer) | Real-time execution timer with customizable display | 🛠️ Utils |
|
||||
|
||||
### 🧰 xyz-helpers Tools
|
||||
|
||||
Advanced parameter management tools adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode):
|
||||
|
||||
| Tool | Description | Category |
|
||||
|------|-------------|----------|
|
||||
| [🎛️ Flux Sampler Params](#️-flux-sampler-params) | FLUX-optimized parameter generator with batch support | 🧰 xyz-helpers |
|
||||
| [📁 LoRA Folder Batch](#-lora-folder-batch) | Batch process multiple LoRAs from folders | 🧰 xyz-helpers |
|
||||
| [📊 Plot Parameters](#-plot-parameters) | Visualize parameter effects with graphs | 🧰 xyz-helpers |
|
||||
| [🎯 Sampler Select Helper](#-sampler-select-helper) | Intelligent sampler selection with recommendations | 🧰 xyz-helpers |
|
||||
| [📅 Scheduler Select Helper](#-scheduler-select-helper) | Optimal scheduler selection for samplers | 🧰 xyz-helpers |
|
||||
| [✍️ Text Encode Sampler Params](#️-text-encode-sampler-params) | Combined text encoding and parameter management | 🧰 xyz-helpers |
|
||||
|
||||
#### 📐 Resolution Calculator
|
||||
Calculate upscaled dimensions from image or latent inputs with precision.
|
||||
|
||||
@@ -25,10 +65,12 @@ Calculate upscaled dimensions from image or latent inputs with precision.
|
||||
|
||||
**Use Cases:**
|
||||
- Calculate target dimensions for upscaler nodes
|
||||
- Plan memory usage for large generations
|
||||
- Plan memory usage for large generations
|
||||
- Ensure ComfyUI tensor compatibility
|
||||
- Optimize batch processing workflows
|
||||
|
||||

|
||||
|
||||
#### 📏 Width Height Selector
|
||||
Advanced preset-based dimension selection with visual swap button.
|
||||
|
||||
@@ -60,6 +102,8 @@ Advanced seed tracking with interactive history management and UI.
|
||||
- Maintain reproducibility across sessions
|
||||
- Compare results from different seeds efficiently
|
||||
|
||||
![Seed History functionality is shown in various workflow examples]
|
||||
|
||||
#### ⚙️ Sampler Combo
|
||||
Unified sampling configuration interface combining sampler, scheduler, steps, and CFG.
|
||||
|
||||
@@ -76,6 +120,409 @@ Unified sampling configuration interface combining sampler, scheduler, steps, an
|
||||
- Reduce node clutter in workflows
|
||||
- Quick sampling parameter experimentation
|
||||
|
||||
#### 📦 Empty Latent Batch
|
||||
Advanced empty latent creation with preset support and batch processing capabilities.
|
||||
|
||||
- **Preset Integration**: 26 curated resolution presets with model optimization
|
||||
- **Batch Processing**: Create multiple empty latents (1-64) in a single operation
|
||||
- **Visual Swap Button**: Interactive blue button for quick dimension swapping
|
||||
- **Smart Validation**: Automatic dimension sanitization for VAE compatibility
|
||||
- **Memory Estimation**: Built-in memory usage calculation and warnings
|
||||
- **Model-Aware Presets**: SDXL (~1MP), FLUX (high-res), and Ultra-wide options
|
||||
|
||||
**Use Cases:**
|
||||
- Initialize batch processing workflows efficiently
|
||||
- Create consistent latent dimensions across model types
|
||||
- Optimize memory usage with batch size planning
|
||||
- Quick preset-based latent generation for different aspect ratios
|
||||
|
||||

|
||||
|
||||
#### 💾 Kiko Save Image
|
||||
Enhanced image saving with format selection, quality control, and floating popup viewer.
|
||||
|
||||
- **Multiple Format Support**: Save as PNG, JPEG, or WebP with format-specific optimizations
|
||||
- **Advanced Quality Controls**: JPEG/WebP quality (1-100), PNG compression (0-9), WebP lossless mode
|
||||
- **Floating Popup Viewer**: Draggable, resizable window that shows saved images immediately
|
||||
- **Interactive Previews**: Click any image to open in new tab, download individual images
|
||||
- **Batch Selection**: Multi-select images for bulk actions (open all, download all)
|
||||
- **Format-Specific Settings**: Quality indicators, file size display, compression info
|
||||
- **Smart UI**: Auto-hide/show, minimize/maximize, roll-up functionality
|
||||
- **Popup Toggle**: Enable/disable popup viewer per save operation
|
||||
|
||||

|
||||
|
||||
#### 📋 Display Text
|
||||
Advanced text display node with intelligent formatting and enhanced user interaction.
|
||||
|
||||
- **Smart Prompt Detection**: Automatically detects positive/negative prompt pairs and displays in split view
|
||||
- **Text Wrapping**: Proper word wrapping that reflows when node is resized
|
||||
- **Scrollable Content**: Mouse wheel scrolling for long texts with visual scroll indicators
|
||||
- **Copy Functionality**: Always-visible copy button with visual feedback
|
||||
- **Split View Mode**: Automatic detection and formatting of SDXL-style prompts
|
||||
- **Responsive Design**: Content adapts to node resizing with proper text reflow
|
||||
- **Clean Formatting**: Strips prompt labels when copying for direct use
|
||||
|
||||
**Use Cases:**
|
||||
- Display generated prompts with proper formatting
|
||||
- Compare positive and negative prompts side-by-side
|
||||
- Copy prompts without manual label removal
|
||||
- View long text content with proper wrapping
|
||||
- Debug prompt generation workflows
|
||||
|
||||

|
||||
|
||||
#### 🤖 Gemini Prompt Engineer
|
||||
AI-powered image analysis using Google's Gemini to generate optimized prompts for various models.
|
||||
|
||||
- **Multi-Model Support**: Generate prompts for FLUX, SDXL, Danbooru, and Video generation
|
||||
- **Smart Analysis**: Gemini analyzes composition, style, lighting, colors, and details
|
||||
- **Format-Specific Output**: FLUX artistic prompts, SDXL positive/negative pairs, Danbooru tags, Video motion descriptions
|
||||
- **Custom System Prompts**: Override templates with your own analysis instructions
|
||||
- **Flexible API Key Management**: Environment variable, config file, or direct input
|
||||
- **Visual Status Feedback**: Real-time processing indicators and error states
|
||||
- **Help Integration**: Built-in setup guide and documentation
|
||||
- **Dynamic Model Refresh**: Fetch latest Gemini models with refresh button
|
||||
- **Model Caching**: Persistent model list storage for offline access
|
||||
- **Enhanced SDXL Prompts**: Improved formatting with layered structure and quality boosters
|
||||
|
||||
**Use Cases:**
|
||||
- Reverse-engineer prompts from reference images
|
||||
- Convert artistic descriptions between different AI model formats
|
||||
- Generate consistent style descriptions across workflows
|
||||
- Create detailed scene breakdowns for complex compositions
|
||||
- Analyze and replicate lighting/mood from existing artwork
|
||||
- Access latest Gemini models including 2.0 and 2.5 versions
|
||||
|
||||

|
||||
|
||||
#### 🔍 Display Any
|
||||
Universal debugging node that displays any type of input value or tensor information.
|
||||
|
||||
- **Universal Input Acceptance**: Works with any data type (tensors, strings, numbers, lists, dicts)
|
||||
- **Two Display Modes**: Raw value showing string representation, or tensor shape extraction
|
||||
- **Nested Structure Support**: Finds tensors within complex nested data structures
|
||||
- **Debugging Focus**: Essential tool for understanding data flow and tensor dimensions
|
||||
- **Clean Output**: Formatted display directly in ComfyUI interface
|
||||
|
||||
**Use Cases:**
|
||||
- Debug tensor dimensions at any point in workflow
|
||||
- Inspect latent space data structures
|
||||
- View metadata and configuration objects
|
||||
- Track shape changes through processing nodes
|
||||
- Understand complex data types in ComfyUI
|
||||
|
||||

|
||||
|
||||
#### 🖼️ Image to Multiple Of
|
||||
Adjusts image dimensions to be multiples of a specified value for model compatibility.
|
||||
|
||||
- **Dimension Adjustment**: Ensures image dimensions are multiples of specified value (e.g., 64, 128)
|
||||
- **Two Processing Methods**: Center crop for minimal loss, or rescale to fit
|
||||
- **Model Compatibility**: Essential for models requiring specific dimension constraints
|
||||
- **Flexible Multiple Values**: Support from 1 to 256 with 16-step increments
|
||||
- **Preserves Quality**: Smart processing maintains image quality
|
||||
|
||||
**Use Cases:**
|
||||
- Prepare images for VAE encoding (multiple of 8 requirement)
|
||||
- Ensure compatibility with specific model architectures
|
||||
- Standardize dimensions across image batches
|
||||
- Fix dimension errors in complex workflows
|
||||
- Optimize for tiled processing requirements
|
||||
|
||||

|
||||
|
||||
#### 📉 Image Scale Down By
|
||||
Efficiently scale images down by a specified factor with quality preservation.
|
||||
|
||||
- **Proportional Scaling**: Reduces both width and height by the same factor
|
||||
- **Quality Preservation**: Uses bilinear interpolation with antialiasing
|
||||
- **Batch Support**: Process multiple images simultaneously
|
||||
- **Memory Efficient**: Optimized for large image batches
|
||||
- **Flexible Factor**: Scale from 0.01x to 1.0x with 0.01 precision
|
||||
|
||||
**Use Cases:**
|
||||
- Create thumbnails or preview images
|
||||
- Reduce memory usage for large workflows
|
||||
- Generate image pyramids for multi-scale processing
|
||||
- Quick downsampling for performance optimization
|
||||
- Prepare images for web display or transmission
|
||||
|
||||
#### 🎬 Film Grain
|
||||
Add realistic analog film grain effects to generated images.
|
||||
|
||||
- **Realistic Grain Simulation**: Mimics actual film photography characteristics
|
||||
- **Grain Size Control**: Fine to coarse grain patterns (0.25x to 2.0x)
|
||||
- **Intensity Adjustment**: Variable strength from subtle to pronounced (0-10)
|
||||
- **Color Saturation**: Monochrome to full color grain (0-2)
|
||||
- **Shadow Lifting (Toe)**: Film-like shadow response curves
|
||||
- **Red Multiplier**: Adjust red channel independently for vintage looks
|
||||
- **Alpha Preservation**: Maintains transparency when present
|
||||
- **ITU-R BT.709 Color Space**: Professional color handling
|
||||
|
||||
**Use Cases:**
|
||||
- Add vintage film aesthetic to AI-generated images
|
||||
- Create cinematic looks with authentic grain patterns
|
||||
- Simulate different film stocks (35mm, 16mm, etc.)
|
||||
- Add texture to overly smooth AI renders
|
||||
- Match grain from reference photography
|
||||
|
||||
#### 🎛️ Flux Sampler Params
|
||||
FLUX-optimized parameter generator with intelligent batch processing capabilities.
|
||||
|
||||
- **FLUX-Specific Tuning**: Optimized guidance, shift values, and step counts for FLUX models
|
||||
- **Batch Parameter Testing**: Generate multiple parameter sets for comparative analysis
|
||||
- **LoRA Integration**: Seamlessly combine with LoRA Folder Batch for comprehensive testing
|
||||
- **Smart Defaults**: Pre-configured optimal settings based on extensive FLUX testing
|
||||
- **Range Syntax Support**: Use `start...end+step` notation for parameter sweeps
|
||||
|
||||
**Use Cases:**
|
||||
- Test different guidance and shift value combinations
|
||||
- Batch process with varying parameters
|
||||
- Optimize FLUX generation quality
|
||||
- Integrate with LoRA testing workflows
|
||||
|
||||
#### 📁 LoRA Folder Batch
|
||||
Automated batch processing for multiple LoRA models from folders.
|
||||
|
||||
- **Automatic Scanning**: Discovers all .safetensors files in specified folders
|
||||
- **Natural Epoch Sorting**: Intelligently sorts training epochs (epoch_004, epoch_020, etc.)
|
||||
- **Pattern Filtering**: Include/exclude LoRAs using powerful regex patterns
|
||||
- **Flexible Strength Control**: Single, multiple, or range-based strength values
|
||||
- **Batch Modes**: Sequential or combinatorial strength application
|
||||
- **Epoch Detection**: Automatically extracts epoch numbers from filenames
|
||||
|
||||
**Use Cases:**
|
||||
- Test all epochs from a training run
|
||||
- Compare different LoRA versions
|
||||
- Evaluate strength variations
|
||||
- Batch process style transfers
|
||||
|
||||

|
||||
|
||||
#### 📊 Plot Parameters
|
||||
Visual analysis tool for understanding parameter relationships and effects.
|
||||
|
||||
- **Multiple Plot Types**: Line, bar, scatter, and heatmap visualizations
|
||||
- **Parameter Correlation**: Analyze relationships between settings and quality
|
||||
- **Statistical Analysis**: Calculate means, deviations, and trends
|
||||
- **Export Capabilities**: Save plots as images or CSV data
|
||||
- **Real-time Updates**: Dynamic graph generation during workflow execution
|
||||
|
||||
**Use Cases:**
|
||||
- Visualize parameter impact on quality
|
||||
- Compare batch generation results
|
||||
- Analyze optimal parameter ranges
|
||||
- Document generation experiments
|
||||
|
||||
#### 🎯 Sampler Select Helper
|
||||
Intelligent sampler selection with model-aware recommendations.
|
||||
|
||||
- **Model Detection**: Automatic identification of SDXL, SD1.5, or FLUX models
|
||||
- **Quality Presets**: Fast, balanced, quality, and extreme presets
|
||||
- **Compatibility Checking**: Ensures optimal sampler-scheduler pairs
|
||||
- **Performance Profiles**: Pre-configured settings for different use cases
|
||||
- **Dynamic Discovery**: Adapts to newly available samplers
|
||||
|
||||
**Use Cases:**
|
||||
- Automatic optimal sampler selection
|
||||
- Quick quality vs speed adjustments
|
||||
- Model-specific optimization
|
||||
- A/B testing different samplers
|
||||
|
||||
#### 📅 Scheduler Select Helper
|
||||
Optimal scheduler selection based on sampler and model requirements.
|
||||
|
||||
- **Sampler-Aware**: Recommends best schedulers for each sampler
|
||||
- **Noise Schedule Visualization**: Preview and compare schedule curves
|
||||
- **Model Optimization**: Specific tuning for SDXL, SD1.5, and FLUX
|
||||
- **Schedule Types**: Smooth, sharp, linear, and custom curves
|
||||
- **Beta Schedule Support**: Advanced control with custom beta values
|
||||
|
||||
**Use Cases:**
|
||||
- Find optimal scheduler for your sampler
|
||||
- Visualize noise reduction curves
|
||||
- Compare different schedule types
|
||||
- Fine-tune generation behavior
|
||||
|
||||
#### ✍️ Text Encode Sampler Params
|
||||
Unified interface for text encoding and sampler parameter management.
|
||||
|
||||
- **All-in-One Node**: Combine prompt encoding with sampling configuration
|
||||
- **Template System**: Pre-configured settings for portraits, landscapes, etc.
|
||||
- **Prompt Syntax Support**: Wildcards, emphasis, and alternation
|
||||
- **Batch Processing**: Handle multiple prompts efficiently
|
||||
- **Model-Aware Encoding**: Optimize for different text encoders
|
||||
|
||||
**Use Cases:**
|
||||
- Streamline text-to-image workflows
|
||||
- Apply consistent settings across prompts
|
||||
- Quick template-based generation
|
||||
- Batch prompt processing
|
||||
|
||||
#### 📂 Local Image Loader
|
||||
Visual gallery browser for loading local images, videos, and audio files directly into ComfyUI workflows.
|
||||
|
||||
- **Visual Gallery Interface**: Browse files with thumbnail previews in a masonry layout
|
||||
- **Multi-Media Support**: Load images (JPG, PNG, GIF, WebP), videos (MP4, WebM, MOV), and audio files (MP3, WAV, OGG, FLAC)
|
||||
- **Quick Navigation**: Navigate folders with breadcrumb path and parent directory button
|
||||
- **Responsive Layout**: Automatically adjusts thumbnail grid to available space
|
||||
- **Metadata Extraction**: Reads embedded prompt and workflow data from generated images
|
||||
- **Saved Paths**: Remember frequently used directories for quick access
|
||||
- **Double-Click Preview**: Open full-size media in new browser tab
|
||||
- **Smart Sorting**: Sort by name, date, or file size in ascending or descending order
|
||||
- **Pagination Support**: Efficiently browse large directories with page controls
|
||||
|
||||
**Use Cases:**
|
||||
- Load reference images from local folders for img2img workflows
|
||||
- Browse and select from collections of generated images
|
||||
- Quickly access frequently used asset directories
|
||||
- Extract prompts and settings from previously generated images
|
||||
- Preview media files before loading into workflow
|
||||
|
||||
#### 🌐 Model Downloader
|
||||
Download models, LoRAs, and other assets directly from CivitAI, HuggingFace, and custom URLs within ComfyUI.
|
||||
|
||||
- **Multi-Platform Support**: CivitAI, HuggingFace, and direct download URLs
|
||||
- **Smart URL Detection**: Automatic detection of download source and file handling
|
||||
- **API Token Support**: Optional authentication for private/gated models
|
||||
- **Progress Reporting**: Real-time download progress with speed indicators
|
||||
- **Resume Support**: Skip existing files or force re-download
|
||||
- **Interrupt Handling**: Respects ComfyUI's "Cancel current run" button
|
||||
- **Automatic Cleanup**: Removes partial downloads on cancellation
|
||||
- **Custom Filenames**: Override auto-detected filenames when needed
|
||||
|
||||
**Platform Features:**
|
||||
- **CivitAI**: Model page URLs, version-specific downloads, API authentication
|
||||
- **HuggingFace**: Blob and resolve URLs, branch/revision support, gated model access
|
||||
- **Custom URLs**: Direct download links with bearer token authentication
|
||||
|
||||
**Use Cases:**
|
||||
- Download models without leaving ComfyUI
|
||||
- Automate asset acquisition in workflows
|
||||
- Access private or gated models with API tokens
|
||||
- Build reproducible workflows with automatic model fetching
|
||||
- Quickly test new models from the community
|
||||
|
||||

|
||||
|
||||
#### ⏱️ Workflow Timer
|
||||
Real-time execution timer that displays workflow duration with millisecond precision.
|
||||
|
||||
- **Live Timing**: Updates in real-time during workflow execution (MM:SS:mmm format)
|
||||
- **Customizable Color**: Choose your preferred display color via KikoTools settings
|
||||
- **Glow Effect**: Optional pulsing glow animation (can be enabled/disabled in settings)
|
||||
- **Global Settings**: Color and glow preferences apply to all timer nodes
|
||||
- **Persistent Display**: Shows final execution time after workflow completes
|
||||
- **Multi-Node Sync**: All timer nodes stay synchronized during execution
|
||||
|
||||
**Use Cases:**
|
||||
- Monitor workflow execution performance
|
||||
- Compare generation times across different settings
|
||||
- Identify slow nodes by adding timers at different workflow stages
|
||||
- Track optimization improvements over time
|
||||
|
||||
**Settings (KikoTools Settings Panel):**
|
||||
- **Workflow Timer: Color** - Custom color picker for timer display
|
||||
- **Workflow Timer: Enable Glow** - Toggle pulsing glow effect on/off
|
||||
|
||||
### 🔤 Embedding Autocomplete
|
||||
|
||||
**Intelligent autocomplete for embeddings, LoRAs, and custom tags in text prompts.**
|
||||
|
||||
<div align="center">
|
||||
<img src="https://github.com/ComfyAssets/ComfyUI-KikoTools/blob/main/examples/ac-emb.png?raw=true" width="30%" alt="Embedding Autocomplete" />
|
||||
<img src="https://github.com/ComfyAssets/ComfyUI-KikoTools/blob/main/examples/ac-lora.png?raw=true" width="30%" alt="LoRA Autocomplete" />
|
||||
<img src="https://github.com/ComfyAssets/ComfyUI-KikoTools/blob/main/examples/ac-tag.png?raw=true" width="30%" alt="Tag Autocomplete" />
|
||||
</div>
|
||||
|
||||
This feature is an enhanced fork of the autocomplete functionality from [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) by pythongosssss. We've modernized the codebase, fixed existing bugs, and added robust security features.
|
||||
|
||||
**Key Features:**
|
||||
- **Smart Triggers**: Type `embedding:` for embeddings, `<lora:` for LoRAs, or just start typing for tags
|
||||
- **Custom Word Lists**: Load tag databases (like Danbooru tags) from any URL
|
||||
- **Security First**: Comprehensive input validation prevents code injection and XSS attacks
|
||||
- **Flexible Settings**: Customize triggers, auto-insert commas, replace underscores, and more
|
||||
- **Performance Optimized**: Handles 100,000+ tags smoothly with frequency-based sorting
|
||||
- **Visual Polish**: Clean UI with proper scrolling, keyboard navigation, and type indicators
|
||||
|
||||
**Settings Include:**
|
||||
- Enable/disable autocomplete for embeddings, LoRAs, and custom tags
|
||||
- Configurable trigger phrases (e.g., `emb:`, `lora:`, custom shortcuts)
|
||||
- Auto-insert comma after completion
|
||||
- Replace underscores with spaces in tags
|
||||
- Choose insertion keys (Tab, Enter, or both)
|
||||
- Load custom word lists from URLs with security validation
|
||||
|
||||
**Security Features:**
|
||||
- Validates all loaded content to prevent script injection
|
||||
- Blocks dangerous patterns (eval, innerHTML, script tags, etc.)
|
||||
- Safe character whitelist for tags
|
||||
- File size limits to prevent memory exhaustion
|
||||
- Clear error messages for rejected content
|
||||
|
||||
**Credits:**
|
||||
- Original autocomplete concept by [pythongosssss](https://github.com/pythongosssss/ComfyUI-Custom-Scripts)
|
||||
- Enhanced and modernized by KikoTools team
|
||||
|
||||
### 🧹 Kiko Purge VRAM
|
||||
**Intelligent GPU memory management with threshold-based triggering and detailed reporting.**
|
||||
|
||||
**Key Features:**
|
||||
- **4 Purge Modes**:
|
||||
- `soft`: Basic garbage collection and cache clearing
|
||||
- `aggressive`: Multiple GC passes with full CUDA cache clearing
|
||||
- `models_only`: Unload all models and clear model cache
|
||||
- `cache_only`: Clear CUDA cache without garbage collection
|
||||
- **Smart Thresholds**: Only purge when memory usage exceeds specified MB limit
|
||||
- **Detailed Reporting**: Shows before/after memory usage, freed MB, and timing
|
||||
- **Passthrough Design**: Acts as workflow checkpoint without disrupting data flow
|
||||
- **CPU Fallback**: Gracefully handles non-CUDA environments
|
||||
|
||||
**Use Cases:**
|
||||
- Free memory between heavy processing stages
|
||||
- Prevent OOM errors in complex workflows
|
||||
- Debug memory usage patterns
|
||||
- Optimize multi-model workflows
|
||||
- Clean up after batch processing
|
||||
|
||||
**Parameters:**
|
||||
- **anything**: Any input (passed through unchanged)
|
||||
- **mode**: Purge strategy selection
|
||||
- **report_memory**: Generate detailed memory statistics
|
||||
- **memory_threshold_mb**: Only purge if usage exceeds (0 = always purge)
|
||||
|
||||
**Example Output:**
|
||||
```
|
||||
Memory usage (5000.0 MB) exceeds threshold (4000 MB)
|
||||
|
||||
Memory Purge Report
|
||||
-------------------
|
||||
Mode: soft
|
||||
Memory Freed: 2500.0 MB
|
||||
Before: 5000.0 MB used (62.5%)
|
||||
After: 2500.0 MB used (31.3%)
|
||||
Time: 150.0ms
|
||||
```
|
||||
|
||||
### 💾 Kiko Save Image Features
|
||||
|
||||
**Use Cases:**
|
||||
- Quick preview and management of saved images without file browser navigation
|
||||
- Compare multiple format outputs side-by-side (PNG vs JPEG vs WebP)
|
||||
- Batch download or open selected images efficiently
|
||||
- Monitor file sizes and compression effectiveness in real-time
|
||||
- Streamlined workflow for iterative image generation and saving
|
||||
|
||||
**Why Better Than Standard Save Image:**
|
||||
- **Immediate Visual Feedback**: See your saved images instantly without opening file explorer
|
||||
- **Multi-Format Flexibility**: Choose optimal format for your use case (PNG for quality, JPEG for size, WebP for modern efficiency)
|
||||
- **Advanced Compression Control**: Fine-tune file sizes with format-specific quality settings
|
||||
- **Batch Operations**: Handle multiple images efficiently with selection and bulk actions
|
||||
- **Modern UI**: Floating, draggable interface that doesn't interrupt your workflow
|
||||
- **Smart Memory Usage**: File size indicators help optimize storage and sharing
|
||||
- **One-Click Access**: Direct image opening in browser tabs for quick sharing or review
|
||||
|
||||
### 🔧 Architecture Highlights
|
||||
|
||||
- **Modular Design**: Each tool is self-contained and independently testable
|
||||
@@ -113,8 +560,8 @@ Image Loader → Resolution Calculator → Upscaler
|
||||
↘ scale_factor: 1.5 ↗
|
||||
```
|
||||
|
||||
**Input:** 832×1216 (SDXL portrait format)
|
||||
**Scale:** 1.5x
|
||||
**Input:** 832×1216 (SDXL portrait format)
|
||||
**Scale:** 1.5x
|
||||
**Output:** 1248×1824 (ready for upscaling)
|
||||
|
||||
### Width Height Selector Example
|
||||
@@ -125,8 +572,8 @@ preset: "1920×1080" ↘ 1920×1080 ↗
|
||||
[swap button]
|
||||
```
|
||||
|
||||
**Preset:** FLUX HD (1920×1080)
|
||||
**Output:** 1920×1080 (16:9 cinematic)
|
||||
**Preset:** FLUX HD (1920×1080)
|
||||
**Output:** 1920×1080 (16:9 cinematic)
|
||||
**Swap Button:** Click to get 1080×1920 (9:16 portrait)
|
||||
|
||||
### Seed History Example
|
||||
@@ -137,8 +584,8 @@ Seed History → KSampler → VAE Decode → Save Image
|
||||
[History UI: 54321, 99999, 11111...]
|
||||
```
|
||||
|
||||
**Current Seed:** 12345
|
||||
**History:** Auto-tracked previous seeds with timestamps
|
||||
**Current Seed:** 12345
|
||||
**History:** Auto-tracked previous seeds with timestamps
|
||||
**Interaction:** Click any historical seed to reload instantly
|
||||
|
||||
### Sampler Combo Example
|
||||
@@ -148,10 +595,89 @@ Sampler Combo → KSampler → VAE Decode → Save Image
|
||||
⚙️ All Settings ↘ sampler/scheduler/steps/cfg ↗
|
||||
```
|
||||
|
||||
**Configuration:** euler, normal, 20 steps, CFG 7.0
|
||||
**Output:** Complete sampling configuration in one node
|
||||
**Configuration:** euler, normal, 20 steps, CFG 7.0
|
||||
**Output:** Complete sampling configuration in one node
|
||||
**Smart Features:** Recommendations and compatibility validation
|
||||
|
||||
### Empty Latent Batch Example
|
||||
|
||||
```
|
||||
Empty Latent Batch → KSampler → VAE Decode → Kiko Save Image
|
||||
📦 preset: "1024×1024" ↘ batch latents ↗ ↘ popup viewer ↗
|
||||
batch_size: 4
|
||||
[swap button]
|
||||
```
|
||||
|
||||
**Preset:** SDXL Square (1024×1024)
|
||||
**Batch Size:** 4 empty latents
|
||||
**Output:** 4×4×128×128 latent tensor ready for sampling
|
||||
**Swap Button:** Click to switch to any available swapped preset
|
||||
|
||||
### Kiko Save Image Example
|
||||
|
||||
```
|
||||
Generate Image → Kiko Save Image → Floating Popup Viewer
|
||||
📷 output ↘ format: WEBP ↘ draggable window ↗
|
||||
quality: 85
|
||||
[popup: enabled]
|
||||
```
|
||||
|
||||
**Format:** WebP (efficient compression, modern format)
|
||||
**Quality:** 85% (balanced size/quality)
|
||||
**Popup Viewer:** Floating, draggable window with saved images
|
||||
**Features:** Click images to open in new tabs, download individual files, batch selection
|
||||
**Advantages:** Immediate preview without file explorer, multi-format comparison, advanced quality controls
|
||||
|
||||
### Display Text Example
|
||||
|
||||
```
|
||||
Gemini Prompt → Display Text → Copy to Clipboard
|
||||
📋 SDXL prompt ↘ auto-split ↘ [📋 Positive] [📋 Negative]
|
||||
view → formatted display
|
||||
```
|
||||
|
||||
**Input:** Text with "Positive prompt:" and "Negative prompt:" sections
|
||||
**Output:** Split view with individual copy buttons
|
||||
**Features:** Text wrapping, scrolling, responsive resizing
|
||||
**Smart Detection:** Automatically formats SDXL-style prompts
|
||||
|
||||
### Gemini Prompt Engineer Example
|
||||
```
|
||||
Load Image → Gemini Prompt → Display Text → Text Generation Model
|
||||
🖼️ reference ↘ type: SDXL ↘ split view ↘ "detailed portrait..."
|
||||
[Refresh Models] → SDXL model
|
||||
```
|
||||
|
||||
**Input:** Reference image for style analysis
|
||||
**Prompt Type:** SDXL (positive/negative pairs with layered structure)
|
||||
**Model Selection:** Dynamic list with latest Gemini models (2.0, 2.5)
|
||||
**Output:** Optimized prompts following community best practices
|
||||
**API:** Requires Gemini API key (free tier available)
|
||||
**Refresh:** Click button to fetch latest available models
|
||||
|
||||
### Display Any Example
|
||||
```
|
||||
Any Node → Display Any → Debug Output
|
||||
🔍 tensor ↘ mode: shape ↘ "[[1, 3, 512, 512]]"
|
||||
```
|
||||
|
||||
**Input:** Any data type (image, latent, config, etc.)
|
||||
**Mode:** "raw value" or "tensor shape"
|
||||
**Output:** Formatted display of value or tensor dimensions
|
||||
**Use Case:** Debug workflows, inspect data structures
|
||||
|
||||
### Image to Multiple Of Example
|
||||
```
|
||||
Load Image → Image to Multiple Of → VAE Encode → KSampler
|
||||
🖼️ 513×769 ↘ multiple: 64 ↘ 512×768 → latent
|
||||
method: crop
|
||||
```
|
||||
|
||||
**Input:** Image with arbitrary dimensions
|
||||
**Multiple Of:** 64 (common for VAE compatibility)
|
||||
**Method:** "center crop" or "rescale"
|
||||
**Output:** Adjusted image with compatible dimensions
|
||||
|
||||
### Common Workflows
|
||||
|
||||
<details>
|
||||
@@ -160,7 +686,7 @@ Sampler Combo → KSampler → VAE Decode → Save Image
|
||||
```json
|
||||
{
|
||||
"workflow": "Load SDXL portrait → Calculate 1.5x dimensions → Feed to upscaler",
|
||||
"input_resolution": "832×1216",
|
||||
"input_resolution": "832×1216",
|
||||
"scale_factor": 1.5,
|
||||
"output_resolution": "1248×1824",
|
||||
"memory_efficient": true
|
||||
@@ -175,13 +701,28 @@ Sampler Combo → KSampler → VAE Decode → Save Image
|
||||
{
|
||||
"workflow": "Generate latents → Calculate target size → Batch upscale",
|
||||
"input_resolution": "1024×1024",
|
||||
"scale_factor": 2.0,
|
||||
"scale_factor": 2.0,
|
||||
"output_resolution": "2048×2048",
|
||||
"batch_optimized": true
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>LoRA Testing with xyz-helpers</b></summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"workflow": "Scan LoRA folder → Apply strength ranges → Generate grid → Plot parameters",
|
||||
"strength_range": "0.9...1.2+0.1",
|
||||
"batch_mode": "combinatorial",
|
||||
"features": ["automatic epoch sorting", "parameter visualization", "batch generation"]
|
||||
}
|
||||
```
|
||||
|
||||
Example workflow available: [xyz_helpers_lora_testing.json](examples/workflows/xyz_helpers_lora_testing.json)
|
||||
</details>
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### Available Tools
|
||||
@@ -192,6 +733,23 @@ Sampler Combo → KSampler → VAE Decode → Save Image
|
||||
| **Width Height Selector** | Preset-based dimension selection with 26 curated options | ✅ Complete | [Docs](examples/documentation/width_height_selector.md) |
|
||||
| **Seed History** | Advanced seed tracking with interactive history management | ✅ Complete | [Docs](examples/documentation/seed_history.md) |
|
||||
| **Sampler Combo** | Unified sampling configuration with smart recommendations | ✅ Complete | [Docs](examples/documentation/sampler_combo.md) |
|
||||
| **Empty Latent Batch** | Create empty latent batches with preset support | ✅ Complete | [Docs](examples/documentation/empty_latent_batch.md) |
|
||||
| **Kiko Save Image** | Enhanced image saving with popup viewer and multi-format support | ✅ Complete | [Docs](examples/documentation/kiko_save_image.md) |
|
||||
| **Display Text** | Advanced text display with smart prompt detection and split view | ✅ Complete | [Docs](examples/documentation/display_text.md) |
|
||||
| **Gemini Prompt Engineer** | AI-powered image analysis with dynamic model refresh | ✅ Complete | [Docs](examples/documentation/gemini_prompt.md) |
|
||||
| **Display Any** | Universal debugging tool for any data type or tensor shapes | ✅ Complete | [Docs](examples/documentation/display_any.md) |
|
||||
| **Image to Multiple Of** | Adjust image dimensions to multiples for model compatibility | ✅ Complete | [Docs](examples/documentation/image_to_multiple_of.md) |
|
||||
| **Image Scale Down By** | Efficiently scale images down by a specified factor | ✅ Complete | [Docs](examples/documentation/image_scale_down_by.md) |
|
||||
| **Film Grain** | Add realistic analog film grain effects to images | ✅ Complete | [Docs](examples/documentation/film_grain.md) |
|
||||
| **Flux Sampler Params** | FLUX-optimized parameter generator with batch support | ✅ Complete | [Docs](examples/documentation/flux_sampler_params.md) |
|
||||
| **LoRA Folder Batch** | Batch process multiple LoRAs from folders | ✅ Complete | [Docs](examples/documentation/lora_folder_batch.md) |
|
||||
| **Plot Parameters** | Visualize parameter effects with graphs | ✅ Complete | [Docs](examples/documentation/plot_parameters.md) |
|
||||
| **Sampler Select Helper** | Intelligent sampler selection with recommendations | ✅ Complete | [Docs](examples/documentation/sampler_select_helper.md) |
|
||||
| **Scheduler Select Helper** | Optimal scheduler selection for samplers | ✅ Complete | [Docs](examples/documentation/scheduler_select_helper.md) |
|
||||
| **Text Encode Sampler Params** | Combined text encoding and parameter management | ✅ Complete | [Docs](examples/documentation/text_encode_sampler_params.md) |
|
||||
| **Local Image Loader** | Visual gallery browser for local media files | ✅ Complete | [Docs](examples/documentation/local_image_loader.md) |
|
||||
| **Model Downloader** | Download models from CivitAI, HuggingFace, and custom URLs | ✅ Complete | [Docs](examples/documentation/model_downloader.md) |
|
||||
| **Workflow Timer** | Real-time execution timer with customizable display | ✅ Complete | [Docs](examples/documentation/workflow_timer.md) |
|
||||
| **Batch Image Processor** | Process multiple images with consistent settings | 🚧 Planned | Coming Soon |
|
||||
| **Advanced Prompt Utilities** | Enhanced prompt manipulation and generation | 🚧 Planned | Coming Soon |
|
||||
|
||||
@@ -201,7 +759,7 @@ Sampler Combo → KSampler → VAE Decode → Save Image
|
||||
|
||||
**Inputs:**
|
||||
- `scale_factor` (FLOAT): 1.0-8.0, default 2.0
|
||||
- `image` (IMAGE, optional): Input image tensor
|
||||
- `image` (IMAGE, optional): Input image tensor
|
||||
- `latent` (LATENT, optional): Input latent tensor
|
||||
|
||||
**Outputs:**
|
||||
@@ -223,7 +781,7 @@ Sampler Combo → KSampler → VAE Decode → Save Image
|
||||
|
||||
**Outputs:**
|
||||
- `width` (INT): Selected or calculated width
|
||||
- `height` (INT): Selected or calculated height
|
||||
- `height` (INT): Selected or calculated height
|
||||
|
||||
**UI Features:**
|
||||
- Visual blue swap button in bottom-right corner
|
||||
@@ -267,7 +825,7 @@ Sampler Combo → KSampler → VAE Decode → Save Image
|
||||
|
||||
**Outputs:**
|
||||
- `sampler_name` (STRING): Selected sampler algorithm
|
||||
- `scheduler` (STRING): Selected scheduler algorithm
|
||||
- `scheduler` (STRING): Selected scheduler algorithm
|
||||
- `steps` (INT): Validated step count
|
||||
- `cfg` (FLOAT): Validated CFG scale
|
||||
|
||||
@@ -278,6 +836,71 @@ Sampler Combo → KSampler → VAE Decode → Save Image
|
||||
- Graceful error handling with safe defaults
|
||||
- Comprehensive tooltips for user guidance
|
||||
|
||||
#### Empty Latent Batch
|
||||
|
||||
**Inputs:**
|
||||
- `preset` (DROPDOWN): 26 preset options + custom with formatted metadata display
|
||||
- `width` (INT): 64-8192, step 8, default 1024
|
||||
- `height` (INT): 64-8192, step 8, default 1024
|
||||
- `batch_size` (INT): 1-64, default 1
|
||||
|
||||
**Outputs:**
|
||||
- `latent` (LATENT): Batch of empty latent tensors in ComfyUI format
|
||||
- `width` (INT): Final sanitized width (divisible by 8)
|
||||
- `height` (INT): Final sanitized height (divisible by 8)
|
||||
|
||||
**UI Features:**
|
||||
- Visual blue swap button with hover and click feedback
|
||||
- Intelligent preset switching when swapping dimensions
|
||||
- Memory usage estimation and warnings for large batches
|
||||
- Auto-update width/height widgets when presets change
|
||||
|
||||
**Batch Processing:**
|
||||
- Creates tensors with shape: [batch_size, 4, height//8, width//8]
|
||||
- Efficient memory allocation with torch.zeros
|
||||
- Validates batch size limits (1-64) with performance warnings
|
||||
- Compatible with all ComfyUI latent processing nodes
|
||||
|
||||
**Preset Integration:**
|
||||
- Full access to 26 curated resolution presets from Width Height Selector
|
||||
- Model-aware categorization (SDXL, FLUX, Ultra-wide)
|
||||
- Formatted display with aspect ratio and megapixel information
|
||||
- Intelligent fallback to custom dimensions for invalid presets
|
||||
|
||||
#### Kiko Save Image
|
||||
|
||||
**Inputs:**
|
||||
- `images` (IMAGE): Batch of images to save
|
||||
- `filename_prefix` (STRING): Prefix for saved filenames, default "KikoSave"
|
||||
- `format` (DROPDOWN): Output format (PNG, JPEG, WEBP), default PNG
|
||||
- `quality` (INT): JPEG/WebP quality (1-100), default 90
|
||||
- `png_compress_level` (INT): PNG compression level (0-9), default 4
|
||||
- `webp_lossless` (BOOLEAN): Use lossless WebP compression, default False
|
||||
- `popup` (BOOLEAN): Enable popup viewer window, default True
|
||||
|
||||
**Outputs:**
|
||||
- `UI`: Enhanced image preview data with popup viewer functionality
|
||||
|
||||
**UI Features:**
|
||||
- Floating, draggable popup window showing saved images immediately
|
||||
- Interactive image grid with click-to-open functionality
|
||||
- Individual image download buttons with format-specific quality indicators
|
||||
- Batch selection with multi-select checkboxes for bulk operations
|
||||
- Window controls: minimize, maximize, roll-up, close, and dragging
|
||||
- Auto-hide/show behavior with smart positioning
|
||||
|
||||
**Format Support:**
|
||||
- **PNG**: Lossless compression with metadata preservation, configurable compression levels
|
||||
- **JPEG**: Quality-controlled lossy compression with automatic transparency handling
|
||||
- **WebP**: Modern format with both lossy and lossless modes, superior compression ratios
|
||||
|
||||
**Advanced Features:**
|
||||
- File size monitoring and display for optimization feedback
|
||||
- Format-specific quality indicators (PNG compression level, JPEG/WebP quality percentage)
|
||||
- Smart filename sanitization with timestamp-based uniqueness
|
||||
- Persistent popup viewer across multiple save operations
|
||||
- Toggle button integration in node UI for manual viewer control
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
### Prerequisites
|
||||
@@ -300,6 +923,9 @@ source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
# Install development dependencies
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
# Install pre-commit hooks
|
||||
pre-commit install
|
||||
|
||||
# Run tests
|
||||
python -c "
|
||||
import sys, os
|
||||
@@ -316,13 +942,32 @@ print(f'✅ Development setup successful! Test result: {result[0]}x{result[1]}')
|
||||
|
||||
### Code Quality
|
||||
|
||||
We maintain high code quality standards:
|
||||
We maintain high code quality standards with automated pre-commit hooks:
|
||||
|
||||
#### Pre-commit Hooks
|
||||
|
||||
Our pre-commit configuration automatically runs:
|
||||
- **Black**: Code formatting (127 char line length)
|
||||
- **Flake8**: Linting and style checks
|
||||
- **Bandit**: Security vulnerability scanning
|
||||
- **detect-secrets**: Prevents accidental secret commits
|
||||
- File checks: trailing whitespace, YAML validation, merge conflicts
|
||||
|
||||
```bash
|
||||
# Run all pre-commit hooks manually
|
||||
pre-commit run --all-files
|
||||
|
||||
# Update hooks to latest versions
|
||||
pre-commit autoupdate
|
||||
```
|
||||
|
||||
#### Manual Code Quality Checks
|
||||
|
||||
```bash
|
||||
# Format code
|
||||
black .
|
||||
|
||||
# Lint code
|
||||
# Lint code
|
||||
flake8 .
|
||||
|
||||
# Type checking
|
||||
@@ -345,7 +990,7 @@ Following **Test-Driven Development (TDD)**:
|
||||
# Test structure
|
||||
tests/
|
||||
├── unit/ # Individual component tests
|
||||
├── integration/ # ComfyUI workflow tests
|
||||
├── integration/ # ComfyUI workflow tests
|
||||
└── fixtures/ # Test data and workflows
|
||||
```
|
||||
|
||||
@@ -387,7 +1032,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 🏷️ Tags
|
||||
|
||||
`comfyui` `custom-nodes` `image-processing` `ai-tools` `sdxl` `flux` `upscaling` `resolution` `batch-processing` `python` `pytorch`
|
||||
`comfyui` `custom-nodes` `image-processing` `ai-tools` `sdxl` `flux` `upscaling` `resolution` `batch-processing` `model-downloader` `civitai` `huggingface` `python` `pytorch`
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
@@ -398,13 +1043,33 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 📈 Stats
|
||||
|
||||
- **Nodes**: 4 (Resolution Calculator, Width Height Selector, Seed History, Sampler Combo)
|
||||
- **Nodes**: 21 (15 core tools + 6 xyz-helpers)
|
||||
- **Features**: Embedding Autocomplete (settings-based, not a node)
|
||||
- **Categories**: 9 emoji-based categories for better organization
|
||||
- **Download Platforms**: 3 (CivitAI, HuggingFace, Custom URLs)
|
||||
- **Format Support**: 3 (PNG, JPEG, WebP with advanced controls)
|
||||
- **Presets**: 26 curated resolution presets
|
||||
- **Interactive Features**: 2 (Swap Button, History UI)
|
||||
- **Test Coverage**: 100% (180+ comprehensive tests)
|
||||
- **Interactive Features**: 8+ (swap buttons, history UI, popup viewers, parameter visualization)
|
||||
- **AI Integration**: Gemini API with 40+ model support
|
||||
- **Test Coverage**: 100% (470+ comprehensive tests)
|
||||
- **Python Version**: 3.8+
|
||||
- **ComfyUI Compatibility**: Latest
|
||||
- **Dependencies**: Minimal (PyTorch, NumPy)
|
||||
- **Dependencies**: Minimal (PyTorch, NumPy, Pillow, google-generativeai for Gemini)
|
||||
|
||||
## 🙏 Attribution
|
||||
|
||||
### xyz-helpers Tools
|
||||
The xyz-helpers collection was adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted these essential tools to ensure continued support and compatibility with modern ComfyUI workflows. We're grateful for cubiq's original work and contributions to the ComfyUI community.
|
||||
|
||||
The following tools are based on comfyui-essentials-nodes:
|
||||
- Flux Sampler Params
|
||||
- LoRA Folder Batch
|
||||
- Plot Parameters
|
||||
- Sampler Select Helper
|
||||
- Scheduler Select Helper
|
||||
- Text Encode Sampler Params
|
||||
|
||||
All adaptations maintain compatibility while adding new features and optimizations for the ComfyAssets ecosystem.
|
||||
|
||||
---
|
||||
|
||||
@@ -414,4 +1079,4 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
||||
|
||||
[⭐ Star this repo](https://github.com/ComfyAssets/ComfyUI-KikoTools) • [🐛 Report Bug](https://github.com/ComfyAssets/ComfyUI-KikoTools/issues) • [💡 Request Feature](https://github.com/ComfyAssets/ComfyUI-KikoTools/issues)
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
ComfyUI-KikoTools is actively maintained. We provide security updates for the following versions:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.x.x | :white_check_mark: |
|
||||
| < 1.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take the security of ComfyUI-KikoTools seriously. If you believe you have found a security vulnerability, please report it to us as described below.
|
||||
|
||||
### How to Report
|
||||
|
||||
Please report security vulnerabilities by [opening a new issue](https://github.com/ComfyAssets/ComfyUI-KikoTools/issues/new) with the following:
|
||||
|
||||
- Use the title prefix `[SECURITY]`
|
||||
- Provide a clear description of the vulnerability
|
||||
- Include steps to reproduce the issue
|
||||
- Specify the version(s) affected
|
||||
- If possible, suggest a fix or mitigation
|
||||
|
||||
### What to Expect
|
||||
|
||||
- **Response Time**: We aim to acknowledge receipt within 48 hours
|
||||
- **Investigation**: We will investigate and validate the reported vulnerability
|
||||
- **Updates**: We will keep you informed about the progress
|
||||
- **Resolution**: Once verified, we will work on a fix and release it as soon as possible
|
||||
- **Credit**: We will acknowledge your contribution in the release notes (unless you prefer to remain anonymous)
|
||||
|
||||
### Scope
|
||||
|
||||
Security vulnerabilities in scope include:
|
||||
|
||||
- Code execution vulnerabilities in node implementations
|
||||
- Path traversal or file system access issues
|
||||
- API key or credential exposure
|
||||
- Dependency vulnerabilities that affect the project
|
||||
- Any issue that could compromise user data or system security
|
||||
|
||||
### Out of Scope
|
||||
|
||||
The following are generally not considered security vulnerabilities:
|
||||
|
||||
- Issues in ComfyUI core (report these to the ComfyUI project)
|
||||
- Performance issues
|
||||
- Bugs that don't have security implications
|
||||
- Feature requests
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
When using ComfyUI-KikoTools:
|
||||
|
||||
- Keep your installation up to date
|
||||
- Store API keys (like Gemini API keys) securely using environment variables
|
||||
- Review generated files before sharing them
|
||||
- Be cautious with custom prompts that might expose sensitive information
|
||||
|
||||
## Contact
|
||||
|
||||
For urgent security matters, you can also reach out to the maintainers directly through GitHub.
|
||||
|
||||
Thank you for helping keep ComfyUI-KikoTools secure!
|
||||
@@ -3,14 +3,101 @@ ComfyUI-KikoTools: Modular collection of custom ComfyUI nodes
|
||||
All nodes are grouped under the "ComfyAssets" category
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from .kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
try:
|
||||
from .kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
except ImportError:
|
||||
# Fallback for testing environment
|
||||
from kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
# Tell ComfyUI where to find our JavaScript extensions
|
||||
WEB_DIRECTORY = "./web"
|
||||
import os
|
||||
|
||||
WEB_DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web")
|
||||
|
||||
# Import server components at module level to ensure they're available
|
||||
try:
|
||||
from aiohttp import web
|
||||
from server import PromptServer
|
||||
import folder_paths
|
||||
|
||||
print("[KikoTools] Server imports successful")
|
||||
|
||||
# Register autocomplete endpoints directly
|
||||
@PromptServer.instance.routes.get("/kikotools/autocomplete/embeddings")
|
||||
async def get_embeddings(request):
|
||||
"""API endpoint for getting list of embeddings with full paths."""
|
||||
print("[KikoTools] Embeddings endpoint called")
|
||||
try:
|
||||
embedding_files = folder_paths.get_filename_list("embeddings")
|
||||
print(f"[KikoTools] Found {len(embedding_files)} embedding files")
|
||||
# Return embeddings with their subdirectory paths, without extensions
|
||||
embeddings = []
|
||||
for f in embedding_files:
|
||||
# Remove extension but keep subdirectory path
|
||||
clean_path = os.path.splitext(f)[0]
|
||||
embeddings.append(
|
||||
{
|
||||
"file_name": clean_path,
|
||||
"model_name": clean_path,
|
||||
"name": os.path.basename(clean_path),
|
||||
"path": clean_path,
|
||||
}
|
||||
)
|
||||
if len(embeddings) > 0:
|
||||
print(f"[KikoTools] Sample embedding: {embeddings[0]}")
|
||||
print(f"[KikoTools] Returning {len(embeddings)} embeddings with paths")
|
||||
return web.json_response(embeddings)
|
||||
except Exception as e:
|
||||
print(f"[KikoTools] Error getting embeddings: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return web.json_response([])
|
||||
|
||||
@PromptServer.instance.routes.get("/kikotools/autocomplete/loras")
|
||||
async def get_loras(request):
|
||||
"""API endpoint for getting list of LoRAs."""
|
||||
print("[KikoTools] LoRA endpoint called")
|
||||
try:
|
||||
lora_files = folder_paths.get_filename_list("loras")
|
||||
print(f"[KikoTools] Found {len(lora_files)} LoRA files")
|
||||
# Return LoRAs with paths
|
||||
loras = []
|
||||
for f in lora_files:
|
||||
clean_path = os.path.splitext(f)[0]
|
||||
loras.append(
|
||||
{
|
||||
"name": os.path.basename(clean_path),
|
||||
"path": clean_path,
|
||||
"file": f,
|
||||
}
|
||||
)
|
||||
print(f"[KikoTools] Returning {len(loras)} LoRAs")
|
||||
return web.json_response(loras)
|
||||
except Exception as e:
|
||||
print(f"[KikoTools] Error getting LoRAs: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return web.json_response([])
|
||||
|
||||
print("[KikoTools] Autocomplete API endpoints registered successfully")
|
||||
print(
|
||||
"[KikoTools] Routes available: /kikotools/autocomplete/embeddings and /kikotools/autocomplete/loras"
|
||||
)
|
||||
|
||||
except ImportError as e:
|
||||
print(f"[KikoTools] Could not import server components: {e}")
|
||||
except Exception as e:
|
||||
print(f"[KikoTools] Unexpected error setting up API: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
# API endpoints are registered above at module import time
|
||||
|
||||
|
||||
def get_version():
|
||||
@@ -28,9 +115,11 @@ def get_version():
|
||||
|
||||
|
||||
# Print startup message with loaded tools
|
||||
print()
|
||||
print(f"\033[94m[ComfyUI-KikoTools] Version:\033[0m {get_version()}")
|
||||
for node_key, display_name in NODE_DISPLAY_NAME_MAPPINGS.items():
|
||||
print(f"🫶 \033[94mLoaded:\033[0m {display_name}")
|
||||
print(f"\033[94mTotal: {len(NODE_CLASS_MAPPINGS)} tools loaded\033[0m")
|
||||
print()
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
import os
|
||||
from typing import Tuple
|
||||
|
||||
import comfy.sd
|
||||
import comfy.utils
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from comfy.sd import CLIP
|
||||
from diffusers import ConsistencyDecoderVAE
|
||||
from folder_paths import get_folder_paths
|
||||
from huggingface_hub import hf_hub_download
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
def find_or_create_cache():
|
||||
cwd = os.getcwd()
|
||||
if os.path.exists(os.path.join(cwd, "ComfyUI")):
|
||||
cwd = os.path.join(cwd, "ComfyUI")
|
||||
if os.path.exists(os.path.join(cwd, "models")):
|
||||
cwd = os.path.join(cwd, "models")
|
||||
if not os.path.exists(os.path.join(cwd, "huggingface_cache")):
|
||||
print("Creating huggingface_cache directory within comfy")
|
||||
os.mkdir(os.path.join(cwd, "huggingface_cache"))
|
||||
|
||||
return str(os.path.join(cwd, "huggingface_cache"))
|
||||
|
||||
|
||||
class ConsistencyDecoder:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"latent": ("LATENT",)}}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "decode"
|
||||
CATEGORY = "latent"
|
||||
|
||||
def __init__(self):
|
||||
self.vae = (
|
||||
ConsistencyDecoderVAE.from_pretrained(
|
||||
"openai/consistency-decoder",
|
||||
torch_dtype=torch.float16,
|
||||
variant="fp16",
|
||||
use_safetensors=True,
|
||||
cache_dir=find_or_create_cache(),
|
||||
)
|
||||
.eval()
|
||||
.to("cuda")
|
||||
)
|
||||
|
||||
def _decode(self, latent):
|
||||
"""Used when patching another vae."""
|
||||
return self.vae.decode(latent.half().cuda()).sample
|
||||
|
||||
def decode(self, latent):
|
||||
"""Used for standalone decoding."""
|
||||
sample = self._decode(latent["samples"])
|
||||
sample = sample.clamp(-1, 1).movedim(1, -1).add(1.0).mul(0.5).cpu()
|
||||
return (sample,)
|
||||
|
||||
|
||||
class PatchDecoderTiled:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"vae": ("VAE",)}}
|
||||
|
||||
RETURN_TYPES = ("VAE",)
|
||||
FUNCTION = "patch"
|
||||
category = "vae"
|
||||
|
||||
def __init__(self):
|
||||
self.vae = ConsistencyDecoder()
|
||||
|
||||
def patch(self, vae):
|
||||
del vae.first_stage_model.decoder
|
||||
vae.first_stage_model.decode = self.vae._decode
|
||||
vae.decode = (
|
||||
lambda x: vae.decode_tiled_(
|
||||
x,
|
||||
tile_x=512,
|
||||
tile_y=512,
|
||||
overlap=64,
|
||||
)
|
||||
.to("cuda")
|
||||
.movedim(1, -1)
|
||||
)
|
||||
|
||||
return (vae,)
|
||||
|
||||
|
||||
# quick node to set SDXL-friendly aspect ratios in 1024^2
|
||||
# adapted from throttlekitty
|
||||
class SDXLAspectRatio:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT")
|
||||
RETURN_NAMES = ("width", "height")
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "image"
|
||||
|
||||
def run(self, image: Tensor) -> Tuple[int, int]:
|
||||
_, height, width, _ = image.shape
|
||||
aspect_ratio = width / height
|
||||
|
||||
aspect_ratios = (
|
||||
(1 / 1, 1024, 1024),
|
||||
(2 / 3, 832, 1216),
|
||||
(3 / 4, 896, 1152),
|
||||
(5 / 8, 768, 1216),
|
||||
(9 / 16, 768, 1344),
|
||||
(9 / 19, 704, 1472),
|
||||
(9 / 21, 640, 1536),
|
||||
(3 / 2, 1216, 832),
|
||||
(4 / 3, 1152, 896),
|
||||
(8 / 5, 1216, 768),
|
||||
(16 / 9, 1344, 768),
|
||||
(19 / 9, 1472, 704),
|
||||
(21 / 9, 1536, 640),
|
||||
)
|
||||
|
||||
# find the closest aspect ratio
|
||||
closest = min(aspect_ratios, key=lambda x: abs(x[0] - aspect_ratio))
|
||||
|
||||
return (closest[1], closest[2])
|
||||
|
||||
|
||||
class ImageToMultipleOf:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"multiple_of": (
|
||||
"INT",
|
||||
{
|
||||
"default": 64,
|
||||
"min": 1,
|
||||
"max": 256,
|
||||
"step": 16,
|
||||
"display": "number",
|
||||
},
|
||||
),
|
||||
"method": (["center crop", "rescale"],),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "image"
|
||||
|
||||
def run(self, image: Tensor, multiple_of: int, method: str) -> Tuple[Tensor]:
|
||||
"""Center crop the image to a specific multiple of a number."""
|
||||
_, height, width, _ = image.shape
|
||||
|
||||
new_height = height - (height % multiple_of)
|
||||
new_width = width - (width % multiple_of)
|
||||
|
||||
if method == "rescale":
|
||||
return (
|
||||
F.interpolate(
|
||||
image.unsqueeze(0),
|
||||
size=(new_height, new_width),
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
).squeeze(0),
|
||||
)
|
||||
else:
|
||||
top = (height - new_height) // 2
|
||||
left = (width - new_width) // 2
|
||||
bottom = top + new_height
|
||||
right = left + new_width
|
||||
return (image[:, top:bottom, left:right, :],)
|
||||
|
||||
|
||||
class HFHubLoraLoader:
|
||||
def __init__(self):
|
||||
self.loaded_lora = None
|
||||
self.loaded_lora_path = None
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP",),
|
||||
"repo_id": ("STRING", {"default": ""}),
|
||||
"subfolder": ("STRING", {"default": ""}),
|
||||
"filename": ("STRING", {"default": ""}),
|
||||
"strength_model": (
|
||||
"FLOAT",
|
||||
{"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01},
|
||||
),
|
||||
"strength_clip": (
|
||||
"FLOAT",
|
||||
{"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP")
|
||||
FUNCTION = "load_lora"
|
||||
|
||||
CATEGORY = "loaders"
|
||||
|
||||
def load_lora(
|
||||
self,
|
||||
model,
|
||||
clip,
|
||||
repo_id: str,
|
||||
subfolder: str,
|
||||
filename: str,
|
||||
strength_model: float,
|
||||
strength_clip: float,
|
||||
):
|
||||
if strength_model == 0 and strength_clip == 0:
|
||||
return (model, clip)
|
||||
|
||||
lora_path = hf_hub_download(
|
||||
repo_id=repo_id.strip(),
|
||||
subfolder=(
|
||||
None
|
||||
if subfolder is None or subfolder.strip() == ""
|
||||
else subfolder.strip()
|
||||
),
|
||||
filename=filename.strip(),
|
||||
cache_dir=find_or_create_cache(),
|
||||
)
|
||||
|
||||
lora = None
|
||||
if self.loaded_lora is not None:
|
||||
if self.loaded_lora_path == lora_path:
|
||||
lora = self.loaded_lora
|
||||
else:
|
||||
self.loaded_lora = None
|
||||
self.loaded_lora_path = None
|
||||
|
||||
if lora is None:
|
||||
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
|
||||
self.loaded_lora = lora
|
||||
self.loaded_lora_path = lora_path
|
||||
|
||||
model_lora, clip_lora = comfy.sd.load_lora_for_models(
|
||||
model, clip, lora, strength_model, strength_clip
|
||||
)
|
||||
return (model_lora, clip_lora)
|
||||
|
||||
|
||||
class HFHubEmbeddingLoader:
|
||||
"""Load a text model embedding from Huggingface Hub.
|
||||
The connected CLIP model is not manipulated."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"clip": ("CLIP",),
|
||||
"repo_id": ("STRING", {"default": ""}),
|
||||
"subfolder": ("STRING", {"default": ""}),
|
||||
"filename": ("STRING", {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CLIP",)
|
||||
FUNCTION = "download_embedding"
|
||||
|
||||
CATEGORY = "n/a"
|
||||
|
||||
def download_embedding(
|
||||
self,
|
||||
clip: CLIP, # added to signify it's best put in between nodes
|
||||
repo_id: str,
|
||||
subfolder: str,
|
||||
filename: str,
|
||||
):
|
||||
hf_hub_download(
|
||||
repo_id=repo_id.strip(),
|
||||
subfolder=(
|
||||
None
|
||||
if subfolder is None or subfolder.strip() == ""
|
||||
else subfolder.strip()
|
||||
),
|
||||
filename=filename.strip(),
|
||||
local_dir=get_folder_paths("embeddings")[0],
|
||||
)
|
||||
|
||||
return (clip,)
|
||||
|
||||
|
||||
class GlifVariable:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"variable": (
|
||||
[
|
||||
"",
|
||||
],
|
||||
),
|
||||
"fallback": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"single_line": True,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "INT", "FLOAT")
|
||||
FUNCTION = "do_it"
|
||||
|
||||
CATEGORY = "glif/variables"
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, variable: str, fallback: str):
|
||||
# Since we populate dynamically, comfy will report invalid inputs. Override to always return True
|
||||
return True
|
||||
|
||||
def do_it(self, variable: str, fallback: str):
|
||||
variable = variable.strip()
|
||||
fallback = fallback.strip()
|
||||
if variable == "" or (variable.startswith("{") and variable.endswith("}")):
|
||||
variable = fallback
|
||||
|
||||
int_val = 0
|
||||
float_val = 0.0
|
||||
string_val = f"{variable}"
|
||||
try:
|
||||
int_val = int(variable)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
float_val = float(variable)
|
||||
except Exception:
|
||||
pass
|
||||
return (string_val, int_val, float_val)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"GlifConsistencyDecoder": ConsistencyDecoder,
|
||||
"GlifPatchConsistencyDecoderTiled": PatchDecoderTiled,
|
||||
"SDXLAspectRatio": SDXLAspectRatio,
|
||||
"ImageToMultipleOf": ImageToMultipleOf,
|
||||
"HFHubLoraLoader": HFHubLoraLoader,
|
||||
"HFHubEmbeddingLoader": HFHubEmbeddingLoader,
|
||||
"GlifVariable": GlifVariable,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"GlifConsistencyDecoder": "Consistency VAE Decoder",
|
||||
"GlifPatchConsistencyDecoderTiled": "Patch Consistency VAE Decoder",
|
||||
"SDXLAspectRatio": "Image to SDXL compatible WH",
|
||||
"ImageToMultipleOf": "Image to Multiple of",
|
||||
"HFHubLoraLoader": "Load HF Lora",
|
||||
"HFHubEmbeddingLoader": "Load HF Embedding",
|
||||
"GlifVariable": "Glif Variable",
|
||||
}
|
||||
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,106 @@
|
||||
# Batch Prompts Node
|
||||
|
||||
The **Batch Prompts** node loads and processes prompts from text files for batch generation workflows. It automatically cycles through prompts with each execution, making it perfect for testing multiple prompts in queue batches.
|
||||
|
||||
## Features
|
||||
|
||||
- **File-based prompt loading** - Load prompts from text files with `---` separators
|
||||
- **Auto-increment mode** - Automatically advance to the next prompt with each execution
|
||||
- **Positive/Negative splitting** - Automatically splits prompts at "Negative:" markers
|
||||
- **Persistent state** - Maintains position across ComfyUI restarts
|
||||
- **Wrap-around support** - Loop back to the first prompt after the last one
|
||||
- **Progress tracking** - Shows current position and total prompts
|
||||
|
||||
## Input Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `prompt_file` | STRING | "prompts.txt" | Path to text file containing prompts |
|
||||
| `index` | INT | 0 | Manual prompt index (when auto_increment is off) |
|
||||
| `auto_increment` | BOOLEAN | True | Automatically advance to next prompt |
|
||||
| `wrap_around` | BOOLEAN | True | Loop back to start after last prompt |
|
||||
| `split_negative` | BOOLEAN | True | Split prompts at "Negative:" marker |
|
||||
| `reload_file` | BOOLEAN | False | Force reload file from disk |
|
||||
| `show_preview` | BOOLEAN | True | Show prompt preview in console |
|
||||
|
||||
## Output Values
|
||||
|
||||
| Output | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `positive` | STRING | The positive prompt text |
|
||||
| `negative` | STRING | The negative prompt text (if split) |
|
||||
| `full_prompt` | STRING | Complete prompt including negative |
|
||||
| `next_prompt` | STRING | Preview of the next prompt |
|
||||
| `current_index` | INT | Current prompt index (0-based) |
|
||||
| `total_prompts` | INT | Total number of prompts |
|
||||
| `batch_info` | STRING | Progress information string |
|
||||
|
||||
## Prompt File Format
|
||||
|
||||
Create a text file with prompts separated by `---` on its own line:
|
||||
|
||||
```
|
||||
A beautiful sunset over the ocean
|
||||
Negative: blurry, dark, low quality
|
||||
---
|
||||
Mountain landscape with snow peaks
|
||||
Negative: foggy, unclear
|
||||
---
|
||||
Futuristic city at night
|
||||
Negative: old, vintage, sepia
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Queue Batch Processing
|
||||
|
||||
1. Create a prompt file in your ComfyUI `input` folder
|
||||
2. Add the Batch Prompts node to your workflow
|
||||
3. Set `prompt_file` to your file name
|
||||
4. Enable `auto_increment` and `wrap_around`
|
||||
5. Connect `positive` to your text encoder
|
||||
6. Connect `negative` to your negative text encoder
|
||||
7. Set Queue Batch to desired number (e.g., 10)
|
||||
8. Run the queue - prompts will cycle automatically
|
||||
|
||||
### Manual Index Control
|
||||
|
||||
For manual control over which prompt to use:
|
||||
|
||||
1. Set `auto_increment` to False
|
||||
2. Control the `index` parameter manually
|
||||
3. Use with other nodes that provide index values
|
||||
|
||||
### Monitoring Progress
|
||||
|
||||
The node provides several ways to track progress:
|
||||
|
||||
- `batch_info` output shows "Prompt X of Y (Z% complete)"
|
||||
- Console logging shows current prompt preview (when `show_preview` is True)
|
||||
- `current_index` and `total_prompts` for custom progress displays
|
||||
|
||||
## Tips
|
||||
|
||||
- Place prompt files in the ComfyUI `input` folder for easy access
|
||||
- Use relative paths like "prompts.txt" for files in the input folder
|
||||
- Use absolute paths for files elsewhere on your system
|
||||
- The node maintains state across ComfyUI restarts
|
||||
- Set `reload_file` to True to force re-reading after editing the file
|
||||
- Empty sections (between `---` markers) are automatically skipped
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Prompts not changing in queue batch
|
||||
- Ensure `auto_increment` is set to True
|
||||
- Check console for "[BatchPrompts] Auto-increment" messages
|
||||
- Restart ComfyUI after installing/updating the node
|
||||
|
||||
### File not found errors
|
||||
- Check that the file exists in the ComfyUI `input` folder
|
||||
- Try using an absolute path to test
|
||||
- Ensure file has read permissions
|
||||
|
||||
### State persistence
|
||||
- State is stored in your system's temp directory
|
||||
- Clear `/tmp/comfyui_batch_prompts/` to reset all counters
|
||||
- Use `reload_file` to reset counter for a specific file
|
||||
@@ -0,0 +1,120 @@
|
||||
# Display Any
|
||||
|
||||
The Display Any node is a debugging and inspection tool that can display any type of input value in ComfyUI. It's particularly useful for understanding data structures and tensor shapes during workflow development.
|
||||
|
||||
## Features
|
||||
|
||||
- **Universal Input**: Accepts any type of input data (tensors, strings, numbers, lists, dictionaries, etc.)
|
||||
- **Two Display Modes**:
|
||||
- **Raw Value**: Shows the string representation of the input
|
||||
- **Tensor Shape**: Extracts and displays the shapes of any tensors found in the input
|
||||
- **Nested Structure Support**: Can find tensors within nested dictionaries and lists
|
||||
- **UI Output**: Displays results directly in the ComfyUI interface
|
||||
|
||||
## Inputs
|
||||
|
||||
- **input** (*): Any value you want to display or inspect
|
||||
- **mode** (DROPDOWN): Display mode selection
|
||||
- `raw value`: Shows the complete string representation of the input
|
||||
- `tensor shape`: Extracts and shows shapes of any tensors in the input
|
||||
|
||||
## Outputs
|
||||
|
||||
- **display_text** (STRING): The formatted display text
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### 1. Display Simple Values
|
||||
|
||||
Connect any output to see its raw value:
|
||||
```
|
||||
String Input: "Hello, ComfyUI!"
|
||||
Mode: raw value
|
||||
Output: "Hello, ComfyUI!"
|
||||
```
|
||||
|
||||
### 2. Inspect Tensor Shapes
|
||||
|
||||
Great for debugging image processing pipelines:
|
||||
```
|
||||
Image Tensor: [1, 3, 512, 512]
|
||||
Mode: tensor shape
|
||||
Output: "[[1, 3, 512, 512]]"
|
||||
```
|
||||
|
||||
### 3. Debug Complex Data Structures
|
||||
|
||||
View nested data structures with multiple tensors:
|
||||
```python
|
||||
Input: {
|
||||
"images": tensor([1, 3, 256, 256]),
|
||||
"masks": [tensor([256, 256]), tensor([256, 256, 1])],
|
||||
"config": {"steps": 20}
|
||||
}
|
||||
Mode: tensor shape
|
||||
Output: "[[1, 3, 256, 256], [256, 256], [256, 256, 1]]"
|
||||
```
|
||||
|
||||
### 4. Workflow Debugging
|
||||
|
||||
Use Display Any nodes at various points in your workflow to understand data flow:
|
||||
- After loading images to verify dimensions
|
||||
- Before/after processing nodes to track shape changes
|
||||
- To inspect conditioning or latent data structures
|
||||
- To view metadata or configuration dictionaries
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Image Pipeline Debugging
|
||||
Place Display Any nodes after image loading and processing nodes to track dimension changes:
|
||||
```
|
||||
Load Image → Display Any (tensor shape) → Resize → Display Any (tensor shape)
|
||||
```
|
||||
|
||||
### Latent Space Inspection
|
||||
Understand latent dimensions in your workflows:
|
||||
```
|
||||
VAE Encode → Display Any (tensor shape) → KSampler → Display Any (raw value)
|
||||
```
|
||||
|
||||
### Configuration Verification
|
||||
Display complex configuration objects to ensure correct settings:
|
||||
```
|
||||
Config Node → Display Any (raw value) → Processing Node
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Multiple Display Nodes**: You can use multiple Display Any nodes in a single workflow to track data at different stages
|
||||
|
||||
2. **Tensor Shape Mode**: Particularly useful when working with:
|
||||
- Image batches to verify batch size
|
||||
- Latent tensors to understand dimensions
|
||||
- Mask arrays to check compatibility
|
||||
|
||||
3. **Raw Value Mode**: Best for:
|
||||
- String prompts and text
|
||||
- Configuration dictionaries
|
||||
- Debugging node outputs
|
||||
- Understanding data structure
|
||||
|
||||
4. **No Tensors Found**: If you see "No tensors found in input" in tensor shape mode, the input doesn't contain any tensor-like objects (numpy arrays, torch tensors, etc.)
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- The node uses `str()` for raw value display, providing Python's string representation
|
||||
- Tensor shape detection works with any object that has a `shape` attribute
|
||||
- Nested structure traversal supports dictionaries, lists, and tuples
|
||||
- The output is both displayed in the UI and available as a string output for further processing
|
||||
|
||||
## Example Workflow Integration
|
||||
|
||||
```
|
||||
[Load Image] → [Image Processing] → [Display Any (tensor shape)]
|
||||
↓
|
||||
"[[1, 3, 512, 512]]"
|
||||
↓
|
||||
[Text Multiline] ← [Concatenate] ← "Image dimensions: "
|
||||
```
|
||||
|
||||
This creates a text output showing the current image dimensions that can be used elsewhere in your workflow.
|
||||
@@ -0,0 +1,147 @@
|
||||
# Display Text
|
||||
|
||||
The Display Text node provides advanced text display capabilities with smart formatting, interactive features, and responsive design for ComfyUI workflows.
|
||||
|
||||
## Features
|
||||
|
||||
- **Smart Prompt Detection**: Automatically detects and formats SDXL-style positive/negative prompt pairs
|
||||
- **Text Wrapping**: Proper word wrapping that reflows when node is resized
|
||||
- **Scrollable Content**: Mouse wheel scrolling for long texts with visual indicators
|
||||
- **Copy Functionality**: Always-visible copy button with visual feedback
|
||||
- **Split View Mode**: Side-by-side display for prompt pairs
|
||||
- **Responsive Design**: Content adapts to node resizing
|
||||
|
||||
## Inputs
|
||||
|
||||
- **text** (STRING): The text to display
|
||||
- Can be a single text block
|
||||
- Can contain "Positive prompt:" and "Negative prompt:" sections for automatic split view
|
||||
|
||||
## Outputs
|
||||
|
||||
- **text** (STRING): Pass-through of the input text
|
||||
|
||||
## Display Modes
|
||||
|
||||
### Single Text Mode
|
||||
|
||||
When the input is regular text without prompt markers, it displays as a single scrollable text area with:
|
||||
- Word wrapping at word boundaries
|
||||
- Vertical scrolling for long content
|
||||
- Single copy button for the entire text
|
||||
|
||||
### Split View Mode
|
||||
|
||||
Automatically activated when text contains both "Positive prompt:" and "Negative prompt:" sections:
|
||||
- Side-by-side display with 50/50 split
|
||||
- Independent scrolling for each section
|
||||
- Separate copy buttons for each prompt
|
||||
- Labels are stripped when copying (clean prompts)
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### 1. Display Generated Prompts
|
||||
|
||||
```
|
||||
Gemini Prompt → Display Text → Copy to workflow
|
||||
```
|
||||
The node automatically detects SDXL format and shows positive/negative prompts side-by-side.
|
||||
|
||||
### 2. Debug Text Processing
|
||||
|
||||
```
|
||||
Text Processing → Display Text → Further Processing
|
||||
```
|
||||
View intermediate text processing results with proper formatting.
|
||||
|
||||
### 3. Show Long Descriptions
|
||||
|
||||
```
|
||||
Load Text → Display Text → Review
|
||||
```
|
||||
Display long text content with scrolling and word wrapping.
|
||||
|
||||
## Interactive Features
|
||||
|
||||
### Copy Button
|
||||
- Always visible in the top-right corner
|
||||
- Shows "✓ Copied!" feedback on click
|
||||
- In split view: separate buttons for each section
|
||||
- Strips prompt labels for clean copying
|
||||
|
||||
### Scrolling
|
||||
- Mouse wheel scrolling when hovering over text
|
||||
- Visual indicators appear when content is scrollable
|
||||
- Smooth scrolling with proper boundaries
|
||||
- Independent scrolling in split view mode
|
||||
|
||||
### Resizing
|
||||
- Text reflows when node width changes
|
||||
- Maintains readability at different sizes
|
||||
- Split view maintains 50/50 proportions
|
||||
- Minimum height ensures usability
|
||||
|
||||
## Smart Prompt Detection
|
||||
|
||||
The node intelligently detects prompt formats:
|
||||
|
||||
1. **SDXL Format**:
|
||||
- Looks for "Positive prompt:" and "Negative prompt:" markers
|
||||
- Case-insensitive detection
|
||||
- Handles various formatting styles
|
||||
|
||||
2. **Label Stripping**:
|
||||
- When copying from split view, labels are removed
|
||||
- "Positive prompt: beautiful sunset" → "beautiful sunset"
|
||||
- Clean prompts ready for direct use
|
||||
|
||||
## Styling
|
||||
|
||||
- **Font**: Monospace for consistent alignment
|
||||
- **Colors**:
|
||||
- Text: Light gray (#ddd) on dark background
|
||||
- Background: Semi-transparent dark (#1a1a1a)
|
||||
- Borders: Subtle gray (#333)
|
||||
- **Spacing**: Comfortable padding and line height
|
||||
- **Visual Feedback**: Hover effects on interactive elements
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Prompt Engineering Workflows
|
||||
- Display AI-generated prompts with proper formatting
|
||||
- Compare positive and negative prompts side-by-side
|
||||
- Copy refined prompts without manual cleanup
|
||||
|
||||
### Text Processing Pipelines
|
||||
- Debug text transformations at each step
|
||||
- View formatted outputs from text nodes
|
||||
- Monitor prompt construction workflows
|
||||
|
||||
### Documentation and Notes
|
||||
- Display workflow instructions
|
||||
- Show generation parameters
|
||||
- Present formatted metadata
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Text Processing**: Preserves original text while adding display formatting
|
||||
- **Responsive Design**: CSS-based layout adapts to node dimensions
|
||||
- **Event Handling**: Proper event propagation for ComfyUI compatibility
|
||||
- **Memory Efficient**: Only renders visible text portions
|
||||
|
||||
## Tips
|
||||
|
||||
1. **For Long Prompts**: The scrolling feature handles texts of any length efficiently
|
||||
2. **Quick Copy**: Use the copy buttons to quickly grab prompts for other nodes
|
||||
3. **Resizing**: Drag node edges to find optimal display width for your content
|
||||
4. **Split View**: Works best with SDXL-format prompts but handles any dual-section text
|
||||
|
||||
## Integration Example
|
||||
|
||||
```
|
||||
[Gemini Prompt Engineer] → [Display Text] → [Copy Button Click]
|
||||
↓ ↓ ↓
|
||||
SDXL Format Split View Display Clean Prompts
|
||||
```
|
||||
|
||||
This creates a seamless workflow from prompt generation to usage, with the Display Text node providing the visual interface for review and interaction.
|
||||
@@ -0,0 +1,222 @@
|
||||
# Empty Latent Batch Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The Empty Latent Batch is a ComfyUI node that creates empty latent tensors with batch support and preset integration. It combines the preset functionality of Width Height Selector with efficient batch processing capabilities, making it ideal for batch workflows and optimized generation pipelines.
|
||||
|
||||
## Features
|
||||
|
||||
### 🎯 **Preset Integration**
|
||||
- **26 Curated Presets**: Full access to SDXL, FLUX, and Ultra-wide presets
|
||||
- **Formatted Display**: Shows aspect ratio, megapixels, and model group
|
||||
- **Smart Fallback**: Automatic fallback to custom dimensions for invalid presets
|
||||
- **Model Optimization**: Preset categories optimized for different model types
|
||||
|
||||
### 📦 **Batch Processing**
|
||||
- **Configurable Batch Size**: Create 1-64 empty latents in single operation
|
||||
- **Memory Efficient**: Uses torch.zeros for optimal memory allocation
|
||||
- **Batch Validation**: Prevents excessive memory usage with warnings
|
||||
- **ComfyUI Compatible**: Standard latent format for seamless integration
|
||||
|
||||
### 🔄 **Visual Swap Button**
|
||||
- **Interactive UI**: Blue swap button with hover and click feedback
|
||||
- **Preset-Aware Swapping**: Intelligent switching between matching presets
|
||||
- **Custom Dimension Support**: Simple value swapping for custom inputs
|
||||
- **Visual Feedback**: Button state changes during interaction
|
||||
|
||||
### ✅ **Smart Validation**
|
||||
- **Dimension Sanitization**: Automatic adjustment to divisible-by-8 constraint
|
||||
- **Memory Estimation**: Built-in memory usage calculation
|
||||
- **Error Handling**: Graceful handling of invalid inputs with helpful messages
|
||||
- **Logging**: Detailed operation logging for debugging
|
||||
|
||||
## Node Interface
|
||||
|
||||
### Inputs
|
||||
- **preset**: Dropdown with 26 formatted preset options + custom
|
||||
- **width**: Custom width (64-8192, step 8, default 1024)
|
||||
- **height**: Custom height (64-8192, step 8, default 1024)
|
||||
- **batch_size**: Number of latents to create (1-64, default 1)
|
||||
|
||||
### Outputs
|
||||
- **latent**: Dictionary containing batch of empty latent tensors
|
||||
- **width**: Final sanitized width (guaranteed divisible by 8)
|
||||
- **height**: Final sanitized height (guaranteed divisible by 8)
|
||||
|
||||
## Preset Reference
|
||||
|
||||
The Empty Latent Batch node uses the same 26 curated presets as the Width Height Selector:
|
||||
|
||||
### SDXL Presets (~1 Megapixel)
|
||||
Optimized for SDXL models with ~1MP resolution constraint.
|
||||
|
||||
### FLUX Presets (High Resolution)
|
||||
Higher resolution presets optimized for FLUX models with better quality/speed balance.
|
||||
|
||||
### Ultra-Wide Presets (Modern Ratios)
|
||||
Modern aspect ratios for ultra-wide and panoramic generation.
|
||||
|
||||
*For complete preset details, see [Width Height Selector Documentation](width_height_selector.md#preset-reference)*
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Empty Latent Creation
|
||||
1. **Select Preset**: Choose from dropdown (e.g., "1024×1024 - 1:1 (1.0MP) - SDXL")
|
||||
2. **Set Batch Size**: Enter desired number of latents (e.g., 4)
|
||||
3. **Connect Output**: Link latent output to KSampler or other processing nodes
|
||||
|
||||
### Custom Batch Creation
|
||||
1. **Set Preset**: Select "custom"
|
||||
2. **Enter Dimensions**: Input width and height manually
|
||||
3. **Set Batch Size**: Configure number of latents needed
|
||||
4. **Validation**: Automatic sanitization ensures compatibility
|
||||
|
||||
### Orientation Swapping
|
||||
1. **Choose Preset**: Any preset (e.g., "1920×1080")
|
||||
2. **Click Swap Button**: Blue button in bottom-right corner
|
||||
3. **Result**: Gets swapped preset if available, or custom dimensions with swapped values
|
||||
4. **Widget Update**: Width/height widgets automatically update
|
||||
|
||||
### Memory-Aware Batch Processing
|
||||
1. **Large Batch**: Set batch_size to 16 or higher
|
||||
2. **Memory Warning**: Node provides memory usage estimation
|
||||
3. **Optimization**: Choose appropriate resolution preset for available VRAM
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Batch Generation Pipeline
|
||||
```
|
||||
Empty Latent Batch → KSampler → VAE Decode → Save Image
|
||||
(batch_size: 4) ↓ ↓ ↓
|
||||
4 samples 4 images 4 files
|
||||
```
|
||||
- Create 4 empty latents at once
|
||||
- Process all through sampling
|
||||
- Generate 4 images in single operation
|
||||
- Efficient for parameter exploration
|
||||
|
||||
### Model Comparison Workflow
|
||||
```
|
||||
Empty Latent Batch → [Multiple KSamplers] → [Multiple VAE Decoders] → Compare Results
|
||||
(batch_size: 8) ↓ ↓ ↓
|
||||
Split batch Process variants Side-by-side
|
||||
```
|
||||
- Create consistent batch of empty latents
|
||||
- Split across different samplers/models
|
||||
- Compare results with identical starting conditions
|
||||
|
||||
### Upscaling Preparation
|
||||
```
|
||||
Empty Latent Batch → KSampler → VAE Decode → Resolution Calculator → Upscaler
|
||||
(832×1216, batch:4) ↓ ↓ ↓ ↓
|
||||
Sample Decode Calculate 2x Upscale batch
|
||||
```
|
||||
- Generate batch at base resolution
|
||||
- Calculate upscale dimensions
|
||||
- Process entire batch through upscaler
|
||||
|
||||
### Aspect Ratio Exploration
|
||||
```
|
||||
Empty Latent Batch → [Clone to multiple orientations] → Parallel Processing
|
||||
(1920×1080) ↓ ↓
|
||||
[Swap Button] → Portrait & Landscape versions Compare orientations
|
||||
```
|
||||
- Start with base preset
|
||||
- Use swap button to create orientation variants
|
||||
- Process both simultaneously
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Memory Estimation
|
||||
The node provides built-in memory estimation for batch operations:
|
||||
|
||||
```python
|
||||
# Example memory calculations
|
||||
Batch Size: 4, Resolution: 1024×1024
|
||||
Latent Tensor: 4 × 4 × 128 × 128 = 262,144 elements
|
||||
Memory Usage: 262,144 × 4 bytes = 1.0 MB per batch
|
||||
```
|
||||
|
||||
### Intelligent Preset Handling
|
||||
- **Formatted Display**: Shows full metadata in dropdown
|
||||
- **Original Extraction**: Extracts original preset name from formatted strings
|
||||
- **Validation**: Verifies preset exists before processing
|
||||
- **Fallback Logic**: Uses custom dimensions if preset is invalid
|
||||
|
||||
### Batch Size Optimization
|
||||
- **Performance Warnings**: Alerts for large batch sizes
|
||||
- **Memory Limits**: Prevents excessive memory allocation
|
||||
- **Hardware Awareness**: Considers available system resources
|
||||
|
||||
## Tips and Best Practices
|
||||
|
||||
### Batch Size Selection
|
||||
- **Small Batches (1-4)**: Good for testing and development
|
||||
- **Medium Batches (5-16)**: Efficient for most production workflows
|
||||
- **Large Batches (17-64)**: Only for high-memory systems and specific use cases
|
||||
|
||||
### Preset Selection
|
||||
- **SDXL Projects**: Use SDXL presets for memory efficiency
|
||||
- **FLUX Projects**: Use FLUX presets for optimal quality
|
||||
- **Ultra-wide Projects**: Ensure sufficient VRAM for large resolutions
|
||||
- **Custom Projects**: Use custom dimensions for specific requirements
|
||||
|
||||
### Memory Management
|
||||
- Monitor memory usage with large batches
|
||||
- Use appropriate resolution presets for available VRAM
|
||||
- Consider splitting very large batches across multiple nodes
|
||||
- Clear GPU memory between large batch operations
|
||||
|
||||
### Workflow Integration
|
||||
- Always connect all three outputs (latent, width, height)
|
||||
- Use width/height outputs for downstream dimension calculations
|
||||
- Combine with Resolution Calculator for upscaling workflows
|
||||
- Leverage batch processing for efficient parameter exploration
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- **Out of Memory**: Reduce batch_size or use lower resolution presets
|
||||
- **Invalid Dimensions**: Node automatically sanitizes to valid values
|
||||
- **Preset Not Found**: Falls back to custom dimensions with warning
|
||||
- **Swap Button Not Working**: Ensure node is not collapsed and button is visible
|
||||
|
||||
### Performance Optimization
|
||||
- **Batch Size**: Start with smaller batches and increase as needed
|
||||
- **Resolution**: Use appropriate presets for your model and VRAM
|
||||
- **Memory Monitoring**: Watch for memory warnings and adjust accordingly
|
||||
- **Cleanup**: Clear unused tensors between large batch operations
|
||||
|
||||
### Error Handling
|
||||
- **Dimension Validation**: Automatic rounding to nearest valid values
|
||||
- **Batch Size Limits**: Clamped to 1-64 range with warnings
|
||||
- **Memory Allocation**: Graceful handling of insufficient memory
|
||||
- **Preset Fallbacks**: Automatic fallback to custom dimensions
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Latent Tensor Format
|
||||
- **Shape**: [batch_size, 4, height//8, width//8]
|
||||
- **Data Type**: torch.float32
|
||||
- **Initialization**: torch.zeros for clean empty state
|
||||
- **Memory Layout**: Contiguous tensor for optimal performance
|
||||
|
||||
### Validation Pipeline
|
||||
1. **Preset Extraction**: Parse formatted preset strings
|
||||
2. **Dimension Calculation**: Get base dimensions from preset or custom
|
||||
3. **Sanitization**: Ensure divisible-by-8 constraint
|
||||
4. **Batch Validation**: Check batch size limits
|
||||
5. **Memory Estimation**: Calculate expected memory usage
|
||||
6. **Tensor Creation**: Allocate and initialize latent tensor
|
||||
|
||||
### UI Integration
|
||||
- **JavaScript Extension**: Custom UI for swap button functionality
|
||||
- **Widget Synchronization**: Auto-update width/height when preset changes
|
||||
- **Visual Feedback**: Hover effects and click animations
|
||||
- **Event Handling**: Proper mouse event management
|
||||
|
||||
### Swap Button Implementation
|
||||
- **Position Calculation**: Dynamic positioning based on node size
|
||||
- **State Management**: Visual feedback for button interactions
|
||||
- **Preset Intelligence**: Smart switching between compatible presets
|
||||
- **Fallback Logic**: Custom dimension swapping when preset not available
|
||||
@@ -0,0 +1,152 @@
|
||||
# Flux Sampler Params
|
||||
|
||||
## Overview
|
||||
The **Flux Sampler Params** node provides a specialized parameter generator for FLUX model sampling. This tool was adapted from the excellent [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) project (now in maintenance mode) and enhanced for the ComfyAssets ecosystem.
|
||||
|
||||
## Attribution
|
||||
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
|
||||
|
||||
## Features
|
||||
- **FLUX-Optimized Parameters**: Specifically tuned for FLUX model requirements
|
||||
- **Batch Processing Support**: Generate multiple parameter sets for comparative testing
|
||||
- **Interactive UI Elements**: Visual controls for quick parameter adjustments
|
||||
- **Smart Defaults**: Pre-configured optimal settings for FLUX workflows
|
||||
- **Comprehensive Parameter Control**: Fine-tune all aspects of FLUX sampling
|
||||
|
||||
## Node Properties
|
||||
- **Category**: `ComfyAssets/🧰 xyz-helpers`
|
||||
- **Node Name**: `FluxSamplerParams`
|
||||
- **Function**: `get_value`
|
||||
|
||||
## Inputs
|
||||
|
||||
### Required
|
||||
| Parameter | Type | Default | Range | Description |
|
||||
|-----------|------|---------|-------|-------------|
|
||||
| `scheduler` | DROPDOWN | normal | [normal, simple, sgm_uniform] | Scheduler algorithm for sampling |
|
||||
| `steps` | INT | 20 | 1-100 | Number of sampling steps |
|
||||
| `guidance` | FLOAT | 3.5 | 0.0-100.0 | Guidance scale for conditioning |
|
||||
| `max_shift` | FLOAT | 1.0 | 0.0-100.0 | Maximum shift value for FLUX |
|
||||
| `base_shift` | FLOAT | 0.5 | 0.0-100.0 | Base shift value for FLUX |
|
||||
| `denoise` | FLOAT | 1.0 | 0.0-1.0 | Denoising strength |
|
||||
| `batch_mode` | DROPDOWN | single | [single, batch] | Single value or batch processing |
|
||||
|
||||
### Optional
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `batch_count` | INT | 1 | Number of batch variations (1-100) |
|
||||
| `batch_seed_mode` | DROPDOWN | incremental | Seed generation mode for batches |
|
||||
| `variation_seed` | INT | None | Optional seed for variations |
|
||||
| `lora_params` | LORA_PARAMS | None | LoRA parameters from LoRAFolderBatch |
|
||||
|
||||
## Outputs
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `sampler_params` | SAMPLER_PARAMS | Complete FLUX sampling parameters |
|
||||
| `scheduler` | STRING | Selected scheduler algorithm |
|
||||
| `steps` | INT | Number of sampling steps |
|
||||
| `guidance` | FLOAT | Guidance scale value |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic FLUX Sampling
|
||||
```
|
||||
FluxSamplerParams → KSampler → VAE Decode → Save Image
|
||||
scheduler: normal
|
||||
steps: 20
|
||||
guidance: 3.5
|
||||
```
|
||||
|
||||
### Batch Parameter Testing
|
||||
```
|
||||
FluxSamplerParams → KSampler → Image Grid → Save
|
||||
batch_mode: batch
|
||||
batch_count: 5
|
||||
guidance: 2.0...5.0
|
||||
```
|
||||
|
||||
### With LoRA Integration
|
||||
```
|
||||
LoRAFolderBatch → FluxSamplerParams → KSampler
|
||||
↓ ↓
|
||||
lora_params → Combined parameters
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### FLUX-Specific Settings
|
||||
- **Guidance**: FLUX typically works best with lower guidance (2.0-5.0)
|
||||
- **Steps**: 15-25 steps usually sufficient for FLUX
|
||||
- **Scheduler**: `normal` or `sgm_uniform` recommended for FLUX
|
||||
- **Shift Values**: Adjust for different quality/speed tradeoffs
|
||||
|
||||
### Batch Testing Workflow
|
||||
1. Set `batch_mode` to `batch`
|
||||
2. Configure parameter ranges using `...` syntax
|
||||
3. Set appropriate `batch_count`
|
||||
4. Use with image grid nodes for comparison
|
||||
|
||||
### Memory Optimization
|
||||
- Start with smaller batch counts for testing
|
||||
- Monitor VRAM usage with high batch counts
|
||||
- Use incremental seed mode for reproducibility
|
||||
|
||||
## Integration with Other Nodes
|
||||
|
||||
### Works Well With
|
||||
- **LoRA Folder Batch**: Combine multiple LoRAs with FLUX parameters
|
||||
- **Plot Parameters**: Visualize parameter effects
|
||||
- **Sampler Select Helper**: Dynamic sampler selection
|
||||
- **Text Encode Sampler Params**: Add text conditioning
|
||||
|
||||
### Common Workflows
|
||||
1. **Parameter Sweep**: Test multiple guidance/step combinations
|
||||
2. **LoRA Testing**: Evaluate different LoRA strengths with FLUX
|
||||
3. **Quality Comparison**: Compare different shift values
|
||||
4. **Seed Exploration**: Generate variations with controlled seeds
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Optimal FLUX Settings
|
||||
```python
|
||||
# High Quality (Slower)
|
||||
scheduler: "sgm_uniform"
|
||||
steps: 25
|
||||
guidance: 3.5
|
||||
max_shift: 1.0
|
||||
base_shift: 0.5
|
||||
|
||||
# Fast Preview
|
||||
scheduler: "simple"
|
||||
steps: 12
|
||||
guidance: 2.5
|
||||
max_shift: 0.8
|
||||
base_shift: 0.4
|
||||
```
|
||||
|
||||
### Batch Parameter Ranges
|
||||
- Steps: `15...25+5` (test 15, 20, 25)
|
||||
- Guidance: `2.0...5.0+0.5` (test 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0)
|
||||
- Denoise: `0.8...1.0+0.1` (test 0.8, 0.9, 1.0)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
1. **Out of Memory**: Reduce batch_count or image resolution
|
||||
2. **Poor Quality**: Increase steps or adjust guidance
|
||||
3. **Artifacts**: Check shift values aren't too high
|
||||
4. **Slow Generation**: Use `simple` scheduler for previews
|
||||
|
||||
### Parameter Guidelines
|
||||
- Don't set guidance too high (>10) for FLUX
|
||||
- Keep denoise at 1.0 for initial generation
|
||||
- Adjust shift values gradually for best results
|
||||
|
||||
## Version History
|
||||
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
|
||||
- **1.0.1**: Added batch processing support
|
||||
- **1.0.2**: Enhanced FLUX-specific optimizations
|
||||
- **1.0.3**: Improved UI elements and parameter validation
|
||||
|
||||
## Credits
|
||||
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
|
||||
@@ -0,0 +1,209 @@
|
||||
# Gemini Prompt Engineer
|
||||
|
||||
The Gemini Prompt Engineer node uses Google's Gemini AI to analyze images and generate optimized prompts for various AI image generation models.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-Model Support**: Generate prompts optimized for FLUX, SDXL, Danbooru, and Video generation
|
||||
- **Custom Prompts**: Override templates with your own system prompts
|
||||
- **Visual Feedback**: UI shows processing status and error states
|
||||
- **Flexible API Key Management**: Multiple ways to provide API credentials
|
||||
- **Dynamic Model Selection**: Fetch and use latest Gemini models with refresh button
|
||||
- **Model Caching**: Persistent storage of available models for offline access
|
||||
- **Help Integration**: Built-in setup guide accessible via help button
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Get API Key
|
||||
|
||||
Get your free Gemini API key from [Google AI Studio](https://makersuite.google.com/app/apikey)
|
||||
|
||||
### 2. Install Dependencies
|
||||
|
||||
```bash
|
||||
pip install google-generativeai
|
||||
```
|
||||
|
||||
### 3. Configure API Key
|
||||
|
||||
Choose one of these methods:
|
||||
|
||||
1. **Environment Variable** (Recommended):
|
||||
```bash
|
||||
export GEMINI_API_KEY="your-api-key-here"
|
||||
```
|
||||
|
||||
2. **Config File**:
|
||||
Create `gemini_config.json` in your ComfyUI root directory:
|
||||
```json
|
||||
{
|
||||
"api_key": "your-api-key-here"
|
||||
}
|
||||
```
|
||||
|
||||
3. **Node Input**:
|
||||
Enter the API key directly in the node's `api_key` field
|
||||
|
||||
## Inputs
|
||||
|
||||
- **image** (IMAGE): The image to analyze
|
||||
- **prompt_type** (DROPDOWN): Type of prompt to generate
|
||||
- `flux`: Detailed artistic prompts with quality markers
|
||||
- `sdxl`: Positive/negative prompt pairs with weight emphasis
|
||||
- `danbooru`: Anime-style booru tags with underscores
|
||||
- `video`: Motion and temporal descriptions for video generation
|
||||
- **model** (DROPDOWN): Gemini model selection
|
||||
- Dynamically populated list of available models
|
||||
- Includes latest models like gemini-2.0-flash-exp
|
||||
- Click refresh button to update model list
|
||||
- **api_key** (STRING, optional): Gemini API key if not set elsewhere
|
||||
- **custom_prompt** (STRING, optional): Override template with custom system prompt
|
||||
|
||||
## Outputs
|
||||
|
||||
- **prompt** (STRING): Generated prompt text
|
||||
- **negative_prompt** (STRING): Negative prompt (only populated for SDXL format)
|
||||
|
||||
## Prompt Type Details
|
||||
|
||||
### FLUX Format
|
||||
Generates detailed prompts optimized for FLUX models:
|
||||
- Starts with main subject and action
|
||||
- Includes style and medium descriptors
|
||||
- Adds lighting and atmosphere details
|
||||
- Uses quality markers like "4K", "highly detailed", "award-winning"
|
||||
|
||||
Example output:
|
||||
```
|
||||
majestic mountain landscape at golden hour, oil painting style, dramatic lighting with sun rays piercing through clouds, wide angle composition, warm color palette with orange and purple hues, highly detailed, 4K resolution, trending on ArtStation, photorealistic rendering
|
||||
```
|
||||
|
||||
### SDXL Format
|
||||
Generates positive and negative prompt pairs with enhanced structure:
|
||||
- Layered positive prompts: main subject → style → composition → technical
|
||||
- Comprehensive negative prompts to avoid common issues
|
||||
- Uses parentheses for emphasis: `(detailed eyes:1.2)`
|
||||
- Includes quality boosters and technical specifications
|
||||
|
||||
Example output:
|
||||
```
|
||||
Positive prompt:
|
||||
beautiful woman with flowing red hair, elegant pose, (detailed eyes:1.2), serene expression
|
||||
oil painting style, renaissance art influence, classical portraiture
|
||||
golden hour lighting, warm color palette, soft shadows, dramatic chiaroscuro
|
||||
centered composition, rule of thirds, shallow depth of field, bokeh background
|
||||
masterpiece, best quality, highly detailed, 8k uhd, professional artwork
|
||||
|
||||
Negative prompt:
|
||||
low quality, worst quality, blurry, out of focus, pixelated, low resolution
|
||||
bad anatomy, deformed features, extra limbs, missing limbs, disconnected limbs
|
||||
poorly drawn face, poorly drawn hands, amateur drawing, bad proportions
|
||||
oversaturated, overexposed, underexposed, bad lighting, harsh shadows
|
||||
jpeg artifacts, watermark, signature, text, cropped, duplicate
|
||||
```
|
||||
|
||||
### Danbooru Format
|
||||
Generates booru-style tags for anime artwork:
|
||||
- Uses underscores for multi-word concepts
|
||||
- Includes character count descriptors (1girl, 2boys)
|
||||
- Orders tags from most to least important
|
||||
|
||||
Example output:
|
||||
```
|
||||
1girl, solo, long_hair, blue_eyes, blonde_hair, school_uniform, serafuku, pleated_skirt, thighhighs, smile, looking_at_viewer, classroom, sitting, desk, window, sunlight, highres, masterpiece
|
||||
```
|
||||
|
||||
### Video Format
|
||||
Generates prompts for video generation models:
|
||||
- Describes motion and camera movements
|
||||
- Includes temporal markers and transitions
|
||||
- Specifies technical details like fps and duration
|
||||
|
||||
Example output:
|
||||
```
|
||||
Aerial shot slowly descending toward a misty forest at dawn, camera smoothly transitions to tracking shot following a deer through the trees, photorealistic style, soft golden hour lighting with fog, 10 second duration, 4K resolution 24fps, ending with close-up of deer looking at camera
|
||||
```
|
||||
|
||||
## Custom System Prompts
|
||||
|
||||
You can override any template by providing your own system prompt. This is useful for:
|
||||
- Specialized use cases
|
||||
- Different language outputs
|
||||
- Custom formatting requirements
|
||||
- Integration with specific workflows
|
||||
|
||||
Example custom prompt:
|
||||
```
|
||||
You are an expert at analyzing images and creating simple, concise descriptions.
|
||||
Focus only on the main subject and primary colors.
|
||||
Keep your response under 50 words.
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The node provides clear error messages for common issues:
|
||||
- Missing API key
|
||||
- API request failures
|
||||
- Invalid image inputs
|
||||
- Rate limiting
|
||||
|
||||
Errors are displayed in the prompt output for easy debugging.
|
||||
|
||||
## Model Selection
|
||||
|
||||
### Dynamic Model List
|
||||
- Click the refresh button (🔄) next to the model dropdown to fetch latest models
|
||||
- Models are fetched from Google's API and include all available versions
|
||||
- Common models include:
|
||||
- `gemini-2.0-flash-exp`: Latest experimental flash model
|
||||
- `gemini-1.5-pro`: Advanced model with larger context
|
||||
- `gemini-1.5-flash`: Fast and efficient for most tasks
|
||||
|
||||
### Model Caching
|
||||
- Available models are cached locally for offline access
|
||||
- Cache persists across ComfyUI sessions
|
||||
- Refresh button updates the cache with latest models
|
||||
|
||||
## UI Features
|
||||
|
||||
### Help Button
|
||||
- Click the help button (?) for quick setup instructions
|
||||
- Shows API key setup methods
|
||||
- Links to Google AI Studio for key generation
|
||||
|
||||
### Status Indicators
|
||||
- Processing spinner during API calls
|
||||
- Error messages displayed in red
|
||||
- Success feedback when prompt is generated
|
||||
|
||||
## Tips
|
||||
|
||||
1. **API Usage**: Gemini has generous free tier limits, but be mindful of rate limits
|
||||
2. **Image Quality**: Higher resolution images provide better analysis results
|
||||
3. **Prompt Refinement**: You can chain multiple Gemini nodes with different custom prompts
|
||||
4. **Caching**: Results are not cached, so identical images will make new API calls
|
||||
5. **Model Selection**: Use flash models for faster responses, pro models for complex analysis
|
||||
|
||||
## Example Workflow
|
||||
|
||||
1. Load an image using Load Image node
|
||||
2. Connect to Gemini Prompt Engineer
|
||||
3. Select appropriate prompt_type for your target model
|
||||
4. Connect prompt output to your generation model
|
||||
5. For SDXL, connect both prompt and negative_prompt outputs
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"API key not found" error**:
|
||||
- Check environment variable is set correctly
|
||||
- Verify config file path and JSON format
|
||||
- Try entering key directly in node
|
||||
|
||||
**"No response generated" error**:
|
||||
- Check internet connection
|
||||
- Verify API key is valid
|
||||
- Image might be too large (resize if needed)
|
||||
|
||||
**Import error for google-generativeai**:
|
||||
- Run `pip install google-generativeai` in your ComfyUI environment
|
||||
- Restart ComfyUI after installation
|
||||
@@ -0,0 +1,82 @@
|
||||
# Image to Multiple Of
|
||||
|
||||
## Overview
|
||||
|
||||
The **Image to Multiple Of** node adjusts image dimensions to be multiples of a specified value. This is particularly useful for models that require input dimensions to be multiples of certain values (e.g., 8, 16, 32, 64) for optimal performance or compatibility.
|
||||
|
||||
## Purpose
|
||||
|
||||
Many AI models, especially diffusion models and VAEs, require input dimensions to be multiples of specific values due to their architecture (e.g., downsampling layers). This node ensures your images meet these requirements without manual calculation.
|
||||
|
||||
## Inputs
|
||||
|
||||
- **image** (IMAGE, required): The input image to process
|
||||
- **multiple_of** (INT, required): The value that dimensions should be multiple of
|
||||
- Default: 64
|
||||
- Range: 1-256
|
||||
- Step: 16
|
||||
- **method** (COMBO, required): Processing method
|
||||
- Options: "center crop", "rescale"
|
||||
|
||||
## Outputs
|
||||
|
||||
- **image** (IMAGE): Processed image with dimensions adjusted to multiples of the specified value
|
||||
|
||||
## Processing Methods
|
||||
|
||||
### Center Crop
|
||||
- Crops the image from the center to achieve the target dimensions
|
||||
- Preserves image quality but may lose edge content
|
||||
- Best for images where the important content is centered
|
||||
|
||||
### Rescale
|
||||
- Resizes the image to the target dimensions using bilinear interpolation
|
||||
- Keeps all content but may slightly affect image quality
|
||||
- Best when you need to preserve all image content
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Prepare for VAE Encoding
|
||||
```
|
||||
Load Image → Image to Multiple Of (multiple_of: 64) → VAE Encode
|
||||
```
|
||||
|
||||
### Example 2: Prepare for Specific Model Requirements
|
||||
```
|
||||
Load Image → Image to Multiple Of (multiple_of: 32) → Model Processing
|
||||
```
|
||||
|
||||
### Example 3: Batch Processing
|
||||
```
|
||||
Load Images → Image to Multiple Of (multiple_of: 16, method: rescale) → Batch Process
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
- Supports batch processing (processes all images in a batch)
|
||||
- Works with any number of channels (RGB, RGBA, grayscale, etc.)
|
||||
- Calculates the largest dimensions that are less than or equal to the original size
|
||||
- For center crop: crops equally from all sides to maintain centering
|
||||
- For rescale: uses bilinear interpolation with align_corners=False
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
1. **VAE Preprocessing**: Ensure images are compatible with VAE encoders that require dimensions divisible by 64
|
||||
2. **Model Compatibility**: Adjust images for models with specific architectural requirements
|
||||
3. **Batch Uniformity**: Ensure all images in a batch have dimensions that meet model requirements
|
||||
4. **Performance Optimization**: Some models perform better with dimensions that are powers of 2
|
||||
|
||||
## Tips
|
||||
|
||||
- Use **center crop** when your subject is centered and you don't mind losing edge details
|
||||
- Use **rescale** when you need to preserve all image content
|
||||
- Common multiple_of values: 8, 16, 32, 64, 128
|
||||
- For Stable Diffusion models, 64 is typically recommended
|
||||
- For some upscaling models, 32 or 16 may be sufficient
|
||||
|
||||
## Error Handling
|
||||
|
||||
The node will raise an error if:
|
||||
- The image dimensions are smaller than the specified multiple_of value
|
||||
- Invalid input types are provided
|
||||
- The resulting dimensions would be 0 or negative
|
||||
@@ -0,0 +1,125 @@
|
||||
# Kiko Film Grain
|
||||
|
||||
## Overview
|
||||
The **Kiko Film Grain** node applies realistic film grain effects to images, simulating the aesthetic of analog film photography. It provides comprehensive controls for grain size, intensity, color saturation, and shadow lifting to achieve various film looks.
|
||||
|
||||
## Node Details
|
||||
- **Category**: ComfyAssets/image
|
||||
- **Node Name**: KikoFilmGrain
|
||||
- **Display Name**: Kiko Film Grain
|
||||
|
||||
## Inputs
|
||||
|
||||
### Required
|
||||
- **image** (`IMAGE`)
|
||||
- The input image to apply film grain to
|
||||
- Supports batch processing
|
||||
- Preserves alpha channel if present
|
||||
|
||||
### Parameters
|
||||
- **scale** (`FLOAT`)
|
||||
- Controls the size of the grain pattern
|
||||
- Range: 0.25 to 2.0
|
||||
- Default: 0.5
|
||||
- Lower values = finer grain, higher values = coarser grain
|
||||
|
||||
- **strength** (`FLOAT`)
|
||||
- Intensity of the grain effect
|
||||
- Range: 0.0 to 10.0
|
||||
- Default: 0.5
|
||||
- 0.0 = no grain, higher values = more pronounced grain
|
||||
|
||||
- **saturation** (`FLOAT`)
|
||||
- Color saturation of the grain
|
||||
- Range: 0.0 to 2.0
|
||||
- Default: 0.7
|
||||
- 0.0 = monochrome grain, 1.0 = full color, >1.0 = oversaturated
|
||||
|
||||
- **toe** (`FLOAT`)
|
||||
- Lifts blacks/shadows for a film-like look
|
||||
- Range: -0.2 to 0.5
|
||||
- Default: 0.0
|
||||
- Positive values lift shadows, negative values crush blacks
|
||||
|
||||
- **seed** (`INT`)
|
||||
- Random seed for grain pattern generation
|
||||
- Range: 0 to maximum integer
|
||||
- Default: 0
|
||||
- Use for reproducible grain patterns
|
||||
|
||||
## Outputs
|
||||
- **image** (`IMAGE`)
|
||||
- The processed image with film grain applied
|
||||
- Same dimensions and batch size as input
|
||||
- Alpha channel preserved if present
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Subtle Film Look
|
||||
```
|
||||
Scale: 0.5
|
||||
Strength: 0.3
|
||||
Saturation: 0.8
|
||||
Toe: 0.05
|
||||
```
|
||||
Creates a subtle, fine-grained film aesthetic suitable for portraits.
|
||||
|
||||
### Vintage Film
|
||||
```
|
||||
Scale: 1.0
|
||||
Strength: 0.8
|
||||
Saturation: 0.5
|
||||
Toe: 0.15
|
||||
```
|
||||
Simulates vintage film with moderate grain and lifted shadows.
|
||||
|
||||
### High ISO Film
|
||||
```
|
||||
Scale: 0.75
|
||||
Strength: 1.5
|
||||
Saturation: 0.6
|
||||
Toe: 0.1
|
||||
```
|
||||
Emulates high ISO film stock with pronounced grain.
|
||||
|
||||
### Black & White Film
|
||||
```
|
||||
Scale: 0.6
|
||||
Strength: 0.6
|
||||
Saturation: 0.0
|
||||
Toe: 0.08
|
||||
```
|
||||
Creates monochrome grain perfect for black and white photography.
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Improvements Over Standard Implementations
|
||||
1. **Pure PyTorch Operations**: No OpenCV dependencies, better GPU utilization
|
||||
2. **ITU-R BT.709 Color Space**: Accurate color conversion for grain application
|
||||
3. **Screen Blend Mode**: Preserves highlights better than multiply blending
|
||||
4. **Channel-Specific Weighting**: Film grain is stronger in blue channel (3x), moderate in red (2x), matching real film characteristics
|
||||
5. **Efficient Memory Management**: Minimizes tensor copies and conversions
|
||||
|
||||
### Algorithm Overview
|
||||
1. Generate random noise at specified scale
|
||||
2. Convert to YCbCr color space for realistic grain distribution
|
||||
3. Apply different blur kernels to each channel:
|
||||
- Y (luminance): 3x3 kernel for fine detail
|
||||
- Cb (blue-yellow): 15x15 kernel for color noise
|
||||
- Cr (red-green): 11x11 kernel for color noise
|
||||
4. Convert back to RGB and apply strength/saturation
|
||||
5. Use screen blend mode to combine with original image
|
||||
6. Apply toe adjustment for film-like shadow response
|
||||
|
||||
## Tips
|
||||
- Start with low strength values (0.2-0.5) and adjust upward
|
||||
- For color images, saturation between 0.5-0.8 looks most natural
|
||||
- Combine with color grading nodes for complete film emulation
|
||||
- Use consistent seed values across batch for uniform grain
|
||||
- Scale parameter affects both grain size and render performance (smaller scale = more computation)
|
||||
|
||||
## Compatibility
|
||||
- Works with any image format supported by ComfyUI
|
||||
- Preserves image properties (alpha channel, batch size)
|
||||
- Compatible with both RGB and RGBA images
|
||||
- Efficient batch processing support
|
||||
@@ -0,0 +1,213 @@
|
||||
# Kiko Save Image
|
||||
|
||||
Enhanced image saving node with multiple format support, quality controls, and an interactive floating popup viewer for ComfyUI.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Format Support**: Save as PNG, JPEG, or WebP with format-specific optimizations
|
||||
- **Advanced Quality Controls**: Fine-tune compression settings per format
|
||||
- **Floating Popup Viewer**: Interactive window showing saved images immediately
|
||||
- **Batch Operations**: Multi-select images for bulk actions
|
||||
- **File Size Display**: Real-time feedback on compression effectiveness
|
||||
- **Smart UI**: Auto-hide, draggable, resizable popup window
|
||||
|
||||
## Inputs
|
||||
|
||||
- **images** (IMAGE): Batch of images to save
|
||||
- **filename_prefix** (STRING): Prefix for saved filenames
|
||||
- Default: "KikoSave"
|
||||
- Supports subfolder paths (e.g., "outputs/renders/final")
|
||||
- **format** (DROPDOWN): Output format selection
|
||||
- `PNG`: Lossless compression, best quality
|
||||
- `JPEG`: Lossy compression, smaller files
|
||||
- `WEBP`: Modern format, best compression ratio
|
||||
- **quality** (INT): JPEG/WebP quality level
|
||||
- Range: 1-100 (default: 90)
|
||||
- Higher values = better quality, larger files
|
||||
- **png_compress_level** (INT): PNG compression level
|
||||
- Range: 0-9 (default: 4)
|
||||
- Higher values = smaller files, slower saving
|
||||
- **webp_lossless** (BOOLEAN): Use lossless WebP compression
|
||||
- Default: False (lossy)
|
||||
- True: Lossless compression like PNG
|
||||
- **popup** (BOOLEAN): Enable popup viewer window
|
||||
- Default: True
|
||||
- Toggle per save operation
|
||||
|
||||
## Outputs
|
||||
|
||||
- **UI**: Enhanced preview data with interactive popup viewer
|
||||
|
||||
## Popup Viewer Features
|
||||
|
||||
### Window Controls
|
||||
- **Drag Handle**: Click and drag the header to move window
|
||||
- **Minimize Button**: Collapse to title bar only
|
||||
- **Maximize Button**: Expand to larger viewing size
|
||||
- **Roll-up Button**: Show/hide content area
|
||||
- **Close Button**: Hide the popup (can reopen with toggle)
|
||||
|
||||
### Image Grid
|
||||
- **Thumbnails**: Click any image to open full-size in new tab
|
||||
- **File Info**: Shows filename and size for each image
|
||||
- **Quality Indicators**:
|
||||
- PNG: Compression level (0-9)
|
||||
- JPEG/WebP: Quality percentage
|
||||
- **Batch Selection**: Checkboxes for multi-select operations
|
||||
|
||||
### Bulk Actions
|
||||
- **Open All Selected**: Opens selected images in new tabs
|
||||
- **Download All Selected**: Downloads selected images as a batch
|
||||
- **Individual Downloads**: Download button per image
|
||||
|
||||
### Smart Behavior
|
||||
- **Auto-positioning**: Appears in convenient screen location
|
||||
- **Persistence**: Stays open across multiple saves
|
||||
- **Auto-hide**: Can be minimized when not needed
|
||||
- **Responsive**: Adapts to different image counts
|
||||
|
||||
## Format Details
|
||||
|
||||
### PNG Format
|
||||
- **Pros**: Lossless quality, transparency support, wide compatibility
|
||||
- **Cons**: Larger file sizes
|
||||
- **Best for**: Final outputs, images with transparency, archival
|
||||
- **Compression**: 0 (none) to 9 (maximum)
|
||||
- Level 4 (default) balances size and speed
|
||||
- Level 9 for maximum compression (slow)
|
||||
|
||||
### JPEG Format
|
||||
- **Pros**: Smaller files, fast loading, universal support
|
||||
- **Cons**: Lossy compression, no transparency
|
||||
- **Best for**: Web images, previews, photos
|
||||
- **Quality**: 1-100%
|
||||
- 90% (default) excellent quality with good compression
|
||||
- 95%+ for near-lossless quality
|
||||
- 70-85% for web optimization
|
||||
|
||||
### WebP Format
|
||||
- **Pros**: Best compression ratios, supports transparency, modern
|
||||
- **Cons**: Limited software support
|
||||
- **Best for**: Web deployment, storage optimization
|
||||
- **Modes**:
|
||||
- Lossy (default): Excellent compression with quality control
|
||||
- Lossless: PNG-like quality with better compression
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### High-Quality Archive
|
||||
```
|
||||
Format: PNG
|
||||
Compression: 0-2
|
||||
Use Case: Final renders for portfolio or client delivery
|
||||
```
|
||||
|
||||
### Web Optimization
|
||||
```
|
||||
Format: JPEG or WebP
|
||||
Quality: 80-85
|
||||
Use Case: Website images, social media posts
|
||||
```
|
||||
|
||||
### Balanced Storage
|
||||
```
|
||||
Format: WebP
|
||||
Quality: 90
|
||||
Lossless: False
|
||||
Use Case: Large batches with storage constraints
|
||||
```
|
||||
|
||||
### Transparency Preservation
|
||||
```
|
||||
Format: PNG or WebP (lossless)
|
||||
Use Case: Logos, UI elements, cutout images
|
||||
```
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
### Basic Save
|
||||
```
|
||||
Generate → Kiko Save Image
|
||||
format: PNG
|
||||
popup: enabled
|
||||
```
|
||||
|
||||
### Format Comparison
|
||||
```
|
||||
Generate → Kiko Save Image (PNG) → Compare file sizes
|
||||
↘ Kiko Save Image (JPEG) ↗
|
||||
↘ Kiko Save Image (WebP) ↗
|
||||
```
|
||||
|
||||
### Batch Processing
|
||||
```
|
||||
Batch Generate → Kiko Save Image → Popup Viewer
|
||||
↓ ↓
|
||||
4 images Select best results
|
||||
```
|
||||
|
||||
## Tips and Best Practices
|
||||
|
||||
1. **Format Selection**:
|
||||
- Use PNG for maximum quality and transparency
|
||||
- Use JPEG for photographs without transparency
|
||||
- Use WebP for modern web deployment
|
||||
|
||||
2. **Quality Settings**:
|
||||
- Start with defaults (90 for JPEG/WebP, 4 for PNG)
|
||||
- Adjust based on file size requirements
|
||||
- Preview results in popup before finalizing
|
||||
|
||||
3. **Popup Management**:
|
||||
- Drag to second monitor for larger workspace
|
||||
- Use roll-up to save screen space
|
||||
- Disable popup for automated workflows
|
||||
|
||||
4. **Batch Operations**:
|
||||
- Use checkboxes to select multiple images
|
||||
- Open all in tabs for side-by-side comparison
|
||||
- Download all for quick collection
|
||||
|
||||
5. **File Organization**:
|
||||
- Use subfolders in filename_prefix
|
||||
- Include descriptive prefixes
|
||||
- Let ComfyUI handle timestamp suffixes
|
||||
|
||||
## Advantages Over Standard Save Image
|
||||
|
||||
- **Immediate Preview**: No need to navigate file system
|
||||
- **Format Flexibility**: Choose optimal format per use case
|
||||
- **Quality Control**: Fine-tune compression settings
|
||||
- **Batch Management**: Handle multiple images efficiently
|
||||
- **Modern UI**: Floating interface doesn't interrupt workflow
|
||||
- **File Size Awareness**: See compression effectiveness immediately
|
||||
- **Quick Access**: One-click opening and downloading
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Image Processing**: Uses Pillow for format conversion
|
||||
- **Metadata**: Preserves ComfyUI metadata in saved files
|
||||
- **File Naming**: Automatic timestamp and counter suffixes
|
||||
- **Memory Efficiency**: Processes images individually
|
||||
- **Thread Safety**: Proper handling of concurrent saves
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Popup not appearing**:
|
||||
- Check that popup input is enabled
|
||||
- Look for minimized window
|
||||
- Try toggling the popup button in node
|
||||
|
||||
**WebP not working**:
|
||||
- Ensure Pillow has WebP support
|
||||
- Update Pillow: `pip install --upgrade pillow`
|
||||
|
||||
**Large file sizes**:
|
||||
- Increase compression (PNG) or reduce quality (JPEG/WebP)
|
||||
- Consider switching formats
|
||||
- Check image dimensions
|
||||
|
||||
**Can't see all images**:
|
||||
- Scroll within the popup grid
|
||||
- Maximize the popup window
|
||||
- Images are shown newest first
|
||||
@@ -0,0 +1,158 @@
|
||||
# Local Image Loader
|
||||
|
||||
## Overview
|
||||
|
||||
The Local Image Loader node provides a visual gallery interface for browsing and selecting images, videos, and audio files from your local filesystem directly within ComfyUI. This streamlined version focuses on essential functionality without the complexity of tagging or metadata management.
|
||||
|
||||
## Features
|
||||
|
||||
- **Visual Gallery Browser**: Browse local directories with thumbnail previews
|
||||
- **Multi-Media Support**: Load images, videos, and audio files
|
||||
- **Directory Navigation**: Navigate through folders with ease
|
||||
- **Sorting Options**: Sort by name, date, or file size
|
||||
- **Saved Paths**: Save frequently used directory paths for quick access
|
||||
- **Pagination**: Handle large directories with paginated display
|
||||
- **Lightbox Preview**: Full-size preview with zoom and pan capabilities
|
||||
|
||||
## Node Inputs
|
||||
|
||||
### Required Inputs
|
||||
None - The node uses a visual interface for file selection
|
||||
|
||||
### Hidden Inputs
|
||||
- `unique_id`: Automatically assigned node identifier
|
||||
|
||||
## Node Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `image` | IMAGE | The selected image as a tensor |
|
||||
| `video_path` | STRING | Path to the selected video file |
|
||||
| `audio_path` | STRING | Path to the selected audio file |
|
||||
| `info` | STRING | JSON metadata about the selected image |
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Workflow
|
||||
|
||||
1. **Add the Node**: Search for "Local Image Loader" in the node menu
|
||||
2. **Browse Directory**: Enter a directory path or use saved paths
|
||||
3. **Select Media**: Click on thumbnails to select files
|
||||
4. **Connect Outputs**: Use the outputs in your workflow
|
||||
|
||||
### Interface Controls
|
||||
|
||||
#### Path Management
|
||||
- **Directory Input**: Enter or paste a directory path
|
||||
- **Saved Paths Dropdown**: Quick access to saved directories
|
||||
- **Save Path Button** (💾): Save current directory to favorites
|
||||
- **Browse Button** (📁): Load the entered directory
|
||||
|
||||
#### View Options
|
||||
- **Videos Checkbox**: Show/hide video files
|
||||
- **Audio Checkbox**: Show/hide audio files
|
||||
- **Sort By**: Choose between Name, Date, or Size
|
||||
- **Sort Order**: Ascending (↑) or Descending (↓)
|
||||
- **Refresh Button** (🔄): Reload current directory
|
||||
|
||||
#### Gallery Display
|
||||
- **Thumbnail Grid**: Visual preview of files
|
||||
- **Blue Border**: Selected items are highlighted
|
||||
- **Folder Icons**: Navigate into subdirectories
|
||||
- **Video Overlay**: Visual indicator for video files
|
||||
- **Pagination**: Navigate through pages of results
|
||||
|
||||
## File Support
|
||||
|
||||
### Supported Image Formats
|
||||
- `.jpg`, `.jpeg`
|
||||
- `.png`
|
||||
- `.bmp`
|
||||
- `.gif`
|
||||
- `.webp`
|
||||
|
||||
### Supported Video Formats
|
||||
- `.mp4`
|
||||
- `.webm`
|
||||
- `.mov`
|
||||
- `.mkv`
|
||||
- `.avi`
|
||||
|
||||
### Supported Audio Formats
|
||||
- `.mp3`
|
||||
- `.wav`
|
||||
- `.ogg`
|
||||
- `.flac`
|
||||
|
||||
## Image Metadata
|
||||
|
||||
When an image is selected, the node extracts and returns metadata including:
|
||||
- **Basic Info**: Filename, width, height, format, mode
|
||||
- **Embedded Parameters**: Generation parameters if present
|
||||
- **Workflow Data**: Embedded ComfyUI workflow if present
|
||||
- **Prompt Data**: Embedded prompt information if present
|
||||
|
||||
## Examples
|
||||
|
||||
### Loading an Image for Processing
|
||||
```
|
||||
Local Image Loader → Load Image → Image Processing Node
|
||||
↓
|
||||
[info] → Display Text (to show metadata)
|
||||
```
|
||||
|
||||
### Setting Up a Multi-Media Workflow
|
||||
```
|
||||
Local Image Loader → [image] → Image Preview
|
||||
↓
|
||||
[video_path] → Video Player Node
|
||||
↓
|
||||
[audio_path] → Audio Player Node
|
||||
```
|
||||
|
||||
## Tips and Best Practices
|
||||
|
||||
1. **Save Frequently Used Paths**: Use the save button to bookmark directories you use often
|
||||
2. **Use Sorting**: Sort by date to find recent files quickly
|
||||
3. **Keyboard Navigation**: Press Enter in the path field to load a directory
|
||||
4. **Performance**: For directories with thousands of files, use pagination to navigate efficiently
|
||||
5. **Thumbnail Generation**: Thumbnails are generated on-demand and cached for performance
|
||||
|
||||
## Differences from Original
|
||||
|
||||
This version simplifies the original ComfyUI_Local_Image_Gallery by removing:
|
||||
- Tag filtering and management
|
||||
- Rating system
|
||||
- Global tag search
|
||||
- Metadata editing capabilities
|
||||
|
||||
These features were removed to focus on the core functionality of browsing and selecting files, making the tool simpler and more straightforward to use.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Directory Not Loading**
|
||||
- Verify the path exists and you have read permissions
|
||||
- Check for special characters in the path
|
||||
- Try using absolute paths instead of relative ones
|
||||
|
||||
**Thumbnails Not Showing**
|
||||
- Ensure the files are in supported formats
|
||||
- Check if the images are corrupted
|
||||
- Try refreshing the gallery
|
||||
|
||||
**Large Directories Slow to Load**
|
||||
- Use sorting and pagination to manage large folders
|
||||
- Consider organizing files into subdirectories
|
||||
- Enable only the media types you need (images, videos, audio)
|
||||
|
||||
## Technical Details
|
||||
|
||||
The node creates a visual widget that runs in the ComfyUI interface and communicates with the backend through API endpoints to:
|
||||
- List directory contents
|
||||
- Generate thumbnails
|
||||
- Save user preferences
|
||||
- Handle file selection
|
||||
|
||||
All file operations are performed server-side for security, with proper path validation to prevent directory traversal attacks.
|
||||
@@ -0,0 +1,264 @@
|
||||
# LoRA Folder Batch
|
||||
|
||||
## Overview
|
||||
The **LoRA Folder Batch** node automates the process of testing multiple LoRA models from a folder. This tool was adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode) and enhanced with batch processing capabilities for efficient LoRA evaluation workflows.
|
||||
|
||||
## Attribution
|
||||
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
|
||||
|
||||
## Features
|
||||
- **Automatic Folder Scanning**: Discovers all .safetensors files in specified folders
|
||||
- **Natural Sorting**: Intelligently sorts epochs (e.g., epoch_004, epoch_020, epoch_100)
|
||||
- **Pattern Filtering**: Include/exclude LoRAs using regex patterns
|
||||
- **Flexible Strength Control**: Single, multiple, or range-based strength values
|
||||
- **Batch Modes**: Sequential or combinatorial strength application
|
||||
- **Epoch Detection**: Automatically extracts epoch numbers from filenames
|
||||
- **Auto-Batching**: Automatically splits large LoRA collections into manageable chunks to prevent UI disconnection
|
||||
|
||||
## Node Properties
|
||||
- **Category**: `ComfyAssets/🧰 xyz-helpers`
|
||||
- **Node Name**: `LoRAFolderBatch`
|
||||
- **Function**: `batch_loras`
|
||||
|
||||
## Inputs
|
||||
|
||||
### Required
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `folder_path` | STRING | "." | Folder path relative to models/loras (or absolute) |
|
||||
| `strength` | STRING | "1.0" | Strength values (see formats below) |
|
||||
| `batch_mode` | DROPDOWN | sequential | [sequential, combinatorial] processing mode |
|
||||
|
||||
### Optional
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `include_pattern` | STRING | "" | Regex pattern to include files |
|
||||
| `exclude_pattern` | STRING | "" | Regex pattern to exclude files |
|
||||
| `max_loras` | INT | 50 | Maximum LoRAs to process (when auto_batch disabled) |
|
||||
| `sort_order` | DROPDOWN | natural | Sorting method [natural, alphabetical, newest, oldest] |
|
||||
| `auto_batch` | DROPDOWN | disabled | Enable auto-batching for large collections [disabled, enabled] |
|
||||
| `batch_size` | INT | 25 | Number of LoRAs per batch when auto-batching |
|
||||
| `batch_index` | INT | 0 | Which batch to output (0-based) when auto-batching |
|
||||
|
||||
### Strength Format Options
|
||||
- **Single**: `"1.0"` - Apply same strength to all LoRAs
|
||||
- **Multiple**: `"0.5, 0.75, 1.0"` - Comma-separated values
|
||||
- **Range**: `"0.5...1.0+0.25"` - Start...End+Step format
|
||||
|
||||
## Outputs
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `lora_params` | LORA_PARAMS | Batch parameters for processing |
|
||||
| `lora_list` | STRING | List of discovered LoRAs with epoch info |
|
||||
| `lora_count` | INT | Number of LoRAs found |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Test All Epochs of a LoRA
|
||||
```
|
||||
LoRAFolderBatch → FluxSamplerParams → KSampler
|
||||
folder_path: "my_lora_training"
|
||||
strength: "1.0"
|
||||
batch_mode: sequential
|
||||
```
|
||||
|
||||
### Strength Testing for Each LoRA
|
||||
```
|
||||
LoRAFolderBatch → KSampler → Image Grid
|
||||
folder_path: "test_loras"
|
||||
strength: "0.5, 0.75, 1.0"
|
||||
batch_mode: combinatorial
|
||||
```
|
||||
|
||||
### Filter Specific Epochs
|
||||
```
|
||||
LoRAFolderBatch → Processing Pipeline
|
||||
folder_path: "training_results"
|
||||
include_pattern: "epoch_0[2-5]0"
|
||||
strength: "0.8...1.2+0.1"
|
||||
```
|
||||
|
||||
### Auto-Batch Large Collections
|
||||
```
|
||||
LoRAFolderBatch → FluxSamplerParams → KSampler
|
||||
folder_path: "massive_lora_collection" # 100+ files
|
||||
strength: "1.0"
|
||||
auto_batch: enabled
|
||||
batch_size: 25
|
||||
batch_index: 0 # Change to 1, 2, 3... for subsequent batches
|
||||
```
|
||||
|
||||
## Batch Modes Explained
|
||||
|
||||
### Sequential Mode
|
||||
Each LoRA gets one strength value in order:
|
||||
- LoRA1 → strength[0]
|
||||
- LoRA2 → strength[1]
|
||||
- LoRA3 → strength[0] (cycles if fewer strengths than LoRAs)
|
||||
|
||||
### Combinatorial Mode
|
||||
Each LoRA is tested with ALL strength values:
|
||||
- LoRA1 → [0.5, 0.75, 1.0]
|
||||
- LoRA2 → [0.5, 0.75, 1.0]
|
||||
- LoRA3 → [0.5, 0.75, 1.0]
|
||||
|
||||
## Auto-Batching for Large Collections
|
||||
|
||||
### Overview
|
||||
When testing large numbers of LoRAs (e.g., 75+ files), ComfyUI can experience UI disconnections or memory issues. Auto-batching solves this by automatically splitting your LoRA collection into smaller, manageable chunks.
|
||||
|
||||
### How It Works
|
||||
1. **Enable Auto-Batching**: Set `auto_batch` to "enabled"
|
||||
2. **Set Batch Size**: Configure `batch_size` (default: 25, range: 5-100)
|
||||
3. **Select Batch**: Use `batch_index` to choose which batch to process
|
||||
|
||||
### Example: Testing 75 LoRAs
|
||||
With 75 LoRAs and batch_size=25, the system creates 3 batches:
|
||||
- **Batch 0**: LoRAs 1-25 (set batch_index=0)
|
||||
- **Batch 1**: LoRAs 26-50 (set batch_index=1)
|
||||
- **Batch 2**: LoRAs 51-75 (set batch_index=2)
|
||||
|
||||
Run your workflow 3 times, changing only the `batch_index` each time.
|
||||
|
||||
### Visual Feedback
|
||||
When auto-batching is enabled, the `lora_list` output includes batch information:
|
||||
```
|
||||
=== Batch 1/3 (LoRAs 1-25) ===
|
||||
|
||||
style-epoch-001
|
||||
style-epoch-002
|
||||
...
|
||||
```
|
||||
|
||||
### Best Practices for Auto-Batching
|
||||
1. **Start with Default**: Use batch_size=25 for most scenarios
|
||||
2. **Adjust for Memory**: Decrease batch_size if you still experience issues
|
||||
3. **Combinatorial Mode**: Be extra careful - 25 LoRAs × 3 strengths = 75 combinations
|
||||
4. **Save Between Batches**: Save your results after each batch to avoid data loss
|
||||
5. **Use Plot Parameters**: The batch info appears in plot visualizations for easy tracking
|
||||
|
||||
## File Naming Patterns
|
||||
|
||||
### Supported Epoch Formats
|
||||
- `model-v1-000004.safetensors` → Epoch 4
|
||||
- `style_epoch_020.safetensors` → Epoch 20
|
||||
- `lora-000100.safetensors` → Epoch 100
|
||||
|
||||
### Natural Sorting Examples
|
||||
Files are sorted intelligently:
|
||||
1. `model-000004.safetensors`
|
||||
2. `model-000020.safetensors`
|
||||
3. `model-000100.safetensors`
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Folder Organization
|
||||
```
|
||||
models/loras/
|
||||
├── my_style/
|
||||
│ ├── style-000010.safetensors
|
||||
│ ├── style-000020.safetensors
|
||||
│ └── style-000030.safetensors
|
||||
└── character/
|
||||
├── char-v2-000005.safetensors
|
||||
└── char-v2-000010.safetensors
|
||||
```
|
||||
|
||||
### Testing Workflows
|
||||
1. **Initial Testing**: Use single strength (1.0) to evaluate all epochs
|
||||
2. **Fine-tuning**: Use combinatorial mode with multiple strengths
|
||||
3. **Final Selection**: Filter to specific epochs and test strength range
|
||||
|
||||
### Pattern Filtering Examples
|
||||
```python
|
||||
# Include only specific versions
|
||||
include_pattern: "v2|v3"
|
||||
|
||||
# Exclude test/backup files
|
||||
exclude_pattern: "test|backup|old"
|
||||
|
||||
# Include specific epoch range
|
||||
include_pattern: "epoch_0[3-7]0"
|
||||
```
|
||||
|
||||
## Integration with Other Nodes
|
||||
|
||||
### Common Pipelines
|
||||
1. **LoRA Comparison Grid**:
|
||||
```
|
||||
LoRAFolderBatch → KSampler → Image Grid → Save
|
||||
```
|
||||
|
||||
2. **Strength Testing**:
|
||||
```
|
||||
LoRAFolderBatch → PlotParameters → Graph Display
|
||||
```
|
||||
|
||||
3. **Combined with FLUX**:
|
||||
```
|
||||
LoRAFolderBatch → FluxSamplerParams → KSampler
|
||||
```
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Memory Management
|
||||
- Start with fewer LoRAs when testing combinatorial mode
|
||||
- Use sequential mode for initial epoch evaluation
|
||||
- Clear LoRA cache between large batch runs
|
||||
|
||||
### Optimal Strength Ranges
|
||||
- **Style LoRAs**: 0.5-1.0
|
||||
- **Character LoRAs**: 0.7-1.2
|
||||
- **Detail LoRAs**: 0.3-0.7
|
||||
|
||||
### Debugging
|
||||
- Check `lora_list` output to verify correct files were found
|
||||
- Use `lora_count` to confirm expected number of LoRAs
|
||||
- Test patterns with include/exclude before full runs
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No LoRAs Found
|
||||
- Verify folder path (relative to models/loras or use absolute)
|
||||
- Check file extensions (.safetensors)
|
||||
- Test without filters first
|
||||
|
||||
### Pattern Not Working
|
||||
- Patterns use Python regex syntax
|
||||
- Test patterns in regex tester first
|
||||
- Case-sensitive by default
|
||||
|
||||
### Memory Issues
|
||||
- Reduce batch_count in combinatorial mode
|
||||
- Process LoRAs in smaller groups
|
||||
- Use sequential mode for large sets
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Multi-Version Testing
|
||||
```python
|
||||
# Test different versions at different strengths
|
||||
folder_path: "character_loras"
|
||||
include_pattern: "v[1-3]"
|
||||
strength: "0.6, 0.8, 1.0"
|
||||
batch_mode: combinatorial
|
||||
```
|
||||
|
||||
### Epoch Progression Analysis
|
||||
```python
|
||||
# Test every 10th epoch
|
||||
folder_path: "training_output"
|
||||
include_pattern: "0[0-9]0\\.safetensors$"
|
||||
strength: "1.0"
|
||||
batch_mode: sequential
|
||||
```
|
||||
|
||||
## Version History
|
||||
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
|
||||
- **1.0.1**: Added natural sorting for epochs
|
||||
- **1.0.2**: Enhanced pattern filtering
|
||||
- **1.0.3**: Improved batch modes and strength parsing
|
||||
- **1.0.4**: Added auto-batching for large LoRA collections
|
||||
|
||||
## Credits
|
||||
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
|
||||
@@ -0,0 +1,234 @@
|
||||
# Plot Parameters
|
||||
|
||||
## Overview
|
||||
The **Plot Parameters** node creates visual graphs and plots from sampler parameters, enabling data-driven analysis of generation settings. Adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode), this tool helps visualize the relationship between parameters and output quality.
|
||||
|
||||
## Attribution
|
||||
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
|
||||
|
||||
## Features
|
||||
- **Multi-Parameter Plotting**: Visualize multiple parameters simultaneously
|
||||
- **Comparison Graphs**: Compare settings across batch runs
|
||||
- **Statistical Analysis**: Calculate means, deviations, and trends
|
||||
- **Export Capabilities**: Save plots as images or data files
|
||||
- **Real-time Updates**: Dynamic graph generation during workflow execution
|
||||
|
||||
## Node Properties
|
||||
- **Category**: `ComfyAssets/🧰 xyz-helpers`
|
||||
- **Node Name**: `PlotParameters`
|
||||
- **Function**: `plot`
|
||||
|
||||
## Inputs
|
||||
|
||||
### Required
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `sampler_params` | SAMPLER_PARAMS | - | Parameters to plot |
|
||||
| `plot_type` | DROPDOWN | line | [line, bar, scatter, heatmap] |
|
||||
| `x_axis` | DROPDOWN | steps | Parameter for X axis |
|
||||
| `y_axis` | DROPDOWN | quality | Metric for Y axis |
|
||||
|
||||
### Optional
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `title` | STRING | "Parameter Analysis" | Graph title |
|
||||
| `show_grid` | BOOLEAN | True | Display grid lines |
|
||||
| `show_legend` | BOOLEAN | True | Display legend |
|
||||
| `color_scheme` | DROPDOWN | default | Color palette selection |
|
||||
|
||||
## Outputs
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `plot_image` | IMAGE | Generated plot as image |
|
||||
| `data_csv` | STRING | Plot data in CSV format |
|
||||
| `statistics` | STRING | Statistical summary |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Parameter Visualization
|
||||
```
|
||||
FluxSamplerParams → PlotParameters → Display Image
|
||||
plot_type: line
|
||||
x_axis: steps
|
||||
y_axis: guidance
|
||||
```
|
||||
|
||||
### Batch Comparison Plot
|
||||
```
|
||||
LoRAFolderBatch → PlotParameters → Save Image
|
||||
plot_type: scatter
|
||||
x_axis: lora_strength
|
||||
y_axis: quality_score
|
||||
```
|
||||
|
||||
### Heatmap Analysis
|
||||
```
|
||||
Parameter Grid → PlotParameters → Analysis Display
|
||||
plot_type: heatmap
|
||||
x_axis: cfg
|
||||
y_axis: steps
|
||||
```
|
||||
|
||||
## Plot Types Explained
|
||||
|
||||
### Line Plot
|
||||
- Best for continuous parameter changes
|
||||
- Shows trends and relationships
|
||||
- Ideal for time series or progression
|
||||
|
||||
### Bar Chart
|
||||
- Compares discrete values
|
||||
- Good for categorical comparisons
|
||||
- Shows distribution clearly
|
||||
|
||||
### Scatter Plot
|
||||
- Reveals correlations
|
||||
- Identifies outliers
|
||||
- Best for large datasets
|
||||
|
||||
### Heatmap
|
||||
- Two-dimensional parameter analysis
|
||||
- Color-coded intensity values
|
||||
- Perfect for grid searches
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Parameter Selection
|
||||
- Choose related parameters for meaningful plots
|
||||
- Use consistent scales for comparison
|
||||
- Consider parameter ranges when plotting
|
||||
|
||||
### Visual Clarity
|
||||
- Limit number of series to 5-7 for readability
|
||||
- Use contrasting colors for multiple lines
|
||||
- Enable grid for precise value reading
|
||||
|
||||
### Data Analysis
|
||||
```python
|
||||
# Effective parameter combinations
|
||||
x_axis: "guidance"
|
||||
y_axis: "perceived_quality"
|
||||
|
||||
# Step efficiency analysis
|
||||
x_axis: "steps"
|
||||
y_axis: "generation_time"
|
||||
|
||||
# LoRA impact assessment
|
||||
x_axis: "lora_strength"
|
||||
y_axis: "style_adherence"
|
||||
```
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Complete Analysis Pipeline
|
||||
```
|
||||
1. Generate with parameters
|
||||
2. Plot results
|
||||
3. Export data
|
||||
4. Statistical analysis
|
||||
```
|
||||
|
||||
### Multi-Plot Workflow
|
||||
```
|
||||
Params → Plot1 (steps vs quality)
|
||||
↘ Plot2 (guidance vs coherence)
|
||||
↘ Plot3 (strength vs style)
|
||||
→ Combined Analysis
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Metrics
|
||||
- Define custom Y-axis metrics
|
||||
- Import external quality scores
|
||||
- Calculate derived values
|
||||
|
||||
### Export Options
|
||||
- PNG/SVG image formats
|
||||
- CSV data export
|
||||
- JSON statistics export
|
||||
|
||||
### Styling Options
|
||||
```python
|
||||
# Professional presentation
|
||||
color_scheme: "scientific"
|
||||
show_grid: True
|
||||
show_legend: True
|
||||
|
||||
# Minimal style
|
||||
color_scheme: "minimal"
|
||||
show_grid: False
|
||||
show_legend: False
|
||||
```
|
||||
|
||||
## Statistical Analysis
|
||||
|
||||
### Available Metrics
|
||||
- Mean, Median, Mode
|
||||
- Standard Deviation
|
||||
- Correlation Coefficients
|
||||
- Trend Lines
|
||||
- R-squared Values
|
||||
|
||||
### Interpretation Guide
|
||||
- **Positive Correlation**: Parameters increase together
|
||||
- **Negative Correlation**: Inverse relationship
|
||||
- **No Correlation**: Independent parameters
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Optimal Visualization
|
||||
1. Start with scatter plots for exploration
|
||||
2. Use line plots for trends
|
||||
3. Apply heatmaps for 2D parameter spaces
|
||||
4. Bar charts for final comparisons
|
||||
|
||||
### Data Preparation
|
||||
- Normalize scales when comparing different metrics
|
||||
- Remove outliers for cleaner plots
|
||||
- Group similar parameters
|
||||
|
||||
### Performance Tips
|
||||
- Cache plot images for repeated viewing
|
||||
- Export data for external analysis
|
||||
- Use lower resolution for preview plots
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Empty Plots
|
||||
- Verify sampler_params contains data
|
||||
- Check axis parameter selection
|
||||
- Ensure valid parameter ranges
|
||||
|
||||
### Scaling Issues
|
||||
- Use logarithmic scale for wide ranges
|
||||
- Normalize data if needed
|
||||
- Adjust plot dimensions
|
||||
|
||||
### Export Problems
|
||||
- Check file permissions
|
||||
- Verify export path exists
|
||||
- Ensure sufficient disk space
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Hyperparameter Optimization
|
||||
Track and visualize the effect of different sampling parameters on output quality.
|
||||
|
||||
### LoRA Strength Analysis
|
||||
Plot the relationship between LoRA strength and style transfer effectiveness.
|
||||
|
||||
### Efficiency Studies
|
||||
Analyze generation time vs quality trade-offs across different settings.
|
||||
|
||||
### Batch Comparison
|
||||
Compare multiple generation runs to identify optimal parameters.
|
||||
|
||||
## Version History
|
||||
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
|
||||
- **1.0.1**: Added heatmap visualization
|
||||
- **1.0.2**: Enhanced statistical analysis
|
||||
- **1.0.3**: Improved export capabilities
|
||||
|
||||
## Credits
|
||||
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
|
||||
@@ -98,4 +98,4 @@ The Resolution Calculator integrates seamlessly with:
|
||||
- Standard ComfyUI image loaders
|
||||
- VAE encode/decode operations
|
||||
- Upscaler nodes (ESRGAN, Real-ESRGAN, etc.)
|
||||
- Custom latent processing workflows
|
||||
- Custom latent processing workflows
|
||||
|
||||
@@ -40,7 +40,7 @@ The Sampler Combo is a unified ComfyUI node that combines sampler, scheduler, st
|
||||
|
||||
### Outputs
|
||||
- **sampler_name**: Selected sampler algorithm
|
||||
- **scheduler**: Selected scheduler algorithm
|
||||
- **scheduler**: Selected scheduler algorithm
|
||||
- **steps**: Number of sampling steps
|
||||
- **cfg**: CFG scale value
|
||||
|
||||
@@ -75,7 +75,7 @@ The Sampler Combo is a unified ComfyUI node that combines sampler, scheduler, st
|
||||
- **linear**: Basic linear distribution
|
||||
- **sgm_uniform**: Uniform distribution
|
||||
|
||||
### Advanced Schedulers
|
||||
### Advanced Schedulers
|
||||
- **karras**: Karras noise schedule (recommended)
|
||||
- **exponential**: Exponential decay
|
||||
- **polyexponential**: Polynomial exponential
|
||||
@@ -99,7 +99,7 @@ Steps: 15-25
|
||||
CFG: 6.0-8.0
|
||||
```
|
||||
|
||||
#### Quality Optimized
|
||||
#### Quality Optimized
|
||||
```
|
||||
Sampler: dpmpp_2m_sde or dpmpp_3m_sde
|
||||
Scheduler: karras
|
||||
@@ -138,7 +138,7 @@ CFG: 7.0-8.5
|
||||
### Basic Configuration
|
||||
```
|
||||
sampler_name: euler
|
||||
scheduler: normal
|
||||
scheduler: normal
|
||||
steps: 20
|
||||
cfg: 7.0
|
||||
```
|
||||
@@ -164,7 +164,7 @@ cfg: 6.5
|
||||
### Compatibility Analysis
|
||||
The node provides real-time analysis of parameter compatibility:
|
||||
- Scheduler compatibility with selected sampler
|
||||
- Steps optimization for sampler type
|
||||
- Steps optimization for sampler type
|
||||
- CFG scale recommendations
|
||||
- Performance impact assessment
|
||||
|
||||
@@ -200,9 +200,9 @@ The node provides real-time analysis of parameter compatibility:
|
||||
|
||||
The Sampler Combo node outputs are compatible with all standard ComfyUI sampling nodes:
|
||||
- KSampler
|
||||
- KSamplerAdvanced
|
||||
- KSamplerAdvanced
|
||||
- Custom sampling workflows
|
||||
- Upscaling pipelines
|
||||
- Img2img workflows
|
||||
|
||||
Connect the outputs directly to your sampling node inputs for streamlined configuration.
|
||||
Connect the outputs directly to your sampling node inputs for streamlined configuration.
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
# Sampler Select Helper
|
||||
|
||||
## Overview
|
||||
The **Sampler Select Helper** node provides intelligent sampler selection with model-specific recommendations and compatibility checking. Adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode), this tool ensures optimal sampler-scheduler combinations for different model architectures.
|
||||
|
||||
## Attribution
|
||||
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
|
||||
|
||||
## Features
|
||||
- **Model-Aware Selection**: Automatic recommendations based on model type
|
||||
- **Compatibility Validation**: Ensures sampler-scheduler pairs work well together
|
||||
- **Performance Profiles**: Pre-configured settings for quality vs speed
|
||||
- **Dynamic Updates**: Adapts to newly available samplers
|
||||
- **Batch Support**: Test multiple samplers in sequence
|
||||
|
||||
## Node Properties
|
||||
- **Category**: `ComfyAssets/🧰 xyz-helpers`
|
||||
- **Node Name**: `SamplerSelectHelper`
|
||||
- **Function**: `select_sampler`
|
||||
|
||||
## Inputs
|
||||
|
||||
### Required
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `model_type` | DROPDOWN | auto | [auto, sdxl, sd15, flux, custom] |
|
||||
| `quality_preset` | DROPDOWN | balanced | [fast, balanced, quality, extreme] |
|
||||
| `sampler_override` | DROPDOWN | auto | Specific sampler selection |
|
||||
|
||||
### Optional
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `scheduler_override` | DROPDOWN | auto | Specific scheduler selection |
|
||||
| `model_name` | STRING | - | Model name for auto-detection |
|
||||
| `custom_rules` | STRING | - | JSON rules for custom selection |
|
||||
|
||||
## Outputs
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `sampler_name` | STRING | Selected sampler |
|
||||
| `scheduler` | STRING | Selected scheduler |
|
||||
| `recommended_steps` | INT | Suggested step count |
|
||||
| `recommended_cfg` | FLOAT | Suggested CFG scale |
|
||||
|
||||
## Model-Specific Recommendations
|
||||
|
||||
### SDXL Models
|
||||
```python
|
||||
quality_preset: "balanced"
|
||||
→ sampler: "dpmpp_2m"
|
||||
→ scheduler: "karras"
|
||||
→ steps: 25
|
||||
→ cfg: 7.0
|
||||
```
|
||||
|
||||
### SD 1.5 Models
|
||||
```python
|
||||
quality_preset: "quality"
|
||||
→ sampler: "dpmpp_2m_sde"
|
||||
→ scheduler: "exponential"
|
||||
→ steps: 30
|
||||
→ cfg: 7.5
|
||||
```
|
||||
|
||||
### FLUX Models
|
||||
```python
|
||||
quality_preset: "fast"
|
||||
→ sampler: "euler"
|
||||
→ scheduler: "simple"
|
||||
→ steps: 15
|
||||
→ cfg: 3.5
|
||||
```
|
||||
|
||||
## Quality Presets Explained
|
||||
|
||||
### Fast (Preview)
|
||||
- **Goal**: Quick iterations
|
||||
- **Steps**: 10-15
|
||||
- **Samplers**: euler, dpm_fast
|
||||
- **Use Case**: Testing prompts
|
||||
|
||||
### Balanced (Default)
|
||||
- **Goal**: Good quality/speed ratio
|
||||
- **Steps**: 20-25
|
||||
- **Samplers**: dpmpp_2m, dpmpp_2m_sde
|
||||
- **Use Case**: Regular generation
|
||||
|
||||
### Quality
|
||||
- **Goal**: Best visual quality
|
||||
- **Steps**: 30-40
|
||||
- **Samplers**: dpmpp_3m_sde, dpm_adaptive
|
||||
- **Use Case**: Final renders
|
||||
|
||||
### Extreme
|
||||
- **Goal**: Maximum quality
|
||||
- **Steps**: 50-100
|
||||
- **Samplers**: dpm_adaptive, dpmpp_3m_sde
|
||||
- **Use Case**: Hero images
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Auto Model Detection
|
||||
```
|
||||
Load Model → SamplerSelectHelper → KSampler
|
||||
model_type: auto
|
||||
quality_preset: balanced
|
||||
```
|
||||
|
||||
### Custom Override
|
||||
```
|
||||
SamplerSelectHelper → KSampler
|
||||
sampler_override: "dpmpp_3m_sde"
|
||||
scheduler_override: "exponential"
|
||||
```
|
||||
|
||||
### Batch Testing
|
||||
```
|
||||
SamplerSelectHelper → Batch Process
|
||||
quality_preset: [fast, balanced, quality]
|
||||
→ Compare outputs
|
||||
```
|
||||
|
||||
## Compatibility Matrix
|
||||
|
||||
### Recommended Combinations
|
||||
| Sampler | Best Schedulers | Avoid |
|
||||
|---------|----------------|--------|
|
||||
| euler | normal, karras | sgm_uniform |
|
||||
| euler_a | normal, karras | simple |
|
||||
| dpmpp_2m | karras, exponential | - |
|
||||
| dpmpp_2m_sde | karras, exponential | simple |
|
||||
| dpmpp_3m_sde | exponential | simple |
|
||||
| dpm_adaptive | normal | karras |
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Model Type Detection
|
||||
1. Use `auto` for automatic detection
|
||||
2. Override only when necessary
|
||||
3. Provide model_name for better accuracy
|
||||
|
||||
### Performance Optimization
|
||||
```python
|
||||
# Quick preview workflow
|
||||
quality_preset: "fast"
|
||||
→ 10 steps, euler sampler
|
||||
|
||||
# Final production
|
||||
quality_preset: "quality"
|
||||
→ 35 steps, dpmpp_3m_sde
|
||||
|
||||
# Experimental/artistic
|
||||
quality_preset: "extreme"
|
||||
→ 75 steps, dpm_adaptive
|
||||
```
|
||||
|
||||
### Custom Rules Format
|
||||
```json
|
||||
{
|
||||
"model_pattern": "anime.*",
|
||||
"sampler": "dpmpp_2m_sde",
|
||||
"scheduler": "karras",
|
||||
"steps": 28,
|
||||
"cfg": 7.0
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with Other Nodes
|
||||
|
||||
### Complete Pipeline
|
||||
```
|
||||
Model Loader → SamplerSelectHelper → KSampler
|
||||
↘ FluxSamplerParams ↗
|
||||
```
|
||||
|
||||
### A/B Testing
|
||||
```
|
||||
SamplerSelectHelper → KSampler → Image A
|
||||
quality: fast
|
||||
SamplerSelectHelper → KSampler → Image B
|
||||
quality: quality
|
||||
→ Compare Results
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Dynamic Sampler Discovery
|
||||
- Automatically detects new samplers
|
||||
- Updates compatibility matrix
|
||||
- Maintains optimal pairings
|
||||
|
||||
### Performance Profiling
|
||||
- Tracks generation times
|
||||
- Suggests optimal settings
|
||||
- Adapts to hardware capabilities
|
||||
|
||||
### Model Fingerprinting
|
||||
- Identifies model architecture
|
||||
- Applies specific optimizations
|
||||
- Learns from usage patterns
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Speed vs Quality
|
||||
1. Start with "fast" for prompt testing
|
||||
2. Move to "balanced" for iteration
|
||||
3. Use "quality" for final output
|
||||
4. Reserve "extreme" for special cases
|
||||
|
||||
### Sampler Selection Logic
|
||||
```python
|
||||
if model_type == "flux":
|
||||
prefer ["euler", "dpmpp_2m"]
|
||||
elif model_type == "sdxl":
|
||||
prefer ["dpmpp_2m_sde", "dpmpp_3m_sde"]
|
||||
else:
|
||||
use ["dpmpp_2m", "euler_a"]
|
||||
```
|
||||
|
||||
### Memory Considerations
|
||||
- Fast presets use less memory
|
||||
- Extreme presets may require more VRAM
|
||||
- Adaptive samplers adjust dynamically
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Wrong Sampler Selected
|
||||
- Check model_type setting
|
||||
- Verify model detection
|
||||
- Use manual override if needed
|
||||
|
||||
### Poor Quality Output
|
||||
- Increase quality preset
|
||||
- Check recommended steps
|
||||
- Verify CFG scale
|
||||
|
||||
### Performance Issues
|
||||
- Start with fast preset
|
||||
- Reduce step count
|
||||
- Try simpler samplers
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Model Comparison
|
||||
Test same prompt across different models with optimal settings for each.
|
||||
|
||||
### Quality Ladder
|
||||
Progress from fast to extreme quality to find optimal balance.
|
||||
|
||||
### Sampler Shootout
|
||||
Compare all compatible samplers for specific model/prompt combination.
|
||||
|
||||
## Version History
|
||||
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
|
||||
- **1.0.1**: Added FLUX model support
|
||||
- **1.0.2**: Enhanced compatibility matrix
|
||||
- **1.0.3**: Improved auto-detection
|
||||
|
||||
## Credits
|
||||
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
|
||||
@@ -0,0 +1,300 @@
|
||||
# Scheduler Select Helper
|
||||
|
||||
## Overview
|
||||
The **Scheduler Select Helper** node provides intelligent scheduler selection with sampler-aware recommendations and model-specific optimizations. Adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode), this tool ensures optimal scheduler selection for different sampling algorithms and models.
|
||||
|
||||
## Attribution
|
||||
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
|
||||
|
||||
## Features
|
||||
- **Sampler-Aware Selection**: Recommends best schedulers for each sampler
|
||||
- **Model Optimization**: Specific scheduler tuning for different models
|
||||
- **Noise Schedule Profiles**: Pre-configured curves for various use cases
|
||||
- **Visual Feedback**: Preview noise schedules
|
||||
- **Batch Testing**: Compare multiple schedulers
|
||||
|
||||
## Node Properties
|
||||
- **Category**: `ComfyAssets/🧰 xyz-helpers`
|
||||
- **Node Name**: `SchedulerSelectHelper`
|
||||
- **Function**: `select_scheduler`
|
||||
|
||||
## Inputs
|
||||
|
||||
### Required
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `sampler_name` | STRING | - | Current sampler being used |
|
||||
| `model_type` | DROPDOWN | auto | [auto, sdxl, sd15, flux] |
|
||||
| `schedule_type` | DROPDOWN | smooth | [smooth, sharp, linear, custom] |
|
||||
|
||||
### Optional
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `override` | DROPDOWN | none | Force specific scheduler |
|
||||
| `beta_schedule` | STRING | - | Custom beta schedule values |
|
||||
| `visualize` | BOOLEAN | False | Show schedule curve |
|
||||
|
||||
## Outputs
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `scheduler` | STRING | Selected scheduler name |
|
||||
| `schedule_curve` | IMAGE | Visualization of noise schedule |
|
||||
| `beta_values` | FLOAT_ARRAY | Beta schedule values |
|
||||
|
||||
## Scheduler Types Explained
|
||||
|
||||
### Normal
|
||||
- **Curve**: Linear noise reduction
|
||||
- **Best For**: General purpose
|
||||
- **Samplers**: euler, dpm_fast
|
||||
|
||||
### Karras
|
||||
- **Curve**: Improved noise schedule
|
||||
- **Best For**: High quality
|
||||
- **Samplers**: dpmpp_2m, dpmpp_2m_sde
|
||||
|
||||
### Exponential
|
||||
- **Curve**: Exponential decay
|
||||
- **Best For**: Fine details
|
||||
- **Samplers**: dpmpp_3m_sde
|
||||
|
||||
### Simple
|
||||
- **Curve**: Basic linear
|
||||
- **Best For**: Fast generation
|
||||
- **Samplers**: euler, lcm
|
||||
|
||||
### SGM Uniform
|
||||
- **Curve**: Uniform distribution
|
||||
- **Best For**: FLUX models
|
||||
- **Samplers**: euler, dpmpp_2m
|
||||
|
||||
## Schedule Types
|
||||
|
||||
### Smooth (Default)
|
||||
```python
|
||||
# Gradual noise reduction
|
||||
# Good for most content
|
||||
→ karras or exponential
|
||||
```
|
||||
|
||||
### Sharp
|
||||
```python
|
||||
# Aggressive early reduction
|
||||
# Good for high contrast
|
||||
→ normal or simple
|
||||
```
|
||||
|
||||
### Linear
|
||||
```python
|
||||
# Constant reduction rate
|
||||
# Predictable results
|
||||
→ normal
|
||||
```
|
||||
|
||||
### Custom
|
||||
```python
|
||||
# User-defined curve
|
||||
# Advanced control
|
||||
→ based on beta_schedule
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Automatic Selection
|
||||
```
|
||||
KSampler Settings → SchedulerSelectHelper → KSampler
|
||||
sampler_name: "dpmpp_2m_sde"
|
||||
model_type: auto
|
||||
→ scheduler: "karras"
|
||||
```
|
||||
|
||||
### Visual Comparison
|
||||
```
|
||||
SchedulerSelectHelper → Display
|
||||
visualize: True
|
||||
→ Shows noise schedule curve
|
||||
```
|
||||
|
||||
### Batch Testing
|
||||
```
|
||||
For each scheduler:
|
||||
SchedulerSelectHelper → KSampler → Save
|
||||
→ Compare results
|
||||
```
|
||||
|
||||
## Sampler-Scheduler Compatibility
|
||||
|
||||
### Optimal Pairings
|
||||
| Sampler | Best Scheduler | Good Alternatives |
|
||||
|---------|---------------|-------------------|
|
||||
| euler | normal | karras |
|
||||
| euler_a | karras | normal |
|
||||
| heun | normal | - |
|
||||
| dpm_fast | normal | simple |
|
||||
| dpm_adaptive | normal | - |
|
||||
| dpmpp_2m | karras | exponential |
|
||||
| dpmpp_2m_sde | karras | exponential |
|
||||
| dpmpp_3m_sde | exponential | karras |
|
||||
| dpmpp_2s_a | karras | normal |
|
||||
| lcm | simple | normal |
|
||||
|
||||
## Model-Specific Recommendations
|
||||
|
||||
### SDXL
|
||||
```python
|
||||
preferred_schedulers = ["karras", "exponential"]
|
||||
# Better convergence for high-res
|
||||
```
|
||||
|
||||
### SD 1.5
|
||||
```python
|
||||
preferred_schedulers = ["karras", "normal"]
|
||||
# Classic combinations
|
||||
```
|
||||
|
||||
### FLUX
|
||||
```python
|
||||
preferred_schedulers = ["simple", "sgm_uniform"]
|
||||
# Optimized for FLUX architecture
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Selection Strategy
|
||||
1. Let auto-detection handle defaults
|
||||
2. Override for specific artistic goals
|
||||
3. Test multiple schedulers for hero images
|
||||
4. Use visualization to understand curves
|
||||
|
||||
### Performance Tips
|
||||
- Simple/normal for quick previews
|
||||
- Karras/exponential for quality
|
||||
- SGM uniform specifically for FLUX
|
||||
- Match scheduler to sampler type
|
||||
|
||||
### Testing Workflow
|
||||
```python
|
||||
schedulers = ["normal", "karras", "exponential"]
|
||||
for scheduler in schedulers:
|
||||
generate_image(scheduler)
|
||||
save_with_metadata(scheduler)
|
||||
compare_results()
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Beta Schedule Customization
|
||||
```python
|
||||
# Custom exponential curve
|
||||
beta_schedule = "0.00085, 0.0012, 0.0018, ..."
|
||||
|
||||
# Sharp early reduction
|
||||
beta_schedule = "0.001, 0.002, 0.004, 0.006, ..."
|
||||
```
|
||||
|
||||
### Schedule Visualization
|
||||
- Plots noise reduction curve
|
||||
- Shows sigma values
|
||||
- Compares with standard schedules
|
||||
- Exports schedule data
|
||||
|
||||
### Adaptive Selection
|
||||
- Learns from user preferences
|
||||
- Adapts to hardware capabilities
|
||||
- Optimizes for generation speed
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Complete Pipeline
|
||||
```
|
||||
Sampler Combo → SchedulerSelectHelper → KSampler
|
||||
↓ ↓
|
||||
sampler_name → Optimal scheduler selection
|
||||
```
|
||||
|
||||
### A/B Testing
|
||||
```
|
||||
Same prompt → Different schedulers → Grid comparison
|
||||
normal vs karras vs exponential
|
||||
```
|
||||
|
||||
### Noise Schedule Analysis
|
||||
```
|
||||
SchedulerSelectHelper → Plot Parameters
|
||||
visualize: True
|
||||
→ Analyze noise curves
|
||||
```
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Quality Optimization
|
||||
```python
|
||||
# For maximum quality
|
||||
if sampler in ["dpmpp_3m_sde"]:
|
||||
use scheduler="exponential"
|
||||
elif sampler in ["dpmpp_2m_sde"]:
|
||||
use scheduler="karras"
|
||||
```
|
||||
|
||||
### Speed Optimization
|
||||
```python
|
||||
# For fast generation
|
||||
use scheduler="simple" or "normal"
|
||||
reduce step count by 20%
|
||||
```
|
||||
|
||||
### Artistic Effects
|
||||
- **Sharp details**: normal scheduler
|
||||
- **Smooth gradients**: karras scheduler
|
||||
- **Fine textures**: exponential scheduler
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Artifacts or Noise
|
||||
- Try different scheduler
|
||||
- Check sampler compatibility
|
||||
- Adjust step count
|
||||
|
||||
### Slow Convergence
|
||||
- Switch from simple to karras
|
||||
- Increase step count
|
||||
- Check model compatibility
|
||||
|
||||
### Inconsistent Results
|
||||
- Use same scheduler for batch
|
||||
- Avoid random scheduler selection
|
||||
- Fix seed for testing
|
||||
|
||||
## Visual Guide
|
||||
|
||||
### Noise Schedule Curves
|
||||
```
|
||||
Normal: ████████████████
|
||||
Linear reduction
|
||||
|
||||
Karras: ███████████▓▓▓░░
|
||||
Smooth curve
|
||||
|
||||
Exponential: ██████▓▓▓░░░░░
|
||||
Fast early reduction
|
||||
```
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Scheduler Comparison
|
||||
Test same seed with different schedulers to find optimal setting.
|
||||
|
||||
### Model Migration
|
||||
When switching models, automatically adjust scheduler for best results.
|
||||
|
||||
### Quality Ladder
|
||||
Progress through schedulers from fast to quality for different use cases.
|
||||
|
||||
## Version History
|
||||
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
|
||||
- **1.0.1**: Added visualization features
|
||||
- **1.0.2**: Enhanced model detection
|
||||
- **1.0.3**: Improved compatibility matrix
|
||||
|
||||
## Credits
|
||||
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
|
||||
@@ -164,4 +164,4 @@ See the `examples/workflows/` directory for complete workflow examples demonstra
|
||||
- Basic seed tracking workflow
|
||||
- Creative iteration with history
|
||||
- Technical reproducibility setup
|
||||
- Batch processing with seed management
|
||||
- Batch processing with seed management
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
# Text Encode Sampler Params
|
||||
|
||||
## Overview
|
||||
The **Text Encode Sampler Params** node combines text encoding with sampler parameter management, providing a unified interface for prompt processing and sampling configuration. Adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode), this tool streamlines the text-to-image pipeline setup.
|
||||
|
||||
## Attribution
|
||||
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
|
||||
|
||||
## Features
|
||||
- **Unified Interface**: Combine text encoding and sampler params in one node
|
||||
- **Dynamic Prompt Processing**: Support for wildcards and syntax
|
||||
- **Parameter Templates**: Pre-configured settings for common scenarios
|
||||
- **Batch Text Processing**: Handle multiple prompts efficiently
|
||||
- **Model-Aware Encoding**: Optimize for different text encoders
|
||||
|
||||
## Node Properties
|
||||
- **Category**: `ComfyAssets/🧰 xyz-helpers`
|
||||
- **Node Name**: `TextEncodeSamplerParams`
|
||||
- **Function**: `encode_and_params`
|
||||
|
||||
## Inputs
|
||||
|
||||
### Required
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `text` | STRING | - | Prompt text to encode |
|
||||
| `clip` | CLIP | - | CLIP model for encoding |
|
||||
| `sampler_name` | DROPDOWN | dpmpp_2m | Sampling algorithm |
|
||||
| `scheduler` | DROPDOWN | karras | Noise scheduler |
|
||||
| `steps` | INT | 20 | Sampling steps |
|
||||
| `cfg` | FLOAT | 7.0 | CFG scale |
|
||||
|
||||
### Optional
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `negative_text` | STRING | "" | Negative prompt |
|
||||
| `seed` | INT | -1 | Random seed (-1 for random) |
|
||||
| `denoise` | FLOAT | 1.0 | Denoising strength |
|
||||
| `template` | DROPDOWN | none | Parameter template |
|
||||
|
||||
## Outputs
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `positive` | CONDITIONING | Encoded positive prompt |
|
||||
| `negative` | CONDITIONING | Encoded negative prompt |
|
||||
| `sampler_params` | DICT | Complete sampler parameters |
|
||||
|
||||
## Templates
|
||||
|
||||
### Portrait Photography
|
||||
```python
|
||||
template: "portrait"
|
||||
→ steps: 25
|
||||
→ cfg: 7.5
|
||||
→ sampler: dpmpp_2m_sde
|
||||
→ scheduler: karras
|
||||
```
|
||||
|
||||
### Landscape Art
|
||||
```python
|
||||
template: "landscape"
|
||||
→ steps: 30
|
||||
→ cfg: 8.0
|
||||
→ sampler: dpmpp_3m_sde
|
||||
→ scheduler: exponential
|
||||
```
|
||||
|
||||
### Quick Preview
|
||||
```python
|
||||
template: "preview"
|
||||
→ steps: 12
|
||||
→ cfg: 6.0
|
||||
→ sampler: euler
|
||||
→ scheduler: normal
|
||||
```
|
||||
|
||||
### High Detail
|
||||
```python
|
||||
template: "detailed"
|
||||
→ steps: 40
|
||||
→ cfg: 7.0
|
||||
→ sampler: dpm_adaptive
|
||||
→ scheduler: karras
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Text-to-Image
|
||||
```
|
||||
TextEncodeSamplerParams → KSampler → VAE Decode
|
||||
text: "beautiful landscape"
|
||||
negative_text: "ugly, blurry"
|
||||
steps: 20
|
||||
```
|
||||
|
||||
### Template-Based Generation
|
||||
```
|
||||
TextEncodeSamplerParams → KSampler
|
||||
text: "portrait of a person"
|
||||
template: "portrait"
|
||||
→ Optimized portrait settings
|
||||
```
|
||||
|
||||
### Batch Processing
|
||||
```
|
||||
Multiple Prompts → TextEncodeSamplerParams → Batch Generate
|
||||
→ Encode all prompts with same settings
|
||||
```
|
||||
|
||||
## Prompt Syntax Support
|
||||
|
||||
### Wildcards
|
||||
```
|
||||
{red|blue|green} car
|
||||
→ Randomly selects color
|
||||
```
|
||||
|
||||
### Emphasis
|
||||
```
|
||||
(important:1.2) detail
|
||||
→ Increases weight to 1.2
|
||||
```
|
||||
|
||||
### Alternation
|
||||
```
|
||||
[cat|dog] in garden
|
||||
→ Alternates between options
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Text Encoding
|
||||
1. Keep prompts concise and descriptive
|
||||
2. Use emphasis for important elements
|
||||
3. Structure prompts logically
|
||||
4. Test negative prompts impact
|
||||
|
||||
### Parameter Selection
|
||||
```python
|
||||
# Quality over speed
|
||||
steps: 30-40
|
||||
cfg: 7-8
|
||||
sampler: dpmpp_3m_sde
|
||||
|
||||
# Speed over quality
|
||||
steps: 10-15
|
||||
cfg: 5-6
|
||||
sampler: euler
|
||||
```
|
||||
|
||||
### Negative Prompts
|
||||
```python
|
||||
# Common negatives
|
||||
"ugly, tiling, poorly drawn, out of frame"
|
||||
|
||||
# Style-specific
|
||||
"cartoon, anime" (for realism)
|
||||
"realistic, photo" (for artwork)
|
||||
```
|
||||
|
||||
## Integration with Other Nodes
|
||||
|
||||
### Complete Pipeline
|
||||
```
|
||||
TextEncodeSamplerParams → KSampler → VAE Decode
|
||||
↓ ↑
|
||||
All parameters From Model Loader
|
||||
```
|
||||
|
||||
### With LoRA
|
||||
```
|
||||
LoRAFolderBatch → TextEncodeSamplerParams → Generate
|
||||
→ Apply LoRA to encoded text
|
||||
```
|
||||
|
||||
### Multi-Pass Processing
|
||||
```
|
||||
TextEncodeSamplerParams → First Pass (low res)
|
||||
↘ Second Pass (high res)
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Dynamic Templates
|
||||
```python
|
||||
# Load template based on prompt content
|
||||
if "portrait" in text:
|
||||
use_template("portrait")
|
||||
elif "landscape" in text:
|
||||
use_template("landscape")
|
||||
```
|
||||
|
||||
### Prompt Weighting
|
||||
```python
|
||||
# Automatic weight calculation
|
||||
analyze_prompt_importance()
|
||||
apply_semantic_weights()
|
||||
```
|
||||
|
||||
### CLIP Skip Support
|
||||
- Adjust CLIP layers used
|
||||
- Model-specific optimization
|
||||
- Quality vs style balance
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Prompt Optimization
|
||||
1. Front-load important elements
|
||||
2. Use commas for separation
|
||||
3. Avoid contradictions
|
||||
4. Test with different CFG values
|
||||
|
||||
### Performance Tuning
|
||||
```python
|
||||
# Memory efficient
|
||||
encode_in_batches = True
|
||||
clear_cache_between = True
|
||||
|
||||
# Speed priority
|
||||
use_half_precision = True
|
||||
minimize_conditioning = True
|
||||
```
|
||||
|
||||
### Quality Enhancement
|
||||
- Higher CFG for prompt adherence
|
||||
- Lower CFG for creativity
|
||||
- Balance with step count
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Style Transfer
|
||||
```
|
||||
Reference Image → Extract Style
|
||||
↓
|
||||
TextEncodeSamplerParams → Apply Style
|
||||
text: "in the style of [extracted]"
|
||||
```
|
||||
|
||||
### Prompt Evolution
|
||||
```
|
||||
Base Prompt → Variations → TextEncodeSamplerParams
|
||||
→ Test different phrasings
|
||||
```
|
||||
|
||||
### A/B Testing
|
||||
```
|
||||
Same prompt → Different parameters → Compare
|
||||
template A vs template B
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Poor Text Adherence
|
||||
- Increase CFG scale
|
||||
- Simplify prompt
|
||||
- Check CLIP model compatibility
|
||||
|
||||
### Over-saturation
|
||||
- Reduce CFG scale
|
||||
- Adjust negative prompt
|
||||
- Check sampler settings
|
||||
|
||||
### Encoding Errors
|
||||
- Verify CLIP model loaded
|
||||
- Check text formatting
|
||||
- Remove special characters
|
||||
|
||||
## Parameter Guidelines
|
||||
|
||||
### CFG Scale Effects
|
||||
```
|
||||
Low (3-5): Creative, loose interpretation
|
||||
Medium (6-8): Balanced adherence
|
||||
High (9-12): Strict prompt following
|
||||
Very High (13+): Potential artifacts
|
||||
```
|
||||
|
||||
### Step Count Impact
|
||||
```
|
||||
Low (10-15): Fast, rough
|
||||
Medium (20-30): Good balance
|
||||
High (40-50): Maximum quality
|
||||
Very High (50+): Diminishing returns
|
||||
```
|
||||
|
||||
## Model-Specific Settings
|
||||
|
||||
### SDXL
|
||||
- CFG: 6-8
|
||||
- CLIP Skip: 1-2
|
||||
- Emphasis: Moderate
|
||||
|
||||
### SD 1.5
|
||||
- CFG: 7-9
|
||||
- CLIP Skip: 1-2
|
||||
- Emphasis: Standard
|
||||
|
||||
### FLUX
|
||||
- CFG: 3-5
|
||||
- CLIP Skip: 0
|
||||
- Emphasis: Subtle
|
||||
|
||||
## Version History
|
||||
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
|
||||
- **1.0.1**: Added template system
|
||||
- **1.0.2**: Enhanced prompt syntax support
|
||||
- **1.0.3**: Improved batch processing
|
||||
|
||||
## Credits
|
||||
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
|
||||
@@ -147,7 +147,7 @@ Width Height Selector → EmptyLatentImage → Resolution Calculator → Upscale
|
||||
|
||||
### Aspect Ratio Considerations
|
||||
- **Portrait**: 3:4, 2:3, 13:19 work well for people
|
||||
- **Landscape**: 16:9, 19:13, 7:4 for scenes and objects
|
||||
- **Landscape**: 16:9, 19:13, 7:4 for scenes and objects
|
||||
- **Square**: 1:1 for centered compositions
|
||||
- **Ultra-wide**: 21:9+ for panoramic and cinematic shots
|
||||
|
||||
@@ -192,4 +192,4 @@ Width Height Selector → EmptyLatentImage → Resolution Calculator → Upscale
|
||||
### Preset Organization
|
||||
- Categorized by model optimization
|
||||
- Sorted by aspect ratio within categories
|
||||
- Comprehensive tooltips for each preset
|
||||
- Comprehensive tooltips for each preset
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
A beautiful sunset over the ocean, golden hour lighting, professional photography, vibrant colors, high detail
|
||||
Negative: blurry, dark, low quality, distorted, oversaturated
|
||||
---
|
||||
Majestic mountain landscape with snow-capped peaks, dramatic clouds, alpine scenery, crystal clear air, epic composition
|
||||
Negative: foggy, flat lighting, boring composition, low contrast
|
||||
---
|
||||
Futuristic cityscape at night, neon lights, cyberpunk aesthetic, rain-slicked streets, atmospheric, blade runner style
|
||||
Negative: daylight, rural, old fashioned, low tech, empty streets
|
||||
---
|
||||
Enchanted forest with magical glowing mushrooms, fairy lights, mystical atmosphere, ancient trees, fantasy art style
|
||||
Negative: desert, urban, modern, realistic, mundane
|
||||
---
|
||||
Space station orbiting Earth, detailed mechanical structures, astronauts performing spacewalk, realistic sci-fi, NASA photography
|
||||
Negative: fantasy, medieval, underwater, cartoon style
|
||||
@@ -0,0 +1,379 @@
|
||||
{
|
||||
"id": "display-any-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 11,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
400,
|
||||
270
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
8
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"tensor shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
50,
|
||||
270
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1,
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
400,
|
||||
520
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
70
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
9
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "VAELoader",
|
||||
"pos": [
|
||||
-250,
|
||||
410
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
6
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "VAELoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ae.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEEncode",
|
||||
"pos": [
|
||||
430,
|
||||
390
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pixels",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
7
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "VAEEncode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
640,
|
||||
270
|
||||
],
|
||||
"size": [
|
||||
450,
|
||||
278
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
640,
|
||||
610
|
||||
],
|
||||
"size": [
|
||||
590,
|
||||
278
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
-320,
|
||||
530
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
270
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Display Any Example\n\nUniversal debugging tool:\n- Accepts ANY input type\n- Two modes: raw value or tensor shape\n- Finds tensors in nested structures\n\nUse cases:\n- Debug tensor dimensions\n- Inspect latent data\n- View config objects\n- Track data flow\n\nConnect anything to see its contents!"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
5,
|
||||
2,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
7,
|
||||
0,
|
||||
8,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
7,
|
||||
8,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
8,
|
||||
1,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
9,
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Display Any",
|
||||
"bounding": [
|
||||
-400,
|
||||
130,
|
||||
1740,
|
||||
850
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.6115909044841477,
|
||||
"offset": [
|
||||
689.1392030323894,
|
||||
-27.435530779258137
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 466 KiB |
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"id": "display-text-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 6,
|
||||
"last_link_id": 2,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
-160,
|
||||
70
|
||||
],
|
||||
"size": [
|
||||
500,
|
||||
400
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "easy positive",
|
||||
"pos": [
|
||||
-620,
|
||||
70
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-easy-use",
|
||||
"ver": "1.3.1",
|
||||
"Node name for S&R": "easy positive"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Positive prompt:\nbeautiful landscape, mountains in background, sunset lighting, golden hour, professional photography, high resolution, detailed textures, vibrant colors, masterpiece\n\nNegative prompt:\nlow quality, blurry, pixelated, bad composition, oversaturated, underexposed, amateur"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
-620,
|
||||
330
|
||||
],
|
||||
"size": [
|
||||
360,
|
||||
250
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Display Text Example\n\nSmart text display with:\n- Auto-detection of SDXL prompt format\n- Split view for positive/negative prompts\n- Text wrapping and scrolling\n- Copy buttons (strips labels)\n- Responsive resizing\n\nTry editing the text to see:\n1. Single text mode (no prompt markers)\n2. Split view mode (with Positive/Negative prompts)\n\nPerfect for displaying Gemini-generated prompts!"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Display Text",
|
||||
"bounding": [
|
||||
-750,
|
||||
-60,
|
||||
1240,
|
||||
700
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.90909090909091,
|
||||
"offset": [
|
||||
720,
|
||||
30
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 272 KiB |
@@ -0,0 +1,301 @@
|
||||
{
|
||||
"id": "empty-latent-batch-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 8,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "EmptyLatentBatch",
|
||||
"pos": [
|
||||
350,
|
||||
350
|
||||
],
|
||||
"size": [
|
||||
370,
|
||||
210
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "latent",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": []
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"slot_index": 1,
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"slot_index": 2,
|
||||
"links": [
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "EmptyLatentBatch"
|
||||
},
|
||||
"widgets_values": [
|
||||
"896×1152 - 7:9 (1.0MP) - SDXL",
|
||||
896,
|
||||
1152,
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-50,
|
||||
320
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
250
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"text": "Empty Latent Batch Example\n\nThis node creates empty latent tensors with:\n- 26 preset resolutions for SDXL/FLUX/Ultra-wide\n- Batch size support (1-64)\n- Interactive swap button\n- Memory usage estimation\n\nConnect to:\n- KSampler for image generation\n- Other latent processing nodes\n\nOutputs sanitized dimensions divisible by 8."
|
||||
},
|
||||
"widgets_values": [
|
||||
"Empty Latent Batch Example\n\nThis node creates empty latent tensors with:\n- 26 preset resolutions for SDXL/FLUX/Ultra-wide\n- Batch size support (1-64)\n- Interactive swap button\n- Memory usage estimation\n\nConnect to:\n- KSampler for image generation\n- Other latent processing nodes\n\nOutputs sanitized dimensions divisible by 8."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
760,
|
||||
280
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
7
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
770,
|
||||
500
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
8
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
1000,
|
||||
280
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
138
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
1010,
|
||||
500
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
138
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
5,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
5,
|
||||
1,
|
||||
2,
|
||||
6,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
7,
|
||||
5,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
8,
|
||||
6,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Empty Latent",
|
||||
"bounding": [
|
||||
-110,
|
||||
120,
|
||||
1440,
|
||||
620
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8264462809917358,
|
||||
"offset": [
|
||||
156.41790912499965,
|
||||
-2.272547312500285
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 278 KiB |
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"id": "gemini-prompt-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 6,
|
||||
"last_link_id": 4,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
300,
|
||||
30
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
1070,
|
||||
30
|
||||
],
|
||||
"size": [
|
||||
500,
|
||||
400
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "GeminiPrompt",
|
||||
"pos": [
|
||||
640,
|
||||
30
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
276
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "negative_prompt",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "GeminiPrompt"
|
||||
},
|
||||
"widgets_values": [
|
||||
"sdxl",
|
||||
"gemini-2.0-flash",
|
||||
"1234-1234-1234-1234",
|
||||
"",
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
300,
|
||||
410
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
240
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Gemini Prompt Engineer Example\n\nAI-powered image analysis:\n- Analyzes image content and style\n- Generates optimized prompts for FLUX/SDXL/Danbooru/Video\n- Dynamic model selection with refresh button\n- Custom system prompt override\n\nSetup:\n1. Get free API key from Google AI Studio\n2. Set GEMINI_API_KEY env var or use config file\n3. Click refresh button to get latest models\n\nDisplayText automatically shows split view for SDXL prompts!"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
3,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Gemini Prompt Engineer",
|
||||
"bounding": [
|
||||
210,
|
||||
-110,
|
||||
1460,
|
||||
850
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.7513148009015777,
|
||||
"offset": [
|
||||
97.5582232217803,
|
||||
133.12148846503663
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 404 KiB |
@@ -0,0 +1,552 @@
|
||||
{
|
||||
"id": "3cd0ac86-8d11-41e5-9d8b-6621bc810ee6",
|
||||
"revision": 0,
|
||||
"last_node_id": 21,
|
||||
"last_link_id": 21,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
-80,
|
||||
850
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314.0000305175781
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "IMAGE",
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
13
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "GetNode",
|
||||
"pos": [
|
||||
540,
|
||||
840
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
34
|
||||
],
|
||||
"flags": {
|
||||
"collapsed": true
|
||||
},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "UPSCALE_MODEL",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"title": "Get_upscale_skin_model",
|
||||
"properties": {
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"upscale_skin_model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "ImageUpscaleWithModel",
|
||||
"pos": [
|
||||
530,
|
||||
810
|
||||
],
|
||||
"size": [
|
||||
221.98202514648438,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "upscale_model",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"link": 16
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
10,
|
||||
21
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "ImageUpscaleWithModel",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "ImageToMultipleOf",
|
||||
"pos": [
|
||||
1000,
|
||||
810
|
||||
],
|
||||
"size": [
|
||||
230,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
18
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfyui-glifnodes",
|
||||
"ver": "1.0.0",
|
||||
"Node name for S&R": "ImageToMultipleOf",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
65,
|
||||
"center crop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "ImageScaleBy",
|
||||
"pos": [
|
||||
760,
|
||||
810
|
||||
],
|
||||
"size": [
|
||||
220,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
6,
|
||||
19
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "ImageScaleBy",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"lanczos",
|
||||
0.5000000000000001
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "ImageUpscaleWithModel",
|
||||
"pos": [
|
||||
280,
|
||||
830
|
||||
],
|
||||
"size": [
|
||||
221.98202514648438,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "upscale_model",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"link": 15
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 13
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
5,
|
||||
20
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "ImageUpscaleWithModel",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "UpscaleModelLoader",
|
||||
"pos": [
|
||||
-90,
|
||||
720
|
||||
],
|
||||
"size": [
|
||||
320,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "UPSCALE_MODEL",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"links": [
|
||||
15
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "UpscaleModelLoader",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"4x-ClearRealityV1.pth"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "UpscaleModelLoader",
|
||||
"pos": [
|
||||
140,
|
||||
610
|
||||
],
|
||||
"size": [
|
||||
320,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "UPSCALE_MODEL",
|
||||
"type": "UPSCALE_MODEL",
|
||||
"links": [
|
||||
16
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.28",
|
||||
"Node name for S&R": "UpscaleModelLoader",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"1xSkinContrast-High-SuperUltraCompact.pth"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1260,
|
||||
810
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
280
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 18
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1010,
|
||||
940
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
258
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 19
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
520,
|
||||
940
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
258
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 20
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
750,
|
||||
940
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
258
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 21
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
5,
|
||||
14,
|
||||
0,
|
||||
9,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
13,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
10,
|
||||
9,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
13,
|
||||
1,
|
||||
0,
|
||||
14,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
15,
|
||||
15,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"UPSCALE_MODEL"
|
||||
],
|
||||
[
|
||||
16,
|
||||
16,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"UPSCALE_MODEL"
|
||||
],
|
||||
[
|
||||
18,
|
||||
10,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
19,
|
||||
13,
|
||||
0,
|
||||
19,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
20,
|
||||
14,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
21,
|
||||
9,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Image to Multiple of",
|
||||
"bounding": [
|
||||
-180,
|
||||
420,
|
||||
1800,
|
||||
830
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.6655312045216585,
|
||||
"offset": [
|
||||
292.51725658775155,
|
||||
-375.0766486499791
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 805 KiB |
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"id": "kiko-film-grain-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 4,
|
||||
"last_link_id": 2,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
50,
|
||||
100
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
450
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [1],
|
||||
"shape": 3,
|
||||
"label": "IMAGE"
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3,
|
||||
"label": "MASK"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "KikoFilmGrain",
|
||||
"pos": [
|
||||
450,
|
||||
100
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
202
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [2],
|
||||
"shape": 3,
|
||||
"label": "image",
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"Node name for S&R": "KikoFilmGrain"
|
||||
},
|
||||
"widgets_values": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.7,
|
||||
0.0,
|
||||
0
|
||||
],
|
||||
"color": "#223",
|
||||
"bgcolor": "#335"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
850,
|
||||
100
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
450
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
450,
|
||||
350
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"properties": {
|
||||
"text": ""
|
||||
},
|
||||
"widgets_values": [
|
||||
"Kiko Film Grain Example\n\nThis workflow demonstrates the film grain effect.\n\nAdjust parameters:\n- Scale: Grain size (0.25-2.0)\n- Strength: Intensity (0.0-10.0)\n- Saturation: Color amount (0.0-2.0)\n- Toe: Shadow lifting (-0.2-0.5)\n- Seed: Random pattern"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1.0,
|
||||
"offset": [0, 0]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"id": "kiko-save-image-example",
|
||||
"revision": 0,
|
||||
"last_node_id": 4,
|
||||
"last_link_id": 1,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "KikoSaveImage",
|
||||
"pos": [
|
||||
390,
|
||||
80
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
390
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "KikoSaveImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"KikoSave",
|
||||
"PNG",
|
||||
90,
|
||||
4,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
-10,
|
||||
80
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.47",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"example.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
-10,
|
||||
460
|
||||
],
|
||||
"size": [
|
||||
370,
|
||||
270
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Kiko Save Image Example\n\nEnhanced image saving with:\n- Format selection: PNG, JPEG, WebP\n- Quality controls per format\n- Floating popup viewer (draggable)\n- Batch operations support\n- File size display\n\nPopup Features:\n- Click images to open in new tab\n- Download individual or selected images\n- Minimize/maximize/roll-up controls\n- Persistent across saves\n\nTry different formats to compare file sizes!"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Kiko save Image",
|
||||
"bounding": [
|
||||
-110,
|
||||
-60,
|
||||
1020,
|
||||
870
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.7513148009015777,
|
||||
"offset": [
|
||||
648.1018911400128,
|
||||
110.02838653698085
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 245 KiB |
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"id": "41469b2d-d616-479d-879a-95cdc6074a37",
|
||||
"revision": 0,
|
||||
"last_node_id": 6,
|
||||
"last_link_id": 5,
|
||||
"last_node_id": 11,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "ResolutionCalculator",
|
||||
"pos": [
|
||||
60,
|
||||
430
|
||||
-30,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
@@ -38,8 +38,7 @@
|
||||
"type": "INT",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
1,
|
||||
3
|
||||
6
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -47,15 +46,15 @@
|
||||
"type": "INT",
|
||||
"slot_index": 1,
|
||||
"links": [
|
||||
2,
|
||||
4
|
||||
7
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"aux_id": "ComfyAssets/ComfyUI-KikoTools",
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "965ad60c74d7f25b1acce890d9c06518e46e6d0b",
|
||||
"Node name for S&R": "ResolutionCalculator",
|
||||
"aux_id": "ComfyAssets/ComfyUI-KikoTools",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
@@ -66,8 +65,8 @@
|
||||
"id": 6,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
-250,
|
||||
430
|
||||
-340,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
274.080078125,
|
||||
@@ -94,8 +93,8 @@
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.40",
|
||||
"widget_ue_connectable": {},
|
||||
"Node name for S&R": "LoadImage"
|
||||
"Node name for S&R": "LoadImage",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"image-2025-06-13-105737.jpg",
|
||||
@@ -103,80 +102,14 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "Display Int (rgthree)",
|
||||
"id": 7,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
420,
|
||||
360
|
||||
-350,
|
||||
610
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"dir": 3,
|
||||
"name": "input",
|
||||
"type": "INT",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "rgthree-comfy",
|
||||
"ver": "1.0.2506081210",
|
||||
"Node name for S&R": "Display Int (rgthree)",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "Display Int (rgthree)",
|
||||
"pos": [
|
||||
430,
|
||||
530
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"dir": 3,
|
||||
"name": "input",
|
||||
"type": "INT",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "rgthree-comfy",
|
||||
"ver": "1.0.2506081210",
|
||||
"widget_ue_connectable": {},
|
||||
"Node name for S&R": "Display Int (rgthree)"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
-150,
|
||||
170
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
410,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
@@ -184,50 +117,167 @@
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"text": "Resolution Calculator Example\n\nThis node calculates upscaled dimensions from:\n- IMAGE tensors (connect from image loaders)\n- LATENT tensors (connect from VAE encode/generation)\n\nUse cases:\n- Calculate target dimensions for upscalers\n- Plan memory usage for large generations\n- Ensure dimensions are divisible by 8\n\nScale factors optimized for SDXL and FLUX models.",
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Resolution Calculator Example\n\nThis node calculates upscaled dimensions from:\n- IMAGE tensors (connect from image loaders)\n- LATENT tensors (connect from VAE encode/generation)\n\nUse cases:\n- Calculate target dimensions for upscalers\n- Plan memory usage for large generations\n- Ensure dimensions are divisible by 8\n\nScale factors optimized for SDXL and FLUX models."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
330,
|
||||
160
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
8
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "DisplayAny",
|
||||
"pos": [
|
||||
330,
|
||||
310
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "*",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "display_text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
9
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayAny"
|
||||
},
|
||||
"widgets_values": [
|
||||
"raw value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
670,
|
||||
160
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
138
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "DisplayText",
|
||||
"pos": [
|
||||
660,
|
||||
400
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
138
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "kikotools",
|
||||
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
|
||||
"Node name for S&R": "DisplayText"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
3,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
3,
|
||||
1,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
5,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
5,
|
||||
6,
|
||||
@@ -235,25 +285,71 @@
|
||||
1,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
1,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
7,
|
||||
1,
|
||||
1,
|
||||
9,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
8,
|
||||
8,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
9,
|
||||
9,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Resolution Calculator",
|
||||
"bounding": [
|
||||
-480,
|
||||
20,
|
||||
1480,
|
||||
880
|
||||
],
|
||||
"color": "#ffffff",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ue_links": [],
|
||||
"ds": {
|
||||
"scale": 0.7972024500000015,
|
||||
"scale": 0.45000000000000145,
|
||||
"offset": [
|
||||
709.6391289161842,
|
||||
-90.0127389290854
|
||||
1248.1498802376432,
|
||||
256.0155843113725
|
||||
]
|
||||
},
|
||||
"links_added_by_ue": [],
|
||||
"frontendVersion": "1.21.7",
|
||||
"frontendVersion": "1.23.4",
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 785 KiB |
@@ -534,4 +534,4 @@
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,4 +641,4 @@
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -719,4 +719,4 @@
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 4.0 MiB |
@@ -0,0 +1,169 @@
|
||||
{
|
||||
"name": "Sampler and Scheduler Comparison Workflow",
|
||||
"description": "Compare different sampler and scheduler combinations using xyz_helpers",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "1",
|
||||
"type": "SamplerSelectHelper",
|
||||
"title": "Select Optimal Sampler",
|
||||
"category": "ComfyAssets/🧰 xyz-helpers",
|
||||
"inputs": {
|
||||
"model_type": "auto",
|
||||
"quality_preset": "balanced",
|
||||
"sampler_override": "auto",
|
||||
"model_name": "sdxl_model.safetensors"
|
||||
},
|
||||
"outputs": {
|
||||
"sampler_name": "STRING",
|
||||
"scheduler": "STRING",
|
||||
"recommended_steps": "INT",
|
||||
"recommended_cfg": "FLOAT"
|
||||
},
|
||||
"pos": [100, 100]
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"type": "SchedulerSelectHelper",
|
||||
"title": "Optimize Scheduler",
|
||||
"category": "ComfyAssets/🧰 xyz-helpers",
|
||||
"inputs": {
|
||||
"sampler_name": ["1", "sampler_name"],
|
||||
"model_type": "sdxl",
|
||||
"schedule_type": "smooth",
|
||||
"visualize": true
|
||||
},
|
||||
"outputs": {
|
||||
"scheduler": "STRING",
|
||||
"schedule_curve": "IMAGE"
|
||||
},
|
||||
"pos": [400, 100]
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"type": "TextEncodeSamplerParams",
|
||||
"title": "Setup Text and Params",
|
||||
"category": "ComfyAssets/🧰 xyz-helpers",
|
||||
"inputs": {
|
||||
"text": "a majestic mountain landscape at sunset, highly detailed",
|
||||
"negative_text": "low quality, blurry, artifacts",
|
||||
"clip": ["model", "clip"],
|
||||
"sampler_name": ["1", "sampler_name"],
|
||||
"scheduler": ["2", "scheduler"],
|
||||
"steps": ["1", "recommended_steps"],
|
||||
"cfg": ["1", "recommended_cfg"],
|
||||
"template": "landscape"
|
||||
},
|
||||
"outputs": {
|
||||
"positive": "CONDITIONING",
|
||||
"negative": "CONDITIONING",
|
||||
"sampler_params": "DICT"
|
||||
},
|
||||
"pos": [700, 100]
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"type": "EmptyLatentBatch",
|
||||
"title": "Create Test Latents",
|
||||
"category": "ComfyAssets/📦 Latents",
|
||||
"inputs": {
|
||||
"preset": "1216×832 (SDXL Landscape)",
|
||||
"batch_size": 4
|
||||
},
|
||||
"outputs": {
|
||||
"latent": "LATENT"
|
||||
},
|
||||
"pos": [100, 300]
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"type": "KSampler",
|
||||
"title": "Generate with Optimal Settings",
|
||||
"inputs": {
|
||||
"model": ["model", "model"],
|
||||
"positive": ["3", "positive"],
|
||||
"negative": ["3", "negative"],
|
||||
"latent_image": ["4", "latent"],
|
||||
"sampler_name": ["1", "sampler_name"],
|
||||
"scheduler": ["2", "scheduler"],
|
||||
"steps": ["1", "recommended_steps"],
|
||||
"cfg": ["1", "recommended_cfg"],
|
||||
"seed": 42
|
||||
},
|
||||
"outputs": {
|
||||
"latent": "LATENT"
|
||||
},
|
||||
"pos": [1000, 200]
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"type": "PlotParameters",
|
||||
"title": "Visualize Parameters",
|
||||
"category": "ComfyAssets/🧰 xyz-helpers",
|
||||
"inputs": {
|
||||
"sampler_params": ["3", "sampler_params"],
|
||||
"plot_type": "bar",
|
||||
"x_axis": "parameter_name",
|
||||
"y_axis": "value",
|
||||
"title": "Sampler Configuration Analysis",
|
||||
"show_grid": true
|
||||
},
|
||||
"outputs": {
|
||||
"plot_image": "IMAGE"
|
||||
},
|
||||
"pos": [700, 400]
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"type": "DisplayAny",
|
||||
"title": "Show Schedule Curve",
|
||||
"category": "ComfyAssets/🔍 Debug",
|
||||
"inputs": {
|
||||
"input": ["2", "schedule_curve"],
|
||||
"mode": "tensor shape"
|
||||
},
|
||||
"pos": [400, 400]
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"type": "VAEDecode",
|
||||
"title": "Decode Results",
|
||||
"inputs": {
|
||||
"samples": ["5", "latent"],
|
||||
"vae": ["model", "vae"]
|
||||
},
|
||||
"outputs": {
|
||||
"image": "IMAGE"
|
||||
},
|
||||
"pos": [1300, 200]
|
||||
},
|
||||
{
|
||||
"id": "9",
|
||||
"type": "KikoSaveImage",
|
||||
"title": "Save Comparison",
|
||||
"category": "ComfyAssets/💾 Images",
|
||||
"inputs": {
|
||||
"images": ["8", "image"],
|
||||
"filename_prefix": "sampler_comparison",
|
||||
"format": "WEBP",
|
||||
"quality": 90,
|
||||
"popup": true
|
||||
},
|
||||
"pos": [1600, 200]
|
||||
}
|
||||
],
|
||||
"workflow_notes": {
|
||||
"purpose": "Compare and optimize sampler/scheduler combinations for best quality",
|
||||
"features": [
|
||||
"Automatic sampler selection based on model",
|
||||
"Scheduler optimization with visualization",
|
||||
"Parameter analysis and plotting",
|
||||
"Batch generation for comparison"
|
||||
],
|
||||
"tips": [
|
||||
"Try different quality_preset values",
|
||||
"Use visualize=true to see noise schedules",
|
||||
"Compare results across multiple seeds"
|
||||
],
|
||||
"attribution": "xyz_helpers nodes adapted from comfyui-essentials-nodes"
|
||||
}
|
||||
}
|
||||
@@ -3,26 +3,109 @@ KikoTools package initialization and node registry
|
||||
Handles automatic discovery and registration of all ComfyAssets tools
|
||||
"""
|
||||
|
||||
from .tools.batch_list_converter import (
|
||||
ImageBatchToImageListNode,
|
||||
ImageListToImageBatchNode,
|
||||
LatentBatchToLatentListNode,
|
||||
LatentListToLatentBatchNode,
|
||||
)
|
||||
from .tools.batch_prompts import BatchPromptsNode
|
||||
from .tools.display_any import DisplayAnyNode
|
||||
from .tools.display_text import DisplayTextNode
|
||||
from .tools.embedding_autocomplete import KikoEmbeddingAutocomplete
|
||||
from .tools.empty_latent_batch import EmptyLatentBatchNode
|
||||
from .tools.gemini_prompt import GeminiPromptNode
|
||||
from .tools.image_scale_down_by import ImageScaleDownByNode
|
||||
from .tools.image_to_multiple_of import ImageToMultipleOfNode
|
||||
from .tools.kiko_film_grain import KikoFilmGrainNode
|
||||
from .tools.kiko_purge_vram import KikoPurgeVRAM
|
||||
from .tools.kiko_workflow_timer import KikoWorkflowTimerNode
|
||||
from .tools.kiko_save_image import KikoSaveImageNode
|
||||
from .tools.local_image_loader import LocalImageLoaderNode
|
||||
from .tools.model_downloader import ModelDownloaderNode
|
||||
from .tools.resolution_calculator import ResolutionCalculatorNode
|
||||
from .tools.width_height_selector import WidthHeightSelectorNode
|
||||
from .tools.sampler_combo import SamplerComboCompactNode, SamplerComboNode
|
||||
from .tools.seed_history import SeedHistoryNode
|
||||
from .tools.sampler_combo import SamplerComboNode, SamplerComboCompactNode
|
||||
from .tools.text_input import TextInputNode
|
||||
from .tools.width_height_selector import WidthHeightSelectorNode
|
||||
from .tools.width_height_to_vec2 import WidthHeightToVec2Node
|
||||
from .tools.xyz_helpers import (
|
||||
FluxSamplerParamsNode,
|
||||
LoRAFolderBatchNode,
|
||||
PlotParametersNode,
|
||||
SamplerSelectHelperNode,
|
||||
SchedulerSelectHelperNode,
|
||||
TextEncodeSamplerParamsNode,
|
||||
)
|
||||
|
||||
# ComfyUI node registration mappings
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ImageBatchToImageList": ImageBatchToImageListNode,
|
||||
"ImageListToImageBatch": ImageListToImageBatchNode,
|
||||
"LatentBatchToLatentList": LatentBatchToLatentListNode,
|
||||
"LatentListToLatentBatch": LatentListToLatentBatchNode,
|
||||
"BatchPrompts": BatchPromptsNode,
|
||||
"ResolutionCalculator": ResolutionCalculatorNode,
|
||||
"WidthHeightSelector": WidthHeightSelectorNode,
|
||||
"SeedHistory": SeedHistoryNode,
|
||||
"SamplerCombo": SamplerComboNode,
|
||||
"SamplerComboCompact": SamplerComboCompactNode,
|
||||
"EmptyLatentBatch": EmptyLatentBatchNode,
|
||||
"KikoSaveImage": KikoSaveImageNode,
|
||||
"ImageToMultipleOf": ImageToMultipleOfNode,
|
||||
"ImageScaleDownBy": ImageScaleDownByNode,
|
||||
"GeminiPrompt": GeminiPromptNode,
|
||||
"DisplayAny": DisplayAnyNode,
|
||||
"DisplayText": DisplayTextNode,
|
||||
"TextInput": TextInputNode,
|
||||
"KikoFilmGrain": KikoFilmGrainNode,
|
||||
"KikoPurgeVRAM": KikoPurgeVRAM,
|
||||
"KikoLocalImageLoader": LocalImageLoaderNode,
|
||||
"KikoModelDownloader": ModelDownloaderNode,
|
||||
"SamplerSelectHelper": SamplerSelectHelperNode,
|
||||
"SchedulerSelectHelper": SchedulerSelectHelperNode,
|
||||
"TextEncodeSamplerParams": TextEncodeSamplerParamsNode,
|
||||
"FluxSamplerParams": FluxSamplerParamsNode,
|
||||
"PlotParameters+": PlotParametersNode,
|
||||
"LoRAFolderBatch": LoRAFolderBatchNode,
|
||||
"WidthHeightToVec2": WidthHeightToVec2Node,
|
||||
"KikoWorkflowTimer": KikoWorkflowTimerNode,
|
||||
# Note: KikoEmbeddingAutocomplete is not registered as a node
|
||||
# It's a settings-only feature accessed through ComfyUI settings menu
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImageBatchToImageList": "Image Batch to Image List",
|
||||
"ImageListToImageBatch": "Image List to Image Batch",
|
||||
"LatentBatchToLatentList": "Latent Batch to Latent List",
|
||||
"LatentListToLatentBatch": "Latent List to Latent Batch",
|
||||
"BatchPrompts": "Batch Prompts",
|
||||
"ResolutionCalculator": "Resolution Calculator",
|
||||
"WidthHeightSelector": "Width Height Selector",
|
||||
"SeedHistory": "Seed History",
|
||||
"SamplerCombo": "Sampler Combo",
|
||||
"SamplerComboCompact": "Sampler Combo (Compact)",
|
||||
"EmptyLatentBatch": "Empty Latent Batch",
|
||||
"KikoSaveImage": "Kiko Save Image",
|
||||
"ImageToMultipleOf": "Image to Multiple of",
|
||||
"ImageScaleDownBy": "Image Scale Down By",
|
||||
"GeminiPrompt": "Gemini Prompt Engineer",
|
||||
"DisplayAny": "Display Any",
|
||||
"DisplayText": "Display Text",
|
||||
"TextInput": "Text Input",
|
||||
"KikoFilmGrain": "Film Grain",
|
||||
"KikoPurgeVRAM": "Kiko Purge VRAM",
|
||||
"KikoLocalImageLoader": "Local Image Loader",
|
||||
"KikoModelDownloader": "Model Downloader 🌐",
|
||||
"SamplerSelectHelper": "Sampler Select Helper",
|
||||
"SchedulerSelectHelper": "Scheduler Select Helper",
|
||||
"TextEncodeSamplerParams": "Text Encode for Sampler Params",
|
||||
"FluxSamplerParams": "Flux Sampler Parameters",
|
||||
"PlotParameters+": "Plot Parameters",
|
||||
"LoRAFolderBatch": "LoRA Folder Batch",
|
||||
"WidthHeightToVec2": "Width Height to VEC2",
|
||||
"KikoWorkflowTimer": "Workflow Timer",
|
||||
# KikoEmbeddingAutocomplete removed - settings only, not a node
|
||||
}
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
"""AnyType for wildcard input matching in ComfyUI nodes."""
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
"""A special type that matches any input type in ComfyUI."""
|
||||
|
||||
def __ne__(self, other):
|
||||
return False
|
||||
@@ -20,7 +20,7 @@ class ComfyAssetsBaseNode:
|
||||
- Consistent return type handling
|
||||
"""
|
||||
|
||||
CATEGORY = "ComfyAssets"
|
||||
CATEGORY = "🫶 ComfyAssets"
|
||||
|
||||
def validate_inputs(self, **kwargs) -> None:
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
"""Tool registry for KikoTools.
|
||||
|
||||
This module provides the central registration system for all KikoTools nodes.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Dict, Any
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class ToolRegistry:
|
||||
"""Central registry for all KikoTools."""
|
||||
|
||||
def __init__(self):
|
||||
self.tools: Dict[str, Any] = {}
|
||||
self.node_classes: Dict[str, Any] = {}
|
||||
|
||||
def register_tool(self, tool_name: str, node_class: Any) -> None:
|
||||
"""Register a tool and its node class.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool
|
||||
node_class: The ComfyUI node class
|
||||
"""
|
||||
self.tools[tool_name] = node_class
|
||||
|
||||
# Also register by class name for ComfyUI
|
||||
class_name = node_class.__name__
|
||||
self.node_classes[class_name] = node_class
|
||||
|
||||
def discover_tools(self) -> None:
|
||||
"""Automatically discover and load all tools in the tools directory."""
|
||||
tools_dir = Path(__file__).parent.parent / "tools"
|
||||
|
||||
if not tools_dir.exists():
|
||||
return
|
||||
|
||||
for tool_dir in tools_dir.iterdir():
|
||||
if tool_dir.is_dir() and not tool_dir.name.startswith("_"):
|
||||
self._load_tool(tool_dir.name)
|
||||
|
||||
def _load_tool(self, tool_name: str) -> None:
|
||||
"""Load a single tool module.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool directory
|
||||
"""
|
||||
try:
|
||||
# Try to import the tool's node module
|
||||
module = importlib.import_module(f"kikotools.tools.{tool_name}.node")
|
||||
|
||||
# Look for node classes (classes with ComfyUI node attributes)
|
||||
for attr_name in dir(module):
|
||||
attr = getattr(module, attr_name)
|
||||
if (
|
||||
isinstance(attr, type)
|
||||
and hasattr(attr, "INPUT_TYPES")
|
||||
and hasattr(attr, "FUNCTION")
|
||||
):
|
||||
self.register_tool(tool_name, attr)
|
||||
|
||||
# If the tool has settings, register them
|
||||
if hasattr(attr, "SETTINGS"):
|
||||
from .settings import settings_registry
|
||||
|
||||
settings_registry.register_tool_settings(
|
||||
tool_name,
|
||||
getattr(
|
||||
attr,
|
||||
"DISPLAY_NAME",
|
||||
tool_name.replace("_", " ").title(),
|
||||
),
|
||||
attr.SETTINGS,
|
||||
)
|
||||
|
||||
except ImportError:
|
||||
# Tool might not have a node.py file yet
|
||||
pass
|
||||
|
||||
def get_node_class_mappings(self) -> Dict[str, Any]:
|
||||
"""Get node class mappings for ComfyUI registration."""
|
||||
return self.node_classes.copy()
|
||||
|
||||
def get_node_display_name_mappings(self) -> Dict[str, str]:
|
||||
"""Get display name mappings for ComfyUI."""
|
||||
mappings = {}
|
||||
for class_name, node_class in self.node_classes.items():
|
||||
if hasattr(node_class, "DISPLAY_NAME"):
|
||||
mappings[class_name] = node_class.DISPLAY_NAME
|
||||
else:
|
||||
# Generate a display name from class name
|
||||
mappings[class_name] = class_name.replace("Kiko", "").replace(
|
||||
"Node", ""
|
||||
)
|
||||
return mappings
|
||||
@@ -0,0 +1,201 @@
|
||||
"""Settings registry for KikoTools.
|
||||
|
||||
This module provides a centralized settings management system for all KikoTools.
|
||||
Tools can register their settings, which are then exposed in ComfyUI's settings UI.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class SettingDefinition:
|
||||
"""Definition of a single setting."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
type: str # "boolean", "combo", "number", "string", "custom"
|
||||
default: Any
|
||||
description: Optional[str] = None
|
||||
options: Optional[Union[List[Any], Dict[str, Any]]] = None
|
||||
min_value: Optional[float] = None
|
||||
max_value: Optional[float] = None
|
||||
step: Optional[float] = None
|
||||
on_change: Optional[str] = None # JavaScript callback as string
|
||||
|
||||
|
||||
@dataclass
|
||||
class ToolSettings:
|
||||
"""Settings collection for a single tool."""
|
||||
|
||||
tool_name: str
|
||||
display_name: str
|
||||
settings: List[SettingDefinition] = field(default_factory=list)
|
||||
|
||||
|
||||
class SettingsRegistry:
|
||||
"""Central registry for all KikoTools settings."""
|
||||
|
||||
def __init__(self):
|
||||
self.tools: Dict[str, ToolSettings] = {}
|
||||
self.settings_by_id: Dict[str, SettingDefinition] = {}
|
||||
|
||||
def register_tool_settings(
|
||||
self, tool_name: str, display_name: str, settings: Dict[str, Dict[str, Any]]
|
||||
) -> None:
|
||||
"""Register settings for a tool.
|
||||
|
||||
Args:
|
||||
tool_name: Internal tool identifier (e.g., "embedding_autocomplete")
|
||||
display_name: Display name for the tool (e.g., "Embedding Autocomplete")
|
||||
settings: Dictionary of setting configurations
|
||||
{
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Enable embedding autocomplete"
|
||||
},
|
||||
"max_suggestions": {
|
||||
"type": "combo",
|
||||
"default": 20,
|
||||
"options": [10, 20, 50],
|
||||
"description": "Maximum number of suggestions"
|
||||
}
|
||||
}
|
||||
"""
|
||||
tool_settings = ToolSettings(tool_name, display_name)
|
||||
|
||||
for setting_key, config in settings.items():
|
||||
# Generate fully qualified setting ID
|
||||
setting_id = f"kikotools.{tool_name}.{setting_key}"
|
||||
|
||||
# Create display name with branding
|
||||
setting_name = f"🫶 {display_name}: {setting_key.replace('_', ' ').title()}"
|
||||
|
||||
setting_def = SettingDefinition(
|
||||
id=setting_id,
|
||||
name=setting_name,
|
||||
type=config.get("type", "string"),
|
||||
default=config.get("default"),
|
||||
description=config.get("description"),
|
||||
options=config.get("options"),
|
||||
min_value=config.get("min"),
|
||||
max_value=config.get("max"),
|
||||
step=config.get("step"),
|
||||
on_change=config.get("on_change"),
|
||||
)
|
||||
|
||||
tool_settings.settings.append(setting_def)
|
||||
self.settings_by_id[setting_id] = setting_def
|
||||
|
||||
self.tools[tool_name] = tool_settings
|
||||
|
||||
def get_setting(self, setting_id: str) -> Optional[SettingDefinition]:
|
||||
"""Get a setting definition by ID."""
|
||||
return self.settings_by_id.get(setting_id)
|
||||
|
||||
def get_tool_settings(self, tool_name: str) -> Optional[ToolSettings]:
|
||||
"""Get all settings for a tool."""
|
||||
return self.tools.get(tool_name)
|
||||
|
||||
def generate_frontend_registration(self) -> str:
|
||||
"""Generate JavaScript code for frontend settings registration."""
|
||||
js_lines = [
|
||||
"// Auto-generated KikoTools settings registration",
|
||||
"// This file is automatically generated by the settings registry",
|
||||
"",
|
||||
"import { app } from '../../scripts/app.js';",
|
||||
"",
|
||||
"app.registerExtension({",
|
||||
" name: 'kikotools.settings',",
|
||||
" async init() {",
|
||||
" // Register all KikoTools settings",
|
||||
]
|
||||
|
||||
for tool_name, tool_settings in self.tools.items():
|
||||
js_lines.append(f" // {tool_settings.display_name} settings")
|
||||
|
||||
for setting in tool_settings.settings:
|
||||
js_lines.append(" app.ui.settings.addSetting({")
|
||||
js_lines.append(f' id: "{setting.id}",')
|
||||
js_lines.append(f' name: "{setting.name}",')
|
||||
js_lines.append(
|
||||
f" defaultValue: {self._js_value(setting.default)},"
|
||||
)
|
||||
js_lines.append(f' type: "{setting.type}",')
|
||||
|
||||
if setting.description:
|
||||
js_lines.append(f' tooltip: "{setting.description}",')
|
||||
|
||||
if setting.type == "combo" and setting.options:
|
||||
js_lines.append(" options: (value) => {")
|
||||
js_lines.append(
|
||||
f" const options = {json.dumps(setting.options)};"
|
||||
)
|
||||
js_lines.append(" return options.map(opt => ({")
|
||||
js_lines.append(" value: opt,")
|
||||
js_lines.append(" text: String(opt),")
|
||||
js_lines.append(" selected: opt === value")
|
||||
js_lines.append(" }));")
|
||||
js_lines.append(" }},")
|
||||
|
||||
if setting.type == "number":
|
||||
if setting.min_value is not None:
|
||||
js_lines.append(f" min: {setting.min_value},")
|
||||
if setting.max_value is not None:
|
||||
js_lines.append(f" max: {setting.max_value},")
|
||||
if setting.step is not None:
|
||||
js_lines.append(f" step: {setting.step},")
|
||||
|
||||
if setting.on_change:
|
||||
js_lines.append(" onChange(value) {")
|
||||
js_lines.append(f" {setting.on_change}")
|
||||
js_lines.append(" }")
|
||||
|
||||
js_lines.append(" }});")
|
||||
js_lines.append("")
|
||||
|
||||
js_lines.extend([" }", "});", ""])
|
||||
|
||||
return "\n".join(js_lines)
|
||||
|
||||
def _js_value(self, value: Any) -> str:
|
||||
"""Convert Python value to JavaScript literal."""
|
||||
if isinstance(value, bool):
|
||||
return "true" if value else "false"
|
||||
elif isinstance(value, str):
|
||||
return f'"{value}"'
|
||||
elif value is None:
|
||||
return "null"
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
def save_frontend_settings(
|
||||
self, output_path: str = "web/js/kikoSettings.js"
|
||||
) -> None:
|
||||
"""Save the generated frontend settings to a file."""
|
||||
js_content = self.generate_frontend_registration()
|
||||
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
with open(output_path, "w") as f:
|
||||
f.write(js_content)
|
||||
|
||||
def get_all_settings(self) -> Dict[str, Any]:
|
||||
"""Get all registered settings as a dictionary."""
|
||||
result = {}
|
||||
for tool_name, tool_settings in self.tools.items():
|
||||
result[tool_name] = {
|
||||
"display_name": tool_settings.display_name,
|
||||
"settings": {
|
||||
setting.id.split(".")[-1]: {
|
||||
"type": setting.type,
|
||||
"default": setting.default,
|
||||
"description": setting.description,
|
||||
"options": setting.options,
|
||||
}
|
||||
for setting in tool_settings.settings
|
||||
},
|
||||
}
|
||||
return result
|
||||
@@ -0,0 +1,15 @@
|
||||
"""Batch/List conversion tool for ComfyUI."""
|
||||
|
||||
from .node import (
|
||||
ImageBatchToImageListNode,
|
||||
ImageListToImageBatchNode,
|
||||
LatentBatchToLatentListNode,
|
||||
LatentListToLatentBatchNode,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ImageBatchToImageListNode",
|
||||
"ImageListToImageBatchNode",
|
||||
"LatentBatchToLatentListNode",
|
||||
"LatentListToLatentBatchNode",
|
||||
]
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Pure tensor split/join functions for batch-list conversions."""
|
||||
|
||||
import torch
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
def split_image_batch(images: torch.Tensor) -> List[torch.Tensor]:
|
||||
"""Split [B,H,W,C] image batch into list of [1,H,W,C] tensors."""
|
||||
return [images[i : i + 1] for i in range(images.shape[0])]
|
||||
|
||||
|
||||
def join_image_batch(image_list: List[torch.Tensor]) -> torch.Tensor:
|
||||
"""Join list of image tensors into single [B,H,W,C] batch."""
|
||||
return torch.cat(image_list, dim=0)
|
||||
|
||||
|
||||
def split_latent_batch(
|
||||
latent: Dict[str, torch.Tensor],
|
||||
) -> List[Dict[str, torch.Tensor]]:
|
||||
"""Split latent dict into list of single-item latent dicts.
|
||||
|
||||
Preserves all keys (e.g. noise_mask, batch_index). Tensor values whose
|
||||
first dimension matches the batch size of ``samples`` are sliced along
|
||||
dim-0; all other values are copied as-is to every item.
|
||||
"""
|
||||
samples = latent["samples"]
|
||||
batch_size = samples.shape[0]
|
||||
result: List[Dict[str, torch.Tensor]] = []
|
||||
for i in range(batch_size):
|
||||
item: Dict[str, torch.Tensor] = {}
|
||||
for key, value in latent.items():
|
||||
if isinstance(value, torch.Tensor) and value.shape[0] == batch_size:
|
||||
item[key] = value[i : i + 1]
|
||||
else:
|
||||
item[key] = value
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
def join_latent_batch(
|
||||
latent_list: List[Dict[str, torch.Tensor]],
|
||||
) -> Dict[str, torch.Tensor]:
|
||||
"""Join list of latent dicts into single batched latent dict.
|
||||
|
||||
Tensor values that were sliced during split are concatenated along dim-0.
|
||||
Non-tensor values are taken from the first item.
|
||||
"""
|
||||
result: Dict[str, torch.Tensor] = {}
|
||||
first = latent_list[0]
|
||||
for key in first:
|
||||
if isinstance(first[key], torch.Tensor):
|
||||
result[key] = torch.cat([lat[key] for lat in latent_list], dim=0)
|
||||
else:
|
||||
result[key] = first[key]
|
||||
return result
|
||||
@@ -0,0 +1,130 @@
|
||||
"""Batch/List conversion nodes for ComfyUI."""
|
||||
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
from ...base.base_node import ComfyAssetsBaseNode
|
||||
from .logic import (
|
||||
split_image_batch,
|
||||
join_image_batch,
|
||||
split_latent_batch,
|
||||
join_latent_batch,
|
||||
)
|
||||
|
||||
|
||||
class ImageBatchToImageListNode(ComfyAssetsBaseNode):
|
||||
"""Split an IMAGE batch [B,H,W,C] into a list of individual images."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "INT")
|
||||
RETURN_NAMES = ("images", "count")
|
||||
OUTPUT_IS_LIST = (True, False)
|
||||
FUNCTION = "split_batch"
|
||||
CATEGORY = "🫶 ComfyAssets/📦 Latents"
|
||||
|
||||
def split_batch(self, images: torch.Tensor) -> Tuple[List[torch.Tensor], int]:
|
||||
image_list = split_image_batch(images)
|
||||
count = len(image_list)
|
||||
self.log_info(f"Split image batch of {count} into list")
|
||||
return (image_list, count)
|
||||
|
||||
|
||||
class ImageListToImageBatchNode(ComfyAssetsBaseNode):
|
||||
"""Join a list of IMAGE tensors into a single batched IMAGE [B,H,W,C]."""
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "INT")
|
||||
RETURN_NAMES = ("images", "count")
|
||||
FUNCTION = "join_batch"
|
||||
CATEGORY = "🫶 ComfyAssets/📦 Latents"
|
||||
|
||||
def join_batch(self, images: List[torch.Tensor]) -> Tuple[torch.Tensor, int]:
|
||||
batch = join_image_batch(images)
|
||||
count = batch.shape[0]
|
||||
self.log_info(f"Joined {count} images into batch")
|
||||
return (batch, count)
|
||||
|
||||
|
||||
class LatentBatchToLatentListNode(ComfyAssetsBaseNode):
|
||||
"""Split a LATENT batch into a list of individual latent dicts."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"latent": ("LATENT",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT", "INT")
|
||||
RETURN_NAMES = ("latents", "count")
|
||||
OUTPUT_IS_LIST = (True, False)
|
||||
FUNCTION = "split_batch"
|
||||
CATEGORY = "🫶 ComfyAssets/📦 Latents"
|
||||
|
||||
def split_batch(
|
||||
self, latent: Dict[str, torch.Tensor]
|
||||
) -> Tuple[List[Dict[str, torch.Tensor]], int]:
|
||||
latent_list = split_latent_batch(latent)
|
||||
count = len(latent_list)
|
||||
self.log_info(f"Split latent batch of {count} into list")
|
||||
return (latent_list, count)
|
||||
|
||||
|
||||
class LatentListToLatentBatchNode(ComfyAssetsBaseNode):
|
||||
"""Join a list of LATENT dicts into a single batched LATENT."""
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"latents": ("LATENT",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT", "INT")
|
||||
RETURN_NAMES = ("latent", "count")
|
||||
FUNCTION = "join_batch"
|
||||
CATEGORY = "🫶 ComfyAssets/📦 Latents"
|
||||
|
||||
def join_batch(
|
||||
self, latents: List[Dict[str, torch.Tensor]]
|
||||
) -> Tuple[Dict[str, torch.Tensor], int]:
|
||||
batch = join_latent_batch(latents)
|
||||
count = batch["samples"].shape[0]
|
||||
self.log_info(f"Joined {count} latents into batch")
|
||||
return (batch, count)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ImageBatchToImageList": ImageBatchToImageListNode,
|
||||
"ImageListToImageBatch": ImageListToImageBatchNode,
|
||||
"LatentBatchToLatentList": LatentBatchToLatentListNode,
|
||||
"LatentListToLatentBatch": LatentListToLatentBatchNode,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImageBatchToImageList": "Image Batch to Image List",
|
||||
"ImageListToImageBatch": "Image List to Image Batch",
|
||||
"LatentBatchToLatentList": "Latent Batch to Latent List",
|
||||
"LatentListToLatentBatch": "Latent List to Latent Batch",
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Batch Prompts node for loading and processing prompts from text files."""
|
||||
|
||||
from .node import BatchPromptsNode
|
||||
|
||||
__all__ = ["BatchPromptsNode"]
|
||||
@@ -0,0 +1,278 @@
|
||||
"""Logic module for Batch Prompts node."""
|
||||
|
||||
import os
|
||||
from typing import List, Tuple, Dict, Any
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_prompts_from_file(file_path: str) -> List[str]:
|
||||
"""
|
||||
Load prompts from a text file where prompts are separated by '---'.
|
||||
|
||||
Args:
|
||||
file_path: Path to the text file containing prompts
|
||||
|
||||
Returns:
|
||||
List of prompts (each prompt may be multi-line)
|
||||
"""
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Split by --- separator
|
||||
prompts = content.split("---")
|
||||
|
||||
# Clean up prompts - remove leading/trailing whitespace but preserve internal formatting
|
||||
cleaned_prompts = []
|
||||
for prompt in prompts:
|
||||
prompt = prompt.strip()
|
||||
if prompt: # Only add non-empty prompts
|
||||
cleaned_prompts.append(prompt)
|
||||
|
||||
logger.info(f"Loaded {len(cleaned_prompts)} prompts from {file_path}")
|
||||
return cleaned_prompts
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading prompts from {file_path}: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def get_prompt_at_index(
|
||||
prompts: List[str], index: int, wrap: bool = True
|
||||
) -> Tuple[str, int]:
|
||||
"""
|
||||
Get prompt at specified index with optional wrapping.
|
||||
|
||||
Args:
|
||||
prompts: List of prompts
|
||||
index: Index to retrieve
|
||||
wrap: Whether to wrap around to beginning when index exceeds list length
|
||||
|
||||
Returns:
|
||||
Tuple of (prompt text, actual index used)
|
||||
"""
|
||||
if not prompts:
|
||||
return ("", 0)
|
||||
|
||||
if wrap:
|
||||
actual_index = index % len(prompts)
|
||||
else:
|
||||
actual_index = min(index, len(prompts) - 1)
|
||||
|
||||
return (prompts[actual_index], actual_index)
|
||||
|
||||
|
||||
def get_next_prompt(
|
||||
prompts: List[str], current_index: int, wrap: bool = True
|
||||
) -> Tuple[str, int]:
|
||||
"""
|
||||
Get the next prompt in sequence.
|
||||
|
||||
Args:
|
||||
prompts: List of prompts
|
||||
current_index: Current prompt index
|
||||
wrap: Whether to wrap around to beginning
|
||||
|
||||
Returns:
|
||||
Tuple of (next prompt text, next index)
|
||||
"""
|
||||
if not prompts:
|
||||
return ("", 0)
|
||||
|
||||
next_index = current_index + 1
|
||||
|
||||
if wrap:
|
||||
next_index = next_index % len(prompts)
|
||||
else:
|
||||
next_index = min(next_index, len(prompts) - 1)
|
||||
|
||||
return (prompts[next_index], next_index)
|
||||
|
||||
|
||||
def get_prompt_preview(prompt: str, max_length: int = 100) -> str:
|
||||
"""
|
||||
Get a preview of a prompt, truncated if necessary.
|
||||
|
||||
Args:
|
||||
prompt: Full prompt text
|
||||
max_length: Maximum length for preview
|
||||
|
||||
Returns:
|
||||
Preview string
|
||||
"""
|
||||
if len(prompt) <= max_length:
|
||||
return prompt
|
||||
|
||||
return prompt[:max_length] + "..."
|
||||
|
||||
|
||||
def parse_prompt_file_list(file_list_str: str) -> List[str]:
|
||||
"""
|
||||
Parse a comma-separated list of prompt file paths.
|
||||
|
||||
Args:
|
||||
file_list_str: Comma-separated file paths
|
||||
|
||||
Returns:
|
||||
List of file paths
|
||||
"""
|
||||
if not file_list_str:
|
||||
return []
|
||||
|
||||
files = []
|
||||
for file_path in file_list_str.split(","):
|
||||
file_path = file_path.strip()
|
||||
if file_path:
|
||||
files.append(file_path)
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def merge_prompts_from_multiple_files(file_paths: List[str]) -> List[str]:
|
||||
"""
|
||||
Load and merge prompts from multiple files.
|
||||
|
||||
Args:
|
||||
file_paths: List of file paths
|
||||
|
||||
Returns:
|
||||
Combined list of all prompts
|
||||
"""
|
||||
all_prompts = []
|
||||
|
||||
for file_path in file_paths:
|
||||
prompts = load_prompts_from_file(file_path)
|
||||
all_prompts.extend(prompts)
|
||||
|
||||
logger.info(f"Merged {len(all_prompts)} prompts from {len(file_paths)} files")
|
||||
return all_prompts
|
||||
|
||||
|
||||
def get_batch_info(prompts: List[str], current_index: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Get information about current batch processing state.
|
||||
|
||||
Args:
|
||||
prompts: List of prompts
|
||||
current_index: Current prompt index
|
||||
|
||||
Returns:
|
||||
Dictionary with batch information
|
||||
"""
|
||||
total = len(prompts)
|
||||
|
||||
return {
|
||||
"current_index": current_index,
|
||||
"total_prompts": total,
|
||||
"progress": f"{current_index + 1}/{total}" if total > 0 else "0/0",
|
||||
"percentage": (current_index / total * 100) if total > 0 else 0,
|
||||
"remaining": total - current_index - 1 if total > 0 else 0,
|
||||
"is_complete": current_index >= total - 1 if total > 0 else True,
|
||||
}
|
||||
|
||||
|
||||
def validate_prompt_file(file_path: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Validate that a prompt file exists and is readable.
|
||||
|
||||
Args:
|
||||
file_path: Path to validate
|
||||
|
||||
Returns:
|
||||
Tuple of (is_valid, error_message)
|
||||
"""
|
||||
if not file_path:
|
||||
return (False, "No file path provided")
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
return (False, f"File not found: {file_path}")
|
||||
|
||||
if not os.path.isfile(file_path):
|
||||
return (False, f"Path is not a file: {file_path}")
|
||||
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
f.read(1) # Try to read one character
|
||||
return (True, "")
|
||||
except Exception as e:
|
||||
return (False, f"Cannot read file: {str(e)}")
|
||||
|
||||
|
||||
def format_prompt_for_display(prompt: str, index: int, total: int) -> str:
|
||||
"""
|
||||
Format a prompt for display with index information.
|
||||
|
||||
Args:
|
||||
prompt: Prompt text
|
||||
index: Current index
|
||||
total: Total number of prompts
|
||||
|
||||
Returns:
|
||||
Formatted display string
|
||||
"""
|
||||
header = f"[Prompt {index + 1}/{total}]"
|
||||
separator = "-" * len(header)
|
||||
|
||||
return f"{header}\n{separator}\n{prompt}"
|
||||
|
||||
|
||||
def split_prompt_into_positive_negative(
|
||||
prompt: str, negative_prefix: str = "Negative:"
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
Split a prompt into positive and negative parts.
|
||||
|
||||
Args:
|
||||
prompt: Full prompt text
|
||||
negative_prefix: Prefix that marks the negative prompt section
|
||||
|
||||
Returns:
|
||||
Tuple of (positive_prompt, negative_prompt)
|
||||
"""
|
||||
# Look for negative prompt marker
|
||||
negative_lower = negative_prefix.lower()
|
||||
prompt_lower = prompt.lower()
|
||||
|
||||
if negative_lower in prompt_lower:
|
||||
# Find the actual position (case-insensitive search)
|
||||
idx = prompt_lower.index(negative_lower)
|
||||
positive = prompt[:idx].strip()
|
||||
negative = prompt[idx + len(negative_prefix) :].strip()
|
||||
return (positive, negative)
|
||||
|
||||
# No negative prompt found
|
||||
return (prompt, "")
|
||||
|
||||
|
||||
def create_batch_queue(
|
||||
prompts: List[str], batch_size: int = 1, randomize: bool = False
|
||||
) -> List[List[int]]:
|
||||
"""
|
||||
Create a queue of prompt indices for batch processing.
|
||||
|
||||
Args:
|
||||
prompts: List of prompts
|
||||
batch_size: Number of prompts per batch
|
||||
randomize: Whether to randomize the order
|
||||
|
||||
Returns:
|
||||
List of batches, where each batch is a list of prompt indices
|
||||
"""
|
||||
if not prompts:
|
||||
return []
|
||||
|
||||
indices = list(range(len(prompts)))
|
||||
|
||||
if randomize:
|
||||
import random
|
||||
|
||||
random.shuffle(indices)
|
||||
|
||||
batches = []
|
||||
for i in range(0, len(indices), batch_size):
|
||||
batch = indices[i : i + batch_size]
|
||||
batches.append(batch)
|
||||
|
||||
return batches
|
||||
@@ -0,0 +1,237 @@
|
||||
"""Batch Prompts node for ComfyUI."""
|
||||
|
||||
import os
|
||||
from typing import Tuple
|
||||
from ...base.base_node import ComfyAssetsBaseNode
|
||||
from .logic import (
|
||||
load_prompts_from_file,
|
||||
get_prompt_at_index,
|
||||
get_next_prompt,
|
||||
get_prompt_preview,
|
||||
get_batch_info,
|
||||
validate_prompt_file,
|
||||
split_prompt_into_positive_negative,
|
||||
)
|
||||
from .state_manager import STATE_MANAGER
|
||||
|
||||
|
||||
class BatchPromptsNode(ComfyAssetsBaseNode):
|
||||
"""
|
||||
Batch Prompts node for loading and iterating through prompts from text files.
|
||||
|
||||
Loads prompts from a text file where prompts are separated by '---' markers,
|
||||
provides iteration control, and outputs both current and next prompts with
|
||||
optional positive/negative splitting.
|
||||
"""
|
||||
|
||||
# Class variable to cache loaded prompts
|
||||
_prompt_cache = {}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
"""Define the input types for the ComfyUI node."""
|
||||
# Try to get input folder path
|
||||
try:
|
||||
import folder_paths
|
||||
|
||||
folder_paths.get_input_directory()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"prompt_file": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "prompts.txt",
|
||||
"multiline": False,
|
||||
"tooltip": "Path to text file containing prompts separated by '---'",
|
||||
},
|
||||
),
|
||||
"index": (
|
||||
"INT",
|
||||
{
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 9999,
|
||||
"step": 1,
|
||||
"tooltip": "Current prompt index (0-based)",
|
||||
},
|
||||
),
|
||||
"auto_increment": (
|
||||
"BOOLEAN",
|
||||
{
|
||||
"default": True,
|
||||
"tooltip": "Automatically increment index after each execution",
|
||||
},
|
||||
),
|
||||
"wrap_around": (
|
||||
"BOOLEAN",
|
||||
{
|
||||
"default": True,
|
||||
"tooltip": "Wrap to first prompt after reaching the end",
|
||||
},
|
||||
),
|
||||
"split_negative": (
|
||||
"BOOLEAN",
|
||||
{
|
||||
"default": True,
|
||||
"tooltip": "Split prompts into positive/negative at 'Negative:' marker",
|
||||
},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"reload_file": (
|
||||
"BOOLEAN",
|
||||
{"default": False, "tooltip": "Force reload file from disk"},
|
||||
),
|
||||
"show_preview": (
|
||||
"BOOLEAN",
|
||||
{"default": True, "tooltip": "Show prompt preview in console"},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "INT", "INT", "STRING")
|
||||
RETURN_NAMES = (
|
||||
"positive",
|
||||
"negative",
|
||||
"full_prompt",
|
||||
"next_prompt",
|
||||
"current_index",
|
||||
"total_prompts",
|
||||
"batch_info",
|
||||
)
|
||||
FUNCTION = "process_batch_prompts"
|
||||
CATEGORY = "🫶 ComfyAssets/📝 Text"
|
||||
|
||||
def process_batch_prompts(
|
||||
self,
|
||||
prompt_file: str,
|
||||
index: int,
|
||||
auto_increment: bool,
|
||||
wrap_around: bool,
|
||||
split_negative: bool,
|
||||
reload_file: bool = False,
|
||||
show_preview: bool = True,
|
||||
) -> Tuple[str, str, str, str, int, int, str]:
|
||||
"""
|
||||
Process batch prompts from file.
|
||||
|
||||
Args:
|
||||
prompt_file: Path to prompt file
|
||||
index: Current prompt index
|
||||
auto_increment: Whether to auto-increment index
|
||||
wrap_around: Whether to wrap around at end
|
||||
split_negative: Whether to split positive/negative prompts
|
||||
reload_file: Force reload from disk
|
||||
show_preview: Show prompt preview in console
|
||||
|
||||
Returns:
|
||||
Tuple of (positive, negative, full_prompt, next_prompt, current_index, total_prompts, batch_info)
|
||||
"""
|
||||
try:
|
||||
# Handle file path first to get a consistent key
|
||||
if not os.path.isabs(prompt_file):
|
||||
# Try to resolve relative to ComfyUI input directory
|
||||
try:
|
||||
import folder_paths
|
||||
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
full_path = os.path.join(input_dir, prompt_file)
|
||||
except Exception:
|
||||
# Fallback to current directory
|
||||
full_path = os.path.abspath(prompt_file)
|
||||
else:
|
||||
full_path = prompt_file
|
||||
|
||||
# Use persistent state manager for tracking execution
|
||||
if auto_increment:
|
||||
# Use file-based persistent state
|
||||
actual_index = STATE_MANAGER.increment_execution_count(full_path)
|
||||
print(
|
||||
f"[BatchPrompts] Auto-increment: using index {actual_index} for {os.path.basename(prompt_file)}"
|
||||
)
|
||||
else:
|
||||
actual_index = index
|
||||
print(f"[BatchPrompts] Manual mode: using index {actual_index}")
|
||||
|
||||
# Validate file
|
||||
is_valid, error_msg = validate_prompt_file(full_path)
|
||||
if not is_valid:
|
||||
self.handle_error(f"Invalid prompt file: {error_msg}")
|
||||
|
||||
# Load prompts (with caching)
|
||||
cache_key = full_path
|
||||
if reload_file or cache_key not in self._prompt_cache:
|
||||
prompts = load_prompts_from_file(full_path)
|
||||
if not prompts:
|
||||
self.handle_error(f"No prompts found in file: {prompt_file}")
|
||||
self._prompt_cache[cache_key] = prompts
|
||||
# Reset execution count when reloading file
|
||||
if reload_file:
|
||||
STATE_MANAGER.reset_execution_count(full_path)
|
||||
self.log_info(f"Loaded {len(prompts)} prompts from {prompt_file}")
|
||||
else:
|
||||
prompts = self._prompt_cache[cache_key]
|
||||
|
||||
# Get current prompt using the determined index
|
||||
current_prompt, used_index = get_prompt_at_index(
|
||||
prompts, actual_index, wrap_around
|
||||
)
|
||||
|
||||
# Get next prompt
|
||||
next_prompt_text, next_index = get_next_prompt(
|
||||
prompts, used_index, wrap_around
|
||||
)
|
||||
|
||||
# Split positive/negative if requested
|
||||
if split_negative:
|
||||
positive, negative = split_prompt_into_positive_negative(current_prompt)
|
||||
else:
|
||||
positive = current_prompt
|
||||
negative = ""
|
||||
|
||||
# Get batch info
|
||||
batch_info_dict = get_batch_info(prompts, used_index)
|
||||
batch_info_str = (
|
||||
f"Prompt {batch_info_dict['current_index'] + 1} of {batch_info_dict['total_prompts']} "
|
||||
f"({batch_info_dict['percentage']:.1f}% complete)"
|
||||
)
|
||||
|
||||
# Show preview if requested
|
||||
if show_preview:
|
||||
preview = get_prompt_preview(positive, 80)
|
||||
self.log_info(
|
||||
f"Current prompt [{used_index + 1}/{len(prompts)}]: {preview}"
|
||||
)
|
||||
|
||||
# No need to manually reset - the modulo operation in get_prompt_at_index handles wrapping
|
||||
|
||||
return (
|
||||
positive,
|
||||
negative,
|
||||
current_prompt,
|
||||
next_prompt_text,
|
||||
used_index,
|
||||
len(prompts),
|
||||
batch_info_str,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.handle_error(f"Error processing batch prompts: {str(e)}")
|
||||
# Return empty values on error
|
||||
return ("", "", "", "", 0, 0, "Error")
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
"""
|
||||
Check if node inputs have changed.
|
||||
This ensures the node re-executes when needed.
|
||||
"""
|
||||
# Import time to ensure unique value each check
|
||||
import time
|
||||
|
||||
# Return current timestamp to guarantee the node is seen as changed
|
||||
# This forces re-execution on every workflow run
|
||||
return str(time.time())
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Simple Batch Prompts node for ComfyUI - debugging version."""
|
||||
|
||||
import os
|
||||
from typing import Tuple
|
||||
from ...base.base_node import ComfyAssetsBaseNode
|
||||
from .logic import (
|
||||
load_prompts_from_file,
|
||||
get_prompt_at_index,
|
||||
split_prompt_into_positive_negative,
|
||||
)
|
||||
|
||||
# Global counter that persists across all executions
|
||||
GLOBAL_COUNTER = {"count": 0}
|
||||
|
||||
|
||||
class SimpleBatchPromptsNode(ComfyAssetsBaseNode):
|
||||
"""
|
||||
Simplified Batch Prompts node for debugging.
|
||||
Uses a global counter to ensure prompts change.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
"""Define the input types for the ComfyUI node."""
|
||||
return {
|
||||
"required": {
|
||||
"prompt_file": ("STRING", {"default": "prompts.txt"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "INT")
|
||||
RETURN_NAMES = ("positive", "negative", "index")
|
||||
FUNCTION = "get_next_prompt"
|
||||
CATEGORY = "🫶 ComfyAssets/📝 Text"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
"""Force re-execution every time."""
|
||||
GLOBAL_COUNTER["count"] += 1
|
||||
return GLOBAL_COUNTER["count"]
|
||||
|
||||
def get_next_prompt(self, prompt_file: str) -> Tuple[str, str, int]:
|
||||
"""Get the next prompt in sequence."""
|
||||
# Resolve file path
|
||||
if not os.path.isabs(prompt_file):
|
||||
try:
|
||||
import folder_paths
|
||||
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
full_path = os.path.join(input_dir, prompt_file)
|
||||
except ImportError:
|
||||
full_path = os.path.abspath(prompt_file)
|
||||
else:
|
||||
full_path = prompt_file
|
||||
|
||||
# Load prompts
|
||||
prompts = load_prompts_from_file(full_path)
|
||||
if not prompts:
|
||||
return ("No prompts found", "", 0)
|
||||
|
||||
# Get current prompt based on global counter
|
||||
index = GLOBAL_COUNTER["count"] % len(prompts)
|
||||
current_prompt, _ = get_prompt_at_index(prompts, index, wrap=True)
|
||||
|
||||
# Split positive/negative
|
||||
positive, negative = split_prompt_into_positive_negative(current_prompt)
|
||||
|
||||
print(
|
||||
f"[SimpleBatchPrompts] Counter={GLOBAL_COUNTER['count']}, Index={index}, Prompt={positive[:30]}..."
|
||||
)
|
||||
|
||||
return (positive, negative, index)
|
||||
@@ -0,0 +1,62 @@
|
||||
"""State management for batch prompts using file persistence."""
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
class StateManager:
|
||||
"""Manages persistent state for batch prompt execution."""
|
||||
|
||||
def __init__(self):
|
||||
# Use temp directory for state files
|
||||
self.state_dir = Path(tempfile.gettempdir()) / "comfyui_batch_prompts"
|
||||
self.state_dir.mkdir(exist_ok=True)
|
||||
self.state_file = self.state_dir / "execution_state.json"
|
||||
|
||||
def get_state(self) -> Dict[str, Any]:
|
||||
"""Load state from file."""
|
||||
if self.state_file.exists():
|
||||
try:
|
||||
with open(self.state_file, "r") as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
pass
|
||||
return {}
|
||||
|
||||
def save_state(self, state: Dict[str, Any]):
|
||||
"""Save state to file."""
|
||||
try:
|
||||
with open(self.state_file, "w") as f:
|
||||
json.dump(state, f)
|
||||
except Exception as e:
|
||||
print(f"[BatchPrompts] Failed to save state: {e}")
|
||||
|
||||
def get_execution_count(self, file_path: str) -> int:
|
||||
"""Get execution count for a specific file."""
|
||||
state = self.get_state()
|
||||
counts = state.get("execution_counts", {})
|
||||
return counts.get(file_path, 0)
|
||||
|
||||
def increment_execution_count(self, file_path: str) -> int:
|
||||
"""Increment and return execution count for a file."""
|
||||
state = self.get_state()
|
||||
counts = state.get("execution_counts", {})
|
||||
current = counts.get(file_path, 0)
|
||||
counts[file_path] = current + 1
|
||||
state["execution_counts"] = counts
|
||||
self.save_state(state)
|
||||
return current
|
||||
|
||||
def reset_execution_count(self, file_path: str):
|
||||
"""Reset execution count for a file."""
|
||||
state = self.get_state()
|
||||
counts = state.get("execution_counts", {})
|
||||
counts[file_path] = 0
|
||||
state["execution_counts"] = counts
|
||||
self.save_state(state)
|
||||
|
||||
|
||||
# Global state manager instance
|
||||
STATE_MANAGER = StateManager()
|
||||
@@ -0,0 +1,5 @@
|
||||
"""DisplayAny tool for ComfyUI."""
|
||||
|
||||
from .node import DisplayAnyNode
|
||||
|
||||
__all__ = ["DisplayAnyNode"]
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Logic for DisplayAny node - displays any input value or tensor shape."""
|
||||
|
||||
from typing import Any, List
|
||||
|
||||
|
||||
def get_tensor_shapes(input_value: Any) -> List[List[int]]:
|
||||
"""Extract tensor shapes from nested structures.
|
||||
|
||||
Args:
|
||||
input_value: Any input value that may contain tensors
|
||||
|
||||
Returns:
|
||||
List of tensor shapes found in the input
|
||||
"""
|
||||
shapes = []
|
||||
|
||||
def extract_shapes(value: Any) -> None:
|
||||
"""Recursively extract shapes from nested structures."""
|
||||
if isinstance(value, dict):
|
||||
for v in value.values():
|
||||
extract_shapes(v)
|
||||
elif isinstance(value, (list, tuple)):
|
||||
for item in value:
|
||||
extract_shapes(item)
|
||||
elif hasattr(value, "shape"):
|
||||
# Handle tensors (numpy arrays, torch tensors, etc.)
|
||||
shapes.append(list(value.shape))
|
||||
|
||||
extract_shapes(input_value)
|
||||
return shapes
|
||||
|
||||
|
||||
def format_display_value(input_value: Any, mode: str = "raw value") -> str:
|
||||
"""Format input value for display based on selected mode.
|
||||
|
||||
Args:
|
||||
input_value: Any input value to display
|
||||
mode: Display mode - "raw value" or "tensor shape"
|
||||
|
||||
Returns:
|
||||
Formatted string representation of the input
|
||||
"""
|
||||
if mode == "tensor shape":
|
||||
shapes = get_tensor_shapes(input_value)
|
||||
if shapes:
|
||||
return str(shapes)
|
||||
else:
|
||||
return "No tensors found in input"
|
||||
|
||||
# Default to raw value display
|
||||
# Try to format as JSON for better readability
|
||||
try:
|
||||
import json
|
||||
|
||||
if isinstance(input_value, (dict, list)):
|
||||
return json.dumps(input_value, indent=2)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
return str(input_value)
|
||||
|
||||
|
||||
def validate_display_mode(mode: str) -> bool:
|
||||
"""Validate if the display mode is supported.
|
||||
|
||||
Args:
|
||||
mode: Display mode to validate
|
||||
|
||||
Returns:
|
||||
True if mode is valid, False otherwise
|
||||
"""
|
||||
valid_modes = ["raw value", "tensor shape"]
|
||||
return mode in valid_modes
|
||||
@@ -0,0 +1,67 @@
|
||||
"""DisplayAny node for ComfyUI - displays any input value or tensor information."""
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from ...base import ComfyAssetsBaseNode
|
||||
from .logic import format_display_value, validate_display_mode
|
||||
|
||||
|
||||
# Define AnyType for wildcard input matching
|
||||
class AnyType(str):
|
||||
"""A special type that matches any input type in ComfyUI."""
|
||||
|
||||
def __ne__(self, other):
|
||||
return False
|
||||
|
||||
|
||||
class DisplayAnyNode(ComfyAssetsBaseNode):
|
||||
"""Display any input value or tensor shape information.
|
||||
|
||||
This node can display any type of input in two modes:
|
||||
- Raw value: Shows the string representation of the input
|
||||
- Tensor shape: Extracts and displays shapes of any tensors in the input
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> Dict[str, Any]:
|
||||
"""Define input types for the node."""
|
||||
return {
|
||||
"required": {
|
||||
"input": (AnyType("*"), {}), # Accept any type of input
|
||||
"mode": (["raw value", "tensor shape"],),
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, **kwargs) -> bool:
|
||||
"""Validate inputs - always returns True as we accept any input."""
|
||||
return True
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
CATEGORY = "🫶 ComfyAssets/👁️ Display"
|
||||
RETURN_NAMES = ("display_text",)
|
||||
FUNCTION = "display"
|
||||
OUTPUT_NODE = True # This node displays output in the UI
|
||||
|
||||
def display(self, input: Any, mode: str = "raw value") -> Dict[str, Any]:
|
||||
"""Display the input value according to the selected mode.
|
||||
|
||||
Args:
|
||||
input: Any input value to display
|
||||
mode: Display mode - "raw value" or "tensor shape"
|
||||
|
||||
Returns:
|
||||
Dictionary with UI display and result
|
||||
"""
|
||||
# Validate mode
|
||||
if not validate_display_mode(mode):
|
||||
mode = "raw value" # Default to raw value if invalid
|
||||
|
||||
# Format the display text
|
||||
display_text = format_display_value(input, mode)
|
||||
|
||||
# Return both UI display and result
|
||||
return {
|
||||
"ui": {"text": [display_text]}, # UI expects array
|
||||
"result": (display_text,),
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Display Text tool for ComfyUI."""
|
||||
|
||||
from .node import DisplayTextNode, NODE_DISPLAY_NAME
|
||||
|
||||
__all__ = ["DisplayTextNode", "NODE_DISPLAY_NAME"]
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Display Text node implementation."""
|
||||
|
||||
from ...base import ComfyAssetsBaseNode
|
||||
|
||||
|
||||
class DisplayTextNode(ComfyAssetsBaseNode):
|
||||
"""Displays text in the ComfyUI interface with copy-to-clipboard functionality."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
"""Define input types for the node."""
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "display_text"
|
||||
CATEGORY = "🫶 ComfyAssets/👁️ Display"
|
||||
|
||||
DESCRIPTION = """
|
||||
Displays text in the UI with a copy-to-clipboard feature.
|
||||
|
||||
Features:
|
||||
- Shows text content in a readable format
|
||||
- Copy button appears on hover
|
||||
- Passes text through for chaining
|
||||
"""
|
||||
|
||||
def display_text(self, text):
|
||||
"""Display the text and pass it through.
|
||||
|
||||
Args:
|
||||
text: Input text to display
|
||||
|
||||
Returns:
|
||||
Tuple containing the text
|
||||
"""
|
||||
# The actual display happens in the frontend
|
||||
# We just pass the text through
|
||||
return {"ui": {"text": [text]}, "result": (text,)}
|
||||
|
||||
|
||||
# Node display name
|
||||
NODE_DISPLAY_NAME = "Display Text"
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Embedding Autocomplete tool for KikoTools."""
|
||||
|
||||
from .node import KikoEmbeddingAutocomplete
|
||||
|
||||
__all__ = ["KikoEmbeddingAutocomplete"]
|
||||
@@ -0,0 +1,291 @@
|
||||
"""KikoEmbeddingAutocomplete node for ComfyUI.
|
||||
|
||||
Provides autocomplete functionality for embeddings and LoRAs in text inputs.
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Dict, List, Any
|
||||
|
||||
try:
|
||||
import folder_paths
|
||||
except ImportError:
|
||||
# For testing outside ComfyUI environment
|
||||
folder_paths = None
|
||||
|
||||
|
||||
class KikoEmbeddingAutocomplete:
|
||||
"""Node that provides embedding autocomplete functionality."""
|
||||
|
||||
DISPLAY_NAME = "🫶 Embedding Autocomplete Settings"
|
||||
CATEGORY = "🫶 ComfyAssets"
|
||||
|
||||
# Settings definition for the settings registry
|
||||
SETTINGS = {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Enable autocomplete",
|
||||
},
|
||||
"show_embeddings": {
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Show embeddings in autocomplete",
|
||||
},
|
||||
"show_loras": {
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Show LoRAs in autocomplete",
|
||||
},
|
||||
"embedding_trigger": {
|
||||
"type": "text",
|
||||
"default": "embedding:",
|
||||
"description": "Trigger text for embeddings (e.g., 'embedding:', 'emb:', or custom)",
|
||||
},
|
||||
"lora_trigger": {
|
||||
"type": "text",
|
||||
"default": "<lora:",
|
||||
"description": "Trigger text for LoRAs (e.g., '<lora:', 'lora:', or custom)",
|
||||
},
|
||||
"quick_trigger": {
|
||||
"type": "text",
|
||||
"default": "em",
|
||||
"description": "Quick trigger to show embeddings (e.g., 'em', 'emb', or disabled with '')",
|
||||
},
|
||||
"min_chars": {
|
||||
"type": "combo",
|
||||
"default": 2,
|
||||
"options": [1, 2, 3, 4, 5],
|
||||
"description": "Minimum characters before showing suggestions",
|
||||
},
|
||||
"max_suggestions": {
|
||||
"type": "combo",
|
||||
"default": 20,
|
||||
"options": [5, 10, 15, 20, 30, 50, 100],
|
||||
"description": "Maximum number of suggestions to display",
|
||||
},
|
||||
"sort_by_directory": {
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Group suggestions by directory",
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
"""Define input types for the node."""
|
||||
return {
|
||||
"required": {},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
RETURN_NAMES = ()
|
||||
FUNCTION = "update_settings"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, **kwargs):
|
||||
return True
|
||||
|
||||
def __init__(self):
|
||||
self.embeddings_cache = None
|
||||
self.loras_cache = None
|
||||
|
||||
def update_settings(self, unique_id=None):
|
||||
"""Update settings display.
|
||||
|
||||
This node serves as a settings indicator.
|
||||
Actual settings are configured in ComfyUI Settings menu.
|
||||
"""
|
||||
# This node doesn't actually process anything
|
||||
# It's just a visual indicator that autocomplete is available
|
||||
return ()
|
||||
|
||||
def refresh_cache(self):
|
||||
"""Refresh the cache of embeddings and LoRAs."""
|
||||
print("[KikoEmbeddingAutocomplete] Refreshing cache...")
|
||||
self.embeddings_cache = self.get_embeddings()
|
||||
self.loras_cache = self.get_loras()
|
||||
print(
|
||||
f"[KikoEmbeddingAutocomplete] Cached {len(self.embeddings_cache)} embeddings, {len(self.loras_cache)} LoRAs"
|
||||
)
|
||||
|
||||
def get_embeddings(self) -> List[Dict[str, Any]]:
|
||||
"""Get list of available embeddings."""
|
||||
embeddings = []
|
||||
|
||||
# Get embedding files from ComfyUI's folder system
|
||||
try:
|
||||
print("[KikoEmbeddingAutocomplete] Getting embeddings list...")
|
||||
if folder_paths is None:
|
||||
return embeddings
|
||||
embedding_files = folder_paths.get_filename_list("embeddings")
|
||||
print(
|
||||
f"[KikoEmbeddingAutocomplete] Found {len(embedding_files)} embedding files"
|
||||
)
|
||||
for file in embedding_files:
|
||||
name = os.path.splitext(file)[0]
|
||||
embeddings.append(
|
||||
{
|
||||
"name": name,
|
||||
"file": file,
|
||||
"type": "embedding",
|
||||
"display": f"embedding:{name}",
|
||||
"value": f"embedding:{name}",
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error loading embeddings: {e}")
|
||||
|
||||
return embeddings
|
||||
|
||||
def get_loras(self) -> List[Dict[str, Any]]:
|
||||
"""Get list of available LoRAs."""
|
||||
loras = []
|
||||
|
||||
# Get LoRA files from ComfyUI's folder system
|
||||
try:
|
||||
if folder_paths is None:
|
||||
return loras
|
||||
lora_files = folder_paths.get_filename_list("loras")
|
||||
for file in lora_files:
|
||||
name = os.path.splitext(file)[0]
|
||||
loras.append(
|
||||
{
|
||||
"name": name,
|
||||
"file": file,
|
||||
"type": "lora",
|
||||
"display": f"<lora:{name}:1.0>",
|
||||
"value": f"<lora:{name}:1.0>",
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error loading LoRAs: {e}")
|
||||
|
||||
return loras
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
"""Check if the node needs to be re-executed."""
|
||||
# Always re-execute if refresh is True
|
||||
if kwargs.get("refresh", False):
|
||||
return float("NaN")
|
||||
|
||||
# Check if embeddings/loras folders have changed
|
||||
try:
|
||||
if folder_paths is None:
|
||||
return 0
|
||||
embeddings_path = folder_paths.get_folder_paths("embeddings")[0]
|
||||
loras_path = folder_paths.get_folder_paths("loras")[0]
|
||||
|
||||
# Return combined modification time
|
||||
return os.path.getmtime(embeddings_path) + os.path.getmtime(loras_path)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
class KikoEmbeddingAutocompleteAPI:
|
||||
"""API endpoints for embedding autocomplete."""
|
||||
|
||||
@staticmethod
|
||||
def get_suggestions(
|
||||
prefix: str,
|
||||
max_results: int = 20,
|
||||
include_embeddings: bool = True,
|
||||
include_loras: bool = True,
|
||||
case_sensitive: bool = False,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Get autocomplete suggestions for a given prefix.
|
||||
|
||||
Args:
|
||||
prefix: The text prefix to match
|
||||
max_results: Maximum number of results to return
|
||||
include_embeddings: Include embeddings in results
|
||||
include_loras: Include LoRAs in results
|
||||
case_sensitive: Use case-sensitive matching
|
||||
|
||||
Returns:
|
||||
List of suggestion dictionaries
|
||||
"""
|
||||
suggestions = []
|
||||
|
||||
# Normalize prefix for matching
|
||||
match_prefix = prefix if case_sensitive else prefix.lower()
|
||||
|
||||
# Get embeddings
|
||||
if include_embeddings:
|
||||
try:
|
||||
if folder_paths is None:
|
||||
embedding_files = []
|
||||
else:
|
||||
embedding_files = folder_paths.get_filename_list("embeddings")
|
||||
for file in embedding_files:
|
||||
name = os.path.splitext(file)[0]
|
||||
match_name = name if case_sensitive else name.lower()
|
||||
|
||||
# Check for match
|
||||
if match_name.startswith(match_prefix):
|
||||
suggestions.append(
|
||||
{
|
||||
"name": name,
|
||||
"type": "embedding",
|
||||
"display": f"embedding:{name}",
|
||||
"value": f"embedding:{name}",
|
||||
"priority": 1 if match_name == match_prefix else 0,
|
||||
}
|
||||
)
|
||||
elif match_prefix in match_name:
|
||||
suggestions.append(
|
||||
{
|
||||
"name": name,
|
||||
"type": "embedding",
|
||||
"display": f"embedding:{name}",
|
||||
"value": f"embedding:{name}",
|
||||
"priority": -1,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error loading embeddings: {e}")
|
||||
|
||||
# Get LoRAs
|
||||
if include_loras:
|
||||
try:
|
||||
if folder_paths is None:
|
||||
lora_files = []
|
||||
else:
|
||||
lora_files = folder_paths.get_filename_list("loras")
|
||||
for file in lora_files:
|
||||
name = os.path.splitext(file)[0]
|
||||
match_name = name if case_sensitive else name.lower()
|
||||
|
||||
# Check for match
|
||||
if match_name.startswith(match_prefix):
|
||||
suggestions.append(
|
||||
{
|
||||
"name": name,
|
||||
"type": "lora",
|
||||
"display": f"<lora:{name}:1.0>",
|
||||
"value": f"<lora:{name}:1.0>",
|
||||
"priority": 1 if match_name == match_prefix else 0,
|
||||
}
|
||||
)
|
||||
elif match_prefix in match_name:
|
||||
suggestions.append(
|
||||
{
|
||||
"name": name,
|
||||
"type": "lora",
|
||||
"display": f"<lora:{name}:1.0>",
|
||||
"value": f"<lora:{name}:1.0>",
|
||||
"priority": -1,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error loading LoRAs: {e}")
|
||||
|
||||
# Sort by priority and name
|
||||
suggestions.sort(key=lambda x: (-x["priority"], x["name"]))
|
||||
|
||||
# Limit results
|
||||
return suggestions[:max_results]
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Empty Latent Batch tool for ComfyUI."""
|
||||
|
||||
from .node import EmptyLatentBatchNode
|
||||
|
||||
__all__ = ["EmptyLatentBatchNode"]
|
||||
@@ -0,0 +1,101 @@
|
||||
"""Logic for creating empty latent tensors with batch support."""
|
||||
|
||||
import torch
|
||||
from typing import Dict, Tuple
|
||||
|
||||
|
||||
def create_empty_latent_batch(
|
||||
width: int, height: int, batch_size: int = 1
|
||||
) -> Dict[str, torch.Tensor]:
|
||||
"""
|
||||
Create empty latent tensor with batch support.
|
||||
|
||||
Args:
|
||||
width: Width in pixels (will be divided by 8 for latent space)
|
||||
height: Height in pixels (will be divided by 8 for latent space)
|
||||
batch_size: Number of latents in the batch
|
||||
|
||||
Returns:
|
||||
Dictionary containing the latent samples tensor
|
||||
|
||||
Raises:
|
||||
ValueError: If dimensions are invalid
|
||||
"""
|
||||
# Validate inputs
|
||||
if width <= 0 or height <= 0:
|
||||
raise ValueError(f"Width and height must be positive, got {width}x{height}")
|
||||
|
||||
if batch_size <= 0:
|
||||
raise ValueError(f"Batch size must be positive, got {batch_size}")
|
||||
|
||||
# Ensure dimensions are divisible by 8 (VAE requirement)
|
||||
if width % 8 != 0 or height % 8 != 0:
|
||||
raise ValueError(
|
||||
f"Width and height must be divisible by 8, got {width}x{height}"
|
||||
)
|
||||
|
||||
# Convert pixel dimensions to latent space (divide by 8)
|
||||
latent_width = width // 8
|
||||
latent_height = height // 8
|
||||
|
||||
# Create empty latent tensor
|
||||
# ComfyUI latent format: [batch, channels, height, width]
|
||||
# Standard VAE uses 4 channels
|
||||
latent_tensor = torch.zeros(batch_size, 4, latent_height, latent_width)
|
||||
|
||||
return {"samples": latent_tensor}
|
||||
|
||||
|
||||
def validate_dimensions(width: int, height: int) -> bool:
|
||||
"""
|
||||
Validate that dimensions are suitable for latent creation.
|
||||
|
||||
Args:
|
||||
width: Width in pixels
|
||||
height: Height in pixels
|
||||
|
||||
Returns:
|
||||
True if dimensions are valid
|
||||
"""
|
||||
# Check basic constraints
|
||||
if width <= 0 or height <= 0:
|
||||
return False
|
||||
|
||||
# Check divisibility by 8
|
||||
if width % 8 != 0 or height % 8 != 0:
|
||||
return False
|
||||
|
||||
# Check reasonable size limits (64x64 to 8192x8192)
|
||||
if width < 64 or height < 64:
|
||||
return False
|
||||
|
||||
if width > 8192 or height > 8192:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def sanitize_dimensions(width: int, height: int) -> Tuple[int, int]:
|
||||
"""
|
||||
Sanitize dimensions to ensure they meet latent requirements.
|
||||
|
||||
Args:
|
||||
width: Input width
|
||||
height: Input height
|
||||
|
||||
Returns:
|
||||
Tuple of (sanitized_width, sanitized_height)
|
||||
"""
|
||||
# Ensure minimum dimensions
|
||||
width = max(64, width)
|
||||
height = max(64, height)
|
||||
|
||||
# Ensure maximum dimensions
|
||||
width = min(8192, width)
|
||||
height = min(8192, height)
|
||||
|
||||
# Round to nearest multiple of 8
|
||||
width = (width + 7) // 8 * 8
|
||||
height = (height + 7) // 8 * 8
|
||||
|
||||
return width, height
|
||||
@@ -0,0 +1,309 @@
|
||||
"""Empty Latent Batch node for ComfyUI."""
|
||||
|
||||
import torch
|
||||
from typing import Dict, Tuple
|
||||
|
||||
from ...base.base_node import ComfyAssetsBaseNode
|
||||
from .logic import (
|
||||
create_empty_latent_batch,
|
||||
validate_dimensions,
|
||||
sanitize_dimensions,
|
||||
)
|
||||
from ..width_height_selector.logic import get_preset_dimensions
|
||||
from ..width_height_selector.presets import (
|
||||
PRESET_OPTIONS,
|
||||
PRESET_METADATA,
|
||||
)
|
||||
|
||||
|
||||
class EmptyLatentBatchNode(ComfyAssetsBaseNode):
|
||||
"""
|
||||
Empty Latent Batch node for creating empty latent tensors with batch support.
|
||||
|
||||
Creates empty latent tensors with specified dimensions and batch size,
|
||||
compatible with ComfyUI's latent format for use with VAE and diffusion models.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
"""Define the input types for the ComfyUI node."""
|
||||
# Create formatted preset options with metadata
|
||||
preset_options = ["custom"] # Custom first
|
||||
|
||||
# Add formatted presets with metadata
|
||||
for preset_name in PRESET_OPTIONS.keys():
|
||||
if preset_name != "custom":
|
||||
metadata = PRESET_METADATA.get(preset_name)
|
||||
if metadata:
|
||||
formatted_option = (
|
||||
f"{preset_name} - {metadata.aspect_ratio} "
|
||||
f"({metadata.megapixels:.1f}MP) - {metadata.model_group}"
|
||||
)
|
||||
preset_options.append(formatted_option)
|
||||
else:
|
||||
preset_options.append(preset_name)
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"preset": (
|
||||
preset_options,
|
||||
{
|
||||
"default": "custom",
|
||||
"tooltip": "Select from optimized resolution presets or use "
|
||||
"custom dimensions. SDXL presets are ~1MP, FLUX presets are "
|
||||
"higher resolution, Ultra-wide presets support modern "
|
||||
"aspect ratios.",
|
||||
},
|
||||
),
|
||||
"width": (
|
||||
"INT",
|
||||
{
|
||||
"default": 1024,
|
||||
"min": 64,
|
||||
"max": 8192,
|
||||
"step": 8,
|
||||
"tooltip": "Custom width in pixels (must be multiple of 8). "
|
||||
"Used when preset is 'custom' or as fallback for invalid "
|
||||
"presets. This will be converted to latent space dimensions.",
|
||||
},
|
||||
),
|
||||
"height": (
|
||||
"INT",
|
||||
{
|
||||
"default": 1024,
|
||||
"min": 64,
|
||||
"max": 8192,
|
||||
"step": 8,
|
||||
"tooltip": "Custom height in pixels (must be multiple of 8). "
|
||||
"Used when preset is 'custom' or as fallback for invalid "
|
||||
"presets. This will be converted to latent space dimensions.",
|
||||
},
|
||||
),
|
||||
"batch_size": (
|
||||
"INT",
|
||||
{
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 64,
|
||||
"step": 1,
|
||||
"tooltip": "Number of empty latents to create in the batch. "
|
||||
"Useful for batch processing workflows.",
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT", "INT", "INT", "INT")
|
||||
RETURN_NAMES = ("latent", "width", "height", "batch_size")
|
||||
FUNCTION = "create_empty_latent"
|
||||
CATEGORY = "🫶 ComfyAssets/📦 Latents"
|
||||
|
||||
def create_empty_latent(
|
||||
self, preset: str, width: int, height: int, batch_size: int
|
||||
) -> Tuple[Dict[str, torch.Tensor], int, int, int]:
|
||||
"""
|
||||
Create empty latent tensor with specified dimensions and batch size.
|
||||
|
||||
Args:
|
||||
preset: Selected preset name or formatted preset string
|
||||
width: Custom width value
|
||||
height: Custom height value
|
||||
batch_size: Number of latents in the batch
|
||||
|
||||
Returns:
|
||||
Tuple containing (latent dict, width, height, batch_size)
|
||||
"""
|
||||
try:
|
||||
# Extract original preset name from formatted string if needed
|
||||
original_preset = self._extract_preset_name(preset)
|
||||
|
||||
# Get base dimensions from preset or custom input
|
||||
base_width, base_height = get_preset_dimensions(
|
||||
original_preset, width, height
|
||||
)
|
||||
|
||||
# Sanitize dimensions to ensure they meet requirements
|
||||
final_width, final_height = sanitize_dimensions(base_width, base_height)
|
||||
|
||||
# Log if dimensions were changed from the base dimensions
|
||||
if final_width != base_width or final_height != base_height:
|
||||
self.log_info(
|
||||
f"Dimensions adjusted from {base_width}×{base_height} to "
|
||||
f"{final_width}×{final_height} to meet VAE requirements"
|
||||
)
|
||||
|
||||
# Validate final dimensions
|
||||
if not validate_dimensions(final_width, final_height):
|
||||
self.handle_error(
|
||||
f"Invalid dimensions after sanitization: {final_width}×{final_height}"
|
||||
)
|
||||
|
||||
# Validate batch size
|
||||
if batch_size <= 0:
|
||||
self.handle_error(f"Batch size must be positive, got {batch_size}")
|
||||
|
||||
if batch_size > 64:
|
||||
self.log_info(
|
||||
f"Large batch size ({batch_size}) may use significant memory"
|
||||
)
|
||||
|
||||
# Create the empty latent batch
|
||||
latent_dict = create_empty_latent_batch(
|
||||
final_width, final_height, batch_size
|
||||
)
|
||||
|
||||
# Log the operation
|
||||
latent_height = final_height // 8
|
||||
latent_width = final_width // 8
|
||||
self.log_info(
|
||||
f"Created empty latent batch: {batch_size}×4×{latent_height}×{latent_width} "
|
||||
f"(pixel dims: {final_width}×{final_height})"
|
||||
)
|
||||
|
||||
return (latent_dict, final_width, final_height, batch_size)
|
||||
|
||||
except Exception as e:
|
||||
# Handle any unexpected errors gracefully
|
||||
error_msg = f"Error creating empty latent batch: {str(e)}"
|
||||
self.handle_error(error_msg, e)
|
||||
|
||||
def _extract_preset_name(self, formatted_preset: str) -> str:
|
||||
"""
|
||||
Extract the original preset name from a formatted preset string.
|
||||
|
||||
Args:
|
||||
formatted_preset: Either original preset name or formatted string
|
||||
|
||||
Returns:
|
||||
Original preset name
|
||||
"""
|
||||
# If it's already "custom", return as-is
|
||||
if formatted_preset == "custom":
|
||||
return formatted_preset
|
||||
|
||||
# If it contains formatting metadata, extract the resolution part
|
||||
if " - " in formatted_preset:
|
||||
# Format is: "1024×1024 - 1:1 (1.0MP) - SDXL"
|
||||
# Extract the first part (resolution)
|
||||
resolution_part = formatted_preset.split(" - ")[0]
|
||||
|
||||
# Verify this is a valid preset name
|
||||
if resolution_part in PRESET_OPTIONS:
|
||||
return resolution_part
|
||||
|
||||
# If no formatting or not found, check if it's directly a valid preset
|
||||
if formatted_preset in PRESET_OPTIONS:
|
||||
return formatted_preset
|
||||
|
||||
# Default to "custom" if we can't parse it
|
||||
return "custom"
|
||||
|
||||
def validate_inputs(
|
||||
self, preset: str, width: int, height: int, batch_size: int
|
||||
) -> bool:
|
||||
"""
|
||||
Validate node inputs.
|
||||
|
||||
Args:
|
||||
preset: Preset name or formatted preset string
|
||||
width: Width value
|
||||
height: Height value
|
||||
batch_size: Batch size value
|
||||
|
||||
Returns:
|
||||
True if inputs are valid
|
||||
"""
|
||||
# Extract original preset name
|
||||
original_preset = self._extract_preset_name(preset)
|
||||
|
||||
# Check if preset exists or is custom
|
||||
if original_preset != "custom" and original_preset not in PRESET_OPTIONS:
|
||||
return False
|
||||
|
||||
# Get dimensions from preset or use custom
|
||||
base_width, base_height = get_preset_dimensions(original_preset, width, height)
|
||||
|
||||
# Check dimension validity (after sanitization)
|
||||
sanitized_width, sanitized_height = sanitize_dimensions(base_width, base_height)
|
||||
if not validate_dimensions(sanitized_width, sanitized_height):
|
||||
return False
|
||||
|
||||
# Check batch size
|
||||
if batch_size <= 0 or batch_size > 64:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_latent_info(self, width: int, height: int, batch_size: int) -> str:
|
||||
"""
|
||||
Get descriptive information about the latent that will be created.
|
||||
|
||||
Args:
|
||||
width: Width in pixels
|
||||
height: Height in pixels
|
||||
batch_size: Batch size
|
||||
|
||||
Returns:
|
||||
Description string for the latent
|
||||
"""
|
||||
sanitized_width, sanitized_height = sanitize_dimensions(width, height)
|
||||
latent_width = sanitized_width // 8
|
||||
latent_height = sanitized_height // 8
|
||||
|
||||
return (
|
||||
f"Empty latent batch: {batch_size} × 4 × {latent_height} × {latent_width} "
|
||||
f"(pixel dimensions: {sanitized_width}×{sanitized_height})"
|
||||
)
|
||||
|
||||
def get_memory_estimate(self, width: int, height: int, batch_size: int) -> str:
|
||||
"""
|
||||
Estimate memory usage for the latent batch.
|
||||
|
||||
Args:
|
||||
width: Width in pixels
|
||||
height: Height in pixels
|
||||
batch_size: Batch size
|
||||
|
||||
Returns:
|
||||
Memory estimate string
|
||||
"""
|
||||
sanitized_width, sanitized_height = sanitize_dimensions(width, height)
|
||||
latent_width = sanitized_width // 8
|
||||
latent_height = sanitized_height // 8
|
||||
|
||||
# Calculate tensor size in bytes (float32 = 4 bytes per element)
|
||||
elements = batch_size * 4 * latent_height * latent_width
|
||||
bytes_size = elements * 4 # 4 bytes per float32
|
||||
|
||||
# Convert to human-readable format
|
||||
if bytes_size < 1024:
|
||||
return f"{bytes_size} bytes"
|
||||
elif bytes_size < 1024 * 1024:
|
||||
return f"{bytes_size / 1024:.1f} KB"
|
||||
elif bytes_size < 1024 * 1024 * 1024:
|
||||
return f"{bytes_size / (1024 * 1024):.1f} MB"
|
||||
else:
|
||||
return f"{bytes_size / (1024 * 1024 * 1024):.1f} GB"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation of the node."""
|
||||
return "EmptyLatentBatchNode"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Detailed string representation of the node."""
|
||||
return (
|
||||
f"EmptyLatentBatchNode("
|
||||
f"category='{self.CATEGORY}', "
|
||||
f"function='{self.FUNCTION}'"
|
||||
f")"
|
||||
)
|
||||
|
||||
|
||||
# Node class mappings for ComfyUI registration
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"EmptyLatentBatch": EmptyLatentBatchNode,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"EmptyLatentBatch": "Empty Latent Batch",
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"models": [
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-flash",
|
||||
"gemini-2.5-flash-lite",
|
||||
"gemini-2.5-pro-preview-03-25",
|
||||
"gemini-2.5-flash-preview-05-20",
|
||||
"gemini-2.5-pro-preview-05-06",
|
||||
"gemini-2.5-pro-preview-06-05",
|
||||
"gemini-2.5-flash-lite-preview-06-17",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-2.0-flash-001",
|
||||
"gemini-2.0-flash-lite-001",
|
||||
"gemini-2.0-flash-lite",
|
||||
"gemini-2.5-flash-preview-tts",
|
||||
"gemini-2.5-pro-preview-tts",
|
||||
"gemini-2.0-flash-preview-image-generation",
|
||||
"gemini-2.0-flash-exp",
|
||||
"gemini-2.0-flash-exp-image-generation",
|
||||
"gemini-2.0-flash-lite-preview-02-05",
|
||||
"gemini-2.0-flash-lite-preview",
|
||||
"gemini-2.0-pro-exp",
|
||||
"gemini-2.0-pro-exp-02-05",
|
||||
"learnlm-2.0-flash-experimental",
|
||||
"gemini-1.5-pro-latest",
|
||||
"gemini-1.5-pro-002",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-1.5-flash-latest",
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-flash-002",
|
||||
"gemini-1.5-flash-8b",
|
||||
"gemini-1.5-flash-8b-001",
|
||||
"gemini-1.5-flash-8b-latest",
|
||||
"gemini-2.0-flash-thinking-exp-01-21",
|
||||
"gemini-2.0-flash-thinking-exp",
|
||||
"gemini-2.0-flash-thinking-exp-1219",
|
||||
"gemma-3-1b-it",
|
||||
"gemma-3-4b-it",
|
||||
"gemma-3-12b-it",
|
||||
"gemma-3-27b-it",
|
||||
"gemma-3n-e4b-it",
|
||||
"gemma-3n-e2b-it",
|
||||
"gemini-exp-1206"
|
||||
],
|
||||
"descriptions": {
|
||||
"gemini-1.5-pro-latest": "Gemini 1.5 Pro Latest",
|
||||
"gemini-1.5-pro-002": "Gemini 1.5 Pro 002",
|
||||
"gemini-1.5-pro": "Gemini 1.5 Pro",
|
||||
"gemini-1.5-flash-latest": "Gemini 1.5 Flash Latest",
|
||||
"gemini-1.5-flash": "Gemini 1.5 Flash",
|
||||
"gemini-1.5-flash-002": "Gemini 1.5 Flash 002",
|
||||
"gemini-1.5-flash-8b": "Gemini 1.5 Flash-8B",
|
||||
"gemini-1.5-flash-8b-001": "Gemini 1.5 Flash-8B 001",
|
||||
"gemini-1.5-flash-8b-latest": "Gemini 1.5 Flash-8B Latest",
|
||||
"gemini-2.5-pro-preview-03-25": "Gemini 2.5 Pro Preview 03-25",
|
||||
"gemini-2.5-flash-preview-05-20": "Gemini 2.5 Flash Preview 05-20",
|
||||
"gemini-2.5-flash": "Gemini 2.5 Flash",
|
||||
"gemini-2.5-flash-lite-preview-06-17": "Gemini 2.5 Flash-Lite Preview 06-17",
|
||||
"gemini-2.5-pro-preview-05-06": "Gemini 2.5 Pro Preview 05-06",
|
||||
"gemini-2.5-pro-preview-06-05": "Gemini 2.5 Pro Preview",
|
||||
"gemini-2.5-pro": "Gemini 2.5 Pro",
|
||||
"gemini-2.0-flash-exp": "Gemini 2.0 Flash Experimental",
|
||||
"gemini-2.0-flash": "Gemini 2.0 Flash",
|
||||
"gemini-2.0-flash-001": "Gemini 2.0 Flash 001",
|
||||
"gemini-2.0-flash-exp-image-generation": "Gemini 2.0 Flash (Image Generation) Experimental",
|
||||
"gemini-2.0-flash-lite-001": "Gemini 2.0 Flash-Lite 001",
|
||||
"gemini-2.0-flash-lite": "Gemini 2.0 Flash-Lite",
|
||||
"gemini-2.0-flash-preview-image-generation": "Gemini 2.0 Flash Preview Image Generation",
|
||||
"gemini-2.0-flash-lite-preview-02-05": "Gemini 2.0 Flash-Lite Preview 02-05",
|
||||
"gemini-2.0-flash-lite-preview": "Gemini 2.0 Flash-Lite Preview",
|
||||
"gemini-2.0-pro-exp": "Gemini 2.0 Pro Experimental",
|
||||
"gemini-2.0-pro-exp-02-05": "Gemini 2.0 Pro Experimental 02-05",
|
||||
"gemini-exp-1206": "Gemini Experimental 1206",
|
||||
"gemini-2.0-flash-thinking-exp-01-21": "Gemini 2.5 Flash Preview 05-20",
|
||||
"gemini-2.0-flash-thinking-exp": "Gemini 2.5 Flash Preview 05-20",
|
||||
"gemini-2.0-flash-thinking-exp-1219": "Gemini 2.5 Flash Preview 05-20",
|
||||
"gemini-2.5-flash-preview-tts": "Gemini 2.5 Flash Preview TTS",
|
||||
"gemini-2.5-pro-preview-tts": "Gemini 2.5 Pro Preview TTS",
|
||||
"learnlm-2.0-flash-experimental": "LearnLM 2.0 Flash Experimental",
|
||||
"gemma-3-1b-it": "Gemma 3 1B",
|
||||
"gemma-3-4b-it": "Gemma 3 4B",
|
||||
"gemma-3-12b-it": "Gemma 3 12B",
|
||||
"gemma-3-27b-it": "Gemma 3 27B",
|
||||
"gemma-3n-e4b-it": "Gemma 3n E4B",
|
||||
"gemma-3n-e2b-it": "Gemma 3n E2B",
|
||||
"gemini-2.5-flash-lite": "Gemini 2.5 Flash-Lite"
|
||||
},
|
||||
"timestamp": 1754568195.1098156
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Gemini Prompt Engineer node for ComfyUI."""
|
||||
|
||||
from .node import GeminiPromptNode
|
||||
|
||||
__all__ = ["GeminiPromptNode"]
|
||||
@@ -0,0 +1,163 @@
|
||||
"""Logic for Gemini API integration and prompt generation."""
|
||||
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from .prompts import PROMPT_TEMPLATES
|
||||
|
||||
|
||||
def tensor_to_pil(tensor: np.ndarray) -> Image.Image:
|
||||
"""Convert ComfyUI tensor to PIL Image.
|
||||
|
||||
Args:
|
||||
tensor: Input tensor in ComfyUI format (B, H, W, C)
|
||||
|
||||
Returns:
|
||||
PIL Image object
|
||||
"""
|
||||
# ComfyUI tensors are in [0, 1] range
|
||||
if tensor.ndim == 4:
|
||||
# Take first image from batch
|
||||
tensor = tensor[0]
|
||||
|
||||
# Convert to uint8
|
||||
image_array = (tensor * 255).astype(np.uint8)
|
||||
|
||||
# Convert to PIL
|
||||
return Image.fromarray(image_array, mode="RGB")
|
||||
|
||||
|
||||
def image_to_base64(image: Image.Image, format: str = "PNG") -> str:
|
||||
"""Convert PIL Image to base64 string.
|
||||
|
||||
Args:
|
||||
image: PIL Image object
|
||||
format: Image format (PNG or JPEG)
|
||||
|
||||
Returns:
|
||||
Base64 encoded string
|
||||
"""
|
||||
buffer = io.BytesIO()
|
||||
image.save(buffer, format=format)
|
||||
buffer.seek(0)
|
||||
return base64.b64encode(buffer.read()).decode("utf-8")
|
||||
|
||||
|
||||
def get_api_key() -> Optional[str]:
|
||||
"""Get Gemini API key from environment or config.
|
||||
|
||||
Returns:
|
||||
API key string or None if not found
|
||||
"""
|
||||
# Check environment variable first
|
||||
api_key = os.environ.get("GEMINI_API_KEY")
|
||||
|
||||
if not api_key:
|
||||
# Check for config file in ComfyUI directory
|
||||
try:
|
||||
config_path = os.path.join(
|
||||
os.path.dirname(__file__), "..", "..", "..", "gemini_config.json"
|
||||
)
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r") as f:
|
||||
config = json.load(f)
|
||||
api_key = config.get("api_key")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return api_key
|
||||
|
||||
|
||||
def analyze_image_with_gemini(
|
||||
image: np.ndarray,
|
||||
prompt_type: str,
|
||||
api_key: Optional[str] = None,
|
||||
custom_prompt: Optional[str] = None,
|
||||
model_name: str = "gemini-1.5-flash",
|
||||
) -> Tuple[str, Optional[str]]:
|
||||
"""Analyze image using Gemini API and generate appropriate prompt.
|
||||
|
||||
Args:
|
||||
image: Input image tensor
|
||||
prompt_type: Type of prompt to generate (flux, sdxl, danbooru, video)
|
||||
api_key: Gemini API key (optional, will try to get from env/config)
|
||||
custom_prompt: Custom system prompt to use instead of templates
|
||||
model_name: Gemini model to use (default: gemini-1.5-flash)
|
||||
|
||||
Returns:
|
||||
Tuple of (generated_prompt, error_message)
|
||||
"""
|
||||
# Get API key
|
||||
if not api_key:
|
||||
api_key = get_api_key()
|
||||
|
||||
if not api_key:
|
||||
return (
|
||||
"",
|
||||
"Gemini API key not found. Please set GEMINI_API_KEY environment variable or provide it in the node.",
|
||||
)
|
||||
|
||||
# Convert tensor to PIL image
|
||||
try:
|
||||
pil_image = tensor_to_pil(image)
|
||||
except Exception as e:
|
||||
return "", f"Failed to convert image: {str(e)}"
|
||||
|
||||
# Get system prompt
|
||||
if custom_prompt:
|
||||
system_prompt = custom_prompt
|
||||
else:
|
||||
system_prompt = PROMPT_TEMPLATES.get(prompt_type, PROMPT_TEMPLATES["flux"])
|
||||
|
||||
# Here we would normally make the API call to Gemini
|
||||
# For now, we'll import the google-generativeai library
|
||||
try:
|
||||
import google.generativeai as genai
|
||||
except ImportError:
|
||||
return (
|
||||
"",
|
||||
"google-generativeai library not installed. Please run: pip install google-generativeai",
|
||||
)
|
||||
|
||||
try:
|
||||
# Configure Gemini
|
||||
genai.configure(api_key=api_key)
|
||||
|
||||
# Create model
|
||||
model = genai.GenerativeModel(model_name)
|
||||
|
||||
# Generate content
|
||||
response = model.generate_content(
|
||||
[
|
||||
system_prompt,
|
||||
pil_image,
|
||||
"Analyze this image and generate an appropriate prompt according to the instructions.",
|
||||
]
|
||||
)
|
||||
|
||||
# Extract text from response
|
||||
if response.text:
|
||||
return response.text.strip(), None
|
||||
else:
|
||||
return "", "No response generated from Gemini"
|
||||
|
||||
except Exception as e:
|
||||
return "", f"Gemini API error: {str(e)}"
|
||||
|
||||
|
||||
def validate_prompt_type(prompt_type: str) -> bool:
|
||||
"""Validate if prompt type is supported.
|
||||
|
||||
Args:
|
||||
prompt_type: Type of prompt to validate
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
return prompt_type in PROMPT_TEMPLATES
|
||||
@@ -0,0 +1,191 @@
|
||||
"""Dynamic model fetching and caching for Gemini API."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Cache settings
|
||||
CACHE_DURATION = 3600 * 24 # 24 hours in seconds
|
||||
CACHE_FILE = os.path.join(os.path.dirname(__file__), ".gemini_models_cache.json")
|
||||
|
||||
|
||||
def get_available_models(
|
||||
api_key: Optional[str] = None, silent: bool = False
|
||||
) -> Tuple[List[str], Dict[str, str]]:
|
||||
"""Fetch available Gemini models that support generateContent.
|
||||
|
||||
Args:
|
||||
api_key: Optional API key. If not provided, will try to get from environment.
|
||||
silent: If True, suppress error logging (useful for initial load).
|
||||
|
||||
Returns:
|
||||
Tuple of (model_names_list, model_descriptions_dict)
|
||||
"""
|
||||
# Check cache first
|
||||
cached_data = _load_cache()
|
||||
if cached_data:
|
||||
return cached_data["models"], cached_data["descriptions"]
|
||||
|
||||
# Try to fetch from API
|
||||
try:
|
||||
models, descriptions = _fetch_models_from_api(api_key, silent=silent)
|
||||
if models:
|
||||
_save_cache(models, descriptions)
|
||||
return models, descriptions
|
||||
except Exception as e:
|
||||
if not silent:
|
||||
logger.warning(f"Failed to fetch models from API: {e}")
|
||||
|
||||
# Fall back to defaults
|
||||
from .prompts import DEFAULT_GEMINI_MODELS
|
||||
|
||||
return DEFAULT_GEMINI_MODELS, {}
|
||||
|
||||
|
||||
def _fetch_models_from_api(
|
||||
api_key: Optional[str] = None, silent: bool = False
|
||||
) -> Tuple[List[str], Dict[str, str]]:
|
||||
"""Fetch models from Gemini API.
|
||||
|
||||
Args:
|
||||
api_key: Optional API key.
|
||||
silent: If True, suppress error logging.
|
||||
|
||||
Returns:
|
||||
Tuple of (model_names_list, model_descriptions_dict)
|
||||
"""
|
||||
try:
|
||||
import google.generativeai as genai
|
||||
except ImportError:
|
||||
if not silent:
|
||||
logger.error("google-generativeai not installed")
|
||||
return [], {}
|
||||
|
||||
# Get API key
|
||||
if not api_key:
|
||||
from .logic import get_api_key
|
||||
|
||||
api_key = get_api_key()
|
||||
|
||||
if not api_key:
|
||||
if not silent:
|
||||
logger.debug("No API key available for fetching models")
|
||||
return [], {}
|
||||
|
||||
try:
|
||||
genai.configure(api_key=api_key)
|
||||
|
||||
models = []
|
||||
descriptions = {}
|
||||
|
||||
# Fetch all models
|
||||
for model in genai.list_models():
|
||||
# Only include models that support generateContent
|
||||
if "generateContent" in model.supported_generation_methods:
|
||||
# Remove "models/" prefix from name
|
||||
model_name = model.name.replace("models/", "")
|
||||
models.append(model_name)
|
||||
descriptions[model_name] = model.display_name
|
||||
|
||||
# Sort models by priority (newer versions first)
|
||||
models = _sort_models(models)
|
||||
|
||||
return models, descriptions
|
||||
|
||||
except Exception as e:
|
||||
if not silent:
|
||||
logger.error(f"Error fetching models from API: {e}")
|
||||
return [], {}
|
||||
|
||||
|
||||
def _sort_models(models: List[str]) -> List[str]:
|
||||
"""Sort models by version and capability.
|
||||
|
||||
Prioritizes:
|
||||
1. Newer versions (2.5 > 2.0 > 1.5)
|
||||
2. Non-experimental models
|
||||
3. Flash models for general use
|
||||
"""
|
||||
|
||||
def sort_key(model: str):
|
||||
# Priority scoring
|
||||
score = 0
|
||||
|
||||
# Version priority
|
||||
if "2.5" in model:
|
||||
score += 1000
|
||||
elif "2.0" in model:
|
||||
score += 800
|
||||
elif "1.5" in model:
|
||||
score += 600
|
||||
|
||||
# Model type priority
|
||||
if "pro" in model and "preview" not in model and "exp" not in model:
|
||||
score += 100
|
||||
elif "flash" in model and "preview" not in model and "exp" not in model:
|
||||
score += 90
|
||||
|
||||
# Penalize experimental/preview models
|
||||
if "exp" in model or "experimental" in model:
|
||||
score -= 50
|
||||
if "preview" in model:
|
||||
score -= 30
|
||||
|
||||
# Penalize specific variants
|
||||
if "thinking" in model:
|
||||
score -= 100
|
||||
if "tts" in model:
|
||||
score -= 100
|
||||
if "lite" in model:
|
||||
score -= 20
|
||||
|
||||
return -score # Negative for descending sort
|
||||
|
||||
return sorted(models, key=sort_key)
|
||||
|
||||
|
||||
def _load_cache() -> Optional[Dict]:
|
||||
"""Load cached model data if available and not expired."""
|
||||
if not os.path.exists(CACHE_FILE):
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(CACHE_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Check if cache is expired
|
||||
if time.time() - data.get("timestamp", 0) > CACHE_DURATION:
|
||||
return None
|
||||
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load cache: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _save_cache(models: List[str], descriptions: Dict[str, str]) -> None:
|
||||
"""Save model data to cache."""
|
||||
try:
|
||||
data = {
|
||||
"models": models,
|
||||
"descriptions": descriptions,
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
|
||||
with open(CACHE_FILE, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save cache: {e}")
|
||||
|
||||
|
||||
def clear_cache() -> None:
|
||||
"""Clear the model cache."""
|
||||
if os.path.exists(CACHE_FILE):
|
||||
try:
|
||||
os.remove(CACHE_FILE)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to clear cache: {e}")
|
||||
@@ -0,0 +1,169 @@
|
||||
"""Gemini Prompt Engineer node implementation."""
|
||||
|
||||
import torch
|
||||
|
||||
from ...base import ComfyAssetsBaseNode
|
||||
|
||||
from .logic import analyze_image_with_gemini, validate_prompt_type
|
||||
from .prompts import PROMPT_OPTIONS, DEFAULT_GEMINI_MODELS
|
||||
from .models import get_available_models
|
||||
|
||||
|
||||
class GeminiPromptNode(ComfyAssetsBaseNode):
|
||||
"""Analyzes images using Gemini AI to generate optimized prompts for various AI models."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
"""Define input types for the node."""
|
||||
# Get available models dynamically (silent mode for initial load)
|
||||
models, _ = get_available_models(silent=True)
|
||||
|
||||
# Use default if no models available
|
||||
if not models:
|
||||
models = DEFAULT_GEMINI_MODELS
|
||||
|
||||
# Find best default model
|
||||
default_model = models[0] if models else "gemini-2.5-flash"
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"prompt_type": (PROMPT_OPTIONS, {"default": "flux"}),
|
||||
"model": (models, {"default": default_model}),
|
||||
},
|
||||
"optional": {
|
||||
"api_key": ("STRING", {"default": "", "multiline": False}),
|
||||
"custom_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"placeholder": "Optional: Enter custom system prompt instead of using templates",
|
||||
},
|
||||
),
|
||||
"refresh_models": (
|
||||
"BOOLEAN",
|
||||
{"default": False, "label_on": "Refresh", "label_off": "Skip"},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING")
|
||||
RETURN_NAMES = ("prompt", "negative_prompt")
|
||||
FUNCTION = "generate_prompt"
|
||||
CATEGORY = "🫶 ComfyAssets/🧠 Prompts"
|
||||
|
||||
DESCRIPTION = """
|
||||
Analyzes images using Google's Gemini AI to generate optimized prompts.
|
||||
|
||||
Supports multiple prompt formats:
|
||||
- FLUX: Detailed artistic prompts with quality markers
|
||||
- SDXL: Positive/negative prompt pairs with weight emphasis
|
||||
- Danbooru: Anime-style booru tags with underscores
|
||||
- Video: Motion and temporal descriptions for video generation
|
||||
|
||||
Requires Gemini API key (set GEMINI_API_KEY env var or provide in node).
|
||||
Install: pip install google-generativeai
|
||||
"""
|
||||
|
||||
def generate_prompt(
|
||||
self,
|
||||
image,
|
||||
prompt_type,
|
||||
model,
|
||||
api_key="",
|
||||
custom_prompt="",
|
||||
refresh_models=False,
|
||||
):
|
||||
"""Generate prompt from image using Gemini.
|
||||
|
||||
Args:
|
||||
image: Input image tensor
|
||||
prompt_type: Type of prompt to generate
|
||||
model: Gemini model to use
|
||||
api_key: Optional API key
|
||||
custom_prompt: Optional custom system prompt
|
||||
refresh_models: Whether to refresh the model list
|
||||
|
||||
Returns:
|
||||
Tuple of (prompt, negative_prompt)
|
||||
"""
|
||||
# Refresh models if requested
|
||||
if refresh_models and api_key:
|
||||
try:
|
||||
from .models import clear_cache
|
||||
|
||||
# Clear cache to force refresh on next node creation
|
||||
clear_cache()
|
||||
print(
|
||||
"Model cache cleared. Please recreate the node to see updated models."
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Failed to clear model cache: {e}")
|
||||
|
||||
# Validate prompt type
|
||||
if not validate_prompt_type(prompt_type):
|
||||
raise ValueError(f"Invalid prompt type: {prompt_type}")
|
||||
|
||||
# Convert torch tensor to numpy if needed
|
||||
if isinstance(image, torch.Tensor):
|
||||
image_np = image.cpu().numpy()
|
||||
else:
|
||||
image_np = image
|
||||
|
||||
# If API key is provided, try to refresh model list in background
|
||||
if api_key:
|
||||
try:
|
||||
from .models import get_available_models
|
||||
|
||||
# Try to get fresh models with the provided API key
|
||||
fresh_models, _ = get_available_models(api_key=api_key, silent=True)
|
||||
if fresh_models and fresh_models != DEFAULT_GEMINI_MODELS:
|
||||
# Models were successfully fetched with this API key
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Analyze image with Gemini
|
||||
prompt, error = analyze_image_with_gemini(
|
||||
image_np,
|
||||
prompt_type,
|
||||
api_key=api_key or None,
|
||||
custom_prompt=custom_prompt or None,
|
||||
model_name=model,
|
||||
)
|
||||
|
||||
if error:
|
||||
# Return error as prompt for visibility
|
||||
return (f"Error: {error}", "")
|
||||
|
||||
# Handle different prompt types
|
||||
if prompt_type == "sdxl":
|
||||
# SDXL returns positive and negative prompts
|
||||
lines = prompt.split("\n")
|
||||
positive_prompt = ""
|
||||
negative_prompt = ""
|
||||
|
||||
for line in lines:
|
||||
if line.lower().startswith("positive:"):
|
||||
positive_prompt = (
|
||||
line.replace("Positive:", "").replace("positive:", "").strip()
|
||||
)
|
||||
elif line.lower().startswith("negative:"):
|
||||
negative_prompt = (
|
||||
line.replace("Negative:", "").replace("negative:", "").strip()
|
||||
)
|
||||
|
||||
# If format not found, assume entire response is positive prompt
|
||||
if not positive_prompt:
|
||||
positive_prompt = prompt
|
||||
|
||||
return (positive_prompt, negative_prompt)
|
||||
|
||||
else:
|
||||
# Other formats don't use negative prompts
|
||||
return (prompt, "")
|
||||
|
||||
|
||||
# Node display name
|
||||
NODE_DISPLAY_NAME = "Gemini Prompt Engineer"
|
||||
@@ -0,0 +1,128 @@
|
||||
"""System prompts for different AI model types."""
|
||||
|
||||
FLUX_PROMPT = """You are an expert FLUX prompt engineer. Analyze the provided image and generate ONLY a FLUX prompt - no explanations, analysis, or additional text.
|
||||
|
||||
FLUX uses natural language descriptions, not comma-separated tags. Write a detailed, flowing description that reads like you're explaining the image to someone.
|
||||
|
||||
Include these elements in your description:
|
||||
- Main subject with specific details (appearance, clothing, expression, pose)
|
||||
- Environment and background details
|
||||
- Lighting conditions and atmosphere
|
||||
- Artistic style or photographic approach
|
||||
- Color palette and mood
|
||||
- Technical details if relevant (camera angle, focal length, etc.)
|
||||
- Textures and materials
|
||||
|
||||
Write in a natural, descriptive style. Use complete sentences that flow together. Be specific and detailed but maintain readability.
|
||||
|
||||
IMPORTANT: Return ONLY the prompt text. No analysis, headers, or additional commentary. Just the natural language description that can be directly used in FLUX.
|
||||
|
||||
Example of correct output:
|
||||
A close-up portrait of a middle-aged woman with curly red hair and green eyes, wearing a blue silk blouse. She has a warm smile and freckles across her cheeks. The lighting is soft and natural, coming from a window to her left, creating gentle shadows that accentuate her features. The background is softly blurred, showing hints of a cozy bookshelf. The overall mood is warm and inviting, captured in a photorealistic style with shallow depth of field."""
|
||||
|
||||
SDXL_PROMPT = """You are an expert prompt engineer specializing in SDXL (Stable Diffusion XL). Your task is to generate high-quality positive and negative prompts that conform to SDXL prompt formatting standards.
|
||||
|
||||
Your expertise includes:
|
||||
- Leveraging community-tested techniques (ComfyUI, A1111, InvokeAI)
|
||||
- Applying photographic theory for realism, composition, lighting
|
||||
- Following Civitai trend standards and style best practices
|
||||
- Mastering Pony Diffusion XL formatting for stylized and anime content
|
||||
|
||||
Structure prompts in this layered, modular format:
|
||||
[Main Subject], [Pose & Camera], [Lighting & Environment], [Style & Details], [Boost Terms], [Style References]
|
||||
|
||||
For SDXL specifically:
|
||||
- Use quality boosters: 8k, RAW photo, masterpiece, ultra detailed, cinematic lighting
|
||||
- Prioritize realism and artistry
|
||||
- Excellent for portraits, landscapes, or cinematic scenes
|
||||
|
||||
Instructions:
|
||||
|
||||
Only reply with two fields:
|
||||
Positive prompt: (Your positive prompt here)
|
||||
Negative prompt: (Your negative prompt here)
|
||||
|
||||
Do not include any commentary or explanation.
|
||||
|
||||
Use concise, highly descriptive language that maximizes visual richness.
|
||||
|
||||
Follow SDXL prompt conventions: prioritize subject clarity, camera perspective, lighting, mood, style tags, and composition.
|
||||
|
||||
Keep total token length efficient (ideally under 250 tokens).
|
||||
|
||||
Avoid redundancy and generic filler words.
|
||||
|
||||
Focus on crafting super high-quality prompts for stunning visual output.
|
||||
|
||||
Example Input:
|
||||
A futuristic cyberpunk samurai standing on a neon-lit rooftop in the rain.
|
||||
|
||||
Example Output:
|
||||
Positive prompt: cyberpunk samurai, neon-lit rooftop, dramatic rain, glowing katana, futuristic cityscape, night scene, cinematic lighting, intense expression, sleek cyber armor, atmospheric depth, ultra-detailed, masterpiece, 8k, sharp focus, trending on artstation
|
||||
Negative prompt: blurry, low quality, poorly drawn, extra limbs, bad anatomy, deformed hands, text, watermark, jpeg artifacts, duplicate, cropped, out of frame
|
||||
"""
|
||||
|
||||
DANBOORU_PROMPT = """You are a Danbooru tagging expert specializing in anime-style image tagging. Analyze the image and generate ONLY Danbooru-style tags - no explanations or analysis.
|
||||
|
||||
CRITICAL: Use strict Danbooru conventions:
|
||||
- Use underscores for multi-word tags (e.g., long_hair, school_uniform)
|
||||
- All tags must be lowercase
|
||||
- Character count comes first (1girl, 2boys, multiple_girls)
|
||||
- For anime models trained on Danbooru data, proper tagging is essential
|
||||
|
||||
Tag order and categories:
|
||||
1. Character count (1girl, solo, 2boys, etc.)
|
||||
2. Character features (hair_color, eye_color, hair_length)
|
||||
3. Expression/pose (smile, looking_at_viewer, sitting)
|
||||
4. Clothing (specific items with underscores)
|
||||
5. Background/setting (simple_background, outdoors, classroom)
|
||||
6. View/composition (upper_body, full_body, from_side)
|
||||
7. Quality tags (masterpiece, best_quality, highres)
|
||||
|
||||
Common quality prefix for anime models:
|
||||
"masterpiece, best_quality, very_aesthetic"
|
||||
|
||||
IMPORTANT: Return ONLY the comma-separated tags. Use underscores, not spaces. All lowercase.
|
||||
|
||||
Example of correct output:
|
||||
1girl, solo, long_hair, blue_eyes, blonde_hair, school_uniform, serafuku, pleated_skirt, smile, looking_at_viewer, classroom, sitting, desk, window, sunlight, upper_body, masterpiece, best_quality"""
|
||||
|
||||
VIDEO_PROMPT = """You are a WAN 2.2 video generation prompt specialist. Analyze the content and generate ONLY a video generation prompt optimized for WAN 2.2 - no explanations or analysis.
|
||||
|
||||
WAN 2.2 excels with rich, descriptive prompts that focus on:
|
||||
- Visual composition and scene elements
|
||||
- Specific movements and actions
|
||||
- Lighting and aesthetic details
|
||||
- Cinematographic elements
|
||||
|
||||
Write a single detailed paragraph describing the video scene. Focus on:
|
||||
- Main subjects and their actions
|
||||
- Visual style and atmosphere
|
||||
- Movement dynamics (use words like "intensely", "smoothly", "rapidly")
|
||||
- Environmental details and lighting
|
||||
- Specific visual elements and their interactions
|
||||
|
||||
Keep the prompt descriptive but concise. WAN 2.2 works best with natural language that paints a clear picture of the desired video.
|
||||
|
||||
IMPORTANT: Return ONLY the video prompt as a single descriptive paragraph. No analysis, headers, or additional text.
|
||||
|
||||
Example of correct output:
|
||||
Two anthropomorphic cats in comfy boxing gear and bright gloves fight intensely on a spotlighted stage, their movements fluid and dynamic as they exchange rapid punches under dramatic theater lighting that casts long shadows across the ring, with the crowd visible as blurred silhouettes in the darkened background."""
|
||||
|
||||
PROMPT_TEMPLATES = {
|
||||
"flux": FLUX_PROMPT,
|
||||
"sdxl": SDXL_PROMPT,
|
||||
"danbooru": DANBOORU_PROMPT,
|
||||
"video": VIDEO_PROMPT,
|
||||
}
|
||||
|
||||
PROMPT_OPTIONS = ["flux", "sdxl", "danbooru", "video"]
|
||||
|
||||
# Default models list (fallback if API is unavailable)
|
||||
DEFAULT_GEMINI_MODELS = [
|
||||
"gemini-2.5-flash",
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.0-flash",
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-pro",
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Image Scale Down By tool for ComfyUI."""
|
||||
|
||||
from .node import ImageScaleDownByNode
|
||||
|
||||
__all__ = ["ImageScaleDownByNode"]
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Core logic for ImageScaleDownBy tool."""
|
||||
|
||||
import torch.nn.functional as F
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
def scale_down_image(image: Tensor, scale_by: float) -> Tensor:
|
||||
"""Scale down an image by a given factor.
|
||||
|
||||
Args:
|
||||
image: Input image tensor of shape (batch, height, width, channels)
|
||||
scale_by: Scale factor between 0.01 and 1.0
|
||||
|
||||
Returns:
|
||||
Scaled down image tensor
|
||||
"""
|
||||
batch, height, width, channels = image.shape
|
||||
|
||||
# Calculate new dimensions
|
||||
new_height = int(height * scale_by)
|
||||
new_width = int(width * scale_by)
|
||||
|
||||
# Ensure minimum size of 1x1
|
||||
new_height = max(1, new_height)
|
||||
new_width = max(1, new_width)
|
||||
|
||||
# Convert from BHWC to BCHW for interpolation
|
||||
image_chw = image.permute(0, 3, 1, 2)
|
||||
|
||||
# Scale down the image using bilinear interpolation
|
||||
scaled = F.interpolate(
|
||||
image_chw,
|
||||
size=(new_height, new_width),
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
antialias=True,
|
||||
)
|
||||
|
||||
# Convert back to BHWC
|
||||
return scaled.permute(0, 2, 3, 1)
|
||||
@@ -0,0 +1,87 @@
|
||||
"""ComfyUI node implementation for ImageScaleDownBy."""
|
||||
|
||||
from typing import Dict, Any, Tuple
|
||||
|
||||
from torch import Tensor
|
||||
|
||||
from ...base import ComfyAssetsBaseNode
|
||||
from .logic import scale_down_image
|
||||
|
||||
|
||||
class ImageScaleDownByNode(ComfyAssetsBaseNode):
|
||||
"""
|
||||
Scales down images by a specified factor.
|
||||
|
||||
Reduces image dimensions proportionally using bilinear interpolation
|
||||
with antialiasing for smooth downscaling.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> Dict[str, Any]:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"scale_by": (
|
||||
"FLOAT",
|
||||
{
|
||||
"default": 0.5,
|
||||
"min": 0.01,
|
||||
"max": 1.0,
|
||||
"step": 0.01,
|
||||
"display": "number",
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "🫶 ComfyAssets/🖼️ Resolution"
|
||||
RETURN_NAMES = ("images",)
|
||||
FUNCTION = "scale_down"
|
||||
|
||||
def scale_down(self, images: Tensor, scale_by: float) -> Tuple[Tensor]:
|
||||
"""
|
||||
Scale down images by the specified factor.
|
||||
|
||||
Args:
|
||||
images: Input image tensor
|
||||
scale_by: Scale factor between 0.01 and 1.0
|
||||
|
||||
Returns:
|
||||
Tuple containing scaled down image tensor
|
||||
"""
|
||||
try:
|
||||
self.validate_inputs(images=images, scale_by=scale_by)
|
||||
|
||||
# Scale down the images
|
||||
scaled_images = scale_down_image(images, scale_by)
|
||||
|
||||
_, new_height, new_width, _ = scaled_images.shape
|
||||
_, orig_height, orig_width, _ = images.shape
|
||||
|
||||
self.log_info(
|
||||
f"Scaled down images from {orig_height}x{orig_width} "
|
||||
f"to {new_height}x{new_width} (scale factor: {scale_by})"
|
||||
)
|
||||
|
||||
return (scaled_images,)
|
||||
|
||||
except Exception as e:
|
||||
self.handle_error(f"Failed to scale down images: {str(e)}", e)
|
||||
|
||||
def validate_inputs(self, **kwargs) -> None:
|
||||
"""Validate inputs for ImageScaleDownBy node."""
|
||||
images = kwargs.get("images")
|
||||
scale_by = kwargs.get("scale_by")
|
||||
|
||||
if images is None:
|
||||
raise ValueError("Images input is required")
|
||||
|
||||
if not isinstance(images, Tensor) or len(images.shape) != 4:
|
||||
raise ValueError(
|
||||
f"Expected image tensor with shape (batch, height, width, channels), "
|
||||
f"got shape {images.shape if isinstance(images, Tensor) else 'non-tensor'}"
|
||||
)
|
||||
|
||||
if scale_by <= 0 or scale_by > 1.0:
|
||||
raise ValueError(f"scale_by must be between 0.01 and 1.0, got {scale_by}")
|
||||
@@ -0,0 +1,5 @@
|
||||
"""ImageToMultipleOf tool for ComfyUI-KikoTools."""
|
||||
|
||||
from .node import ImageToMultipleOfNode
|
||||
|
||||
__all__ = ["ImageToMultipleOfNode"]
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Core logic for ImageToMultipleOf tool."""
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
import torch.nn.functional as F
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
def calculate_dimensions_to_multiple(
|
||||
height: int, width: int, multiple_of: int
|
||||
) -> Tuple[int, int]:
|
||||
"""Calculate new dimensions that are multiples of the specified value.
|
||||
|
||||
Args:
|
||||
height: Original height
|
||||
width: Original width
|
||||
multiple_of: Value that dimensions should be multiple of
|
||||
|
||||
Returns:
|
||||
Tuple of (new_height, new_width)
|
||||
"""
|
||||
new_height = height - (height % multiple_of)
|
||||
new_width = width - (width % multiple_of)
|
||||
return new_height, new_width
|
||||
|
||||
|
||||
def process_image_to_multiple_of(
|
||||
image: Tensor, multiple_of: int, method: str
|
||||
) -> Tensor:
|
||||
"""Process image to ensure dimensions are multiples of specified value.
|
||||
|
||||
Args:
|
||||
image: Input image tensor of shape (batch, height, width, channels)
|
||||
multiple_of: Value that dimensions should be multiple of
|
||||
method: Processing method - "center crop" or "rescale"
|
||||
|
||||
Returns:
|
||||
Processed image tensor
|
||||
"""
|
||||
_, height, width, _ = image.shape
|
||||
new_height, new_width = calculate_dimensions_to_multiple(height, width, multiple_of)
|
||||
|
||||
if method == "rescale":
|
||||
# Rescale the image to the new dimensions
|
||||
# Convert from BHWC to BCHW for interpolation
|
||||
image_chw = image.permute(0, 3, 1, 2)
|
||||
rescaled = F.interpolate(
|
||||
image_chw,
|
||||
size=(new_height, new_width),
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)
|
||||
# Convert back to BHWC
|
||||
return rescaled.permute(0, 2, 3, 1)
|
||||
else: # center crop
|
||||
# Calculate crop offsets to center the crop
|
||||
top = (height - new_height) // 2
|
||||
left = (width - new_width) // 2
|
||||
bottom = top + new_height
|
||||
right = left + new_width
|
||||
return image[:, top:bottom, left:right, :]
|
||||
@@ -0,0 +1,103 @@
|
||||
"""ComfyUI node implementation for ImageToMultipleOf."""
|
||||
|
||||
from typing import Dict, Any, Tuple
|
||||
|
||||
from torch import Tensor
|
||||
|
||||
from ...base import ComfyAssetsBaseNode
|
||||
from .logic import process_image_to_multiple_of
|
||||
|
||||
|
||||
class ImageToMultipleOfNode(ComfyAssetsBaseNode):
|
||||
"""
|
||||
Adjusts image dimensions to be multiples of a specified value.
|
||||
|
||||
Useful for models that require specific dimension constraints.
|
||||
Supports both center cropping and rescaling methods.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> Dict[str, Any]:
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"multiple_of": (
|
||||
"INT",
|
||||
{
|
||||
"default": 64,
|
||||
"min": 1,
|
||||
"max": 256,
|
||||
"step": 16,
|
||||
"display": "number",
|
||||
},
|
||||
),
|
||||
"method": (["center crop", "rescale"],),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
CATEGORY = "🫶 ComfyAssets/🖼️ Resolution"
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "process"
|
||||
|
||||
def process(self, image: Tensor, multiple_of: int, method: str) -> Tuple[Tensor]:
|
||||
"""
|
||||
Process image to ensure dimensions are multiples of specified value.
|
||||
|
||||
Args:
|
||||
image: Input image tensor
|
||||
multiple_of: Value that dimensions should be multiple of
|
||||
method: Processing method - "center crop" or "rescale"
|
||||
|
||||
Returns:
|
||||
Tuple containing processed image tensor
|
||||
"""
|
||||
try:
|
||||
self.validate_inputs(image=image, multiple_of=multiple_of, method=method)
|
||||
|
||||
# Process the image
|
||||
processed_image = process_image_to_multiple_of(image, multiple_of, method)
|
||||
|
||||
_, new_height, new_width, _ = processed_image.shape
|
||||
self.log_info(
|
||||
f"Processed image from {image.shape[1]}x{image.shape[2]} "
|
||||
f"to {new_height}x{new_width} (multiple of {multiple_of}) "
|
||||
f"using {method}"
|
||||
)
|
||||
|
||||
return (processed_image,)
|
||||
|
||||
except Exception as e:
|
||||
self.handle_error(f"Failed to process image: {str(e)}", e)
|
||||
|
||||
def validate_inputs(self, **kwargs) -> None:
|
||||
"""Validate inputs for ImageToMultipleOf node."""
|
||||
image = kwargs.get("image")
|
||||
multiple_of = kwargs.get("multiple_of")
|
||||
method = kwargs.get("method")
|
||||
|
||||
if image is None:
|
||||
raise ValueError("Image input is required")
|
||||
|
||||
if not isinstance(image, Tensor) or len(image.shape) != 4:
|
||||
raise ValueError(
|
||||
f"Expected image tensor with shape (batch, height, width, channels), "
|
||||
f"got shape {image.shape if isinstance(image, Tensor) else 'non-tensor'}"
|
||||
)
|
||||
|
||||
if multiple_of <= 0:
|
||||
raise ValueError(f"multiple_of must be positive, got {multiple_of}")
|
||||
|
||||
if method not in ["center crop", "rescale"]:
|
||||
raise ValueError(f"Invalid method: {method}")
|
||||
|
||||
# Check if resulting dimensions would be too small
|
||||
_, height, width, _ = image.shape
|
||||
new_height = height - (height % multiple_of)
|
||||
new_width = width - (width % multiple_of)
|
||||
|
||||
if new_height <= 0 or new_width <= 0:
|
||||
raise ValueError(
|
||||
f"Image dimensions ({height}x{width}) are too small "
|
||||
f"to be adjusted to multiple of {multiple_of}"
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
from .node import KikoFilmGrainNode
|
||||
|
||||
__all__ = ["KikoFilmGrainNode"]
|
||||