little update
v1.1.1 backup removal
This commit is contained in:
@@ -1,109 +0,0 @@
|
||||
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",
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
|
||||
import nodes # Register JS directory
|
||||
nodes.EXTENSION_WEB_DIRS["ComfyUI_AnimeCharacterSelect"] = os.path.join(os.path.dirname(__file__), "js")
|
||||
|
||||
|
||||
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 data and dropdowns
|
||||
char_data = []
|
||||
action_data = {}
|
||||
CHARACTERS = []
|
||||
ACTIONS = []
|
||||
|
||||
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)
|
||||
|
||||
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": {
|
||||
"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")
|
||||
FUNCTION = "build_prompt"
|
||||
CATEGORY = "Prompting/Anime Character"
|
||||
|
||||
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")
|
||||
|
||||
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 = selected_char
|
||||
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 {selected_char}: {e}")
|
||||
break
|
||||
|
||||
action_prompt = self.action_data.get(selected_act, "")
|
||||
|
||||
print("[DEBUG] char_prompt:", char_prompt)
|
||||
print("[DEBUG] action_prompt:", action_prompt)
|
||||
print("[DEBUG] input prompt:", prompt)
|
||||
|
||||
# Build the live-updated editable prompt
|
||||
new_prompt_parts = [char_prompt, action_prompt]
|
||||
combined_prompt = ", ".join([p for p in new_prompt_parts if p])
|
||||
|
||||
if prompt.strip():
|
||||
if combined_prompt:
|
||||
prompt = combined_prompt + ", " + prompt
|
||||
else:
|
||||
prompt = combined_prompt
|
||||
|
||||
print("[DEBUG] final prompt:", prompt)
|
||||
|
||||
conditioning_output = []
|
||||
if clip:
|
||||
try:
|
||||
cross_attn = clip.encode(prompt)
|
||||
conditioning_output = [(cross_attn, {})]
|
||||
except Exception as e:
|
||||
print(f"⚠️ CLIP encode failed: {e}")
|
||||
|
||||
return (prompt, preview_image, conditioning_output)
|
||||
|
||||
def decode_base64_to_image(self, base64_str):
|
||||
data = base64.b64decode(base64_str)
|
||||
try:
|
||||
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
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"EnhancedCharacterPromptNode": EnhancedCharacterPromptNode,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"EnhancedCharacterPromptNode": "Character + Action Prompt (Live Insert)",
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
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",
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
console.log("EnhancedPromptNode JS loaded!")
|
||||
function updatePromptValue(textarea, value) {
|
||||
if (!textarea || !value || value.startsWith("Select")) return;
|
||||
const existing = textarea.value.trim();
|
||||
const parts = existing.length > 0 ? existing.split(",").map(p => p.trim()) : [];
|
||||
if (!parts.includes(value)) {
|
||||
parts.push(value);
|
||||
textarea.value = parts.join(", ");
|
||||
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
}
|
||||
}
|
||||
|
||||
function setupLiveCharacterActionPromptHook() {
|
||||
const observer = new MutationObserver(() => {
|
||||
document.querySelectorAll(".comfy-control").forEach(container => {
|
||||
const label = container.querySelector("label");
|
||||
const select = container.querySelector("select");
|
||||
|
||||
if (!label || !select) return;
|
||||
|
||||
const labelText = label.innerText.trim();
|
||||
if (
|
||||
(labelText.includes("Select to add Character") || labelText.includes("Select to add Action")) &&
|
||||
!select.dataset._enhanced_hooked
|
||||
) {
|
||||
select.dataset._enhanced_hooked = "true";
|
||||
select.addEventListener("change", () => {
|
||||
const node = container.closest(".node");
|
||||
if (!node) return;
|
||||
|
||||
const textarea = node.querySelector("textarea");
|
||||
if (!textarea) return;
|
||||
|
||||
const selectedValue = select.value;
|
||||
updatePromptValue(textarea, selectedValue);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
if (document.readyState !== "loading") {
|
||||
setupLiveCharacterActionPromptHook();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", setupLiveCharacterActionPromptHook);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// comfyui-animecharacterselect/js/enhanced_prompt.js
|
||||
|
||||
console.log("EnhancedPrompt JS Loaded");
|
||||
|
||||
function insertIntoPrompt(node, dropdownName) {
|
||||
// Zoek het promptveld (textarea) en de dropdown
|
||||
const promptWidget = node.widgets.find(w => w.name === "prompt");
|
||||
const dropdownWidget = node.widgets.find(w => w.name === dropdownName);
|
||||
if (!promptWidget || !dropdownWidget) return;
|
||||
|
||||
const promptInput = promptWidget.inputEl;
|
||||
const selectInput = dropdownWidget.inputEl;
|
||||
if (!promptInput || !selectInput) return;
|
||||
|
||||
selectInput.addEventListener("change", () => {
|
||||
const value = selectInput.value;
|
||||
if (value && !value.startsWith("Select")) {
|
||||
let current = promptInput.value.trim();
|
||||
// Voeg toe als niet al aanwezig
|
||||
if (!current.includes(value)) {
|
||||
if (current.length > 0) {
|
||||
current += ", " + value;
|
||||
} else {
|
||||
current = value;
|
||||
}
|
||||
promptInput.value = current;
|
||||
promptWidget.value = current;
|
||||
promptInput.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
}
|
||||
// Reset dropdown
|
||||
selectInput.value = selectInput.options[0].value;
|
||||
selectInput.dispatchEvent(new Event("change"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hook in op Node create/update
|
||||
app.registerExtension({
|
||||
name: "ComfyUI_AnimeCharacterSelect",
|
||||
nodeCreated(node) {
|
||||
// Je node heet "EnhancedCharacterPromptNode"
|
||||
if (node.type === "EnhancedCharacterPromptNode") {
|
||||
insertIntoPrompt(node, "Select to add Character");
|
||||
insertIntoPrompt(node, "Select to add Action");
|
||||
}
|
||||
},
|
||||
nodeRenamed(node) {
|
||||
if (node.type === "EnhancedCharacterPromptNode") {
|
||||
insertIntoPrompt(node, "Select to add Character");
|
||||
insertIntoPrompt(node, "Select to add Action");
|
||||
}
|
||||
}
|
||||
});
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "animecharacterselect"
|
||||
description = "WAI's Character select ported to ComfyUI with a few tweaks"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
license = {file = "LICENSE"}
|
||||
# classifiers = [
|
||||
# # For OS-independent nodes (works on all operating systems)
|
||||
|
||||
Reference in New Issue
Block a user