Files
3218485270 3dc594cf57 v92-v105 多国适配 + 模板导出增强 + 生图可靠性优化
- 新增韩国(KR)适配:countries/pinterest 种子词、K-pop 提示词、UI 国家列表
- 爬虫跨运行持久化已采集 URL(.collected_urls.json),同关键词重复搜索采新图
- 模板导出增强:SPU 商品属性列名前缀剥离匹配、尺码下拉框 INDIRECT 动态引用、
  SPU 字段映射(袖长/门襟/胸垫等)、季节/印花图案女装映射、建议售价统一必填、
  SKU 分类/数量/单位直接填默认值
- 自定义模式:多模态提示词独立(custom_analyze_*)、生图提示词模板可配置
- Pinterest:空选品守卫、连续空分析保护(max_empty_analyze)、flat_prompt 配置覆盖
- 生图可靠性:开始/完成进度日志、异步任务轮询超时 60s→300s
2026-08-31 18:35:51 +08:00

165 lines
9.5 KiB
Python
Raw Permalink 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.
"""节点 5/6:提示词构造(prompt_build)。
读取 prompts/<country>/ 的 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_negativecompose 生图时作为负向参数传给图像后端)。
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}