"""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}