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.
This commit is contained in:
@@ -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。
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user