v106-v109 童装支持 + 标题模板外部化 + 模板导出增强

- 新增男童/女童检测(gender_from_category 优先判童装)与童装场景图生成
  (configs/kids_features.yaml:模特/场景/服装风格,同一商品固定同一组)
- 童装 SPU 字段映射:kids_type→SPU商品属性-类型、kids_age→适用年龄段、
  target_audience 按性别映射、kids_type_map 女童「上衣」→「针织上衣」
- 标题生成提示词外部化:prompts/title_prompt_{1,2,3}.md + config.yaml 路由表
- 模板多站点匹配:经营站点可配多个,命中任意一个即匹配
- 种草图生成失败自动重试(seed_shot.retries,换场景/模特/风格)
- 模板导出新增 Preview 文件夹(成功产品 _composite.oss.jpg + result.xlsx)
- 修复 SKU 尺码未按从小到大排序(_size_rank 支持单一年龄码 6Y/10Y)
This commit is contained in:
2026-09-01 17:24:23 +08:00
parent 3dc594cf57
commit d68cc3b9e3
20 changed files with 826 additions and 136 deletions
+21 -16
View File
@@ -113,18 +113,26 @@ FLAT_LAY_PROMPT = (
)
def _active_prompt(kind: str, prompts: Optional[Dict] = None) -> str:
def _read_prompt_md(filename: str) -> str:
"""读取 prompts/<filename> 提示词文件:优先运行根(exe 旁,可编辑),回退数据根;找不到/空返回空串。"""
for base in (runtime_root(), project_root()):
p = base / "prompts" / filename
if p.exists():
text = p.read_text(encoding="utf-8").strip()
if text:
return text
return ""
def _active_prompt(kind: str) -> str:
"""返回当前图源应使用的合成提示词。
kind="model" → 模特三图提示词(配置覆盖优先生效,否则内置 MODEL_WEAR_PROMPT);
kind="flat" → 平铺三图提示词(配置覆盖优先生效,否则内置 FLAT_LAY_PROMPT)。
kind="model" → 模特三图提示词(优先读 prompts/model_prompt.md,缺失/留空回退内置 MODEL_WEAR_PROMPT);
kind="flat" → 平铺三图提示词(优先读 prompts/flat_prompt.md,缺失/留空回退内置 FLAT_LAY_PROMPT)。
"""
cfgs = prompts or {}
if kind == "flat":
return (str(cfgs.get("flat_prompt") or "").strip()
or FLAT_LAY_PROMPT)
return (str(cfgs.get("model_prompt") or "").strip()
or MODEL_WEAR_PROMPT)
return _read_prompt_md("flat_prompt.md") or FLAT_LAY_PROMPT
return _read_prompt_md("model_prompt.md") or MODEL_WEAR_PROMPT
def _resolve_sku(db_path, basemap_root, spu_code: str, sku_code: str, colors=None) -> Optional[str]:
@@ -187,7 +195,7 @@ def _process_spu(
db_path, basemap_root, material_root, category, prod_dir, brief, ib,
spu, sku_code, pcfg, errors, shared_design=None, title_backend=None, country="",
img_code="", model_img=None, design_size="1024x1024", compose_size="1536x2048",
on_503=None, model_kind="model", prompts=None,
on_503=None, model_kind="model",
) -> Optional[Dict[str, Any]]:
"""处理单个款号:选色 → 底图 → 设计稿 → (mark==1) 模特 → 合成 → 模板导出。
shared_design: compose 节点生成的纯印花设计稿路径(图2);为 None 时回退本节点 generate。
@@ -195,7 +203,6 @@ def _process_spu(
(按货号命名,包含该货号对应的所有图片)。
on_503: 致命图像服务错误(503/账户不可用)回调(供调用方提前终止任务)。
model_kind: 图源类型 "model"(模特图)/ "flat"(平铺图),决定用哪个合成提示词。
prompts: {"model": str, "flat": str} 可配置提示词覆盖(来自 config.product.mark_dirs);缺省用内置。
返回 result dict;内部异常已兜底,不中断。
"""
# 输出目录 = 货号子文件夹(product/DG000/…),该货号所有图片都放这里
@@ -324,8 +331,8 @@ def _process_spu(
elif model_img is not None:
composite_path = str(prod_dir / f"{img_code}_composite.png")
try:
# 三图合成:按图源类型选提示词(可配置覆盖优先,否则内置 MODEL_WEAR/FLAT_LAY
wear_prompt = _active_prompt(model_kind, prompts)
# 三图合成:按图源类型选提示词(优先读 prompts/*.md,否则内置 MODEL_WEAR/FLAT_LAY
wear_prompt = _active_prompt(model_kind)
kind_label = "平铺" if model_kind == "flat" else "模特"
print(f"{tag} {kind_label}三图合成提交中(3 参考图 img2img,网关处理约 2-6 分钟,请耐心等待)…")
t0 = time.time()
@@ -407,7 +414,7 @@ def _process_spu(
continue
cp = str(prod_dir / f"{img_code}_{str(sc).split('-')[-1]}_composite.png")
try:
ib.print(_active_prompt(model_kind, prompts), str(model_img), cp,
ib.print(_active_prompt(model_kind), str(model_img), cp,
brief.get("composite_negative", ""),
extra_images=[design_path, str(bm)], # 图2印花, 图3该色底图
size=compose_size) # 合成图尺寸按 config compose.size
@@ -546,7 +553,6 @@ def product_node(state: Dict[str, Any]) -> Dict[str, Any]:
# → product_node 用对应提示词合成(模特三图 / 平铺三图)
model_assign: Dict[int, Any] = {}
mark_dirs = pcfg.get("mark_dirs") or {}
prompts_cfg = (mark_dirs.get("1") or {})
try:
from graph.product import build_mark_sources
sources = build_mark_sources(material_root, mark_dirs, category)
@@ -559,7 +565,7 @@ def product_node(state: Dict[str, Any]) -> Dict[str, Any]:
if source_pool:
for _i in range(len(worklist)):
img, kind = random.choice(source_pool) # 每任务随机抽一张(含 kind
model_assign[_i] = {"img": img, "kind": kind, "prompts": prompts_cfg}
model_assign[_i] = {"img": img, "kind": kind}
print(f"[product] 任务级图源分配:{len(model_assign)} 个产品任务(mark_dirs 模特/平铺图随机抽,含 kind)")
else:
print("[product] material_library 无任何可用图(模特/平铺均无)→ 跳过合成,仅导出模板")
@@ -628,7 +634,6 @@ def product_node(state: Dict[str, Any]) -> Dict[str, Any]:
country, img_code=img_code,
model_img=_src.get("img"), # 按任务序号取独立随机图源
model_kind=_src.get("kind", "model"),
prompts=_src.get("prompts"),
design_size=str((config.get("compose") or {}).get("design_size") or "1024x1024"),
compose_size=str((config.get("compose") or {}).get("size") or "1536x2048"))
if r: