update JS hook

This commit is contained in:
Icyman86
2025-06-29 22:15:59 +02:00
parent 37b2f3c089
commit 7e1c5daed9
4 changed files with 299 additions and 23 deletions
+109
View File
@@ -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",
}
+118
View File
@@ -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",
}
+30 -23
View File
@@ -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)",
}
+42
View File
@@ -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);