From a8b885509e0b8ad2c53954615112e08f58a64a3f Mon Sep 17 00:00:00 2001 From: Nakano Kenji Date: Mon, 2 Feb 2026 01:38:25 +0800 Subject: [PATCH] feat: support prompt parts JSON for anima vars Expose extra JSON keys as Mustache vars; accept 'neg' as alias; auto-compose 'positive' from ordered parts when missing. --- docs/anima_prompt_parts.md | 71 +++++++++++++++++++++++++++++++++++++ nodes/prompt_json_unpack.py | 53 ++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 docs/anima_prompt_parts.md diff --git a/docs/anima_prompt_parts.md b/docs/anima_prompt_parts.md new file mode 100644 index 0000000..7e60613 --- /dev/null +++ b/docs/anima_prompt_parts.md @@ -0,0 +1,71 @@ +## Anima 分段提示词(JSON 输出 8 段 + 参数) + +目标:让 LLM **一次性输出一个 JSON**,里面既包含: + +- 你想用 Mustache 拼接的 8 段字段: + `quality_meta_year_safe / count / character / series / artist / style / tags / neg` +- 同时给出可直接接到出图工作流的:`positive / negative + width/height/steps/cfg/sampler/seed + notes` + +这样你可以: + +- 直接用 `Prompt JSON Unpack` 接出 `positive/negative/参数` +- 或者用 `{{quality_meta_year_safe}}, {{count}}, ...` 重新拼一遍(做“可编辑分段”工作流) + +--- + +## 直接可用的 System Prompt(粘贴到 `SimpleChat -> Chat` 的 `system`) + +```text +你是“Anima(circlestone-labs/Anima)专用提示词工程师”,目标是在 ComfyUI 里用 Anima 生成高质量二次元/插画向图像(非写实、非摄影)。 + +### 硬规则(必须遵守) +1) 输出必须可直接用于 ComfyUI:通过 JSON 字段给出正面/负面提示词和参数建议。 +2) 标签顺序固定为: + [质量/元数据/年份/安全] [人数] [角色] [作品] [画师] [通用标签] +3) 画师标签必须以 @ 开头(例如 @xxx)。 +4) 允许混合:Danbooru 标签 + 自然语言。自然语言至少 2 句;多人/复杂构图时要逐个描述,避免只堆角色名。 +5) 默认参数(可按需求微调): + - 分辨率:约 1MP(如 1024x1024 / 896x1152 / 1152x896) + - Steps:30–50 + - CFG:4–5 + - Sampler:优先 er_sde;也可 euler_a;更发散可 dpmpp_2m_sde_gpu +6) 安全标签必须明确:safe / sensitive / nsfw / explicit,并在负面提示词里加入“相反约束”: + - 若正面为 safe:负面必须包含 nsfw, explicit + - 若正面为 nsfw 或 explicit:负面必须包含 safe + - 若正面为 sensitive:负面至少包含 explicit(可按情况加入 nsfw) +7) 不要追求写实:避免 photo, realistic, 真实摄影、皮肤毛孔等措辞;应强调 illustration, anime style, painterly, lineart 等。 +8) 若用户想要非二次元风格:positive 第一行写 dataset tag(ye-pop 或 deviantart),换行后再给标题/描述,再给正常标签行。 + +### 生成技巧(写进 notes) +- 用年份/时期控制画风:year 2025 / newest / recent / mid / early / old +- 用质量标签:masterpiece / best quality 或 score_9..score_1(可混用) +- 角色外观要说清:发色发型、眼睛、服装、表情、姿态、镜头、光照、背景元素 +- Tag dropout 存在:不必塞满所有标签,但关键标签必须有 + +### 输出格式(必须严格 JSON;不要代码块;不要额外解释文字) +你必须输出一个 JSON 对象,键固定如下(所有字段都要给,没信息就给空字符串): +{ + "quality_meta_year_safe": "masterpiece, best quality, year 2025, safe", + "count": "1girl", + "character": "角色名与关键外观(不要重复 count/作品/画师)", + "series": "作品名或世界观标签(可为空)", + "artist": "@画师名(必须 @ 开头;可为空)", + "style": "画风/技法(如 illustration, anime style, lineart, painterly...)", + "tags": "通用标签为主 + 至少两句自然语言描述(可换行写)", + "neg": "负面标签 + 相反安全约束(例如 safe -> nsfw, explicit)", + + "positive": "把上面 7 段按固定顺序拼成一行:quality_meta_year_safe, count, character, series, artist, style, tags", + "negative": "与 neg 相同", + + "width": 1024, + "height": 1024, + "steps": 40, + "cfg": 4.5, + "sampler": "er_sde", + "seed": -1, + "notes": "1-3条简短技巧/提醒" +} + +现在开始:根据用户输入生成上述 JSON。 +``` + diff --git a/nodes/prompt_json_unpack.py b/nodes/prompt_json_unpack.py index 0d2cbdb..db6ad07 100644 --- a/nodes/prompt_json_unpack.py +++ b/nodes/prompt_json_unpack.py @@ -99,6 +99,21 @@ def _as_str(value: Any, default: str = "") -> str: return str(value) +def _clean_piece(text: str) -> str: + if not isinstance(text, str): + return "" + s = text.strip() + # trim common trailing separators so joining doesn't create ", ," + s = s.rstrip(",,") + return s.strip() + + +def _join_pieces(*pieces: str) -> str: + cleaned = [_clean_piece(p) for p in pieces] + cleaned = [p for p in cleaned if p] + return ", ".join(cleaned) + + _SAMPLER_ALIASES = { # community shorthand -> ComfyUI names "euler_a": "euler_ancestral", @@ -171,8 +186,23 @@ class SimpleChatPromptJsonUnpack: if not isinstance(data, dict): raise ValueError("Expected a JSON object (dict) at top-level.") + # Core fields positive = _as_str(data.get("positive", ""), "") - negative = _as_str(data.get("negative", ""), "") + negative = _as_str(data.get("negative", data.get("neg", "")), "") + + # Optional "parts" schema (for Mustache templating / UI editing) + # If `positive` is missing, build it from parts in Anima tag order: + # [quality/meta/year/safety] [count] [character] [series] [artist] [style] [tags] + if not positive.strip(): + positive = _join_pieces( + _as_str(data.get("quality_meta_year_safe", ""), ""), + _as_str(data.get("count", ""), ""), + _as_str(data.get("character", ""), ""), + _as_str(data.get("series", ""), ""), + _as_str(data.get("artist", ""), ""), + _as_str(data.get("style", ""), ""), + _as_str(data.get("tags", ""), ""), + ) width = _as_int(data.get("width"), 1024) height = _as_int(data.get("height"), 1024) @@ -226,6 +256,27 @@ class SimpleChatPromptJsonUnpack: "anima.备注": notes, } + # Also expose any extra top-level JSON keys as Mustache vars (backward compatible): + # - Enables schemas like { "quality_meta_year_safe": "...", ... } without changing nodes. + for k, v in data.items(): + if not isinstance(k, str) or not k: + continue + if k in vars_dict: + continue + if k.startswith("anima."): + # Avoid clobbering prefixed keys we already manage. + continue + + # Keep scalars as-is; stringify everything else (lists/objects). + if v is None or isinstance(v, (str, int, float, bool)): + vars_dict[k] = "" if v is None else v + else: + vars_dict[k] = _as_str(v, "") + + prefixed = f"anima.{k}" + if prefixed not in vars_dict: + vars_dict[prefixed] = vars_dict[k] + # Build an actual sampler object for SAMPLER output (for custom-sampling graphs) import comfy.samplers sampler_obj = comfy.samplers.sampler_object(sampler_name)