Add files via upload
This commit is contained in:
+672
-107
@@ -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 (*^_^*)")
|
||||
@@ -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> | <a href="#-styles">Styles</a> | <a href="#-installation">Installation</a> | <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!**
|
||||
File diff suppressed because it is too large
Load Diff
+7353
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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) --- ")
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ---`);
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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`);
|
||||
Reference in New Issue
Block a user