First commit with core code

Core developed code and functionality of LevelPixel nodes
This commit is contained in:
Level Pixel Dev
2024-11-05 01:35:36 +06:00
parent 675353dcdb
commit 418db2c60d
21 changed files with 13123 additions and 12 deletions
+128
View File
@@ -0,0 +1,128 @@
import numpy as np
import io
import torch
from PIL import Image
import matplotlib.pyplot as plt
color_mapping = {
"white": (255, 255, 255),
"black": (0, 0, 0),
"red": (255, 0, 0),
"green": (0, 255, 0),
"blue": (0, 0, 255),
"yellow": (255, 255, 0),
"cyan": (0, 255, 255),
"magenta": (255, 0, 255),
"orange": (255, 165, 0),
"purple": (128, 0, 128),
"pink": (255, 192, 203),
"brown": (165, 42, 42),
"gray": (128, 128, 128),
"lightgray": (211, 211, 211),
"darkgray": (169, 169, 169),
"olive": (128, 128, 0),
"lime": (0, 128, 0),
"teal": (0, 128, 128),
"navy": (0, 0, 128),
"maroon": (128, 0, 0),
"fuchsia": (255, 0, 128),
"aqua": (0, 255, 128),
"silver": (192, 192, 192),
"gold": (255, 215, 0),
"turquoise": (64, 224, 208),
"lavender": (230, 230, 250),
"violet": (238, 130, 238),
"coral": (255, 127, 80),
"indigo": (75, 0, 130),
}
COLORS = ["custom", "white", "black", "red", "green", "blue", "yellow",
"cyan", "magenta", "orange", "purple", "pink", "brown", "gray",
"lightgray", "darkgray", "olive", "lime", "teal", "navy", "maroon",
"fuchsia", "aqua", "silver", "gold", "turquoise", "lavender",
"violet", "coral", "indigo"]
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
def hex_to_rgb(hex_color):
hex_color = hex_color.lstrip('#')
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
return (r, g, b)
class FastCheckerPattern:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"width": ("INT", {"default": 512, "min": 64, "max": 4096}),
"height": ("INT", {"default": 512, "min": 64, "max": 4096}),
"color_1": (COLORS,),
"color_2": (COLORS,),
"grid_frequency": ("INT", {"default": 50, "min": 1, "max": 200, "step": 1}),
},
"optional": {
"color1_hex": ("STRING", {"multiline": False, "default": "#C0C0C0"}),
"color2_hex": ("STRING", {"multiline": False, "default": "#808080"}),
}
}
RETURN_TYPES = ("IMAGE", )
RETURN_NAMES = ("IMAGE", )
FUNCTION = "draw"
CATEGORY = "LevelPixel/Image"
def draw(self, width, height, color_1, color_2,
grid_frequency, color1_hex='#C0C0C0', color2_hex='#808080'):
if color_1 == "custom":
color1_rgb = hex_to_rgb(color1_hex)
else:
color1_rgb = color_mapping.get(color_1, (255, 255, 255))
if color_2 == "custom":
color2_rgb = hex_to_rgb(color2_hex)
else:
color2_rgb = color_mapping.get(color_2, (0, 0, 0))
canvas = np.zeros((height, width, 3), dtype=np.uint8)
grid_size = width // grid_frequency
x_indices = np.arange(width) // grid_size
y_indices = np.arange(height) // grid_size
x_grid, y_grid = np.meshgrid(x_indices, y_indices)
checker_pattern = (x_grid + y_grid) % 2
canvas[checker_pattern == 0] = color1_rgb
canvas[checker_pattern == 1] = color2_rgb
fig, ax = plt.subplots(figsize=(width/100, height/100))
ax.imshow(canvas)
plt.axis('off')
plt.tight_layout(pad=0, w_pad=0, h_pad=0)
plt.autoscale(tight=True)
img_buf = io.BytesIO()
plt.savefig(img_buf, format='png')
img = Image.open(img_buf)
image_out = pil2tensor(img.convert("RGB"))
return (image_out,)
NODE_CLASS_MAPPINGS = {
"FastCheckerPattern|LP": FastCheckerPattern,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FastCheckerPattern|LP": "Fast Checker Pattern [LP]",
}
+44
View File
@@ -0,0 +1,44 @@
import glob
import os
class FileCounter:
FUNCTION = "file_counter"
OUTPUT_NODE = True
CATEGORY = "LevelPixel/IO"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"directory_path": ("STRING", {"default": '', "multiline": False}),
"patterns": ("STRING", {"default": '*.jpg|*.png|*.jpeg', "multiline": False}),
},
}
RETURN_TYPES = ("INT","STRING")
RETURN_NAMES = ("Total INT","Total STRING")
FUNCTION = "file_counter"
@classmethod
def IS_CHANGED(cls, *v):
return ALWAYS_CHANGED_FLAG
def file_counter(self, directory_path, patterns):
if not os.path.isdir(directory_path):
return (0,)
total_int = 0
for pattern in patterns.split("|"):
files = list(glob.glob(pattern, root_dir=directory_path))
total_int += len(files)
total_string = str(total_int)
print("total " + str(total_int))
return (total_int, total_string)
NODE_CLASS_MAPPINGS = {
"FileCounter|LP": FileCounter,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FileCounter|LP": "File Counter [LP]",
}
+174
View File
@@ -0,0 +1,174 @@
import numpy as np
import os
from PIL import Image, ImageOps, ImageSequence
import folder_paths
import re
import torch
import node_helpers
import hashlib
class ImageLoaderFromPath:
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.input_directory
image_folder = [name for name in os.listdir(input_dir) if os.path.isdir(os.path.join(input_dir,name))]
image_folder.append("")
return {"required": {"input_folder": (sorted(image_folder), ),
"start_index": ("INT", {"default": 0, "min": 0, "max": 9999}),
"max_images": ("INT", {"default": 1, "min": 1, "max": 9999}),
"white_bg": (["disable","enable"],),
"patterns": ("STRING", {"default": '*.jpg|*.png|*.jpeg', "multiline": False}),
},
"optional": {"input_path": ("STRING", {"default": '', "multiline": False}),
}
}
RETURN_TYPES = ("IMAGE", "MASK", "STRING" )
RETURN_NAMES = ("IMAGE", "MASK", "Filename STRING" )
OUTPUT_IS_LIST = (True, True, True)
FUNCTION = "loader_images"
CATEGORY = "LevelPixel/IO"
def loader_images(self, start_index, max_images, white_bg, patterns, input_folder="", input_path=None):
if input_path != '' and input_path is not None:
if not os.path.exists(input_path):
print(f"[Warning] Image Loader From Path: The input_path `{input_path}` does not exist")
return ("",)
in_path = input_path
else:
input_dir = folder_paths.input_directory
in_path = os.path.join(input_dir, input_folder)
if not os.listdir(in_path):
print(f"[Warning] Image Loader From Path: The folder `{in_path}` is empty")
return None
file_list = sorted(os.listdir(in_path), key=lambda s: sum(((s, int(n)) for s, n in re.findall(r'(\D+)(\d+)', 'a%s0' % s)), ()))
extensions = tuple(patterns.replace('*', '').split('|'))
file_list = [f for f in file_list if f.lower().endswith(extensions)]
image_list = []
mask_list = []
filename_list = []
start_index = max(0, start_index)
end_index = min(start_index + max_images, len(file_list))
for num in range(start_index, end_index):
img = Image.open(os.path.join(in_path, file_list[num]))
image = img.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in img.getbands():
mask = np.array(img.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
if white_bg=="enable":
nw = mask.unsqueeze(0).unsqueeze(-1).repeat(1, 1, 1, 3)
image[nw == 1] = 1.0
else:
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
image_list.append(image)
mask_list.append(mask)
filename_list.append(file_list[num])
if not image_list:
print("Image Loader From Path: No images found.")
return None
return (image_list, mask_list, filename_list)
class LoadImage:
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
return {"required":
{"image": (sorted(files), {"image_upload": True})},
}
RETURN_TYPES = ("IMAGE", "MASK", "STRING" )
RETURN_NAMES = ("IMAGE", "MASK", "Filename STRING")
FUNCTION = "load_image"
CATEGORY = "LevelPixel/IO"
def load_image(self, image):
image_path = folder_paths.get_annotated_filepath(image)
file_name = os.path.basename(image_path)
img = node_helpers.pillow(Image.open, image_path)
output_images = []
output_masks = []
output_filenames = []
w, h = None, None
excluded_formats = ['MPO']
for i in ImageSequence.Iterator(img):
i = node_helpers.pillow(ImageOps.exif_transpose, i)
if i.mode == 'I':
i = i.point(lambda i: i * (1 / 255))
image = i.convert("RGB")
if len(output_images) == 0:
w = image.size[0]
h = image.size[1]
if image.size[0] != w or image.size[1] != h:
continue
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
else:
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
output_images.append(image)
output_masks.append(mask.unsqueeze(0))
output_filenames.append(file_name)
if len(output_images) > 1 and img.format not in excluded_formats:
output_image = torch.cat(output_images, dim=0)
output_mask = torch.cat(output_masks, dim=0)
else:
output_image = output_images[0]
output_mask = output_masks[0]
output_filename = output_filenames[0]
return (output_image, output_mask, output_filename)
@classmethod
def IS_CHANGED(s, image):
image_path = folder_paths.get_annotated_filepath(image)
m = hashlib.sha256()
with open(image_path, 'rb') as f:
m.update(f.read())
return m.digest().hex()
@classmethod
def VALIDATE_INPUTS(s, image):
if not folder_paths.exists_annotated_filepath(image):
return "Invalid image file: {}".format(image)
return True
NODE_CLASS_MAPPINGS = {
"ImageLoaderFromPath|LP": ImageLoaderFromPath,
"LoadImage|LP": LoadImage,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ImageLoaderFromPath|LP": "Image Loader From Path [LP]",
"LoadImage|LP": "Load Image [LP]",
}
+37
View File
@@ -0,0 +1,37 @@
class FloatSlider:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"number":("FLOAT", {
"default": 0,
"min": 0.0001,
"max": 1.0000,
"step": 0.0001,
"display": "slider"
}),
},
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ('FLOAT',)
FUNCTION = "run"
CATEGORY = "LevelPixel/IO"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self, number):
if number < 0.0001:
number = 0.0001
elif number > 1.0000:
number = 1.0000
return (number,)
NODE_CLASS_MAPPINGS = {
"SimpleFloatSlider|LP": FloatSlider,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SimpleFloatSlider|LP": "Simple Float Slider [LP]",
}
+48
View File
@@ -0,0 +1,48 @@
class ShowTextLP:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"forceInput": True}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
},
}
INPUT_IS_LIST = True
RETURN_TYPES = ("STRING",)
FUNCTION = "show_text_lp"
OUTPUT_NODE = True
OUTPUT_IS_LIST = (True,)
CATEGORY = "LevelPixel/IO"
def show_text_lp(self, text, unique_id=None, extra_pnginfo=None):
if unique_id is not None and extra_pnginfo is not None:
if not isinstance(extra_pnginfo, list):
print("Error: extra_pnginfo is not a list")
elif (
not isinstance(extra_pnginfo[0], dict)
or "workflow" not in extra_pnginfo[0]
):
print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key")
else:
workflow = extra_pnginfo[0]["workflow"]
node = next(
(x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])),
None,
)
if node:
node["widgets_values"] = [text]
return {"ui": {"text": text}, "result": (text,)}
NODE_CLASS_MAPPINGS = {
"ShowText|LP": ShowTextLP,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ShowText|LP": "Show Text [LP]",
}
+164
View File
@@ -0,0 +1,164 @@
import folder_paths
import os
from llama_cpp import Llama
import gc
import torch
supported_LLava_extensions = set(['.gguf'])
try:
folder_paths.folder_names_and_paths["LLavacheckpoints"] = (folder_paths.folder_names_and_paths["LLavacheckpoints"][0], supported_LLava_extensions)
except:
if not os.path.isdir(os.path.join(folder_paths.models_dir, "LLavacheckpoints")):
os.mkdir(os.path.join(folder_paths.models_dir, "LLavacheckpoints"))
folder_paths.folder_names_and_paths["LLavacheckpoints"] = ([os.path.join(folder_paths.models_dir, "LLavacheckpoints")], supported_LLava_extensions)
class LLMLoader:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"ckpt_name": (folder_paths.get_filename_list("LLavacheckpoints"), ),
"max_ctx": ("INT", {"default": 2048, "min": 128, "max": 128000, "step": 64}),
"gpu_layers": ("INT", {"default": 27, "min": 0, "max": 100, "step": 1}),
"n_threads": ("INT", {"default": 8, "min": 1, "max": 100, "step": 1}),
}
}
RETURN_TYPES = ("CUSTOM",)
RETURN_NAMES = ("model",)
FUNCTION = "load_llm_checkpoint"
CATEGORY = "LevelPixel/LLM"
def load_llm_checkpoint(self, ckpt_name, max_ctx, gpu_layers, n_threads):
ckpt_path = folder_paths.get_full_path("LLavacheckpoints", ckpt_name)
llm = Llama(model_path = ckpt_path, chat_format="chatml", offload_kqv=True,
f16_kv=True, use_mlock=False, embedding=False, n_batch=1024,
last_n_tokens_size=1024, verbose=True, seed=42, n_ctx = max_ctx,
n_gpu_layers=gpu_layers, n_threads=n_threads,)
return (llm, )
class LLMSampler:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("CUSTOM", {"default": ""}),
"max_tokens": ("INT", {"default": 512, "min": 1, "max": 2048, "step": 1}),
"temperature": ("FLOAT", {"default": 0.2, "min": 0.01, "max": 1.0, "step": 0.01}),
"top_p": ("FLOAT", {"default": 0.95, "min": 0.1, "max": 1.0, "step": 0.01}),
"top_k": ("INT", {"default": 40, "step": 1}),
"frequency_penalty": ("FLOAT", {"default": 0.0, "step": 0.01}),
"presence_penalty": ("FLOAT", {"default": 0.0, "step": 0.01}),
"repeat_penalty": ("FLOAT", {"default": 1.1, "step": 0.01}),
"seed": ("INT", {"default": 42, "step": 1}),
"prompt": ("STRING",{"multiline": True, "default": ""}),
"system_msg": ("STRING",{ "multiline": True, "default" : "You are an assistant who perfectly describes images."}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_text_sampler"
CATEGORY = "LevelPixel/LLM"
def generate_text_sampler(self, system_msg, prompt, model, max_tokens,
temperature, top_p, top_k, frequency_penalty,
presence_penalty, repeat_penalty, seed):
llm = model
response = llm.create_chat_completion(messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": prompt + " Assistant:"},
],
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
top_k=top_k,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
repeat_penalty=repeat_penalty,
seed=seed
)
return (f"{response['choices'][0]['message']['content']}", )
class LLMAdvanced:
def __init__(self):
self.llm = None
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"ckpt_name": (folder_paths.get_filename_list("LLavacheckpoints"), ),
"max_ctx": ("INT", {"default": 2048, "min": 300, "max": 100000, "step": 64}),
"gpu_layers": ("INT", {"default": 27, "min": 0, "max": 100, "step": 1}),
"n_threads": ("INT", {"default": 8, "min": 1, "max": 100, "step": 1}),
"max_tokens": ("INT", {"default": 512, "min": 1, "max": 2048, "step": 1}),
"temperature": ("FLOAT", {"default": 0.1, "min": 0.01, "max": 1.0, "step": 0.01}),
"top_p": ("FLOAT", {"default": 0.95, "min": 0.1, "max": 1.0, "step": 0.01}),
"top_k": ("INT", {"default": 40, "step": 1}),
"frequency_penalty": ("FLOAT", {"default": 0.0, "step": 0.01}),
"presence_penalty": ("FLOAT", {"default": 0.0, "step": 0.01}),
"repeat_penalty": ("FLOAT", {"default": 1.1, "step": 0.01}),
"seed": ("INT", {"default": 42, "step": 1}),
"unload": ("BOOLEAN", {"default": False}),
"prompt": ("STRING", {"multiline": True, "default": ""}),
"system_msg": ("STRING", {"multiline": True, "default": "You are an assistant who perfectly describes images."}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_text_advanced"
CATEGORY = "LevelPixel/LLM"
def generate_text_advanced(self, ckpt_name, max_ctx, gpu_layers, n_threads,
system_msg, prompt, max_tokens, temperature, top_p,
top_k, frequency_penalty, presence_penalty, repeat_penalty, seed, unload):
ckpt_path = folder_paths.get_full_path("LLavacheckpoints", ckpt_name)
self.llm = Llama(model_path = ckpt_path, offload_kqv=True, f16_kv=True,
use_mlock=False, embedding=False, n_batch=1024, last_n_tokens_size=1024,
verbose=True, seed=42, n_ctx = max_ctx, n_gpu_layers=gpu_layers,
n_threads=n_threads, logits_all=True, echo=False)
response = self.llm.create_chat_completion(messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": prompt},
],
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
top_k=top_k,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
repeat_penalty=repeat_penalty,
seed=seed,
)
if unload and self.llm is not None:
self.llm.close()
del self.llm
self.llm = None
gc.collect()
torch.cuda.empty_cache()
return (f"{response['choices'][0]['message']['content']}", )
NODE_CLASS_MAPPINGS = {
"LLMLoader|LP": LLMLoader,
"LLMSampler|LP": LLMSampler,
"LLMAdvanced|LP": LLMAdvanced,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LLMLoader|LP": "LLM Loader [LP]",
"LLMSampler|LP": "LLM Sampler [LP]",
"LLMAdvanced|LP": "LLM Advanced [LP]"
}
File diff suppressed because it is too large Load Diff
+443
View File
@@ -0,0 +1,443 @@
import os
import json
tag_category = json.load(open(os.path.join(os.path.dirname(os.path.realpath(__file__)),"tag_category.json")))
def category_for_tags(tags):
tags = [tag.strip() for tag in tags.replace(".", ",").replace("\n", ",").split(",")]
tags2 = [tag.replace(" ", "_").lower() for tag in tags]
result = []
for i, tag2 in enumerate(tags2):
if tag2 in tag_category:
category_list = tag_category.get(tag2, [])
for category in category_list:
if category not in result:
result.append(category)
break
return ", ".join(result)
class TagCategoryFilter:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tags": ("STRING", {"default": ""}),
"include_categories": ("STRING", {"default": ""}),
"exclude_categories": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("result",)
FUNCTION = "tag_category"
CATEGORY = "LevelPixel/Tags"
OUTPUT_NODE = True
def tag_category(self, tags, include_categories="", exclude_categories=""):
targets = []
exclude_targets = []
if not include_categories:
print("categories - ", category_for_tags(tags))
include_categories = category_for_tags(tags)
include_categories = include_categories.strip()
if include_categories:
targets += [category.strip() for category in include_categories.replace("\n",",").split(",")]
if exclude_categories:
exclude_targets = [category.strip() for category in exclude_categories.replace("\n",",").split(",")]
targets = [target for target in targets if target not in exclude_targets]
print("targets", targets)
tags = [tag.strip() for tag in tags.replace(".", ",").replace("\n", ",").split(",")]
tags2 = [tag.replace(" ", "_").lower() for tag in tags]
result = []
for i, tag2 in enumerate(tags2):
if tag2 in tag_category:
category_list = tag_category.get(tag2, [])
for category in category_list:
if category in exclude_targets:
break
else:
for category in category_list:
if '*' in include_categories or ( category in targets and tags[i] not in result ):
result.append(tags[i])
break
return (", ".join(result),)
class TagCategoryKeeper:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tags": ("STRING", {"default": ""}),
"include_categories": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("result",)
FUNCTION = "tag_keeper"
CATEGORY = "LevelPixel/Tags"
OUTPUT_NODE = True
def tag_keeper(self, tags, include_categories=""):
targets = []
include_categories = include_categories.strip()
if include_categories:
targets += [category.strip() for category in include_categories.replace("\n",",").split(",")]
print("targets", targets)
tags = [tag.strip() for tag in tags.replace(".", ",").replace("\n", ",").split(",")]
tags2 = [tag.replace(" ", "_").lower() for tag in tags]
result = []
for i, tag2 in enumerate(tags2):
if tag2 in tag_category:
category_list = tag_category.get(tag2, [])
for category in category_list:
if '*' in include_categories or ( category in targets and tags[i] not in result ):
result.append(tags[i])
break
return (", ".join(result),)
class TagCategoryRemover:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tags": ("STRING", {"default": ""}),
"exclude_categories": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("result",)
FUNCTION = "tag_remover"
CATEGORY = "LevelPixel/Tags"
OUTPUT_NODE = True
def tag_remover(self, tags, exclude_categories=""):
targets = []
exclude_targets = []
if exclude_categories:
exclude_targets = [category.strip() for category in exclude_categories.replace("\n",",").split(",")]
targets = [target for target in targets if target not in exclude_targets]
print("targets", targets)
tags = [tag.strip() for tag in tags.replace(".", ",").replace("\n", ",").split(",")]
tags2 = [tag.replace(" ", "_").lower() for tag in tags]
result = []
for i, tag2 in enumerate(tags2):
if tag2 in tag_category:
category_list = tag_category.get(tag2, [])
for category in category_list:
if category in exclude_targets:
break
else:
if tags[i] not in result:
result.append(tags[i])
break
return (", ".join(result),)
class TagSwitcher:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_tags": ("STRING", {"default": ""}),
"default_image": ("IMAGE", {"default": ""}),
"tags1": ("STRING", {"default": ""}),
"image1": ("IMAGE", {"default": ""}),
"any1": ("BOOLEAN", {"default": True}),
},
"optional": {
"tags2": ("STRING", {"default": ""}),
"image2": ("IMAGE", {"default": ""}),
"any2": ("BOOLEAN", {"default": True}),
"tags3": ("STRING", {"default": ""}),
"image3": ("IMAGE", {"default": ""}),
"any3": ("BOOLEAN", {"default": True}),
"tags4": ("STRING", {"default": ""}),
"image4": ("IMAGE", {"default": ""}),
"any4": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "tag_switcher"
CATEGORY = "LevelPixel/Tags"
OUTPUT_NODE = True
def _tag_split(self, tags: str) -> list:
return [tag.strip().replace("_", " ").lower().strip() for tag in tags.replace(".",",").replace("\n",",").split(",")]
def tag_switcher(self, input_tags="", default_image=None, tags1="", image1=None, any1=True, tags2="", image2=None, any2=True, tags3="", image3=None, any3=True, tags4="", image4=None, any4=True):
input_tags = self._tag_split(input_tags)
target_tags = []
tags1 = set(self._tag_split(tags1))
target_tags.append((tags1, image1, any1))
tags2 = set(self._tag_split(tags2))
target_tags.append((tags2, image2, any2))
tags3 = set(self._tag_split(tags3))
target_tags.append((tags3, image3, any3))
tags4 = set(self._tag_split(tags4))
target_tags.append((tags4, image4, any4))
for tags, image, any_flag in target_tags:
if any_flag:
if any(tag in tags for tag in input_tags):
return (image,)
else:
if all(tag in input_tags for tag in tags):
return (image,)
return (default_image,)
class TagMerger:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tags1": ("STRING", {"default": ""}),
"tags2": ("STRING", {"default": ""}),
"under_score": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("result",)
FUNCTION = "tag_merger"
CATEGORY = "LevelPixel/Tags"
OUTPUT_NODE = True
def tag_merger(self, tags1:str, tags2:str, under_score=True):
tags1 = [tag.strip().replace(" ", "_").lower() for tag in tags1.replace(".",",").replace("\n",",").split(",")]
tags2 = [tag.strip().replace(" ", "_").lower() for tag in tags2.replace(".",",").replace("\n",",").split(",")]
tags = tags1 + list(set(tags2) - set(tags1))
tags = [tag for tag in tags if tag]
if not under_score:
tags = [tag.replace("_", " ") for tag in tags]
return (", ".join(tags),)
class TagRemover:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tags": ("STRING", {"default": ""}),
"exclude_tags": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("result",)
FUNCTION = "tag_remover"
CATEGORY = "LevelPixel/Tags"
OUTPUT_NODE = True
def tag_remover(self, tags:str, exclude_tags:str=""):
tags = [tag.strip() for tag in tags.replace("\n",",").split(",")]
tags2 = [tag.replace(" ", "_").lower().strip() for tag in tags]
exclude_tags = [tag.strip() for tag in exclude_tags.replace("\n",",").split(",")]
exclude_tags2 = [tag.replace(" ", "_").lower().strip() for tag in exclude_tags]
result = []
for i, tag2 in enumerate(tags2):
if tag2 not in exclude_tags2:
result.append(tags[i])
return (", ".join(result),)
class TagReplace:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tags": ("STRING", {"default": ""}),
"replace_tags": ("STRING", {"default": ""}),
"match": ("FLOAT", {"default": 0.3}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("result",)
FUNCTION = "tag_replace"
CATEGORY = "LevelPixel/Tags"
OUTPUT_NODE = True
def _get_categories(self, tag: str) -> set:
return set(tag_category.get(tag, []))
def _category_match_percentage(self, categories1: set, categories2: set) -> float:
if not categories1 or not categories2:
return 0
intersection = categories1.intersection(categories2)
union = categories1.union(categories2)
return len(intersection) / len(union)
def tag_replace(self, tags:str, replace_tags:str="", match:float=0.3):
tags = [tag.strip() for tag in tags.replace("\n",",").split(",")]
tags_normalized = [tag.replace(" ", "_").lower().strip() for tag in tags]
replace_tags = [tag.strip() for tag in replace_tags.replace("\n",",").split(",")]
replace_tags_normalized = [tag.replace(" ", "_").lower().strip() for tag in replace_tags]
replace_tags_used = {tag:False for tag in replace_tags_normalized}
result = []
for i, tag in enumerate(tags_normalized):
tag_categories = self._get_categories(tag)
best_match_tag = None
best_match_tag_id = None
best_match_percentage = 0
for k, replace_tag in enumerate(replace_tags_normalized):
replace_categories = self._get_categories(replace_tag)
match_percentage = self._category_match_percentage(tag_categories, replace_categories)
if match_percentage and match_percentage > best_match_percentage:
best_match_percentage = match_percentage
best_match_tag = replace_tag
replace_tags_used[replace_tag] = True
best_match_tag_id = k
if best_match_tag and best_match_percentage >= match:
result.append(replace_tags[best_match_tag_id])
else:
result.append(tags[i])
extra_tags = [replace_tag for replace_tag, used in replace_tags_used.items() if not used]
result.extend(extra_tags)
return (", ".join(result),)
class TagCategory:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"tags": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("result",)
FUNCTION = "tag_category_info"
CATEGORY = "LevelPixel/Tags"
OUTPUT_NODE = True
def tag_category_info(self, tags):
tags = [tag.strip() for tag in tags.replace(".", ",").replace("\n", ",").split(",")]
tags2 = [tag.replace(" ", "_").lower() for tag in tags]
result = []
for i, tag2 in enumerate(tags2):
if tag2 in tag_category:
category_list = tag_category.get(tag2, [])
for category in category_list:
if category not in result:
result.append(category)
break
return (", ".join(result),)
NODE_CLASS_MAPPINGS = {
"TagCategory|LP": TagCategory,
"TagCategoryFilter|LP": TagCategoryFilter,
"TagCategoryKeeper|LP": TagCategoryKeeper,
"TagCategoryRemover|LP": TagCategoryRemover,
"TagSwitcher|LP": TagSwitcher,
"TagMerger|LP": TagMerger,
"TagReplace|LP": TagReplace,
"TagRemover|LP": TagRemover,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"TagCategory|LP": "Tag Category [LP]",
"TagCategoryFilter|LP": "Tag Category Filter [LP]",
"TagCategoryKeeper|LP": "Tag Category Keeper [LP]",
"TagCategoryRemover|LP": "Tag Category Remover [LP]",
"TagSwitcher|LP": "Tag Switcher [LP]",
"TagMerger|LP": "Tag Merger [LP]",
"TagReplace|LP": "Tag Replace [LP]",
"TagRemover|LP": "Tag Remover [LP]"
}
+61
View File
@@ -0,0 +1,61 @@
import re
import random
import time
class TextChoiceParser:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True, "dynamicPrompts": False}),
},
"optional": {
"variables": ("STRING", {"multiline": True, "dynamicPrompts": False}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "text_choice_parser"
OUTPUT_NODE = True
CATEGORY = "LevelPixel/Text"
def text_choice_parser(self, text, variables="", seed=None):
if len(text) > 10000:
return ("Text too large to process at once",)
if seed is None or seed == 0:
seed = int(time.time() * 1000)
random.seed(seed)
var_dict = {}
for line in variables.split('\n'):
if '=' in line:
key, value = line.split('=', 1)
var_dict[key.strip()] = value.strip()
for key, value in var_dict.items():
text = text.replace(f"[{key}]", value)
pattern = r'\{([^}]+)\}'
def replace_random(match):
return random.choice(match.group(1).split('|'))
result = re.sub(pattern, replace_random, text)
return (result,)
@classmethod
def IS_CHANGED(s, text, variables="", seed=None):
return (text, variables, seed)
NODE_CLASS_MAPPINGS = {
"TextChoiceParser|LP": TextChoiceParser,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"TextChoiceParser|LP": "Text Choice Parser [LP]",
}
+116
View File
@@ -0,0 +1,116 @@
from comfy import model_management
import gc
import torch
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
any = AnyType("*")
class ModelUnloader:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"source": (any, {}),
},
}
RETURN_TYPES = (any,)
FUNCTION = "unload_model"
OUTPUT_NODE = True
CATEGORY = "LevelPixel/Unloaders"
def unload_model(self, **kwargs):
loadedmodels=model_management.current_loaded_models
unloaded_model = False
for i in range(len(loadedmodels) -1, -1, -1):
m = loadedmodels.pop(i)
m.model_unload()
del m
unloaded_model = True
if unloaded_model:
model_management.soft_empty_cache()
gc.collect()
torch.cuda.empty_cache()
return (kwargs["source"],)
class SoftModelUnloader:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"source": (any, {}),
},
}
RETURN_TYPES = (any,)
FUNCTION = "soft_unload_model"
OUTPUT_NODE = True
CATEGORY = "LevelPixel/Unloaders"
def soft_unload_model(self, **kwargs):
model_management.soft_empty_cache()
gc.collect()
torch.cuda.empty_cache()
return (kwargs["source"],)
class HardModelUnloader:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"source": (any, {}),
},
}
RETURN_TYPES = (any,)
FUNCTION = "hard_unload_model"
OUTPUT_NODE = True
CATEGORY = "LevelPixel/Unloaders"
def hard_unload_model(self, **kwargs):
loadedmodels=model_management.current_loaded_models
unloaded_model = False
for i in range(len(loadedmodels) -1, -1, -1):
m = loadedmodels.pop(i)
m.model_unload()
del m
unloaded_model = True
if unloaded_model:
model_management.soft_empty_cache()
gc.collect()
torch.cuda.empty_cache()
return (kwargs["source"],)
NODE_CLASS_MAPPINGS = {
"ModelUnloader|LP": ModelUnloader,
"SoftModelUnloader|LP": SoftModelUnloader,
"HardModelUnloader|LP": HardModelUnloader,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ModelUnloader|LP": "Model Unloader [LP]",
"SoftModelUnloader|LP": "Soft Model Unloader [LP]",
"HardModelUnloader|LP": "Hard Model Unloader [LP]",
}
+367
View File
@@ -0,0 +1,367 @@
import folder_paths
import os
from io import BytesIO
from llama_cpp import Llama
from llama_cpp.llama_chat_format import Llava16ChatHandler
import base64
from torchvision.transforms import ToPILImage
import gc
import torch
supported_LLava_extensions = set(['.gguf'])
try:
folder_paths.folder_names_and_paths["LLavacheckpoints"] = (folder_paths.folder_names_and_paths["LLavacheckpoints"][0], supported_LLava_extensions)
except:
if not os.path.isdir(os.path.join(folder_paths.models_dir, "LLavacheckpoints")):
os.mkdir(os.path.join(folder_paths.models_dir, "LLavacheckpoints"))
folder_paths.folder_names_and_paths["LLavacheckpoints"] = ([os.path.join(folder_paths.models_dir, "LLavacheckpoints")], supported_LLava_extensions)
class LLavaLoader:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"ckpt_name": (folder_paths.get_filename_list("LLavacheckpoints"), ),
"max_ctx": ("INT", {"default": 4096, "min": 128, "max": 8192, "step": 64}),
"gpu_layers": ("INT", {"default": 27, "min": 0, "max": 100, "step": 1}),
"n_threads": ("INT", {"default": 8, "min": 1, "max": 100, "step": 1}),
"clip": ("CUSTOM", {"default": ""}),
}
}
RETURN_TYPES = ("CUSTOM",)
RETURN_NAMES = ("model",)
FUNCTION = "load_llava_checkpoint"
CATEGORY = "LevelPixel/LLava"
def load_llava_checkpoint(self, ckpt_name, max_ctx, gpu_layers, n_threads, clip ):
ckpt_path = folder_paths.get_full_path("LLavacheckpoints", ckpt_name)
llm = Llama(model_path = ckpt_path, chat_handler=clip,offload_kqv=True, f16_kv=True,
use_mlock=False, embedding=False, n_batch=1024, last_n_tokens_size=1024,
verbose=True, seed=42, n_ctx = max_ctx, n_gpu_layers=gpu_layers, n_threads=n_threads,
logits_all=True, echo=False)
return (llm, )
class LLavaClipLoader:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"clip_name": (folder_paths.get_filename_list("LLavacheckpoints"), ),
}
}
RETURN_TYPES = ("CUSTOM", )
RETURN_NAMES = ("clip", )
FUNCTION = "load_clip_checkpoint"
CATEGORY = "LevelPixel/LLava"
def load_clip_checkpoint(self, clip_name):
clip_path = folder_paths.get_full_path("LLavacheckpoints", clip_name)
clip = Llava16ChatHandler(clip_model_path = clip_path, verbose=False)
return (clip, )
class LLavaSamplerSimple:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"model": ("CUSTOM", {"default": ""}),
"temperature": ("FLOAT", {"default": 0.1, "min": 0.01, "max": 1.0, "step": 0.01}),
"prompt": ("STRING",{"multiline": True} ),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_text_simple"
CATEGORY = "LevelPixel/LLava"
def generate_text_simple(self, image, prompt, model, temperature):
pil_image = ToPILImage()(image[0].permute(2, 0, 1))
buffer = BytesIO()
pil_image.save(buffer, format="PNG")
image_bytes = buffer.getvalue()
base64_string = f"data:image/jpeg;base64,{base64.b64encode(image_bytes).decode('utf-8')}"
llm = model
response = llm.create_chat_completion(
messages = [
{"role": "system", "content": "You are an assistant who perfectly describes images."},
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url" : base64_string}},
{"type" : "text", "text": f"{prompt}"}
]
}
],
temperature = temperature,
)
return (f"{response['choices'][0]['message']['content']}", )
class LLavaSamplerAdvanced:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"model": ("CUSTOM", {"default": ""}),
"max_tokens": ("INT", {"default": 512, "min": 1, "max": 2048, "step": 1}),
"temperature": ("FLOAT", {"default": 0.1, "min": 0.01, "max": 1.0, "step": 0.01}),
"top_p": ("FLOAT", {"default": 0.95, "min": 0.1, "max": 1.0, "step": 0.01}),
"top_k": ("INT", {"default": 40, "step": 1}),
"frequency_penalty": ("FLOAT", {"default": 0.0, "step": 0.01}),
"presence_penalty": ("FLOAT", {"default": 0.0, "step": 0.01}),
"repeat_penalty": ("FLOAT", {"default": 1.1, "step": 0.01}),
"seed": ("INT", {"default": 42, "step":1}),
"prompt": ("STRING",{"multiline": True, "default": ""}),
"system_msg": ("STRING",{"multiline": True, "default" : "You are an assistant who perfectly describes images."}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_text_advanced"
CATEGORY = "LevelPixel/LLava"
def generate_text_advanced(self, image, system_msg, prompt, model, max_tokens, temperature, top_p,
frequency_penalty, presence_penalty, repeat_penalty, top_k,seed):
pil_image = ToPILImage()(image[0].permute(2, 0, 1))
buffer = BytesIO()
pil_image.save(buffer, format="PNG")
image_bytes = buffer.getvalue()
base64_string = f"data:image/jpeg;base64,{base64.b64encode(image_bytes).decode('utf-8')}"
llm = model
response = llm.create_chat_completion(
messages = [
{"role": "system", "content": system_msg},
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url" : base64_string}},
{"type" : "text", "text": f"{prompt}"}
]
}
],
max_tokens = max_tokens,
temperature = temperature,
top_p = top_p,
top_k = top_k,
frequency_penalty = frequency_penalty,
presence_penalty = presence_penalty,
repeat_penalty = repeat_penalty,
seed=seed
)
return (f"{response['choices'][0]['message']['content']}", )
class LLavaSimple:
def __init__(self):
self.llm = None
self.clip = None
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"ckpt_name": (folder_paths.get_filename_list("LLavacheckpoints"), ),
"clip_name": (folder_paths.get_filename_list("LLavacheckpoints"), ),
"max_ctx": ("INT", {"default": 4096, "min": 128, "max": 128000, "step": 64}),
"gpu_layers": ("INT", {"default": 27, "min": 0, "max": 100, "step": 1}),
"n_threads": ("INT", {"default": 8, "min": 1, "max": 100, "step": 1}),
"temperature": ("FLOAT", {"default": 0.1, "min": 0.01, "max": 1.0, "step": 0.01}),
"unload": ("BOOLEAN", {"default": False}),
"prompt": ("STRING", {"multiline": True, "forceInput": True}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_text_full_simple"
CATEGORY = "LevelPixel/LLava"
def generate_text_full_simple(self, ckpt_name, clip_name, max_ctx, gpu_layers, n_threads, image, prompt, temperature, unload):
clip_path = folder_paths.get_full_path("LLavacheckpoints", clip_name)
self.clip = Llava16ChatHandler(clip_model_path=clip_path, verbose=False)
ckpt_path = folder_paths.get_full_path("LLavacheckpoints", ckpt_name)
self.llm = Llama(model_path = ckpt_path, chat_handler=self.clip, offload_kqv=True, f16_kv=True,
use_mlock=False, embedding=False, n_batch=1024, last_n_tokens_size=1024,
verbose=True, seed=42, n_ctx = max_ctx, n_gpu_layers=gpu_layers, n_threads=n_threads,
logits_all=True, echo=False)
pil_image = ToPILImage()(image[0].permute(2, 0, 1))
buffer = BytesIO()
pil_image.save(buffer, format="PNG")
image_bytes = buffer.getvalue()
base64_string = f"data:image/jpeg;base64,{base64.b64encode(image_bytes).decode('utf-8')}"
response = self.llm.create_chat_completion(
messages=[
{"role": "system", "content": "You are an assistant who perfectly describes images."},
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": base64_string}},
{"type": "text", "text": f"{prompt}"}
]
}
],
temperature=temperature,
)
if unload and self.llm is not None:
self.llm.close()
del self.llm
self.llm = None
gc.collect()
torch.cuda.empty_cache()
if unload and self.clip is not None:
self.clip._exit_stack.close() # info https://github.com/abetlen/llama-cpp-python/issues/1746
del self.clip
self.clip = None
gc.collect()
torch.cuda.empty_cache()
return (f"{response['choices'][0]['message']['content']}", )
class LLavaAdvanced:
def __init__(self):
self.llm = None
self.clip = None
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"ckpt_name": (folder_paths.get_filename_list("LLavacheckpoints"), ),
"clip_name": (folder_paths.get_filename_list("LLavacheckpoints"), ),
"max_ctx": ("INT", {"default": 4096, "min": 128, "max": 128000, "step": 64}),
"gpu_layers": ("INT", {"default": 27, "min": 0, "max": 100, "step": 1}),
"n_threads": ("INT", {"default": 8, "min": 1, "max": 100, "step": 1}),
"max_tokens": ("INT", {"default": 512, "min": 1, "max": 2048, "step": 1}),
"temperature": ("FLOAT", {"default": 0.1, "min": 0.01, "max": 1.0, "step": 0.01}),
"top_p": ("FLOAT", {"default": 0.95, "min": 0.1, "max": 1.0, "step": 0.01}),
"top_k": ("INT", {"default": 40, "step": 1}),
"frequency_penalty": ("FLOAT", {"default": 0.0, "step": 0.01}),
"presence_penalty": ("FLOAT", {"default": 0.0, "step": 0.01}),
"repeat_penalty": ("FLOAT", {"default": 1.1, "step": 0.01}),
"seed": ("INT", {"default": 42, "step": 1}),
"unload": ("BOOLEAN", {"default": False}),
"prompt": ("STRING", {"multiline": True, "default": ""}),
"system_msg": ("STRING", {"multiline": True, "default": "You are an assistant who perfectly describes images."}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_text_full_advanced"
CATEGORY = "LevelPixel/LLava"
def generate_text_full_advanced(self, ckpt_name, clip_name, max_ctx, gpu_layers, n_threads, image,
system_msg, prompt, max_tokens, temperature, top_p, top_k, frequency_penalty,
presence_penalty, repeat_penalty, seed, unload):
clip_path = folder_paths.get_full_path("LLavacheckpoints", clip_name)
self.clip = Llava16ChatHandler(clip_model_path=clip_path, verbose=False)
ckpt_path = folder_paths.get_full_path("LLavacheckpoints", ckpt_name)
self.llm = Llama(model_path = ckpt_path, chat_handler=self.clip, offload_kqv=True, f16_kv=True,
use_mlock=False, embedding=False, n_batch=1024, last_n_tokens_size=1024,
verbose=True, seed=42, n_ctx = max_ctx, n_gpu_layers=gpu_layers, n_threads=n_threads,
logits_all=True, echo=False)
pil_image = ToPILImage()(image[0].permute(2, 0, 1))
buffer = BytesIO()
pil_image.save(buffer, format="PNG")
image_bytes = buffer.getvalue()
base64_string = f"data:image/jpeg;base64,{base64.b64encode(image_bytes).decode('utf-8')}"
response = self.llm.create_chat_completion(
messages=[
{"role": "system", "content": system_msg},
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": base64_string}},
{"type": "text", "text": f"{prompt}"}
]
}
],
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
top_k=top_k,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
repeat_penalty=repeat_penalty,
seed=seed,
)
if unload and self.llm is not None:
self.llm.close()
del self.llm
self.llm = None
gc.collect()
torch.cuda.empty_cache()
if unload and self.clip is not None:
self.clip._exit_stack.close() # info https://github.com/abetlen/llama-cpp-python/issues/1746
del self.clip
self.clip = None
gc.collect()
torch.cuda.empty_cache()
return (f"{response['choices'][0]['message']['content']}", )
NODE_CLASS_MAPPINGS = {
"LLavaLoader|LP": LLavaLoader,
"LLavaClipLoader|LP": LLavaClipLoader,
"LLavaSamplerSimple|LP": LLavaSamplerSimple,
"LLavaSamplerAdvanced|LP": LLavaSamplerAdvanced,
"LLavaSimple|LP": LLavaSimple,
"LLavaAdvanced|LP": LLavaAdvanced,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LLavaLoader|LP": "LLava Loader [LP]",
"LLavaClipLoader|LP": "LLava Clip Loader [LP]",
"LLavaSamplerSimple|LP": "LLava Sampler Simple [LP]",
"LLavaSamplerAdvanced|LP": "LLava Sampler Advanced [LP]",
"LLavaSimple|LP": "LLava Simple [LP]",
"LLavaAdvanced|LP": "LLava Advanced [LP]",
}