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
This commit is contained in:
2026-08-31 18:35:51 +08:00
parent 71a48e4ed5
commit 3dc594cf57
29 changed files with 1369 additions and 321 deletions
+203 -113
View File
@@ -31,9 +31,7 @@ import yaml
from graph.agent import run_country
COUNTRIES = ["US", "GB", "JP", "AU", "MX", "DE", "BR", "SA", "PL", "ES", "IT", "CA"]
# LLM 只适配 OpenAI 兼容协议:openai(真调用,可在下方配置自定义模型/URL/Key)/ mock(无 key 演示占位)
PROVIDERS = ["openai", "mock"]
COUNTRIES = ["US", "GB", "JP", "AU", "MX", "DE", "BR", "SA", "PL", "ES", "IT", "CA", "KR"]
def resource_root() -> Path:
@@ -389,6 +387,10 @@ def run_pinterest_pipeline(countries, provider, log_q, spu_tasks=None, spu_count
custom_image_dir 非空 → 自定义图片模式:不搜索不采集,把该文件夹有效图片直接送多模态分析,
沿用 Pinterest 后续所有步骤;数量硬校验(有效图片数 ≥ 选品清单总数)由 run_pinterest_ref 执行。
"""
if not spu_tasks:
log_q.put(("log", "[UI] Pinterest/自定义模式必须通过「+添加选品」添加任务清单后才能运行\n"))
log_q.put(("error", None))
return
config = load_config()
config["seed_provider"] = provider
apply_openai_cfg(config, oai)
@@ -498,8 +500,10 @@ def _pack_cache(country: str, log_q=None) -> str:
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("POD 热点抓取 Agent")
self.geometry("920x660")
self.title("POD 智能设计 Agent")
self.geometry("1280x840")
self.minsize(1100, 720)
self.configure(bg="#F2F4F8")
self._q = queue.Queue()
self._busy = False
self._results = {}
@@ -513,151 +517,249 @@ class App(tk.Tk):
except Exception as e: # noqa: BLE001
LOG_FILE = None
print(f"[UI] 日志文件创建失败(不影响运行): {e}")
self._setup_style()
self._build_ui()
self._load_cache()
self.after(100, self._poll)
def _default_provider(self) -> str:
"""默认模式:config.yaml llm_screen.provider(用户已配 key → openai 默认)。"""
# ---------- 主题样式 ----------
def _setup_style(self):
style = ttk.Style(self)
try:
return "openai" if (load_config().get("llm_screen") or {}).get("provider") == "openai" else "mock"
except Exception:
return "mock"
style.theme_use("clam")
except Exception: # noqa: BLE001
pass
# 主色板
self._C_PRIMARY = "#2563EB" # 主蓝
self._C_PRIMARY_DK = "#1D4ED8" # 主蓝深
self._C_ACCENT = "#F59E0B" # 强调橙
self._C_BG = "#F2F4F8" # 窗口背景
self._C_CARD = "#FFFFFF" # 卡片背景
self._C_BORDER = "#DCE1EA" # 边框
self._C_TEXT = "#1F2937" # 主文字
self._C_SUB = "#6B7280" # 次要文字
self._C_OK = "#16A34A" # 成功绿
self._C_WARN = "#B45309" # 警示
self._FONT = ("Microsoft YaHei UI", 10)
self._FONT_SM = ("Microsoft YaHei UI", 9)
self._FONT_TITLE = ("Microsoft YaHei UI", 15, "bold")
self._FONT_SUB = ("Microsoft YaHei UI", 9)
style.configure(".", font=self._FONT, background=self._C_BG,
foreground=self._C_TEXT)
style.configure("Card.TLabelframe", background=self._C_CARD,
bordercolor=self._C_BORDER, relief="solid", borderwidth=1)
style.configure("Card.TLabelframe.Label", background=self._C_CARD,
foreground=self._C_PRIMARY_DK, font=("Microsoft YaHei UI", 10, "bold"))
style.configure("Card.TFrame", background=self._C_CARD)
style.configure("TLabel", background=self._C_BG, foreground=self._C_TEXT)
style.configure("Card.TLabel", background=self._C_CARD, foreground=self._C_TEXT)
style.configure("Card.TRadiobutton", background=self._C_CARD, foreground=self._C_TEXT)
style.map("Card.TRadiobutton",
background=[("active", self._C_CARD)],
foreground=[("active", self._C_PRIMARY_DK)])
style.configure("Sub.TLabel", background=self._C_BG, foreground=self._C_SUB,
font=self._FONT_SM)
style.configure("CardSub.TLabel", background=self._C_CARD, foreground=self._C_SUB,
font=self._FONT_SM)
style.configure("Accent.TLabel", background=self._C_BG, foreground=self._C_PRIMARY_DK,
font=self._FONT_SM)
style.configure("TButton", font=self._FONT, padding=(12, 6))
style.configure("Primary.TButton", font=self._FONT, padding=(18, 8),
background=self._C_PRIMARY, foreground="#FFFFFF", borderwidth=0)
style.map("Primary.TButton",
background=[("active", self._C_PRIMARY_DK), ("disabled", "#A5B4FC")],
foreground=[("disabled", "#EEF2FF")])
style.configure("AccentBtn.TButton", font=self._FONT, padding=(14, 7),
background=self._C_ACCENT, foreground="#FFFFFF", borderwidth=0)
style.map("AccentBtn.TButton",
background=[("active", "#D97706"), ("disabled", "#FCD34D")],
foreground=[("disabled", "#FFFBEB")])
style.configure("TCombobox", padding=4)
style.configure("TEntry", padding=4)
style.configure("Treeview", font=self._FONT_SM, rowheight=26,
background="#FFFFFF", fieldbackground="#FFFFFF",
bordercolor=self._C_BORDER)
style.configure("Treeview.Heading", font=("Microsoft YaHei UI", 9, "bold"),
background="#EEF2F7", foreground=self._C_TEXT, padding=6)
style.map("Treeview", background=[("selected", "#DBEAFE")],
foreground=[("selected", "#1E3A8A")])
style.configure("TNotebook", background=self._C_BG, borderwidth=0)
style.configure("TNotebook.Tab", font=self._FONT, padding=(14, 7))
style.map("TNotebook.Tab",
background=[("selected", self._C_CARD)],
foreground=[("selected", self._C_PRIMARY_DK)])
# ---------- UI 构建 ----------
def _build_ui(self):
top = ttk.Frame(self, padding=8)
top.pack(fill="x")
ttk.Label(top, text="国家:").pack(side="left")
# —— 顶部标题栏 ——
header = tk.Frame(self, bg=self._C_PRIMARY, height=62)
header.pack(fill="x")
header.pack_propagate(False)
tk.Label(header, text="🎨 智能设计 Agent",
bg=self._C_PRIMARY, fg="#FFFFFF",
font=("Microsoft YaHei UI", 16, "bold")).pack(side="left", padx=16, pady=12)
tk.Label(header, text="一键生成 T 恤印花设计 · 多国多站点",
bg=self._C_PRIMARY, fg="#DBEAFE",
font=self._FONT_SM).pack(side="left", padx=(4, 0), pady=12)
self.hotspot_time_var = tk.StringVar(value="")
tk.Label(header, textvariable=self.hotspot_time_var,
bg=self._C_PRIMARY, fg="#DBEAFE",
font=self._FONT_SM).pack(side="right", padx=(0, 8), pady=12)
# —— 运行设置卡片:国家 / 流程 / 自定义图片 / 种子数量 ——
cfg = ttk.LabelFrame(self, text="运行设置", style="Card.TLabelframe", padding=10)
cfg.pack(fill="x", padx=12, pady=(10, 4))
ttk.Label(cfg, text="目标国家:", style="Card.TLabel").pack(side="left")
self.country_var = tk.StringVar(value="US")
self.country_combo = ttk.Combobox(top, textvariable=self.country_var,
self.country_combo = ttk.Combobox(cfg, textvariable=self.country_var,
values=COUNTRIES, state="readonly", width=6)
self.country_combo.pack(side="left", padx=4)
self.country_combo.pack(side="left", padx=(4, 14))
self.country_combo.bind("<<ComboboxSelected>>", lambda e: self._on_country_selected())
ttk.Label(top, text=" 模式:").pack(side="left", padx=(14, 0))
self.provider_var = tk.StringVar(value=self._default_provider())
ttk.Radiobutton(top, text="OpenAI(真 LLM+生图)", value="openai",
variable=self.provider_var).pack(side="left", padx=2)
ttk.Radiobutton(top, text="Mock 演示", value="mock",
variable=self.provider_var).pack(side="left", padx=2)
ttk.Label(top, text=" 流程:").pack(side="left", padx=(14, 0))
self.flow_var = tk.StringVar(value="trends")
ttk.Radiobutton(top, text="热点采集", value="trends",
ttk.Label(cfg, text="流程:", style="Card.TLabel").pack(side="left")
self.flow_var = tk.StringVar(value="pinterest")
ttk.Radiobutton(cfg, text="热点采集", value="trends", style="Card.TRadiobutton",
variable=self.flow_var).pack(side="left", padx=2)
ttk.Radiobutton(top, text="Pinterest 参考", value="pinterest",
ttk.Radiobutton(cfg, text="Pinterest 参考", value="pinterest", style="Card.TRadiobutton",
variable=self.flow_var).pack(side="left", padx=2)
ttk.Radiobutton(top, text="自定义图片", value="custom",
ttk.Radiobutton(cfg, text="自定义图片", value="custom", style="Card.TRadiobutton",
variable=self.flow_var).pack(side="left", padx=2)
ttk.Button(top, text="📁 选图片文件夹…", command=self._choose_custom_dir).pack(side="left", padx=(8, 0))
ttk.Button(cfg, text="📁 选图片文件夹…", command=self._choose_custom_dir).pack(side="left", padx=(10, 0))
self.custom_dir_var = tk.StringVar(value="")
self.custom_dir_name_var = tk.StringVar(value="")
ttk.Label(top, textvariable=self.custom_dir_name_var, anchor="w",
foreground="#185FA5").pack(side="left", padx=(6, 0))
ttk.Label(top, text=" 种子数量:").pack(side="left", padx=(14, 0))
self.seed_var = tk.StringVar(value="24")
ttk.Entry(top, textvariable=self.seed_var, width=4).pack(side="left")
# 右上角:热点状态(缓存时间 / 采集时间)
self.hotspot_time_var = tk.StringVar(value="")
ttk.Label(top, textvariable=self.hotspot_time_var, anchor="e",
foreground="#185FA5").pack(side="right")
ttk.Label(cfg, textvariable=self.custom_dir_name_var, anchor="w",
style="Accent.TLabel").pack(side="left", padx=(6, 14))
# —— 商品上传模板(必须上传后才能开始任务;template_export 从该模板解析并填充)——
tpl = ttk.LabelFrame(self, text="商品上传模板(必须上传 .xlsx 后才能运行)", padding=4)
tpl.pack(fill="x", padx=8, pady=(2, 0))
ttk.Label(cfg, text="种子数量:", style="Card.TLabel").pack(side="left")
self.seed_var = tk.StringVar(value="24")
ttk.Entry(cfg, textvariable=self.seed_var, width=5).pack(side="left", padx=(4, 0))
# —— 商品上传模板卡片 ——
tpl = ttk.LabelFrame(self, text="商品上传模板(必须上传 .xlsx 后才能运行)",
style="Card.TLabelframe", padding=8)
tpl.pack(fill="x", padx=12, pady=(4, 4))
ttk.Button(tpl, text="📁 选择模板…", command=self._choose_template).pack(side="left")
self.template_path_var = tk.StringVar(value="")
self.template_name_var = tk.StringVar(value="(未选择)")
ttk.Label(tpl, textvariable=self.template_name_var, anchor="w",
foreground="#993C1D").pack(side="left", padx=(8, 0))
style="CardSub.TLabel").pack(side="left", padx=(10, 0))
# —— 选品面板:国家→款号→颜色→添加任务(支持多款号 + 数量上限)——
sel = ttk.LabelFrame(self, text="选品(选定国家自动加载款号;款号 → 颜色 → 添加任务", padding=6)
sel.pack(fill="x", padx=8, pady=(4, 0))
# —— 选品卡片:款号 → 颜色 → 添加任务 ——
sel = ttk.LabelFrame(self, text="选品(选定国家自动加载款号;款号 → 颜色 → 添加选品",
style="Card.TLabelframe", padding=10)
sel.pack(fill="x", padx=12, pady=(4, 4))
# 左:款号列表
lf1 = ttk.Frame(sel)
lf1 = ttk.Frame(sel, style="Card.TFrame")
lf1.pack(side="left", padx=4)
ttk.Label(lf1, text="款号(选定后自动加载颜色与最低价):").pack(anchor="w")
self.spu_list = tk.Listbox(lf1, selectmode="extended", height=5, width=14, exportselection=False)
self.spu_list.pack(side="left")
ttk.Label(lf1, text="款号(多选)", style="Card.TLabel").pack(anchor="w")
self.spu_list = tk.Listbox(lf1, selectmode="extended", height=6, width=16,
exportselection=False, bg="#FFFFFF", fg=self._C_TEXT,
selectbackground="#DBEAFE", selectforeground="#1E3A8A",
highlightthickness=1, highlightbackground=self._C_BORDER,
highlightcolor=self._C_PRIMARY, font=self._FONT_SM,
relief="solid", borderwidth=1)
self.spu_list.pack(side="left", pady=(4, 0))
self.spu_list.bind("<<ListboxSelect>>", self._on_spu_selected)
self.min_price_var = tk.StringVar(value="")
ttk.Label(lf1, textvariable=self.min_price_var, anchor="w", foreground="#993C1D").pack(fill="x", pady=(4, 0))
ttk.Label(lf1, textvariable=self.min_price_var, anchor="w",
style="CardSub.TLabel").pack(fill="x", pady=(4, 0))
# 中:颜色多选(点击即多选)+ 添加按钮
lf2 = ttk.Frame(sel)
lf2.pack(side="left", padx=(10, 4))
ttk.Label(lf2, text="当前款号颜色(点击多选,显示色名/最低价):").pack(anchor="w")
self.sku_list = tk.Listbox(lf2, selectmode="multiple", height=5, width=34, exportselection=False)
self.sku_list.pack(side="left")
b2 = ttk.Frame(lf2)
b2.pack(side="left", padx=4)
# 中:颜色多选 + 添加按钮
lf2 = ttk.Frame(sel, style="Card.TFrame")
lf2.pack(side="left", padx=(12, 4))
ttk.Label(lf2, text="当前款号颜色(点击多选", style="Card.TLabel").pack(anchor="w")
self.sku_list = tk.Listbox(lf2, selectmode="multiple", height=6, width=38,
exportselection=False, bg="#FFFFFF", fg=self._C_TEXT,
selectbackground="#DBEAFE", selectforeground="#1E3A8A",
highlightthickness=1, highlightbackground=self._C_BORDER,
highlightcolor=self._C_PRIMARY, font=self._FONT_SM,
relief="solid", borderwidth=1)
self.sku_list.pack(side="left", pady=(4, 0))
b2 = ttk.Frame(lf2, style="Card.TFrame")
b2.pack(side="left", padx=6)
ttk.Button(b2, text="全选", command=self._select_all_colors).pack(fill="x")
ttk.Button(b2, text="清空", command=lambda: self.sku_list.selection_clear(0, "end")).pack(fill="x", pady=2)
ttk.Button(b2, text="+添加选品", command=self._add_task).pack(fill="x", pady=(4, 0))
ttk.Label(b2, text="该款SPU/设计数(1-N):", anchor="w").pack(fill="x", pady=(6, 0))
ttk.Button(b2, text="+添加选品", style="AccentBtn.TButton",
command=self._add_task).pack(fill="x", pady=(4, 0))
ttk.Label(b2, text="该款设计数:", style="CardSub.TLabel").pack(fill="x", pady=(6, 0))
self.count_var = tk.StringVar(value="10")
ttk.Entry(b2, textvariable=self.count_var, width=6).pack(fill="x")
# 右:任务清单
lf3 = ttk.Frame(sel)
lf3 = ttk.Frame(sel, style="Card.TFrame")
lf3.pack(side="left", padx=4)
ttk.Label(lf3, text="选品任务清单:").pack(anchor="w")
ttk.Label(lf3, text="选品任务清单", style="Card.TLabel").pack(anchor="w")
self.task_stat_var = tk.StringVar(value="0 个任务")
ttk.Label(lf3, textvariable=self.task_stat_var, anchor="w",
foreground="#993C1D").pack(anchor="w")
self.task_list = tk.Listbox(lf3, height=5, width=40, exportselection=False)
self.task_list.pack(side="left")
b3 = ttk.Frame(lf3)
b3.pack(side="left", padx=4)
style="Accent.TLabel").pack(anchor="w")
self.task_list = tk.Listbox(lf3, height=6, width=44, exportselection=False,
bg="#FFFFFF", fg=self._C_TEXT,
selectbackground="#DBEAFE", selectforeground="#1E3A8A",
highlightthickness=1, highlightbackground=self._C_BORDER,
highlightcolor=self._C_PRIMARY, font=self._FONT_SM,
relief="solid", borderwidth=1)
self.task_list.pack(side="left", pady=(4, 0))
b3 = ttk.Frame(lf3, style="Card.TFrame")
b3.pack(side="left", padx=6)
ttk.Button(b3, text="删选中", command=self._del_task).pack(fill="x")
ttk.Button(b3, text="清空", command=self._clear_tasks).pack(fill="x", pady=2)
# 加价百分比(供后续定价使用)+ 货号前缀
lf4 = ttk.Frame(sel)
lf4.pack(side="left", padx=(12, 0))
ttk.Label(lf4, text="加价%:").pack(anchor="w")
# 右侧:加价 / 货号前缀
lf4 = ttk.Frame(sel, style="Card.TFrame")
lf4.pack(side="left", padx=(16, 0))
ttk.Label(lf4, text="加价 %", style="Card.TLabel").pack(anchor="w")
self.markup_var = tk.StringVar(value="0")
ttk.Entry(lf4, textvariable=self.markup_var, width=7).pack(anchor="w", pady=(2, 0))
ttk.Label(lf4, text="按 SKU 最低价加价", anchor="w", foreground="#5F5E5A").pack(anchor="w", pady=(4, 0))
ttk.Entry(lf4, textvariable=self.markup_var, width=7).pack(anchor="w", pady=(4, 0))
ttk.Label(lf4, text="按 SKU 最低价加价", style="CardSub.TLabel").pack(anchor="w", pady=(4, 0))
lf5 = ttk.Frame(sel)
lf5.pack(side="left", padx=(12, 0))
ttk.Label(lf5, text="货号前缀:").pack(anchor="w")
lf5 = ttk.Frame(sel, style="Card.TFrame")
lf5.pack(side="left", padx=(16, 0))
ttk.Label(lf5, text="货号前缀", style="Card.TLabel").pack(anchor="w")
self.code_prefix_var = tk.StringVar(value="DG")
ttk.Entry(lf5, textvariable=self.code_prefix_var, width=8).pack(anchor="w", pady=(2, 0))
ttk.Label(lf5, text="前缀+3位计数(000起)\nOSS: 国家/时间戳/货号_4位随机", anchor="w",
foreground="#5F5E5A").pack(anchor="w", pady=(4, 0))
ttk.Entry(lf5, textvariable=self.code_prefix_var, width=8).pack(anchor="w", pady=(4, 0))
ttk.Label(lf5, text="前缀+3位计数(000起)", style="CardSub.TLabel").pack(anchor="w", pady=(4, 0))
self._sku_map = {}
self._tasks: list = []
self._reload_spus()
bar = ttk.Frame(self, padding=(8, 0))
# —— 操作栏 ——
bar = ttk.Frame(self, padding=(12, 6))
bar.pack(fill="x")
self.run_btn = ttk.Button(bar, text="▶ 运行", style="Primary.TButton", command=self._on_run)
self.run_btn.pack(side="left")
self.fetch_btn = ttk.Button(bar, text="📥 采集热点", command=self._on_fetch)
self.fetch_btn.pack(side="left")
self.run_btn = ttk.Button(bar, text="▶ 运行", command=self._on_run)
self.run_btn.pack(side="left", padx=6)
ttk.Button(bar, text="打开产物目录", command=self._open_output).pack(side="left", padx=8)
ttk.Button(bar, text="打开日志目录", command=self._open_logs).pack(side="left")
ttk.Button(bar, text="清空日志", command=self._clear_log).pack(side="left")
self.fetch_btn.pack(side="left", padx=(10, 0))
ttk.Button(bar, text="打开产物目录", command=self._open_output).pack(side="left", padx=(10, 0))
ttk.Button(bar, text="打开日志目录", command=self._open_logs).pack(side="left", padx=(10, 0))
ttk.Button(bar, text="清空日志", command=self._clear_log).pack(side="left", padx=(10, 0))
ttk.Label(self, text="运行日志:").pack(anchor="w", padx=8)
self.log_text = tk.Text(self, height=8, state="disabled")
self.log_text.pack(fill="x", padx=8)
# —— 运行日志(深色终端风格)——
log_card = ttk.LabelFrame(self, text="运行日志", style="Card.TLabelframe", padding=6)
log_card.pack(fill="x", padx=12, pady=(4, 4))
self.log_text = tk.Text(log_card, height=7, state="disabled", wrap="word",
bg="#0F172A", fg="#E2E8F0", font=("Consolas", 9),
relief="flat", padx=8, pady=6, insertbackground="#E2E8F0")
self.log_text.pack(fill="both", expand=True)
ttk.Label(self, text="设计简报(双击查看完整提示词):").pack(anchor="w", padx=8)
# —— 设计简报表格 ——
tree_card = ttk.LabelFrame(self, text="设计简报(双击查看完整提示词)",
style="Card.TLabelframe", padding=6)
tree_card.pack(fill="both", expand=True, padx=12, pady=(4, 10))
cols = ("country", "topic", "category", "risk", "score")
self.tree = ttk.Treeview(self, columns=cols, show="headings", height=12)
self.tree = ttk.Treeview(tree_card, columns=cols, show="headings", height=12)
for col, title, w, anchor in zip(
cols, ["国家", "热点词", "类别", "风险", "分数"],
[52, 340, 100, 70, 64],
[70, 420, 180, 90, 80],
["center", "w", "w", "center", "center"],
):
self.tree.heading(col, text=title)
self.tree.column(col, width=w, anchor=anchor)
self.tree.pack(fill="both", expand=True, padx=8, pady=(2, 8))
self.tree.pack(fill="both", expand=True)
self.tree.bind("<Double-1>", self._show_detail)
def _log(self, s: str):
@@ -707,12 +809,6 @@ class App(tk.Tk):
if colors:
self.sku_list.selection_set(0) # 默认选中第一个颜色
def _current_spu(self):
sel = self.spu_list.curselection()
if sel and sel[0] < len(self._spu_list):
return self._spu_list[sel[0]]["code"]
return ""
def _add_task(self):
"""把当前选中的【款号】加入任务清单(可多选、可重复新增)。
每个款号一条「集合」:skus=选中颜色集合(多色用逗号分隔),
@@ -863,7 +959,7 @@ class App(tk.Tk):
self.run_btn.config(state="disabled")
threading.Thread(
target=fetch_keywords,
args=(self.country_var.get(), self.provider_var.get(), ms, self._q,
args=(self.country_var.get(), "openai", ms, self._q,
self._oai_cfg()),
daemon=True,
).start()
@@ -1023,18 +1119,12 @@ class App(tk.Tk):
messagebox.showerror("参数错误", "加价%不能为负数")
return
# 任务清单优先;空则用当前选中款号+颜色(保留旧行为)
# 任务清单必须通过「+添加选品」显式添加后才允许运行;
# 不允许用当前选中款号+颜色兜底(否则未添加选品也能启动,会按数量扩展生成大量简报)。
tasks = list(self._tasks)
if not tasks:
spu = self._current_spu()
if not spu:
messagebox.showwarning("提示", "请先添加选品(款号 → 颜色 → +添加选品)")
return
sel_colors = [self.sku_list.get(i).split(" ")[0] for i in self.sku_list.curselection()]
if not sel_colors:
messagebox.showwarning("提示", "请至少选择一个颜色")
return
tasks = [{"spu": spu, "skus": ",".join(sel_colors)}]
messagebox.showwarning("提示", "请先添加选品(款号 → 颜色 → +添加选品)")
return
# ① 兜底校验:任务开始前检查所有款号颜色的 basemap 底图是否存在;
# 存在缺失 → 直接报错提醒,不开始任何任务
@@ -1063,7 +1153,7 @@ class App(tk.Tk):
# —— 自定义图片模式硬校验:必须先选文件夹;有效图片数 ≥ 选品清单总数 ——
if not custom_dir:
self._busy = False
self.run_btn.config(state="normal", text="运行")
self.run_btn.config(state="normal", text="运行")
self.fetch_btn.config(state="normal")
messagebox.showwarning("自定义图片模式", "请先点击「📁 选图片文件夹…」选择上传的图片文件夹")
return
@@ -1076,7 +1166,7 @@ class App(tk.Tk):
ok, valid_n, msg = False, 0, f"校验失败: {e}"
if not ok:
self._busy = False
self.run_btn.config(state="normal", text="运行")
self.run_btn.config(state="normal", text="运行")
self.fetch_btn.config(state="normal")
messagebox.showerror("自定义图片数量校验未通过", msg)
self._log("[UI] 自定义图片数量校验未通过,任务未运行: " + msg + "\n")
@@ -1084,7 +1174,7 @@ class App(tk.Tk):
self._log(f"[UI] 自定义图片数量校验通过:有效图片 {valid_n} 张 ≥ 选品清单 {expanded}\n")
threading.Thread(
target=run_pinterest_pipeline,
args=(countries, self.provider_var.get(), self._q,
args=(countries, "openai", self._q,
tasks, spu_count, self._oai_cfg(), markup,
self.code_prefix_var.get().strip() or "DG",
self.template_path_var.get().strip(),
@@ -1094,7 +1184,7 @@ class App(tk.Tk):
else:
threading.Thread(
target=run_pipeline,
args=(countries, self.provider_var.get(), ms, self._q,
args=(countries, "openai", ms, self._q,
"", "", spu_count, tasks, self._oai_cfg(), markup,
self.code_prefix_var.get().strip() or "DG",
self.template_path_var.get().strip()),
@@ -1176,7 +1266,7 @@ class App(tk.Tk):
("国家", "country"), ("热点词", "topic"), ("类别", "design_category"),
("风险", "risk_level"), ("风险原因", "risk_reasons"), ("概念", "concept"),
("主体 motif", "motif"), ("艺术风格", "art_style"), ("配色", "color_palette"),
("构图", "composition"), ("负向", "negative_prompt"),
("构图", "composition"),
("image_prompt", "image_prompt"), ("composite_prompt", "composite_prompt"),
]:
v = b.get(k)