"""节点 5/6:提示词构造(prompt_build)。 读取 prompts// 的 extra 风格规则,用固定模板装配四种最终提示词 (image_prompt / wearable_prompt / composite_prompt / composite_negative)。 四要素缺失时用 derive_style_palette 动态兜底,保证每条提示词结构一致、有规则。 """ from typing import Any, Dict, List import random from graph.style_rules import derive_style_palette, derive_composition from graph.templates import assemble_prompts from graph.validate import validate_brief, with_fallback # —— Pinterest 图生图生最终设计稿时统一追加的「小印花 + 纯白底」约束段 —— # (从热点搜集的文字生图模板里提炼:尺寸缩小、禁止自带背景/满幅) PINTEREST_PRINT_SUFFIX = ( " standalone pure print design on a pure white background, " "the print artwork is SMALL and CENTERED with clearly larger white margins around it, " "print area between about 15x18 cm and 26x32 cm, " "do NOT fill the entire canvas, do NOT force full-bleed, " "do NOT add any gradient, texture or background color behind the artwork, " "no garment, no shirt, no model, no mannequin, no watermark" ) # Pinterest 生图提示词 3 段结构中第 2 段(仅当设计含文字时追加): # 要求模型把引号内的文字按原文逐字正确拼写,避免乱码/拼错 SPELLING_RULE = ( "Render every phrase shown in quotes exactly as written, " "correctly spelled." ) # review(疑似商标/受保护主题)简报统一追加的「原创化魔改」引导段: # 只做风格参考,禁复刻品牌/商标/角色,换名换细节,生成通用非侵权致敬式设计。 # 两个模式(热点采集 / Pinterest 参考)共用同一文本,避免不一致。 REVIEW_REBRAND_HINT = ( "IMPORTANT: this theme is ONLY a loose stylistic reference. " "Do NOT reproduce any brand logo, trademark, character, mascot, copyrighted artwork or real person. " "Create a fully ORIGINAL design with a different name and distinct visual details and colors — " "a generic, non-infringing homage in the same mood, clearly distinct from the original." ) # —— 自定义模式(custom)生图模板:固定前缀 + 分析模型 image_prompt + 固定负向 —— # 自定义模式分析模型产出的是「新设计描述」,生图时套用这套固定模板(含防复制/防服装约束), # 负向用固定文本(写入 composite_negative,compose 生图时作为负向参数传给图像后端)。 CUSTOM_IMAGE_PROMPT_TEMPLATE = ( "Use the attached bestseller product photo only as loose inspiration for " "overall mood, theme, era and style genre — do NOT reproduce, trace, " "rearrange, recolor or closely imitate its printed artwork, characters, " "layout or text, and do NOT render a shirt, garment, model, hanger, photo " "scene, product mockup or background of any kind. First, carefully examine " "the printed artwork on the garment in the attached photo: zoom in mentally " "on the print area, identify its subject, technique, palette and layout, and " "base the new design on THOSE observed traits. Ignore the model, background " "and photo quality. Then: {image_prompt}" ) CUSTOM_NEGATIVE_PROMPT = ( "copy of reference artwork, lookalike of the bestseller print, characters, " "mascots, likenesses, logos, trademarks, watermark, photorealistic shirt, " "apparel, product mockup, model, garment, hanger, busy background" ) # 图像生成策略敏感词 → 安全等效描述(生成设计稿前清洗 motif, # 避免 gpt-image 等内容策略频繁拦截导致"生图限制多") _IMG_RISKY_SWAP = { "skull": "smiley mascot", "skeleton": "cute mascot", "blood": "red accents", "gore": "bold shapes", "gun": "star", "weapon": "tool", "bomb": "firework", "drug": "confetti", "demon": "cute monster", "devil": "mischievous imp", "occult": "mystic pattern", "satanic": "dark pattern", "nazi": "retro emblem", "hitler": "retro emblem", "zombie": "friendly ghoul", "horror": "spooky-cute", "vampire": "night owl", "politics": "abstract shapes", "political": "abstract", "president": "captain", "army": "team", "police": "officer", } def _safe_motif(motif: str) -> str: """清洗 motif 中的图像策略敏感词(替换为安全等效描述),降低生图内容政策拦截率。""" low = motif.lower() for k, v in _IMG_RISKY_SWAP.items(): if k in low: # 按词边界替换(避免误伤 "letterhead" 等) import re motif = re.sub(rf"\b{re.escape(k)}\b", v, motif, flags=re.IGNORECASE) low = motif.lower() return motif @with_fallback("prompt_build") def prompt_node(state: Dict[str, Any]) -> Dict[str, Any]: screened: List[Dict[str, Any]] = state.get("screened") or [] config = state["config"] country = state["country"] cc = state["country_config"] extra_rules = cc.get("extra_style_rules") or [] tpls = config.get("prompt_templates") or {} # Pinterest 生图提示词固定段:可配置(config.pinterest.prompt_pieces),留空/缺失回退内置常量。 # 自定义模式(custom_mode)用独立的一套固定段(config.custom.prompt_pieces),默认与 Pinterest 相同、可单独编辑。 if bool(state.get("custom_mode")): pp = (config.get("custom") or {}).get("prompt_pieces") or {} else: pp = (config.get("pinterest") or {}).get("prompt_pieces") or {} print_suffix = (pp.get("print_suffix") or "").strip() or PINTEREST_PRINT_SUFFIX spelling_rule = (pp.get("spelling_rule") or "").strip() or SPELLING_RULE review_rebrand_hint = (pp.get("review_rebrand_hint") or "").strip() or REVIEW_REBRAND_HINT custom_ip_tpl = (pp.get("image_prompt_template") or "").strip() or CUSTOM_IMAGE_PROMPT_TEMPLATE custom_neg = (pp.get("negative_prompt") or "").strip() or CUSTOM_NEGATIVE_PROMPT briefs: List[Dict[str, Any]] = [] for r in screened: r = validate_brief(r) art, pal = derive_style_palette( r["topic"], country, extra_rules=extra_rules, category=r.get("design_category") ) motif = (r.get("motif") or "").strip() or r.get("topic", "") cleaned = _safe_motif(motif) if cleaned != motif: print(f"[prompt] motif 敏感词清洗: 「{motif}」→「{cleaned}」(降低生图内容政策拦截)") r["motif"] = cleaned motif = cleaned art_style = (r.get("art_style") or art).strip() palette = (r.get("color_palette") or pal).strip() composition = (r.get("composition") or derive_composition(r["topic"], r.get("design_category"))).strip() prompts = assemble_prompts(motif, art_style, palette, composition, tpls, country) llm_ip = (r.get("image_prompt") or "").strip() if r.get("source") == "pinterest" and llm_ip: if bool(state.get("custom_mode")): # —— 自定义模式:固定模板(前缀 + 分析 image_prompt),负向用固定文本 —— # 模板含防复制/防服装约束,不再追加 print_suffix;负向写入 composite_negative, # compose 生图时作为负向参数传给图像后端。 prompts["image_prompt"] = custom_ip_tpl.replace("{image_prompt}", llm_ip) prompts["composite_negative"] = custom_neg print(f"[prompt] 自定义模式按固定模板拼 image_prompt(含固定负向): 「{r['topic']}」") else: # —— Pinterest 参考模式:跳过四要素模板,按 3 段结构拼 image_prompt —— # ① image_prompt(分析模型产出) + 固定输出形态后缀 PINTEREST_PRINT_SUFFIX # ② 拼写锁定句 SPELLING_RULE(仅当 LLM image_prompt 已含引号文字段时) # 是否含文字、拼写与否均由分析模型产出决定,本模式不注入 slogan。 seg: List[str] = [llm_ip, print_suffix] if '"' in llm_ip: seg.append(spelling_rule) prompts["image_prompt"] = ", ".join(seg) print(f"[prompt] Pinterest 简报按 3 段结构拼 image_prompt(跳过四要素模板): 「{r['topic']}」") else: # —— 热点采集模式:四要素模板装配 + 文字印花(约 30% 概率注入 slogan)—— slogan = (r.get("slogan") or "").strip() if slogan and random.random() < float(config.get("prompt_templates", {}).get("text_ratio", 0.3)): text_seg = (f', with the text "{slogan}" rendered as bold retro typography, ' f'lettering clean and correctly spelled, high contrast, as the focal text of the print') prompts["image_prompt"] = prompts["image_prompt"] + text_seg r["used_slogan"] = slogan # review(疑似商标/受保护主题)→ 追加「原创化魔改」引导(两个模式通用) if str(r.get("risk_level", "")).strip().lower() == "review": prompts["image_prompt"] = prompts["image_prompt"] + " " + review_rebrand_hint print(f"[prompt] review 简报注入原创化魔改引导: 「{r['topic']}」") r.update(prompts) r["motif"] = motif r["art_style"] = art_style r["color_palette"] = palette r["composition"] = composition briefs.append(r) stats = dict(state.get("stats") or {}) stats["prompt"] = {"briefs": len(briefs)} return {"briefs": briefs, "stats": stats}