diff --git a/.editorconfig b/.editorconfig
index 2f7ebc3..d4a2c44 100755
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,4 +1,4 @@
-# http://editorconfig.org
+# http://editorconfig.org
root = true
diff --git a/.gitattributes b/.gitattributes
index 20e628e..dfe0770 100755
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,2 @@
-# Auto detect text files and perform LF normalization
+# Auto detect text files and perform LF normalization
* text=auto
diff --git a/.gitignore b/.gitignore
index 7a0f7bc..d2d9a89 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,17 +1,17 @@
-# Byte-compiled / optimized / DLL files
+# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
-# OSX useful to ignore
+# OSX useful to ignore
*.DS_Store
.AppleDouble
.LSOverride
-# Thumbnails
+# Thumbnails
._*
-# Files that might appear in the root of a volume
+# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
@@ -20,17 +20,17 @@ __pycache__/
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
-# Directories potentially created on remote AFP share
+# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
-# C extensions
+# C extensions
*.so
-# Distribution / packaging
+# Distribution / packaging
.Python
env/
venv/
@@ -49,17 +49,17 @@ var/
.installed.cfg
*.egg
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
-# Installer logs
+# Installer logs
pip-log.txt
pip-delete-this-directory.txt
-# Unit test / coverage reports
+# Unit test / coverage reports
htmlcov/
.tox/
.coverage
@@ -71,30 +71,30 @@ coverage.xml
.hypothesis/
.pytest_cache/
-# Translations
+# Translations
*.mo
*.pot
-# Django stuff:
+# Django stuff:
*.log
-# Sphinx documentation
+# Sphinx documentation
docs/_build/
-# IntelliJ Idea
+# IntelliJ Idea
.idea
*.iml
*.ipr
*.iws
-# PyBuilder
+# PyBuilder
target/
-# Cookiecutter
+# Cookiecutter
output/
python_boilerplate/
cookiecutter-pypackage-env/
-# vscode settings
+# vscode settings
.history/
*.code-workspace
diff --git a/__init__.py b/__init__.py
index 368a25e..17440ab 100755
--- a/__init__.py
+++ b/__init__.py
@@ -4,7 +4,7 @@ import os
import sys
from .pysssss import init, get_ext_dir
-# High-Compatibility ANSI Colors
+# High-Compatibility ANSI Colors
MAGENTA_BRIGHT = "\033[95m"
BLUE_BRIGHT = "\033[94m"
CYAN_BRIGHT = "\033[96m"
@@ -13,8 +13,8 @@ CYAN = "\033[1;36m"
MAGENTA = "\033[1;35m"
RESET = "\033[0m"
-# 5-Line Gradient ASCII Header: EmAySee (Block Style)
-# Each line is wrapped individually to force the color shift
+# 5-Line Gradient ASCII Header: EmAySee (Block Style)
+# Each line is wrapped individually to force the color shift
banner = f"""
{MAGENTA_BRIGHT} ███████╗███╗ ███╗ █████╗ ██╗ ██╗ ██████╗███████╗███████╗{RESET}
{MAGENTA_BRIGHT} ██╔════╝████╗ ████║██╔══██╗╚██╗ ██╔╝██╔════╝██╔════╝██╔════╝{RESET}
@@ -49,14 +49,14 @@ if init():
if hasattr(module, "NODE_CLASS_MAPPINGS") and getattr(module, "NODE_CLASS_MAPPINGS") is not None:
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
- # Success Checkmark
+ # Success Checkmark
print(f" {GREEN}[✓]{RESET} {module_name}")
if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS") and getattr(module, "NODE_DISPLAY_NAME_MAPPINGS") is not None:
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
except Exception as e:
- # Failure Cross
+ # Failure Cross
print(f" \033[1;31m[✗]\033[0m {module_name} : Error -> {str(e)}")
print(f"{MAGENTA}----------------------------------------------------------{RESET}")
diff --git a/node.zip b/node.zip
index 465442c..eb9a79c 100755
Binary files a/node.zip and b/node.zip differ
diff --git a/py/EmAySee_AnyPassthrough.py b/py/EmAySee_AnyPassthrough.py
index b262dfa..b2cd6ce 100755
--- a/py/EmAySee_AnyPassthrough.py
+++ b/py/EmAySee_AnyPassthrough.py
@@ -8,38 +8,38 @@ class EmAySee_AnyPassthrough:
def INPUT_TYPES(s):
return {
"required": {
- # Input connection that accepts any data type (*).
- # You can rename this input visually in the UI.
+ # Input connection that accepts any data type (*).
+ # You can rename this input visually in the UI.
"input_any": ("*",),
}
}
- # The node outputs the same type as the input (any type)
+ # The node outputs the same type as the input (any type)
RETURN_TYPES = ("*",)
- # You can rename the output connection visually in the UI as well.
+ # You can rename the output connection visually in the UI as well.
RETURN_NAMES = ("output_any",)
- # Category for the ComfyUI menu
+ # Category for the ComfyUI menu
CATEGORY = "EmAySee_Utils"
- # Title displayed on the node
+ # Title displayed on the node
TITLE = "EmAySee Any Passthrough"
- # The method that will be executed when the node runs
+ # The method that will be executed when the node runs
FUNCTION = "pass_through_any"
def pass_through_any(self, input_any):
"""
Simply returns the input data of any type.
"""
- # The node's logic is just to return the input data.
+ # The node's logic is just to return the input data.
return (input_any,)
-# Mapping of node class name to the class
+# Mapping of node class name to the class
NODE_CLASS_MAPPINGS = {
"EmAySee_AnyPassthrough": EmAySee_AnyPassthrough
}
-# Mapping of node class name to the display name in the UI
+# Mapping of node class name to the display name in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_AnyPassthrough": "EmAySee Any Passthrough"
}
diff --git a/py/EmAySee_CalculateAspectRatio.py b/py/EmAySee_CalculateAspectRatio.py
index abd4dbc..7dc901e 100755
--- a/py/EmAySee_CalculateAspectRatio.py
+++ b/py/EmAySee_CalculateAspectRatio.py
@@ -18,7 +18,7 @@ class EmAySee_CalculateAspectRatio:
def EmAySee_calculate_dimensions(self, image, output_height):
batch_size, original_height, original_width, channels = image.shape
- # Calculate the new width based on the aspect ratio
+ # Calculate the new width based on the aspect ratio
ratio = original_width / original_height
output_width = int(output_height * ratio)
diff --git a/py/EmAySee_CheckboxFloatNode.py b/py/EmAySee_CheckboxFloatNode.py
index 6c977be..b521530 100755
--- a/py/EmAySee_CheckboxFloatNode.py
+++ b/py/EmAySee_CheckboxFloatNode.py
@@ -17,10 +17,10 @@ class EmAySee_CheckboxFloatNode:
FUNCTION = "checkbox_to_float"
def checkbox_to_float(self, checkbox_input):
- if checkbox_input: # If checkbox_input is True (checked)
+ if checkbox_input: # If checkbox_input is True (checked)
return (1.0,)
- else: # If checkbox_input is False (unchecked)
- return (0.0,) # Or you could return (0.0,) if you prefer 0 when unchecked
+ else: # If checkbox_input is False (unchecked)
+ return (0.0,) # Or you could return (0.0,) if you prefer 0 when unchecked
NODE_CLASS_MAPPINGS = {
"EmAySee_CheckboxFloatNode": EmAySee_CheckboxFloatNode
diff --git a/py/EmAySee_ConditionalResize.py b/py/EmAySee_ConditionalResize.py
index 3d89f9c..f066a17 100755
--- a/py/EmAySee_ConditionalResize.py
+++ b/py/EmAySee_ConditionalResize.py
@@ -35,14 +35,14 @@ class EmAySee_ConditionalResize:
if method == "lanczos":
s = samples.clone()
- # Lanczos kernel implementation usually requires custom handling or specific libraries in standard Torch.
- # Using bicubic as a fallback for standard torch interpolate if lanczos isn't explicitly defined in context,
- # but standard ComfyUI `common_upscale` handles this.
- # To keep this dependency-free and simple torch:
- # We will use bicubic for lanczos slot here or implement standard resize.
- # Actually, simpler to map standard torch modes.
- # Let's use bicubic for 'lanczos' to avoid external dependencies,
- # or simple bicubic with antialias=True if supported.
+ # Lanczos kernel implementation usually requires custom handling or specific libraries in standard Torch.
+ # Using bicubic as a fallback for standard torch interpolate if lanczos isn't explicitly defined in context,
+ # but standard ComfyUI `common_upscale` handles this.
+ # To keep this dependency-free and simple torch:
+ # We will use bicubic for lanczos slot here or implement standard resize.
+ # Actually, simpler to map standard torch modes.
+ # Let's use bicubic for 'lanczos' to avoid external dependencies,
+ # or simple bicubic with antialias=True if supported.
mode = "bicubic"
else:
mode = method
diff --git a/py/EmAySee_ContextManager.py b/py/EmAySee_ContextManager.py
index 753fb47..12cf938 100755
--- a/py/EmAySee_ContextManager.py
+++ b/py/EmAySee_ContextManager.py
@@ -21,13 +21,13 @@ class EmAySee_ContextManager:
CATEGORY = "EmAySee/Automation"
def manage_context(self, file_path, history_count, mode, force_reset, new_generation=""):
- # Force absolute path to avoid "Ghost" file issues in ComfyUI temp folders
+ # Force absolute path to avoid "Ghost" file issues in ComfyUI temp folders
abs_path = os.path.abspath(file_path)
if force_reset and os.path.exists(abs_path):
os.remove(abs_path)
- # Helper to read blocks
+ # Helper to read blocks
def get_blocks(path):
if not os.path.exists(path) or os.path.getsize(path) == 0:
return []
@@ -39,11 +39,11 @@ class EmAySee_ContextManager:
blocks = get_blocks(abs_path)
if mode == "Read_Context":
- # SEEDING: If file is empty, use the input wire as the current state
+ # SEEDING: If file is empty, use the input wire as the current state
if not blocks:
if new_generation and new_generation.strip():
seed = new_generation.strip()
- # Write immediately so the Append node sees it later in the same cycle
+ # Write immediately so the Append node sees it later in the same cycle
with open(abs_path, 'w', encoding='utf-8') as f:
f.write(seed + "\n\n")
f.flush()
@@ -56,17 +56,17 @@ class EmAySee_ContextManager:
if mode == "Append_and_Prune":
if new_generation and new_generation.strip():
- # Avoid doubling up if Read_Context already seeded it
+ # Avoid doubling up if Read_Context already seeded it
if not blocks or blocks[-1] != new_generation.strip():
blocks.append(new_generation.strip())
pruned = blocks[-history_count:]
- # ATOMIC WRITE
+ # ATOMIC WRITE
with open(abs_path, 'w', encoding='utf-8') as f:
f.write("\n\n".join(pruned))
f.flush()
- os.fsync(f.fileno()) # Forces Windows/Linux to write to disk NOW
+ os.fsync(f.fileno()) # Forces Windows/Linux to write to disk NOW
return (new_generation if new_generation else "",)
diff --git a/py/EmAySee_ContextManagerRWNodes_V3.py b/py/EmAySee_ContextManagerRWNodes_V3.py
index 5708ba5..6ce564c 100755
--- a/py/EmAySee_ContextManagerRWNodes_V3.py
+++ b/py/EmAySee_ContextManagerRWNodes_V3.py
@@ -16,7 +16,7 @@ def get_blocks(path):
return []
with open(path, 'r', encoding='utf-8') as f:
data = f.read()
- # V5 RE-DESIGNED REGEX: Looks for PROMPT and the LAST occurrence of STATE in a chunk
+ # V5 RE-DESIGNED REGEX: Looks for PROMPT and the LAST occurrence of STATE in a chunk
found = re.findall(r"(PROMPT:.*?STATE:.*?)(?=PROMPT:|\Z)", data, re.IGNORECASE | re.DOTALL)
return [b.strip() for b in found if b.strip()]
@@ -70,12 +70,12 @@ class EmAySee_ContextWriter:
text = new_generation.strip()
- # --- V5 REPAIR LOGIC ---
- # 1. Ensure PROMPT header
+ # --- V5 REPAIR LOGIC ---
+ # 1. Ensure PROMPT header
if not re.search(r"^PROMPT:", text, re.IGNORECASE):
text = "PROMPT: " + text
- # 2. Check for STATE header (case insensitive)
+ # 2. Check for STATE header (case insensitive)
if not re.search(r"STATE:", text, re.IGNORECASE):
print("--- [V5 WRITER] Repairing Missing STATE ---")
text = text + "\n\nSTATE: [Repaired Placeholder]"
@@ -83,10 +83,10 @@ class EmAySee_ContextWriter:
abs_path = os.path.join(get_comfy_root(), file_name)
blocks = get_blocks(abs_path)
- # Deduplication check
+ # Deduplication check
if not (blocks and blocks[-1][:100] == text[:100]):
blocks.append(text)
- # Keep only the requested history depth
+ # Keep only the requested history depth
atomic_write(abs_path, blocks[-history_count:])
return (text,)
diff --git a/py/EmAySee_ContextManager_V2.py b/py/EmAySee_ContextManager_V2.py
index 15761d3..8edef1d 100755
--- a/py/EmAySee_ContextManager_V2.py
+++ b/py/EmAySee_ContextManager_V2.py
@@ -21,7 +21,7 @@ class EmAySee_ContextManager_V2:
CATEGORY = "EmAySee_Automation"
def manage_context(self, file_path, history_count, mode, force_reset, new_generation=""):
- # ABSOLUTE PATH LOGGING
+ # ABSOLUTE PATH LOGGING
abs_path = os.path.abspath(file_path)
print(f"--- [EmAySee V2] DEBUG: Working Directory is {os.getcwd()} ---")
print(f"--- [EmAySee V2] DEBUG: Looking for file at {abs_path} ---")
@@ -42,7 +42,7 @@ class EmAySee_ContextManager_V2:
with open(path, 'r', encoding='utf-8') as f:
data = f.read()
- # This regex is strict. If your file doesn't have "PROMPT:" it will return nothing.
+ # This regex is strict. If your file doesn't have "PROMPT:" it will return nothing.
found = re.findall(r"(PROMPT:.*?STATE:.*?)(?=PROMPT:|\Z)", data, re.DOTALL)
print(f"--- [EmAySee V2] DEBUG: Found {len(found)} valid PROMPT/STATE blocks. ---")
return [b.strip() for b in found if b.strip()]
diff --git a/py/EmAySee_ContextWriter_V5.py b/py/EmAySee_ContextWriter_V5.py
index ec3061e..39fd8d8 100755
--- a/py/EmAySee_ContextWriter_V5.py
+++ b/py/EmAySee_ContextWriter_V5.py
@@ -16,28 +16,28 @@ class EmAySee_ContextWriter_V5:
CATEGORY = "EmAySee_Automation/Context"
def write_context(self, file_path, text_to_append, max_history):
- # 1. Truncation Guard
+ # 1. Truncation Guard
if "[REASONING TRUNCATED]" in text_to_append:
raise ValueError(f"CRITICAL ERROR: LLM Output was truncated. Workflow halted to prevent history corruption.")
- # 2. Path Validation
+ # 2. Path Validation
if not os.path.exists(os.path.dirname(file_path)):
os.makedirs(os.path.dirname(file_path), mode=0o777, exist_ok=True)
- # 3. Read existing to manage history length
+ # 3. Read existing to manage history length
history = []
if os.path.exists(file_path):
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
history = [block for block in content.split("---") if "STATE:" in block]
- # 4. Append New Entry
+ # 4. Append New Entry
history.append(text_to_append)
- # 5. Keep only the last N entries
+ # 5. Keep only the last N entries
history = history[-max_history:]
- # 6. Rewrite file
+ # 6. Rewrite file
with open(file_path, "w", encoding="utf-8") as f:
f.write("---".join(history))
diff --git a/py/EmAySee_DeepReasoningConnector.py b/py/EmAySee_DeepReasoningConnector.py
index 2fe77ef..83b2fbd 100755
--- a/py/EmAySee_DeepReasoningConnector.py
+++ b/py/EmAySee_DeepReasoningConnector.py
@@ -121,7 +121,7 @@ class EmAySee_DeepReasoningConnector:
if stop_on_error: raise e
return (f"Error: {str(e)}", "", "ERROR")
- # Parsing results
+ # Parsing results
think_pattern = r'<(?:think|thought)>(.*?)(?:think|thought)>'
think_match = re.search(think_pattern, full_content, flags=re.DOTALL | re.IGNORECASE)
diff --git a/py/EmAySee_DynamicStringSelectorNode.py b/py/EmAySee_DynamicStringSelectorNode.py
index 192d808..7c6bde9 100755
--- a/py/EmAySee_DynamicStringSelectorNode.py
+++ b/py/EmAySee_DynamicStringSelectorNode.py
@@ -7,7 +7,7 @@ class EmAySee_DynamicStringSelectorNode:
@classmethod
def INPUT_TYPES(s):
return {"required": {
- "string_input_1": ("STRING", {"default": "Initial Input"}), # Added initial input
+ "string_input_1": ("STRING", {"default": "Initial Input"}), # Added initial input
"select_index": ("INT", {"default": 1, "min": 1, "max": 1, "step": 1, "display": "slider", "label": "Select Input"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}, "optional": {}}
@@ -20,7 +20,7 @@ class EmAySee_DynamicStringSelectorNode:
@classmethod
def IS_CHANGED(s, *args):
- return float("nan") # Never cache, so the node updates every time
+ return float("nan") # Never cache, so the node updates every time
@classmethod
def VALIDATE_INPUTS(s, select_index, seed, **kwargs):
@@ -31,7 +31,7 @@ class EmAySee_DynamicStringSelectorNode:
def EmAySee_function(self, select_index, seed, **kwargs):
if not kwargs:
- return ("",) # No inputs connected
+ return ("",) # No inputs connected
random.seed(seed) #seed the random generator
return (list(kwargs.values())[select_index - 1],)
diff --git a/py/EmAySee_EphemeralPreview.py b/py/EmAySee_EphemeralPreview.py
index 98d0e43..b5f6c12 100755
--- a/py/EmAySee_EphemeralPreview.py
+++ b/py/EmAySee_EphemeralPreview.py
@@ -16,34 +16,34 @@ class EphemeralPreview:
RETURN_TYPES = ()
FUNCTION = "preview_no_cache"
OUTPUT_NODE = True
- CATEGORY = "EmAySee_Tools" # Added per your preference
+ CATEGORY = "EmAySee_Tools" # Added per your preference
def preview_no_cache(self, images):
results = []
- # We use a fixed filename to encourage the browser to reuse the memory slot
- # slightly changing the query param (subfolder) to force a refresh but try to keep it lean.
+ # We use a fixed filename to encourage the browser to reuse the memory slot
+ # slightly changing the query param (subfolder) to force a refresh but try to keep it lean.
for i, image in enumerate(images):
i_np = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i_np, 0, 255).astype(np.uint8))
- # Save to a fixed filename in temp to avoid disk bloat
+ # Save to a fixed filename in temp to avoid disk bloat
filename = f"ephemeral_monitor_{i}.png"
file = os.path.join(self.output_dir, filename)
img.save(file, optimize=True, compress_level=4)
- # We append a random int to the subfolder only to trigger the UI update,
- # but the file on disk remains constant.
+ # We append a random int to the subfolder only to trigger the UI update,
+ # but the file on disk remains constant.
results.append({
"filename": filename,
"subfolder": "",
"type": self.type
})
- # This return structure mimics a PreviewImage node
+ # This return structure mimics a PreviewImage node
return {"ui": {"images": results}}
-# Register the node
+# Register the node
NODE_CLASS_MAPPINGS = {
"EmAySee_EphemeralPreview": EphemeralPreview
}
diff --git a/py/EmAySee_GetModelPath.py b/py/EmAySee_GetModelPath.py
index a61f7b2..96cd458 100755
--- a/py/EmAySee_GetModelPath.py
+++ b/py/EmAySee_GetModelPath.py
@@ -15,8 +15,8 @@ class EmAySee_GetModelPath:
FUNCTION = "EmAySee_get_path"
def EmAySee_get_path(self, model):
- # A model object in ComfyUI typically has the file path in a specific attribute
- # We assume the first path is the one you need.
+ # A model object in ComfyUI typically has the file path in a specific attribute
+ # We assume the first path is the one you need.
model_path = model.model_filenames[0]
return (model_path,)
diff --git a/py/EmAySee_HostPinger.py b/py/EmAySee_HostPinger.py
index 8a320ab..61c350e 100755
--- a/py/EmAySee_HostPinger.py
+++ b/py/EmAySee_HostPinger.py
@@ -11,27 +11,27 @@ class EmAySee_HostPinger:
def INPUT_TYPES(s):
return {
"required": {
- "host": ("STRING", {"default": "google.com"}), # Hostname or IP address to ping
+ "host": ("STRING", {"default": "google.com"}), # Hostname or IP address to ping
}
}
- RETURN_TYPES = ("INT",) # Output is a single integer (1 or 0)
- RETURN_NAMES = ("is_up",) # Name for the output
- CATEGORY = "EmAySee_Network" # Category in the ComfyUI menu
- TITLE = "EmAySee Host Pinger" # Title displayed on the node
+ RETURN_TYPES = ("INT",) # Output is a single integer (1 or 0)
+ RETURN_NAMES = ("is_up",) # Name for the output
+ CATEGORY = "EmAySee_Network" # Category in the ComfyUI menu
+ TITLE = "EmAySee Host Pinger" # Title displayed on the node
- FUNCTION = "EmAySee_check_host" # The method that will be executed
+ FUNCTION = "EmAySee_check_host" # The method that will be executed
def EmAySee_check_host(self, host):
"""
Executes a system ping command to check if the host is reachable.
Returns 1 if successful, 0 otherwise.
"""
- # Determine the correct ping command based on the operating system
+ # Determine the correct ping command based on the operating system
param = '-n' if platform.system().lower() == 'windows' else '-c'
- count = '1' # Number of ping requests
+ count = '1' # Number of ping requests
timeout_param = '-w' if platform.system().lower() == 'windows' else '-W'
- timeout_value = '1000' if platform.system().lower() == 'windows' else '1' # Timeout in ms (Windows) or seconds (Linux/macOS)
+ timeout_value = '1000' if platform.system().lower() == 'windows' else '1' # Timeout in ms (Windows) or seconds (Linux/macOS)
command = [
'ping',
@@ -40,28 +40,28 @@ class EmAySee_HostPinger:
host
]
- is_up = 0 # Default to 0 (down)
+ is_up = 0 # Default to 0 (down)
try:
- # Execute the ping command
- # capture_output=True captures stdout and stderr
- # text=True decodes stdout/stderr as text
- # timeout sets a maximum time to wait for the command to complete
+ # Execute the ping command
+ # capture_output=True captures stdout and stderr
+ # text=True decodes stdout/stderr as text
+ # timeout sets a maximum time to wait for the command to complete
result = subprocess.run(
command,
capture_output=True,
text=True,
- timeout=2, # Set a reasonable timeout for the subprocess itself
- check=False # Do not raise an exception for non-zero exit codes (like host unreachable)
+ timeout=2, # Set a reasonable timeout for the subprocess itself
+ check=False # Do not raise an exception for non-zero exit codes (like host unreachable)
)
- # Check the return code
- # A return code of 0 usually indicates success (host is reachable)
+ # Check the return code
+ # A return code of 0 usually indicates success (host is reachable)
if result.returncode == 0:
is_up = 1
else:
- # You can optionally print stderr for debugging if the ping fails
- # print(f"Ping command failed for {host}: {result.stderr}")
+ # You can optionally print stderr for debugging if the ping fails
+ # print(f"Ping command failed for {host}: {result.stderr}")
is_up = 0
except FileNotFoundError:
@@ -74,14 +74,14 @@ class EmAySee_HostPinger:
print(f"An unexpected error occurred while pinging {host}: {e}")
is_up = 0
- return (is_up,) # Return the result as a tuple
+ return (is_up,) # Return the result as a tuple
-# Mapping of node class name to the class
+# Mapping of node class name to the class
NODE_CLASS_MAPPINGS = {
"EmAySee_HostPinger": EmAySee_HostPinger
}
-# Mapping of node class name to the display name in the UI
+# Mapping of node class name to the display name in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_HostPinger": "EmAySee Host Pinger"
}
diff --git a/py/EmAySee_ImagePassthrough.py b/py/EmAySee_ImagePassthrough.py
index 47df991..4e54b25 100755
--- a/py/EmAySee_ImagePassthrough.py
+++ b/py/EmAySee_ImagePassthrough.py
@@ -8,37 +8,37 @@ class EmAySee_ImagePassthrough:
def INPUT_TYPES(s):
return {
"required": {
- # Input image connection. You can rename this input visually in the UI.
+ # Input image connection. You can rename this input visually in the UI.
"input_image": ("IMAGE",),
}
}
- # The node outputs an image
+ # The node outputs an image
RETURN_TYPES = ("IMAGE",)
- # You can rename the output connection visually in the UI as well.
+ # You can rename the output connection visually in the UI as well.
RETURN_NAMES = ("output_image",)
- # Category for the ComfyUI menu
+ # Category for the ComfyUI menu
CATEGORY = "EmAySee_Utils"
- # Title displayed on the node
+ # Title displayed on the node
TITLE = "EmAySee Image Passthrough"
- # The method that will be executed when the node runs
+ # The method that will be executed when the node runs
FUNCTION = "pass_through"
def pass_through(self, input_image):
"""
Simply returns the input image.
"""
- # The node's logic is just to return the input image.
+ # The node's logic is just to return the input image.
return (input_image,)
-# Mapping of node class name to the class
+# Mapping of node class name to the class
NODE_CLASS_MAPPINGS = {
"EmAySee_ImagePassthrough": EmAySee_ImagePassthrough
}
-# Mapping of node class name to the display name in the UI
+# Mapping of node class name to the display name in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_ImagePassthrough": "EmAySee Image Passthrough"
}
diff --git a/py/EmAySee_ImagePreviewPassthrough.py b/py/EmAySee_ImagePreviewPassthrough.py
index f92d0af..229cc71 100755
--- a/py/EmAySee_ImagePreviewPassthrough.py
+++ b/py/EmAySee_ImagePreviewPassthrough.py
@@ -6,7 +6,7 @@ import time
class EmAySee_ImagePreviewPassthrough:
def __init__(self):
- # Get the temp directory from ComfyUI
+ # Get the temp directory from ComfyUI
self.output_dir = folder_paths.get_temp_directory()
self.type = "temp"
@@ -24,39 +24,39 @@ class EmAySee_ImagePreviewPassthrough:
CATEGORY = "EmAySee/Utils"
def preview_and_passthrough(self, image):
- # 1. --- Prepare images for preview ---
- # This logic is adapted from ComfyUI's PreviewImage node
+ # 1. --- Prepare images for preview ---
+ # This logic is adapted from ComfyUI's PreviewImage node
- # Convert tensor to PIL images
+ # Convert tensor to PIL images
images = []
for i in range(image.shape[0]):
img_tensor = image[i]
img = (img_tensor * 255.0).cpu().numpy().astype(np.uint8)
images.append(Image.fromarray(img))
- # Save images to temp folder and get filenames
+ # Save images to temp folder and get filenames
preview_images = []
for img in images:
- # Generate a unique filename based on current time
+ # Generate a unique filename based on current time
filename = f"emaysee_preview_{time.time_ns()}.png"
filepath = os.path.join(self.output_dir, filename)
- # Save the image
- img.save(filepath, format='PNG', compress_level=4) # Use low compression for speed
+ # Save the image
+ img.save(filepath, format='PNG', compress_level=4) # Use low compression for speed
- # Add to preview list for the UI
+ # Add to preview list for the UI
preview_images.append({
"filename": filename,
- "subfolder": "", # We are using the temp folder directly
+ "subfolder": "", # We are using the temp folder directly
"type": self.type
})
- # 2. --- Return results ---
- # This dictionary structure tells ComfyUI what to display in the UI
- # and what to pass to the output port.
+ # 2. --- Return results ---
+ # This dictionary structure tells ComfyUI what to display in the UI
+ # and what to pass to the output port.
return {
- "ui": { "images": preview_images }, # This triggers the inline preview
- "result": (image,) # This is the passthrough output
+ "ui": { "images": preview_images }, # This triggers the inline preview
+ "result": (image,) # This is the passthrough output
}
NODE_CLASS_MAPPINGS = {
diff --git a/py/EmAySee_IntegerStringSelectorNode.py b/py/EmAySee_IntegerStringSelectorNode.py
index 01a46c5..53acf6f 100755
--- a/py/EmAySee_IntegerStringSelectorNode.py
+++ b/py/EmAySee_IntegerStringSelectorNode.py
@@ -15,7 +15,7 @@ class EmAySee_IntegerStringSelectorNode:
"min": 1,
"max": 4,
"step": 1,
- "display": "slider", # Or "number"
+ "display": "slider", # Or "number"
"label": "Select Input (1-4)"
}),
},
@@ -37,13 +37,13 @@ class EmAySee_IntegerStringSelectorNode:
elif select_index == 4:
return (string_input_4,)
else:
- return ("",) # Should not happen given input constraints, but as a failsafe
+ return ("",) # Should not happen given input constraints, but as a failsafe
-NODE_CLASS_MAPPINGS = { # Moved to after the class definition
+NODE_CLASS_MAPPINGS = { # Moved to after the class definition
"EmAySee_IntegerStringSelectorNode": EmAySee_IntegerStringSelectorNode
}
-NODE_DISPLAY_NAME_MAPPINGS = { # Moved to after the class definition
+NODE_DISPLAY_NAME_MAPPINGS = { # Moved to after the class definition
"EmAySee_IntegerStringSelectorNode": "EmAySee Integer String Selector"
}
\ No newline at end of file
diff --git a/py/EmAySee_IsGreaterThan.py b/py/EmAySee_IsGreaterThan.py
index 10f4d66..7d56207 100755
--- a/py/EmAySee_IsGreaterThan.py
+++ b/py/EmAySee_IsGreaterThan.py
@@ -19,10 +19,10 @@ class EmAySee_GreaterThanNode:
}
}
- RETURN_TYPES = ("INT",) # The node will return an integer (0 or 1)
- RETURN_NAMES = ("RESULT",) # The output port will be named 'RESULT'
- FUNCTION = "compare_floats" # The name of the function that executes the node's logic
- CATEGORY = "Logic" # The category where the node will appear in ComfyUI
+ RETURN_TYPES = ("INT",) # The node will return an integer (0 or 1)
+ RETURN_NAMES = ("RESULT",) # The output port will be named 'RESULT'
+ FUNCTION = "compare_floats" # The name of the function that executes the node's logic
+ CATEGORY = "Logic" # The category where the node will appear in ComfyUI
def compare_floats(self, A, B):
"""
@@ -30,17 +30,17 @@ class EmAySee_GreaterThanNode:
Returns 1 if A is greater than B, otherwise returns 0.
"""
if A > B:
- return (1,) # Return as a tuple, as ComfyUI expects
+ return (1,) # Return as a tuple, as ComfyUI expects
else:
- return (0,) # Return as a tuple
+ return (0,) # Return as a tuple
-# A dictionary of all custom nodes available in this file.
-# The key is the node class name, and the value is the class itself.
+# A dictionary of all custom nodes available in this file.
+# The key is the node class name, and the value is the class itself.
NODE_CLASS_MAPPINGS = {
"EmAySee_GreaterThanNode": EmAySee_GreaterThanNode
}
-# A dictionary that provides more user-friendly names for the nodes in the UI.
+# A dictionary that provides more user-friendly names for the nodes in the UI.
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_GreaterThanNode": "EmAySee Greater Than (Float)"
}
diff --git a/py/EmAySee_LLMOutputCleaner.py b/py/EmAySee_LLMOutputCleaner.py
index b597970..78b8265 100755
--- a/py/EmAySee_LLMOutputCleaner.py
+++ b/py/EmAySee_LLMOutputCleaner.py
@@ -15,15 +15,15 @@ class EmAySee_LLMOutputCleaner:
CATEGORY = "EmAySee/Utils"
def clean_text(self, text):
- # 1. Remove ... blocks (DeepSeek/Reasoning models)
- # flags=re.DOTALL ensures the dot matches newlines inside the think block
+ # 1. Remove ... blocks (DeepSeek/Reasoning models)
+ # flags=re.DOTALL ensures the dot matches newlines inside the think block
cleaned = re.sub(r'.*?', '', text, flags=re.DOTALL)
- # 2. Remove common "Here is the thinking process:" preambles if they exist
- # (Optional, but helps with some chatty models)
- # cleaned = re.sub(r'(?i)here[\'s\s]+the\s+thinking.*?:', '', cleaned)
+ # 2. Remove common "Here is the thinking process:" preambles if they exist
+ # (Optional, but helps with some chatty models)
+ # cleaned = re.sub(r'(?i)here[\'s\s]+the\s+thinking.*?:', '', cleaned)
- # 3. Clean up extra whitespace left behind
+ # 3. Clean up extra whitespace left behind
cleaned = cleaned.strip()
return (cleaned,)
diff --git a/py/EmAySee_LoRAMetadataExtractor.py b/py/EmAySee_LoRAMetadataExtractor.py
index bfa8b42..5fcdefd 100755
--- a/py/EmAySee_LoRAMetadataExtractor.py
+++ b/py/EmAySee_LoRAMetadataExtractor.py
@@ -2,11 +2,11 @@ import json
import os
from safetensors import safe_open
-# Node class mapping and display name (how it appears in ComfyUI)
+# Node class mapping and display name (how it appears in ComfyUI)
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
-class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name
+class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name
"""
A ComfyUI custom node to extract and display training metadata
from a .safetensors LoRA file.
@@ -17,19 +17,19 @@ class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name
def __init__(self):
pass
- # Define the return types of the node's outputs
- # This node will output a single STRING
+ # Define the return types of the node's outputs
+ # This node will output a single STRING
RETURN_TYPES = ("STRING",)
- # Define the names of the node's outputs
+ # Define the names of the node's outputs
RETURN_NAMES = ("metadata_json_string",)
- # Define the category where the node will appear in ComfyUI
+ # Define the category where the node will appear in ComfyUI
FUNCTION = "extract_metadata"
- CATEGORY = "Mac Custom Nodes/LoRA Tools" # You can change this category if you prefer
+ CATEGORY = "Mac Custom Nodes/LoRA Tools" # You can change this category if you prefer
- # Define the input types of the node
- # This node requires a file path to a .safetensors file
+ # Define the input types of the node
+ # This node requires a file path to a .safetensors file
@classmethod
def INPUT_TYPES(s):
return {
@@ -37,10 +37,10 @@ class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name
"lora_filepath": ("STRING", {"multiline": False, "default": ""}),
},
"optional": {
- # Optional input for a file upload, if you prefer to drag-and-drop
- # Note: ComfyUI's file input might not directly give a full path easily
- # Manual string path is often more reliable for existing files.
- "lora_upload": ("LORA", {"forceInput": True}), # This allows drag-and-drop, but you'd still need to get the path
+ # Optional input for a file upload, if you prefer to drag-and-drop
+ # Note: ComfyUI's file input might not directly give a full path easily
+ # Manual string path is often more reliable for existing files.
+ "lora_upload": ("LORA", {"forceInput": True}), # This allows drag-and-drop, but you'd still need to get the path
}
}
@@ -59,7 +59,7 @@ class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name
if not lora_filepath:
return ("Error: LoRA filepath cannot be empty.",)
- # Ensure the file exists
+ # Ensure the file exists
if not os.path.exists(lora_filepath):
return (f"Error: File not found at '{lora_filepath}'. Please check the path.",)
if not os.path.isfile(lora_filepath):
@@ -70,58 +70,58 @@ class EmAySee_LoRAMetadataExtractor: # Prepend EmAySee_ to the class name
metadata = f.metadata()
output_data = {}
- # Attempt to get the full Kohya_ss config if nested under __metadata__ (newer Kohya)
+ # Attempt to get the full Kohya_ss config if nested under __metadata__ (newer Kohya)
if "__metadata__" in metadata:
try:
output_data = json.loads(metadata["__metadata__"])
- # Add a flag to indicate it's the full config
+ # Add a flag to indicate it's the full config
output_data["_metadata_source"] = "Full Kohya_ss Config (__metadata__)"
except json.JSONDecodeError:
output_data["_error"] = "Failed to parse __metadata__ as JSON."
output_data["_raw_metadata"] = metadata["__metadata__"]
output_data["_metadata_source"] = "Raw __metadata__ (Parse Error)"
else:
- # If not, extract individual ss_ prefixed keys and their values
+ # If not, extract individual ss_ prefixed keys and their values
extracted_params = {}
for key, value in metadata.items():
- # First, attempt general type conversion (int, float, bool, None)
+ # First, attempt general type conversion (int, float, bool, None)
if isinstance(value, str):
if value.lower() == "true":
value = True
elif value.lower() == "false":
value = False
- elif value.lower() == "none": # Handle "None" as Python None
+ elif value.lower() == "none": # Handle "None" as Python None
value = None
else:
try:
- if "." in value: # Likely a float
+ if "." in value: # Likely a float
value = float(value)
- else: # Try integer
+ else: # Try integer
value = int(value)
except ValueError:
- pass # Keep as string if not a simple number/boolean
+ pass # Keep as string if not a simple number/boolean
- # Then, specifically parse known nested JSON strings
+ # Then, specifically parse known nested JSON strings
if key in ["ss_tag_frequency", "ss_bucket_info", "ss_datasets"]:
- if isinstance(value, str): # Only try to parse if it's still a string
+ if isinstance(value, str): # Only try to parse if it's still a string
try:
value = json.loads(value)
except json.JSONDecodeError:
- # If parsing fails, keep it as a string but note the error
+ # If parsing fails, keep it as a string but note the error
value = f"ERROR_PARSING_JSON: {value}"
- # Store the processed value
+ # Store the processed value
extracted_params[key] = value
output_data = extracted_params
output_data["_metadata_source"] = "Extracted ss_ Prefixed Keys"
- # Return the data as a pretty-printed JSON string
+ # Return the data as a pretty-printed JSON string
return (json.dumps(output_data, indent=2),)
except Exception as e:
return (f"An unexpected error occurred during metadata extraction: {e}",)
-# Register the node
-NODE_CLASS_MAPPINGS["EmAySee_LoRAMetadataExtractor"] = EmAySee_LoRAMetadataExtractor # Prepend EmAySee_
-NODE_DISPLAY_NAME_MAPPINGS["EmAySee_LoRA Metadata Extractor"] = "EmAySee_LoRA Metadata Extractor" # Prepend EmAySee_
+# Register the node
+NODE_CLASS_MAPPINGS["EmAySee_LoRAMetadataExtractor"] = EmAySee_LoRAMetadataExtractor # Prepend EmAySee_
+NODE_DISPLAY_NAME_MAPPINGS["EmAySee_LoRA Metadata Extractor"] = "EmAySee_LoRA Metadata Extractor" # Prepend EmAySee_
diff --git a/py/EmAySee_LoRAMetadataExtractorTagDrop.py b/py/EmAySee_LoRAMetadataExtractorTagDrop.py
index 2ed83b9..b4bde3e 100755
--- a/py/EmAySee_LoRAMetadataExtractorTagDrop.py
+++ b/py/EmAySee_LoRAMetadataExtractorTagDrop.py
@@ -2,10 +2,10 @@ import json
import os
from safetensors import safe_open
-# Node class mapping and display name (how it appears in ComfyUI)
-# These mappings are defined here for direct node loading.
+# Node class mapping and display name (how it appears in ComfyUI)
+# These mappings are defined here for direct node loading.
-class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class name
+class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class name
"""
A ComfyUI custom node to extract and display training metadata
from a .safetensors LoRA file.
@@ -17,31 +17,31 @@ class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class nam
def __init__(self):
pass
- # Define the return types of the node's outputs
- # This node will output a single STRING
+ # Define the return types of the node's outputs
+ # This node will output a single STRING
RETURN_TYPES = ("STRING",)
- # Define the names of the node's outputs
+ # Define the names of the node's outputs
RETURN_NAMES = ("metadata_json_string",)
- # Define the category where the node will appear in ComfyUI
+ # Define the category where the node will appear in ComfyUI
FUNCTION = "extract_metadata"
- CATEGORY = "EmAySee/LoRA Tools" # Updated category for consistency with your prefix
+ CATEGORY = "EmAySee/LoRA Tools" # Updated category for consistency with your prefix
- # Define the input types of the node
- # This node requires a file path to a .safetensors file
+ # Define the input types of the node
+ # This node requires a file path to a .safetensors file
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"lora_filepath": ("STRING", {"multiline": False, "default": ""}),
- "exclude_tag_frequency": ("BOOLEAN", {"default": False}), # New toggle input
+ "exclude_tag_frequency": ("BOOLEAN", {"default": False}), # New toggle input
},
"optional": {
- # Optional input for a file upload, if you prefer to drag-and-drop
- # Note: ComfyUI's file input might not directly give a full path easily
- # Manual string path is often more reliable for existing files.
- "lora_upload": ("LORA", {"forceInput": True}), # This allows drag-and-drop, but you'd still need to get the path
+ # Optional input for a file upload, if you prefer to drag-and-drop
+ # Note: ComfyUI's file input might not directly give a full path easily
+ # Manual string path is often more reliable for existing files.
+ "lora_upload": ("LORA", {"forceInput": True}), # This allows drag-and-drop, but you'd still need to get the path
}
}
@@ -61,7 +61,7 @@ class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class nam
if not lora_filepath:
return ("Error: LoRA filepath cannot be empty.",)
- # Ensure the file exists
+ # Ensure the file exists
if not os.path.exists(lora_filepath):
return (f"Error: File not found at '{lora_filepath}'. Please check the path.",)
if not os.path.isfile(lora_filepath):
@@ -72,69 +72,69 @@ class EmAySee_LoRAMetadataExtractorTagDrop: # Prepend EmAySee_ to the class nam
metadata = f.metadata()
output_data = {}
- # Attempt to get the full Kohya_ss config if nested under __metadata__ (newer Kohya)
+ # Attempt to get the full Kohya_ss config if nested under __metadata__ (newer Kohya)
if "__metadata__" in metadata:
try:
output_data = json.loads(metadata["__metadata__"])
- # Add a flag to indicate it's the full config
+ # Add a flag to indicate it's the full config
output_data["_metadata_source"] = "Full Kohya_ss Config (__metadata__)"
except json.JSONDecodeError:
output_data["_error"] = "Failed to parse __metadata__ as JSON."
output_data["_raw_metadata"] = metadata["__metadata__"]
output_data["_metadata_source"] = "Raw __metadata__ (Parse Error)"
else:
- # If not, extract individual ss_ prefixed keys and their values
+ # If not, extract individual ss_ prefixed keys and their values
extracted_params = {}
for key, value in metadata.items():
- # First, attempt general type conversion (int, float, bool, None)
+ # First, attempt general type conversion (int, float, bool, None)
if isinstance(value, str):
if value.lower() == "true":
value = True
elif value.lower() == "false":
value = False
- elif value.lower() == "none": # Handle "None" as Python None
+ elif value.lower() == "none": # Handle "None" as Python None
value = None
else:
try:
- if "." in value: # Likely a float
+ if "." in value: # Likely a float
value = float(value)
- else: # Try integer
+ else: # Try integer
value = int(value)
except ValueError:
- pass # Keep as string if not a simple number/boolean
+ pass # Keep as string if not a simple number/boolean
- # Then, specifically parse known nested JSON strings
+ # Then, specifically parse known nested JSON strings
if key in ["ss_tag_frequency", "ss_bucket_info", "ss_datasets"]:
- if isinstance(value, str): # Only try to parse if it's still a string
+ if isinstance(value, str): # Only try to parse if it's still a string
try:
value = json.loads(value)
except json.JSONDecodeError:
- # If parsing fails, keep it as a string but note the error
+ # If parsing fails, keep it as a string but note the error
value = f"ERROR_PARSING_JSON: {value}"
- # Store the processed value
+ # Store the processed value
extracted_params[key] = value
output_data = extracted_params
output_data["_metadata_source"] = "Extracted ss_ Prefixed Keys"
- # Apply the toggle: remove ss_tag_frequency if exclude_tag_frequency is True
+ # Apply the toggle: remove ss_tag_frequency if exclude_tag_frequency is True
if exclude_tag_frequency:
if "ss_tag_frequency" in output_data:
del output_data["ss_tag_frequency"]
- # If the full config was loaded, check for ss_tag_frequency within it
+ # If the full config was loaded, check for ss_tag_frequency within it
if "_metadata_source" in output_data and output_data["_metadata_source"] == "Full Kohya_ss Config (__metadata__)":
if "ss_tag_frequency" in output_data:
del output_data["ss_tag_frequency"]
- # Return the data as a pretty-printed JSON string
+ # Return the data as a pretty-printed JSON string
return (json.dumps(output_data, indent=2),)
except Exception as e:
return (f"An unexpected error occurred during metadata extraction: {e}",)
-# Register the node
+# Register the node
NODE_CLASS_MAPPINGS = {
"EmAySee_LoRAMetadataExtractorTagDrop": EmAySee_LoRAMetadataExtractorTagDrop,
}
diff --git a/py/EmAySee_LoraFilePicker.py b/py/EmAySee_LoraFilePicker.py
index d5afeb9..8ea53e1 100755
--- a/py/EmAySee_LoraFilePicker.py
+++ b/py/EmAySee_LoraFilePicker.py
@@ -1,8 +1,8 @@
import os
-import folder_paths # ComfyUI utility to get model paths
+import folder_paths # ComfyUI utility to get model paths
-# Define the main class for your custom node.
-# The class name will be used in NODE_CLASS_MAPPINGS.
+# Define the main class for your custom node.
+# The class name will be used in NODE_CLASS_MAPPINGS.
class EmAySee_LoraFilePicker:
"""
A ComfyUI custom node to pick LORA files from configured paths.
@@ -10,7 +10,7 @@ class EmAySee_LoraFilePicker:
the selected filename and its full file path.
"""
def __init__(self):
- # Constructor for the node. No special initialization needed for this node.
+ # Constructor for the node. No special initialization needed for this node.
pass
@classmethod
@@ -19,38 +19,38 @@ class EmAySee_LoraFilePicker:
Defines the input types (widgets and connections) for the node.
This method is a class method and must return a dictionary.
"""
- # Get all LORA files from ComfyUI's configured LORA paths.
- # This will return a list of relative filenames (e.g., "my_lora.safetensors",
- # "subdir/another_lora.pt"). The 'return_full_paths=True' argument is removed
- # for compatibility with older ComfyUI versions.
+ # Get all LORA files from ComfyUI's configured LORA paths.
+ # This will return a list of relative filenames (e.g., "my_lora.safetensors",
+ # "subdir/another_lora.pt"). The 'return_full_paths=True' argument is removed
+ # for compatibility with older ComfyUI versions.
lora_files = folder_paths.get_filename_list("loras")
- # Add a "None" option at the beginning of the list.
- # This allows the user to explicitly select no LORA.
+ # Add a "None" option at the beginning of the list.
+ # This allows the user to explicitly select no LORA.
lora_files.insert(0, "None")
return {
"required": {
- # "lora_name" is the name of the input port that will appear on the node.
- # (lora_files,) creates a dropdown widget in the ComfyUI interface,
- # populated with the list of LORA files.
+ # "lora_name" is the name of the input port that will appear on the node.
+ # (lora_files,) creates a dropdown widget in the ComfyUI interface,
+ # populated with the list of LORA files.
"lora_name": (lora_files,),
},
}
- # Define the types of data that this node will output.
- # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL").
- RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings.
+ # Define the types of data that this node will output.
+ # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL").
+ RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings.
- # Define the names for the output ports. These names will appear on the node.
+ # Define the names for the output ports. These names will appear on the node.
RETURN_NAMES = ("filename", "full_filepath",)
- # Specifies the Python function within this class that ComfyUI should call
- # when the node is executed in a workflow.
+ # Specifies the Python function within this class that ComfyUI should call
+ # when the node is executed in a workflow.
FUNCTION = "pick_lora_file"
- # Defines the category under which this node will appear in the ComfyUI "Add Node" menu.
- # Using "EmAySee/" as a prefix helps organize your custom nodes.
+ # Defines the category under which this node will appear in the ComfyUI "Add Node" menu.
+ # Using "EmAySee/" as a prefix helps organize your custom nodes.
CATEGORY = "EmAySee/File Utilities"
def pick_lora_file(self, lora_name):
@@ -59,57 +59,57 @@ class EmAySee_LoraFilePicker:
It takes the selected LORA filename as input and returns the filename
and its corresponding full file path.
"""
- # If the "None" option is selected in the dropdown, return empty strings.
+ # If the "None" option is selected in the dropdown, return empty strings.
if lora_name == "None":
return ("", "")
full_lora_path = None
- # Get all configured base directories for LORAs.
- # folder_paths.get_folder_paths("loras") returns a list of absolute paths
- # to the directories where LORAs are stored.
+ # Get all configured base directories for LORAs.
+ # folder_paths.get_folder_paths("loras") returns a list of absolute paths
+ # to the directories where LORAs are stored.
lora_folders = folder_paths.get_folder_paths("loras")
- # Iterate through each LORA folder to find the selected LORA file.
+ # Iterate through each LORA folder to find the selected LORA file.
for folder in lora_folders:
- # Construct a potential full path by joining the base folder and the selected LORA name.
+ # Construct a potential full path by joining the base folder and the selected LORA name.
potential_path = os.path.join(folder, lora_name)
- # Verify if this constructed path actually exists and points to a file.
+ # Verify if this constructed path actually exists and points to a file.
if os.path.exists(potential_path) and os.path.isfile(potential_path):
full_lora_path = potential_path
break
- # Also check if the lora_name itself contains subdirectories (e.g., "subdir/lora.safetensors")
- # and the folder is a base path.
- # This handles cases where the dropdown might show "subdir/lora.safetensors" directly.
+ # Also check if the lora_name itself contains subdirectories (e.g., "subdir/lora.safetensors")
+ # and the folder is a base path.
+ # This handles cases where the dropdown might show "subdir/lora.safetensors" directly.
if os.path.exists(os.path.join(folder, lora_name)) and os.path.isfile(os.path.join(folder, lora_name)):
full_lora_path = os.path.join(folder, lora_name)
break
- # If after all attempts, the file still cannot be located, print a warning
- # and return empty strings to ensure the workflow doesn't crash.
+ # If after all attempts, the file still cannot be located, print a warning
+ # and return empty strings to ensure the workflow doesn't crash.
if full_lora_path is None:
print(f"Warning: LORA file '{lora_name}' not found in known ComfyUI LORA paths.")
return ("", "")
- # Return the selected filename and its successfully determined full path.
+ # Return the selected filename and its successfully determined full path.
return (lora_name, full_lora_path)
-# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover
-# and register your custom nodes. The keys are internal identifiers, and the
-# values are the Python classes that define your nodes.
+# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover
+# and register your custom nodes. The keys are internal identifiers, and the
+# values are the Python classes that define your nodes.
NODE_CLASS_MAPPINGS = {
"EmAySee_LoraFilePicker": EmAySee_LoraFilePicker,
}
-# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides
-# more user-friendly names for your nodes in the ComfyUI interface.
-# This name will appear in the "Add Node" menu.
+# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides
+# more user-friendly names for your nodes in the ComfyUI interface.
+# This name will appear in the "Add Node" menu.
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_LoraFilePicker": "EmAySee Lora File Picker",
}
-# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS).
-# For this node, it's not strictly necessary, but it's a common practice to include it
-# if you plan to extend the node with a custom UI later.
-# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS
+# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS).
+# For this node, it's not strictly necessary, but it's a common practice to include it
+# if you plan to extend the node with a custom UI later.
+# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS
diff --git a/py/EmAySee_LoraLoaderFromInput.py b/py/EmAySee_LoraLoaderFromInput.py
index 1982a67..d419134 100755
--- a/py/EmAySee_LoraLoaderFromInput.py
+++ b/py/EmAySee_LoraLoaderFromInput.py
@@ -9,7 +9,7 @@ class EmAySee_LoraLoaderFromInput:
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
- # This input will connect to the output of the EmAySee Lora Name Selector
+ # This input will connect to the output of the EmAySee Lora Name Selector
"lora_filename": ("LORA",),
"strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
@@ -22,16 +22,16 @@ class EmAySee_LoraLoaderFromInput:
def EmAySee_load_lora(self, model, clip, lora_filename, strength_model, strength_clip):
- # 1. Find the full path of the Lora file using the filename from input
+ # 1. Find the full path of the Lora file using the filename from input
lora_path = folder_paths.get_full_path("loras", lora_filename)
- # 2. Check if the file exists before proceeding
+ # 2. Check if the file exists before proceeding
if lora_path is None:
- # We print an error but return the original model/clip to prevent the workflow from crashing
+ # We print an error but return the original model/clip to prevent the workflow from crashing
print(f"[EmAySee_LoraLoaderFromInput] Lora file not found: {lora_filename}. Skipping Lora application.")
return (model, clip)
- # 3. Load and apply the Lora using ComfyUI's standard utility function
+ # 3. Load and apply the Lora using ComfyUI's standard utility function
model_lora, clip_lora = comfy.sd.load_lora(model, clip, lora_path, strength_model, strength_clip)
return (model_lora, clip_lora)
diff --git a/py/EmAySee_LoraSelectionNodes.py b/py/EmAySee_LoraSelectionNodes.py
index c8fac48..b967eba 100755
--- a/py/EmAySee_LoraSelectionNodes.py
+++ b/py/EmAySee_LoraSelectionNodes.py
@@ -101,7 +101,7 @@ class EmAySee_LoraStacker20:
"optional": {}
}
- # Create inputs for 20 Loras with individual strength controls
+ # Create inputs for 20 Loras with individual strength controls
for i in range(1, 21):
inputs["optional"][f"lora_{i}"] = ("LORA_OBJECT",)
inputs["required"][f"strength_model_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
@@ -117,13 +117,13 @@ class EmAySee_LoraStacker20:
current_model = model
current_clip = clip
- # Iterate through all 20 possible slots
+ # Iterate through all 20 possible slots
for i in range(1, 21):
lora_obj = kwargs.get(f"lora_{i}", None)
str_model = kwargs.get(f"strength_model_{i}", 1.0)
str_clip = kwargs.get(f"strength_clip_{i}", 1.0)
- # Only apply if a Lora Object is connected to the slot
+ # Only apply if a Lora Object is connected to the slot
if lora_obj is not None:
if str_model == 0 and str_clip == 0:
continue
diff --git a/py/EmAySee_LoraSelector.py b/py/EmAySee_LoraSelector.py
index af038cb..be9d54b 100755
--- a/py/EmAySee_LoraSelector.py
+++ b/py/EmAySee_LoraSelector.py
@@ -7,11 +7,11 @@ class EmAySee_LoraSelector:
"""
@classmethod
def INPUT_TYPES(s):
- # Define the controls as part of the INPUT_TYPES but specify that they should
- # be rendered as internal widgets (input_is_widget=True)
+ # Define the controls as part of the INPUT_TYPES but specify that they should
+ # be rendered as internal widgets (input_is_widget=True)
return {
"required": {
- # This input is used solely for the internal widget rendering.
+ # This input is used solely for the internal widget rendering.
"lora_name": ("LORA", {"default": "NONE", "input_is_widget": True, "widget_name": "lora_name_select"}),
"strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "input_is_widget": True, "widget_name": "strength_model_select"}),
"strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "input_is_widget": True, "widget_name": "strength_clip_select"}),
@@ -19,14 +19,14 @@ class EmAySee_LoraSelector:
"optional": {},
}
- # The output now includes the filename and the two strength values as separate outputs
+ # The output now includes the filename and the two strength values as separate outputs
RETURN_TYPES = ("LORA", "FLOAT", "FLOAT",)
RETURN_NAMES = ("lora_filename", "strength_model", "strength_clip",)
CATEGORY = "EmAySee/Loaders"
FUNCTION = "EmAySee_select_lora"
def EmAySee_select_lora(self, lora_name, strength_model, strength_clip):
- # The node receives values from its internal widgets and passes them out.
+ # The node receives values from its internal widgets and passes them out.
return (lora_name, strength_model, strength_clip,)
NODE_CLASS_MAPPINGS = {
diff --git a/py/EmAySee_Lora_Path-Name-Selector.py b/py/EmAySee_Lora_Path-Name-Selector.py
index 9122155..407ef67 100755
--- a/py/EmAySee_Lora_Path-Name-Selector.py
+++ b/py/EmAySee_Lora_Path-Name-Selector.py
@@ -1,8 +1,8 @@
import os
-import folder_paths # ComfyUI utility to get model paths
+import folder_paths # ComfyUI utility to get model paths
-# Define the main class for your custom node.
-# The class name will be used in NODE_CLASS_MAPPINGS.
+# Define the main class for your custom node.
+# The class name will be used in NODE_CLASS_MAPPINGS.
class EmAySee_LoraFilePicker:
"""
A ComfyUI custom node to pick LORA files from configured paths.
@@ -10,7 +10,7 @@ class EmAySee_LoraFilePicker:
the selected filename and its full file path.
"""
def __init__(self):
- # Constructor for the node. No special initialization needed for this node.
+ # Constructor for the node. No special initialization needed for this node.
pass
@classmethod
@@ -19,37 +19,37 @@ class EmAySee_LoraFilePicker:
Defines the input types (widgets and connections) for the node.
This method is a class method and must return a dictionary.
"""
- # Get all LORA files from ComfyUI's configured LORA paths.
- # folder_paths.get_filename_list("loras") returns a list of relative filenames
- # (e.g., "my_lora.safetensors", "subdir/another_lora.pt").
+ # Get all LORA files from ComfyUI's configured LORA paths.
+ # folder_paths.get_filename_list("loras") returns a list of relative filenames
+ # (e.g., "my_lora.safetensors", "subdir/another_lora.pt").
lora_files = folder_paths.get_filename_list("loras")
- # Add a "None" option at the beginning of the list.
- # This allows the user to explicitly select no LORA.
+ # Add a "None" option at the beginning of the list.
+ # This allows the user to explicitly select no LORA.
lora_files.insert(0, "None")
return {
"required": {
- # "lora_name" is the name of the input port that will appear on the node.
- # (lora_files,) creates a dropdown widget in the ComfyUI interface,
- # populated with the list of LORA files.
+ # "lora_name" is the name of the input port that will appear on the node.
+ # (lora_files,) creates a dropdown widget in the ComfyUI interface,
+ # populated with the list of LORA files.
"lora_name": (lora_files,),
},
}
- # Define the types of data that this node will output.
- # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL").
- RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings.
+ # Define the types of data that this node will output.
+ # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL").
+ RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings.
- # Define the names for the output ports. These names will appear on the node.
+ # Define the names for the output ports. These names will appear on the node.
RETURN_NAMES = ("filename", "full_filepath",)
- # Specifies the Python function within this class that ComfyUI should call
- # when the node is executed in a workflow.
+ # Specifies the Python function within this class that ComfyUI should call
+ # when the node is executed in a workflow.
FUNCTION = "pick_lora_file"
- # Defines the category under which this node will appear in the ComfyUI "Add Node" menu.
- # Using "EmAySee/" as a prefix helps organize your custom nodes.
+ # Defines the category under which this node will appear in the ComfyUI "Add Node" menu.
+ # Using "EmAySee/" as a prefix helps organize your custom nodes.
CATEGORY = "EmAySee/File Utilities"
def pick_lora_file(self, lora_name):
@@ -58,60 +58,60 @@ class EmAySee_LoraFilePicker:
It takes the selected LORA filename as input and returns the filename
and its corresponding full file path.
"""
- # If the "None" option is selected in the dropdown, return empty strings.
+ # If the "None" option is selected in the dropdown, return empty strings.
if lora_name == "None":
return ("", "")
full_lora_path = None
- # Attempt to find the full path of the selected LORA file.
- # folder_paths.get_filename_list("loras", return_full_paths=True)
- # returns a list of absolute paths for all LORA files that ComfyUI has discovered.
+ # Attempt to find the full path of the selected LORA file.
+ # folder_paths.get_filename_list("loras", return_full_paths=True)
+ # returns a list of absolute paths for all LORA files that ComfyUI has discovered.
for lora_path in folder_paths.get_filename_list("loras", return_full_paths=True):
- # Check if the base name of the current lora_path matches the selected lora_name,
- # or if the full lora_path ends with the selected lora_name (this handles
- # cases where lora_name might include subdirectories, e.g., "my_folder/my_lora.safetensors").
+ # Check if the base name of the current lora_path matches the selected lora_name,
+ # or if the full lora_path ends with the selected lora_name (this handles
+ # cases where lora_name might include subdirectories, e.g., "my_folder/my_lora.safetensors").
if os.path.basename(lora_path) == lora_name or lora_path.endswith(lora_name):
full_lora_path = lora_path
break
- # Fallback mechanism: If the LORA was not found by direct matching (e.g., due to
- # inconsistencies in how names are reported vs. stored, or new files not yet indexed).
+ # Fallback mechanism: If the LORA was not found by direct matching (e.g., due to
+ # inconsistencies in how names are reported vs. stored, or new files not yet indexed).
if full_lora_path is None:
- # Get all configured base directories for LORAs.
+ # Get all configured base directories for LORAs.
lora_folders = folder_paths.get_folder_paths("loras")
for folder in lora_folders:
- # Construct a potential full path by joining the base folder and the selected LORA name.
+ # Construct a potential full path by joining the base folder and the selected LORA name.
potential_path = os.path.join(folder, lora_name)
- # Verify if this constructed path actually exists and points to a file.
+ # Verify if this constructed path actually exists and points to a file.
if os.path.exists(potential_path) and os.path.isfile(potential_path):
full_lora_path = potential_path
break
- # If after all attempts, the file still cannot be located, print a warning
- # and return empty strings to ensure the workflow doesn't crash.
+ # If after all attempts, the file still cannot be located, print a warning
+ # and return empty strings to ensure the workflow doesn't crash.
if full_lora_path is None:
print(f"Warning: LORA file '{lora_name}' not found in known ComfyUI LORA paths.")
return ("", "")
- # Return the selected filename and its successfully determined full path.
+ # Return the selected filename and its successfully determined full path.
return (lora_name, full_lora_path)
-# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover
-# and register your custom nodes. The keys are internal identifiers, and the
-# values are the Python classes that define your nodes.
+# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover
+# and register your custom nodes. The keys are internal identifiers, and the
+# values are the Python classes that define your nodes.
NODE_CLASS_MAPPINGS = {
"EmAySee_LoraFilePicker": EmAySee_LoraFilePicker,
}
-# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides
-# more user-friendly names for your nodes in the ComfyUI interface.
-# This name will appear in the "Add Node" menu.
+# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides
+# more user-friendly names for your nodes in the ComfyUI interface.
+# This name will appear in the "Add Node" menu.
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_LoraFilePicker": "EmAySee Lora File Picker",
}
-# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS).
-# For this node, it's not strictly necessary, but it's a common practice to include it
-# if you plan to extend the node with a custom UI later.
-# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS
\ No newline at end of file
+# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS).
+# For this node, it's not strictly necessary, but it's a common practice to include it
+# if you plan to extend the node with a custom UI later.
+# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS
\ No newline at end of file
diff --git a/py/EmAySee_MetadataFormatter.py b/py/EmAySee_MetadataFormatter.py
index 220db58..fb3462f 100755
--- a/py/EmAySee_MetadataFormatter.py
+++ b/py/EmAySee_MetadataFormatter.py
@@ -1,6 +1,6 @@
import json
-# Node class mapping and display name (how it appears in ComfyUI)
+# Node class mapping and display name (how it appears in ComfyUI)
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
@@ -13,17 +13,17 @@ class EmAySee_MetadataFormatter:
def __init__(self):
pass
- # Define the return types of the node's outputs
+ # Define the return types of the node's outputs
RETURN_TYPES = ("STRING",)
- # Define the names of the node's outputs
+ # Define the names of the node's outputs
RETURN_NAMES = ("formatted_metadata_string",)
- # Define the category where the node will appear in ComfyUI
+ # Define the category where the node will appear in ComfyUI
FUNCTION = "format_metadata"
- CATEGORY = "Mac Custom Nodes/LoRA Tools" # Consistent with your other node
+ CATEGORY = "Mac Custom Nodes/LoRA Tools" # Consistent with your other node
- # Define the input types of the node
+ # Define the input types of the node
@classmethod
def INPUT_TYPES(s):
return {
@@ -38,39 +38,39 @@ class EmAySee_MetadataFormatter:
a readable line-by-line string.
"""
try:
- # Parse the input JSON string
+ # Parse the input JSON string
metadata = json.loads(metadata_json_string)
except json.JSONDecodeError as e:
return (f"Error: Invalid JSON input for metadata: {e}",)
formatted_lines = []
- # Add a header for clarity
+ # Add a header for clarity
formatted_lines.append("--- LoRA Training Parameters ---")
- # Sort keys for consistent output order
+ # Sort keys for consistent output order
sorted_keys = sorted(metadata.keys())
for key in sorted_keys:
value = metadata[key]
- display_key = key.replace("ss_", "") # Remove ss_ prefix for display
+ display_key = key.replace("ss_", "") # Remove ss_ prefix for display
- # Skip internal metadata source flag
+ # Skip internal metadata source flag
if key == "_metadata_source":
continue
- # Handle specific nested JSON fields for better readability
+ # Handle specific nested JSON fields for better readability
if key == "ss_tag_frequency":
formatted_lines.append(f"\n{display_key.replace('_', ' ').title()}:")
- # Parse the nested tag frequency JSON string
- if isinstance(value, dict): # Should already be parsed by Extractor node
+ # Parse the nested tag frequency JSON string
+ if isinstance(value, dict): # Should already be parsed by Extractor node
for sub_key, sub_value in value.items():
formatted_lines.append(f" - {sub_key}: {sub_value}")
else:
formatted_lines.append(f" (Raw data, could not parse: {value})")
elif key == "ss_bucket_info":
formatted_lines.append(f"\n{display_key.replace('_', ' ').title()}:")
- if isinstance(value, dict): # Should already be parsed by Extractor node
+ if isinstance(value, dict): # Should already be parsed by Extractor node
if "buckets" in value and isinstance(value["buckets"], dict):
formatted_lines.append(" Buckets:")
for bucket_id, bucket_data in value["buckets"].items():
@@ -83,11 +83,11 @@ class EmAySee_MetadataFormatter:
formatted_lines.append(f" (Raw data, could not parse: {value})")
elif key == "ss_datasets":
formatted_lines.append(f"\n{display_key.replace('_', ' ').title()}:")
- if isinstance(value, list): # Should already be parsed by Extractor node
+ if isinstance(value, list): # Should already be parsed by Extractor node
for i, dataset_entry in enumerate(value):
formatted_lines.append(f" Dataset Entry {i}:")
for ds_key, ds_val in dataset_entry.items():
- if isinstance(ds_val, dict): # For nested dicts like tag_frequency within datasets
+ if isinstance(ds_val, dict): # For nested dicts like tag_frequency within datasets
formatted_lines.append(f" - {ds_key.replace('_', ' ').title()}:")
for sub_key, sub_val in ds_val.items():
formatted_lines.append(f" - {sub_key}: {sub_val}")
@@ -96,11 +96,11 @@ class EmAySee_MetadataFormatter:
else:
formatted_lines.append(f" (Raw data, could not parse: {value})")
else:
- # Format other parameters
- # Attempt to make values more readable (e.g., float to scientific notation if very small)
+ # Format other parameters
+ # Attempt to make values more readable (e.g., float to scientific notation if very small)
display_value = value
if isinstance(value, float) and value < 0.001 and value != 0.0:
- display_value = f"{value:.1e}" # Scientific notation for small floats
+ display_value = f"{value:.1e}" # Scientific notation for small floats
formatted_lines.append(f"{display_key.replace('_', ' ').title()}: {display_value}")
@@ -108,6 +108,6 @@ class EmAySee_MetadataFormatter:
return ("\n".join(formatted_lines),)
-# Register the node
+# Register the node
NODE_CLASS_MAPPINGS["EmAySee_MetadataFormatter"] = EmAySee_MetadataFormatter
NODE_DISPLAY_NAME_MAPPINGS["EmAySee_LoRA Metadata Formatter"] = "EmAySee_LoRA Metadata Formatter"
diff --git a/py/EmAySee_ProbabilityStringSelectorNode.py b/py/EmAySee_ProbabilityStringSelectorNode.py
index a2c27fb..555dd92 100755
--- a/py/EmAySee_ProbabilityStringSelectorNode.py
+++ b/py/EmAySee_ProbabilityStringSelectorNode.py
@@ -10,10 +10,10 @@ class EmAySee_ProbabilityStringSelectorNode:
"string_input_1": ("STRING", {"default": "Option 1 - Less Likely"}),
"string_input_2": ("STRING", {"default": "Option 2 - More Likely"}),
"probability_option_1": ("FLOAT", {
- "default": 0.1, # Default probability of 10% for option 1
- "min": 0.0, # Minimum probability is 0%
- "max": 1.0, # Maximum probability is 100%
- "step": 0.01, # Probability step of 1%
+ "default": 0.1, # Default probability of 10% for option 1
+ "min": 0.0, # Minimum probability is 0%
+ "max": 1.0, # Maximum probability is 100%
+ "step": 0.01, # Probability step of 1%
"label": "Probability of Option 1 (0.0-1.0)"
}),
},
@@ -26,13 +26,13 @@ class EmAySee_ProbabilityStringSelectorNode:
FUNCTION = "EmAySee_function"
def EmAySee_function(self, string_input_1, string_input_2, probability_option_1):
- # Generate a random float between 0.0 and 1.0
+ # Generate a random float between 0.0 and 1.0
random_value = random.random()
if random_value < probability_option_1:
- return (string_input_1,) # Select string_input_1 if random_value is less than the probability
+ return (string_input_1,) # Select string_input_1 if random_value is less than the probability
else:
- return (string_input_2,) # Otherwise, select string_input_2
+ return (string_input_2,) # Otherwise, select string_input_2
NODE_CLASS_MAPPINGS = {
"EmAySee_ProbabilityStringSelectorNode": EmAySee_ProbabilityStringSelectorNode
diff --git a/py/EmAySee_QwenPixelAligner.py b/py/EmAySee_QwenPixelAligner.py
index 2809c1f..f7b7756 100755
--- a/py/EmAySee_QwenPixelAligner.py
+++ b/py/EmAySee_QwenPixelAligner.py
@@ -4,7 +4,7 @@ import torch.nn.functional as F
class EmAySee_QwenPixelAligner:
@classmethod
def INPUT_TYPES(s):
- # Generate the list of resolutions from 784 to 2240 in steps of 112
+ # Generate the list of resolutions from 784 to 2240 in steps of 112
resolutions = [str(x) for x in range(784, 2241, 112)]
return {
@@ -25,33 +25,33 @@ class EmAySee_QwenPixelAligner:
target_edge = int(longest_edge)
B, H, W, C = image.shape
- # Calculate aspect ratio and new dimensions
+ # Calculate aspect ratio and new dimensions
current_longest = max(H, W)
- # Scale factor to match the longest edge target
+ # Scale factor to match the longest edge target
scale_factor = target_edge / current_longest
target_h = H * scale_factor
target_w = W * scale_factor
- # Snap dimensions to the nearest alignment multiple
+ # Snap dimensions to the nearest alignment multiple
new_height = int(round(target_h / align_val) * align_val)
new_width = int(round(target_w / align_val) * align_val)
- # Ensure dimensions are at least one alignment block
+ # Ensure dimensions are at least one alignment block
new_height = max(align_val, new_height)
new_width = max(align_val, new_width)
- # Permute image to [B, C, H, W] for interpolation
+ # Permute image to [B, C, H, W] for interpolation
img_permuted = image.permute(0, 3, 1, 2)
- # Resize using bicubic interpolation
+ # Resize using bicubic interpolation
resized_img = F.interpolate(img_permuted, size=(new_height, new_width), mode="bicubic", align_corners=False)
- # Permute back to [B, H, W, C]
+ # Permute back to [B, H, W, C]
final_image = resized_img.permute(0, 2, 3, 1)
- # Create resolution string
+ # Create resolution string
res_text = f"{new_width}x{new_height}"
return (final_image, new_width, new_height, res_text)
diff --git a/py/EmAySee_RandomIntFromList.py b/py/EmAySee_RandomIntFromList.py
index 47a0a8b..0f46874 100755
--- a/py/EmAySee_RandomIntFromList.py
+++ b/py/EmAySee_RandomIntFromList.py
@@ -1,5 +1,5 @@
import random
-import time # Import the time module
+import time # Import the time module
class EmAySee_RandomIntFromList:
"""
@@ -21,10 +21,10 @@ class EmAySee_RandomIntFromList:
}),
"seed": ("INT", {
"default": 0,
- "min": 0, # Minimum seed value
- "step": 1, # Increment/decrement step
- "display": "number" # Optional, for better UI
- # Can add "forceInput": True to *require* a connection.
+ "min": 0, # Minimum seed value
+ "step": 1, # Increment/decrement step
+ "display": "number" # Optional, for better UI
+ # Can add "forceInput": True to *require* a connection.
}),
},
"optional": {
@@ -50,12 +50,12 @@ class EmAySee_RandomIntFromList:
if not numbers:
return (0,)
- # Seed handling:
+ # Seed handling:
if use_system_time:
- # Use system time * number of milliseconds for a more unique seed each run
- seed = int(time.time() * 1000) # Use milliseconds for more variation
+ # Use system time * number of milliseconds for a more unique seed each run
+ seed = int(time.time() * 1000) # Use milliseconds for more variation
- random.seed(seed) # Set the seed *before* calling random.choice
+ random.seed(seed) # Set the seed *before* calling random.choice
random_int = random.choice(numbers)
return (random_int,)
diff --git a/py/EmAySee_RandomIntFromList2.py b/py/EmAySee_RandomIntFromList2.py
index 20f3564..673ea09 100755
--- a/py/EmAySee_RandomIntFromList2.py
+++ b/py/EmAySee_RandomIntFromList2.py
@@ -1,5 +1,5 @@
import random
-import time # Import the time module
+import time # Import the time module
class EmAySee_RandomIntFromList:
"""
@@ -21,10 +21,10 @@ class EmAySee_RandomIntFromList:
}),
"seed": ("INT", {
"default": 0,
- "min": 0, # Minimum seed value
- "step": 1, # Increment/decrement step
- "display": "number" # Optional, for better UI
- # Can add "forceInput": True to *require* a connection.
+ "min": 0, # Minimum seed value
+ "step": 1, # Increment/decrement step
+ "display": "number" # Optional, for better UI
+ # Can add "forceInput": True to *require* a connection.
}),
},
"optional": {
@@ -50,12 +50,12 @@ class EmAySee_RandomIntFromList:
if not numbers:
return (0,)
- # Seed handling:
+ # Seed handling:
if use_system_time:
- # Use system time * number of milliseconds for a more unique seed each run
- seed = int(time.time() * 1000) # Use milliseconds for more variation
+ # Use system time * number of milliseconds for a more unique seed each run
+ seed = int(time.time() * 1000) # Use milliseconds for more variation
- random.seed(seed) # Set the seed *before* calling random.choice
+ random.seed(seed) # Set the seed *before* calling random.choice
random_int = random.choice(numbers)
return (random_int,)
diff --git a/py/EmAySee_RandomIntegerFromListNode.py b/py/EmAySee_RandomIntegerFromListNode.py
index a1bf3c8..71b0f44 100755
--- a/py/EmAySee_RandomIntegerFromListNode.py
+++ b/py/EmAySee_RandomIntegerFromListNode.py
@@ -32,7 +32,7 @@ class EmAySee_RandomIntegerFromListNode:
return (selected_integer,)
else:
print("EmAySee Random Integer From List: No valid integers found in input string. Returning 0.")
- return (0,) # Or you could return (None,) if you prefer None when no valid integers
+ return (0,) # Or you could return (None,) if you prefer None when no valid integers
NODE_CLASS_MAPPINGS = {
"EmAySee_RandomIntegerFromListNode": EmAySee_RandomIntegerFromListNode
diff --git a/py/EmAySee_RandomIntegerFromTogglesNode_PremadeLabels.py b/py/EmAySee_RandomIntegerFromTogglesNode_PremadeLabels.py
index e33b7b9..1a4465b 100755
--- a/py/EmAySee_RandomIntegerFromTogglesNode_PremadeLabels.py
+++ b/py/EmAySee_RandomIntegerFromTogglesNode_PremadeLabels.py
@@ -8,7 +8,7 @@ class EmAySee_RandomIntegerFromTogglesNode_PremadeLabels:
def INPUT_TYPES(s):
return {"required": {
"node_description": ("STRING", {"multiline": True, "default": "Select options below to choose from randomly."}),
- "option_1": ("TOGGLE", {"default": False, "label": "Option 1: Label for Option 1"}), # Example labels
+ "option_1": ("TOGGLE", {"default": False, "label": "Option 1: Label for Option 1"}), # Example labels
"option_2": ("BOOLEAN", {"default": False, "label": "Option 2: Label for Option 2"}),
"option_3": ("BOOLEAN", {"default": False, "label": "Option 3: Label for Option 3"}),
"option_4": ("BOOLEAN", {"default": False, "label": "Option 4: Label for Option 4"}),
@@ -36,10 +36,10 @@ class EmAySee_RandomIntegerFromTogglesNode_PremadeLabels:
FUNCTION = "get_random_integer_from_toggles"
- def get_random_integer_from_toggles(self, node_description, **kwargs): # Only node_description and kwargs now
+ def get_random_integer_from_toggles(self, node_description, **kwargs): # Only node_description and kwargs now
selected_integers = []
for i in range(1, 21):
- toggle_name = f"option_{i}" # Toggle input names are now option_1, option_2,...
+ toggle_name = f"option_{i}" # Toggle input names are now option_1, option_2,...
if kwargs.get(toggle_name, False):
selected_integers.append(i)
diff --git a/py/EmAySee_RandomLoraSelector.py b/py/EmAySee_RandomLoraSelector.py
index dd1e940..1003d7f 100755
--- a/py/EmAySee_RandomLoraSelector.py
+++ b/py/EmAySee_RandomLoraSelector.py
@@ -18,7 +18,7 @@ class EmAySee_RandomLoraLoader:
}
}
- # Dynamically create 10 LoRA slots with activation toggles
+ # Dynamically create 10 LoRA slots with activation toggles
for i in range(1, 11):
inputs["required"][f"lora_name_{i}"] = (lora_list,)
inputs["required"][f"lora_active_{i}"] = ("BOOLEAN", {"default": True})
@@ -32,7 +32,7 @@ class EmAySee_RandomLoraLoader:
def load_random_lora(self, model, clip, strength_model, strength_clip, seed, **kwargs):
lora_options = []
- # Collect all active LoRAs from the inputs
+ # Collect all active LoRAs from the inputs
for i in range(1, 11):
lora_name = kwargs.get(f"lora_name_{i}")
is_active = kwargs.get(f"lora_active_{i}")
@@ -42,17 +42,17 @@ class EmAySee_RandomLoraLoader:
selected_lora_name = "None"
selected_lora_index = 0
- # If no valid loras are active or strength is 0, pass through the original model/clip
+ # If no valid loras are active or strength is 0, pass through the original model/clip
if not lora_options or (strength_model == 0 and strength_clip == 0):
return (model, clip, selected_lora_name, selected_lora_index)
- # Use the provided seed for reproducibility
+ # Use the provided seed for reproducibility
random.seed(seed)
selected_lora = random.choice(lora_options)
selected_lora_name = selected_lora["name"]
selected_lora_index = selected_lora["index"]
- # --- LoRA Loading Logic (adapted from the default LoraLoader) ---
+ # --- LoRA Loading Logic (adapted from the default LoraLoader) ---
lora_path = folder_paths.get_full_path("loras", selected_lora_name)
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora, strength_model, strength_clip)
diff --git a/py/EmAySee_RandomStringSelectorNode.py b/py/EmAySee_RandomStringSelectorNode.py
index 4a6aabb..43a52de 100755
--- a/py/EmAySee_RandomStringSelectorNode.py
+++ b/py/EmAySee_RandomStringSelectorNode.py
@@ -1,7 +1,7 @@
import comfy.sd
import torch
import numpy as np
-import random # Import the random module
+import random # Import the random module
class EmAySee_RandomStringSelectorNode:
@classmethod
@@ -14,18 +14,18 @@ class EmAySee_RandomStringSelectorNode:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("RANDOM_SELECTED_STRING",)
- CATEGORY = "EmAySee_Utils" # Or a category you prefer
+ CATEGORY = "EmAySee_Utils" # Or a category you prefer
FUNCTION = "EmAySee_function"
def EmAySee_function(self, string_input_1, string_input_2):
- # Generate a random number (0 or 1)
+ # Generate a random number (0 or 1)
random_choice = random.randint(0, 1)
if random_choice == 0:
- return (string_input_1,) # Return the first input if random_choice is 0
+ return (string_input_1,) # Return the first input if random_choice is 0
else:
- return (string_input_2,) # Return the second input if random_choice is 1
+ return (string_input_2,) # Return the second input if random_choice is 1
NODE_CLASS_MAPPINGS = {
"EmAySee_RandomStringSelectorNode": EmAySee_RandomStringSelectorNode
diff --git a/py/EmAySee_RandomStringSelectorNode2.py b/py/EmAySee_RandomStringSelectorNode2.py
index 6abccbe..660c6ea 100755
--- a/py/EmAySee_RandomStringSelectorNode2.py
+++ b/py/EmAySee_RandomStringSelectorNode2.py
@@ -1,7 +1,7 @@
import comfy.sd
import torch
import numpy as np
-import random # Import the random module
+import random # Import the random module
class EmAySee_RandomStringSelectorNode:
@classmethod
@@ -14,18 +14,18 @@ class EmAySee_RandomStringSelectorNode:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("RANDOM_SELECTED_STRING",)
- CATEGORY = "EmAySee_Utils" # Or a category you prefer
+ CATEGORY = "EmAySee_Utils" # Or a category you prefer
FUNCTION = "EmAySee_function"
def EmAySee_function(self, string_input_1, string_input_2):
- # Generate a random number (0 or 1)
+ # Generate a random number (0 or 1)
random_choice = random.randint(0, 1)
if random_choice == 0:
- return (string_input_1,) # Return the first input if random_choice is 0
+ return (string_input_1,) # Return the first input if random_choice is 0
else:
- return (string_input_2,) # Return the second input if random_choice is 1
+ return (string_input_2,) # Return the second input if random_choice is 1
NODE_CLASS_MAPPINGS = {
"EmAySee_RandomStringSelectorNode": EmAySee_RandomStringSelectorNode
diff --git a/py/EmAySee_RandomStringSelectorNodeFourChoice.py b/py/EmAySee_RandomStringSelectorNodeFourChoice.py
index 80bdf95..360edb1 100755
--- a/py/EmAySee_RandomStringSelectorNodeFourChoice.py
+++ b/py/EmAySee_RandomStringSelectorNodeFourChoice.py
@@ -1,7 +1,7 @@
import comfy.sd
import torch
import numpy as np
-import random # Import the random module
+import random # Import the random module
class EmAySee_RandomStringSelectorNodeFourChoice:
@classmethod
@@ -16,22 +16,22 @@ class EmAySee_RandomStringSelectorNodeFourChoice:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("RANDOM_SELECTED_STRING",)
- CATEGORY = "EmAySee_Utils" # Or a category you prefer
+ CATEGORY = "EmAySee_Utils" # Or a category you prefer
FUNCTION = "EmAySee_function"
def EmAySee_function(self, string_input_1, string_input_2, string_input_3, string_input_4):
- # Generate a random number (0 , 1 , 2 , 3)
+ # Generate a random number (0 , 1 , 2 , 3)
random_choice = random.randint(0, 3)
print(random_choice)
if random_choice == 0:
- return (string_input_1,) # Return the first input if random_choice is 0
+ return (string_input_1,) # Return the first input if random_choice is 0
if random_choice == 1:
- return (string_input_2,) # Return the first input if random_choice is 1
+ return (string_input_2,) # Return the first input if random_choice is 1
if random_choice == 2:
- return (string_input_3,) # Return the first input if random_choice is 2
+ return (string_input_3,) # Return the first input if random_choice is 2
else:
- return (string_input_4,) # Return the second input if random_choice is 3
+ return (string_input_4,) # Return the second input if random_choice is 3
NODE_CLASS_MAPPINGS = {
"EmAySee_RandomStringSelectorNodeFourChoice": EmAySee_RandomStringSelectorNodeFourChoice
diff --git a/py/EmAySee_RandomStringSelectorNodeThreeChoice.py b/py/EmAySee_RandomStringSelectorNodeThreeChoice.py
index ef845fa..fa1387a 100755
--- a/py/EmAySee_RandomStringSelectorNodeThreeChoice.py
+++ b/py/EmAySee_RandomStringSelectorNodeThreeChoice.py
@@ -1,7 +1,7 @@
import comfy.sd
import torch
import numpy as np
-import random # Import the random module
+import random # Import the random module
class EmAySee_RandomStringSelectorNodeThreeChoice:
@classmethod
@@ -15,20 +15,20 @@ class EmAySee_RandomStringSelectorNodeThreeChoice:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("RANDOM_SELECTED_STRING",)
- CATEGORY = "EmAySee_Utils" # Or a category you prefer
+ CATEGORY = "EmAySee_Utils" # Or a category you prefer
FUNCTION = "EmAySee_function"
def EmAySee_function(self, string_input_1, string_input_2, string_input_3):
- # Generate a random number (0 , 1 , 2 )
+ # Generate a random number (0 , 1 , 2 )
random_choice = random.randint(0, 2)
if random_choice == 0:
- return (string_input_1,) # Return the first input if random_choice is 0
+ return (string_input_1,) # Return the first input if random_choice is 0
if random_choice == 1:
- return (string_input_2,) # Return the first input if random_choice is 1
+ return (string_input_2,) # Return the first input if random_choice is 1
else:
- return (string_input_3,) # Return the first input if random_choice is 2
+ return (string_input_3,) # Return the first input if random_choice is 2
NODE_CLASS_MAPPINGS = {
diff --git a/py/EmAySee_RemoveDuplicatesFromString.py b/py/EmAySee_RemoveDuplicatesFromString.py
index d448004..bee9e31 100755
--- a/py/EmAySee_RemoveDuplicatesFromString.py
+++ b/py/EmAySee_RemoveDuplicatesFromString.py
@@ -7,50 +7,50 @@ class EmAySee_RemoveDuplicatesFromString:
def INPUT_TYPES(s):
return {
"required": {
- "input_string": ("STRING", {"multiline": True, "default": "apple, banana, apple, orange, banana, grape"}), # Comma-separated list of words
+ "input_string": ("STRING", {"multiline": True, "default": "apple, banana, apple, orange, banana, grape"}), # Comma-separated list of words
}
}
- RETURN_TYPES = ("STRING",) # Output is a single string
- RETURN_NAMES = ("unique_list_string",) # Name for the output
- CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu
- TITLE = "EmAySee Remove Duplicates From String" # Title displayed on the node
+ RETURN_TYPES = ("STRING",) # Output is a single string
+ RETURN_NAMES = ("unique_list_string",) # Name for the output
+ CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu
+ TITLE = "EmAySee Remove Duplicates From String" # Title displayed on the node
- FUNCTION = "EmAySee_process_string" # The method that will be executed
+ FUNCTION = "EmAySee_process_string" # The method that will be executed
def EmAySee_process_string(self, input_string):
"""
Splits the input string, removes duplicates, and rejoins.
"""
if not input_string:
- return ("",) # Return an empty string if input is empty
+ return ("",) # Return an empty string if input is empty
- # 1. Split the string by comma
- # strip() removes leading/trailing whitespace from each word
+ # 1. Split the string by comma
+ # strip() removes leading/trailing whitespace from each word
words = [word.strip() for word in input_string.split(',')]
- # 2. Use a set to automatically handle uniqueness
- # Sets do not preserve order, but for a simple list of words,
- # this is often acceptable. If order is crucial, more complex
- # logic would be needed (e.g., iterating and adding to a new list
- # only if not already present).
+ # 2. Use a set to automatically handle uniqueness
+ # Sets do not preserve order, but for a simple list of words,
+ # this is often acceptable. If order is crucial, more complex
+ # logic would be needed (e.g., iterating and adding to a new list
+ # only if not already present).
unique_words_set = set(words)
- # 3. Convert the set back to a list (optional, but useful for joining)
- # Sorting here makes the output consistent, otherwise order is arbitrary.
+ # 3. Convert the set back to a list (optional, but useful for joining)
+ # Sorting here makes the output consistent, otherwise order is arbitrary.
unique_words_list = sorted(list(unique_words_set))
- # 4. Join the unique words back into a comma-separated string
+ # 4. Join the unique words back into a comma-separated string
output_string = ", ".join(unique_words_list)
- return (output_string,) # Return the result as a tuple
+ return (output_string,) # Return the result as a tuple
-# Mapping of node class name to the class
+# Mapping of node class name to the class
NODE_CLASS_MAPPINGS = {
"EmAySee_RemoveDuplicatesFromString": EmAySee_RemoveDuplicatesFromString
}
-# Mapping of node class name to the display name in the UI
+# Mapping of node class name to the display name in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_RemoveDuplicatesFromString": "EmAySee Remove Duplicates From String"
}
\ No newline at end of file
diff --git a/py/EmAySee_RemoveDuplicatesFromString_V2.py b/py/EmAySee_RemoveDuplicatesFromString_V2.py
index 19e71dc..a4a31ba 100755
--- a/py/EmAySee_RemoveDuplicatesFromString_V2.py
+++ b/py/EmAySee_RemoveDuplicatesFromString_V2.py
@@ -8,17 +8,17 @@ class EmAySee_RemoveDuplicatesFromStringV2:
def INPUT_TYPES(s):
return {
"required": {
- "input_string": ("STRING", {"multiline": True, "default": "apple, banana, apple, orange, banana, grape"}), # Comma-separated list of words
- "sort_output": ("BOOLEAN", {"default": True}), # New: Toggle to sort the unique words alphabetically
+ "input_string": ("STRING", {"multiline": True, "default": "apple, banana, apple, orange, banana, grape"}), # Comma-separated list of words
+ "sort_output": ("BOOLEAN", {"default": True}), # New: Toggle to sort the unique words alphabetically
}
}
- RETURN_TYPES = ("STRING",) # Output is a single string
- RETURN_NAMES = ("unique_list_string",) # Name for the output
- CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu
- TITLE = "EmAySee Remove Duplicates From String" # Title displayed on the node
+ RETURN_TYPES = ("STRING",) # Output is a single string
+ RETURN_NAMES = ("unique_list_string",) # Name for the output
+ CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu
+ TITLE = "EmAySee Remove Duplicates From String" # Title displayed on the node
- FUNCTION = "EmAySee_process_string" # The method that will be executed
+ FUNCTION = "EmAySee_process_string" # The method that will be executed
def EmAySee_process_string(self, input_string, sort_output):
"""
@@ -26,31 +26,31 @@ class EmAySee_RemoveDuplicatesFromStringV2:
Can optionally sort the unique words.
"""
if not input_string:
- return ("",) # Return an empty string if input is empty
+ return ("",) # Return an empty string if input is empty
- # 1. Split the string by comma and strip whitespace from each word
+ # 1. Split the string by comma and strip whitespace from each word
words = [word.strip() for word in input_string.split(',')]
- # 2. Remove duplicates while preserving original order (for Python 3.7+ dicts are insertion-ordered)
- # This method uses dict.fromkeys() to create a dict where keys are unique words
- # and then converts the keys back to a list, preserving their first-seen order.
+ # 2. Remove duplicates while preserving original order (for Python 3.7+ dicts are insertion-ordered)
+ # This method uses dict.fromkeys() to create a dict where keys are unique words
+ # and then converts the keys back to a list, preserving their first-seen order.
unique_words_list = list(dict.fromkeys(words))
- # 3. If sorting is requested, sort the list alphabetically
+ # 3. If sorting is requested, sort the list alphabetically
if sort_output:
- unique_words_list.sort() # Sorts in-place alphabetically
+ unique_words_list.sort() # Sorts in-place alphabetically
- # 4. Join the unique words back into a comma-separated string
+ # 4. Join the unique words back into a comma-separated string
output_string = ", ".join(unique_words_list)
- return (output_string,) # Return the result as a tuple
+ return (output_string,) # Return the result as a tuple
-# Mapping of node class name to the class
+# Mapping of node class name to the class
NODE_CLASS_MAPPINGS = {
"EmAySee_RemoveDuplicatesFromStringV2": EmAySee_RemoveDuplicatesFromStringV2
}
-# Mapping of node class name to the display name in the UI
+# Mapping of node class name to the display name in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_RemoveDuplicatesFromStringV2": "EmAySee Remove Duplicates From StringV2"
}
diff --git a/py/EmAySee_RemoveWordsFromText.py b/py/EmAySee_RemoveWordsFromText.py
index 0e33f53..e842305 100755
--- a/py/EmAySee_RemoveWordsFromText.py
+++ b/py/EmAySee_RemoveWordsFromText.py
@@ -8,47 +8,47 @@ class EmAySee_RemoveWordsFromText:
def INPUT_TYPES(s):
return {
"required": {
- "main_text": ("STRING", {"multiline": True, "default": "A dog, a cat, and a bird."}), # The main text to be parsed
- "words_to_remove": ("STRING", {"multiline": True, "default": "dog, cat, and"}), # Comma-separated list of words to remove
+ "main_text": ("STRING", {"multiline": True, "default": "A dog, a cat, and a bird."}), # The main text to be parsed
+ "words_to_remove": ("STRING", {"multiline": True, "default": "dog, cat, and"}), # Comma-separated list of words to remove
}
}
- RETURN_TYPES = ("STRING",) # Output is a single string
- RETURN_NAMES = ("cleaned_text",) # Name for the output
- CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu
- TITLE = "EmAySee Remove Words From Text" # Title displayed on the node
+ RETURN_TYPES = ("STRING",) # Output is a single string
+ RETURN_NAMES = ("cleaned_text",) # Name for the output
+ CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu
+ TITLE = "EmAySee Remove Words From Text" # Title displayed on the node
- FUNCTION = "EmAySee_process_text" # The method that will be executed
+ FUNCTION = "EmAySee_process_text" # The method that will be executed
def EmAySee_process_text(self, main_text, words_to_remove):
"""
Removes each word from the 'words_to_remove' list from the 'main_text' string.
"""
if not main_text or not words_to_remove:
- return (main_text,) # Return the original text if either input is empty
+ return (main_text,) # Return the original text if either input is empty
- # 1. Split the string of words to remove by comma, and strip whitespace.
- # We escape special characters for use in a regex pattern.
+ # 1. Split the string of words to remove by comma, and strip whitespace.
+ # We escape special characters for use in a regex pattern.
words_list = [re.escape(word.strip()) for word in words_to_remove.split(',')]
- # 2. Join the words with a pipe '|' to create a regex OR pattern.
- # \b ensures we match whole words only, avoiding partial matches.
- # For example, we don't want to remove "cat" from "caterpillar".
+ # 2. Join the words with a pipe '|' to create a regex OR pattern.
+ # \b ensures we match whole words only, avoiding partial matches.
+ # For example, we don't want to remove "cat" from "caterpillar".
pattern = r"\b(" + "|".join(words_list) + r")\b"
- # 3. Use re.sub to find and replace all matching words with an empty string.
- # We also replace multiple spaces that might be left behind.
+ # 3. Use re.sub to find and replace all matching words with an empty string.
+ # We also replace multiple spaces that might be left behind.
cleaned_text = re.sub(pattern, "", main_text, flags=re.IGNORECASE)
cleaned_text = re.sub(r'\s{2,}', ' ', cleaned_text).strip()
- return (cleaned_text,) # Return the cleaned string as a tuple
+ return (cleaned_text,) # Return the cleaned string as a tuple
-# Mapping of node class name to the class
+# Mapping of node class name to the class
NODE_CLASS_MAPPINGS = {
"EmAySee_RemoveWordsFromText": EmAySee_RemoveWordsFromText
}
-# Mapping of node class name to the display name in the UI
+# Mapping of node class name to the display name in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_RemoveWordsFromText": "EmAySee Remove Words From Text"
}
\ No newline at end of file
diff --git a/py/EmAySee_RepaintKSampler.py b/py/EmAySee_RepaintKSampler.py
index 99b6e9b..e6a621a 100755
--- a/py/EmAySee_RepaintKSampler.py
+++ b/py/EmAySee_RepaintKSampler.py
@@ -30,20 +30,20 @@ class EmAySee_RepaintKSampler:
FUNCTION = "EmAySee_function"
def EmAySee_function(self, model, positive, negative, latent_image, mask, sampler_name, scheduler, steps, cfg, denoise):
- # Invert the mask because in ComfyUI, white usually means "keep" and black means "change" for inpainting
+ # Invert the mask because in ComfyUI, white usually means "keep" and black means "change" for inpainting
inverted_mask = 1.0 - mask.unsqueeze(1)
- # Apply the mask to the latent image
+ # Apply the mask to the latent image
masked_latent = latent_image["samples"] * inverted_mask
- # Create an empty latent image for the masked area (this will be filled by the sampler)
+ # Create an empty latent image for the masked area (this will be filled by the sampler)
initial_noise = torch.randn_like(latent_image["samples"]) * denoise
- # Combine the masked original and the initial noise for the masked area
- # Where the mask is white (1.0), we take the noise; where it's black (0.0), we take the original latent
+ # Combine the masked original and the initial noise for the masked area
+ # Where the mask is white (1.0), we take the noise; where it's black (0.0), we take the original latent
combined_latent = masked_latent + initial_noise * mask.unsqueeze(1)
- # Perform the K-sampling with the combined latent as the starting point
+ # Perform the K-sampling with the combined latent as the starting point
sampler = ComfyUISampler(model)
samples = sampler.sample(noise=combined_latent,
positive=positive,
diff --git a/py/EmAySee_SaveImage.py b/py/EmAySee_SaveImage.py
index f44d2e1..df99323 100755
--- a/py/EmAySee_SaveImage.py
+++ b/py/EmAySee_SaveImage.py
@@ -22,7 +22,7 @@ class EmAySee_SaveImage:
"images": ("IMAGE",),
"subfolder": ("STRING", {"default": ""}),
"name_postfix": ("STRING", {"default": "image_"}),
- "timestamp_format": (["none", "YYYYMMDDHHMMSS"], {"default": "YYYYMMDDHHMMSS"}), # Removed .f option
+ "timestamp_format": (["none", "YYYYMMDDHHMMSS"], {"default": "YYYYMMDDHHMMSS"}), # Removed .f option
"name_prefix": ("STRING", {"default": ""}),
"separator": ("STRING", {"default": "_"}),
},
@@ -48,31 +48,31 @@ class EmAySee_SaveImage:
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
- # --- Filename Construction ---
+ # --- Filename Construction ---
filename_parts = []
- # Add name prefix (if provided)
+ # Add name prefix (if provided)
if name_prefix:
filename_parts.append(name_prefix)
- # Add timestamp (if selected)
+ # Add timestamp (if selected)
if timestamp_format != "none":
if timestamp_format == "YYYYMMDDHHMMSS":
- timestamp = time.strftime("%Y%m%d%H%M%S") # Keep only YYYYMMDDHHMMSS
- # No else needed, as the only other option is "none"
+ timestamp = time.strftime("%Y%m%d%H%M%S") # Keep only YYYYMMDDHHMMSS
+ # No else needed, as the only other option is "none"
filename_parts.append(timestamp)
- # Add the base filename postfix
+ # Add the base filename postfix
filename_parts.append(name_postfix)
- # Join the parts with the separator
+ # Join the parts with the separator
file_name = separator.join(filename_parts)
- file = f"{file_name}" # comfyui counter
+ file = f"{file_name}" # comfyui counter
file_path = os.path.join(full_output_folder, f"{file}.png")
filepaths.append(file_path)
- # --- Metadata Handling ---
+ # --- Metadata Handling ---
if extra_pnginfo is not None:
pnginfo = PngImagePlugin.PngInfo()
for k, v in extra_pnginfo.items():
diff --git a/py/EmAySee_SaveTextToFile.py b/py/EmAySee_SaveTextToFile.py
index 3ff53bf..72f9d5f 100755
--- a/py/EmAySee_SaveTextToFile.py
+++ b/py/EmAySee_SaveTextToFile.py
@@ -1,5 +1,5 @@
import os
-import folder_paths # ComfyUI utility to get paths
+import folder_paths # ComfyUI utility to get paths
class EmAySee_SaveTextToFile:
"""
@@ -9,48 +9,48 @@ class EmAySee_SaveTextToFile:
def INPUT_TYPES(s):
return {
"required": {
- "text_to_save": ("STRING", {"multiline": True}), # The text content to save
- "filename": ("STRING", {"default": "output_text"}), # The desired filename (without extension)
- "subfolder": ("STRING", {"default": ""}), # Optional subfolder within the output directory
+ "text_to_save": ("STRING", {"multiline": True}), # The text content to save
+ "filename": ("STRING", {"default": "output_text"}), # The desired filename (without extension)
+ "subfolder": ("STRING", {"default": ""}), # Optional subfolder within the output directory
}
}
- # Define RETURN_TYPES as an empty tuple since this node has no direct outputs
- # This is required by ComfyUI even for nodes without outputs.
+ # Define RETURN_TYPES as an empty tuple since this node has no direct outputs
+ # This is required by ComfyUI even for nodes without outputs.
RETURN_TYPES = ()
- # RETURN_NAMES = () # RETURN_NAMES is not strictly necessary if RETURN_TYPES is empty
+ # RETURN_NAMES = () # RETURN_NAMES is not strictly necessary if RETURN_TYPES is empty
- CATEGORY = "EmAySee_Utils" # Category in the ComfyUI menu
- TITLE = "EmAySee Save Text to File" # Title displayed on the node
+ CATEGORY = "EmAySee_Utils" # Category in the ComfyUI menu
+ TITLE = "EmAySee Save Text to File" # Title displayed on the node
- FUNCTION = "EmAySee_save_text" # The method that will be executed
+ FUNCTION = "EmAySee_save_text" # The method that will be executed
def EmAySee_save_text(self, text_to_save, filename, subfolder):
"""
Saves the text content to a file in the specified subfolder.
"""
- # Get the base output directory for ComfyUI
- output_dir = folder_paths.get_output_paths(None)[0] # Get the first output path
+ # Get the base output directory for ComfyUI
+ output_dir = folder_paths.get_output_paths(None)[0] # Get the first output path
- # Construct the full directory path
+ # Construct the full directory path
if subfolder:
save_dir = os.path.join(output_dir, subfolder)
else:
save_dir = output_dir
- # Ensure the directory exists, create if necessary
+ # Ensure the directory exists, create if necessary
os.makedirs(save_dir, exist_ok=True)
- # Construct the full file path with .txt extension
- # Sanitize filename to remove potentially problematic characters
+ # Construct the full file path with .txt extension
+ # Sanitize filename to remove potentially problematic characters
safe_filename = "".join(c for c in filename if c.isalnum() or c in (' ', '_', '-')).rstrip()
if not safe_filename:
- safe_filename = "output_text" # Fallback if filename becomes empty after sanitization
+ safe_filename = "output_text" # Fallback if filename becomes empty after sanitization
file_path = os.path.join(save_dir, f"{safe_filename}.txt")
try:
- # Save the text content to the file
+ # Save the text content to the file
with open(file_path, "w", encoding="utf-8") as f:
f.write(text_to_save)
@@ -61,15 +61,15 @@ class EmAySee_SaveTextToFile:
except Exception as e:
print(f"An unexpected error occurred while saving text: {e}")
- # This node doesn't return any specific output values, so return an empty tuple.
+ # This node doesn't return any specific output values, so return an empty tuple.
return ()
-# Mapping of node class name to the class
+# Mapping of node class name to the class
NODE_CLASS_MAPPINGS = {
"EmAySee_SaveTextToFile": EmAySee_SaveTextToFile
}
-# Mapping of node class name to the display name in the UI
+# Mapping of node class name to the display name in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_SaveTextToFile": "EmAySee Save Text to File"
}
diff --git a/py/EmAySee_SelectiveModelUnloader.py b/py/EmAySee_SelectiveModelUnloader.py
index f83a9ea..0435aeb 100755
--- a/py/EmAySee_SelectiveModelUnloader.py
+++ b/py/EmAySee_SelectiveModelUnloader.py
@@ -101,17 +101,17 @@ class EmAySee_SelectiveModelUnloader:
gc.collect()
comfy.model_management.soft_empty_cache()
- # Replicating the "Free model and node cache" button backend logic:
+ # Replicating the "Free model and node cache" button backend logic:
if not exclude_checkpoints:
comfy.model_management.unload_all_models()
if hasattr(comfy.model_management, "loaded_models"):
comfy.model_management.loaded_models.clear()
- # Clear the execution cache (Node Cache)
+ # Clear the execution cache (Node Cache)
if hasattr(comfy.model_management, "node_cache"):
comfy.model_management.node_cache.clear()
- # Brute force memory cleanup
+ # Brute force memory cleanup
try:
comfy.model_management.cleanup_models(keep_free_mem=0)
except:
diff --git a/py/EmAySee_StringPoseSelectorNode.py b/py/EmAySee_StringPoseSelectorNode.py
index b32decf..16f5acf 100755
--- a/py/EmAySee_StringPoseSelectorNode.py
+++ b/py/EmAySee_StringPoseSelectorNode.py
@@ -16,7 +16,7 @@ class EmAySee_StringPoseSelectorNode:
"min": 1,
"max": 5,
"step": 1,
- "display": "slider", # Or "number"
+ "display": "slider", # Or "number"
"label": "Select Input (1-5)"
}),
},
@@ -40,13 +40,13 @@ class EmAySee_StringPoseSelectorNode:
elif select_index == 5:
return (string_input_5,)
else:
- return ("",) # Should not happen given input constraints, but as a failsafe
+ return ("",) # Should not happen given input constraints, but as a failsafe
-NODE_CLASS_MAPPINGS = { # Moved to after the class definition
+NODE_CLASS_MAPPINGS = { # Moved to after the class definition
"EmAySee_StringPoseSelectorNode": EmAySee_StringPoseSelectorNode
}
-NODE_DISPLAY_NAME_MAPPINGS = { # Moved to after the class definition
+NODE_DISPLAY_NAME_MAPPINGS = { # Moved to after the class definition
"EmAySee_StringPoseSelectorNode": "EmAySee Pose Option Selector"
}
\ No newline at end of file
diff --git a/py/EmAySee_StringTupleInputNode.py b/py/EmAySee_StringTupleInputNode.py
index f18877d..84168ef 100755
--- a/py/EmAySee_StringTupleInputNode.py
+++ b/py/EmAySee_StringTupleInputNode.py
@@ -17,8 +17,8 @@ class EmAySee_StringTupleInputNode:
FUNCTION = "create_string_tuple"
def create_string_tuple(self, string_list):
- lines = string_list.strip().split('\n') # Split multiline string into lines
- string_tuple = tuple(line.strip() for line in lines) # Create a tuple of strings, stripping whitespace from each line
+ lines = string_list.strip().split('\n') # Split multiline string into lines
+ string_tuple = tuple(line.strip() for line in lines) # Create a tuple of strings, stripping whitespace from each line
return (string_tuple,)
NODE_CLASS_MAPPINGS = {
diff --git a/py/EmAySee_SubmitToOobaboogaAPI-withKey-ThinkingParser.py b/py/EmAySee_SubmitToOobaboogaAPI-withKey-ThinkingParser.py
index 1523db8..5497c97 100755
--- a/py/EmAySee_SubmitToOobaboogaAPI-withKey-ThinkingParser.py
+++ b/py/EmAySee_SubmitToOobaboogaAPI-withKey-ThinkingParser.py
@@ -35,7 +35,7 @@ class EmAySee_SubmitToOobaboogaAPIWithKeyThinker:
"Authorization": f"Bearer {api_key}"
}
- # Construct the payload for OpenAI-compatible API
+ # Construct the payload for OpenAI-compatible API
data = {
"messages": [
{"role": "system", "content": system_prompt},
@@ -45,7 +45,7 @@ class EmAySee_SubmitToOobaboogaAPIWithKeyThinker:
"temperature": temperature,
"top_p": top_p,
"seed": seed,
- "mode": "instruct", # Helpful for some Ooba configs
+ "mode": "instruct", # Helpful for some Ooba configs
"stream": False
}
@@ -55,20 +55,20 @@ class EmAySee_SubmitToOobaboogaAPIWithKeyThinker:
result_json = response.json()
- # Extract content from OpenAI format
+ # Extract content from OpenAI format
if "choices" in result_json and len(result_json["choices"]) > 0:
raw_content = result_json["choices"][0]["message"]["content"]
else:
raw_content = ""
print(f"EmAySee_SubmitToOobaboogaAPIWithKeyThinker: No content in response: {result_json}")
- # --- Logic to separate content from clean text ---
+ # --- Logic to separate content from clean text ---
- # 1. Extract thinking content
+ # 1. Extract thinking content
think_match = re.search(r'(.*?)', raw_content, flags=re.DOTALL)
thinking_content = think_match.group(1).strip() if think_match else ""
- # 2. Remove thinking tags to get clean text
+ # 2. Remove thinking tags to get clean text
clean_text = re.sub(r'.*?', '', raw_content, flags=re.DOTALL).strip()
return (clean_text, thinking_content)
diff --git a/py/EmAySee_SubmitToOobaboogaAPI-withKey-test.py b/py/EmAySee_SubmitToOobaboogaAPI-withKey-test.py
index 0f05cf0..375980a 100755
--- a/py/EmAySee_SubmitToOobaboogaAPI-withKey-test.py
+++ b/py/EmAySee_SubmitToOobaboogaAPI-withKey-test.py
@@ -2,7 +2,7 @@ import requests
import json
import time
-class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with EmAySee_
+class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with EmAySee_
def __init__(self):
pass
@@ -12,23 +12,23 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with
"required": {
"apikey_input": ("STRING", {"default": "Enter your api key here"}),
"text_input": ("STRING", {"default": "Enter your prompt for text-generation-webui here"}),
- "api_endpoint": ("STRING", ["http://10.0.0.71:5000/v1/completions","http://10.0.0.76:5000/v1/completions"],{"default": "http://10.0.0.71:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming
- "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default
- "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
- "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga
- "min_p": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga
- "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
- "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default
+ "api_endpoint": ("STRING", ["http://10.0.0.71:5000/v1/completions","http://10.0.0.76:5000/v1/completions"],{"default": "http://10.0.0.71:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming
+ "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default
+ "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
+ "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga
+ "min_p": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga
+ "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
+ "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default
"max_new_tokens": (
- ["25", "50", "75", "100", "125", "150", "175", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800"], # Dropdown options
- {"default": "200"} # Default, adjust as needed
+ ["25", "50", "75", "100", "125", "150", "175", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800"], # Dropdown options
+ {"default": "200"} # Default, adjust as needed
),
- "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API
+ "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API
"delay_seconds": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.1, "label": "Delay (seconds)"}),
"show_api_response": ("BOOLEAN", {"default": False, "label": "Show API Response in Console"}),
},
"optional": {
- # You can add 'stopping_strings' or other completion-specific parameters here later if needed
+ # You can add 'stopping_strings' or other completion-specific parameters here later if needed
}
}
@@ -37,28 +37,28 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with
OUTPUT_NODE = True
FUNCTION = "submitToOobabooga"
- def submitToOobabooga(self, apikey_input, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed
+ def submitToOobabooga(self, apikey_input, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed
"""
Submits a request to the text-generation-webui (oobabooga) /v1/completions API for non-streaming text generation.
Waits for the full response before returning.
"""
payload = {
"prompt": text_input,
- "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions
+ "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"min_p": top_k,
"typical_p": typical_p,
"repetition_penalty": repetition_penalty,
- "seed": seed, # Added seed to payload
- "stream": False # **Ensuring non-streaming: explicitly set stream to False**
- # Add other oobabooga /v1/completions parameters here as needed, consult API docs
+ "seed": seed, # Added seed to payload
+ "stream": False # **Ensuring non-streaming: explicitly set stream to False**
+ # Add other oobabooga /v1/completions parameters here as needed, consult API docs
}
headers = {
'Content-Type': 'application/json',
- 'Authorization': f'Bearer {apikey_input}' # <-- ADD THIS LINE
+ 'Authorization': f'Bearer {apikey_input}' # <-- ADD THIS LINE
}
try:
time.sleep(delay_seconds)
@@ -76,12 +76,12 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with
print("Oobabooga API Response Content:")
print(api_response)
- # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS!
+ # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS!
if isinstance(api_response, dict) and 'choices' in api_response and api_response['choices']:
- api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions
- elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly
+ api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions
+ elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly
api_response_text = api_response['text']
- elif isinstance(api_response, str): # Fallback if API returns plain text string
+ elif isinstance(api_response, str): # Fallback if API returns plain text string
api_response_text = api_response
else:
error_message = f"Unexpected Oobabooga API response format for /v1/completions. JSON: {api_response}"
@@ -98,9 +98,9 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_test: # Class name now prefixed with
NODE_CLASS_MAPPINGS = {
- "EmAySee_SubmitToOobaboogaAPIWithKey_test": EmAySee_SubmitToOobaboogaAPIWithKey_test # Class mapping with alias
+ "EmAySee_SubmitToOobaboogaAPIWithKey_test": EmAySee_SubmitToOobaboogaAPIWithKey_test # Class mapping with alias
}
NODE_DISPLAY_NAME_MAPPINGS = {
- "EmAySee_SubmitToOobaboogaAPIWithKey_test": "EmAySee Submit to Oobabooga API Node With API Key_test" # Display name with alias
+ "EmAySee_SubmitToOobaboogaAPIWithKey_test": "EmAySee Submit to Oobabooga API Node With API Key_test" # Display name with alias
}
\ No newline at end of file
diff --git a/py/EmAySee_SubmitToOobaboogaAPI-withKey.py b/py/EmAySee_SubmitToOobaboogaAPI-withKey.py
index 55f9946..453c56e 100755
--- a/py/EmAySee_SubmitToOobaboogaAPI-withKey.py
+++ b/py/EmAySee_SubmitToOobaboogaAPI-withKey.py
@@ -2,7 +2,7 @@ import requests
import json
import time
-class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAySee_
+class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAySee_
def __init__(self):
pass
@@ -12,22 +12,22 @@ class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAy
"required": {
"apikey_input": ("STRING", {"default": "Enter your api key here"}),
"text_input": ("STRING", {"default": "Enter your prompt for text-generation-webui here"}),
- "api_endpoint": ("STRING", {"default": "http://10.0.0.57:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming
- "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default
- "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
- "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga
- "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
- "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default
+ "api_endpoint": ("STRING", {"default": "http://10.0.0.57:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming
+ "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default
+ "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
+ "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga
+ "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
+ "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default
"max_new_tokens": (
- ["25", "50", "75", "100", "125", "150", "175", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800", "1024", "2048", "4096"], # Dropdown options
- {"default": "300"} # Default, adjust as needed
+ ["25", "50", "75", "100", "125", "150", "175", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800", "1024", "2048", "4096"], # Dropdown options
+ {"default": "300"} # Default, adjust as needed
),
- "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API
+ "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API
"delay_seconds": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.1, "label": "Delay (seconds)"}),
"show_api_response": ("BOOLEAN", {"default": False, "label": "Show API Response in Console"}),
},
"optional": {
- # You can add 'stopping_strings' or other completion-specific parameters here later if needed
+ # You can add 'stopping_strings' or other completion-specific parameters here later if needed
}
}
@@ -36,27 +36,27 @@ class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAy
OUTPUT_NODE = True
FUNCTION = "submitToOobabooga"
- def submitToOobabooga(self, apikey_input, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed
+ def submitToOobabooga(self, apikey_input, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed
"""
Submits a request to the text-generation-webui (oobabooga) /v1/completions API for non-streaming text generation.
Waits for the full response before returning.
"""
payload = {
"prompt": text_input,
- "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions
+ "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"typical_p": typical_p,
"repetition_penalty": repetition_penalty,
- "seed": seed, # Added seed to payload
- "stream": False # **Ensuring non-streaming: explicitly set stream to False**
- # Add other oobabooga /v1/completions parameters here as needed, consult API docs
+ "seed": seed, # Added seed to payload
+ "stream": False # **Ensuring non-streaming: explicitly set stream to False**
+ # Add other oobabooga /v1/completions parameters here as needed, consult API docs
}
headers = {
'Content-Type': 'application/json',
- 'Authorization': f'Bearer {apikey_input}' # <-- ADD THIS LINE
+ 'Authorization': f'Bearer {apikey_input}' # <-- ADD THIS LINE
}
try:
time.sleep(delay_seconds)
@@ -74,12 +74,12 @@ class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAy
print("Oobabooga API Response Content:")
print(api_response)
- # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS!
+ # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS!
if isinstance(api_response, dict) and 'choices' in api_response and api_response['choices']:
- api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions
- elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly
+ api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions
+ elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly
api_response_text = api_response['text']
- elif isinstance(api_response, str): # Fallback if API returns plain text string
+ elif isinstance(api_response, str): # Fallback if API returns plain text string
api_response_text = api_response
else:
error_message = f"Unexpected Oobabooga API response format for /v1/completions. JSON: {api_response}"
@@ -96,9 +96,9 @@ class EmAySee_SubmitToOobaboogaAPIWithKey: # Class name now prefixed with EmAy
NODE_CLASS_MAPPINGS = {
- "EmAySee_SubmitToOobaboogaAPIWithKey": EmAySee_SubmitToOobaboogaAPIWithKey # Class mapping with alias
+ "EmAySee_SubmitToOobaboogaAPIWithKey": EmAySee_SubmitToOobaboogaAPIWithKey # Class mapping with alias
}
NODE_DISPLAY_NAME_MAPPINGS = {
- "EmAySee_SubmitToOobaboogaAPIWithKey": "EmAySee Submit to Oobabooga API Node With API Key" # Display name with alias
+ "EmAySee_SubmitToOobaboogaAPIWithKey": "EmAySee Submit to Oobabooga API Node With API Key" # Display name with alias
}
\ No newline at end of file
diff --git a/py/EmAySee_SubmitToOobaboogaAPI.py b/py/EmAySee_SubmitToOobaboogaAPI.py
index 2e0e75a..48e747f 100755
--- a/py/EmAySee_SubmitToOobaboogaAPI.py
+++ b/py/EmAySee_SubmitToOobaboogaAPI.py
@@ -2,7 +2,7 @@ import requests
import json
import time
-class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_
+class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_
def __init__(self):
pass
@@ -11,22 +11,22 @@ class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_
return {
"required": {
"text_input": ("STRING", {"default": "Enter your prompt for text-generation-webui here"}),
- "api_endpoint": ("STRING", {"default": "http://127.0.0.1:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming
- "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default
- "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
- "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga
- "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
- "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default
+ "api_endpoint": ("STRING", {"default": "http://127.0.0.1:5000/v1/completions"}), # Default oobabooga COMPletions API endpoint - Non-streaming
+ "temperature": ("FLOAT", {"default": 0.7, "min": 0.01, "max": 2.0, "step": 0.01}), # Common oobabooga default
+ "top_p": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
+ "top_k": ("INT", {"default": 0, "min": 0, "max": 200}), # 0 means disabled in oobabooga
+ "typical_p": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), # Common oobabooga default
+ "repetition_penalty": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 2.0, "step": 0.01}), # Common oobabooga default
"max_new_tokens": (
- ["50", "100", "150", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800"], # Dropdown options
- {"default": "200"} # Default, adjust as needed
+ ["50", "100", "150", "200", "250", "300", "350", "400", "450", "500", "550", "600", "650", "700", "750", "800"], # Dropdown options
+ {"default": "200"} # Default, adjust as needed
),
- "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API
+ "seed": ("INT", {"default": -1, "label": "Seed (-1 for random)"}), # Added Seed - common in completions API
"delay_seconds": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.1, "label": "Delay (seconds)"}),
"show_api_response": ("BOOLEAN", {"default": False, "label": "Show API Response in Console"}),
},
"optional": {
- # You can add 'stopping_strings' or other completion-specific parameters here later if needed
+ # You can add 'stopping_strings' or other completion-specific parameters here later if needed
}
}
@@ -35,22 +35,22 @@ class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_
OUTPUT_NODE = True
FUNCTION = "submitToOobabooga"
- def submitToOobabooga(self, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed
+ def submitToOobabooga(self, api_endpoint, text_input, temperature, top_p, top_k, typical_p, repetition_penalty, max_new_tokens, seed, delay_seconds, show_api_response, **kwargs): # Removed history_, added seed
"""
Submits a request to the text-generation-webui (oobabooga) /v1/completions API for non-streaming text generation.
Waits for the full response before returning.
"""
payload = {
"prompt": text_input,
- "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions
+ "max_tokens": int(max_new_tokens), # Use 'max_tokens' for /v1/completions
"temperature": temperature,
"top_p": top_p,
"top_k": top_k,
"typical_p": typical_p,
"repetition_penalty": repetition_penalty,
- "seed": seed, # Added seed to payload
- "stream": False # **Ensuring non-streaming: explicitly set stream to False**
- # Add other oobabooga /v1/completions parameters here as needed, consult API docs
+ "seed": seed, # Added seed to payload
+ "stream": False # **Ensuring non-streaming: explicitly set stream to False**
+ # Add other oobabooga /v1/completions parameters here as needed, consult API docs
}
headers = {'Content-Type': 'application/json'}
@@ -71,12 +71,12 @@ class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_
print("Oobabooga API Response Content:")
print(api_response)
- # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS!
+ # Response parsing for /v1/completions endpoint - VERIFY AGAINST DOCS!
if isinstance(api_response, dict) and 'choices' in api_response and api_response['choices']:
- api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions
- elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly
+ api_response_text = api_response['choices'][0]['text'] # Expecting text directly under 'choices'-> 0 -> 'text' for /v1/completions
+ elif isinstance(api_response, dict) and 'text' in api_response: # Fallback if API just returns text directly
api_response_text = api_response['text']
- elif isinstance(api_response, str): # Fallback if API returns plain text string
+ elif isinstance(api_response, str): # Fallback if API returns plain text string
api_response_text = api_response
else:
error_message = f"Unexpected Oobabooga API response format for /v1/completions. JSON: {api_response}"
@@ -93,9 +93,9 @@ class EmAySee_SubmitToOobaboogaAPI: # Class name now prefixed with EmAySee_
NODE_CLASS_MAPPINGS = {
- "EmAySee_SubmitToOobaboogaAPI": EmAySee_SubmitToOobaboogaAPI # Class mapping with alias
+ "EmAySee_SubmitToOobaboogaAPI": EmAySee_SubmitToOobaboogaAPI # Class mapping with alias
}
NODE_DISPLAY_NAME_MAPPINGS = {
- "EmAySee_SubmitToOobaboogaAPI": "EmAySee Submit to Oobabooga API Node" # Display name with alias
+ "EmAySee_SubmitToOobaboogaAPI": "EmAySee Submit to Oobabooga API Node" # Display name with alias
}
\ No newline at end of file
diff --git a/py/EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse.py b/py/EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse.py
index 3dd31c0..2f5d14a 100755
--- a/py/EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse.py
+++ b/py/EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse.py
@@ -26,7 +26,7 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse:
"dry_base": ("FLOAT", {"default": 1.75, "min": 1.0, "max": 5.0, "step": 0.05}),
"dry_allowed_length": ("INT", {"default": 2, "min": 0, "max": 20}),
"enable_thinking": ("BOOLEAN", {"default": True}),
- # New Parameter: reasoning_effort
+ # New Parameter: reasoning_effort
"reasoning_effort": (["low", "medium", "high"], {"default": "medium"}),
"guidance_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.1}),
"seed": ("INT", {"default": -1, "min": -1, "max": 0xffffffffffffffff}),
@@ -71,7 +71,7 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse:
"dry_base": dry_base,
"dry_allowed_length": dry_allowed_length,
"enable_thinking": enable_thinking,
- "reasoning_effort": reasoning_effort, # Added here
+ "reasoning_effort": reasoning_effort, # Added here
"guidance_scale": guidance_scale,
"negative_prompt": negative_prompt,
"instruction_template": instruction_template,
@@ -95,11 +95,11 @@ class EmAySee_SubmitToOobaboogaAPIWithKey_WithThinkParse:
raw_content = ""
print(f"EmAySee_SubmitToOobaboogaAPIWithKey: No content in response: {result_json}")
- # 1. Extract thinking content
+ # 1. Extract thinking content
think_match = re.search(r'(.*?)', raw_content, flags=re.DOTALL)
thinking_content = think_match.group(1).strip() if think_match else ""
- # 2. Remove thinking tags to get clean text
+ # 2. Remove thinking tags to get clean text
clean_text = re.sub(r'.*?', '', raw_content, flags=re.DOTALL).strip()
return (clean_text, thinking_content)
diff --git a/py/EmAySee_ToggleIntNode.py b/py/EmAySee_ToggleIntNode.py
index a54ffa9..7dd3765 100755
--- a/py/EmAySee_ToggleIntNode.py
+++ b/py/EmAySee_ToggleIntNode.py
@@ -19,9 +19,9 @@ class EmAySee_ToggleIntNode:
FUNCTION = "toggle_to_integer"
def toggle_to_integer(self, toggle_input, int_if_on, int_if_off):
- if toggle_input: # If toggle_input is True (toggle is ON)
+ if toggle_input: # If toggle_input is True (toggle is ON)
return (int_if_on,)
- else: # If toggle_input is False (toggle is OFF)
+ else: # If toggle_input is False (toggle is OFF)
return (int_if_off,)
NODE_CLASS_MAPPINGS = {
diff --git a/py/EmAySee_VarTextReplacer.py b/py/EmAySee_VarTextReplacer.py
index 97c67dd..9cf3397 100755
--- a/py/EmAySee_VarTextReplacer.py
+++ b/py/EmAySee_VarTextReplacer.py
@@ -8,26 +8,26 @@ class EmAySee_VarTextReplacer:
def INPUT_TYPES(s):
return {
"required": {
- "main_text": ("STRING", {"default": "This is %var1%, that is %var2%, and here is %var3%. We also have %var4%, %var5%, %var6%, %var7%, %var8%, %var9%, and finally %var10%.", "multiline": True}), # Main text with placeholders
- "var1": ("STRING", {"default": "Variable 1"}), # Input variable for %var1%
- "var2": ("STRING", {"default": "Variable 2"}), # Input variable for %var2%
- "var3": ("STRING", {"default": "Variable 3"}), # Input variable for %var3%
- "var4": ("STRING", {"default": "Variable 4"}), # Input variable for %var4%
- "var5": ("STRING", {"default": "Variable 5"}), # Input variable for %var5%
- "var6": ("STRING", {"default": "Variable 6"}), # Input variable for %var6%
- "var7": ("STRING", {"default": "Variable 7"}), # Input variable for %var7%
- "var8": ("STRING", {"default": "Variable 8"}), # Input variable for %var8%
- "var9": ("STRING", {"default": "Variable 9"}), # Input variable for %var9%
- "var10": ("STRING", {"default": "Variable 10"}), # Input variable for %var10%
+ "main_text": ("STRING", {"default": "This is %var1%, that is %var2%, and here is %var3%. We also have %var4%, %var5%, %var6%, %var7%, %var8%, %var9%, and finally %var10%.", "multiline": True}), # Main text with placeholders
+ "var1": ("STRING", {"default": "Variable 1"}), # Input variable for %var1%
+ "var2": ("STRING", {"default": "Variable 2"}), # Input variable for %var2%
+ "var3": ("STRING", {"default": "Variable 3"}), # Input variable for %var3%
+ "var4": ("STRING", {"default": "Variable 4"}), # Input variable for %var4%
+ "var5": ("STRING", {"default": "Variable 5"}), # Input variable for %var5%
+ "var6": ("STRING", {"default": "Variable 6"}), # Input variable for %var6%
+ "var7": ("STRING", {"default": "Variable 7"}), # Input variable for %var7%
+ "var8": ("STRING", {"default": "Variable 8"}), # Input variable for %var8%
+ "var9": ("STRING", {"default": "Variable 9"}), # Input variable for %var9%
+ "var10": ("STRING", {"default": "Variable 10"}), # Input variable for %var10%
}
}
- RETURN_TYPES = ("STRING",) # Output is a single string
- RETURN_NAMES = ("replaced_text",) # Name for the output
- CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu
- TITLE = "EmAySee Var Text Replacer" # Title displayed on the node
+ RETURN_TYPES = ("STRING",) # Output is a single string
+ RETURN_NAMES = ("replaced_text",) # Name for the output
+ CATEGORY = "EmAySee_Text" # Category in the ComfyUI menu
+ TITLE = "EmAySee Var Text Replacer" # Title displayed on the node
- FUNCTION = "EmAySee_replace_placeholders" # The method that will be executed
+ FUNCTION = "EmAySee_replace_placeholders" # The method that will be executed
def EmAySee_replace_placeholders(self, main_text, var1, var2, var3, var4, var5, var6, var7, var8, var9, var10):
"""
@@ -35,7 +35,7 @@ class EmAySee_VarTextReplacer:
"""
output_text = main_text
- # Replace the specific variables we defined in INPUT_TYPES
+ # Replace the specific variables we defined in INPUT_TYPES
output_text = output_text.replace("%var1%", str(var1))
output_text = output_text.replace("%var2%", str(var2))
output_text = output_text.replace("%var3%", str(var3))
@@ -47,17 +47,17 @@ class EmAySee_VarTextReplacer:
output_text = output_text.replace("%var9%", str(var9))
output_text = output_text.replace("%var10%", str(var10))
- # Note: Using str() just in case a non-string input is ever connected,
- # though INPUT_TYPES specifies STRING.
+ # Note: Using str() just in case a non-string input is ever connected,
+ # though INPUT_TYPES specifies STRING.
- return (output_text,) # Return the result as a tuple
+ return (output_text,) # Return the result as a tuple
-# Mapping of node class name to the class
+# Mapping of node class name to the class
NODE_CLASS_MAPPINGS = {
"EmAySee_VarTextReplacer": EmAySee_VarTextReplacer
}
-# Mapping of node class name to the display name in the UI
+# Mapping of node class name to the display name in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_VarTextReplacer": "EmAySee Var Text Replacer"
}
diff --git a/py/EmAySee_VeryUniqueStringSelectorNode.py b/py/EmAySee_VeryUniqueStringSelectorNode.py
index e613b69..97e7dfa 100755
--- a/py/EmAySee_VeryUniqueStringSelectorNode.py
+++ b/py/EmAySee_VeryUniqueStringSelectorNode.py
@@ -14,17 +14,17 @@ class EmAySee_StringSelectorNode:
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("SELECTED_STRING",)
- CATEGORY = "EmAySee_Utils" # Or a category you prefer
+ CATEGORY = "EmAySee_Utils" # Or a category you prefer
- FUNCTION = "EmAySee_function" # <-- IMPORTANT: Back to string for FUNCTION
+ FUNCTION = "EmAySee_function" # <-- IMPORTANT: Back to string for FUNCTION
- def EmAySee_function(self, string_input_1, string_input_2, select_input): # Ensure defined AFTER FUNCTION is set
+ def EmAySee_function(self, string_input_1, string_input_2, select_input): # Ensure defined AFTER FUNCTION is set
if select_input == "string_input_1":
return (string_input_1,)
elif select_input == "string_input_2":
return (string_input_2,)
else:
- return ("",) # Should not happen, but default to empty string
+ return ("",) # Should not happen, but default to empty string
NODE_CLASS_MAPPINGS = {
"EmAySee_VeryUniqueStringSelectorNode": EmAySee_StringSelectorNode
diff --git a/py/EmAySee_VisualMachine_SaveImage.py b/py/EmAySee_VisualMachine_SaveImage.py
index b649a5b..ee65bcf 100755
--- a/py/EmAySee_VisualMachine_SaveImage.py
+++ b/py/EmAySee_VisualMachine_SaveImage.py
@@ -36,8 +36,8 @@ class EmAySee_VisualMachine_SaveImageAndText_V2:
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
- # 1. Handle Naming
- # Uses the prefix as the base name; extension replacement happens here
+ # 1. Handle Naming
+ # Uses the prefix as the base name; extension replacement happens here
file_base = f"{filename_prefix}"
image_file = f"{file_base}.png"
log_file = f"{file_base}.txt"
@@ -45,10 +45,10 @@ class EmAySee_VisualMachine_SaveImageAndText_V2:
image_path = os.path.join(full_output_folder, image_file)
log_path = os.path.join(full_output_folder, log_file)
- # 2. Save the Image
+ # 2. Save the Image
img.save(image_path, pnginfo=None, compress_level=4)
- # 3. Save the Companion Text File
+ # 3. Save the Companion Text File
entry = (
f"--- SPECTRE V2 METADATA ---\n"
f"FILENAME: {image_file}\n"
diff --git a/py/EmAySee_WANVAECompatibleAspectRatioCalculator.py b/py/EmAySee_WANVAECompatibleAspectRatioCalculator.py
index 343a6c5..9d1869e 100755
--- a/py/EmAySee_WANVAECompatibleAspectRatioCalculator.py
+++ b/py/EmAySee_WANVAECompatibleAspectRatioCalculator.py
@@ -21,18 +21,18 @@ class EmAySee_VAECompatibleAspectRatioCalculator:
FUNCTION = "EmAySee_calculate_dimensions"
def EmAySee_calculate_dimensions(self, image, output_height):
- # We'll use the first image in the batch to determine dimensions.
+ # We'll use the first image in the batch to determine dimensions.
batch_size, original_height, original_width, channels = image.shape
- # Calculate the ideal width based on the original aspect ratio.
+ # Calculate the ideal width based on the original aspect ratio.
ratio = original_width / original_height
- # Calculate the new width based on the desired height and aspect ratio.
- # This new width must be a multiple of 64 to be VAE-compatible.
+ # Calculate the new width based on the desired height and aspect ratio.
+ # This new width must be a multiple of 64 to be VAE-compatible.
vae_stride = 64
calculated_width = output_height * ratio
- # Round the width to the nearest multiple of the VAE stride.
+ # Round the width to the nearest multiple of the VAE stride.
output_width = int(round(calculated_width / vae_stride) * vae_stride)
return (output_width, output_height,)
diff --git a/py/EmAySee_WD14TagFilter.py b/py/EmAySee_WD14TagFilter.py
index f57cc22..82d7479 100755
--- a/py/EmAySee_WD14TagFilter.py
+++ b/py/EmAySee_WD14TagFilter.py
@@ -17,24 +17,24 @@ class EmAySee_WD14TagFilter:
FUNCTION = "EmAySee_filter_tags"
def EmAySee_filter_tags(self, wd14_tags_input, tags_to_remove):
- # 1. Normalize the input WD14 tags string (replace newlines with commas, split)
- # This assumes the WD14 output is a string of tags separated by commas, spaces, or newlines.
+ # 1. Normalize the input WD14 tags string (replace newlines with commas, split)
+ # This assumes the WD14 output is a string of tags separated by commas, spaces, or newlines.
input_tags = re.split(r'[,\s\n]+', wd14_tags_input.lower().strip())
input_tags = [tag.strip() for tag in input_tags if tag.strip()]
- # 2. Normalize the tags to remove string
- # Split the string in the text box by commas or newlines
+ # 2. Normalize the tags to remove string
+ # Split the string in the text box by commas or newlines
remove_list_raw = re.split(r'[,\n]+', tags_to_remove.lower().strip())
tags_to_remove_set = {tag.strip() for tag in remove_list_raw if tag.strip()}
- # 3. Perform the filtering
+ # 3. Perform the filtering
filtered_tags = []
for tag in input_tags:
- # Only keep the tag if it is NOT in the set of tags to remove
+ # Only keep the tag if it is NOT in the set of tags to remove
if tag not in tags_to_remove_set:
filtered_tags.append(tag)
- # 4. Join the remaining tags back into a single string
+ # 4. Join the remaining tags back into a single string
filtered_tags_output = ", ".join(filtered_tags)
return (filtered_tags_output,)
diff --git a/py/emaysee_lora_filepath-name-selector.x b/py/emaysee_lora_filepath-name-selector.x
index 9122155..407ef67 100755
--- a/py/emaysee_lora_filepath-name-selector.x
+++ b/py/emaysee_lora_filepath-name-selector.x
@@ -1,8 +1,8 @@
import os
-import folder_paths # ComfyUI utility to get model paths
+import folder_paths # ComfyUI utility to get model paths
-# Define the main class for your custom node.
-# The class name will be used in NODE_CLASS_MAPPINGS.
+# Define the main class for your custom node.
+# The class name will be used in NODE_CLASS_MAPPINGS.
class EmAySee_LoraFilePicker:
"""
A ComfyUI custom node to pick LORA files from configured paths.
@@ -10,7 +10,7 @@ class EmAySee_LoraFilePicker:
the selected filename and its full file path.
"""
def __init__(self):
- # Constructor for the node. No special initialization needed for this node.
+ # Constructor for the node. No special initialization needed for this node.
pass
@classmethod
@@ -19,37 +19,37 @@ class EmAySee_LoraFilePicker:
Defines the input types (widgets and connections) for the node.
This method is a class method and must return a dictionary.
"""
- # Get all LORA files from ComfyUI's configured LORA paths.
- # folder_paths.get_filename_list("loras") returns a list of relative filenames
- # (e.g., "my_lora.safetensors", "subdir/another_lora.pt").
+ # Get all LORA files from ComfyUI's configured LORA paths.
+ # folder_paths.get_filename_list("loras") returns a list of relative filenames
+ # (e.g., "my_lora.safetensors", "subdir/another_lora.pt").
lora_files = folder_paths.get_filename_list("loras")
- # Add a "None" option at the beginning of the list.
- # This allows the user to explicitly select no LORA.
+ # Add a "None" option at the beginning of the list.
+ # This allows the user to explicitly select no LORA.
lora_files.insert(0, "None")
return {
"required": {
- # "lora_name" is the name of the input port that will appear on the node.
- # (lora_files,) creates a dropdown widget in the ComfyUI interface,
- # populated with the list of LORA files.
+ # "lora_name" is the name of the input port that will appear on the node.
+ # (lora_files,) creates a dropdown widget in the ComfyUI interface,
+ # populated with the list of LORA files.
"lora_name": (lora_files,),
},
}
- # Define the types of data that this node will output.
- # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL").
- RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings.
+ # Define the types of data that this node will output.
+ # RETURN_TYPES must be a tuple of ComfyUI data types (e.g., "STRING", "IMAGE", "MODEL").
+ RETURN_TYPES = ("STRING", "STRING",) # Outputs two strings.
- # Define the names for the output ports. These names will appear on the node.
+ # Define the names for the output ports. These names will appear on the node.
RETURN_NAMES = ("filename", "full_filepath",)
- # Specifies the Python function within this class that ComfyUI should call
- # when the node is executed in a workflow.
+ # Specifies the Python function within this class that ComfyUI should call
+ # when the node is executed in a workflow.
FUNCTION = "pick_lora_file"
- # Defines the category under which this node will appear in the ComfyUI "Add Node" menu.
- # Using "EmAySee/" as a prefix helps organize your custom nodes.
+ # Defines the category under which this node will appear in the ComfyUI "Add Node" menu.
+ # Using "EmAySee/" as a prefix helps organize your custom nodes.
CATEGORY = "EmAySee/File Utilities"
def pick_lora_file(self, lora_name):
@@ -58,60 +58,60 @@ class EmAySee_LoraFilePicker:
It takes the selected LORA filename as input and returns the filename
and its corresponding full file path.
"""
- # If the "None" option is selected in the dropdown, return empty strings.
+ # If the "None" option is selected in the dropdown, return empty strings.
if lora_name == "None":
return ("", "")
full_lora_path = None
- # Attempt to find the full path of the selected LORA file.
- # folder_paths.get_filename_list("loras", return_full_paths=True)
- # returns a list of absolute paths for all LORA files that ComfyUI has discovered.
+ # Attempt to find the full path of the selected LORA file.
+ # folder_paths.get_filename_list("loras", return_full_paths=True)
+ # returns a list of absolute paths for all LORA files that ComfyUI has discovered.
for lora_path in folder_paths.get_filename_list("loras", return_full_paths=True):
- # Check if the base name of the current lora_path matches the selected lora_name,
- # or if the full lora_path ends with the selected lora_name (this handles
- # cases where lora_name might include subdirectories, e.g., "my_folder/my_lora.safetensors").
+ # Check if the base name of the current lora_path matches the selected lora_name,
+ # or if the full lora_path ends with the selected lora_name (this handles
+ # cases where lora_name might include subdirectories, e.g., "my_folder/my_lora.safetensors").
if os.path.basename(lora_path) == lora_name or lora_path.endswith(lora_name):
full_lora_path = lora_path
break
- # Fallback mechanism: If the LORA was not found by direct matching (e.g., due to
- # inconsistencies in how names are reported vs. stored, or new files not yet indexed).
+ # Fallback mechanism: If the LORA was not found by direct matching (e.g., due to
+ # inconsistencies in how names are reported vs. stored, or new files not yet indexed).
if full_lora_path is None:
- # Get all configured base directories for LORAs.
+ # Get all configured base directories for LORAs.
lora_folders = folder_paths.get_folder_paths("loras")
for folder in lora_folders:
- # Construct a potential full path by joining the base folder and the selected LORA name.
+ # Construct a potential full path by joining the base folder and the selected LORA name.
potential_path = os.path.join(folder, lora_name)
- # Verify if this constructed path actually exists and points to a file.
+ # Verify if this constructed path actually exists and points to a file.
if os.path.exists(potential_path) and os.path.isfile(potential_path):
full_lora_path = potential_path
break
- # If after all attempts, the file still cannot be located, print a warning
- # and return empty strings to ensure the workflow doesn't crash.
+ # If after all attempts, the file still cannot be located, print a warning
+ # and return empty strings to ensure the workflow doesn't crash.
if full_lora_path is None:
print(f"Warning: LORA file '{lora_name}' not found in known ComfyUI LORA paths.")
return ("", "")
- # Return the selected filename and its successfully determined full path.
+ # Return the selected filename and its successfully determined full path.
return (lora_name, full_lora_path)
-# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover
-# and register your custom nodes. The keys are internal identifiers, and the
-# values are the Python classes that define your nodes.
+# NODE_CLASS_MAPPINGS is a mandatory dictionary that ComfyUI uses to discover
+# and register your custom nodes. The keys are internal identifiers, and the
+# values are the Python classes that define your nodes.
NODE_CLASS_MAPPINGS = {
"EmAySee_LoraFilePicker": EmAySee_LoraFilePicker,
}
-# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides
-# more user-friendly names for your nodes in the ComfyUI interface.
-# This name will appear in the "Add Node" menu.
+# NODE_DISPLAY_NAME_MAPPINGS is an optional dictionary that provides
+# more user-friendly names for your nodes in the ComfyUI interface.
+# This name will appear in the "Add Node" menu.
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_LoraFilePicker": "EmAySee Lora File Picker",
}
-# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS).
-# For this node, it's not strictly necessary, but it's a common practice to include it
-# if you plan to extend the node with a custom UI later.
-# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS
\ No newline at end of file
+# WEB_DIRECTORY can be defined if your node has custom web components (JavaScript/CSS).
+# For this node, it's not strictly necessary, but it's a common practice to include it
+# if you plan to extend the node with a custom UI later.
+# WEB_DIRECTORY = "./web" # Uncomment and create a 'web' folder if needed for JS/CSS
\ No newline at end of file
diff --git a/py/emaysee_multiplier_node.py b/py/emaysee_multiplier_node.py
index 6d06bee..e7481f3 100755
--- a/py/emaysee_multiplier_node.py
+++ b/py/emaysee_multiplier_node.py
@@ -1,4 +1,4 @@
-import torch # Often needed for ComfyUI nodes
+import torch # Often needed for ComfyUI nodes
class EmAySee_MultiplierNode:
"""
@@ -9,36 +9,36 @@ class EmAySee_MultiplierNode:
def INPUT_TYPES(s):
return {
"required": {
- "multiplier_slider": ("FLOAT", {"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.01}), # Slider input
- "integer_field_1": ("INT", {"default": 10, "min": -10000, "max": 10000}), # First integer input field
- "integer_field_2": ("INT", {"default": 20, "min": -10000, "max": 10000}), # Second integer input field
+ "multiplier_slider": ("FLOAT", {"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.01}), # Slider input
+ "integer_field_1": ("INT", {"default": 10, "min": -10000, "max": 10000}), # First integer input field
+ "integer_field_2": ("INT", {"default": 20, "min": -10000, "max": 10000}), # Second integer input field
}
}
- RETURN_TYPES = ("INT", "INT",) # Two integer outputs
- RETURN_NAMES = ("multiplied_output_1", "multiplied_output_2",) # Names for the outputs
- CATEGORY = "EmAySee_Utils" # Category in the ComfyUI menu
- TITLE = "EmAySee Multiplier Node" # Title displayed on the node
+ RETURN_TYPES = ("INT", "INT",) # Two integer outputs
+ RETURN_NAMES = ("multiplied_output_1", "multiplied_output_2",) # Names for the outputs
+ CATEGORY = "EmAySee_Utils" # Category in the ComfyUI menu
+ TITLE = "EmAySee Multiplier Node" # Title displayed on the node
- FUNCTION = "EmAySee_calculate_multiplication" # The method that will be executed
+ FUNCTION = "EmAySee_calculate_multiplication" # The method that will be executed
def EmAySee_calculate_multiplication(self, multiplier_slider, integer_field_1, integer_field_2):
"""
Calculates the multiplication of each integer field by the slider value.
"""
- # Perform the multiplication and cast to integer for the output
+ # Perform the multiplication and cast to integer for the output
output_1 = int(integer_field_1 * multiplier_slider)
output_2 = int(integer_field_2 * multiplier_slider)
- # Return the results as a tuple
+ # Return the results as a tuple
return (output_1, output_2,)
-# Mapping of node class name to the class
+# Mapping of node class name to the class
NODE_CLASS_MAPPINGS = {
"EmAySee_MultiplierNode": EmAySee_MultiplierNode
}
-# Mapping of node class name to the display name in the UI
+# Mapping of node class name to the display name in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
"EmAySee_MultiplierNode": "EmAySee Multiplier Node"
}