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:
@@ -152,19 +152,22 @@ class MockBackend:
|
||||
seeds = [str(s).strip() for s in (context.get("seeds") or []) if str(s).strip()]
|
||||
used = {str(u).strip().lower() for u in (context.get("used_terms") or [])}
|
||||
count = int(context.get("count", 10))
|
||||
suffix = str(context.get("search_term_suffix") or "").strip()
|
||||
pool = [s for s in seeds if s.lower() not in used]
|
||||
random.shuffle(pool)
|
||||
terms = pool[:count]
|
||||
# 不足时用「种子词 + 风格词」组合补足(视觉导向,避免与已用重复)
|
||||
style_tail = ["t-shirt design", "graphic tee", "print art", "vintage tee", "flat design"]
|
||||
style_tail = ["graphic tee", "print art", "vintage tee", "flat design"]
|
||||
i = 0
|
||||
while len(terms) < count and pool:
|
||||
combo = f"{pool[i % len(pool)]} {style_tail[(i // len(pool)) % len(style_tail)]}"
|
||||
if combo.lower() not in used and combo not in terms:
|
||||
terms.append(combo)
|
||||
i += 1
|
||||
# 自动追加 " t-shirt design":让 Pinterest 返回真正的 T 恤印花图(更适合作印花设计参考)
|
||||
terms = [f"{t} t-shirt design" if "t-shirt design" not in t.lower() else t for t in terms]
|
||||
# 自动追加自定义后缀(config.pinterest.search_term_suffix,默认 t-shirt design):
|
||||
# 让 Pinterest 返回真正的 T 恤印花图(更适合作印花设计参考)
|
||||
if suffix:
|
||||
terms = [f"{t} {suffix}" if suffix not in t.lower() else t for t in terms]
|
||||
return {"search_terms": terms}
|
||||
|
||||
def analyze_pinterest_images(self, image_paths, term="", country="", on_400=None):
|
||||
|
||||
@@ -17,7 +17,7 @@ import requests
|
||||
# 模型调用一律直连:用户常开 VPN(系统代理),LLM 网关多为国内/自建,走代理会被拦截或变慢。
|
||||
# 仅请求级 proxies=NO_PROXY 直连,不设置进程级 NO_PROXY 环境变量(避免影响 Google Trends 等外部采集)。
|
||||
from .base import LLMBackend
|
||||
from graph.paths import runtime_root
|
||||
from graph.paths import project_root, runtime_root
|
||||
|
||||
# 直连策略:忽略环境代理(用户挂 VPN 时代理会拦截国内/自建网关的请求)
|
||||
NO_PROXY = {"http": None, "https": None}
|
||||
@@ -260,7 +260,7 @@ that are DIRECTLY usable as reference for a t-shirt print design.
|
||||
|
||||
RULES:
|
||||
- Generate EXACTLY the requested number of search terms (usually 1 per call).
|
||||
- Every term MUST be a "t-shirt design" style query: think of it as if the user typed "<concept> t-shirt design" on
|
||||
- Every term MUST be a "__SUFFIX__" style query: think of it as if the user typed "<concept>__SUFFIX__" on
|
||||
Pinterest, so the scraped images are actual t-shirt graphics / flat print artworks, NOT lifestyle photos, scenery,
|
||||
architecture, food plates, or anything that cannot become a clean chest print.
|
||||
- Terms MUST be suitable for a SHORT-SLEEVE T-SHIRT PRINT: a flat, graphic, print-ready concept (illustration, mascot,
|
||||
@@ -309,7 +309,7 @@ def build_pinterest_term_user_prompt(context: Dict[str, Any]) -> str:
|
||||
f"Generate {count} new, diverse, non-overlapping Pinterest search term(s) "
|
||||
f"that are suitable for a SHORT-SLEEVE T-SHIRT PRINT design "
|
||||
f"(flat, graphic, print-ready motif that works as a chest print). "
|
||||
f"Each term should read like \"<concept> t-shirt design\" so Pinterest returns "
|
||||
f"Each term should read like \"<concept>__SUFFIX__\" so Pinterest returns "
|
||||
f"actual t-shirt graphics / flat print artwork as reference.",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
@@ -394,7 +394,35 @@ PINTEREST_ANALYZE_SCHEMA = {
|
||||
}
|
||||
|
||||
|
||||
def build_pinterest_analyze_user_prompt() -> str:
|
||||
def _pinterest_analyze_prompt_file(country: str, filename: str) -> str:
|
||||
"""定位多模态分析提示词文件:优先运行根 prompts(exe 旁,可编辑),回退数据根 prompts。
|
||||
|
||||
支持国家覆盖(prompts/<country>/<filename>)优先于全局(prompts/<filename>)。
|
||||
找不到则返回空串,由调用方回退内置默认。
|
||||
"""
|
||||
for base in (runtime_root(), project_root()):
|
||||
if country:
|
||||
p = base / "prompts" / country / filename
|
||||
if p.exists():
|
||||
return p.read_text(encoding="utf-8").strip()
|
||||
p = base / "prompts" / filename
|
||||
if p.exists():
|
||||
return p.read_text(encoding="utf-8").strip()
|
||||
return ""
|
||||
|
||||
|
||||
def resolve_pinterest_analyze_system_prompt(country: str = "") -> str:
|
||||
"""多模态分析系统提示词(可配置):命中 prompts/pinterest_analyze_system.md(国家覆盖优先),
|
||||
否则回退内置 PINTEREST_ANALYZE_SYSTEM_PROMPT。"""
|
||||
text = _pinterest_analyze_prompt_file(country, "pinterest_analyze_system.md")
|
||||
return text if text else PINTEREST_ANALYZE_SYSTEM_PROMPT
|
||||
|
||||
|
||||
def build_pinterest_analyze_user_prompt(country: str = "") -> str:
|
||||
"""多模态分析用户提示词(可配置):命中 prompts/pinterest_analyze_user.md 否则内置默认。"""
|
||||
text = _pinterest_analyze_prompt_file(country, "pinterest_analyze_user.md")
|
||||
if text:
|
||||
return text
|
||||
return (
|
||||
"Analyze the attached image and produce one ORIGINAL T-shirt print design brief "
|
||||
"that captures its visual vibe without copying it."
|
||||
@@ -583,15 +611,21 @@ class OpenAICompatBackend(LLMBackend):
|
||||
max_used = int((cfg or {}).get("max_used_terms_in_prompt", 100) or 100)
|
||||
if max_used > 0:
|
||||
ctx["used_terms"] = used[-max_used:]
|
||||
# 后缀占位符:读 ctx 配置(允许为空=不追加后缀);仅在确有后缀时替换占位
|
||||
suffix = str(ctx.get("search_term_suffix") or "").strip()
|
||||
sys_prompt = PINTEREST_TERM_SYSTEM_PROMPT.replace("__SUFFIX__", f" {suffix}" if suffix else "")
|
||||
user_prompt = build_pinterest_term_user_prompt(ctx).replace("__SUFFIX__", f" {suffix}" if suffix else "")
|
||||
messages = [
|
||||
{"role": "system", "content": PINTEREST_TERM_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": build_pinterest_term_user_prompt(ctx)},
|
||||
{"role": "system", "content": sys_prompt},
|
||||
{"role": "user", "content": user_prompt},
|
||||
]
|
||||
raw = _retry(lambda: call_openai_compatible_structured(cfg, messages, PINTEREST_TERM_SCHEMA, timeout=120))
|
||||
parsed = _extract_json(raw)
|
||||
terms = [str(x).strip() for x in (parsed.get("search_terms", []) or []) if str(x).strip()]
|
||||
# 自动追加 " t-shirt design":让 Pinterest 返回真正的 T 恤印花图(更适合作印花设计参考)
|
||||
terms = [f"{t} t-shirt design" if "t-shirt design" not in t.lower() else t for t in terms]
|
||||
# 自动追加自定义后缀(config.pinterest.search_term_suffix,默认 t-shirt design):
|
||||
# 让 Pinterest 返回真正的印花图(更适合作印花设计参考)
|
||||
if suffix:
|
||||
terms = [f"{t} {suffix}" if suffix not in t.lower() else t for t in terms]
|
||||
return {"search_terms": terms}
|
||||
|
||||
def analyze_pinterest_images(self, image_paths: List[str], term: str, country: str = "",
|
||||
@@ -650,13 +684,13 @@ class OpenAICompatBackend(LLMBackend):
|
||||
|
||||
def _call() -> str:
|
||||
user_content: List[Any] = [
|
||||
{"type": "text", "text": build_pinterest_analyze_user_prompt()},
|
||||
{"type": "text", "text": build_pinterest_analyze_user_prompt(country)},
|
||||
]
|
||||
user_content += [{"type": "image_url", "image_url": {"url": u}} for u in data_uris]
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "system", "content": PINTEREST_ANALYZE_SYSTEM_PROMPT},
|
||||
{"role": "system", "content": resolve_pinterest_analyze_system_prompt(country)},
|
||||
{"role": "user", "content": user_content},
|
||||
],
|
||||
"temperature": 0.5,
|
||||
|
||||
Reference in New Issue
Block a user