From 585a9ff6737afa541e49bb76d10f81112ebeecb2 Mon Sep 17 00:00:00 2001 From: Vince Date: Mon, 22 Apr 2024 17:49:12 +0200 Subject: [PATCH] Remove obsolete files and add new node implementations This commit removes some obsolete Python files related to latent, logging, and prompt functionalities. It also adds new implementations for node classes, including ShowText, ImageSizer, ImageDimensionResizer, RandomTitleCharacter, and others. The new implementations restructure and optimize the existing functionalities. --- __init__.py | 22 +- js/showText.js | 55 ++++ latent.py | 116 ------- logging.py | 19 -- nodes/latent/image_dimension_resizer.py | 53 +++ nodes/latent/image_sizer.py | 65 ++++ nodes/logging/show_text.py | 40 +++ nodes/prompt/element_concatenator.py | 30 ++ nodes/prompt/insert_character.py | 74 +++++ nodes/prompt/llm_enhance.py | 57 ++++ nodes/prompt/wildcard/random_elements.py | 45 +++ .../prompt/wildcard/random_title_character.py | 93 ++++++ prompt.py | 305 ------------------ wildcards/put_wildcards_here | 0 14 files changed, 526 insertions(+), 448 deletions(-) create mode 100644 js/showText.js delete mode 100644 latent.py delete mode 100644 logging.py create mode 100644 nodes/latent/image_dimension_resizer.py create mode 100644 nodes/latent/image_sizer.py create mode 100644 nodes/logging/show_text.py create mode 100644 nodes/prompt/element_concatenator.py create mode 100644 nodes/prompt/insert_character.py create mode 100644 nodes/prompt/llm_enhance.py create mode 100644 nodes/prompt/wildcard/random_elements.py create mode 100644 nodes/prompt/wildcard/random_title_character.py delete mode 100644 prompt.py create mode 100644 wildcards/put_wildcards_here 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