Assembles a character portrait prompt from cast_character.manifest.json fields. Self-contained (bundled manifest + ported compose logic). MIT licensed.
84 lines
3.2 KiB
Python
84 lines
3.2 KiB
Python
"""ComfyUI-node die de Character-builder-prompt bouwt uit het gedeelde cast_character-manifest."""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
try: # als package (ComfyUI) én flat (pytest) importeerbaar
|
|
from .compose_logic import compose_form, _cleanup
|
|
except ImportError: # pragma: no cover
|
|
from compose_logic import compose_form, _cleanup
|
|
|
|
_HERE = Path(__file__).resolve().parent
|
|
|
|
|
|
def _resolve_manifest_path():
|
|
"""env CHARACTER_STUDIO_MANIFEST → repo-relatief app-pad → gebundelde kopie."""
|
|
candidates = []
|
|
env = os.environ.get("CHARACTER_STUDIO_MANIFEST")
|
|
if env:
|
|
candidates.append(Path(env))
|
|
# comfy_nodes/character_studio_prompt/ → ../../orchestrator/workflows/... (bij symlink vanuit de repo)
|
|
candidates.append(_HERE.parents[1] / "orchestrator" / "workflows" / "cast_character.manifest.json")
|
|
candidates.append(_HERE / "cast_character.manifest.json") # gebundelde fallback
|
|
for p in candidates:
|
|
try:
|
|
if p and p.is_file():
|
|
return p
|
|
except OSError:
|
|
continue
|
|
return None
|
|
|
|
|
|
def _load_form_param():
|
|
p = _resolve_manifest_path()
|
|
if p is None:
|
|
return None
|
|
try:
|
|
data = json.loads(p.read_text(encoding="utf-8"))
|
|
return next((x for x in data.get("params", []) if x.get("type") == "form_builder"), None)
|
|
except Exception: # noqa: BLE001 — kapot/onvindbaar manifest → node toont een foutmelding
|
|
return None
|
|
|
|
|
|
def _input_types_from(cb: dict) -> dict:
|
|
"""Bouw de ComfyUI INPUT_TYPES-dict uit de manifest-velden (enum→dropdown, tekst/wardrobe→STRING)."""
|
|
if not cb:
|
|
return {"required": {"error": ("STRING", {"default": "cast_character.manifest.json niet gevonden", "multiline": True})}}
|
|
required = {}
|
|
for f in cb.get("fields", []):
|
|
key = f["key"]
|
|
if f.get("type") == "enum" and f.get("options"):
|
|
options = list(f["options"])
|
|
default = f.get("default") if f.get("default") in options else options[0]
|
|
required[key] = (options, {"default": default})
|
|
else: # text, wardrobe, of onbekend type → vrije tekst
|
|
required[key] = ("STRING", {"default": f.get("default", ""), "multiline": False})
|
|
return {"required": required, "optional": {"extra": ("STRING", {"default": "", "multiline": True})}}
|
|
|
|
|
|
_CB = _load_form_param()
|
|
|
|
|
|
class CharacterStudioPrompt:
|
|
"""Character Studio — portret-prompt: vult de manifest-template met je gekozen velden."""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return _input_types_from(_CB)
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("prompt",)
|
|
FUNCTION = "build"
|
|
CATEGORY = "Character Studio"
|
|
|
|
def build(self, extra="", **kwargs):
|
|
if not _CB:
|
|
return (kwargs.get("error", "cast_character.manifest.json niet gevonden"),)
|
|
wardrobe_text = kwargs.get("wardrobe", "")
|
|
fields = {k: v for k, v in kwargs.items() if k != "wardrobe"}
|
|
value = {"fields": fields, "wardrobe": {"mode": "free", "text": wardrobe_text}}
|
|
prompt = compose_form(_CB, value)
|
|
if extra and str(extra).strip():
|
|
prompt = _cleanup(prompt + " " + str(extra).strip())
|
|
return (prompt,)
|