Files
zeeoale-PromptCreatorNode/PromptCreatorNode.py
T

311 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import random
import json
import requests
class PromptCreatorNode:
@classmethod
def INPUT_TYPES(cls):
json_dir = os.path.join(os.path.dirname(__file__), "JSON_DATA")
json_files = [f for f in os.listdir(json_dir) if f.endswith(".json")]
return {
"required": {
"json_name": (sorted(json_files),),
"use_enhancer": (["none", "ollama", "openai", "cohere", "gemini"],),
"enhancer_mode": (["standard", "dark_ritual", "aesthetic_focus", "compact"],),
"add_symbols": (["no", "yes"],),
"seed": ("INT", {"default": 0}),
"gender": (["neutral", "female", "2 female", "3 female", "female vampire", "male", "custom"],),
"custom_intro": ("STRING", {"default": "", "multiline": True}),
"horror_intensity": (["auto"] + [str(i) for i in range(11)],),
"lora_triggers": ("STRING", {"default": ""}),
"subject_count": (["1", "2", "3"],),
"lock_last_prompt": (["no", "yes"], {"default": "no"}),
"multi_object_count": ("INT", {"default": 1, "min": 1, "max": 5}),
# Ollama settings
"ollama_host": ("STRING", {"default": "http://192.168.1.1:11434"}),
"ollama_model": ("STRING", {"default": "llama3.2"})
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "generate_prompt"
CATEGORY = "Prompt Creator"
def _system_prompts(self):
return {
"standard": (
"You are a professional prompt enhancer for AI image generation. "
"Return ONE long, multi-clause sentence (~120–160 words), richly descriptive. "
"Cover: genre/style; era; foreground/midground/background; materials & textures; color palette (3–5 tones); "
"lighting (type/direction/intensity); atmosphere; camera angle & lens (mm); composition rule; 3–5 post-process keywords. "
"No story, no dialogue, no lists, no headings."
),
"dark_ritual": (
"You are a cinematic horror stylist. Return ONE long, multi-clause sentence (~120–160 words) in poetic gothic tone; "
"focus on occult symbolism, chiaroscuro lighting, decayed textures, sinister color palette, slow shutter/soft bloom, "
"low-angle or three-quarter framing, and lingering dread. No story, no lists."
),
"aesthetic_focus": (
"You are a fashion-forward visual stylist. Return ONE long, multi-clause sentence (~120–160 words) with elegance and balance; "
"specify fabrics, cuts, surface finishes, palette harmony, key light/fill/rim, lens and composition details, "
"and tasteful post-process cues. No lists, no headings."
),
"compact": (
"You are an expert in minimal but expressive prompts. Return ONE compact sentence (~70–100 words) with dense visuals; "
"include palette, lighting, camera angle/lens, and one composition rule. No lists."
),
}
def _build_prompt_from_json(self, data, gender, custom_intro, horror_intensity, subject_count, multi_object_count):
parts = []
# Gender / subject intro
if gender == "custom" and custom_intro.strip():
parts.append(custom_intro.strip())
else:
gender_inserted = False
if gender in data:
values = data[gender]
if isinstance(values, list) and values:
parts.append(random.choice(values))
gender_inserted = True
if not gender_inserted:
gender_defaults = {
"male": "a mysterious man",
"female": "a beautiful woman",
"2 female": "two beautiful womans",
"3 female": "three unique beautiful womans",
"female vampire": "a beautiful vampire woman"
}
parts.append(gender_defaults.get(gender, "a striking figure"))
# Optional color realm
color_realm_value = None
if "COLOR_REALM" in data:
possible_realms = data["COLOR_REALM"]
if isinstance(possible_realms, list) and possible_realms:
color_realm_value = random.choice(possible_realms)
print(f"[PromptCreator] Using COLOR_REALM: {color_realm_value}")
parts.append(color_realm_value)
multi_keys = ["OBJECTS", "ACCESSORIES"]
# Pick values according to realm or flat lists
if color_realm_value:
for key in ["OUTFITS", "LIGHTING", "BACKGROUNDS", "OBJECTS", "ACCESSORIES", "ATMOSPHERES"]:
values_by_realm = data.get(key, {}).get(color_realm_value, [])
if isinstance(values_by_realm, list) and values_by_realm:
if key in multi_keys:
sampled = random.sample(values_by_realm, min(multi_object_count, len(values_by_realm)))
parts.extend(sampled)
else:
parts.append(random.choice(values_by_realm))
else:
for key, values in data.items():
if key.lower() in ["male", "female", "neutral", "horror_intensity", "color_realm"]:
continue
if isinstance(values, list) and values:
if key in multi_keys:
sampled = random.sample(values, min(multi_object_count, len(values)))
parts.extend(sampled)
else:
parts.append(random.choice(values))
# Horror intensity
if horror_intensity != "auto" and "HORROR_INTENSITY" in data:
try:
horror_level = int(horror_intensity)
intensity_entries = data["HORROR_INTENSITY"]
if isinstance(intensity_entries, dict):
matching = intensity_entries.get(str(horror_level))
if matching:
parts.append(matching)
except ValueError:
pass
if subject_count != "1":
parts.append(f"{subject_count} subjects present")
return ", ".join(parts)
def _read_api_keys(self, base_path):
keys_path = os.path.join(base_path, "api_keys.txt")
keys = {}
if os.path.exists(keys_path):
with open(keys_path, "r", encoding="utf-8") as f:
for line in f:
if "=" in line:
k, v = line.strip().split("=", 1)
keys[k.strip()] = v.strip()
return keys
def _enhance_with_ollama(self, host, model, system_prompt, user_prompt):
# Prefer chat to support system role
r = requests.post(f"{host}/api/chat", json={
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
"stream": False
}, timeout=120)
r.raise_for_status()
return r.json()["message"]["content"].strip()
def _enhance_with_openai(self, base_path, system_prompt, user_prompt):
import openai
keys = self._read_api_keys(base_path)
openai.api_key = keys.get("openai", "")
resp = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
)
return resp.choices[0].message.content.strip()
def _enhance_with_cohere(self, base_path, system_prompt, user_prompt):
import cohere, re, random
keys = self._read_api_keys(base_path)
co = cohere.Client(keys.get("cohere", ""))
fused = (
f"SYSTEM: {system_prompt}\n"
f"USER: Expand this seed into the required format. "
f"Write exactly one sentence (allow multiple clauses with commas and semicolons). "
f"\"Seed: {user_prompt}\\n\\nAssistant:\""
)
# Prova GENERATE con più candidati e parametri 'ricchi'
try:
resp = co.generate(
model="command-r-plus",
prompt=fused,
max_tokens=700,
temperature=0.9,
p=0.85,
k=0,
frequency_penalty=0.15,
presence_penalty=0.15,
truncate="NONE",
num_generations=3,
stop_sequences=["\nSYSTEM:", "\nUSER:", "\nAssistant:"]
)
cands = [g.text.strip() for g in resp.generations if getattr(g, "text", None)]
cands = [re.sub(r"\s*\n+\s*", " ", c).strip() for c in cands]
# scegli la più lunga (più ricca)
best = max(cands, key=len) if cands else ""
# fallback: se ancora troppo corta, un secondo pass più caldo
if len(best.split()) < 80:
resp2 = co.generate(
model="command-r-plus",
prompt=fused,
max_tokens=800,
temperature=1.0,
p=0.9,
k=0,
truncate="NONE",
num_generations=2
)
c2 = [g.text.strip() for g in resp2.generations if getattr(g, "text", None)]
c2 = [re.sub(r"\s*\n+\s*", " ", c).strip() for c in c2]
if c2:
best = max([best] + c2, key=len)
return best
except Exception:
# Fallback su CHAT se generate non è disponibile/limita
r = co.chat(
model="command-r-plus",
messages=[
{"role": "SYSTEM", "content": system_prompt},
{"role": "USER", "content": user_prompt + "\nReturn one long sentence (~100–130 words), no lists."}
],
temperature=0.9,
max_tokens=700
)
txt = (getattr(r, "text", "") or "").strip()
return re.sub(r"\s*\n+\s*", " ", txt)
def _enhance_with_gemini(self, base_path, system_prompt, user_prompt):
import google.generativeai as genai
keys = self._read_api_keys(base_path)
genai.configure(api_key=keys.get("gemini", ""))
# In Gemini, passiamo tutto come testo
model = genai.GenerativeModel("models/gemini-2.5-pro")
txt = (
f"SYSTEM: {system_prompt}\n"
f"USER: {user_prompt}\n"
"Return only the final prompt as a single paragraph."
)
r = model.generate_content(txt)
return r.candidates[0].content.parts[0].text.strip()
def generate_prompt(self, json_name, use_enhancer, enhancer_mode, add_symbols, seed, gender, custom_intro, horror_intensity, lora_triggers, subject_count, lock_last_prompt, multi_object_count, ollama_host, ollama_model):
base_path = os.path.dirname(__file__)
json_path = os.path.join(base_path, "JSON_DATA", json_name)
# history lock
history_path = os.path.join(base_path, "history", f"last_prompt_{json_name}.txt")
if lock_last_prompt == "yes" and os.path.exists(history_path):
with open(history_path, "r", encoding="utf-8") as f:
prompt = f.read().strip()
print("[PromptCreator] Prompt locked and loaded from history")
return (prompt,)
try:
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
print(f"[PromptCreator] Errore nel caricamento del JSON: {e}")
return ("",)
# Seed opzionale per coerenza
if seed:
random.seed(seed)
# Costruisci prompt base
prompt = self._build_prompt_from_json(
data, gender, custom_intro, horror_intensity, subject_count, multi_object_count
)
# System prompt condiviso per tutti i backend
system_prompts = self._system_prompts()
system_prompt = system_prompts.get(enhancer_mode, system_prompts["standard"])
user_prompt = f"Enhance this prompt: {prompt}"
# Enhancer backends
if use_enhancer != "none":
try:
if use_enhancer == "ollama":
prompt = self._enhance_with_ollama(ollama_host, ollama_model, system_prompt, user_prompt)
elif use_enhancer == "openai":
prompt = self._enhance_with_openai(base_path, system_prompt, user_prompt)
elif use_enhancer == "cohere":
prompt = self._enhance_with_cohere(base_path, system_prompt, user_prompt)
elif use_enhancer == "gemini":
prompt = self._enhance_with_gemini(base_path, system_prompt, user_prompt)
except Exception as e:
print(f"[PromptCreator] Errore nell'enhancer ({use_enhancer}): {e}")
# LoRA triggers
if lora_triggers.strip():
lts = [x.strip() for x in lora_triggers.split(",") if x.strip()]
if lts:
prompt += ", " + ", ".join(lts)
print(f"[PromptCreator] LoRA triggers aggiunti: {lts}")
if add_symbols == "yes":
prompt = f"[{prompt}]"
os.makedirs(os.path.join(base_path, "history"), exist_ok=True)
with open(history_path, "w", encoding="utf-8") as f:
f.write(prompt.strip())
print(f"[PromptCreator] Prompt finale: {prompt}")
return (prompt,)