Add files via upload

This commit is contained in:
FaberVS
2025-04-29 15:48:55 +03:00
committed by GitHub
parent eddaa3cf2a
commit 87f94a67eb
13 changed files with 13592 additions and 114 deletions
+672 -107
View File
@@ -1,107 +1,672 @@
import comfy.sd
import comfy.samplers # For accessing lists of SAMPLERS and SCHEDULERS
import folder_paths
import json
import inspect # For type verification
# Define a custom type for "pipe" for better readability
PIPE_TYPE_NAME = "PARAMS_PIPE"
# --- Node 1: ModelParamsPipeNode ---
class ModelParamsPipeNode:
@classmethod
def INPUT_TYPES(cls):
samplers = comfy.samplers.KSampler.SAMPLERS
schedulers = comfy.samplers.KSampler.SCHEDULERS
return {
"required": {
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.1}),
"sampler_name": (samplers,),
"scheduler": (schedulers,),
"positive_tag": ("STRING", {"multiline": True, "default": ""}),
"negative_tag": ("STRING", {"multiline": True, "default": ""}),
"clip_skip": ("INT", {"default": -2, "min": -12, "max": 0, "step": 1}),
},
"optional": {
"vae_override": ("VAE",),
}
}
RETURN_TYPES = ("MODEL", "CLIP", "VAE",
"INT", "FLOAT", "STRING", "STRING", "STRING", "STRING",
PIPE_TYPE_NAME)
RETURN_NAMES = ("MODEL", "CLIP", "VAE",
"steps", "cfg", "sampler_name", "scheduler",
"positive_tag", "negative_tag",
"params_pipe")
FUNCTION = "process_to_pipe"
CATEGORY = "MultiModel"
def process_to_pipe(self, ckpt_name, steps, cfg, sampler_name, scheduler,
positive_tag, negative_tag, clip_skip, vae_override=None):
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
model = out[0]
clip = out[1]
vae_from_checkpoint = out[2]
clip_skipped = clip.clone()
clip_skipped.clip_layer(clip_skip)
final_vae = vae_override if vae_override is not None else vae_from_checkpoint
combined_params = {
"ckpt_name": ckpt_name, "steps": steps, "cfg": cfg,
"sampler_name": str(sampler_name), "scheduler": str(scheduler),
"positive_tag": positive_tag.strip() if isinstance(positive_tag, str) else positive_tag,
"negative_tag": negative_tag.strip() if isinstance(negative_tag, str) else negative_tag,
"clip_skip": clip_skip
}
pipe_data = (model, clip_skipped, final_vae, combined_params)
return (model, clip_skipped, final_vae, steps, cfg, str(sampler_name), str(scheduler),
positive_tag, negative_tag, pipe_data)
# --- Node 2: ParamsPipeUnpack ---
class ParamsPipeUnpack:
@classmethod
def INPUT_TYPES(cls):
return { "required": { "params_pipe": (PIPE_TYPE_NAME,), } }
RETURN_TYPES = ("MODEL", "CLIP", "VAE", "INT", "FLOAT",
comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS,
"STRING", "STRING", PIPE_TYPE_NAME)
RETURN_NAMES = ("MODEL", "CLIP", "VAE", "steps", "cfg",
"sampler_name", "scheduler",
"positive_tag", "negative_tag", "params_pipe")
FUNCTION = "unpack_pipe"
CATEGORY = "MultiModel"
def unpack_pipe(self, params_pipe):
if not isinstance(params_pipe, tuple) or len(params_pipe) != 4:
raise ValueError(f"Unpack: Expected PARAMS_PIPE tuple of length 4, but got: {type(params_pipe)}")
model, clip, vae, params_dict = params_pipe
if not isinstance(params_dict, dict):
raise ValueError(f"Unpack: Expected dict as the 4th element in PARAMS_PIPE, but got: {type(params_dict)}")
steps = params_dict.get("steps")
cfg = params_dict.get("cfg")
sampler_name = params_dict.get("sampler_name")
scheduler = params_dict.get("scheduler")
positive_tag = params_dict.get("positive_tag")
negative_tag = params_dict.get("negative_tag")
return (model, clip, vae, steps, cfg, sampler_name, scheduler,
positive_tag, negative_tag, params_pipe)
# --- Registration for only the first two nodes ---
NODE_CLASS_MAPPINGS = {
"ModelParamsPipeNode": ModelParamsPipeNode,
"ParamsPipeUnpack": ParamsPipeUnpack
}
# --- Display names for only the first two nodes ---
NODE_DISPLAY_NAME_MAPPINGS = {
"ModelParamsPipeNode": "Model Parameters Pipe Hub",
"ParamsPipeUnpack": "Unpack Parameters Pipe"
}
# Loading message
print("✅ Loaded custom nodes (Reverted): ModelParamsPipeNode, ParamsPipeUnpack ▪B ▪B ▪С")
import comfy.sd
import comfy.samplers # For accessing lists of SAMPLERS and SCHEDULERS
import comfy.sample
import folder_paths
import json
import inspect # For type verification
import torch
import comfy.utils # Потрібен для PROGRESS_BAR_ENABLED
# import comfy.latent_preview as latent_preview # Видалено
import comfy.model_management # Залишаємо, може бути потрібен для інших нод?
import os # Додано для роботи з шляхами
from server import PromptServer # Додано
from aiohttp import web # Додано
import git
from typing import Any, Dict
import yaml
# Проксі для любого типу
class AlwaysEqualProxy(str):
def __eq__(self, _):
return True
def __ne__(self, _):
return False
# Ревізія ComfyUI для lazy_options
comfy_ui_revision = None
def get_comfyui_revision() -> Any:
try:
repo = git.Repo(os.path.dirname(folder_paths.__file__))
return len(list(repo.iter_commits('HEAD')))
except:
return "Unknown"
def compare_revision(num: int) -> bool:
global comfy_ui_revision
if comfy_ui_revision is None:
comfy_ui_revision = get_comfyui_revision()
return comfy_ui_revision == "Unknown" or int(comfy_ui_revision) >= num
WEB_DIRECTORY = "js" # Додано для JS файлу
MAX_FLOW_NUM = 10
lazy_options = {"lazy": True}
any_type = AlwaysEqualProxy("*")
# Define a custom type for "pipe" for better readability
PIPE_TYPE_NAME = "PARAMS_PIPE"
# IMG2TXT_TYPE_NAME = "IMG2TXT_STRING" # Видалено
# --- Helper Functions for Styles ---
def get_styles_dir():
# Визначаємо шлях до папки Styles відносно поточного файлу
return os.path.join(os.path.dirname(__file__), "Styles")
def get_style_files():
styles_dir = get_styles_dir()
if not os.path.isdir(styles_dir):
return ["None"]
files = [f for f in os.listdir(styles_dir) if (f.endswith('.json') or f.endswith('.yaml') or f.endswith('.yml')) and os.path.isfile(os.path.join(styles_dir, f))]
return ["None"] + sorted(files)
def get_style_names(style_filename):
# Прибираємо перевірку на "None" тут, бо API endpoint не повинен викликатися для "None"
# Однак, якщо він все ж викликається, повернемо порожній список.
if style_filename is None or style_filename == "None":
return []
styles_dir = get_styles_dir()
filepath = os.path.join(styles_dir, style_filename)
names = [] # Ініціалізуємо порожнім списком, без "None"
try:
if os.path.isfile(filepath):
if filepath.endswith('.yaml') or filepath.endswith('.yml'):
with open(filepath, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
else:
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
if isinstance(data, list):
# Додаємо імена, пропускаючи порожні або None
names.extend([item.get("name") for item in data if isinstance(item, dict) and item.get("name")])
except Exception as e:
print(f"Error loading style names from {filepath}: {e}")
return [] # Повертаємо порожній список при помилці
# Видаляємо дублікати (якщо є) і сортуємо. НЕ додаємо "None".
unique_names = sorted(list(set(n for n in names if n))) # Додаткова фільтрація None/empty
return unique_names
# Оновлена функція common_ksampler без latent_preview
def common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0, disable_noise=False, start_step=None, last_step=None, force_full_denoise=False):
latent_image = latent["samples"]
latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image)
if disable_noise:
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
else:
batch_inds = latent["batch_index"] if "batch_index" in latent else None
noise = comfy.sample.prepare_noise(latent_image, seed, batch_inds)
noise_mask = None
if "noise_mask" in latent:
noise_mask = latent["noise_mask"]
# callback = latent_preview.prepare_callback(model, steps) # Видалено рядок з колбеком
callback = None # Встановлюємо колбек в None
disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED
samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative, latent_image,
denoise=denoise, disable_noise=disable_noise, start_step=start_step, last_step=last_step,
force_full_denoise=force_full_denoise, noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=seed)
out = latent.copy()
out["samples"] = samples
return (out, )
# --- Node 1: ModelParamsPipeNode ---
class ModelParamsPipeNode:
@classmethod
def INPUT_TYPES(cls):
samplers = comfy.samplers.KSampler.SAMPLERS
schedulers = comfy.samplers.KSampler.SCHEDULERS
return {
"required": {
# Переносимо теги на початок
"positive_tag": ("STRING", {"multiline": True, "default": ""}),
"negative_tag": ("STRING", {"multiline": True, "default": ""}),
# Решта полів
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.1}),
"sampler_name": (samplers,),
"scheduler": (schedulers,),
"clip_skip": ("INT", {"default": -2, "min": -12, "max": 0, "step": 1}),
},
"optional": {
"vae_override": ("VAE",),
}
}
RETURN_TYPES = ("MODEL", "CLIP", "VAE",
"INT", "FLOAT", comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS,
"STRING", "STRING",
PIPE_TYPE_NAME)
RETURN_NAMES = ("MODEL", "CLIP", "VAE",
"steps", "cfg", "sampler_name", "scheduler",
"positive_tag", "negative_tag",
"params_pipe")
FUNCTION = "process_to_pipe"
CATEGORY = "MultiModel"
def process_to_pipe(self, ckpt_name, steps, cfg, sampler_name, scheduler,
positive_tag, negative_tag, clip_skip, vae_override=None):
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
model = out[0]
clip = out[1]
vae_from_checkpoint = out[2]
clip_skipped = clip.clone()
clip_skipped.clip_layer(clip_skip)
final_vae = vae_override if vae_override is not None else vae_from_checkpoint
# Safely handle positive_tag and negative_tag even if they're None
safe_positive_tag = "" if positive_tag is None else positive_tag.strip()
safe_negative_tag = "" if negative_tag is None else negative_tag.strip()
# Store the original sampler_name and scheduler objects
combined_params = {
"ckpt_name": ckpt_name,
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"positive_tag": safe_positive_tag,
"negative_tag": safe_negative_tag,
"clip_skip": clip_skip
}
pipe_data = (model, clip_skipped, final_vae, combined_params)
# Convert sampler_name and scheduler to string only for display purposes
return (model, clip_skipped, final_vae, steps, cfg,
sampler_name, scheduler,
safe_positive_tag, safe_negative_tag, pipe_data)
# --- Node 2: ParamsPipeUnpack ---
class ParamsPipeUnpack:
@classmethod
def INPUT_TYPES(cls):
return { "required": { "params_pipe": (PIPE_TYPE_NAME,), } }
# Змінюємо типи повернення для sampler_name та scheduler
RETURN_TYPES = ("MODEL", "CLIP", "VAE", "INT", "FLOAT",
comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS,
"STRING", "STRING", PIPE_TYPE_NAME)
RETURN_NAMES = ("MODEL", "CLIP", "VAE", "steps", "cfg",
"sampler_name", "scheduler",
"positive_tag", "negative_tag", "params_pipe")
FUNCTION = "unpack_pipe"
CATEGORY = "MultiModel"
def unpack_pipe(self, params_pipe):
if not isinstance(params_pipe, tuple) or len(params_pipe) != 4:
raise ValueError(f"Unpack: Expected PARAMS_PIPE tuple of length 4, but got: {type(params_pipe)}")
model, clip, vae, params_dict = params_pipe
if not isinstance(params_dict, dict):
raise ValueError(f"Unpack: Expected dict as the 4th element in PARAMS_PIPE, but got: {type(params_dict)}")
# Get parameters with proper type checking
steps = params_dict.get("steps", 20)
cfg = params_dict.get("cfg", 7.0)
sampler_name = params_dict.get("sampler_name", "euler_ancestral") # Отримуємо оригінальний об'єкт
scheduler = params_dict.get("scheduler", "normal") # Отримуємо оригінальний об'єкт
positive_tag = params_dict.get("positive_tag", "")
negative_tag = params_dict.get("negative_tag", "")
# Повертаємо оригінальні об'єкти, а не їх рядкові представлення
return (model, clip, vae, steps, cfg,
sampler_name, scheduler, # Без конвертації в str
positive_tag, negative_tag, params_pipe)
# --- Node 3: ListSelectorNode ---
class ListSelectorNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
# Input for multiline text where each line is a list item
"list_items": ("STRING", {"multiline": True, "default": "Item 1\nItem 2\nItem 3"}),
# Input for selecting the item's number (1-based index)
"index": ("INT", {"default": 1, "min": 1, "max": 10000, "step": 1}),
}
}
# Return types: selected string and its number
RETURN_TYPES = ("STRING", "INT")
# Output names
RETURN_NAMES = ("selected_item", "selected_index")
FUNCTION = "get_item_by_index"
CATEGORY = "MultiModel" # Category for the node
def get_item_by_index(self, list_items, index):
# Split the input text into lines
lines = list_items.splitlines()
# Filter out empty lines and trim unnecessary spaces
items = [line.strip() for line in lines if line.strip()]
selected_item_str = ""
selected_item_index = 1 # Default to the first item
if not items:
# If the list is empty after filtering
print("Warning: ListSelectorNode - Input list is empty.")
selected_item_str = ""
selected_item_index = 1 # Nothing to select, return index 1
else:
# Adjust the 1-based input index to 0-based for Python list access
zero_based_index = index - 1
# Check if the index is within bounds
# If index is too small (<0), use 0
if zero_based_index < 0:
zero_based_index = 0
# If index is too large, use the index of the last item
elif zero_based_index >= len(items):
zero_based_index = len(items) - 1
# Get the selected item
selected_item_str = items[zero_based_index]
# Determine the actual 1-based index of the selected item
selected_item_index = zero_based_index + 1
# Return the selected string and its 1-based index
return (selected_item_str, selected_item_index)
class DenoiseSelector:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
}
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("denoise",)
FUNCTION = "select_denoise"
CATEGORY = "MultiModel"
def select_denoise(self, denoise):
return (denoise,)
# --- Наша існуюча нода KSamplerPipeNode ---
class KSamplerPipeNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"params_pipe": (PIPE_TYPE_NAME,),
# "latent": ("LATENT",), # Переміщено до optional
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
},
"optional": {
"latent": ("LATENT",), # Тепер опціональний
"image_override": ("IMAGE",)
}
}
RETURN_TYPES = (PIPE_TYPE_NAME, "IMAGE")
RETURN_NAMES = ("params_pipe", "image")
FUNCTION = "sample"
CATEGORY = "MultiModel"
def sample(self, params_pipe, positive, negative, seed, denoise, latent=None, image_override=None):
if latent is not None and image_override is not None:
raise ValueError("Both 'latent' and 'image_override' inputs are connected. Please connect only one.")
if latent is None and image_override is None:
raise ValueError("Either 'latent' or 'image_override' input must be provided to KSamplerPipeNode.")
if not isinstance(params_pipe, tuple) or len(params_pipe) != 4:
raise ValueError(f"KSamplerPipeNode: Expected PARAMS_PIPE tuple of length 4, but got: {type(params_pipe)}")
model, clip, vae, params_dict = params_pipe
if not isinstance(params_dict, dict):
raise ValueError(f"KSamplerPipeNode: Expected dict as the 4th element in PARAMS_PIPE, but got: {type(params_dict)}")
# Отримуємо параметри з params_pipe
steps = params_dict.get("steps", 20)
cfg = params_dict.get("cfg", 7.0)
sampler_name = params_dict.get("sampler_name", "euler_ancestral")
scheduler = params_dict.get("scheduler", "normal")
# Визначаємо, який латент використовувати
if image_override is not None:
print("KSamplerPipeNode: Using image_override input.")
if vae is None:
raise ValueError("VAE is required to encode image_override, but it's missing in the params_pipe.")
encoded_samples = vae.encode(image_override[:,:,:,:3])
latent_input = {"samples": encoded_samples}
else: # image_override is None, тому використовуємо latent (ми вже перевірили, що він не None)
print("KSamplerPipeNode: Using latent input.")
latent_input = latent
# Використовуємо common_ksampler
latent_result = common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_input, denoise=denoise)[0]
# Декодуємо зображення
if vae is None:
raise ValueError("VAE is required for decoding the result, but it's missing in the params_pipe.")
image = vae.decode(latent_result["samples"])
return (params_pipe, image)
# --- Нова нода PromptBuilderNode ---
class PromptBuilderNode:
@classmethod
def INPUT_TYPES(cls):
style_files = get_style_files() # Ця функція все ще повертає список з "None"
# Ми більше не використовуємо get_style_names для початкового списку тут
return {
"required": {
"params_pipe": (PIPE_TYPE_NAME,),
"positive_base": ("STRING", {"multiline": True, "default": ""}),
"negative_base": ("STRING", {"multiline": True, "default": ""}),
},
"optional": {
"from_IMG2TXT": ("STRING", {"multiline": True, "default": "", "forceInput": True}),
"style_file": (style_files, ),
# Змінюємо default на порожній рядок, оскільки "None" більше не буде опцією
"style_name": ("STRING", {"default": "", "multiline": False}),
}
}
RETURN_TYPES = ("STRING", "STRING", "CONDITIONING", "CONDITIONING")
RETURN_NAMES = ("positive_text", "negative_text", "positive_prompt", "negative_prompt")
FUNCTION = "build_prompts"
CATEGORY = "MultiModel"
@classmethod
def IS_CHANGED(cls, *args, **kwargs):
# Використовуємо *args, **kwargs, щоб уникнути помилок з аргументами
return float("NaN")
def build_prompts(self, params_pipe, positive_base, negative_base, from_IMG2TXT=None, style_file="None", style_name="None"):
# 1. Розпаковка params_pipe
if not isinstance(params_pipe, tuple) or len(params_pipe) != 4:
raise ValueError("PromptBuilderNode: Invalid params_pipe structure.")
model, clip, vae, params_dict = params_pipe
if not isinstance(params_dict, dict):
raise ValueError("PromptBuilderNode: Invalid params_dict in params_pipe.")
if clip is None:
raise ValueError("PromptBuilderNode: CLIP object is missing in the params_pipe.")
# 2. Отримання тегів з pipe
positive_tags = params_dict.get("positive_tag", "").strip()
negative_tags = params_dict.get("negative_tag", "").strip()
# Очищення вхідного тексту з вузла
safe_from_img2txt = "" if from_IMG2TXT is None else from_IMG2TXT.strip()
safe_positive_base = "" if positive_base is None else positive_base.strip()
safe_negative_base = "" if negative_base is None else negative_base.strip()
style_prompt_template = "{prompt}" # Default template
style_negative_prompt = "" # Default negative from style
# 3. Обробка стилю (отримання шаблонів)
if style_file != "None" and style_name != "None":
styles_dir = get_styles_dir()
filepath = os.path.join(styles_dir, style_file)
try:
if os.path.isfile(filepath):
if filepath.endswith('.yaml') or filepath.endswith('.yml'):
with open(filepath, 'r', encoding='utf-8') as f:
styles_data = yaml.safe_load(f)
else:
with open(filepath, 'r', encoding='utf-8') as f:
styles_data = json.load(f)
if isinstance(styles_data, list):
selected_style = next((s for s in styles_data if isinstance(s, dict) and s.get("name") == style_name), None)
if selected_style:
style_prompt_template = selected_style.get("prompt", "{prompt}")
style_negative_prompt = selected_style.get("negative_prompt", "")
else:
print(f"Warning: Style '{style_name}' not found in {style_file}.")
except Exception as e:
print(f"Error processing style file {style_file}: {e}")
# 4. Формування позитивного тексту (НОВИЙ ПОРЯДОК)
# Спочатку збираємо теги, базу та img2txt
core_positive_parts = []
if positive_tags:
core_positive_parts.append(positive_tags)
if safe_positive_base:
core_positive_parts.append(safe_positive_base)
if safe_from_img2txt:
core_positive_parts.append(safe_from_img2txt)
core_prompt = ". ".join(filter(None, core_positive_parts))
# Тепер вставляємо зібраний core_prompt у шаблон стилю
final_positive_text = style_prompt_template.replace("{prompt}", core_prompt) if core_prompt else style_prompt_template.replace("{prompt}", "")
# Додатково очистимо кінцевий результат від зайвих пробілів
final_positive_text = final_positive_text.strip()
# 5. Формування негативного тексту (ЗМІНЕНО ПОРЯДОК)
# Спочатку збираємо базу і стиль
negative_base_and_style_parts = []
if safe_negative_base:
negative_base_and_style_parts.append(safe_negative_base)
if style_negative_prompt:
negative_base_and_style_parts.append(style_negative_prompt)
base_and_style_negative = ". ".join(filter(None, negative_base_and_style_parts))
# Тепер збираємо фінальний негативний, ставлячи теги першими
final_negative_parts = []
if negative_tags:
final_negative_parts.append(negative_tags)
if base_and_style_negative:
final_negative_parts.append(base_and_style_negative)
final_negative_text = ". ".join(filter(None, final_negative_parts))
# 6 & 7. Кодування промптів (залишається без змін)
tokens_positive = clip.tokenize(final_positive_text)
cond_positive, pooled_positive = clip.encode_from_tokens(tokens_positive, return_pooled=True)
tokens_negative = clip.tokenize(final_negative_text)
cond_negative, pooled_negative = clip.encode_from_tokens(tokens_negative, return_pooled=True)
# 8. Повернення результатів (залишається без змін)
return (final_positive_text, final_negative_text,
[[cond_positive, {"pooled_output": pooled_positive}]],
[[cond_negative, {"pooled_output": pooled_negative}]])
# --- Новий вузол-байпасер для ModelParamsPipeNode ---
class ActiveModel:
# Цьому вузлу не потрібні входи чи виходи, визначені в Python,
# оскільки вся логіка відбувається в JavaScript.
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = ()
FUNCTION = "do_nothing" # Потрібно вказати функцію, навіть якщо вона нічого не робить
OUTPUT_NODE = True # Вказує, що вузол може не мати виходів або має особливу логіку
CATEGORY = "MultiModel" # Змінюємо категорію на основну категорію
def do_nothing(self):
# Ця функція ніколи не буде викликана, якщо JS обробляє дії,
# але вона потрібна для реєстрації.
return ()
# --- API Endpoint for Dynamic Styles ---
@PromptServer.instance.routes.post('/multi_model/get_style_names')
async def get_style_names_endpoint(request):
# print("--- API Endpoint /multi_model/get_style_names called ---") # Keep this commented for less verbose logs
try:
json_data = await request.json()
filename = json_data.get('filename')
if filename is None: # Check explicitly for None, as empty string might be valid (though unlikely here)
print("API Error: Filename not provided in request")
return web.Response(status=400, text="Filename not provided")
style_names = get_style_names(filename)
# print(f"Returning style names for {filename}: {style_names}") # Keep commented
return web.json_response({"style_names": style_names})
except json.JSONDecodeError as json_err:
try:
raw_body = await request.read()
body_text = raw_body.decode('utf-8', errors='ignore')
except Exception:
body_text = "[Could not read body]"
print(f"API Error: Invalid JSON received. Body: {body_text}. Error: {json_err}")
return web.Response(status=400, text=f"Invalid JSON received: {json_err}")
except Exception as e:
print(f"API Error in get_style_names_endpoint: {e}")
import traceback
print(traceback.format_exc())
return web.Response(status=500, text=f"Internal server error: {e}")
# --- Helper definitions for MySwitchIndex ---
class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
def __ne__(self, __value: object) -> bool:
return False
class FlexibleOptionalInputType(dict):
"""A special class to make flexible nodes that pass data to our python handlers.
Enables both flexible/dynamic input types (like for Any Switch) or a dynamic number of inputs
(like for Any Switch, Context Switch, Context Merge, Power Lora Loader, etc).
"""
def __init__(self, type):
self.type = type
def __getitem__(self, key):
# Allow specific named inputs if needed, otherwise default to the flexible type
# Returning the flexible type tuple for __contains__ to work seamlessly
return (self.type,)
def __contains__(self, key):
# We accept any key starting with 'input_'
return key.startswith('input_')
any_type_switch = AnyType("*") # Using a different variable name to avoid conflicts
def is_context_empty(ctx):
"""Checks if the provided ctx is None or contains just None values."""
return not ctx or all(v is None for v in ctx.values())
def is_none(value):
"""Checks if a value is none. Adapted from rgthree."""
if value is not None:
# Handle rgthree context specifically if needed, or remove if not applicable
if isinstance(value, dict) and 'model' in value and 'clip' in value:
return is_context_empty(value)
return value is None
# --- Node: MySwitchIndex ---
class MySwitchIndex:
"""
A node that takes multiple inputs and outputs the first non-empty one
along with its index (firstActive mode) OR outputs the input at a specific index.
Inputs are dynamically managed by the frontend.
"""
# Set NAME and CATEGORY directly
NAME = "Multi Model Switch"
CATEGORY = "MultiModel"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"mode": (["firstActive", "index"], ),
# Оновлюємо min та default для 1-базованого індексу
"index": ("INT", {"default": 1, "min": 1, "max": 999}),
},
"optional": FlexibleOptionalInputType(any_type_switch),
}
# Output the selected value and its index
RETURN_TYPES = (any_type_switch, "INT",)
RETURN_NAMES = ("selected_value", "selected_index",)
FUNCTION = "switch_index"
# Змінюємо сигнатуру: приймаємо всі входи через **kwargs
def switch_index(self, **kwargs):
"""
Selects and returns an input value and its 1-based index based on the selected mode.
- firstActive: Returns the first non-none input.
- index: Returns the input at the specified 1-based index from the widget.
Returns 0 for selected_index if no active input is found.
"""
# Витягуємо mode та index_from_widget з kwargs
mode = kwargs.get("mode", "firstActive") # За замовчуванням firstActive
# Отримуємо 1-базований індекс з віджета, за замовчуванням 1
index_from_widget = kwargs.get("index", 1)
selected_value = None
# Внутрішній індекс буде 0-базованим, або -1 якщо не знайдено
internal_selected_index = -1
if mode == "firstActive":
input_items = []
# Проходимо по всіх kwargs, щоб знайти динамічні входи
for key, value in kwargs.items():
if key.startswith("input_"):
try:
# Assume 1-based index in name from frontend, store 0-based
key_index_0_based = int(key.split('_')[-1]) - 1
input_items.append({'index': key_index_0_based, 'key': key, 'value': value})
except ValueError:
print(f"[MultiModelSwitch] Warning: Could not parse index from key '{key}'")
continue
# Sort by index parsed from the key name
input_items.sort(key=lambda item: item['index'])
for item in input_items:
if not is_none(item['value']):
selected_value = item['value']
internal_selected_index = item['index'] # Store the 0-based index
break
elif mode == "index":
# index_from_widget тепер 1-базований
target_key = f"input_{index_from_widget}" # Ключ будується з 1-базованого індексу
# Перевіряємо наявність ключа та значення в kwargs
if target_key in kwargs:
input_value = kwargs[target_key]
if not is_none(input_value):
selected_value = input_value
# Зберігаємо внутрішній 0-базований індекс
internal_selected_index = index_from_widget - 1
else:
print(f"[MultiModelSwitch] Warning: Unknown mode '{mode}'")
# Конвертуємо 0-базований внутрішній індекс в 1-базований для виходу, або 0 якщо не знайдено
output_index = internal_selected_index + 1 if internal_selected_index != -1 else 0
return (selected_value, output_index,)
# --- Node Mappings ---
NODE_CLASS_MAPPINGS = {
"ModelParamsPipe": ModelParamsPipeNode,
"ParamsPipeUnpack": ParamsPipeUnpack,
"ListSelector": ListSelectorNode,
"DenoiseSelector": DenoiseSelector,
"KSamplerPipe": KSamplerPipeNode,
"PromptBuilder": PromptBuilderNode,
"ActiveModel": ActiveModel,
"MySwitchIndex": MySwitchIndex, # Register the new node
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ModelParamsPipe": "Model Parameters Pipe",
"ParamsPipeUnpack": "Unpack Parameters Pipe",
"ListSelector": "List Selector",
"DenoiseSelector": "Denoise Selector",
"KSamplerPipe": "KSampler (Pipe)",
"PromptBuilder": "Prompt Builder",
"ActiveModel": "Active Model",
"MySwitchIndex": "Multi Model Switch", # Оновлено відображуване ім'я
}
print("✅ Loaded ModelParamsNode.py with all nodes (*^_^*)")
+95
View File
@@ -0,0 +1,95 @@
<h1 align="center">
MultiModel1 for ComfyUI
<br>
<sub><sup><i>Flexible multi-model workflows, prompt engineering, and style management for ComfyUI</i></sup></sub>
<br>
</h1>
<p align="center">
<a href="#️-the-nodes">The Nodes</a> &nbsp; | &nbsp; <a href="#-styles">Styles</a> &nbsp; | &nbsp; <a href="#-installation">Installation</a> &nbsp; | &nbsp; <a href="#-example-usage">Example Usage</a>
</p>
<hr>
A collection of nodes and utilities to make working with multiple models, custom parameters, and prompt styles in ComfyUI easier, faster, and more flexible.
You are welcome to use and adapt them for your own workflows!
---
# ✴️ The Nodes
## ModelParamsPipeNode
> Create a "pipe" containing the selected model, CLIP, VAE, and all generation parameters (steps, CFG, sampler, scheduler, tags, etc).
> Makes it easy to pass model settings between nodes.
## ParamsPipeUnpack
> Unpack all parameters and objects from a pipe: model, CLIP, VAE, steps, CFG, sampler, scheduler, tags.
## KSamplerPipeNode
> A sampler node that takes a pipe, conditioning, seed, denoise, and either latent or image_override.
> Works with any model passed via the pipe.
## PromptBuilderNode
> Build final positive/negative texts and CLIP conditioning, taking into account styles (from JSON), base texts, tags, and img2txt.
> Supports custom styles for advanced prompt engineering.
## ListSelectorNode
> Select an item from a list by index.
> Useful for UI and dynamic workflows.
## DenoiseSelector
> Simple node for selecting the denoise value.
## DualTextInput
> Two text fields for positive and negative prompts.
## ActiveModel
> Dummy node for JavaScript-side integration (client logic).
## MySwitchIndex (Multi Model Switch)
> Вузол для вибору одного з декількох вхідних сигналів. Працює у двох режимах:
> <ul>
> <li><b>firstActive</b> — повертає перший не-порожній (не None) вхід та його індекс.</li>
> <li><b>index</b> — повертає вхід із заданим індексом (1-базований), якщо він не порожній.</li>
> </ul>
> Динамічно керує кількістю входів через UI. Повертає обране значення та його індекс (або 0, якщо не знайдено).
> <br>
> <b>Корисно для мульти-модельних пайплайнів, умовної маршрутизації, вибору активного результату.</b>
---
# 🎨 Styles
- Styles are stored in the `Styles` folder inside this package.
- For dynamic style selection, the API endpoint `/multi_model/get_style_names` is used.
- A style is a JSON file containing an array of objects, each with `name`, `prompt`, and `negative_prompt` fields.
---
# ⚡ Installation
## Recommended: via ComfyUI-Manager
1. Open ComfyUI-Manager.
2. Search for `MultiModel1` (or your package name) and click "Install".
## Manual Install
1. Copy the `MultiModel1` folder to `ComfyUI/custom_nodes/`.
2. Restart ComfyUI.
---
# 🚀 Example Usage
1. Add `ModelParamsPipeNode` to select your model and parameters.
2. Build prompts using `PromptBuilderNode` (optionally connect styles).
3. Use `KSamplerPipeNode` to generate images.
4. For multi-model pipelines, add several pipes and switch between them using `MySwitchIndex`.
---
# 🙏 Credits
- [ComfyUI](https://github.com/comfyanonymous/ComfyUI) – The base for node development and workflow.
- Authors and contributors of this package.
---
**Feedback, bug reports, and suggestions are welcome via GitHub Issues or your favorite chat!**
+2631
View File
File diff suppressed because it is too large Load Diff
+7353
View File
File diff suppressed because it is too large Load Diff
+332
View File
@@ -0,0 +1,332 @@
[
{
"name": "3d-model",
"prompt": "professional 3d model of {prompt}. octane render, highly detailed, volumetric, dramatic lighting",
"negative_prompt": "ugly, deformed, noisy, low poly, blurry, painting"
},
{
"name": "abstract",
"prompt": "abstract style of {prompt}. non-representational, colors and shapes, expression of feelings, imaginative, highly detailed",
"negative_prompt": "realistic, photographic, figurative, concrete"
},
{
"name": "analog",
"prompt": "analog film photo of {prompt}. faded film, desaturated, 35mm photo, grainy, vignette, vintage, Kodachrome, Lomography, stained, highly detailed, found footage",
"negative_prompt": "painting, drawing, illustration, glitch, deformed, mutated, cross-eyed, ugly, disfigured"
},
{
"name": "anime",
"prompt": "anime artwork of {prompt}. anime style, key visual, vibrant, studio anime, highly detailed",
"negative_prompt": "photo, deformed, black and white, realism, disfigured, low contrast"
},
{
"name": "arcade",
"prompt": "retro arcade style of {prompt}. 8-bit, pixelated, vibrant, classic video game, old school gaming, reminiscent of 80s and 90s arcade games",
"negative_prompt": "modern, ultra-high resolution, photorealistic, 3D"
},
{
"name": "architectural",
"prompt": "architectural style of {prompt}. clean lines, geometric shapes, minimalist, modern, architectural drawing, highly detailed",
"negative_prompt": "curved lines, ornate, baroque, abstract, grunge"
},
{
"name": "art-deco",
"prompt": "art deco style of {prompt}. geometric shapes, bold colors, luxurious, elegant, decorative, symmetrical, ornate, detailed",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, modernist, minimalist"
},
{
"name": "automotive",
"prompt": "automotive advertisement style of {prompt}. sleek, dynamic, professional, commercial, vehicle-focused, high-resolution, highly detailed",
"negative_prompt": "noisy, blurry, unattractive, sloppy, unprofessional"
},
{
"name": "biomechanics",
"prompt": "biomechanical cyberpunk of {prompt}. cybernetics, human-machine fusion, dystopian, organic meets artificial, dark, intricate, highly detailed",
"negative_prompt": "natural, colorful, deformed, sketch, low contrast, watercolor"
},
{
"name": "child's-drawing",
"prompt": "hand drawing of {prompt}. cute, crayon, pencil, crayola, scribble, sketch, paper, vibrant, colorful, loosely colored, unsophisticated",
"negative_prompt": "cgi, 3D, photoreal, out of focus, glitch, photorealistic, realistic"
},
{
"name": "cinematic",
"prompt": "cinematic film still of {prompt}. shallow depth of field, vignette, highly detailed, high budget, bokeh, cinemascope, moody, epic, gorgeous, film grain, grainy",
"negative_prompt": "anime, cartoon, graphic, text, painting, crayon, graphite, abstract, glitch, deformed, mutated, ugly, disfigured"
},
{
"name": "clay",
"prompt": "play-doh style of {prompt}. sculpture, clay art, centered composition, Claymation, plasticine",
"negative_prompt": "sloppy, messy, grainy, highly detailed, ultra textured, photo"
},
{
"name": "comic-book",
"prompt": "comic of {prompt}. graphic illustration, comic art, graphic novel art, vibrant, highly detailed",
"negative_prompt": "photograph, deformed, glitch, noisy, realistic, stock photo"
},
{
"name": "constructivist",
"prompt": "constructivist style of {prompt}. geometric shapes, bold colors, dynamic composition, propaganda art style",
"negative_prompt": "realistic, photorealistic, low contrast, plain, simple, abstract expressionism"
},
{
"name": "corporate",
"prompt": "corporate branding style of {prompt}. professional, clean, modern, sleek, minimalist, business-oriented, highly detailed",
"negative_prompt": "noisy, blurry, grungy, sloppy, cluttered, disorganized"
},
{
"name": "cubist",
"prompt": "cubist artwork of {prompt}. cubism, geometric shapes, abstract, innovative, revolutionary, picasso",
"negative_prompt": "anime, photorealistic, 35mm film, deformed, glitch, low contrast, noisy"
},
{
"name": "cute",
"prompt": "kawaii style of {prompt}. cute, adorable, brightly colored, cheerful, anime influence, highly detailed",
"negative_prompt": "dark, scary, realistic, monochrome, abstract"
},
{
"name": "cyber-punk",
"prompt": "cyberpunk cityscape of {prompt}. neon lights, dark alleys, skyscrapers, futuristic, vibrant colors, high contrast, highly detailed",
"negative_prompt": "natural, rural, deformed, low contrast, black and white, sketch, watercolor"
},
{
"name": "cybernetic",
"prompt": "biomechanical style of {prompt}. blend of organic and mechanical elements, futuristic, cybernetic, detailed, intricate",
"negative_prompt": "natural, rustic, primitive, organic, simplistic"
},
{
"name": "digital-painting",
"prompt": "concept art of {prompt}. digital artwork, illustrative, painterly, matte painting, highly detailed",
"negative_prompt": "photo, photorealistic, realism, ugly"
},
{
"name": "dreamscape",
"prompt": "dreamscape of {prompt}. surreal, ethereal, dreamy, mysterious, fantasy, highly detailed",
"negative_prompt": "realistic, concrete, ordinary, mundane"
},
{
"name": "dystopian",
"prompt": "dystopian style of {prompt}. bleak, post-apocalyptic, somber, dramatic, highly detailed",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, cheerful, optimistic, vibrant, colorful"
},
{
"name": "expressionist",
"prompt": "expressionist painting of {prompt}. energetic brushwork, bold colors, abstract forms, expressive, emotional",
"negative_prompt": "realistic, photorealistic, low contrast, plain, simple, monochrome"
},
{
"name": "fantasy",
"prompt": "ethereal fantasy concept art of {prompt}. magnificent, celestial, ethereal, painterly, epic, majestic, magical, fantasy art, cover art, dreamy",
"negative_prompt": "photographic, realistic, realism, 35mm film, dslr, cropped, frame, text, deformed, glitch, noise, noisy, off-center, deformed, cross-eyed, closed eyes, bad anatomy, ugly, disfigured, sloppy, duplicate, mutated, black and white"
},
{
"name": "fashion",
"prompt": "fashion editorial style of {prompt}. high fashion, trendy, stylish, editorial, magazine style, professional, highly detailed",
"negative_prompt": "outdated, blurry, noisy, unattractive, sloppy"
},
{
"name": "film-noir",
"prompt": "film noir style of {prompt}. monochrome, high contrast, dramatic shadows, 1940s style, mysterious, cinematic",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, vibrant, colorful"
},
{
"name": "food",
"prompt": "food photography style of {prompt}. appetizing, professional, culinary, high-resolution, commercial, highly detailed",
"negative_prompt": "unappetizing, sloppy, unprofessional, noisy, blurry"
},
{
"name": "food-(gourmet)",
"prompt": "gourmet food photo of {prompt}. soft natural lighting, macro details, vibrant colors, fresh ingredients, glistening textures, bokeh background, styled plating, wooden tabletop, garnished, tantalizing, editorial quality",
"negative_prompt": "cartoon, anime, sketch, grayscale, dull, overexposed, cluttered, messy plate, deformed"
},
{
"name": "futuristic",
"prompt": "futuristic style of {prompt}. sleek, modern, ultramodern, high tech, detailed",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, vintage, antique"
},
{
"name": "glamour",
"prompt": "glamorous photo of {prompt}. high fashion, luxurious, extravagant, stylish, sensual, opulent, elegance, stunning beauty, professional, high contrast, detailed",
"negative_prompt": "ugly, deformed, noisy, blurry, distorted, grainy, sketch, low contrast, dull, plain, modest"
},
{
"name": "gothic",
"prompt": "gothic style of {prompt}. dark, mysterious, haunting, dramatic, ornate, detailed",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, cheerful, optimistic"
},
{
"name": "horror",
"prompt": "horror-themed of {prompt}. eerie, unsettling, dark, spooky, suspenseful, grim, highly detailed",
"negative_prompt": "cheerful, bright, vibrant, light-hearted, cute"
},
{
"name": "hyperreal",
"prompt": "hyperrealistic art of {prompt}. extremely high-resolution details, photographic, realism pushed to extreme, fine texture, incredibly lifelike",
"negative_prompt": "simplified, abstract, unrealistic, impressionistic, low resolution"
},
{
"name": "impressionist",
"prompt": "impressionist painting of {prompt}. loose brushwork, vibrant color, light and shadow play, captures feeling over form",
"negative_prompt": "anime, photorealistic, 35mm film, deformed, glitch, low contrast, noisy"
},
{
"name": "isometric",
"prompt": "isometric style of {prompt}. vibrant, beautiful, crisp, detailed, ultra detailed, intricate",
"negative_prompt": "deformed, mutated, ugly, disfigured, blur, blurry, noise, noisy, realistic, photographic"
},
{
"name": "kirigami",
"prompt": "kirigami representation of {prompt}. 3D, paper folding, paper cutting, Japanese, intricate, symmetrical, precision, clean lines",
"negative_prompt": "painting, drawing, 2D, noisy, blurry, deformed"
},
{
"name": "line-art",
"prompt": "line art drawing of {prompt}. professional, sleek, modern, minimalist, graphic, line art, vector graphics",
"negative_prompt": "anime, photorealistic, 35mm film, deformed, glitch, blurry, noisy, off-center, deformed, cross-eyed, closed eyes, bad anatomy, ugly, disfigured, mutated, realism, realistic, impressionism, expressionism, oil, acrylic"
},
{
"name": "long-exposure",
"prompt": "long exposure photo of {prompt}. Blurred motion, streaks of light, surreal, dreamy, ghosting effect, highly detailed",
"negative_prompt": "static, noisy, deformed, shaky, abrupt, flat, low contrast"
},
{
"name": "lovecraftian",
"prompt": "lovecraftian horror of {prompt}. eldritch, cosmic horror, unknown, mysterious, surreal, highly detailed",
"negative_prompt": "light-hearted, mundane, familiar, simplistic, realistic"
},
{
"name": "low-poly",
"prompt": "low-poly style of {prompt}. low-poly game art, polygon mesh, jagged, blocky, wireframe edges, centered composition",
"negative_prompt": "noisy, sloppy, messy, grainy, highly detailed, ultra textured, photo"
},
{
"name": "luxury",
"prompt": "luxury product style of {prompt}. elegant, sophisticated, high-end, luxurious, professional, highly detailed",
"negative_prompt": "cheap, noisy, blurry, unattractive, amateurish"
},
{
"name": "manga",
"prompt": "manga style of {prompt}. vibrant, high-energy, detailed, iconic, Japanese comic style",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, Western comic style"
},
{
"name": "minimalist",
"prompt": "minimalist style of {prompt}. simple, clean, uncluttered, modern, elegant",
"negative_prompt": "ornate, complicated, highly detailed, cluttered, disordered, messy, noisy"
},
{
"name": "monochrome",
"prompt": "monochrome of {prompt}. black and white, contrast, tone, texture, detailed",
"negative_prompt": "colorful, vibrant, noisy, blurry, deformed"
},
{
"name": "neon-punk",
"prompt": "neonpunk style of {prompt}. cyberpunk, vaporwave, neon, vibes, vibrant, stunningly beautiful, crisp, detailed, sleek, ultramodern, magenta highlights, dark purple shadows, high contrast, cinematic, ultra detailed, intricate, professional",
"negative_prompt": "\"painting\""
},
{
"name": "origami",
"prompt": "origami style of {prompt}. paper art, pleated paper, folded, origami art, pleats, cut and fold, centered composition",
"negative_prompt": "noisy, sloppy, messy, grainy, highly detailed, ultra textured, photo"
},
{
"name": "paper-mache",
"prompt": "paper mache representation of {prompt}. 3D, sculptural, textured, handmade, vibrant, fun",
"negative_prompt": "2D, flat, photo, sketch, digital art, deformed, noisy, blurry"
},
{
"name": "papercraft",
"prompt": "papercraft style of {prompt}. mixed media, layered, textural, detailed, paper, clean cuts, sharp edges",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic"
},
{
"name": "papercut",
"prompt": "flat papercut style of {prompt}. silhouette, clean cuts, paper, sharp edges, minimalist, color block",
"negative_prompt": "3D, high detail, noise, grainy, blurry, painting, drawing, photo, disfigured"
},
{
"name": "papercut-(stacked)",
"prompt": "stacked papercut art of {prompt}. 3D, layered, dimensional, depth, precision cut, stacked layers, papercut, high contrast",
"negative_prompt": "2D, flat, noisy, blurry, painting, drawing, photo, deformed"
},
{
"name": "pixel-art",
"prompt": "pixel-art of {prompt}. low-res, blocky, pixel art style, 8-bit graphics",
"negative_prompt": "sloppy, messy, blurry, noisy, highly detailed, ultra textured, photo, realistic"
},
{
"name": "pointillism",
"prompt": "pointillism style of {prompt}. composed entirely of small, distinct dots of color, vibrant, highly detailed",
"negative_prompt": "line drawing, smooth shading, large color fields, simplistic"
},
{
"name": "pop",
"prompt": "pop art style of {prompt}. bright colors, bold outlines, popular culture themes, ironic or kitsch",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, minimalist"
},
{
"name": "product",
"prompt": "advertising poster style of {prompt}. Professional, modern, product-focused, commercial, eye-catching, highly detailed",
"negative_prompt": "noisy, blurry, amateurish, sloppy, unattractive"
},
{
"name": "professional",
"prompt": "cinematic photo of {prompt}. 35mm photograph, film, bokeh, professional, 4k, highly detailed",
"negative_prompt": "drawing, painting, crayon, sketch, graphite, impressionist, noisy, blurry, soft, deformed, ugly"
},
{
"name": "psychedelic",
"prompt": "psychedelic style of {prompt}. vibrant colors, swirling patterns, abstract forms, surreal, trippy",
"negative_prompt": "monochrome, black and white, low contrast, realistic, photorealistic, plain, simple"
},
{
"name": "real-estate",
"prompt": "real estate photography style of {prompt}. professional, inviting, well-lit, high-resolution, property-focused, commercial, highly detailed",
"negative_prompt": "dark, blurry, unappealing, noisy, unprofessional"
},
{
"name": "renaissance",
"prompt": "renaissance style of {prompt}. realistic, perspective, light and shadow, religious or mythological themes, highly detailed",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, modernist, minimalist, abstract"
},
{
"name": "retro-futurism",
"prompt": "retro-futuristic of {prompt}. vintage sci-fi, 50s and 60s style, atomic age, vibrant, highly detailed",
"negative_prompt": "contemporary, realistic, rustic, primitive"
},
{
"name": "robotics",
"prompt": "cybernetic robot of {prompt}. android, AI, machine, metal, wires, tech, futuristic, highly detailed",
"negative_prompt": "organic, natural, human, sketch, watercolor, low contrast"
},
{
"name": "sci-fi",
"prompt": "sci-fi style of {prompt}. futuristic, technological, alien worlds, space themes, advanced civilizations",
"negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic, historical, medieval"
},
{
"name": "steampunk",
"prompt": "steampunk style of {prompt}. antique, mechanical, brass and copper tones, gears, intricate, detailed",
"negative_prompt": "deformed, glitch, noisy, low contrast, anime, photorealistic"
},
{
"name": "surreal",
"prompt": "surrealist art of {prompt}. dreamlike, mysterious, provocative, symbolic, intricate, detailed",
"negative_prompt": "anime, photorealistic, realistic, deformed, glitch, noisy, low contrast"
},
{
"name": "tilt-shift",
"prompt": "tilt-shift photo of {prompt}. selective focus, miniature effect, blurred background, highly detailed, vibrant, perspective control",
"negative_prompt": "blurry, noisy, deformed, flat, low contrast, unrealistic, oversaturated, underexposed"
},
{
"name": "vaporwave",
"prompt": "vaporwave style of {prompt}. retro aesthetic, cyberpunk, vibrant, neon colors, vintage 80s and 90s style, highly detailed",
"negative_prompt": "monochrome, muted colors, realism, rustic, minimalist, dark"
},
{
"name": "watercolor",
"prompt": "watercolor painting of {prompt}. vibrant, beautiful, painterly, detailed, textural, artistic",
"negative_prompt": "anime, photorealistic, 35mm film, deformed, glitch, low contrast, noisy"
}
]
+539
View File
@@ -0,0 +1,539 @@
# It is recommended not to modify this file,
# as it serves as the initialization file for the node.
#
# Instead, create a copy, place it in the same folder,
# and the iTools Prompt Styler node will read all your style files from there.
#
# Ensure that the formatting is kept the same as in this file.
# Indents in YAML are critical. Ensure proper indentation, as incorrect spacing can cause errors.
#
# Best approach is to copy a block of name, negative_prompt and a prompt and then replace their values
# Note that your text_positive will be added in this part {prompt}
# Adding - name: random will allow the node to pick random name from this file
- name: none
negative_prompt: ''
prompt: ''
- name: random
negative_prompt: ''
prompt: ''
- name: Canon EOS 90D
negative_prompt: slow shooting, inaccurate capture, static subjects
prompt: Photograph of {prompt}, captured on a (Canon EOS 90D)
- name: Canon EOS M50
negative_prompt: imbalanced quality
prompt: Photograph of {prompt}, captured on a (Canon EOS M50)
- name: Canon EOS M50 Mark II
negative_prompt: complex, non-vlogging, specialized photography
prompt: Photograph of {prompt}, captured on a (Canon EOS M50 Mark II)
- name: Canon EOS M6 Mark II
negative_prompt: professionals, poor image quality, slow capture, specialized
prompt: Photograph of {prompt}, captured on a (Canon EOS M6 Mark II)
- name: Canon EOS R5
negative_prompt: inferior capture, lack of stabilization, amateur use
prompt: Photograph of {prompt}, captured on a (Canon EOS R5)
- name: Canon EOS R5 (Unique Variant)
negative_prompt: low-resolution, basic video, amateurs, single field
prompt: Photograph of {prompt}, captured on a (Canon EOS R5)
- name: Canon EOS RP
negative_prompt: cropped frame, amateur quality, professionals
prompt: Photograph of {prompt}, captured on a (Canon EOS RP)
- name: Canon EOS Rebel T8i
negative_prompt: professional-level
prompt: Photograph of {prompt}, captured on a (Canon EOS Rebel T8i), characterized
by (vibrant colors, sharp details)
- name: Canon EOS Rebel T8i (Unique Variant)
negative_prompt: professional-level
prompt: Photograph of {prompt}, captured on a (Canon EOS Rebel T8i)
- name: Canon EOS-1D X Mark III
negative_prompt: slow speed, inaccuracy
prompt: Photograph of {prompt}, captured on a (Canon EOS-1D X Mark III)
- name: Canon EOS-1D X Mark III (Unique Dynamic Capture Variant)
negative_prompt: amateur, portrait photographers, slow shooting, inaccurate capture,
static scenes
prompt: Photograph of {prompt}, captured on a (Canon EOS-1D X Mark III)
- name: Canon EOS-1D X Mark III (Unique Dynamic Scenes Variant)
negative_prompt: amateur, slow shooting, unreliable capture, still life, passive
scenes
prompt: Photograph of {prompt}, captured on a (Canon EOS-1D X Mark III)
- name: Canon PowerShot G7 X Mark III
negative_prompt: complex, poor vlogging, HD video
prompt: Photograph of {prompt}, captured on a (Canon PowerShot G7 X Mark III)
- name: Canon PowerShot G7 X Mark III (Unique Specific Variant)
negative_prompt: photographers, video limitations, professional recording
prompt: Photograph of {prompt}, captured on a (Canon PowerShot G7 X Mark III)
- name: Canon PowerShot G7 X Mark III (Unique Variant)
negative_prompt: still photography, HD video, traditional photographers, stationary
prompt: Photograph of {prompt}, captured on a (Canon PowerShot G7 X Mark III )
- name: Fujifilm FinePix XP140
negative_prompt: indoor use, shock-prone
prompt: Photograph of {prompt}, captured on a (Fujifilm FinePix XP140)
- name: Fujifilm GFX 100
negative_prompt: low resolution, color inconsistency, amateurish quality
prompt: Photograph of {prompt}, captured on a (Fujifilm GFX 100)
- name: Fujifilm GFX 100 (Unique Specific Variant)
negative_prompt: low resolution, lack of stabilization, blurred images
prompt: Photograph of {prompt}, captured on a (Fujifilm GFX 100)
- name: Fujifilm GFX 100 (Unique Variant)
negative_prompt: low resolution, lack of detail, limited range, amateur
prompt: Photograph of {prompt}, captured on a (Fujifilm GFX 100 )
- name: Fujifilm GFX 50S
negative_prompt: low resolution, poor tones, casual snapfocus
prompt: Photograph of {prompt}, captured on a (Fujifilm GFX 50S)
- name: Fujifilm Instax Mini 11
negative_prompt: serious, complex, professional events, solitude, digital storage
prompt: Photograph of {prompt}, captured on a (Fujifilm Instax Mini 11)
- name: Fujifilm X-E4
negative_prompt: dull color, studio photography
prompt: Photograph of {prompt}, captured on a (Fujifilm X-E4)
- name: Fujifilm X-E4 (Unique Variant)
negative_prompt: studio, portrait photographers
prompt: Photograph of {prompt}, captured on a (Fujifilm X-E4)
- name: Fujifilm X-Pro3
negative_prompt: viewfinder, digital look, modern images
prompt: Photograph of {prompt}, captured on a (Fujifilm X-Pro3)
- name: Fujifilm X-Pro3 (Unique Variant)
negative_prompt: viewfinder, digital look, modern experience, digital aesthetics
prompt: Photograph of {prompt}, captured on a (Fujifilm X-Pro3)
- name: Fujifilm X-T200
negative_prompt: complex, dull color, professionals, low-quality images
prompt: Photograph of {prompt}, captured on a (Fujifilm X-T200)
- name: Fujifilm X-T4
negative_prompt: slowness, lack of stabilization, amateur videography
prompt: Photograph of {prompt}, captured on a (Fujifilm X-T4)
- name: Fujifilm X-T4 (Unique Strong Stabilization)
negative_prompt: weak stabilization, specialized performance, photography only,
videography only
prompt: Photograph of {prompt}, captured on a (Fujifilm X-T4)
- name: Fujifilm X100V
negative_prompt: digital look, contemporary images
prompt: Photograph of {prompt}, captured on a (Fujifilm X100V)
- name: Fujifilm X100V (Unique Variant)
negative_prompt: modern looks, average image quality, studio photography
prompt: Photograph of {prompt}, captured on a (Fujifilm X100V)
- name: GoPro HERO9 Black
negative_prompt: shaky footage, static scenes
prompt: Photograph of {prompt}, captured on a (GoPro HERO9 Black)
- name: Hasselblad 907X 50C
negative_prompt: modern aesthetics, poor detail
prompt: Photograph of {prompt}, captured on a (Hasselblad 907X 50C)
- name: Hasselblad 907X 50C (Unique Large Format)
negative_prompt: poor image quality
prompt: Photograph of {prompt}, captured on a (Hasselblad 907X 50C)
- name: Hasselblad H5D-50c
negative_prompt: lack of detail, color inaccuracy
prompt: Photograph of {prompt}, captured on a (Hasselblad H5D-50c)
- name: Hasselblad H6D-100c
negative_prompt: low resolution, amateur work
prompt: Photograph of {prompt}, captured on a (Hasselblad H6D-100c)
- name: Hasselblad X1D II 50C
negative_prompt: limited detail, narrow dynamic range, dull colors
prompt: Photograph of {prompt}, captured on a (Hasselblad X1D II 50C)
- name: Hasselblad X1D II 50C (Unique High Resolution)
negative_prompt: low resolution, color inaccuracy, amateur portraits
prompt: Photograph of {prompt}, captured on a (Hasselblad X1D II 50C)
- name: Hasselblad XCD 4/45P
negative_prompt: average , harsh bokeh
prompt: Photograph of {prompt}, captured on a (Hasselblad XCD 4/45P)
- name: Leica CL
negative_prompt: average optics, soft images
prompt: Photograph of {prompt}, captured on a (Leica CL)
- name: Leica M-D
negative_prompt: DSLR, enhanced photography
prompt: Photograph of {prompt}, captured on a (Leica M-D)
- name: Leica M10 Monochrom
negative_prompt: color photography, flat tones, contemporary look
prompt: Photograph of {prompt}, captured on a (Leica M10 Monochrom)
- name: Leica M10 Monochrom (Unique Variant)
negative_prompt: color photography, conventional, non-artists, color enthusiasts
prompt: Photograph of {prompt}, captured on a (Leica M10 Monochrom)
- name: Leica M10-R
negative_prompt: mediocre quality, lack of detail, snapshot photography
prompt: Photograph of {prompt}, captured on a (Leica M10-R)
- name: Leica M11
negative_prompt: ''
prompt: Photograph of {prompt}, captured on a (Leica M11)
- name: Leica Q-P
negative_prompt: attention-grabbing, studio photographers
prompt: Photograph of {prompt}, captured on a (Leica Q-P)
- name: Leica Q2
negative_prompt: cropped sensor, lack of , low
prompt: Photograph of {prompt}, captured on a (Leica Q2)
- name: Leica Q2 Monochrom
negative_prompt: color photography, complex approach, low-resolution, color enthusiasts
prompt: Photograph of {prompt}, captured on a (Leica Q2 Monochrom)
- name: Leica S3
negative_prompt: mediocre image quality, basic build, low-end commercial
prompt: Photograph of {prompt}, captured on a (Leica S3)
- name: Leica SL2
negative_prompt: low-quality images, cheap feel
prompt: Photograph of {prompt}, captured on a (Leica SL2)
- name: Leica SL2 (Unique Variant)
negative_prompt: cropped sensor, mediocre quality, snap, budget advertising
prompt: Photograph of {prompt}, captured on a (Leica SL2)
- name: Leica SL2-S
negative_prompt: cropped-frame, basic build, specialization, photography only, videography
only
prompt: Photograph of {prompt}, captured on a (Leica SL2-S)
- name: Nikon Coolpix P950
negative_prompt: limited zoom, lack of stabilization, close-up photography
prompt: Photograph of {prompt}, captured on a (Nikon Coolpix P950)
- name: Nikon Coolpix P950 (Unique Variant)
negative_prompt: limited zoom, no stabilization, close-up subjects
prompt: Photograph of {prompt}, captured on a (Nikon Coolpix P950)
- name: Nikon Coolpix W300
negative_prompt: studio, , HD video
prompt: Photograph of {prompt}, captured on a (Nikon Coolpix W300)
- name: Nikon D3500
negative_prompt: poor image quality
prompt: Photograph of {prompt}, captured on a (Nikon D3500), characterized by (rich
colors, smooth tonal transitions)
- name: Nikon D6
negative_prompt: amateur, slowness, inaccuracy, still life photography, static scenes
prompt: Photograph of {prompt}, captured on a (Nikon D6)
- name: Nikon D780
negative_prompt: poor low-light, limited video, low-detail images
prompt: Photograph of {prompt}, captured on a (Nikon D780)
- name: Nikon D850
negative_prompt: low resolution, slow performance
prompt: Photograph of {prompt}, captured on a (Nikon D850)
- name: Nikon D850 (Unique High Resolution)
negative_prompt: cropped-frame, low resolution, specialized performance, amateur,
narrow range
prompt: Photograph of {prompt}, captured on a (Nikon D850)
- name: Nikon Z5
negative_prompt: cropped-frame, imbalance, poor performance, expensive, beginners,
high-end professionals
prompt: Photograph of {prompt}, captured on a (Nikon Z5)
- name: Nikon Z50
negative_prompt: complex, dull images
prompt: Photograph of {prompt}, captured on a (Nikon Z50)
- name: Nikon Z50 (Unique Variant)
negative_prompt: complex, professionals, stationary photography
prompt: Photograph of {prompt}, captured on a (Nikon Z50)
- name: Nikon Z6
negative_prompt: cropped sensor, poor low-light performance, noisy images
prompt: Photograph of {prompt}, captured on a (Nikon Z6)
- name: Nikon Z6 II
negative_prompt: poor low-light, slow capture, static , day scenes
prompt: Photograph of {prompt}, captured on a (Nikon Z6 II)
- name: Nikon Z7 II
negative_prompt: low-resolution, slow shooting, lack of detail, static scenes
prompt: Photograph of {prompt}, captured on a (Nikon Z7 II)
- name: Nikon Z7 II (Unique Variant)
negative_prompt: poor resolution, limited range, portrait photographers, vague details
prompt: Photograph of {prompt}, captured on a (Nikon Z7 II)
- name: Olympus E-M1X
negative_prompt: weak stabilization, slow capture, still life, passive scenes
prompt: Photograph of {prompt}, captured on a (Olympus E-M1X)
- name: Olympus OM-D E-M1 Mark II
negative_prompt: slow capture, action, missed timing
prompt: Photograph of {prompt}, captured on a (Olympus OM-D E-M1 Mark II)
- name: Olympus OM-D E-M10 Mark IV
negative_prompt: heavy, fixed location, shaky images
prompt: Photograph of {prompt}, captured on a (Olympus OM-D E-M10 Mark IV)
- name: Olympus OM-D E-M10 Mark IV (Unique Variant)
negative_prompt: complex, professional-level
prompt: Photograph of {prompt}, captured on a (Olympus OM-D E-M10 Mark IV)
- name: Olympus OM-D E-M1X
negative_prompt: amateur, still life, basic stabilization, non-weather-sealed, controlled
conditions
prompt: Photograph of {prompt}, captured on a (Olympus OM-D E-M1X)
- name: Olympus OM-D E-M5 Mark III
negative_prompt: weak stabilization, studio photography
prompt: Photograph of {prompt}, captured on a (Olympus OM-D E-M5 Mark III)
- name: Olympus OM-D E-M5 Mark III (Unique Variant)
negative_prompt: heavy, non-weather-sealed, indoor photography, controlled weather
prompt: Photograph of {prompt}, captured on a (Olympus OM-D E-M5 Mark III)
- name: Olympus PEN E-PL10
negative_prompt: technical photographers, images
prompt: Photograph of {prompt}, captured on a (Olympus PEN E-PL10)
- name: Olympus PEN-F
negative_prompt: poor color reproduction, dull photos
prompt: Photograph of {prompt}, captured on a (Olympus PEN-F)
- name: Olympus PEN-F (Unique Specific Variant)
negative_prompt: fixed , limited creativity, studio photography
prompt: Photograph of {prompt}, captured on a (Olympus PEN-F)
- name: Olympus PEN-F (Unique Variant)
negative_prompt: modern-styled, controls, non-artists, lack of style, limited functionality
prompt: Photograph of {prompt}, captured on a (Olympus PEN-F )
- name: Olympus Tough TG-6
negative_prompt: not waterproof, indoor photography
prompt: Photograph of {prompt}, captured on a (Olympus Tough TG-6)
- name: Olympus Tough TG-6 (Unique Variant)
negative_prompt: studio, , shock-prone
prompt: Photograph of {prompt}, captured on a (Olympus Tough TG-6)
- name: Panasonic Lumix DC-G100
negative_prompt: photographers, external microphone required, poor audio
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix DC-G100)
- name: Panasonic Lumix DC-G9
negative_prompt: videographers, low-resolution, slow burst, still life, static scenes
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix DC-G9)
- name: Panasonic Lumix DC-GH5
negative_prompt: HD video, basic features, photographers, non-cinematic content
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix DC-GH5)
- name: Panasonic Lumix DC-S1R
negative_prompt: low-resolution, build, landscapes, amateur indoor
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix DC-S1R)
- name: Panasonic Lumix DC-S5
negative_prompt: photo-only, limited video, specialization, single-format creators
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix DC-S5)
- name: Panasonic Lumix DC-S5 (Unique Variant)
negative_prompt: cropped-frame, imbalance, poor image quality, limited video, specialized
creators
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix DC-S5)
- name: Panasonic Lumix DMC-FZ300
negative_prompt: variable aperture, non-weather-sealed, indoor photography
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix DMC-FZ300), characterized
by (fast autofocus, great macro capabilities)
- name: Panasonic Lumix DMC-FZ300 (Unique Variant)
negative_prompt: fixed , studio photographers, non-adventurous
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix DMC-FZ300)
- name: Panasonic Lumix DMC-LX100 II
negative_prompt: budget, slow , HD video, studio work, specialized photography
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix DMC-LX100 II)
- name: Panasonic Lumix FZ1000 II
negative_prompt: limited zoom, HD video, single-purpose
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix FZ1000 II)
- name: Panasonic Lumix G100
negative_prompt: still photography, HD video, fixed , limited framing
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix G100)
- name: Panasonic Lumix GH5 II
negative_prompt: still photography, shaky footage, low frame rates, amateur video
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix GH5 II)
- name: Panasonic Lumix S1
negative_prompt: still photography, photographers, indoor videography
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix S1)
- name: Panasonic Lumix S1R
negative_prompt: low resolution, lack of detail, amateur use
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix S1R)
- name: Panasonic Lumix S5
negative_prompt: HD video, non-vlogging
prompt: Photograph of {prompt}, captured on a (Panasonic Lumix S5)
- name: Pentax 645Z
negative_prompt: lack of detail, limited range, amateur portraits
prompt: Photograph of {prompt}, captured on a (Pentax 645Z)
- name: Pentax 645Z (Unique Large Format)
negative_prompt: average image quality, limited range, amateur, action, on-the-go
prompt: Photograph of {prompt}, captured on a (Pentax 645Z)
- name: Pentax K-1 Mark II
negative_prompt: soft , limited dynamic range
prompt: Photograph of {prompt}, captured on a (Pentax K-1 Mark II)
- name: Pentax KP
negative_prompt: build, non-weather-sealed, indoor photography
prompt: Photograph of {prompt}, captured on a (Pentax KP)
- name: Phase One XF IQ4 150MP
negative_prompt: low-end, low resolution, amateur, lack of detail
prompt: Photograph of {prompt}, captured on a (Phase One XF IQ4 150MP)
- name: Phase One XT
negative_prompt: studio camera, action, portrait, lack of detail, disconnected system
prompt: Photograph of {prompt}, captured on a (Phase One XT)
- name: Ricoh GR III
negative_prompt: soft , shaky , handheld images
prompt: Photograph of {prompt}, captured on a (Ricoh GR III)
- name: Ricoh GR III (Unique Steady Focus)
negative_prompt: soft , studio photography
prompt: Photograph of {prompt}, captured on a (Ricoh GR III)
- name: Ricoh GR III (Unique Variant)
negative_prompt: studio photography, softness
prompt: Photograph of {prompt}, captured on a (Ricoh GR III)
- name: Ricoh Theta Z1
negative_prompt: photography, flat images, limited views
prompt: Photograph of {prompt}, captured on a (Ricoh Theta Z1)
- name: Ricoh Theta Z1 (Unique Variant)
negative_prompt: camera, flat images, non-VR, limited angles
prompt: Photograph of {prompt}, captured on a (Ricoh Theta Z1)
- name: Sigma SD Quattro H
negative_prompt: traditional sensor, blended color, soft
prompt: Photograph of {prompt}, captured on a (Sigma SD Quattro H)
- name: Sigma fp
negative_prompt: limited versatility, soft images, jittery video
prompt: Photograph of {prompt}, captured on a (Sigma fp)
- name: Sigma fp (Unique Specific Variant)
negative_prompt: rigidity, limited scenarios
prompt: Photograph of {prompt}, captured on a (Sigma fp)
- name: Sigma fp (Unique Variant)
negative_prompt: cropped-frame, bulkiness, specialization, photo only, video only
prompt: Photograph of {prompt}, captured on a (Sigma fp )
- name: Sigma fp L
negative_prompt: low resolution, limited creativity
prompt: Photograph of {prompt}, captured on a (Sigma fp L)
- name: Sony A6000
negative_prompt: poor capture, studio photography, staged
prompt: Photograph of {prompt}, captured on a (Sony A6000)
- name: Sony A6400
negative_prompt: slowness, capture inaccuracy, still photography
prompt: Photograph of {prompt}, captured on a (Sony A6400)
- name: Sony A7R IV
negative_prompt: low resolution, limited range, lack of detail, portraits, casual
snapfocus
prompt: Photograph of {prompt}, captured on a (Sony A7R IV)
- name: Sony A7S III
negative_prompt: poor low-light, HD video, non-filmmakers, daytime photography
prompt: Photograph of {prompt}, captured on a (Sony A7S III)
- name: Sony A9 II
negative_prompt: poor capture, single shot, action
prompt: Photograph of {prompt}, captured on a (Sony A9 II)
- name: Sony Alpha 1
negative_prompt: entry-level, slow speed, amateur quality
prompt: Photograph of {prompt}, captured on a (Sony Alpha 1)
- name: Sony Alpha 1 (Unique Variant)
negative_prompt: amateur, still life, slow subjects, slow burst, imprecise capture
prompt: Photograph of {prompt}, captured on a (Sony Alpha 1)
- name: Sony Alpha A7 III
negative_prompt: overpriced, average quality images, unreliable performance, beginners,
casual users
prompt: Photograph of {prompt}, captured on a (Sony Alpha A7 III)
- name: Sony Alpha A9 II
negative_prompt: amateur, slow shooting, unreliable capture, still life photography
prompt: Photograph of {prompt}, captured on a (Sony Alpha A9 II)
- name: Sony Cyber-shot DSC-RX10 IV
negative_prompt: short zoom, poor video, portrait photography, close-up subjects
prompt: Photograph of {prompt}, captured on a (Sony Cyber-shot DSC-RX10 IV)
- name: Sony Cyber-shot DSC-W800
negative_prompt: professional-level, expensive, expert photographers, primary camera
prompt: Photograph of {prompt}, captured on a (Sony Cyber-shot DSC-W800)
- name: Sony Cyber-shot RX100 VII
negative_prompt: large-sized, limited zoom, studio work, specialized photography,
compromise quality
prompt: Photograph of {prompt}, captured on a (Sony Cyber-shot RX100 VII)
- name: Sony RX10 IV
negative_prompt: limited zoom, poor capture, close-up photography
prompt: Photograph of {prompt}, captured on a (Sony RX10 IV)
- name: Sony RX100 VII
negative_prompt: slow capture, limited zoom, images
prompt: Photograph of {prompt}, captured on a (Sony RX100 VII)
- name: Sony RX100 VII (Unique Fast Capture)
negative_prompt: budget, slow , basic capture, studio photography, static scenes
prompt: Photograph of {prompt}, captured on a (Sony RX100 VII)
- name: Sony ZV-1
negative_prompt: general use, slow , poor capture, videos
prompt: Photograph of {prompt}, captured on a (Sony ZV-1)
+163
View File
@@ -0,0 +1,163 @@
# It is recommended not to modify this file,
# as it serves as the initialization file for the node.
#
# Instead, create a copy, place it in the same folder,
# and the iTools Prompt Styler node will read all your style files from there.
#
# Ensure that the formatting is kept the same as in this file.
# Indents in YAML are critical. Ensure proper indentation, as incorrect spacing can cause errors.
#
# Best approach is to copy a block of name, negative_prompt and a prompt and then replace their values
# Note that your text_positive will be added in this part {prompt}
# Adding - name: random will allow the node to pick random name from this file
- name: none
negative_prompt: ''
prompt: ''
- name: random
negative_prompt: ''
prompt: ''
- name: Abyssal Silence
negative_prompt: Unsilenced, ordinary, lacking darkness, uninhabited, mundane.
prompt: A profound and eerie mood of deep silence, where the world is shrouded in
darkness and an overwhelming sense of isolation prevails.
- name: Aggressive
negative_prompt: calm
prompt: aggressive {prompt}
- name: Bittersweet Nostalgia
negative_prompt: Unbittersweet, ordinary, lacking nostalgia, unreflective, mundane.
prompt: A bittersweet and nostalgic mood where the world is tinged with memories
of the past, evoking a sense of longing and reflection.
- name: Calm
negative_prompt: energetic
prompt: calm {prompt}
- name: Celestial Harmony
negative_prompt: Unharmonious, ordinary, lacking celestial bodies, uncosmic, mundane.
prompt: A harmonious and celestial mood where the world is adorned with celestial
bodies, and a sense of cosmic unity and serenity prevails.
- name: Chaos and Discord
negative_prompt: Unchaotic, ordinary, lacking discord, unharmonious, mundane.
prompt: A chaotic and discordant mood where the world is filled with cacophonous
sounds and jarring visuals, creating a sense of disarray and confusion.
- name: Chaotic
negative_prompt: serene
prompt: chaotic {prompt}
- name: Dark, Brooding Atmosphere
negative_prompt: Unbrooding, ordinary, lacking darkness, ununeasy, mundane.
prompt: A moody and ominous mood where shadows loom large, and a sense of foreboding
and tension hangs in the air, creating a sense of unease and suspense.
- name: Dreamy Serenity
negative_prompt: Undreamy, ordinary, lacking serenity, unsoothing, mundane.
prompt: A dreamlike and serene mood where the world is bathed in soft, pastel colors,
and a sense of calm and tranquility pervades, creating a soothing and dreamy atmosphere.
- name: Dystopian Desolation
negative_prompt: Undystopian, ordinary, lacking desolation, unhopeful, mundane.
prompt: A mood of dystopian desolation where the world is a bleak and desolate landscape,
and a sense of despair and isolation hangs in the air.
- name: Enchanted Whispers
negative_prompt: Unenchanted, ordinary, lacking whispers, unmysterious, mundane.
prompt: A mystical and enchanting mood where the world is hushed, and whispers of
magic and wonder are carried on the wind, igniting a sense of enchantment and
mystery.
- name: Energetic
negative_prompt: relaxed
prompt: energetic {prompt}
- name: Enigmatic Wonder
negative_prompt: Unenigmatic, ordinary, lacking wonders, uncurious, mundane.
prompt: A mysterious and awe-inspiring mood where the world is filled with enigmatic
phenomena and hidden wonders waiting to be uncovered, inspiring curiosity and
fascination.
- name: Eternal Twilight
negative_prompt: Untwilight, ordinary, lacking light, untimeless, mundane.
prompt: A perpetual twilight mood where the world is bathed in soft, muted light,
evoking a sense of timeless serenity and contemplation.
- name: Ethereal Tranquility
negative_prompt: Untranquil, ordinary, lacking music, unrelaxing, mundane.
prompt: A serene and otherworldly mood where soft, ambient music plays, and the
world seems to slow down, evoking a sense of peace and relaxation.
- name: Frenetic Energy
negative_prompt: Unfrenetic, ordinary, lacking energy, unexciting, mundane.
prompt: A high-energy and frenetic mood where the world pulsates with vitality and
motion, creating a sense of exhilaration and excitement.
- name: Gothic Elegance
negative_prompt: Ungothic, ordinary, lacking elegance, unbeguiling, mundane.
prompt: A mood of dark and gothic elegance, where the world is adorned with intricate
architecture, opulent decor, and a sense of brooding beauty.
- name: Happy
negative_prompt: sad
prompt: happy {prompt}
- name: Hushed Reverence
negative_prompt: Unreverent, ordinary, lacking silence, unawe-inspiring, mundane.
prompt: A reverent and tranquil mood where silence reigns, and the world is filled
with a sense of awe and sacredness, inviting contemplation and meditation.
- name: Joyful Celebration
negative_prompt: Unjoyful, ordinary, lacking celebration, uncamaraderie, mundane.
prompt: A festive and celebratory mood where the world is alive with music, dancing,
and laughter, evoking a sense of joy and camaraderie.
- name: Melancholic Reflection
negative_prompt: Unmelancholic, ordinary, lacking reflection, unreflective, mundane.
prompt: A contemplative and melancholic mood where the world is bathed in soft,
muted colors, and a sense of nostalgia and introspection fills the air.
- name: Mysterious
negative_prompt: clear
prompt: mysterious {prompt}
- name: Relaxed
negative_prompt: excited
prompt: relaxed {prompt}
- name: Romantic
negative_prompt: depressed
prompt: romantic {prompt}
- name: Sad
negative_prompt: happy
prompt: sad {prompt}
- name: Serene
negative_prompt: chaotic
prompt: serene {prompt}
- name: Spectral Whispers
negative_prompt: Unspectral, ordinary, lacking whispers, unhaunted, mundane.
prompt: A spectral and haunting mood where ghostly whispers fill the air, and the
world is haunted by the echoes of the past, creating an eerie and ethereal atmosphere.
- name: Surreal Euphoria
negative_prompt: Uneuphoric, ordinary, lacking surrealism, undreamlike, mundane.
prompt: A surreal and euphoric mood where the boundaries of reality are blurred,
and a sense of euphoria and elation fills the world, creating a dreamlike and
euphoric experience.
- name: Tense, Suspenseful Ambiance
negative_prompt: Untense, ordinary, lacking suspense, unintriguing, mundane.
prompt: A tense and suspenseful mood where every moment is charged with anticipation
and uncertainty, creating a sense of adrenaline and intrigue.
- name: Whimsical Delight
negative_prompt: Unwhimsical, ordinary, lacking playfulness, unjoyful, mundane.
prompt: A playful and lighthearted mood where the world is filled with whimsical
characters, magical creatures, and joyful surprises, inviting laughter and joy.
+554
View File
@@ -0,0 +1,554 @@
# It is recommended not to modify this file,
# as it serves as the initialization file for the node.
#
# Instead, create a copy, place it in the same folder,
# and the iTools Prompt Styler node will read all your style files from there.
#
# Ensure that the formatting is kept the same as in this file.
# Indents in YAML are critical. Ensure proper indentation, as incorrect spacing can cause errors.
#
# Best approach is to copy a block of name, negative_prompt and a prompt and then replace their values
# Note that your text_positive will be added in this part {prompt}
# Adding - name: random will allow the node to pick random name from this file
- name: none
negative_prompt: ''
prompt: ''
- name: random
negative_prompt: ''
prompt: ''
- name: ads-advertising
negative_prompt: noisy, blurry, amateurish, sloppy, unattractive
prompt: Advertising poster style of {prompt}. Professional, modern, product-focused,
commercial, eye-catching, highly detailed
- name: ads-automotive
negative_prompt: noisy, blurry, unattractive, sloppy, unprofessional
prompt: Automotive advertisement style of {prompt}. Sleek, dynamic, professional,
commercial, vehicle-focused, high-resolution, highly detailed
- name: ads-corporate
negative_prompt: noisy, blurry, grungy, sloppy, cluttered, disorganized
prompt: Corporate branding style of {prompt}. Professional, clean, modern, sleek,
minimalist, business-oriented, highly detailed
- name: ads-fashion editorial
negative_prompt: outdated, blurry, noisy, unattractive, sloppy
prompt: Fashion editorial style of {prompt}. High fashion, trendy, stylish, editorial,
magazine style, professional, highly detailed
- name: ads-food photography
negative_prompt: unappetizing, sloppy, unprofessional, noisy, blurry
prompt: Food photography style of {prompt}. Appetizing, professional, culinary,
high-resolution, commercial, highly detailed
- name: ads-luxury
negative_prompt: cheap, noisy, blurry, unattractive, amateurish
prompt: Luxury product style of {prompt}. Elegant, sophisticated, high-end, luxurious,
professional, highly detailed
- name: ads-real estate
negative_prompt: dark, blurry, unappealing, noisy, unprofessional
prompt: Real estate photography style of {prompt}. Professional, inviting, well-lit,
high-resolution, property-focused, commercial, highly detailed
- name: ads-retail
negative_prompt: noisy, blurry, amateurish, sloppy, unattractive
prompt: Retail packaging style of {prompt}. Vibrant, enticing, commercial, product-focused,
eye-catching, professional, highly detailed
- name: artstyle-abstract
negative_prompt: realistic, photographic, figurative, concrete
prompt: abstract style of {prompt}. non-representational, colors and shapes, expression
of feelings, imaginative, highly detailed
- name: artstyle-abstract expressionism
negative_prompt: realistic, photorealistic, low contrast, plain, simple, monochrome
prompt: abstract expressionist painting of {prompt}. energetic brushwork, bold colors,
abstract forms, expressive, emotional
- name: artstyle-art deco
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic,
modernist, minimalist
prompt: Art Deco style of {prompt}. geometric shapes, bold colors, luxurious, elegant,
decorative, symmetrical, ornate, detailed
- name: artstyle-art nouveau
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic,
modernist, minimalist
prompt: Art Nouveau style of {prompt}. elegant, decorative, curvilinear forms, nature-inspired,
ornate, detailed
- name: artstyle-constructivist
negative_prompt: realistic, photorealistic, low contrast, plain, simple, abstract
expressionism
prompt: constructivist style of {prompt}. geometric shapes, bold colors, dynamic
composition, propaganda art style
- name: artstyle-cubist
negative_prompt: anime, photorealistic, 35mm film, deformed, glitch, low contrast,
noisy
prompt: cubist artwork of {prompt}. geometric shapes, abstract, innovative, revolutionary
- name: artstyle-expressionist
negative_prompt: realism, symmetry, quiet, calm, photo
prompt: expressionist of {prompt}. raw, emotional, dynamic, distortion for emotional
effect, vibrant, use of unusual colors, detailed
- name: artstyle-graffiti
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic
prompt: graffiti style of {prompt}. street art, vibrant, urban, detailed, tag, mural
- name: artstyle-hyperrealism
negative_prompt: simplified, abstract, unrealistic, impressionistic, low resolution
prompt: hyperrealistic art of {prompt}. extremely high-resolution details, photographic,
realism pushed to extreme, fine texture, incredibly lifelike
- name: artstyle-impressionist
negative_prompt: anime, photorealistic, 35mm film, deformed, glitch, low contrast,
noisy
prompt: impressionist painting of {prompt}. loose brushwork, vibrant color, light
and shadow play, captures feeling over form
- name: artstyle-pointillism
negative_prompt: line drawing, smooth shading, large color fields, simplistic
prompt: pointillism style of {prompt}. composed entirely of small, distinct dots
of color, vibrant, highly detailed
- name: artstyle-pop art
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic,
minimalist
prompt: Pop Art style of {prompt}. bright colors, bold outlines, popular culture
themes, ironic or kitsch
- name: artstyle-psychedelic
negative_prompt: monochrome, black and white, low contrast, realistic, photorealistic,
plain, simple
prompt: psychedelic style of {prompt}. vibrant colors, swirling patterns, abstract
forms, surreal, trippy
- name: artstyle-renaissance
negative_prompt: ugly, deformed, noisy, blurry, low contrast, modernist, minimalist,
abstract
prompt: Renaissance style of {prompt}. realistic, perspective, light and shadow,
religious or mythological themes, highly detailed
- name: artstyle-steampunk
negative_prompt: deformed, glitch, noisy, low contrast, anime, photorealistic
prompt: steampunk style of {prompt}. antique, mechanical, brass and copper tones,
gears, intricate, detailed
- name: artstyle-surrealist
negative_prompt: anime, photorealistic, realistic, deformed, glitch, noisy, low
contrast
prompt: surrealist art of {prompt}. dreamlike, mysterious, provocative, symbolic,
intricate, detailed
- name: artstyle-typography
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic
prompt: typographic art of {prompt}. stylized, intricate, detailed, artistic, text-based
- name: artstyle-watercolor
negative_prompt: anime, photorealistic, 35mm film, deformed, glitch, low contrast,
noisy
prompt: watercolor painting of {prompt}. vibrant, beautiful, painterly, detailed,
textural, artistic
- name: futuristic-biomechanical
negative_prompt: natural, rustic, primitive, organic, simplistic
prompt: biomechanical style of {prompt}. blend of organic and mechanical elements,
futuristic, cybernetic, detailed, intricate
- name: futuristic-biomechanical cyberpunk
negative_prompt: natural, colorful, deformed, sketch, low contrast, watercolor
prompt: biomechanical cyberpunk of {prompt}. cybernetics, human-machine fusion,
dystopian, organic meets artificial, dark, intricate, highly detailed
- name: futuristic-cybernetic
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic,
historical, medieval
prompt: cybernetic style of {prompt}. futuristic, technological, cybernetic enhancements,
robotics, artificial intelligence themes
- name: futuristic-cybernetic robot
negative_prompt: organic, natural, human, sketch, watercolor, low contrast
prompt: cybernetic robot of {prompt}. android, AI, machine, metal, wires, tech,
futuristic, highly detailed
- name: futuristic-cyberpunk cityscape
negative_prompt: natural, rural, deformed, low contrast, black and white, sketch,
watercolor
prompt: cyberpunk cityscape of {prompt}. neon lights, dark alleys, skyscrapers,
futuristic, vibrant colors, high contrast, highly detailed
- name: futuristic-futuristic
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic,
vintage, antique
prompt: futuristic style of {prompt}. sleek, modern, ultramodern, high tech, detailed
- name: futuristic-retro cyberpunk
negative_prompt: modern, desaturated, black and white, realism, low contrast
prompt: retro cyberpunk of {prompt}. 80's inspired, synthwave, neon, vibrant, detailed,
retro futurism
- name: futuristic-retro futurism
negative_prompt: contemporary, realistic, rustic, primitive
prompt: retro-futuristic of {prompt}. vintage sci-fi, 50s and 60s style, atomic
age, vibrant, highly detailed
- name: futuristic-sci-fi
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic,
historical, medieval
prompt: sci-fi style of {prompt}. futuristic, technological, alien worlds, space
themes, advanced civilizations
- name: futuristic-vaporwave
negative_prompt: monochrome, muted colors, realism, rustic, minimalist, dark
prompt: vaporwave style of {prompt}. retro aesthetic, cyberpunk, vibrant, neon colors,
vintage 80s and 90s style, highly detailed
- name: game-bubble bobble
negative_prompt: realistic, modern, photorealistic, violent, horror
prompt: Bubble Bobble style of {prompt}. 8-bit, cute, pixelated, fantasy, vibrant,
reminiscent of Bubble Bobble game
- name: game-cyberpunk game
negative_prompt: historical, natural, rustic, low detailed
prompt: cyberpunk game style of {prompt}. neon, dystopian, futuristic, digital,
vibrant, detailed, high contrast, reminiscent of cyberpunk genre video games
- name: game-fighting game
negative_prompt: peaceful, calm, minimalist, photorealistic
prompt: fighting game style of {prompt}. dynamic, vibrant, action-packed, detailed
character design, reminiscent of fighting video games
- name: game-gta
negative_prompt: realistic, black and white, low contrast, impressionist, cubist,
noisy, blurry, deformed
prompt: GTA-style artwork of {prompt}. satirical, exaggerated, pop art style, vibrant
colors, iconic characters, action-packed
- name: game-mario
negative_prompt: realistic, modern, horror, dystopian, violent
prompt: Super Mario style of {prompt}. vibrant, cute, cartoony, fantasy, playful,
reminiscent of Super Mario series
- name: game-minecraft
negative_prompt: smooth, realistic, detailed, photorealistic, noise, blurry, deformed
prompt: Minecraft style of {prompt}. blocky, pixelated, vibrant colors, recognizable
characters and objects, game assets
- name: game-pokemon
negative_prompt: realistic, modern, horror, dystopian, violent
prompt: Pokémon style of {prompt}. vibrant, cute, anime, fantasy, reminiscent of
Pokémon series
- name: game-retro arcade
negative_prompt: modern, ultra-high resolution, photorealistic, 3D
prompt: retro arcade style of {prompt}. 8-bit, pixelated, vibrant, classic video
game, old school gaming, reminiscent of 80s and 90s arcade games
- name: game-retro game
negative_prompt: realistic, photorealistic, 35mm film, deformed, glitch, low contrast,
noisy
prompt: retro game art of {prompt}. 16-bit, vibrant colors, pixelated, nostalgic,
charming, fun
- name: game-rpg fantasy game
negative_prompt: sci-fi, modern, urban, futuristic, low detailed
prompt: role-playing game (RPG) style fantasy of {prompt}. detailed, vibrant, immersive,
reminiscent of high fantasy RPG games
- name: game-strategy game
negative_prompt: first-person view, modern, photorealistic
prompt: strategy game style of {prompt}. overhead view, detailed map, units, reminiscent
of real-time strategy video games
- name: game-streetfighter
negative_prompt: 3D, realistic, modern, photorealistic, turn-based strategy
prompt: Street Fighter style of {prompt}. vibrant, dynamic, arcade, 2D fighting
game, highly detailed, reminiscent of Street Fighter series
- name: game-zelda
negative_prompt: sci-fi, modern, realistic, horror
prompt: Legend of Zelda style of {prompt}. vibrant, fantasy, detailed, epic, heroic,
reminiscent of The Legend of Zelda series
- name: misc-architectural
negative_prompt: curved lines, ornate, baroque, abstract, grunge
prompt: architectural style of {prompt}. clean lines, geometric shapes, minimalist,
modern, architectural drawing, highly detailed
- name: misc-disco
negative_prompt: minimalist, rustic, monochrome, contemporary, simplistic
prompt: disco-themed of {prompt}. vibrant, groovy, retro 70s style, shiny disco
balls, neon lights, dance floor, highly detailed
- name: misc-dreamscape
negative_prompt: realistic, concrete, ordinary, mundane
prompt: dreamscape of {prompt}. surreal, ethereal, dreamy, mysterious, fantasy,
highly detailed
- name: misc-dystopian
negative_prompt: ugly, deformed, noisy, blurry, low contrast, cheerful, optimistic,
vibrant, colorful
prompt: dystopian style of {prompt}. bleak, post-apocalyptic, somber, dramatic,
highly detailed
- name: misc-fairy tale
negative_prompt: realistic, modern, ordinary, mundane
prompt: fairy tale of {prompt}. magical, fantastical, enchanting, storybook style,
highly detailed
- name: misc-gothic
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic,
cheerful, optimistic
prompt: gothic style of {prompt}. dark, mysterious, haunting, dramatic, ornate,
detailed
- name: misc-grunge
negative_prompt: smooth, clean, minimalist, sleek, modern, photorealistic
prompt: grunge style of {prompt}. textured, distressed, vintage, edgy, punk rock
vibe, dirty, noisy
- name: misc-horror
negative_prompt: cheerful, bright, vibrant, light-hearted, cute
prompt: horror-themed of {prompt}. eerie, unsettling, dark, spooky, suspenseful,
grim, highly detailed
- name: misc-kawaii
negative_prompt: dark, scary, realistic, monochrome, abstract
prompt: kawaii style of {prompt}. cute, adorable, brightly colored, cheerful, anime
influence, highly detailed
- name: misc-lovecraftian
negative_prompt: light-hearted, mundane, familiar, simplistic, realistic
prompt: lovecraftian horror of {prompt}. eldritch, cosmic horror, unknown, mysterious,
surreal, highly detailed
- name: misc-macabre
negative_prompt: bright, cheerful, light-hearted, cartoonish, cute
prompt: macabre style of {prompt}. dark, gothic, grim, haunting, highly detailed
- name: misc-manga
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic,
Western comic style
prompt: manga style of {prompt}. vibrant, high-energy, detailed, iconic, Japanese
comic style
- name: misc-metropolis
negative_prompt: rural, natural, rustic, historical, simple
prompt: metropolis-themed of {prompt}. urban, cityscape, skyscrapers, modern, futuristic,
highly detailed
- name: misc-minimalist
negative_prompt: ornate, complicated, highly detailed, cluttered, disordered, messy,
noisy
prompt: minimalist style of {prompt}. simple, clean, uncluttered, modern, elegant
- name: misc-monochrome
negative_prompt: colorful, vibrant, noisy, blurry, deformed
prompt: monochrome of {prompt}. black and white, contrast, tone, texture, detailed
- name: misc-nautical
negative_prompt: landlocked, desert, mountains, urban, rustic
prompt: nautical-themed of {prompt}. sea, ocean, ships, maritime, beach, marine
life, highly detailed
- name: misc-space
negative_prompt: earthly, mundane, ground-based, realism
prompt: space-themed of {prompt}. cosmic, celestial, stars, galaxies, nebulas, planets,
science fiction, highly detailed
- name: misc-stained glass
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic
prompt: stained glass style of {prompt}. vibrant, beautiful, translucent, intricate,
detailed
- name: misc-techwear fashion
negative_prompt: vintage, rural, colorful, low contrast, realism, sketch, watercolor
prompt: techwear fashion of {prompt}. futuristic, cyberpunk, urban, tactical, sleek,
dark, highly detailed
- name: misc-tribal
negative_prompt: modern, futuristic, minimalist, pastel
prompt: tribal style of {prompt}. indigenous, ethnic, traditional patterns, bold,
natural colors, highly detailed
- name: misc-zentangle
negative_prompt: colorful, representative, simplistic, large fields of color
prompt: zentangle of {prompt}. intricate, abstract, monochrome, patterns, meditative,
highly detailed
- name: papercraft-collage
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic
prompt: collage style of {prompt}. mixed media, layered, textural, detailed, artistic
- name: papercraft-flat papercut
negative_prompt: 3D, high detail, noise, grainy, blurry, painting, drawing, photo,
disfigured
prompt: flat papercut style of {prompt}. silhouette, clean cuts, paper, sharp edges,
minimalist, color block
- name: papercraft-kirigami
negative_prompt: painting, drawing, 2D, noisy, blurry, deformed
prompt: kirigami representation of {prompt}. 3D, paper folding, paper cutting, Japanese,
intricate, symmetrical, precision, clean lines
- name: papercraft-paper mache
negative_prompt: 2D, flat, photo, sketch, digital art, deformed, noisy, blurry
prompt: paper mache representation of {prompt}. 3D, sculptural, textured, handmade,
vibrant, fun
- name: papercraft-paper quilling
negative_prompt: photo, painting, drawing, 2D, flat, deformed, noisy, blurry
prompt: paper quilling art of {prompt}. intricate, delicate, curling, rolling, shaping,
coiling, loops, 3D, dimensional, ornamental
- name: papercraft-papercut collage
negative_prompt: photo, 3D, realistic, drawing, painting, high detail, disfigured
prompt: papercut collage of {prompt}. mixed media, textured paper, overlapping,
asymmetrical, abstract, vibrant
- name: papercraft-papercut shadow box
negative_prompt: painting, drawing, photo, 2D, flat, high detail, blurry, noisy,
disfigured
prompt: 3D papercut shadow box of {prompt}. layered, dimensional, depth, silhouette,
shadow, papercut, handmade, high contrast
- name: papercraft-stacked papercut
negative_prompt: 2D, flat, noisy, blurry, painting, drawing, photo, deformed
prompt: stacked papercut art of {prompt}. 3D, layered, dimensional, depth, precision
cut, stacked layers, papercut, high contrast
- name: papercraft-thick layered papercut
negative_prompt: 2D, flat, thin paper, low stack, smooth texture, painting, drawing,
photo, deformed
prompt: thick layered papercut art of {prompt}. deep 3D, volumetric, dimensional,
depth, thick paper, high stack, heavy texture, tangible layers
- name: photo-alien
negative_prompt: earthly, mundane, common, realistic, simple
prompt: alien-themed of {prompt}. extraterrestrial, cosmic, otherworldly, mysterious,
sci-fi, highly detailed
- name: photo-film noir
negative_prompt: ugly, deformed, noisy, blurry, low contrast, realism, photorealistic,
vibrant, colorful
prompt: film noir style of {prompt}. monochrome, high contrast, dramatic shadows,
1940s style, mysterious, cinematic
- name: photo-hdr
negative_prompt: flat, low contrast, oversaturated, underexposed, overexposed, blurred,
noisy
prompt: HDR photo of {prompt}. High dynamic range, vivid, rich details, clear shadows
and highlights, realistic, intense, enhanced contrast, highly detailed
- name: photo-long exposure
negative_prompt: static, noisy, deformed, shaky, abrupt, flat, low contrast
prompt: long exposure photo of {prompt}. Blurred motion, streaks of light, surreal,
dreamy, ghosting effect, highly detailed
- name: photo-neon noir
negative_prompt: bright, sunny, daytime, low contrast, black and white, sketch,
watercolor
prompt: neon noir of {prompt}. cyberpunk, dark, rainy streets, neon signs, high
contrast, low light, vibrant, highly detailed
- name: photo-silhouette
negative_prompt: ugly, deformed, noisy, blurry, low contrast, color, realism, photorealistic
prompt: silhouette style of {prompt}. high contrast, minimalistic, black and white,
stark, dramatic
- name: photo-tilt-shift
negative_prompt: blurry, noisy, deformed, flat, low contrast, unrealistic, oversaturated,
underexposed
prompt: tilt-shift photo of {prompt}. Selective focus, miniature effect, blurred
background, highly detailed, vibrant, perspective control
- name: sai-anime
negative_prompt: photo, deformed, black and white, realism, disfigured, low contrast
prompt: anime artwork of {prompt}. anime style, key visual, vibrant, studio anime, highly
detailed
- name: sai-cinematic
negative_prompt: anime, cartoon, graphic, text, painting, crayon, graphite, abstract,
glitch, deformed, mutated, ugly, disfigured
prompt: cinematic film still of {prompt}. shallow depth of field, vignette, highly
detailed, high budget, bokeh, cinemascope, moody, epic, gorgeous, film grain,
grainy
- name: sai-comic book
negative_prompt: photograph, deformed, glitch, noisy, realistic, stock photo
prompt: comic of {prompt}. graphic illustration, comic art, graphic novel art, vibrant,
highly detailed
- name: sai-craft clay
negative_prompt: sloppy, messy, grainy, highly detailed, ultra textured, photo
prompt: play-doh style of {prompt}. sculpture, clay art, centered composition, Claymation
- name: sai-digital art
negative_prompt: photo, photorealistic, realism, ugly
prompt: concept art of {prompt}. digital artwork, illustrative, painterly, matte
painting, highly detailed
- name: sai-enhance
negative_prompt: ugly, deformed, noisy, blurry, distorted, grainy
prompt: breathtaking of {prompt}. award-winning, professional, highly detailed
- name: sai-fantasy art
negative_prompt: photographic, realistic, realism, 35mm film, dslr, cropped, frame,
text, deformed, glitch, noise, noisy, off-center, deformed, cross-eyed, closed
eyes, bad anatomy, ugly, disfigured, sloppy, duplicate, mutated, black and white
prompt: ethereal fantasy concept art of {prompt}. magnificent, celestial, ethereal,
painterly, epic, majestic, magical, fantasy art, cover art, dreamy
- name: sai-isometric
negative_prompt: deformed, mutated, ugly, disfigured, blur, blurry, noise, noisy,
realistic, photographic
prompt: isometric style of {prompt}. vibrant, beautiful, crisp, detailed, ultra
detailed, intricate
- name: sai-line art
negative_prompt: anime, photorealistic, 35mm film, deformed, glitch, blurry, noisy,
off-center, deformed, cross-eyed, closed eyes, bad anatomy, ugly, disfigured,
mutated, realism, realistic, impressionism, expressionism, oil, acrylic
prompt: line art drawing of {prompt}. professional, sleek, modern, minimalist, graphic,
line art, vector graphics
- name: sai-lowpoly
negative_prompt: noisy, sloppy, messy, grainy, highly detailed, ultra textured,
photo
prompt: low-poly style of {prompt}. low-poly game art, polygon mesh, jagged, blocky,
wireframe edges, centered composition
- name: sai-neonpunk
negative_prompt: painting, drawing, illustration, glitch, deformed, mutated, cross-eyed,
ugly, disfigured
prompt: neonpunk style of {prompt}. cyberpunk, vaporwave, neon, vibes, vibrant,
stunningly beautiful, crisp, detailed, sleek, ultramodern, magenta highlights,
dark purple shadows, high contrast, cinematic, ultra detailed, intricate, professional
- name: sai-origami
negative_prompt: noisy, sloppy, messy, grainy, highly detailed, ultra textured,
photo
prompt: origami style of {prompt}. paper art, pleated paper, folded, origami art,
pleats, cut and fold, centered composition
- name: sai-photographic
negative_prompt: drawing, painting, crayon, sketch, graphite, impressionist, noisy,
blurry, soft, deformed, ugly
prompt: cinematic photo of {prompt}. 35mm photograph, film, bokeh, professional,
4k, highly detailed
- name: sai-pixel art
negative_prompt: sloppy, messy, blurry, noisy, highly detailed, ultra textured,
photo, realistic
prompt: pixel-art of {prompt}. low-res, blocky, pixel art style, 8-bit graphics
- name: sai-texture
negative_prompt: ugly, deformed, noisy, blurry
prompt: texture of {prompt}top down close-up
+30 -7
View File
@@ -1,12 +1,35 @@
from .WF_nodes import NODE_CLASS_MAPPINGS as DENOISE_NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as DENOISE_NODE_DISPLAY_NAME_MAPPINGS
from .ModelParamsNode import NODE_CLASS_MAPPINGS as MODEL_NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as MODEL_NODE_DISPLAY_NAME_MAPPINGS
import traceback
# Ініціалізація порожніх словників
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
NODE_CLASS_MAPPINGS.update(DENOISE_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(MODEL_NODE_CLASS_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(DENOISE_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(MODEL_NODE_DISPLAY_NAME_MAPPINGS)
print(f"--- Initializing MultiModel --- ")
try:
# Спробуємо імпортувати ноди з ModelParamsNode.py
from .ModelParamsNode import NODE_CLASS_MAPPINGS as MODEL_NODE_CLASS_MAPPINGS
from .ModelParamsNode import NODE_DISPLAY_NAME_MAPPINGS as MODEL_NODE_DISPLAY_NAME_MAPPINGS
# Оновлення словників даними з ModelParamsNode.py
NODE_CLASS_MAPPINGS.update(MODEL_NODE_CLASS_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(MODEL_NODE_DISPLAY_NAME_MAPPINGS)
print("✅ MultiModel: Mappings imported and updated successfully.")
except ImportError as e:
print(f"❌ MultiModel: Failed to import from ModelParamsNode. ImportError: {e}")
print(traceback.format_exc())
except Exception as e:
print(f"❌ MultiModel: An unexpected error occurred during initialization: {e}")
print(traceback.format_exc())
# Видалено блок імпорту для logic.py, оскільки файли видалено
# Вказуємо ComfyUI, що в папці js є веб-файли
WEB_DIRECTORY = "js"
# Цей рядок залишиться, щоб показати, чи дійшов __init__.py до кінця
print(f"--- MultiModel initialization finished (check for errors above) --- ")
+121
View File
@@ -0,0 +1,121 @@
import { app } from "/scripts/app.js";
// Simple test log to confirm the file is loaded
// console.log("SIMPLE TEST: js/ModelParamsNode.js executed!");
console.log("%c[ModelParamsNode] JS файл завантажився! (v2-dynamic-inputs)", "color: white; background: #007acc; font-weight: bold; padding: 2px 8px; border-radius: 3px;");
// --- Loading js/ModelParamsNode.js --- v2-dynamic-inputs
// Helper function to fetch style names from the backend
async function fetchStyleNames(filename) {
// Return empty array immediately if filename is 'None' or invalid
if (!filename || filename === "None") {
return [];
}
try {
const response = await fetch('/multi_model/get_style_names', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ filename: filename })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status} - ${await response.text()}`);
}
const data = await response.json();
// console.log("Received style names:", data); // Keep commented
return data.style_names || []; // Return empty array on failure or no names
} catch (error) {
console.error(`[MultiModel Extension] Error fetching style names for ${filename}:`, error);
return []; // Return empty array on error
}
}
const ext = {
name: "MultiModel.PromptBuilderNode.StyleUpdater",
async setup(app) {
// console.log("MultiModel.PromptBuilderNode.StyleUpdater extension setup called");
},
async nodeCreated(node) {
// console.log(`MultiModel Extension: nodeCreated called for node: ${node.getTitle()} (Type: ${node.constructor.type})`);
// Check if this is the target node type we want to modify
if (node.constructor.type === "PromptBuilder") {
// console.log(`MultiModel Extension: Found target node type 'PromptBuilderNode': ${node.getTitle()}`);
const styleFileWidget = node.widgets.find(w => w.name === "style_file");
const styleNameWidget = node.widgets.find(w => w.name === "style_name");
if (!styleFileWidget) {
console.error("[MultiModel Extension] Could not find 'style_file' widget on PromptBuilderNode!");
return;
}
if (!styleNameWidget) {
console.error("[MultiModel Extension] Could not find 'style_name' widget on PromptBuilderNode!");
return;
}
// Force the widget type to combo, even if Python defined it as STRING
styleNameWidget.type = "combo";
// Ensure options object exists for combo box properties
if (!styleNameWidget.options) { styleNameWidget.options = {}; }
// Add a callback to handle selection changes for the style_name widget itself
styleNameWidget.callback = (value) => {
// Explicitly set the widget's value on selection
styleNameWidget.value = value;
console.log(`[MultiModel Extension] style_name selected and value set to: ${styleNameWidget.value}`);
// Optional: Force redraw if the value change doesn't update visually
// node.setDirtyCanvas(true, false);
};
// console.log("MultiModel Extension: Found 'style_file' and 'style_name' widgets.");
// Function to update style_name options
const updateStyleNames = async (selectedFilename) => {
const names = await fetchStyleNames(selectedFilename);
if (styleNameWidget.options) {
// console.log(`[MultiModel Extension] Updating styleNameWidget.options.values to:`, names);
styleNameWidget.options.values = names;
// Встановлюємо значення: або поточне (якщо воно є в новому списку), або перший елемент нового списку
// console.log(`[MultiModel Extension] Before setting value - current styleNameWidget.value: ${styleNameWidget.value}, styleNames includes current value: ${names.includes(styleNameWidget.value)}`);
styleNameWidget.value = names.includes(styleNameWidget.value) ? styleNameWidget.value : names[0];
// console.log(`[MultiModel Extension] After setting value - new styleNameWidget.value: ${styleNameWidget.value}`);
// Додаємо колбек, якщо його ще немає
if (!styleNameWidget.callback) {
styleNameWidget.callback = (value) => {
styleNameWidget.value = value;
console.log(`[MultiModel Extension] style_name selected and value set to: ${styleNameWidget.value}`);
};
}
} else {
console.error("[MultiModel Extension] 'style_name' widget does not have accessible options.");
}
};
const originalCallback = styleFileWidget.callback;
styleFileWidget.callback = async (value) => {
// console.log(`MultiModel Extension: style_file changed to: ${value}`);
await updateStyleNames(value);
if (originalCallback) {
originalCallback.call(styleFileWidget, value);
}
};
if (styleFileWidget.value) {
// console.log(`MultiModel Extension: Initializing style_names for file: ${styleFileWidget.value}`);
setTimeout(() => updateStyleNames(styleFileWidget.value), 100); // Keep timeout for initialization
} else {
console.warn("[MultiModel Extension] 'style_file' widget has no initial value on creation.");
}
}
}
};
app.registerExtension(ext);
+237
View File
@@ -0,0 +1,237 @@
// js/ModelParamsNodeExt.js
// Розширення для ModelParamsPipeNode, щоб він повідомляв про свої зміни
import { app } from "/scripts/app.js";
const TARGET_NODE_TYPE = "ModelParamsPipe"; // Цільовий тип вузла, який ми розширюємо
console.log("%c[ModelParamsNodeExt] JS файл завантажився! (v2-dynamic-inputs)", "color: white; background: #007acc; font-weight: bold; padding: 2px 8px; border-radius: 3px;");
// --- Loading js/ModelParamsNodeExt.js --- v2-dynamic-inputs
console.log(`--- Loading extension for ${TARGET_NODE_TYPE} ---`);
app.registerExtension({
name: "MultiModel.ModelParamsPipeNode.Notifier",
// Додатковий трекінг для діагностики
newNodesDetected: false,
// Додаємо базову ініціалізацію та прямий пошук вузлів
async setup() {
console.log(`[${TARGET_NODE_TYPE} Notifier] SETUP starting...`);
// Перевіряємо наявність сервісу одразу
if (app.HubNodesService) {
console.log(`[${TARGET_NODE_TYPE} Notifier] HubNodesService found during setup`);
} else {
console.error(`[${TARGET_NODE_TYPE} Notifier] HubNodesService NOT found during setup!`);
}
// Зачекаємо, доки граф буде завантажено, і потім проскануємо його
const checkForGraph = () => {
if (app.graph) {
console.log(`[${TARGET_NODE_TYPE} Notifier] Graph found in setup`);
// Додаємо обробник зміни графу
if (app.graph.onNodeAdded && !app.graph._notifier_attached) {
const origNodeAdded = app.graph.onNodeAdded;
app.graph.onNodeAdded = function(node) {
console.log(`[${TARGET_NODE_TYPE} Notifier] Graph onNodeAdded called for node ID: ${node.id}, type: ${node.type}`);
if (origNodeAdded) {
origNodeAdded.call(this, node);
}
// Перевіряємо, чи це наш цільовий тип
if (node.type === TARGET_NODE_TYPE) {
console.log(`[${TARGET_NODE_TYPE} Notifier] Target node added to graph: ID ${node.id}`);
// Викликаємо наш обробник
const extension = app.extensions.find(ext => ext.name === "MultiModel.ModelParamsPipeNode.Notifier");
if (extension) {
extension.setupNodeMonitoring(node);
if (app.HubNodesService) {
console.log(`[${TARGET_NODE_TYPE} Notifier] Notifying service about new graph node: ID ${node.id}`);
app.HubNodesService.notifyHubAdded(node);
// Пробуємо різні методи оновлення інтерфейсу
if (typeof app.HubNodesService.forceUpdates === 'function') {
app.HubNodesService.forceUpdates();
} else if (typeof app.HubNodesService.updateWidgets === 'function') {
app.HubNodesService.updateWidgets();
} else if (app.HubNodesService.refreshUI && typeof app.HubNodesService.refreshUI === 'function') {
app.HubNodesService.refreshUI();
}
}
}
}
};
app.graph._notifier_attached = true;
console.log(`[${TARGET_NODE_TYPE} Notifier] Attached graph.onNodeAdded handler`);
}
// Зробимо сканування графу з невеликою затримкою для певності
setTimeout(() => {
if (!app.graph || !app.graph._nodes) {
console.log(`[${TARGET_NODE_TYPE} Notifier] No graph nodes found yet`);
return;
}
const targetNodes = app.graph._nodes.filter(node => node.type === TARGET_NODE_TYPE);
console.log(`[${TARGET_NODE_TYPE} Notifier] Initial graph scan found ${targetNodes.length} target nodes`);
const extension = app.extensions.find(ext => ext.name === "MultiModel.ModelParamsPipeNode.Notifier");
if (!extension) {
console.error(`[${TARGET_NODE_TYPE} Notifier] Extension not found in array!`);
return;
}
// Обробляємо знайдені вузли
for (const node of targetNodes) {
extension.setupNodeMonitoring(node);
// Якщо знайшли хоча б один вузол, спробуємо оновити інтерфейс
if (app.HubNodesService) {
console.log(`[${TARGET_NODE_TYPE} Notifier] Found existing node in scan: ID ${node.id}`);
}
}
// Якщо було знайдено вузли, форсуємо оновлення
if (targetNodes.length > 0 && app.HubNodesService) {
console.log(`[${TARGET_NODE_TYPE} Notifier] Forcing update after scan found ${targetNodes.length} nodes`);
if (typeof app.HubNodesService.forceUpdates === 'function') {
app.HubNodesService.forceUpdates();
} else if (typeof app.HubNodesService.updateWidgets === 'function') {
app.HubNodesService.updateWidgets();
} else if (app.HubNodesService.refreshUI && typeof app.HubNodesService.refreshUI === 'function') {
app.HubNodesService.refreshUI();
}
}
}, 500);
} else {
console.log(`[${TARGET_NODE_TYPE} Notifier] Graph not available yet, will retry...`);
setTimeout(checkForGraph, 500);
}
};
// Запускаємо перевірку графу
checkForGraph();
console.log(`[${TARGET_NODE_TYPE} Notifier] SETUP completed`);
},
// Встановлення моніторингу title/видалення для вузла
setupNodeMonitoring(node, appInstance = app) {
if (!node) return false;
console.log(`[${TARGET_NODE_TYPE} Notifier] Setting up monitoring for node ID: ${node.id}`);
// --- Встановлюємо моніторинг title ---
if (!node.__title_monitored) {
console.log(`[${TARGET_NODE_TYPE} Notifier] Setting up title monitoring for node ID: ${node.id}`);
try {
const originalTitle = node.title;
// Зберігаємо оригінальне значення
Object.defineProperty(node, '_monitored_title', {
value: originalTitle,
writable: true
});
// Перевизначаємо геттер/сеттер для title
Object.defineProperty(node, 'title', {
get: function() {
return this._monitored_title;
},
set: function(newTitle) {
console.log(`---> [${TARGET_NODE_TYPE} Notifier] title property CHANGED: "${this._monitored_title}" → "${newTitle}" for node ID: ${this.id}`);
// Якщо значення не змінилось, не виконуємо додаткові дії
if (this._monitored_title === newTitle) {
console.log(` Title not changed (same value), skipping notification.`);
return;
}
// Зберігаємо нове значення
this._monitored_title = newTitle;
// Повідомляємо сервіс про перейменування з невеликою затримкою
setTimeout(() => {
if (appInstance.HubNodesService) {
console.log(` Notifying service about rename for node ID: ${this.id}`);
appInstance.HubNodesService.notifyHubRenamed(this);
} else {
console.error(` HubNodesService not found! Cannot notify rename.`);
}
}, 0);
},
enumerable: true,
configurable: true
});
node.__title_monitored = true;
console.log(`[${TARGET_NODE_TYPE} Notifier] Title property monitored for node ID: ${node.id}`);
} catch (error) {
console.error(`[${TARGET_NODE_TYPE} Notifier] Error setting up title monitoring:`, error);
}
}
// --- Встановлюємо перехоплення видалення ---
if (!node.onRemoved || !node.onRemoved.__isNotifierOverridden) {
try {
const originalOnRemoved = node.onRemoved;
node.onRemoved = function() {
console.log(`[${TARGET_NODE_TYPE} Notifier] Node removed: ID ${this.id}`);
// Повідомляємо сервіс про видалення
if (appInstance.HubNodesService) {
console.log(` Notifying service about removal for node ID: ${this.id}`);
appInstance.HubNodesService.notifyHubRemoved(this);
} else {
console.error(` HubNodesService not found! Cannot notify removal.`);
}
// Викликаємо оригінальний метод, якщо він існує
if (originalOnRemoved) {
originalOnRemoved.apply(this, arguments);
}
};
node.onRemoved.__isNotifierOverridden = true;
console.log(`[${TARGET_NODE_TYPE} Notifier] onRemoved overridden for node ID: ${node.id}`);
} catch (error) {
console.error(`[${TARGET_NODE_TYPE} Notifier] Error overriding onRemoved:`, error);
}
}
return true;
},
// Для логування проблемних випадків
async nodeCreated(node) {
console.log(`[${TARGET_NODE_TYPE} Notifier] nodeCreated called for node type: ${node ? node.type : 'undefined'}, ID: ${node ? node.id : 'undefined'}`);
},
// Для логування проблемних випадків
async onNodeAdded(node) {
console.log(`[${TARGET_NODE_TYPE} Notifier] onNodeAdded called for node type: ${node ? node.type : 'undefined'}, ID: ${node ? node.id : 'undefined'}`);
},
// Хук для обробки вузлів, що вже існують при завантаженні
async loadedGraphNode(node, appInstance) {
if (!node) return;
console.log(`[${TARGET_NODE_TYPE} Notifier] loadedGraphNode called for node type: ${node.type}, ID: ${node.id}`);
if (node.type === TARGET_NODE_TYPE) {
// Встановлюємо моніторинг для існуючого вузла
this.setupNodeMonitoring(node, appInstance);
}
}
});
console.log(`--- Extension for ${TARGET_NODE_TYPE} loaded ---`);
+127
View File
@@ -0,0 +1,127 @@
import { app } from "../../../scripts/app.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
// Simple debounce function
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Use the class name registered in NODE_CLASS_MAPPINGS
const nodeClassName = "MySwitchIndex";
app.registerExtension({
name: "MultiModel.MySwitchIndex",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// Compare with the class name
if (nodeData.name === nodeClassName) {
console.log(`[MultiModel] Patching ${nodeClassName}`);
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
onNodeCreated?.apply(this, arguments);
console.log(`[MultiModel] ${nodeClassName} created`);
// Find widgets (should exist based on Python definition)
const modeWidget = this.widgets.find(w => w.name === "mode");
const indexWidget = this.widgets.find(w => w.name === "index");
if (!modeWidget) {
console.error(`[MultiModel] ${nodeClassName}: 'mode' widget not found! Cannot apply dynamic visibility.`);
return;
}
if (!indexWidget) {
console.error(`[MultiModel] ${nodeClassName}: 'index' widget not found! Cannot apply dynamic visibility.`);
return;
}
// Function to update index widget visibility
const updateVisibility = (modeValue) => {
if (indexWidget) {
const shouldBeHidden = (modeValue !== "index"); // Поле повинно бути приховане, якщо режим НЕ "index"
// Завжди встановлюємо правильний стан `hidden`
indexWidget.hidden = shouldBeHidden;
console.log(`[MultiModel] ${nodeClassName}: Setting index widget hidden to: ${indexWidget.hidden} (mode is ${modeValue})`);
// Запитуємо перемалювання, щоб зміни відобразились
app.graph.setDirtyCanvas(true, false);
// Можливо, знадобиться примусове оновлення розміру, якщо ComfyUI не робить це автоматично при зміні hidden
// this.computeSize();
}
};
// Store original callback and wrap it
const originalModeCallback = modeWidget.callback;
modeWidget.callback = (value) => {
console.log(`[MultiModel] ${nodeClassName}: Mode widget callback triggered with value: ${value}`);
// Викликаємо оригінальний callback, якщо він існує
originalModeCallback?.apply(modeWidget, [value]);
updateVisibility(value);
};
// Set initial visibility based on the current mode value
// Викликаємо відразу після ініціалізації віджетів
console.log(`[MultiModel] ${nodeClassName}: Setting initial visibility for mode: ${modeWidget.value}`);
updateVisibility(modeWidget.value);
// --- Keep dynamic input logic ---
// We might want this active even in 'index' mode if the user wants
// to visually see which inputs are available, though they aren't used
// for selection in that mode.
this.updateInputs(); // Initial call to potentially add the first input
};
// --- Dynamic input logic (kept mostly as is) ---
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function(type, index, connected, link_info, slot) {
onConnectionsChange?.apply(this, arguments);
this.scheduleUpdateInputs();
};
nodeType.prototype.scheduleUpdateInputs = debounce(function() {
this.updateInputs();
}, 100);
nodeType.prototype.updateInputs = function() {
const prefix = "input_";
const minInputs = 1;
let lastConnectedIndex = -1;
for (let i = 0; i < this.inputs.length; i++) {
if (this.inputs[i].name.startsWith(prefix) && this.inputs[i].link != null) {
lastConnectedIndex = i;
}
}
let removalIndex = this.inputs.length - 1;
while (removalIndex > lastConnectedIndex && this.inputs.length > minInputs && removalIndex >= 0) {
if (this.inputs[removalIndex].name.startsWith(prefix) && this.inputs[removalIndex].link == null) {
if(this.inputs.length > minInputs) {
console.log(`[MultiModel] Removing input ${this.inputs[removalIndex].name}`);
this.removeInput(removalIndex);
} else {
break;
}
} else {
break;
}
removalIndex--;
}
// Add a new input if the last one is connected OR if there are no inputs yet
if (this.inputs.length === 0 || (this.inputs[this.inputs.length - 1].name.startsWith(prefix) && this.inputs[this.inputs.length - 1].link != null)) {
const nextIndex = this.inputs.length + 1; // 1-based index for name
const name = `${prefix}${nextIndex}`;
console.log(`[MultiModel] Adding input ${name}`);
this.addInput(name, "*");
}
app.graph.setDirtyCanvas(true, true);
};
}
},
});
+738
View File
@@ -0,0 +1,738 @@
console.log("%c[ActiveModel] JS файл завантажився! (v2-dynamic-inputs)", "color: white; background: #007acc; font-weight: bold; padding: 2px 8px; border-radius: 3px;");
// --- Loading js/active_model.js --- v2-dynamic-inputs
import { app } from "/scripts/app.js"; // Виправлено шлях на абсолютний
// Цільовий тип вузла, який ми шукаємо
const TARGET_NODE_TYPE = "ModelParamsPipe";
// Режими роботи вузлів ComfyUI/LiteGraph
const MODE_ALWAYS = 0; // Завжди активний
const MODE_BYPASS = 4; // Режим обходу (Bypass)
const NODE_TITLE = "Active Model"; // Змінено заголовок
const NODE_TYPE = "ActiveModel"; // І тип
class ActiveModel {
// Налаштування для LiteGraph
constructor() {
// Цей вузол є переважно контейнером для дій
this.properties = {};
console.log(`[${ActiveModel.title}] CONSTRUCTOR CALLED.`);
// Спробуємо знайти та вивести вузли одразу при створенні
this.logTargetNodeNames();
}
// Статичні властивості для визначення типу вузла
static title = "Active Model";
static type = "ActiveModel"; // Унікальний тип для цього вузла
// Функція для пошуку та логування імен цільових вузлів
logTargetNodeNames() {
console.log(`[${ActiveModel.title}] Searching for nodes of type '${TARGET_NODE_TYPE}'...`);
const nodes = app.graph._nodes.filter(node => node.type === TARGET_NODE_TYPE);
console.log(`[${ActiveModel.title}] Found ${nodes.length} nodes:`);
if (nodes.length > 0) {
nodes.forEach(n => console.log(` - Node ID: ${n.id}, Title: ${n.title}, Current mode: ${n.mode}`));
} else {
console.log(` (No nodes found)`);
}
return nodes; // Повертаємо для можливого використання в onAction
}
// Метод для встановлення режиму для всіх знайдених цільових вузлів
setHubsMode(mode) {
const nodes = this.logTargetNodeNames();
if (nodes.length === 0) {
console.log(`[${ActiveModel.title}] No target nodes found to set mode.`);
return; // Виходимо, якщо немає цільових вузлів
}
const modeStr = mode === MODE_ALWAYS ? "ALWAYS" : (mode === MODE_BYPASS ? "BYPASS" : mode);
console.log(`[${ActiveModel.title}] Attempting to set mode to ${modeStr} for ${nodes.length} nodes.`);
let changed = false;
for (const node of nodes) {
console.log(` - Processing Node ID: ${node.id}, Title: ${node.title}, Current mode: ${node.mode}`);
if (node.mode !== mode) {
node.mode = mode;
console.log(` - Mode set to: ${node.mode}`);
changed = true;
} else {
console.log(` - Mode already set to ${node.mode}. Skipping.`);
}
}
if (changed) {
console.log(`[${ActiveModel.title}] Triggering canvas redraw.`);
app.graph.setDirtyCanvas(true, true);
} else {
console.log(`[${ActiveModel.title}] No modes were changed. Skipping redraw.`);
}
}
// Спрощена обробка дій для тестування
onAction(action) {
console.log(`[${ActiveModel.title}] === onAction ENTERED === Action: ${action}`);
// Просто логуємо знайдені вузли ще раз при виклику дії
this.logTargetNodeNames();
// Логіку зміни режимів поки що прибрали
// if (action === "Bypass All Hubs") { ... }
}
// Цей вузол не виконує обчислень, тому onExecute може бути порожнім
onExecute() {
// console.log(`[${ActiveModel.title}] Executed (likely no-op).`);
}
// Статичний метод для налаштування та реєстрації вузла
static setUp(clazz) {
// Явно встановлюємо статичні властивості, які потрібні LiteGraph
clazz.title = ActiveModel.title;
clazz.type = ActiveModel.type;
clazz.title_mode = LiteGraph.NORMAL_TITLE;
clazz.collapsable = true;
clazz.size = [240, 50];
clazz.category = "MultiModel"; // Змінюємо категорію на основну
console.log(`[${ActiveModel.title}] Attempting to register node type via setUp: ${clazz.type}`);
try {
// Реєструємо сам клас
LiteGraph.registerNodeType(clazz.type, clazz);
console.log(`[${ActiveModel.title}] Node type registration via setUp SUCCESSFUL.`);
} catch (e) {
console.error(`[${ActiveModel.title}] Node type registration via setUp FAILED:`, e);
}
}
}
// Визначаємо дії, доступні в контекстному меню
ActiveModel.exposedActions = ["Log Hub Names", "Enable All Hubs", "Bypass All Hubs", "Toggle All Hubs"];
// Визначаємо статичні властивості title та type тут, щоб вони були доступні для setUp
ActiveModel.title = "Active Model";
ActiveModel.type = "ActiveModel";
// --- Допоміжні функції --- //
// Функція пошуку цільових вузлів (тепер отримує їх з сервісу)
function findTargetNodes() {
return HubNodesService.getHubNodes();
}
// Функція зміни режиму ОДНОГО вузла
function setHubMode(targetNode, mode) {
if (!targetNode) return;
const currentMode = targetNode.mode;
const modeStr = mode === MODE_ALWAYS ? "ON" : "OFF";
console.log(`[${NODE_TITLE}] Setting Node ID ${targetNode.id} ('${targetNode.title}') to mode ${modeStr}`);
// Якщо ми вмикаємо вузол (MODE_ALWAYS), і при цьому поточний режим НЕ MODE_ALWAYS
// (тобто вузол був вимкнений, а тепер ми його вмикаємо)
if (mode === MODE_ALWAYS && currentMode !== MODE_ALWAYS) {
// Потрібно перевірити, чи є інші активні вузли, і вимкнути їх
const allHubNodes = HubNodesService.getHubNodes();
const otherActiveNodes = allHubNodes.filter(n => n.id !== targetNode.id && n.mode === MODE_ALWAYS);
if (otherActiveNodes.length > 0) {
console.log(`[${NODE_TITLE}] Found ${otherActiveNodes.length} other active nodes, disabling them...`);
otherActiveNodes.forEach(node => {
node.mode = MODE_BYPASS;
console.log(`[${NODE_TITLE}] Disabled node ID ${node.id} ('${node.title}')`);
// Оновлюємо віджети всіх байпасерів для цього вузла
updateAllBypasserWidgets(node);
});
}
}
// Змінюємо режим цільового вузла, незалежно від того, чи є інші активні вузли
if (currentMode !== mode) {
targetNode.mode = mode;
app.graph.setDirtyCanvas(true, true);
// Також оновлюємо віджети для цього вузла
updateAllBypasserWidgets(targetNode);
}
}
// Нова функція для оновлення віджетів у всіх байпасерах для конкретного вузла
function updateAllBypasserWidgets(targetNode) {
if (!targetNode) return;
console.log(`[${NODE_TITLE}] Updating widgets for node ID: ${targetNode.id} in all bypassers`);
const bypassers = HubNodesService.bypasserNodes;
bypassers.forEach(bypasser => {
if (!bypasser || !bypasser.widgets) return;
const widgetName = `hub_${targetNode.id}`;
const widget = bypasser.widgets.find(w => w.name === widgetName);
if (widget) {
// Оновлюємо значення віджета
const isBypassed = targetNode.mode === MODE_BYPASS;
const newValue = !isBypassed;
// Оновлюємо також заголовок, щоб він відповідав новому формату
const newLabel = `${targetNode.title || '(Untitled)'}`;
let changed = false;
if (widget.value !== newValue) {
console.log(`[${NODE_TITLE}] Updating widget value in bypasser ID: ${bypasser.id} for node ID: ${targetNode.id} to: ${newValue}`);
widget.value = newValue;
changed = true;
}
if (widget.label !== newLabel) {
console.log(`[${NODE_TITLE}] Updating widget label in bypasser ID: ${bypasser.id} for node ID: ${targetNode.id} to: "${newLabel}"`);
widget.label = newLabel;
changed = true;
}
if (changed) {
bypasser.graph.setDirtyCanvas(true, false);
}
}
});
}
// --- Сервіс для керування вузлами --- //
const HubNodesService = {
hubNodes: [],
bypasserNodes: [],
updateTimeout: null,
updateDelay: 100,
app: null, // Додано для доступу до app
registerBypasserNode(node) {
if (!this.bypasserNodes.find(n => n.id === node.id)) {
console.log(`[HubNodesService] Registering bypasser node ID: ${node.id}`);
this.bypasserNodes.push(node);
this.scheduleUpdate(`registerBypasser_${node.id}`);
}
},
unregisterBypasserNode(node) {
const index = this.bypasserNodes.findIndex(n => n.id === node.id);
if (index !== -1) {
console.log(`[HubNodesService] Unregistering bypasser node ID: ${node.id}`);
this.bypasserNodes.splice(index, 1);
// Оновлення не потрібне при видаленні самого байпасера
} else {
console.log(`[HubNodesService] Node ID ${node.id} not found for unregistration.`);
}
},
getHubNodes() {
return [...this.hubNodes];
},
// --- Нові методи для сповіщень від Hub --- //
notifyHubAdded(node) {
if (!node) return;
console.log(`[HubNodesService] Hub node added notification received: ID ${node.id}`);
if (!this.hubNodes.find(n => n.id === node.id)) {
this.hubNodes.push(node);
this.hubNodes.sort((a, b) => a.id - b.id); // Підтримуємо сортування
this.scheduleUpdate(`notifyHubAdded_${node.id}`);
} else {
console.log(`[HubNodesService] Hub node ID ${node.id} already exists.`);
}
},
notifyHubRemoved(node) {
if (!node) return;
console.log(`[HubNodesService] Hub node removed notification received: ID ${node.id}`);
const index = this.hubNodes.findIndex(n => n.id === node.id);
if (index !== -1) {
this.hubNodes.splice(index, 1);
this.scheduleUpdate(`notifyHubRemoved_${node.id}`);
} else {
console.log(`[HubNodesService] Hub node ID ${node.id} not found for removal notification.`);
}
},
notifyHubRenamed(node) {
if (!node) return;
console.log(`[HubNodesService] Hub node rename notification received: ID ${node.id}`);
// Сам список hubNodes містить посилання на об'єкти вузлів,
// тому оновлювати список не потрібно, зміна title вже відбулася.
// Просто плануємо оновлення віджетів, щоб відобразити нову назву.
this.scheduleUpdate(`notifyHubRenamed_${node.id}`);
},
// --- Кінець нових методів --- //
scheduleUpdate(reason = "unknown") {
console.log(`[HubNodesService] Scheduling update. Reason: ${reason}`);
if (this.updateTimeout) {
clearTimeout(this.updateTimeout);
}
this.updateTimeout = setTimeout(() => {
console.log("[HubNodesService] Timeout reached. Running update...");
this.updateTimeout = null;
// Оновлення списку hubNodes більше не потрібне тут
// this.refreshHubNodes();
this.bypasserNodes.forEach(node => {
if (node.refreshWidgets) {
console.log(`[HubNodesService] Refreshing widgets for bypasser node ID: ${node.id}`);
node.refreshWidgets();
} else {
console.warn(`[HubNodesService] Bypasser node ID: ${node.id} missing refreshWidgets method.`);
}
});
console.log("[HubNodesService] Update finished.");
}, this.updateDelay);
}
};
app.registerExtension({
name: "MultiModel.ActiveModel.ServiceDriven", // Оновлено назву розширення
setup(appInstance) {
console.log(`[${NODE_TITLE}] === Extension setup CALLED ===`);
// Реєструємо сервіс, щоб він був доступний іншим розширенням
appInstance.HubNodesService = HubNodesService;
HubNodesService.app = appInstance;
// Додаємо перехоплення onNodeAdded для відстеження додавання нашої ноди
if (appInstance.graph && !appInstance.graph._activemodel_onNodeAdded_attached) {
const origNodeAdded = appInstance.graph.onNodeAdded;
appInstance.graph.onNodeAdded = function(node) {
// Спочатку викликаємо оригінальний обробник
if (origNodeAdded) {
origNodeAdded.call(this, node);
}
// Перевіряємо, чи це наша нода ActiveModel
if (node.type === NODE_TYPE) {
console.log(`[${NODE_TITLE}] Our node type was added to graph: ID ${node.id}`);
// Викликаємо обробку ноди для додавання віджетів
const extension = appInstance.extensions.find(ext => ext.name === "MultiModel.ActiveModel.ServiceDriven");
if (extension) {
// Конфігуруємо ноду
extension.loadedGraphNode(node, appInstance);
// Оновлюємо віджети з невеликою затримкою
setTimeout(() => {
if (node.refreshWidgets) {
console.log(`[${NODE_TITLE}] Running refreshWidgets for newly added node ID: ${node.id}`);
node.refreshWidgets();
}
}, 100);
}
}
};
appInstance.graph._activemodel_onNodeAdded_attached = true;
console.log(`[${NODE_TITLE}] Attached our onNodeAdded handler to graph`);
}
// Початкове сканування потрібне, щоб знайти вузли, які вже існують при завантаженні
setTimeout(() => {
console.log("[HubNodesService] Initial node scan after setup.");
// Знаходимо існуючі Hub та Bypasser ноди і реєструємо їх
let initialHubsFound = 0;
let initialBypassersFound = 0;
appInstance.graph._nodes.forEach(node => {
if (node.type === TARGET_NODE_TYPE) {
HubNodesService.notifyHubAdded(node);
initialHubsFound++;
} else if (node.type === NODE_TYPE) {
// Реєстрація bypasser'а відбувається також у loadedGraphNode,
// але продублюємо тут на випадок проблем з порядком
HubNodesService.registerBypasserNode(node);
initialBypassersFound++;
// Додаємо додаткову ініціалізацію для вже існуючих Bypasser нод
this.loadedGraphNode(node, appInstance);
}
});
console.log(`[HubNodesService] Initial scan found ${initialHubsFound} hubs and ${initialBypassersFound} bypassers.`);
// Додаємо окремий виклик для оновлення віджетів для всіх bypassers з більшою затримкою
if (initialBypassersFound > 0) {
setTimeout(() => {
console.log(`[${NODE_TITLE}] Refreshing widgets for ${initialBypassersFound} bypassers after initial scan`);
HubNodesService.bypasserNodes.forEach(node => {
if (node && node.refreshWidgets) {
node.refreshWidgets();
}
});
}, 500);
}
// Оновлення віджетів запланується всередині notifyHubAdded/registerBypasserNode
}, 500);
},
// loadedGraphNode тепер відповідає ТІЛЬКИ за конфігурацію САМОГО ActiveModel
loadedGraphNode(node, appInstance) {
if (node.type === NODE_TYPE) {
console.log(`[${NODE_TITLE}] Configuring node instance ID: ${node.id}`);
node.app = appInstance;
node.IS_BYPASSER_NODE = true;
delete node.onAction; // Видаляємо, бо додамо getActions
// Визначаємо refreshWidgets
node.refreshWidgets = function() {
console.log(`[${NODE_TITLE} ID: ${this.id}] Running refreshWidgets...`);
const targetNodes = findTargetNodes(); // Отримуємо з сервісу
console.log(`[${NODE_TITLE} ID: ${this.id}] Got ${targetNodes.length} target nodes from service.`);
// Додаємо детальну діагностику
if (targetNodes.length === 0) {
console.log(`[${NODE_TITLE} ID: ${this.id}] No target nodes found. HubNodesService state:`,
{hubNodesCount: HubNodesService.hubNodes.length,
bypassersCount: HubNodesService.bypasserNodes.length});
} else {
console.log(`[${NODE_TITLE} ID: ${this.id}] Target nodes details:`,
targetNodes.map(n => ({id: n.id, title: n.title, mode: n.mode})));
}
// --- Подальша логіка залишається ТАКОЮ Ж САМОЮ, як і раніше ---
// (Порівняння віджетів, додавання, видалення, оновлення)
const currentWidgets = this.widgets ? [...this.widgets] : [];
let widgetsChanged = false;
const activeWidgetNames = new Set();
// Add/Update widgets
targetNodes.forEach(targetNode => {
if (!targetNode) {
console.warn(`[${NODE_TITLE} ID: ${this.id}] Found a null/undefined targetNode in the list from service. Skipping.`);
return; // Пропускаємо невалідні ноди
}
const widgetName = `hub_${targetNode.id}`;
// Прибираємо ID ноди з заголовка
const widgetLabel = `${targetNode.title || '(Untitled)'}`;
const isBypassed = targetNode.mode === MODE_BYPASS;
const widgetValue = !isBypassed;
activeWidgetNames.add(widgetName);
let existingWidget = this.widgets?.find(w => w.name === widgetName);
if (existingWidget) {
let labelChanged = false;
if (existingWidget.label !== widgetLabel) {
existingWidget.label = widgetLabel;
labelChanged = true;
}
if (existingWidget.value !== widgetValue) {
existingWidget.value = widgetValue;
widgetsChanged = true;
}
if (labelChanged) widgetsChanged = true;
if (!existingWidget.callback) {
existingWidget.callback = (value) => {
// Нова логіка: якщо перемикач вмикається, вимикаємо всі інші
// якщо вимикається - просто вимикаємо
const nodeToChange = app.graph.getNodeById(targetNode.id);
if (nodeToChange) setHubMode(nodeToChange, value ? MODE_ALWAYS : MODE_BYPASS);
};
widgetsChanged = true;
}
} else {
this.addWidget(
"toggle", widgetName, widgetValue,
(value) => {
// Нова логіка: дозволяємо будь-які переключення
const nodeToChange = app.graph.getNodeById(targetNode.id);
if (nodeToChange) setHubMode(nodeToChange, value ? MODE_ALWAYS : MODE_BYPASS);
else this.refreshWidgets();
},
{ on: "ON", off: "OFF" }
);
this.widgets[this.widgets.length - 1].label = widgetLabel;
widgetsChanged = true;
}
});
// Remove outdated widgets
let removedWidgets = false;
if (this.widgets) {
for (let i = this.widgets.length - 1; i >= 0; i--) {
const widget = this.widgets[i];
if (widget.name?.startsWith("hub_") && !activeWidgetNames.has(widget.name)) {
this.widgets.splice(i, 1);
widgetsChanged = true;
removedWidgets = true;
}
}
}
// Resize node only if widgets were added/removed
if (widgetsChanged) {
// ... (логіка зміни розміру залишається такою ж) ...
const baseHeight = LiteGraph.NODE_TITLE_HEIGHT + 5;
const widgetHeight = LiteGraph.NODE_WIDGET_HEIGHT;
const spacing = 4;
const hubWidgets = this.widgets?.filter(w => w.name?.startsWith("hub_")) || [];
const numWidgets = hubWidgets.length;
const requiredHeight = baseHeight + numWidgets * (widgetHeight + spacing) + (numWidgets > 0 ? spacing : 0);
const computedSize = this.computeSize();
const newHeight = Math.max(computedSize[1], requiredHeight);
if (Math.abs(this.size[1] - newHeight) > 2 || removedWidgets) {
this.size[1] = newHeight;
this.graph.setDirtyCanvas(true, true);
} else if (widgetsChanged) {
this.graph.setDirtyCanvas(true, false);
}
}
// Перевірка на кількість активних вузлів після оновлення віджетів
setTimeout(() => {
const activeNodes = targetNodes.filter(node => node.mode === MODE_ALWAYS);
console.log(`[${NODE_TITLE} ID: ${this.id}] After refresh: ${activeNodes.length} active nodes`);
// Якщо більше одного активного вузла, залишаємо тільки перший
// Але не активуємо автоматично вузли, якщо всі вимкнені
if (activeNodes.length > 1) {
console.log(`[${NODE_TITLE} ID: ${this.id}] Multiple active nodes (${activeNodes.length}), keeping only the first one...`);
// Залишаємо перший вузол активним, інші вимикаємо
for (let i = 1; i < activeNodes.length; i++) {
setHubMode(activeNodes[i], MODE_BYPASS);
// Оновлюємо відповідний віджет
const widgetName = `hub_${activeNodes[i].id}`;
const widget = this.widgets?.find(w => w.name === widgetName);
if (widget) {
widget.value = false;
}
}
this.graph.setDirtyCanvas(true, false);
}
}, 100);
console.log(`[${NODE_TITLE} ID: ${this.id}] refreshWidgets finished.`);
};
// Визначаємо дії для контекстного меню
node.getActions = function() {
const actions = [];
if (this.widgets?.some(w => w.name.startsWith("hub_"))) {
actions.push({ name: "Activate First Hub", callback: () => this.setAllHubsMode(MODE_ALWAYS) });
actions.push({ name: "Switch To Next Hub", callback: () => this.toggleAllHubsMode() });
}
actions.push({ name: "Log Hub Names (Console)", callback: () => this.logTargetNodeNames() });
return actions;
};
node.logTargetNodeNames = function() { // Додаємо метод до екземпляра
console.log(`[${NODE_TITLE} ID: ${this.id}] Logging target node names...`);
const nodes = HubNodesService.getHubNodes();
console.log(` Found ${nodes.length} nodes via service:`);
if (nodes.length > 0) {
nodes.forEach(n => console.log(` - Node ID: ${n.id}, Title: ${n.title}, Current mode: ${n.mode}`));
} else {
console.log(` (No nodes found)`);
}
};
// Оновлений метод для встановлення режиму всіх хабів
node.setAllHubsMode = function(mode) {
console.log(`[${NODE_TITLE} ID: ${this.id}] setAllHubsMode called with mode: ${mode}`);
const hubNodes = HubNodesService.getHubNodes();
if (hubNodes.length === 0) {
console.log(`[${NODE_TITLE} ID: ${this.id}] No hub nodes found to set mode.`);
return;
}
if (mode === MODE_ALWAYS) {
// Активуємо тільки перший вузол, інші вимикаємо
console.log(`[${NODE_TITLE} ID: ${this.id}] Enabling only the first hub node, disabling others.`);
hubNodes.forEach((node, index) => {
const newMode = index === 0 ? MODE_ALWAYS : MODE_BYPASS;
if (node.mode !== newMode) {
node.mode = newMode;
updateAllBypasserWidgets(node);
}
});
app.graph.setDirtyCanvas(true, true);
} else if (mode === MODE_BYPASS) {
// Якщо режим BYPASS, вимикаємо всі вузли
console.log(`[${NODE_TITLE} ID: ${this.id}] Disabling all hub nodes.`);
let changed = false;
hubNodes.forEach(node => {
if (node.mode !== MODE_BYPASS) {
node.mode = MODE_BYPASS;
updateAllBypasserWidgets(node);
changed = true;
}
});
if (changed) {
app.graph.setDirtyCanvas(true, true);
}
}
};
// Оновлений метод для перемикання вузлів
node.toggleAllHubsMode = function() {
console.log(`[${NODE_TITLE} ID: ${this.id}] toggleAllHubsMode called`);
const hubNodes = HubNodesService.getHubNodes();
if (hubNodes.length === 0) {
console.log(`[${NODE_TITLE} ID: ${this.id}] No hub nodes found to toggle.`);
return;
}
// Перевіряємо, чи є хоча б один активний вузол
const activeNodes = hubNodes.filter(n => n.mode === MODE_ALWAYS);
if (activeNodes.length === 0) {
// Якщо немає активних вузлів, активуємо перший
console.log(`[${NODE_TITLE} ID: ${this.id}] No active nodes, activating the first one.`);
if (hubNodes.length > 0) {
const node = hubNodes[0];
node.mode = MODE_ALWAYS;
updateAllBypasserWidgets(node);
app.graph.setDirtyCanvas(true, true);
}
} else {
// Якщо є активний вузол, знаходимо його індекс і активуємо наступний
const activeNode = activeNodes[0];
const currentIndex = hubNodes.findIndex(n => n.id === activeNode.id);
// Вимикаємо поточний активний вузол
activeNode.mode = MODE_BYPASS;
updateAllBypasserWidgets(activeNode);
// Знаходимо наступний вузол (або переходимо до першого, якщо це останній)
const nextIndex = (currentIndex + 1) % hubNodes.length;
const nextNode = hubNodes[nextIndex];
console.log(`[${NODE_TITLE} ID: ${this.id}] Toggling from node ID ${activeNode.id} to node ID ${nextNode.id}`);
// Активуємо наступний вузол
nextNode.mode = MODE_ALWAYS;
updateAllBypasserWidgets(nextNode);
app.graph.setDirtyCanvas(true, true);
}
};
// Реєстрація/видалення самого bypasser'а в сервісі
node.onAdded = function() {
console.log(`[${NODE_TITLE} ID: ${this.id}] Bypasser node added to graph. Registering with service.`);
if (appInstance.HubNodesService) { // Перевіряємо наявність сервісу
appInstance.HubNodesService.registerBypasserNode(this);
} else {
console.error(`[${NODE_TITLE} ID: ${this.id}] Cannot register bypasser, HubNodesService not found!`);
}
};
node.onRemoved = function() {
console.log(`[${NODE_TITLE} ID: ${this.id}] Bypasser node removed from graph. Unregistering from service.`);
if (appInstance.HubNodesService) {
appInstance.HubNodesService.unregisterBypasserNode(this);
} else {
console.error(`[${NODE_TITLE} ID: ${this.id}] Cannot unregister bypasser, HubNodesService not found!`);
}
};
// Початкова реєстрація, якщо вузол вже існує при завантаженні
if (appInstance.HubNodesService) {
appInstance.HubNodesService.registerBypasserNode(node);
} else {
// Якщо сервіс ще не створено (малоймовірно, але можливо),
// реєстрація відбудеться пізніше через onAdded
console.warn(`[${NODE_TITLE} ID: ${node.id}] Initial registration deferred, HubNodesService not yet available.`);
}
// Початкове оновлення віджетів для цього конкретного вузла
setTimeout(() => { if (node.refreshWidgets) node.refreshWidgets(); }, 50);
}
},
getExtraMenuOptions(_, options) {
options.push({
content: "Add Active Model",
callback: () => {
const node = LiteGraph.createNode(NODE_TYPE);
// Записуємо текст для коментаря, щоб показати функціональність
node.properties.desc = "Controls the flow of Model Parameters Pipe Hubs";
app.graph.add(node);
// Розміщення вузла в центрі видимої області
const canvas = app.canvas;
const visible_rect = canvas.visible_area;
node.pos[0] = visible_rect[0] + visible_rect[2] * 0.5 - node.size[0] * 0.5;
node.pos[1] = visible_rect[1] + visible_rect[3] * 0.5 - node.size[1] * 0.5;
console.log(`[${NODE_TITLE}] Node created from menu with ID: ${node.id}`);
// Явно налаштуємо ноду після створення з меню
if (app.HubNodesService) {
// Отримуємо розширення
const extension = app.extensions.find(ext => ext.name === "MultiModel.ActiveModel.ServiceDriven");
if (extension) {
// Конфігуруємо ноду
console.log(`[${NODE_TITLE}] Manually configuring node created from menu`);
extension.loadedGraphNode(node, app);
// Оновлюємо віджети з затримкою
setTimeout(() => {
if (node.refreshWidgets) {
console.log(`[${NODE_TITLE}] Refreshing widgets for node created from menu`);
node.refreshWidgets();
}
}, 100);
}
}
}
});
},
nodeRegistered(type, clazz) {
console.log(`[${NODE_TITLE}] nodeRegistered called for type: ${type}`);
if (type === NODE_TYPE) {
try {
console.log(`[${NODE_TITLE}] Running additional setup for our node type`);
ActiveModel.setUp(clazz);
} catch (e) {
console.error(`[${NODE_TITLE}] Error in additional node setup:`, e);
}
}
},
// Додаємо обробники створення ноди для максимального покриття
nodeCreated(node) {
console.log(`[${NODE_TITLE}] nodeCreated called for node type: ${node?.type || 'undefined'}, ID: ${node?.id || 'undefined'}`);
if (node && node.type === NODE_TYPE) {
console.log(`[${NODE_TITLE}] Our node created, configuring it...`);
this.loadedGraphNode(node, app);
// Запускаємо оновлення віджетів з затримкою
setTimeout(() => {
if (node.refreshWidgets) {
console.log(`[${NODE_TITLE}] Refreshing widgets after node creation`);
node.refreshWidgets();
}
}, 100);
}
},
onNodeAdded(node) {
console.log(`[${NODE_TITLE}] onNodeAdded callback called for node type: ${node?.type || 'undefined'}, ID: ${node?.id || 'undefined'}`);
if (node && node.type === NODE_TYPE) {
console.log(`[${NODE_TITLE}] Our node added via onNodeAdded, configuring it...`);
this.loadedGraphNode(node, app);
// Запускаємо оновлення віджетів з затримкою
setTimeout(() => {
if (node.refreshWidgets) {
console.log(`[${NODE_TITLE}] Refreshing widgets after node was added`);
node.refreshWidgets();
}
}, 100);
}
}
});
console.log(`[${NODE_TITLE}] JS file loaded`);