diff --git a/__init__.py b/__init__.py index 765a833..978c19b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,21 +1,25 @@ -from custom_nodes.GR85.latent import ImageSizer, ImageDimensionResizer -from custom_nodes.GR85.logging import ShowString -from custom_nodes.GR85.prompt import RandomTitleCharacter, InsertCharacter, RandomElements, ElementsConcatenator, \ - LlmEnhancer +from custom_nodes.GR85.nodes.latent.image_dimension_resizer import ImageDimensionResizer +from custom_nodes.GR85.nodes.latent.image_sizer import ImageSizer +from custom_nodes.GR85.nodes.logging.show_text import ShowText +from custom_nodes.GR85.nodes.prompt.element_concatenator import ElementConcatenator +from custom_nodes.GR85.nodes.prompt.insert_character import InsertCharacter +from custom_nodes.GR85.nodes.prompt.llm_enhance import LlmEnhancer +from custom_nodes.GR85.nodes.prompt.wildcard.random_elements import RandomElements +from custom_nodes.GR85.nodes.prompt.wildcard.random_title_character import RandomTitleCharacter NODE_CLASS_MAPPINGS = { - "GR85_ShowString": ShowString, + "GR85_ShowText": ShowText, "GR85_ImageSizer": ImageSizer, "GR85_ImageDimensionResizer": ImageDimensionResizer, "GR85_RandomTitleCharacter": RandomTitleCharacter, "GR85_InsertCharacter": InsertCharacter, "GR85_RandomElements": RandomElements, - "GR85_ElementsConcatenator": ElementsConcatenator, + "GR85_ElementsConcatenator": ElementConcatenator, "GR85_LlmEnhancer": LlmEnhancer, } NODE_DISPLAY_NAME_MAPPINGS = { - "GR85_ShowString": "Show String", + "GR85_ShowText": "Show Text", "GR85_ImageSizer": "Image Sizer", "GR85_ImageDimensionResizer": "Image Dimension Resizer", "GR85_RandomTitleCharacter": "Random Title Character", @@ -25,4 +29,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "GR85_LlmEnhancer": "Llm Enhancer", } -__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] +WEB_DIRECTORY = "./js" + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] diff --git a/js/showText.js b/js/showText.js new file mode 100644 index 0000000..0b6014d --- /dev/null +++ b/js/showText.js @@ -0,0 +1,55 @@ +import {app} from '../../scripts/app.js' +import {ComfyWidgets} from '../../scripts/widgets.js' + +/** + * @typedef {import("types/comfy").ComfyExtension} ComfyExtension + */ + +// Displays input text on a node +app.registerExtension({ + name: 'GR85.ShowText', + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === 'GR85_ShowText') { + /** + * @param text {string[]} + * @this {ComfyNode} + */ + function populate(text) { + console.log(this) + if (this.widgets) { + for (let i = 1; i < this.widgets.length; i++) { + this.widgets[i].onRemove?.() + } + this.widgets.length = 1 + } + + const v = [...text] + if (!v[0]) { + v.shift() + } + + for (const list of v) { + const w = ComfyWidgets['STRING'](this, 'text', ['STRING', {multiline: true}], app).widget + w.inputEl.readOnly = true + w.inputEl.style.opacity = 0.6 + w.value = list + } + } + + // When the node is executed we will be sent the input text, display this in the widget + const onExecuted = nodeType.prototype.onExecuted + nodeType.prototype.onExecuted = function (message) { + onExecuted?.apply(this, arguments) + populate.call(this, message.text) + } + + const onConfigure = nodeType.prototype.onConfigure + nodeType.prototype.onConfigure = function () { + onConfigure?.apply(this, arguments) + if (this.widgets_values?.length) { + populate.call(this, this.widgets_values) + } + } + } + }, +}) diff --git a/latent.py b/latent.py deleted file mode 100644 index 8c1e098..0000000 --- a/latent.py +++ /dev/null @@ -1,116 +0,0 @@ -import math - - -class ImageSizer: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "original_dimensions": (["512x512", "512*768", "768x768", "1024x1024", "1280x720", "1920x1080"],), - "ratio_1": ("INT", { - "default": 1, - "min": 1, - "max": 4096, - "step": 1, - "display": "number" - }), - "ratio_2": ("INT", { - "default": 1, - "min": 1, - "max": 4096, - "step": 1, - "display": "number" - }), - "orientation": (["landscape", "portrait"],), - } - } - - RETURN_TYPES = ("INT", "INT") - RETURN_NAMES = ("width", "height") - - FUNCTION = "resize_dimensions" - - CATEGORY = "GR85/Latent" - - def resize_dimensions(self, original_dimensions, ratio_1, ratio_2, orientation): - original_width, original_height = map(int, original_dimensions.split("x")) - - """Calculates new dimensions for an image while maintaining the same pixel count - and a specified aspect ratio. - - Args: - original_width: The original width of the image in pixels. - original_height: The original height of the image in pixels. - ratio_1: The first part of the desired aspect ratio. - ratio_2: The second part of the desired aspect ratio. - orientation: Either "landscape" or "portrait" (used for clarity, not calculation) - - Returns: - A tuple containing the new width and height of the image. - """ - - total_pixels = original_width * original_height - new_width = math.sqrt(total_pixels * ratio_1 / ratio_2) - new_height = new_width * ratio_2 / ratio_1 - - # Adjust if the desired orientation is different from the original - if (orientation == 'landscape' and original_height > original_width) or \ - (orientation == 'portrait' and original_width > original_height): - new_width, new_height = new_height, new_width # Swap dimensions - - return int(round(new_width)), int(round(new_height)) - - -class ImageDimensionResizer: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "original_width": ("INT", { - "forceInput": True, - "default": 512, - "min": 1, - "max": 4096, - "step": 1, - "display": "number" - }), - "original_height": ("INT", { - "forceInput": True, - "default": 512, - "min": 1, - "max": 4096, - "step": 1, - "display": "number" - }), - "target_dimensions": (["512x512", "512*768", "768x768", "1024x1024", "1280x720", "1920x1080"],), - } - } - - RETURN_TYPES = ("INT", "INT") - RETURN_NAMES = ("width", "height") - - FUNCTION = "resize_dimensions" - - CATEGORY = "GR85/Latent" - - def resize_dimensions(self, original_width, original_height, target_dimensions): - """ - Calculates new dimensions while maintaining pixel count - and maintaining the ratio of the original dimensions. - - first calculate the original ratio - then calculate the target pixel count - then calculate the new dimensions - """ - original_ratio = original_width / original_height - target_width, target_height = map(int, target_dimensions.split("x")) - target_pixels = target_width * target_height - new_width = math.sqrt(target_pixels * original_ratio) - new_height = new_width / original_ratio - return int(round(new_width)), int(round(new_height)) diff --git a/logging.py b/logging.py deleted file mode 100644 index 22a0561..0000000 --- a/logging.py +++ /dev/null @@ -1,19 +0,0 @@ -class ShowString: - @classmethod - def INPUT_TYPES(self): - return { - "required": { - "str": ("STRING", {"forceInput": True}), - "key": ('STRING', {"default": "text"}), - } - } - - RETURN_TYPES = ("STRING",) - - FUNCTION = "show" - - CATEGORY = "GR85/Logging" - OUTPUT_NODE = True - - def show(self, str, key): - return {"ui": {key: (str,)}, "result": (str,)} diff --git a/nodes/latent/image_dimension_resizer.py b/nodes/latent/image_dimension_resizer.py new file mode 100644 index 0000000..cff0dba --- /dev/null +++ b/nodes/latent/image_dimension_resizer.py @@ -0,0 +1,53 @@ +import math + + +class ImageDimensionResizer: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "original_width": ("INT", { + "forceInput": True, + "default": 512, + "min": 1, + "max": 4096, + "step": 1, + "display": "number" + }), + "original_height": ("INT", { + "forceInput": True, + "default": 512, + "min": 1, + "max": 4096, + "step": 1, + "display": "number" + }), + "target_dimensions": (["512x512", "512*768", "768x768", "1024x1024", "1280x720", "1920x1080"],), + } + } + + RETURN_TYPES = ("INT", "INT") + RETURN_NAMES = ("width", "height") + + FUNCTION = "resize_dimensions" + + CATEGORY = "GR85/Latent" + + def resize_dimensions(self, original_width, original_height, target_dimensions): + """ + Calculates new dimensions while maintaining pixel count + and maintaining the ratio of the original dimensions. + + first calculate the original ratio + then calculate the target pixel count + then calculate the new dimensions + """ + original_ratio = original_width / original_height + target_width, target_height = map(int, target_dimensions.split("x")) + target_pixels = target_width * target_height + new_width = math.sqrt(target_pixels * original_ratio) + new_height = new_width / original_ratio + return int(round(new_width)), int(round(new_height)) diff --git a/nodes/latent/image_sizer.py b/nodes/latent/image_sizer.py new file mode 100644 index 0000000..2cba260 --- /dev/null +++ b/nodes/latent/image_sizer.py @@ -0,0 +1,65 @@ +import math + + +class ImageSizer: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "original_dimensions": (["512x512", "512*768", "768x768", "1024x1024", "1280x720", "1920x1080"],), + "ratio_1": ("INT", { + "default": 1, + "min": 1, + "max": 4096, + "step": 1, + "display": "number" + }), + "ratio_2": ("INT", { + "default": 1, + "min": 1, + "max": 4096, + "step": 1, + "display": "number" + }), + "orientation": (["landscape", "portrait"],), + } + } + + RETURN_TYPES = ("INT", "INT") + RETURN_NAMES = ("width", "height") + + FUNCTION = "resize_dimensions" + + CATEGORY = "GR85/Latent" + + def resize_dimensions(self, original_dimensions, ratio_1, ratio_2, orientation): + original_width, original_height = map(int, original_dimensions.split("x")) + + """Calculates new dimensions for an image while maintaining the same pixel count + and a specified aspect ratio. + + Args: + original_width: The original width of the image in pixels. + original_height: The original height of the image in pixels. + ratio_1: The first part of the desired aspect ratio. + ratio_2: The second part of the desired aspect ratio. + orientation: Either "landscape" or "portrait" (used for clarity, not calculation) + + Returns: + A tuple containing the new width and height of the image. + """ + + total_pixels = original_width * original_height + side_1 = math.sqrt(total_pixels * ratio_1 / ratio_2) + side_2 = side_1 * ratio_2 / ratio_1 + + bigger_side = max(side_1, side_2) + smaller_side = min(side_1, side_2) + + if orientation == 'landscape': + return int(round(bigger_side)), int(round(smaller_side)) + else: + return int(round(smaller_side)), int(round(bigger_side)) diff --git a/nodes/logging/show_text.py b/nodes/logging/show_text.py new file mode 100644 index 0000000..232c87d --- /dev/null +++ b/nodes/logging/show_text.py @@ -0,0 +1,40 @@ +class ShowText: + @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 = "notify" + OUTPUT_NODE = True + OUTPUT_IS_LIST = (True,) + + CATEGORY = "GR85/Logging" + + def notify(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,)} diff --git a/nodes/prompt/element_concatenator.py b/nodes/prompt/element_concatenator.py new file mode 100644 index 0000000..646f994 --- /dev/null +++ b/nodes/prompt/element_concatenator.py @@ -0,0 +1,30 @@ +class ElementConcatenator: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "title_character_elements": ("STRING", { + "forceInput": True, + "default": "", + "multiline": True + }), + "random_elements": ("STRING", { + "forceInput": True, + "default": "", + "multiline": True + }), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("concatenated_elements",) + + FUNCTION = "concatenate_elements" + + CATEGORY = "GR85/Prompt" + + def concatenate_elements(self, title_character_elements, random_elements): + return (f"{title_character_elements}, {random_elements}",) diff --git a/nodes/prompt/insert_character.py b/nodes/prompt/insert_character.py new file mode 100644 index 0000000..d17ded3 --- /dev/null +++ b/nodes/prompt/insert_character.py @@ -0,0 +1,74 @@ +class InsertCharacter: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "title_character": ("STRING", { + "forceInput": True, + "default": "", + "multiline": True, + }), + "elements": ("STRING", { + "forceInput": True, + "default": "", + "multiline": True + }), + "facial_features": ("STRING", { + "forceInput": True, + "default": "", + "multiline": True + }), + "append": ("STRING", { + "default": "", + "multiline": True + }), + "anime_template": ("STRING", { + "default": "1girl, {title_character}, anime, {elements}, {append}", + "multiline": True + }), + "realistic_template": ("STRING", { + "default": "realistic, 1girl, {title_character}, {facial_features}, {elements}, {append}", + "multiline": True + }), + "face_detailer_template": ("STRING", { + "default": "realistic, 1girl, {title_character}, {facial_features}, cute eyes, natural beauty, attractive face, {append}", + "multiline": True + }), + } + } + + RETURN_TYPES = ("STRING", "STRING", "STRING") + RETURN_NAMES = ("anime_string", "realistic_string", "face_detailer_string") + + FUNCTION = "insert_character" + + CATEGORY = "GR85/Prompt" + + def insert_character(self, title_character, elements, facial_features, append, anime_template, realistic_template, + face_detailer_template): + """ + This function takes the output of the RandomTitleCharacter node and inserts it into three different templates. + The templates are strings that contain placeholders for the different elements of the character. + The placeholders are: + - {title_character} + - {elements} + - {facial_features} + - {append} + + The function will replace these placeholders with the corresponding elements and return the resulting strings. + """ + anime = anime_template.format(title_character=title_character, elements=elements, + facial_features=facial_features, append=append) + realistic = realistic_template.format(title_character=title_character, elements=elements, + facial_features=facial_features, append=append) + face_detailer = face_detailer_template.format(title_character=title_character, elements=elements, + facial_features=facial_features, append=append) + + print("Anime:", anime) + print("Realistic:", realistic) + print("Face Detailer:", face_detailer) + + return anime, realistic, face_detailer diff --git a/nodes/prompt/llm_enhance.py b/nodes/prompt/llm_enhance.py new file mode 100644 index 0000000..67a763f --- /dev/null +++ b/nodes/prompt/llm_enhance.py @@ -0,0 +1,57 @@ +import openai + + +class LlmEnhancer: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "title_character": ("STRING", { + "forceInput": True, + "default": "", + "multiline": True + }), + "elements": ("STRING", { + "forceInput": True, + "default": "", + "multiline": True + }), + "system_prompt": ("STRING", { + "default": "Enhance the following elements into a short and simple movie still that is coherent with the character.", + "multiline": True + }), + "user_prompt": ("STRING", { + "default": "Character in scene: {title_character}, Props: {elements}", + "multiline": True + }), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("enhanced_string",) + FUNCTION = "enhance_string" + CATEGORY = "GR85/Prompt" + + def enhance_string(self, title_character, elements, system_prompt, user_prompt): + """ + This makes a call to: http://localhost:7875/v1/chat/completions + This service is a ChatGPT model that has been fine-tuned to respond with an enhanced version of the input text. + """ + client = openai.OpenAI(base_url="http://localhost:7875/v1", api_key="lm-studio") + + completion = client.chat.completions.create( + model="Undi95/Xwin-MLewd-13B-V0.2-GGUF", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt.format(title_character=title_character, elements=elements)}, + ], + temperature=0.7, + ) + + try: + return (completion.choices[0].message.content,) + except Exception as e: + return (str(e),) diff --git a/nodes/prompt/wildcard/random_elements.py b/nodes/prompt/wildcard/random_elements.py new file mode 100644 index 0000000..0c6195d --- /dev/null +++ b/nodes/prompt/wildcard/random_elements.py @@ -0,0 +1,45 @@ +import yaml +import random + + +class RandomElements: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "seed": ("INT", {"forceInput": True, "default": 0, "min": 0, "max": 0xffffffffffffffff}), + "elements_amount": ("INT", { + "default": 4, + "min": 1, + "max": 100, + "step": 1, + "display": "number" + }), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("random_elements",) + + FUNCTION = "choose_random_elements" + + CATEGORY = "GR85/Prompt/Wildcard" + + def choose_random_elements(self, seed, elements_amount): + with open("ComfyUI/custom_nodes/GR85/wildcards/elements.txt", "r") as file: + elements = file.readlines() + result = [] + for element in elements: + parts = element.split(':') + if len(parts) == 2: # Check if we have two parts + amount_str, name = parts + amount = int(amount_str) + result.extend([name.strip()] * amount) + else: + result.append(element.strip()) # Handle elements without ':' + random.seed(seed) + chosen_elements = random.sample(result, elements_amount) + return (", ".join(chosen_elements),) diff --git a/nodes/prompt/wildcard/random_title_character.py b/nodes/prompt/wildcard/random_title_character.py new file mode 100644 index 0000000..5ad074c --- /dev/null +++ b/nodes/prompt/wildcard/random_title_character.py @@ -0,0 +1,93 @@ +import random + +import yaml + + +class RandomTitleCharacter: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "seed": ("INT", { + "forceInput": True, "default": 0, "min": 0, "max": 0xffffffffffffffff}), + "title_elements_amount": ("INT", { + "default": 1, + "min": 1, + "max": 10, + "step": 1, + "display": "number" + }), + "character_elements_amount": ("INT", { + "default": 2, + "min": 1, + "max": 10, + "step": 1, + "display": "number" + }), + "facial_features_amount": ("INT", { + "default": 2, + "min": 1, + "max": 10, + "step": 1, + "display": "number" + }), + + } + } + + RETURN_TYPES = ("STRING", "STRING", "STRING") + RETURN_NAMES = ("random_character", "elements", "facial_features") + + FUNCTION = "choose_random_title_characters" + + CATEGORY = "GR85/Prompt/Wildcard" + + def choose_random_title_characters(self, + seed, + title_elements_amount, + character_elements_amount, + facial_features_amount): + """ + There is a YAML file in the same directory as this file named: characters.yaml + It contains a list of characters in the following format: + - Movie/TV Show/Video Game Title: + Title_Elements: + - Title Element + Characters: + - Character Name: + Elements: + - Character element + Facial Features: + - Facial feature + + This function will: + - Choose a random title + - Choose a random character from that title + - Choose 3 random elements from that character + - Choose a random element from that title + + Returns a string in the following format: + "Character Name, Title Name, Character element 1, Character element 2, Title Element 1" + """ + with open("characters.yaml", "r") as file: + titles = yaml.load(file, Loader=yaml.FullLoader) + + # Use the seed to generate a random number + random.seed(seed) + + title = random.choice(titles) + title_name = list(title.keys())[0] + title_elements = random.sample(title["Title_Elements"], title_elements_amount) + + title_character = random.choice(title["Characters"]) + title_character_name = list(title_character.keys())[0] + title_character_elements = random.sample(title_character["Elements"], character_elements_amount) + + facial_features = random.sample(title_character["Facial Features"], facial_features_amount) + + return (f"{title_character_name}, {title_name}", + f"{', '.join(title_character_elements)}, {', '.join(title_elements)}", + f"{', '.join(facial_features)}") diff --git a/prompt.py b/prompt.py deleted file mode 100644 index 2d52adc..0000000 --- a/prompt.py +++ /dev/null @@ -1,305 +0,0 @@ -import random - -import openai -import yaml - - -class RandomTitleCharacter: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "seed": ("INT", { - "forceInput": True, "default": 0, "min": 0, "max": 0xffffffffffffffff}), - "title_elements_amount": ("INT", { - "default": 1, - "min": 1, - "max": 10, - "step": 1, - "display": "number" - }), - "character_elements_amount": ("INT", { - "default": 2, - "min": 1, - "max": 10, - "step": 1, - "display": "number" - }), - "facial_features_amount": ("INT", { - "default": 2, - "min": 1, - "max": 10, - "step": 1, - "display": "number" - }), - - } - } - - RETURN_TYPES = ("STRING", "STRING", "STRING") - RETURN_NAMES = ("random_character", "elements", "facial_features") - - FUNCTION = "choose_random_title_characters" - - CATEGORY = "GR85/Prompt" - - def choose_random_title_characters(self, - seed, - title_elements_amount, - character_elements_amount, - facial_features_amount): - """ - There is a YAML file in the same directory as this file named: characters.yaml - It contains a list of characters in the following format: - - Movie/TV Show/Video Game Title: - Title_Elements: - - Title Element - Characters: - - Character Name: - Elements: - - Character element - Facial Features: - - Facial feature - - This function will: - - Choose a random title - - Choose a random character from that title - - Choose 3 random elements from that character - - Choose a random element from that title - - Returns a string in the following format: - "Character Name, Title Name, Character element 1, Character element 2, Title Element 1" - """ - with open("characters.yaml", "r") as file: - titles = yaml.load(file, Loader=yaml.FullLoader) - - # Use the seed to generate a random number - random.seed(seed) - - title = random.choice(titles) - title_name = list(title.keys())[0] - title_elements = random.sample(title["Title_Elements"], title_elements_amount) - - title_character = random.choice(title["Characters"]) - title_character_name = list(title_character.keys())[0] - title_character_elements = random.sample(title_character["Elements"], character_elements_amount) - - facial_features = random.sample(title_character["Facial Features"], facial_features_amount) - - return (f"{title_character_name}, {title_name}", - f"{', '.join(title_character_elements)}, {', '.join(title_elements)}", - f"{', '.join(facial_features)}") - - -class InsertCharacter: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "title_character": ("STRING", { - "forceInput": True, - "default": "", - "multiline": True, - }), - "elements": ("STRING", { - "forceInput": True, - "default": "", - "multiline": True - }), - "facial_features": ("STRING", { - "forceInput": True, - "default": "", - "multiline": True - }), - "append": ("STRING", { - "default": "", - "multiline": True - }), - "anime_template": ("STRING", { - "default": "1girl, {title_character}, anime, {elements}, {append}", - "multiline": True - }), - "realistic_template": ("STRING", { - "default": "realistic, 1girl, {title_character}, {facial_features}, {elements}, {append}", - "multiline": True - }), - "face_detailer_template": ("STRING", { - "default": "realistic, 1girl, {title_character}, {facial_features}, cute eyes, natural beauty, attractive face, {append}", - "multiline": True - }), - } - } - - RETURN_TYPES = ("STRING", "STRING", "STRING") - RETURN_NAMES = ("anime_string", "realistic_string", "face_detailer_string") - - FUNCTION = "insert_character" - - CATEGORY = "GR85/Prompt" - - def insert_character(self, title_character, elements, facial_features, append, anime_template, realistic_template, - face_detailer_template): - """ - This function takes the output of the RandomTitleCharacter node and inserts it into three different templates. - The templates are strings that contain placeholders for the different elements of the character. - The placeholders are: - - {title_character} - - {elements} - - {facial_features} - - {append} - - The function will replace these placeholders with the corresponding elements and return the resulting strings. - """ - anime = anime_template.format(title_character=title_character, elements=elements, - facial_features=facial_features, append=append) - realistic = realistic_template.format(title_character=title_character, elements=elements, - facial_features=facial_features, append=append) - face_detailer = face_detailer_template.format(title_character=title_character, elements=elements, - facial_features=facial_features, append=append) - - print("Anime:", anime) - print("Realistic:", realistic) - print("Face Detailer:", face_detailer) - - return anime, realistic, face_detailer - - -class RandomElements: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "seed": ("INT", {"forceInput": True, "default": 0, "min": 0, "max": 0xffffffffffffffff}), - "elements_amount": ("INT", { - "default": 4, - "min": 1, - "max": 100, - "step": 1, - "display": "number" - }), - } - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("random_elements",) - - FUNCTION = "choose_random_elements" - - CATEGORY = "GR85/Prompt" - - def choose_random_elements(self, seed, elements_amount): - with open("elements.yaml", "r") as file: - elements = yaml.load(file, Loader=yaml.FullLoader) - - result = [] - for element in elements: - parts = element.split(':') - if len(parts) == 2: # Check if we have two parts - amount_str, name = parts - amount = int(amount_str) - result.extend([name] * amount) - else: - result.append(element) # Handle elements without ':' - - random.seed(seed) - - chosen_elements = random.sample(result, elements_amount) - - return (", ".join(chosen_elements),) - - -class ElementsConcatenator: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "title_character_elements": ("STRING", { - "forceInput": True, - "default": "", - "multiline": True - }), - "random_elements": ("STRING", { - "forceInput": True, - "default": "", - "multiline": True - }), - } - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("concatenated_elements",) - - FUNCTION = "concatenate_elements" - - CATEGORY = "GR85/Prompt" - - def concatenate_elements(self, title_character_elements, random_elements): - return (f"{title_character_elements}, {random_elements}",) - - -class LlmEnhancer: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "title_character": ("STRING", { - "forceInput": True, - "default": "", - "multiline": True - }), - "elements": ("STRING", { - "forceInput": True, - "default": "", - "multiline": True - }), - "system_prompt": ("STRING", { - "default": "Enhance the following elements into a short and simple movie still that is coherent with the character.", - "multiline": True - }), - "user_prompt": ("STRING", { - "default": "Character in scene: {title_character}, Props: {elements}", - "multiline": True - }), - } - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("enhanced_string",) - FUNCTION = "enhance_string" - CATEGORY = "GR85/Prompt" - - def enhance_string(self, title_character, elements, system_prompt, user_prompt): - """ - This makes a call to: http://localhost:7875/v1/chat/completions - This service is a ChatGPT model that has been fine-tuned to respond with an enhanced version of the input text. - """ - client = openai.OpenAI(base_url="http://localhost:7875/v1", api_key="lm-studio") - - completion = client.chat.completions.create( - model="Undi95/Xwin-MLewd-13B-V0.2-GGUF", - messages=[ - {"role": "system", "content": system_prompt}, - {"role": "user", "content": user_prompt.format(title_character=title_character, elements=elements)}, - ], - temperature=0.7, - ) - - try: - return (completion.choices[0].message.content,) - except Exception as e: - return (str(e),) diff --git a/wildcards/put_wildcards_here b/wildcards/put_wildcards_here new file mode 100644 index 0000000..e69de29