v89-v91 模板增强 + 图源映射 + 多模态提示词可配置化
- 图源映射统一:热点采集与 Pinterest 模式均走 config.product.mark_dirs 配置,按任务序号随机抽模特图/平铺图 - 商品产地固定:统一为「中国大陆」+「产地省份=广东省」(不再读站点/字典映射) - 模板 SKU 字段检测:按建议售价同一套路检测 SKU分类/SKU数量/SKU数量单位,必填时填入单品/1/件 - 多模态分析提示词可配置:prompts/pinterest_analyze_system.md + user.md,支持国家覆盖,不丢文件回退内置 - 自定义图片模式:新增 pinterest_custom_load_node,图片数量硬校验,选品清单 ≤ 有效图片数 - 模板导出优化:写入前按货号末 3 位升序排序,不再产生空白 xlsx - 修复 v90 project review 10 项(503 致命终止、线程安全、原子写入等)
This commit is contained in:
@@ -4,6 +4,7 @@ from .fetch_node import fetch_node
|
||||
from .filter_node import filter_node
|
||||
from .oss_upload_node import oss_upload_node
|
||||
from .pinterest_analyze_node import pinterest_analyze_node
|
||||
from .pinterest_custom_load_node import pinterest_custom_load_node
|
||||
from .pinterest_scrape_node import pinterest_scrape_node
|
||||
from .pinterest_search_node import pinterest_search_node
|
||||
from .product_node import product_node
|
||||
@@ -29,4 +30,5 @@ __all__ = [
|
||||
"pinterest_search_node",
|
||||
"pinterest_scrape_node",
|
||||
"pinterest_analyze_node",
|
||||
"pinterest_custom_load_node",
|
||||
]
|
||||
|
||||
@@ -114,6 +114,11 @@ def pinterest_analyze_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if batch_size <= 0:
|
||||
batch_size = max_designs # 自动:一次最多分析 max_designs 张(每张图→1条简报)
|
||||
need = batch_size
|
||||
# 自定义模式:本地图池是唯一且有限的图源,最多分析「选品清单总数」张即可,
|
||||
# 避免多余分析(超出的图源在简报达标后由自定义路由结束,不浪费配额)。
|
||||
if bool(state.get("custom_mode")):
|
||||
_t = int(state.get("pinterest_target") or 1) or 1
|
||||
need = max(0, min(need, _t))
|
||||
|
||||
# 1) 图池取未消费图片(md5 不在 used_images);无 → 返回空,路由触发搜索
|
||||
pool = load_image_pool(output_dir, country)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Pinterest 参考模式自定义节点:加载本地图片文件夹 → 校验数量 → 注册进图池(pinterest_custom_load)。
|
||||
|
||||
仅自定义模式(pinterest.mode=custom)使用,替代 pinterest_search + pinterest_scrape:
|
||||
直接把 pinterest.custom_image_dir 内的有效图片(jpg/jpeg/png/webp)注册进图池,
|
||||
再复用 pinterest_analyze 直接送多模态分析,沿用 Pinterest 后续所有步骤
|
||||
(分析→设计→三合一→OSS→种草图→模板导出)。
|
||||
|
||||
数量校验(硬校验,不满足则不启动分析):
|
||||
1. 文件夹必须有有效图片(>0);
|
||||
2. 选品清单总数(product.spu_tasks 展开后,state.pinterest_target)必须 ≤ 有效图片数 ——
|
||||
「选品清单不得大于有效图片数」。
|
||||
|
||||
每次运行把图池重建为该文件夹的图片集(自定义模式唯一图源),并清空已消费拉黑
|
||||
(used_images),保证用户每次重新上传/选择文件夹的所有有效图片都会被重新多模态分析。
|
||||
"""
|
||||
from typing import Any, Dict
|
||||
|
||||
from graph.pinterest import (
|
||||
image_md5,
|
||||
list_valid_images,
|
||||
save_image_pool,
|
||||
save_used_images,
|
||||
)
|
||||
from graph.validate import with_fallback
|
||||
|
||||
|
||||
@with_fallback("pinterest_custom_load")
|
||||
def pinterest_custom_load_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
config = state["config"]
|
||||
output_dir = state["output_dir"]
|
||||
country = state["country"]
|
||||
|
||||
pcfg = config.get("pinterest") or {}
|
||||
folder = str(pcfg.get("custom_image_dir") or "").strip()
|
||||
|
||||
images = list_valid_images(folder)
|
||||
valid_n = len(images)
|
||||
target = int(state.get("pinterest_target") or 1)
|
||||
|
||||
errors = list(state.get("errors") or [])
|
||||
stats = dict(state.get("stats") or {})
|
||||
|
||||
# —— 硬校验 1:必须有有效图片 ——
|
||||
if valid_n == 0:
|
||||
msg = (f"自定义图片文件夹「{folder or '(未填写)'}」中没有有效图片"
|
||||
f"(请填写 pinterest.custom_image_dir,文件夹内应有 jpg/jpeg/png/webp 图片)")
|
||||
errors.append({"node": "pinterest_custom_load", "type": "ValidationError",
|
||||
"message": msg, "trace": ""})
|
||||
stats["pinterest_custom"] = {"folder": folder, "valid_images": 0, "target": target, "ok": False}
|
||||
print(f"[pinterest_custom_load] ❌ {msg}")
|
||||
return {"errors": errors, "stats": stats}
|
||||
|
||||
# —— 硬校验 2:选品清单总数 ≤ 有效图片数 ——
|
||||
if target > valid_n:
|
||||
msg = (f"选品清单数量({target})大于自定义图片有效数量({valid_n}):"
|
||||
f"选品清单不得大于有效图片数,请补充图片或减少选品")
|
||||
errors.append({"node": "pinterest_custom_load", "type": "ValidationError",
|
||||
"message": msg, "trace": ""})
|
||||
stats["pinterest_custom"] = {"folder": folder, "valid_images": valid_n, "target": target, "ok": False}
|
||||
print(f"[pinterest_custom_load] ❌ {msg}")
|
||||
return {"errors": errors, "stats": stats}
|
||||
|
||||
# —— 每次运行重建图池为该文件夹图片集 + 清空已消费拉黑 → 所有有效图片都被重新分析 ——
|
||||
pool = {"updated_at": "", "images": []}
|
||||
seen_md5: set = set()
|
||||
for f in images:
|
||||
m = str(image_md5(str(f)) or "").strip().lower()
|
||||
if not m or m in seen_md5:
|
||||
continue
|
||||
seen_md5.add(m)
|
||||
pool["images"].append({"path": str(f), "md5": m, "term": "custom"})
|
||||
save_image_pool(output_dir, country, pool)
|
||||
save_used_images(output_dir, country, set())
|
||||
|
||||
stats["pinterest_custom"] = {
|
||||
"folder": folder, "valid_images": valid_n,
|
||||
"target": target, "loaded": len(pool["images"]), "ok": True,
|
||||
}
|
||||
print(f"[pinterest_custom_load] 自定义图源:{folder} → 有效图片 {valid_n} 张(选品清单 {target}),"
|
||||
f"已注册进图池,直接进入多模态分析")
|
||||
return {"custom_mode": True, "pinterest_custom_folder": folder,
|
||||
"custom_load_ok": True, "errors": errors, "stats": stats}
|
||||
@@ -33,6 +33,8 @@ def pinterest_search_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
provider = str(pcfg.get("provider") or "openai").strip().lower()
|
||||
search_mode = str(pcfg.get("search_mode") or "direct").strip().lower()
|
||||
_raw_suffix = str(pcfg.get("search_term_suffix") if pcfg.get("search_term_suffix") is not None else " t-shirt design").strip()
|
||||
suffix = _raw_suffix if _raw_suffix else "" # 配置留空则后缀为空(不追加),不再回退默认
|
||||
want = int(pcfg.get("search_terms_per_run", 1)) # 每次搜索词数量
|
||||
seed_sample = int(pcfg.get("seed_sample", 40))
|
||||
max_used_in_prompt = int(pcfg.get("max_used_terms_in_prompt", 100))
|
||||
@@ -57,9 +59,9 @@ def pinterest_search_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
fresh = [s for s in pool if s.lower() not in used_set]
|
||||
if not fresh:
|
||||
fresh = pool # 库内词全部用过 → 允许复用(词库有限)
|
||||
terms = [f"{s} t-shirt design" if "t-shirt design" not in s.lower() else s
|
||||
terms = [f"{s} {suffix}".strip() if suffix and suffix not in s.lower() else s
|
||||
for s in random.sample(fresh, min(want, len(fresh)))]
|
||||
print(f"[pinterest_search] direct 模式:国家种子词库随机抽 {len(terms)} 个 + t-shirt design({country})")
|
||||
print(f"[pinterest_search] direct 模式:国家种子词库随机抽 {len(terms)} 个 + 后缀「{suffix}」({country})")
|
||||
else:
|
||||
used_llm = merge_used(used, attempted)
|
||||
if max_used_in_prompt > 0:
|
||||
@@ -79,7 +81,8 @@ def pinterest_search_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
if llm is not None and hasattr(llm, "generate_pinterest_terms"):
|
||||
try:
|
||||
ctx = {"country": country, "seeds": seeds, "used_terms": used_llm, "count": want}
|
||||
ctx = {"country": country, "seeds": seeds, "used_terms": used_llm, "count": want,
|
||||
"search_term_suffix": suffix}
|
||||
res = llm.generate_pinterest_terms(ctx)
|
||||
terms = [str(t).strip() for t in (res.get("search_terms") or []) if str(t).strip()]
|
||||
print(f"[pinterest_search] LLM 生成搜索词 {len(terms)} 个({country},已用词注入 {len(used_llm)})")
|
||||
@@ -89,7 +92,7 @@ def pinterest_search_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
# 3) 兜底:LLM 无结果 → 种子词池随机抽样
|
||||
if not terms:
|
||||
terms = [f"{s} t-shirt design" if "t-shirt design" not in s.lower() else s
|
||||
terms = [f"{s} {suffix}".strip() if suffix and suffix not in s.lower() else s
|
||||
for s in random.sample(seeds, min(want, len(seeds)))]
|
||||
print(f"[pinterest_search] 兜底:从种子词池取 {len(terms)} 个")
|
||||
|
||||
|
||||
+86
-32
@@ -83,6 +83,49 @@ MODEL_WEAR_PROMPT = (
|
||||
"图1原本的背景、人物、构图及光影结构100%不变,仅替换图1衣服上的印花图案与衣服底色。"
|
||||
)
|
||||
|
||||
# 平铺图专属三图合成提示词(mark 配置 flat_dir 平铺图文件夹):图1平铺实拍 + 图2印花 + 图3底图
|
||||
FLAT_LAY_PROMPT = (
|
||||
"你是一个专业的电商AI视觉合成工具,执行“高保真印花与色彩移植/印花替换”:把图2的印花设计"
|
||||
"印到图3底色的面料上,替换图1平铺衣服原有的底色与图案,输出一张“图3底色+图2印花”的平铺服装商品图。"
|
||||
"全程无人参与。\n"
|
||||
"【图片角色,按提交顺序】\n"
|
||||
"图1=衣服平铺实拍图(基底图:提供衣服版型、轮廓、褶皱、光影、拍摄背景与构图,"
|
||||
"最终输出必须与图1同角度、同摆放);\n"
|
||||
"图2=纯印花设计稿;\n"
|
||||
"图3=平铺衣服底图(只取衣服本身的底色与面料材质,忽略平铺图背景/桌面/场景,只保留面料颜色与质感)。\n"
|
||||
"TASK: 把图2的印花设计印到图3底色的面料上,替换图1平铺衣服原有的底色与图案,"
|
||||
"输出一张“图3底色+图2印花”的平铺服装商品图。全程无人参与。\n"
|
||||
"【执行规则】\n"
|
||||
"0.禁止人物:输出图中严禁出现任何人体、模特、人台/假人、头颈、手臂或“穿着效果”,"
|
||||
"必须保持图1的纯平铺俯拍商品图形式,衣服平放在原背景上。\n"
|
||||
"1.底色锁定:从图3提取衣服底色与面料,最终合成中必须100%保持不变,严禁偏色。\n"
|
||||
"2.印花提取:从图2精准提取纯印花图案(线条/色号/比例),叠加到图3底色上形成合成面料。\n"
|
||||
"3.印花尺寸适配:印花整体尺寸与衣服面料面积成合理比例,居中印在胸/背/衣身主体区域,"
|
||||
"占衣身面积约30%-45%,四周留白,严禁过大撑满整件或过小(低于20%)。\n"
|
||||
"4.主体遮罩:识别图1中衣服本体的完整区域(领口到下摆、含袖子;忽略背景/桌面/无关物品),"
|
||||
"用合成面料完整覆盖,彻底清除原衣服的颜色与图案;图1中衣服以外的物品保持原样。\n"
|
||||
"5.精准贴合:合成面料严格跟随图1衣服的平铺形态,领口/袖口/下摆/侧缝等版型结构清晰保留,"
|
||||
"褶皱/翻折/堆叠处印花随之自然变形,杜绝“贴纸感”与“平面涂色感”。\n"
|
||||
"6.光影融合:按图1拍摄光线方向调整亮度/对比度,印花随褶皱产生明暗变化但色号不偏移;"
|
||||
"衣服投影与图1保持一致。\n"
|
||||
"7.纯净输出:仅输出一张最终合成平铺图;图1的背景/桌面/构图/角度/光影100%不变,"
|
||||
"仅替换衣服的底色与印花;画面中不得出现任何人像、肢体或人台。"
|
||||
)
|
||||
|
||||
|
||||
def _active_prompt(kind: str, prompts: Optional[Dict] = None) -> str:
|
||||
"""返回当前图源应使用的合成提示词。
|
||||
|
||||
kind="model" → 模特三图提示词(配置覆盖优先生效,否则内置 MODEL_WEAR_PROMPT);
|
||||
kind="flat" → 平铺三图提示词(配置覆盖优先生效,否则内置 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)
|
||||
|
||||
|
||||
def _resolve_sku(db_path, basemap_root, spu_code: str, sku_code: str, colors=None) -> Optional[str]:
|
||||
"""选定 SKU:显式指定优先;否则第一个有本地底图的;再无则第一个颜色(便于模板导出)。"""
|
||||
@@ -144,13 +187,15 @@ 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,
|
||||
on_503=None, model_kind="model", prompts=None,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""处理单个款号:选色 → 底图 → 设计稿 → (mark==1) 模特 → 合成 → 模板导出。
|
||||
shared_design: compose 节点生成的纯印花设计稿路径(图2);为 None 时回退本节点 generate。
|
||||
img_code: 货号(前缀+3位计数);本产品所有图片文件归入 prod_dir/{img_code}/ 子文件夹
|
||||
(按货号命名,包含该货号对应的所有图片)。
|
||||
on_503: 致命图像服务错误(503/账户不可用)回调(供调用方提前终止任务)。
|
||||
model_kind: 图源类型 "model"(模特图)/ "flat"(平铺图),决定用哪个合成提示词。
|
||||
prompts: {"model": str, "flat": str} 可配置提示词覆盖(来自 config.product.mark_dirs);缺省用内置。
|
||||
返回 result dict;内部异常已兜底,不中断。
|
||||
"""
|
||||
# 输出目录 = 货号子文件夹(product/DG000/…),该货号所有图片都放这里
|
||||
@@ -253,40 +298,43 @@ def _process_spu(
|
||||
# 6) 模板选择按 SPU.mark 决定:
|
||||
# mark==1 → 新三图合成模板 MODEL_WEAR_PROMPT(图1模特 + 图2印花设计 + 图3底图)
|
||||
# mark!=1 → 旧两图合成模板 composite_prompt(底图 + 印花设计)
|
||||
# mark==1 统一只做三合一:无模特图时跳过合成,不再回退两图合成(印花+底图)
|
||||
# mark==1 统一只做三合一:无图(模特/平铺)时跳过合成,不再回退两图合成(印花+底图)
|
||||
if int(spu.get("mark") or 0) == 1:
|
||||
print(f"{tag} SPU {spu['code']} mark=1 → 使用三图合成模板(图1模特+图2印花+图3底图)")
|
||||
kind_label = "平铺" if model_kind == "flat" else "模特"
|
||||
print(f"{tag} SPU {spu['code']} mark=1,图源={kind_label} → 使用三图合成模板(图1{kind_label}+图2印花+图3底图)")
|
||||
if model_img is not None:
|
||||
# 任务级模特分配(product_node 预分配:一个 SPU 一个模特,SPU 数>模特数循环兜底)
|
||||
model_copy = prod_dir / f"{img_code}_model{model_img.suffix}"
|
||||
# 任务级图源分配(pipeline 预分配:mark=1 在有图的模特/平铺文件夹间随机抽)
|
||||
model_copy = prod_dir / f"{img_code}_{model_kind}{model_img.suffix}"
|
||||
shutil.copy2(model_img, model_copy)
|
||||
result["model_path"] = str(model_copy)
|
||||
result["model_folder"] = model_img.parent.name
|
||||
print(f"{tag} 模特图(任务级分配,{model_img.parent.name}/): {model_copy}")
|
||||
result["model_kind"] = model_kind
|
||||
print(f"{tag} {kind_label}图(任务级分配,{model_img.parent.name}/): {model_copy}")
|
||||
else:
|
||||
print(f"{tag} material_library 无模特图,mark=1 统一只做三合一,跳过合成")
|
||||
print(f"{tag} material_library 无{kind_label}图,mark=1 统一只做三合一,跳过合成")
|
||||
else:
|
||||
print(f"{tag} SPU {spu['code']} mark={spu.get('mark')} → 使用两图合成模板 composite_prompt(底图+印花)")
|
||||
|
||||
# 7) 合成:
|
||||
# 有模特图 → 三图合成(图1=模特 / 图2=印花设计 / 图3=底图)
|
||||
# mark=1 无模特图 → 跳过合成(统一只做三合一,不做印花+底图两图合成)
|
||||
# mark!=1 无模特图 → 两图合成平铺服装图(图3=底图 + 图2=印花设计)
|
||||
# mark=1 有图(模特/平铺)→ 三图合成(图1=模特或平铺 / 图2=印花设计 / 图3=底图)
|
||||
# mark=1 无图 → 跳过合成(统一只做三合一,不做印花+底图两图合成)
|
||||
# mark!=1 无图 → 两图合成平铺服装图(图3=底图 + 图2=印花设计)
|
||||
if "design_path" not in result:
|
||||
print(f"{tag} 无设计稿,跳过合成")
|
||||
elif model_img is not None:
|
||||
composite_path = str(prod_dir / f"{img_code}_composite.png")
|
||||
try:
|
||||
# 三图合成:优先用简报的 composite_prompt(模板化三图文案),回退内置 MODEL_WEAR_PROMPT
|
||||
wear_prompt = (brief.get("composite_prompt") or "").strip() or MODEL_WEAR_PROMPT
|
||||
print(f"{tag} 三图合成提交中(3 参考图 img2img,网关处理约 2-6 分钟,请耐心等待)…")
|
||||
# 三图合成:按图源类型选提示词(可配置覆盖优先,否则内置 MODEL_WEAR/FLAT_LAY)
|
||||
wear_prompt = _active_prompt(model_kind, prompts)
|
||||
kind_label = "平铺" if model_kind == "flat" else "模特"
|
||||
print(f"{tag} {kind_label}三图合成提交中(3 参考图 img2img,网关处理约 2-6 分钟,请耐心等待)…")
|
||||
t0 = time.time()
|
||||
ib.print(wear_prompt, str(model_img), composite_path,
|
||||
brief.get("composite_negative", ""),
|
||||
extra_images=[design_path, str(basemap_img)], # 图2印花, 图3底图
|
||||
size=compose_size) # 合成图尺寸按 config compose.size
|
||||
result["composite_path"] = composite_path
|
||||
print(f"{tag} 三图模特合成图已生成(耗时 {int(time.time()-t0)}s): {composite_path}")
|
||||
print(f"{tag} {kind_label}三图合成图已生成(耗时 {int(time.time()-t0)}s): {composite_path}")
|
||||
except Exception as e: # noqa: BLE001
|
||||
# 合成失败 → 带退避重试(网关超载/超时常见,重试 3 次)
|
||||
if _fatal(e):
|
||||
@@ -359,7 +407,7 @@ def _process_spu(
|
||||
continue
|
||||
cp = str(prod_dir / f"{img_code}_{str(sc).split('-')[-1]}_composite.png")
|
||||
try:
|
||||
ib.print(MODEL_WEAR_PROMPT, str(model_img), cp,
|
||||
ib.print(_active_prompt(model_kind, prompts), str(model_img), cp,
|
||||
brief.get("composite_negative", ""),
|
||||
extra_images=[design_path, str(bm)], # 图2印花, 图3该色底图
|
||||
size=compose_size) # 合成图尺寸按 config compose.size
|
||||
@@ -493,25 +541,28 @@ def product_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
prod_dir = output_dir / "product"
|
||||
prod_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 4.1) 任务级模特分配(按 spu.mark 映射模特目录 → 过滤非 3:4 图片 → 每个任务随机抽取):
|
||||
# 每个产品任务(含同一 SPU 的多个款)都独立随机抽一个模特,保证同款多产品模特不重复
|
||||
# 4.1) 任务级图源分配(与 Pinterest 模式一致):按 config.product.mark_dirs 可配置映射,
|
||||
# 在「模特图/平铺图」两个有图的文件夹间随机抽图,每张携带 kind(model/flat)
|
||||
# → 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_model_map, find_model_images_for_mark
|
||||
mark_map = build_mark_model_map(db_path, material_root)
|
||||
from graph.product import build_mark_sources
|
||||
sources = build_mark_sources(material_root, mark_dirs, category)
|
||||
except Exception: # noqa: BLE001
|
||||
mark_map = {}
|
||||
for _i, (_spu, _skus, _tb) in enumerate(worklist):
|
||||
mark = str(_spu.get("mark") or "").strip() or "1"
|
||||
folder = mark_map.get(mark, category)
|
||||
try:
|
||||
pool_imgs = find_model_images_for_mark(db_path, material_root, mark, folder)
|
||||
except Exception: # noqa: BLE001
|
||||
pool_imgs = []
|
||||
if pool_imgs:
|
||||
model_assign[_i] = random.choice(pool_imgs) # 过滤后随机抽(按任务序号)
|
||||
if model_assign:
|
||||
print(f"[product] 任务级模特分配:{len(model_assign)} 个产品任务(按 mark 过滤 3:4 后随机抽取)")
|
||||
sources = {"model": [], "flat": []}
|
||||
source_pool = []
|
||||
for kind in ("model", "flat"):
|
||||
for p in sources.get(kind) or []:
|
||||
source_pool.append((p, kind))
|
||||
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}
|
||||
print(f"[product] 任务级图源分配:{len(model_assign)} 个产品任务(mark_dirs 模特/平铺图随机抽,含 kind)")
|
||||
else:
|
||||
print("[product] material_library 无任何可用图(模特/平铺均无)→ 跳过合成,仅导出模板")
|
||||
|
||||
# 5) compose 节点生成的共享设计稿(图2):每个任务用自己的热点简报设计(tb.design_path),
|
||||
# 一个货号对应一个设计(多颜色共用该设计),不再所有产品共用第一个
|
||||
@@ -571,10 +622,13 @@ def product_node(state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
design_path = design_src
|
||||
tb = dict(tb)
|
||||
tb["design_path"] = design_path
|
||||
_src = model_assign.get(wi) or {}
|
||||
r = _process_spu(db_path, basemap_root, material_root, category, prod_dir,
|
||||
tb, ib, spu, skus, pcfg, _safe_errors, design_path, title_backend,
|
||||
country, img_code=img_code,
|
||||
model_img=model_assign.get(wi), # 按任务序号取独立随机模特
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user