diff --git a/Backups/__init__(Working Backup 29-6).py b/Backups/__init__(Working Backup 29-6).py new file mode 100644 index 0000000..23f333e --- /dev/null +++ b/Backups/__init__(Working Backup 29-6).py @@ -0,0 +1,109 @@ +import os +import json +import base64 +from PIL import Image +from io import BytesIO + + +class EnhancedCharacterPromptNode: + """ComfyUI node: kies character + action, toon preview image, output prompt + conditioning""" + + # JSON-bestanden met characters en actions + CHARACTER_JSON_FILES = [ + os.path.join(os.path.dirname(__file__), f"output_{i}.json") for i in range(1, 12) + ] + ACTION_JSON = os.path.join(os.path.dirname(__file__), "action.json") + + # Class-variabelen voor data en dropdowns + char_data = [] + action_data = {} + CHARACTERS = [] + ACTIONS = [] + + # Laad JSON data zodra class geladen wordt + try: + for path in CHARACTER_JSON_FILES: + with open(path, "r", encoding="utf-8") as f: + char_data.extend(json.load(f)) + except Exception as e: + print(f"⚠️ Error loading {path}: {e}") + + try: + with open(ACTION_JSON, "r", encoding="utf-8") as f: + action_data = json.load(f) + except Exception as e: + print("❌ Error loading action.json:", e) + + # Vul dropdown lijsten op basis van geladen data + CHARACTERS = [list(entry.keys())[0] for entry in char_data if isinstance(entry, dict) and len(entry) >= 1] + ACTIONS = list(action_data.keys()) + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "character": (cls.CHARACTERS,), + "action": (cls.ACTIONS,), + "extra_prompt": ("STRING", {"multiline": True, "default": ""}), + "clip": ("CLIP",), # optioneel, voor conditioning + } + } + + RETURN_TYPES = ("STRING", "IMAGE", "CONDITIONING") + RETURN_NAMES = ("prompt", "preview_image", "CONDITIONING") + FUNCTION = "build_prompt" + CATEGORY = "Prompting/Anime Character" + + def build_prompt(self, character, action, extra_prompt, clip): + char_prompt = "" + action_prompt = EnhancedCharacterPromptNode.action_data.get(action, "") + preview_image = None + + for entry in EnhancedCharacterPromptNode.char_data: + if isinstance(entry, dict) and character in entry: + value = entry[character] + if isinstance(value, str) and value.startswith("data:image"): + char_prompt = character + preview_data = value + else: + char_prompt = value + preview_data = entry.get("preview", "") + + if isinstance(preview_data, str) and preview_data.startswith("data:image"): + try: + base64_data = preview_data.split("base64,", 1)[1] + preview_image = self.decode_base64_to_image(base64_data) + except Exception as e: + print(f"⚠️ Base64 decode failed for {character}: {e}") + break + + final_prompt = ", ".join(p for p in [char_prompt, action_prompt, extra_prompt] if p).strip() + + conditioning_output = [] + if clip: + try: + cross_attn = clip.encode(final_prompt) + pooled = clip.encode_pooled(final_prompt) + cond_dict = {"pooled_output": pooled} + conditioning_output = [(cross_attn, cond_dict)] + except Exception as e: + print(f"⚠️ CLIP encode failed: {e}") + + return (final_prompt, preview_image, conditioning_output) + + def decode_base64_to_image(self, base64_str): + data = base64.b64decode(base64_str) + try: + img = Image.open(BytesIO(data)).convert("RGB") + except Exception as e: + raise ValueError("Failed to decode base64 image") from e + return img + + +NODE_CLASS_MAPPINGS = { + "EnhancedCharacterPromptNode": EnhancedCharacterPromptNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "EnhancedCharacterPromptNode": "Character + Action Prompt + Preview", +} diff --git a/Backups/__init__(working pt2).py b/Backups/__init__(working pt2).py new file mode 100644 index 0000000..3741dea --- /dev/null +++ b/Backups/__init__(working pt2).py @@ -0,0 +1,118 @@ +import os +import json +import base64 +from PIL import Image +from io import BytesIO + + +class EnhancedCharacterPromptNode: + """ComfyUI node: select character + action, show preview image, output prompt + conditioning""" + + # JSON files with characters and actions + CHARACTER_JSON_FILES = [ + os.path.join(os.path.dirname(__file__), f"output_{i}.json") for i in range(1, 12) + ] + ACTION_JSON = os.path.join(os.path.dirname(__file__), "action.json") + + # Class variables for dropdown content + char_data = [] + action_data = {} + CHARACTERS = [] + ACTIONS = [] + + # Load JSON data once when class is loaded + try: + for path in CHARACTER_JSON_FILES: + with open(path, "r", encoding="utf-8") as f: + char_data.extend(json.load(f)) + except Exception as e: + print(f"⚠️ Error loading {path}: {e}") + + try: + with open(ACTION_JSON, "r", encoding="utf-8") as f: + action_data = json.load(f) + except Exception as e: + print("❌ Error loading action.json:", e) + + # Populate dropdowns + CHARACTERS = [list(entry.keys())[0] for entry in char_data if isinstance(entry, dict) and len(entry) >= 1] + ACTIONS = list(action_data.keys()) + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "character": (cls.CHARACTERS,), + "action": (cls.ACTIONS,), + "extra_prompt": ("STRING", {"multiline": True, "default": ""}), + "custom_prompt": ("STRING", {"multiline": True, "default": ""}), + "clip": ("CLIP",), + } + } + + RETURN_TYPES = ("STRING", "IMAGE", "CONDITIONING") + RETURN_NAMES = ("prompt", "preview_image", "conditioning") + FUNCTION = "build_prompt" + CATEGORY = "Prompting/Anime Character" + + def build_prompt(self, character, action, extra_prompt, custom_prompt, clip): + char_prompt = "" + action_prompt = EnhancedCharacterPromptNode.action_data.get(action, "") + preview_image = None + + for entry in EnhancedCharacterPromptNode.char_data: + if isinstance(entry, dict) and character in entry: + value = entry[character] + if isinstance(value, str) and value.startswith("data:image"): + char_prompt = character + preview_data = value + else: + char_prompt = value + preview_data = entry.get("preview", "") + + if isinstance(preview_data, str) and preview_data.startswith("data:image"): + try: + base64_data = preview_data.split("base64,", 1)[1] + preview_image = self.decode_base64_to_image(base64_data) + except Exception as e: + print(f"⚠️ Base64 decode failed for {character}: {e}") + break + + # Build editable prompt string + base_prompt = ", ".join(p for p in [char_prompt, action_prompt, extra_prompt] if p).strip() + final_prompt = custom_prompt if custom_prompt.strip() else base_prompt + + # Encode prompt for conditioning + conditioning_output = [] + if clip: + try: + if hasattr(clip, "encode_with_transformer"): + cross_attn, pooled = clip.encode_with_transformer(final_prompt) + cond_dict = {"pooled_output": pooled} + conditioning_output = [(cross_attn, cond_dict)] + else: + cross_attn = clip.encode(final_prompt) + pooled = clip.encode_pooled(final_prompt) + cond_dict = {"pooled_output": pooled} + conditioning_output = [(cross_attn, cond_dict)] + except Exception as e: + print(f"⚠️ CLIP encode failed: {e}") + + return (final_prompt, preview_image, conditioning_output) + + def decode_base64_to_image(self, base64_str): + data = base64.b64decode(base64_str) + try: + img = Image.open(BytesIO(data)).convert("RGB") + except Exception as e: + raise ValueError("Failed to decode base64 image") from e + return img + + +NODE_CLASS_MAPPINGS = { + "EnhancedCharacterPromptNode": EnhancedCharacterPromptNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "EnhancedCharacterPromptNode": "Character + Action Prompt + Preview", +} diff --git a/__init__.py b/__init__.py index 23f333e..035461e 100644 --- a/__init__.py +++ b/__init__.py @@ -4,23 +4,25 @@ import base64 from PIL import Image from io import BytesIO +import nodes # Register JS directory +nodes.EXTENSION_WEB_DIRS["EnhancedPromptNode"] = os.path.join(os.path.dirname(__file__), "js") + class EnhancedCharacterPromptNode: - """ComfyUI node: kies character + action, toon preview image, output prompt + conditioning""" + """ComfyUI node: select character + action, show preview image, output prompt + conditioning""" - # JSON-bestanden met characters en actions + # JSON files with characters and actions CHARACTER_JSON_FILES = [ os.path.join(os.path.dirname(__file__), f"output_{i}.json") for i in range(1, 12) ] ACTION_JSON = os.path.join(os.path.dirname(__file__), "action.json") - # Class-variabelen voor data en dropdowns + # Class variables for data and dropdowns char_data = [] action_data = {} CHARACTERS = [] ACTIONS = [] - # Laad JSON data zodra class geladen wordt try: for path in CHARACTER_JSON_FILES: with open(path, "r", encoding="utf-8") as f: @@ -34,7 +36,6 @@ class EnhancedCharacterPromptNode: except Exception as e: print("❌ Error loading action.json:", e) - # Vul dropdown lijsten op basis van geladen data CHARACTERS = [list(entry.keys())[0] for entry in char_data if isinstance(entry, dict) and len(entry) >= 1] ACTIONS = list(action_data.keys()) @@ -42,28 +43,32 @@ class EnhancedCharacterPromptNode: def INPUT_TYPES(cls): return { "required": { - "character": (cls.CHARACTERS,), - "action": (cls.ACTIONS,), - "extra_prompt": ("STRING", {"multiline": True, "default": ""}), - "clip": ("CLIP",), # optioneel, voor conditioning + "prompt": ("STRING", {"multiline": True, "dynamicPrompts": False, "default": ""}), + "Select to add Character": (["Select the character to insert"] + cls.CHARACTERS,), + "Select to add Action": (["Select the action to insert"] + cls.ACTIONS,), + "clip": ("CLIP",), } } RETURN_TYPES = ("STRING", "IMAGE", "CONDITIONING") - RETURN_NAMES = ("prompt", "preview_image", "CONDITIONING") + RETURN_NAMES = ("prompt", "preview_image", "conditioning") FUNCTION = "build_prompt" CATEGORY = "Prompting/Anime Character" - def build_prompt(self, character, action, extra_prompt, clip): - char_prompt = "" - action_prompt = EnhancedCharacterPromptNode.action_data.get(action, "") - preview_image = None + def build_prompt(self, prompt, **kwargs): + selected_char = kwargs.get("Select to add Character", None) + selected_act = kwargs.get("Select to add Action", None) + clip = kwargs.get("clip") - for entry in EnhancedCharacterPromptNode.char_data: - if isinstance(entry, dict) and character in entry: - value = entry[character] + preview_image = None + char_prompt = "" + action_prompt = "" + + for entry in self.char_data: + if isinstance(entry, dict) and selected_char in entry: + value = entry[selected_char] if isinstance(value, str) and value.startswith("data:image"): - char_prompt = character + char_prompt = selected_char preview_data = value else: char_prompt = value @@ -74,10 +79,12 @@ class EnhancedCharacterPromptNode: base64_data = preview_data.split("base64,", 1)[1] preview_image = self.decode_base64_to_image(base64_data) except Exception as e: - print(f"⚠️ Base64 decode failed for {character}: {e}") + print(f"⚠️ Base64 decode failed for {selected_char}: {e}") break - final_prompt = ", ".join(p for p in [char_prompt, action_prompt, extra_prompt] if p).strip() + action_prompt = self.action_data.get(selected_act, "") + + final_prompt = ", ".join(p for p in [char_prompt, action_prompt, prompt] if p).strip() conditioning_output = [] if clip: @@ -94,10 +101,10 @@ class EnhancedCharacterPromptNode: def decode_base64_to_image(self, base64_str): data = base64.b64decode(base64_str) try: - img = Image.open(BytesIO(data)).convert("RGB") + from comfy.utils import pil_to_tensor + return pil_to_tensor(Image.open(BytesIO(data)).convert("RGB")) except Exception as e: raise ValueError("Failed to decode base64 image") from e - return img NODE_CLASS_MAPPINGS = { @@ -105,5 +112,5 @@ NODE_CLASS_MAPPINGS = { } NODE_DISPLAY_NAME_MAPPINGS = { - "EnhancedCharacterPromptNode": "Character + Action Prompt + Preview", + "EnhancedCharacterPromptNode": "Character + Action Prompt (Live Insert)", } diff --git a/js/enhanced_prompt.js b/js/enhanced_prompt.js new file mode 100644 index 0000000..00648b8 --- /dev/null +++ b/js/enhanced_prompt.js @@ -0,0 +1,42 @@ +// enhanced_prompt.js + +function insertIntoPrompt(promptField, text) { + if (!promptField || !text) return; + if (promptField.value.trim().length > 0) { + promptField.value += ", " + text; + } else { + promptField.value = text; + } + promptField.dispatchEvent(new Event("input", { bubbles: true })); +} + +function setupLiveInsert() { + const observer = new MutationObserver(() => { + document.querySelectorAll(".comfy-control").forEach(container => { + const label = container.querySelector("label"); + const select = container.querySelector("select"); + const promptField = container.parentElement.querySelector("textarea"); + + if (!label || !select || !promptField) return; + + if (label.innerText.includes("Select to add Character") || label.innerText.includes("Select to add Action")) { + if (select.dataset._enhanced) return; // Already hooked + select.dataset._enhanced = "true"; + + select.addEventListener("change", () => { + const value = select.value; + if (value && !value.startsWith("Select")) { + insertIntoPrompt(promptField, value); + select.value = select.options[0].value; + select.dispatchEvent(new Event("change")); + } + }); + } + }); + }); + + observer.observe(document.body, { childList: true, subtree: true }); +} + +if (document.readyState !== "loading") setupLiveInsert(); +else document.addEventListener("DOMContentLoaded", setupLiveInsert);