feat(product-suite): add category targets and prompt compatibility
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
套图名称:{套图名称}{补充描述}
|
生成目标:{生成目标}
|
||||||
平台:{平台};国家地区:{国家地区};输出语言:{输出语言}。
|
平台:{平台};国家地区:{国家地区};输出语言:{输出语言}。
|
||||||
{尺寸与长图规则}
|
{尺寸与长图规则}
|
||||||
{禁用内容规则}
|
{禁用内容规则}
|
||||||
|
|||||||
+58
-10
@@ -6,14 +6,29 @@ from collections import OrderedDict
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
FIXED_CATEGORIES = ("白底图", "场景图", "卖点图")
|
FIXED_CATEGORIES = ("白底图", "场景图", "模特场景图", "细节说明图", "卖点图")
|
||||||
FIXED_CATEGORY_HELPERS = {
|
FIXED_CATEGORY_HELPERS = {
|
||||||
"白底图": "白底主图,多角度呈现商品细节",
|
"白底图": "白底主图,多角度呈现商品细节",
|
||||||
"场景图": "生活化场景展示商品使用方式",
|
"场景图": "生活化场景展示商品使用方式",
|
||||||
|
"模特场景图": "模特或手持场景展示商品真实使用",
|
||||||
|
"细节说明图": "突出材质、做工和关键细节",
|
||||||
"卖点图": "突出核心卖点和差异化优势",
|
"卖点图": "突出核心卖点和差异化优势",
|
||||||
}
|
}
|
||||||
|
FIXED_CATEGORY_DESCRIPTIONS = {
|
||||||
|
"白底图": "生成 Shopee 台灣商品白底主圖,純白背景,商品清晰置中,不添加多餘文字。",
|
||||||
|
"场景图": "生成生活化使用場景圖,展示商品在真實情境中的用途與氛圍,畫面自然可信。",
|
||||||
|
"模特场景图": "生成模特或手持使用情境圖,畫面自然可信,商品為主角。",
|
||||||
|
"细节说明图": "生成商品細節特寫說明圖,突出材質、做工、接口、紋理或關鍵細節。",
|
||||||
|
"卖点图": "生成賣點詳解圖,使用繁體中文短文案呈現核心優勢,版面乾淨。",
|
||||||
|
}
|
||||||
DEFAULT_CATEGORY_COUNTS = OrderedDict(
|
DEFAULT_CATEGORY_COUNTS = OrderedDict(
|
||||||
(("白底图", 1), ("场景图", 2), ("卖点图", 2))
|
(
|
||||||
|
("白底图", 1),
|
||||||
|
("场景图", 2),
|
||||||
|
("模特场景图", 0),
|
||||||
|
("细节说明图", 0),
|
||||||
|
("卖点图", 2),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
PLATFORMS = ("Shopee", "Lazada", "TikTok Shop", "Amazon")
|
PLATFORMS = ("Shopee", "Lazada", "TikTok Shop", "Amazon")
|
||||||
COUNTRIES = ("中国台湾", "新加坡", "马来西亚", "菲律宾", "泰国", "越南")
|
COUNTRIES = ("中国台湾", "新加坡", "马来西亚", "菲律宾", "泰国", "越南")
|
||||||
@@ -23,6 +38,7 @@ LAST_SETTING_KEYS = ("platform", "country", "language", "ratio")
|
|||||||
MAX_CATEGORY_NAME_LENGTH = 10
|
MAX_CATEGORY_NAME_LENGTH = 10
|
||||||
MAX_GENERATION_COUNT_WITHOUT_CONFIRM = 16
|
MAX_GENERATION_COUNT_WITHOUT_CONFIRM = 16
|
||||||
PRODUCT_SUITE_PLACEHOLDERS = (
|
PRODUCT_SUITE_PLACEHOLDERS = (
|
||||||
|
"生成目标",
|
||||||
"套图名称",
|
"套图名称",
|
||||||
"补充描述",
|
"补充描述",
|
||||||
"平台",
|
"平台",
|
||||||
@@ -38,9 +54,7 @@ PRODUCT_SUITE_PLACEHOLDERS = (
|
|||||||
"价格信息规则",
|
"价格信息规则",
|
||||||
"尺码信息规则",
|
"尺码信息规则",
|
||||||
)
|
)
|
||||||
PRODUCT_SUITE_REQUIRED_PLACEHOLDERS = (
|
PRODUCT_SUITE_COMMON_REQUIRED_PLACEHOLDERS = (
|
||||||
"套图名称",
|
|
||||||
"补充描述",
|
|
||||||
"图片比例",
|
"图片比例",
|
||||||
"参考图规则",
|
"参考图规则",
|
||||||
"商品卖点与要求",
|
"商品卖点与要求",
|
||||||
@@ -49,6 +63,15 @@ PRODUCT_SUITE_REQUIRED_PLACEHOLDERS = (
|
|||||||
"价格信息规则",
|
"价格信息规则",
|
||||||
"尺码信息规则",
|
"尺码信息规则",
|
||||||
)
|
)
|
||||||
|
PRODUCT_SUITE_REQUIRED_PLACEHOLDERS = (
|
||||||
|
"生成目标",
|
||||||
|
*PRODUCT_SUITE_COMMON_REQUIRED_PLACEHOLDERS,
|
||||||
|
)
|
||||||
|
PRODUCT_SUITE_LEGACY_REQUIRED_PLACEHOLDERS = (
|
||||||
|
"套图名称",
|
||||||
|
"补充描述",
|
||||||
|
*PRODUCT_SUITE_COMMON_REQUIRED_PLACEHOLDERS,
|
||||||
|
)
|
||||||
PRODUCT_SUITE_READ_ONLY_RULE_PLACEHOLDERS = (
|
PRODUCT_SUITE_READ_ONLY_RULE_PLACEHOLDERS = (
|
||||||
"尺寸与长图规则",
|
"尺寸与长图规则",
|
||||||
"禁用内容规则",
|
"禁用内容规则",
|
||||||
@@ -154,8 +177,13 @@ def category_helper(category):
|
|||||||
|
|
||||||
|
|
||||||
def category_description(category):
|
def category_description(category):
|
||||||
helper = category_helper(category)
|
name = str(category or "").strip()
|
||||||
return "," + helper if helper else ""
|
fixed_description = FIXED_CATEGORY_DESCRIPTIONS.get(name)
|
||||||
|
if fixed_description:
|
||||||
|
return fixed_description
|
||||||
|
if name:
|
||||||
|
return "生成自定义分类图片:%s。" % name
|
||||||
|
return "生成自定义分类图片。"
|
||||||
|
|
||||||
|
|
||||||
def suite_total_count(settings, image_count):
|
def suite_total_count(settings, image_count):
|
||||||
@@ -186,10 +214,20 @@ def product_suite_prompt_errors(template_text):
|
|||||||
errors.append("模板包含未知变量:%s" % "、".join("{%s}" % name for name in unknown))
|
errors.append("模板包含未知变量:%s" % "、".join("{%s}" % name for name in unknown))
|
||||||
|
|
||||||
missing = [
|
missing = [
|
||||||
name for name in PRODUCT_SUITE_REQUIRED_PLACEHOLDERS if name not in names
|
name
|
||||||
|
for name in PRODUCT_SUITE_COMMON_REQUIRED_PLACEHOLDERS
|
||||||
|
if name not in names
|
||||||
]
|
]
|
||||||
if missing:
|
if missing:
|
||||||
errors.append("模板缺少必需变量:%s" % "、".join("{%s}" % name for name in missing))
|
errors.append("模板缺少必需变量:%s" % "、".join("{%s}" % name for name in missing))
|
||||||
|
has_new_target = "生成目标" in names
|
||||||
|
has_legacy_target = all(
|
||||||
|
name in names for name in ("套图名称", "补充描述")
|
||||||
|
)
|
||||||
|
if not has_new_target and not has_legacy_target:
|
||||||
|
errors.append(
|
||||||
|
"模板缺少必需变量:{生成目标}(旧模板需同时包含{套图名称}和{补充描述})"
|
||||||
|
)
|
||||||
|
|
||||||
invalid_rule_lines = []
|
invalid_rule_lines = []
|
||||||
for line in text.splitlines():
|
for line in text.splitlines():
|
||||||
@@ -228,8 +266,9 @@ def product_suite_prompt_context(
|
|||||||
item_text = "未绑定商品"
|
item_text = "未绑定商品"
|
||||||
reference_index = max(1, int(source_index or 1))
|
reference_index = max(1, int(source_index or 1))
|
||||||
return {
|
return {
|
||||||
|
"生成目标": category_description(category),
|
||||||
"套图名称": str(category or ""),
|
"套图名称": str(category or ""),
|
||||||
"补充描述": category_description(category),
|
"补充描述": "," + category_description(category),
|
||||||
"平台": normalized["platform"],
|
"平台": normalized["platform"],
|
||||||
"国家地区": normalized["country"],
|
"国家地区": normalized["country"],
|
||||||
"输出语言": normalized["language"],
|
"输出语言": normalized["language"],
|
||||||
@@ -255,9 +294,18 @@ def render_product_suite_prompt(template_text, context):
|
|||||||
name: str((context or {}).get(name, ""))
|
name: str((context or {}).get(name, ""))
|
||||||
for name in PRODUCT_SUITE_PLACEHOLDERS
|
for name in PRODUCT_SUITE_PLACEHOLDERS
|
||||||
}
|
}
|
||||||
|
context = context or {}
|
||||||
missing_context = [
|
missing_context = [
|
||||||
name for name in PRODUCT_SUITE_REQUIRED_PLACEHOLDERS if name not in (context or {})
|
name
|
||||||
|
for name in PRODUCT_SUITE_COMMON_REQUIRED_PLACEHOLDERS
|
||||||
|
if name not in context
|
||||||
]
|
]
|
||||||
|
has_new_target = "生成目标" in context
|
||||||
|
has_legacy_target = all(
|
||||||
|
name in context for name in ("套图名称", "补充描述")
|
||||||
|
)
|
||||||
|
if not has_new_target and not has_legacy_target:
|
||||||
|
missing_context.append("生成目标")
|
||||||
if missing_context:
|
if missing_context:
|
||||||
raise ProductSuitePromptError(
|
raise ProductSuitePromptError(
|
||||||
"提示词上下文缺少变量:%s"
|
"提示词上下文缺少变量:%s"
|
||||||
|
|||||||
@@ -436,9 +436,9 @@ data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新
|
|||||||
- 进度:标题和图片两条进度分开显示;只生成标题时图片进度显示本轮未生成/0 张,并在运行日志写明本轮生成内容。
|
- 进度:标题和图片两条进度分开显示;只生成标题时图片进度显示本轮未生成/0 张,并在运行日志写明本轮生成内容。
|
||||||
- 任一组件生成后 `stage=generated`;**不设逐条人工审核阶段**。若只有标题,③可选择只更新标题;若只有封面,③可选择只更新封面,②标题状态仍为待生成,后续补标题会保留已有封面且不重复生图。双击任务弹窗查看旧封面、新封面和历史候选图;T-577 后弹窗内「重置图片」只清当前任务 `new_cover_path` 并归档旧图,不启动单条 `GenerateWorker`,用户退出后用状态筛选「待生成」批量补生成封面。②「重置生成结果」提供标题/封面/全部的多选或当前筛选范围重置,默认不删除本地新封面文件;已生成且未提交线上的新标题可在②表格本地微调。
|
- 任一组件生成后 `stage=generated`;**不设逐条人工审核阶段**。若只有标题,③可选择只更新标题;若只有封面,③可选择只更新封面,②标题状态仍为待生成,后续补标题会保留已有封面且不重复生图。双击任务弹窗查看旧封面、新封面和历史候选图;T-577 后弹窗内「重置图片」只清当前任务 `new_cover_path` 并归档旧图,不启动单条 `GenerateWorker`,用户退出后用状态筛选「待生成」批量补生成封面。②「重置生成结果」提供标题/封面/全部的多选或当前筛选范围重置,默认不删除本地新封面文件;已生成且未提交线上的新标题可在②表格本地微调。
|
||||||
- 并发数、重试、分辨率、jpg 质量、模型/Key 均来自设置(`data/config.json` 的 `ai` 段;Key 存 `data/config/cmhub.json` 或 direct 兼容清单)。T-547 后标题并发和图片并发都限制为 1..5,失败重试次数限制为 0..10;旧 `config.json` 或手工配置的超限值会在加载/保存时夹紧。设置仍只展示一个「图片并发」设置;cmhub 模式下②运行日志显示“图片并发 X,cmhub实际生图并发 Y,下载并发 Y”。
|
- 并发数、重试、分辨率、jpg 质量、模型/Key 均来自设置(`data/config.json` 的 `ai` 段;Key 存 `data/config/cmhub.json` 或 direct 兼容清单)。T-547 后标题并发和图片并发都限制为 1..5,失败重试次数限制为 0..10;旧 `config.json` 或手工配置的超限值会在加载/保存时夹紧。设置仍只展示一个「图片并发」设置;cmhub 模式下②运行日志显示“图片并发 X,cmhub实际生图并发 Y,下载并发 Y”。
|
||||||
- 商品套图固定使用设置保存的 cmhub 生图 alias。T-651 后,常规新一轮生成先完成历史确认,再按最终 `build_job_specs()` 在后台读取或复用短期模型目录缓存,最后才显示数量和扣点确认;逐图主图开启时白底图仅使用第一张原图,其他分类按每张原图展开。只有当前生图别名存在唯一无条件 `points_cost` 时显示单张与总预估点数,总数严格按 planned job 数量计算;目录不可用或价格有条件时不显示数字。确认默认、Esc 或关闭均取消,计划在读取期间发生变化时不提交旧 specs;单图重试和恢复不进入该常规确认。预估不落库、不预扣,实际扣点仍以 job 的 cmhub 响应为准。T-637 后套图提示词事实来源分为:安装包只读默认 `app/default_prompts/product_suite/base.txt`、用户全局模板 `data/prompts/product_suite/base.txt`、`app/product_suite.py` 中的结构化上下文与只读规则常量。用户模板首次缺失或为空时,必须先按完整占位符契约校验内置模板,再通过原子写入初始化;已有用户模板不被升级静默覆盖。模板无效时在创建 project/job/worker 和调用 cmhub 前阻断新一轮生成,单张历史重试继续使用原 `image_studio_jobs.prompt` 快照。
|
- 商品套图固定使用设置保存的 cmhub 生图 alias。T-651 后,常规新一轮生成先完成历史确认,再按最终 `build_job_specs()` 在后台读取或复用短期模型目录缓存,最后才显示数量和扣点确认;逐图主图开启时白底图仅使用第一张原图,其他分类按每张原图展开。固定分类顺序为白底图、场景图、模特场景图、细节说明图、卖点图;默认数量为1、2、0、0、2,新增分类不因升级自动产生任务。只有当前生图别名存在唯一无条件 `points_cost` 时显示单张与总预估点数,总数严格按 planned job 数量计算;目录不可用或价格有条件时不显示数字。确认默认、Esc 或关闭均取消,计划在读取期间发生变化时不提交旧 specs;单图重试和恢复不进入该常规确认。预估不落库、不预扣,实际扣点仍以 job 的 cmhub 响应为准。T-637 后套图提示词事实来源分为:安装包只读默认 `app/default_prompts/product_suite/base.txt`、用户全局模板 `data/prompts/product_suite/base.txt`、`app/product_suite.py` 中的分类目标、结构化上下文与只读规则常量。用户模板首次缺失或为空时,必须先按完整占位符契约校验内置模板,再通过原子写入初始化;已有用户模板不被升级静默覆盖。模板无效时在创建 project/job/worker 和调用 cmhub 前阻断新一轮生成,单张历史重试继续使用原 `image_studio_jobs.prompt` 快照。
|
||||||
- T-647 后商品套图中的「AI帮写」不是②标题生成的复用入口:它使用 `vision_alias` 调 `POST /api/v1/analyze/images`,把当前项目中1至8张可用本地原图按 `source_order` 在一次请求中发送;超过8张时明确只使用前8张,原图勾选状态不改变该输入集合。视觉提示词将这些图片定义为同一商品项目的多角度、细节、包装或场景证据集,要求产出一份商品级联合分析,不按“图1/图2”逐图罗列;结果按商品概述、可确认卖点、适用人群与场景、套图画面要求、待确认或避免编造的信息组织,并把可见差异明确为待确认项,不强行合并为事实。单图上限10MiB、总计上限32MiB,缺图、未下载、超限或未配置别名时不发请求且保留用户现有卖点。读取等待固定120秒、连接等待沿用设置;读超时或网络中断只提示“结果未确认”,不自动重发。成功只显示图片张数、扣点和余额,不显示路径、base64、接口URL、完整提示词或上游原始响应;取消只在请求前/返回后协作生效,不强杀网络线程。
|
- T-647 后商品套图中的「AI帮写」不是②标题生成的复用入口:它使用 `vision_alias` 调 `POST /api/v1/analyze/images`,把当前项目中1至8张可用本地原图按 `source_order` 在一次请求中发送;超过8张时明确只使用前8张,原图勾选状态不改变该输入集合。视觉提示词将这些图片定义为同一商品项目的多角度、细节、包装或场景证据集,要求产出一份商品级联合分析,不按“图1/图2”逐图罗列;结果按商品概述、可确认卖点、适用人群与场景、套图画面要求、待确认或避免编造的信息组织,并把可见差异明确为待确认项,不强行合并为事实。单图上限10MiB、总计上限32MiB,缺图、未下载、超限或未配置别名时不发请求且保留用户现有卖点。读取等待固定120秒、连接等待沿用设置;读超时或网络中断只提示“结果未确认”,不自动重发。成功只显示图片张数、扣点和余额,不显示路径、base64、接口URL、完整提示词或上游原始响应;取消只在请求前/返回后协作生效,不强杀网络线程。
|
||||||
- `product_suite.render_product_suite_prompt()` 是弹窗预览和真实生成的唯一 renderer;`build_job_specs()` 建立本轮 specs 前只读取一次用户模板并冻结,每个 job 保存最终完整 prompt,运行中修改模板只影响下一轮。允许占位符为 `{套图名称}`、`{补充描述}`、平台/地区/语言/比例、可选商品ID/主参考图序号、参考图规则、商品卖点及四个只读规则;必需变量缺失、未知/未闭合花括号、只读规则变量未独占一行都视为无效。四个只读规则覆盖尺寸与长图(含禁止多宫格拼接)、政治标识、价格和尺码;商品主体一致性与禁止编造并入参考图规则。比例仍同时传入 `image_studio_generation.run_jobs(aspect_ratio=...)`,进入 cmhub 请求与输出资产元数据。
|
- `product_suite.render_product_suite_prompt()` 是弹窗预览和真实生成的唯一 renderer;`build_job_specs()` 建立本轮 specs 前只读取一次用户模板并冻结,每个 job 保存最终完整 prompt,运行中修改模板只影响下一轮。新模板使用 `{生成目标}`,其值为内置分类固定目标描述或含实际名称的“生成自定义分类图片:分类名称。”;同时保留 `{套图名称}`、`{补充描述}` 及平台/地区/语言/比例、可选商品ID/主参考图序号、参考图规则、商品卖点和四个只读规则。旧模板必须同时包含 `{套图名称}` 与 `{补充描述}` 才能按兼容路径继续使用;必需变量缺失、未知/未闭合花括号、只读规则变量未独占一行都视为无效。四个只读规则覆盖尺寸与长图(含禁止多宫格拼接)、政治标识、价格和尺码;商品主体一致性与禁止编造并入参考图规则。比例仍同时传入 `image_studio_generation.run_jobs(aspect_ratio=...)`,进入 cmhub 请求与输出资产元数据。
|
||||||
- `image_studio_projects.suite_settings_json` 持久化套图设置,旧数据库由 `db.init_db()` 原位补列,默认 `{}`;`draft_prompt` 继续保存卖点文本。`image_studio_assets` 中有效商品原图最多16张,历史 missing 记录不占有效名额;手工原图不会因再次同步蝦皮 URL 被误标 missing。商品套图原图列表的批量勾选只保存在当前 `SuiteTaskState` 对应的界面上下文,不写库;批量移除由 `remove_original_assets_if_unused()` 一次校验项目归属、原图类型和 job/终选引用,并在单个 SQLite 事务中删除资产行、连续重排 `source_order`。服务不删除本地文件或蝦皮线上图片,任一资产校验失败时整批回滚。
|
- `image_studio_projects.suite_settings_json` 持久化套图设置,旧数据库由 `db.init_db()` 原位补列,默认 `{}`;`draft_prompt` 继续保存卖点文本。`image_studio_assets` 中有效商品原图最多16张,历史 missing 记录不占有效名额;手工原图不会因再次同步蝦皮 URL 被误标 missing。商品套图原图列表的批量勾选只保存在当前 `SuiteTaskState` 对应的界面上下文,不写库;批量移除由 `remove_original_assets_if_unused()` 一次校验项目归属、原图类型和 job/终选引用,并在单个 SQLite 事务中删除资产行、连续重排 `source_order`。服务不删除本地文件或蝦皮线上图片,任一资产校验失败时整批回滚。
|
||||||
- T-636 起,`image_studio_projects` 增加 `binding_state`(`draft` / `bound`)和稳定 `storage_key`。既有项目迁移为 `bound`,并以原 `item_id` 回填 `storage_key`;项目目录改用 `storage_key`,因此临时草稿绑定正式商品 ID 后不移动目录、不改写已有资产路径。草稿内部使用 `draft_<uuid>` 作为仅数据库可见的非空 `item_id`,GUI 输入框始终保持空白,用户日志和 cmhub 提示词只使用“临时草稿”或“未绑定商品”,不得暴露该内部值。
|
- T-636 起,`image_studio_projects` 增加 `binding_state`(`draft` / `bound`)和稳定 `storage_key`。既有项目迁移为 `bound`,并以原 `item_id` 回填 `storage_key`;项目目录改用 `storage_key`,因此临时草稿绑定正式商品 ID 后不移动目录、不改写已有资产路径。草稿内部使用 `draft_<uuid>` 作为仅数据库可见的非空 `item_id`,GUI 输入框始终保持空白,用户日志和 cmhub 提示词只使用“临时草稿”或“未绑定商品”,不得暴露该内部值。
|
||||||
- 在商品套图中,已选账号但未填写商品 ID 时允许输入卖点、导入、拖入或粘贴本地图片。非空卖点在现有防抖稳定后会创建一个可恢复临时草稿并保存到 `image_studio_projects.draft_prompt`;首次有效图片导入也会创建草稿。空白卖点、取消选择和全部导入失败不保留空草稿。只有卖点的草稿同样属于可恢复业务内容;卖点清空且没有资产/job 时仍可按空草稿规则清理。草稿可管理本地图片、AI 帮写、生成套图、查看历史和打开结果目录,但在创建 worker、启动 Chrome 或执行 CDP 前禁止「拉取蝦皮主图」。输入合法数字商品 ID 后,经确认原地绑定同一个 `project_id`;资产、job、selection、提示词、套图设置和 `storage_key` 均保持不变。若同账号目标 ID(含软删除项目)已存在则拒绝覆盖或合并。
|
- 在商品套图中,已选账号但未填写商品 ID 时允许输入卖点、导入、拖入或粘贴本地图片。非空卖点在现有防抖稳定后会创建一个可恢复临时草稿并保存到 `image_studio_projects.draft_prompt`;首次有效图片导入也会创建草稿。空白卖点、取消选择和全部导入失败不保留空草稿。只有卖点的草稿同样属于可恢复业务内容;卖点清空且没有资产/job 时仍可按空草稿规则清理。草稿可管理本地图片、AI 帮写、生成套图、查看历史和打开结果目录,但在创建 worker、启动 Chrome 或执行 CDP 前禁止「拉取蝦皮主图」。输入合法数字商品 ID 后,经确认原地绑定同一个 `project_id`;资产、job、selection、提示词、套图设置和 `storage_key` 均保持不变。若同账号目标 ID(含软删除项目)已存在则拒绝覆盖或合并。
|
||||||
@@ -455,7 +455,7 @@ data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新
|
|||||||
|
|
||||||
- **标题提示词**:`data/title_prompt.txt` 是当前工作文本,「保存标题提示词」写入该文件;软件启动时加载该文件回显到输入框(缺失则空)。标题命名模板另存于 `data/prompts/title/*.txt`,左侧模板行提供下拉、新建、保存模板、另存为、重命名、删除;模板只负责被用户选中后载入编辑框,或把当前编辑框内容保存为命名模板,不改变生成读取路径,也不在启动时覆盖 `title_prompt.txt`。T-549 后标题提示词支持 `{旧标题}` 占位符:若提示词含 `{旧标题}`,生成前替换为该任务旧标题且不再自动追加旧标题块;若不含,则保持旧行为自动追加“旧标题:...”块。两种情况都会保留“请只返回新标题,不要解释。”输出约束。
|
- **标题提示词**:`data/title_prompt.txt` 是当前工作文本,「保存标题提示词」写入该文件;软件启动时加载该文件回显到输入框(缺失则空)。标题命名模板另存于 `data/prompts/title/*.txt`,左侧模板行提供下拉、新建、保存模板、另存为、重命名、删除;模板只负责被用户选中后载入编辑框,或把当前编辑框内容保存为命名模板,不改变生成读取路径,也不在启动时覆盖 `title_prompt.txt`。T-549 后标题提示词支持 `{旧标题}` 占位符:若提示词含 `{旧标题}`,生成前替换为该任务旧标题且不再自动追加旧标题块;若不含,则保持旧行为自动追加“旧标题:...”块。两种情况都会保留“请只返回新标题,不要解释。”输出约束。
|
||||||
- **封面提示词**:多模板。左侧模板行提供下拉(读 `data/prompts/cover/*.txt`)、新建、保存模板、模板操作(另存为/重命名/删除);重名校验、删除二次确认、删空给默认。
|
- **封面提示词**:多模板。左侧模板行提供下拉(读 `data/prompts/cover/*.txt`)、新建、保存模板、模板操作(另存为/重命名/删除);重名校验、删除二次确认、删空给默认。
|
||||||
- **商品套图提示词**:单一全局基础模板,用户文件为 `data/prompts/product_suite/base.txt`,内置恢复源为 `app/default_prompts/product_suite/base.txt`。商品套图中的「商品卖点与要求」标题行提供「提示词设置」;弹窗左侧编辑模板、右侧按白底图或其他当前分类只读预览最终请求,编辑使用 200ms 防抖。保存与「恢复默认」共用同一个 UTF-8 原子写入服务;恢复默认会先校验内置资源并经确认立即落盘。用户可移动只读规则占位符位置,但不能修改规则内容或删除必需变量;预览和新 job prompt 必须逐字一致。
|
- **商品套图提示词**:单一全局基础模板,用户文件为 `data/prompts/product_suite/base.txt`,内置恢复源为 `app/default_prompts/product_suite/base.txt`。商品套图中的「商品卖点与要求」标题行提供「提示词设置」;弹窗左侧编辑模板、右侧按白底图或其他当前分类只读预览最终请求,默认模板首行是“生成目标:{生成目标}”,编辑使用 200ms 防抖。保存与「恢复默认」共用同一个 UTF-8 原子写入服务;恢复默认会先校验内置资源并经确认立即落盘。用户可移动只读规则占位符位置,但不能修改规则内容或删除必需变量;预览和新 job prompt 必须逐字一致。旧版用户模板中的 `{套图名称}` 与 `{补充描述}` 继续兼容,不被启动升级静默替换。
|
||||||
- **变量**:标题提示词本阶段只支持 `{旧标题}`,左侧按钮「插入旧标题」在标题提示词光标处插入 `{旧标题}`。封面提示词支持占位符 `{旧标题}`、`{新标题}`、`{商品id}`、`{店铺}`,生成前用该任务真实值替换(`render_prompt`)。「插入标题」= 在封面提示词光标处插入 `{新标题}`;「预览」= 用某条任务的值替换封面变量后展示,确认实际发送给 AI 的内容。
|
- **变量**:标题提示词本阶段只支持 `{旧标题}`,左侧按钮「插入旧标题」在标题提示词光标处插入 `{旧标题}`。封面提示词支持占位符 `{旧标题}`、`{新标题}`、`{商品id}`、`{店铺}`,生成前用该任务真实值替换(`render_prompt`)。「插入标题」= 在封面提示词光标处插入 `{新标题}`;「预览」= 用某条任务的值替换封面变量后展示,确认实际发送给 AI 的内容。
|
||||||
|
|
||||||
### 6.3 应用更新(③ Tab)
|
### 6.3 应用更新(③ Tab)
|
||||||
|
|||||||
+2
-1
@@ -422,7 +422,7 @@ export_generation_round(project_id, generation_round_key, parent_dir, path=None,
|
|||||||
要点:
|
要点:
|
||||||
|
|
||||||
- 项目唯一键为账号别名 + 商品 ID;图片文件默认在 `data/images/pool/<slug>/<item_id>/` 下分 `originals/generated/exports`,删除生成图进入项目内 `.trash` 并可撤销。
|
- 项目唯一键为账号别名 + 商品 ID;图片文件默认在 `data/images/pool/<slug>/<item_id>/` 下分 `originals/generated/exports`,删除生成图进入项目内 `.trash` 并可撤销。
|
||||||
- `image_studio_projects.suite_settings_json` 保存平台/国家/语言/比例/逐图主图/分类数量;`draft_prompt` 保存商品卖点。有效原图上限16张,missing 历史不占名额。
|
- `image_studio_projects.suite_settings_json` 保存平台/国家/语言/比例/逐图主图/分类数量;固定分类顺序为白底图、场景图、模特场景图、细节说明图、卖点图,默认数量为1、2、0、0、2;自定义分类名称和数量继续保存在同一分类映射中。新增分类默认数量为0,不会无提示增加任务或点数消耗。`draft_prompt` 保存商品卖点。有效原图上限16张,missing 历史不占名额。
|
||||||
- T-643 后 `image_studio_projects.current_generation_round_key` 记录项目主界面的当前生成轮次;`image_studio_jobs.generation_round_key` 与 `generation_slot_index` 记录整轮归属和稳定展示槽位。`promote_generation_round_if_success()` 只会在同项目、同轮已存在成功 job 时原子切换当前轮;`list_generation_rounds()` 返回轮次摘要,`list_generation_round_current_jobs()` 返回每个槽位最新尝试,`list_generation_round_attempts()` 返回完整尝试历史。旧 job 保持 NULL 并按“旧版历史记录”兼容,不猜测轮次边界。
|
- T-643 后 `image_studio_projects.current_generation_round_key` 记录项目主界面的当前生成轮次;`image_studio_jobs.generation_round_key` 与 `generation_slot_index` 记录整轮归属和稳定展示槽位。`promote_generation_round_if_success()` 只会在同项目、同轮已存在成功 job 时原子切换当前轮;`list_generation_rounds()` 返回轮次摘要,`list_generation_round_current_jobs()` 返回每个槽位最新尝试,`list_generation_round_attempts()` 返回完整尝试历史。旧 job 保持 NULL 并按“旧版历史记录”兼容,不猜测轮次边界。
|
||||||
- T-646 后 `list_global_generation_rounds()` 是商品套图跨项目历史的唯一查询入口:仅返回未软删除项目的轮次摘要,按创建时间倒序并支持账号/店铺、商品 ID、项目 ID、`limit`/`offset` 筛选;正常轮次只统计每个稳定槽位的最新有效 job,完整尝试数仅用于计算重试数,NULL 轮次保留“旧版历史记录”语义。`ProductSuiteGlobalHistoryDialog` 组合该查询和 `list_generation_round_current_jobs()`,默认读取30轮,显示受限缩略图并可按轮预览;窗口不写业务数据,也不改变当前轮次。`ProductSuiteHistoryExportWorker` 在后台调用 `export_generation_round()`,只复制该轮成功且存在的输出 asset 到用户选择目录中新建的安全子目录;同名目录追加稳定序号,不覆盖外部文件,部分文件失败只汇总中文结果,源 asset 保持不变。
|
- T-646 后 `list_global_generation_rounds()` 是商品套图跨项目历史的唯一查询入口:仅返回未软删除项目的轮次摘要,按创建时间倒序并支持账号/店铺、商品 ID、项目 ID、`limit`/`offset` 筛选;正常轮次只统计每个稳定槽位的最新有效 job,完整尝试数仅用于计算重试数,NULL 轮次保留“旧版历史记录”语义。`ProductSuiteGlobalHistoryDialog` 组合该查询和 `list_generation_round_current_jobs()`,默认读取30轮,显示受限缩略图并可按轮预览;窗口不写业务数据,也不改变当前轮次。`ProductSuiteHistoryExportWorker` 在后台调用 `export_generation_round()`,只复制该轮成功且存在的输出 asset 到用户选择目录中新建的安全子目录;同名目录追加稳定序号,不覆盖外部文件,部分文件失败只汇总中文结果,源 asset 保持不变。
|
||||||
- T-648 后 `get_successful_generation_history_summary()` 是商品套图常规新一轮生成前的只读判据:它只查询未软删除当前项目的最新有效成功 job,返回成功轮数、成功图片数和最近成功时间;正常轮次按稳定槽位取最新尝试,NULL 旧版轮次整体按一轮计数,只有失败/取消/进行中的 job 返回零摘要。该 API 不扫描文件目录、不创建 job/asset/轮次,也不改变当前轮;GUI 的“查看历史”仅调用全局历史窗口并临时启用当前商品筛选。
|
- T-648 后 `get_successful_generation_history_summary()` 是商品套图常规新一轮生成前的只读判据:它只查询未软删除当前项目的最新有效成功 job,返回成功轮数、成功图片数和最近成功时间;正常轮次按稳定槽位取最新尝试,NULL 旧版轮次整体按一轮计数,只有失败/取消/进行中的 job 返回零摘要。该 API 不扫描文件目录、不创建 job/asset/轮次,也不改变当前轮;GUI 的“查看历史”仅调用全局历史窗口并临时启用当前商品筛选。
|
||||||
@@ -435,6 +435,7 @@ export_generation_round(project_id, generation_round_key, parent_dir, path=None,
|
|||||||
- 终选顺序由 `replace_selections()` 事务替换,主图/详情图同类别去重、跨类别可复用。
|
- 终选顺序由 `replace_selections()` 事务替换,主图/详情图同类别去重、跨类别可复用。
|
||||||
- 导出只写 JPEG 图片文件,透明图铺白底;商品目录已存在时只能覆盖受管命名文件或新建带时间目录,不合并、不递归清空。
|
- 导出只写 JPEG 图片文件,透明图铺白底;商品目录已存在时只能覆盖受管命名文件或新建带时间目录,不合并、不递归清空。
|
||||||
- 所有真实网络/CDP/图片生成/下载/转码由 GUI worker 调用;Widget slot 不直接执行长耗时操作。
|
- 所有真实网络/CDP/图片生成/下载/转码由 GUI worker 调用;Widget slot 不直接执行长耗时操作。
|
||||||
|
- 商品套图默认提示词首行使用 `{生成目标}`;五个内置分类使用固定目标描述,自定义分类使用“生成自定义分类图片:分类名称。”并注入实际分类名称。旧模板同时包含 `{套图名称}` 和 `{补充描述}` 时继续兼容,预览和 `build_job_specs()` 使用同一 renderer,job 保存最终提示词快照。
|
||||||
|
|
||||||
## gui 模块(`app/gui/` 包,已建,PySide6)
|
## gui 模块(`app/gui/` 包,已建,PySide6)
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -191,7 +191,7 @@
|
|||||||
│ 平台 站点 语言 比例(同行) │ [结果卡][结果卡][失败卡·重试] │
|
│ 平台 站点 语言 比例(同行) │ [结果卡][结果卡][失败卡·重试] │
|
||||||
│ 每张上传图分别作为主图生成 │ │
|
│ 每张上传图分别作为主图生成 │ │
|
||||||
│ 商品卖点与要求 [AI帮写/取消] │ │
|
│ 商品卖点与要求 [AI帮写/取消] │ │
|
||||||
│ 白底图/场景图/卖点图/自定义类 │ 进度条 套图X/Y(秒)·失败N │
|
│ 白底图/场景图/模特图/细节图/卖点图 │ 进度条 套图X/Y(秒)·失败N │
|
||||||
│ [生成套图(N) ⇄ 停止生成] │ │
|
│ [生成套图(N) ⇄ 停止生成] │ │
|
||||||
└─────────────────────────────┴──────────────────────────────────┘
|
└─────────────────────────────┴──────────────────────────────────┘
|
||||||
```
|
```
|
||||||
@@ -203,7 +203,8 @@
|
|||||||
- 商品原图最多16张。前6个槽位固定显示主图与参考1~5;列表关闭内部滚动条,按可用宽度换行并自然向下展开,由左侧配置区统一滚动。支持文件选择、外部拖入、剪贴板粘贴和列表内排序。历史失效远程图不占有效名额;第1张是主参考图。
|
- 商品原图最多16张。前6个槽位固定显示主图与参考1~5;列表关闭内部滚动条,按可用宽度换行并自然向下展开,由左侧配置区统一滚动。支持文件选择、外部拖入、剪贴板粘贴和列表内排序。历史失效远程图不占有效名额;第1张是主参考图。
|
||||||
- 每张真实原图左上角提供独立勾选框,标题行显示「已选 N 张 / 全选 / 反选」;添加占位图不参与选择。勾选只在当前任务界面内临时保留,普通刷新和排序按资产 ID 保留,切换任务或删除成功后清空。右键或 Delete 可批量移除,确认框说明准确数量、主图变化及非破坏性边界;生成中或勾选项仍在下载时整批阻断。移除只删除当前项目的本地资产记录,不删除本地源文件或蝦皮线上图片。
|
- 每张真实原图左上角提供独立勾选框,标题行显示「已选 N 张 / 全选 / 反选」;添加占位图不参与选择。勾选只在当前任务界面内临时保留,普通刷新和排序按资产 ID 保留,切换任务或删除成功后清空。右键或 Delete 可批量移除,确认框说明准确数量、主图变化及非破坏性边界;生成中或勾选项仍在下载时整批阻断。移除只删除当前项目的本地资产记录,不删除本地源文件或蝦皮线上图片。
|
||||||
- 「拉取蝦皮主图」复用只读 CDP,读取 URL 后由最多2个下载 worker 后台落盘;不改标题/封面、不拖拽、不点击更新。拉取、下载期间其余界面和其他任务仍可操作。
|
- 「拉取蝦皮主图」复用只读 CDP,读取 URL 后由最多2个下载 worker 后台落盘;不改标题/封面、不拖拽、不点击更新。拉取、下载期间其余界面和其他任务仍可操作。
|
||||||
- 套图只有一个图片类型,不再展示详情图、终选盘或模板 CRUD。默认分类为白底图1、场景图2、卖点图2;自定义分类名称非空、无空格、最多10字且不可重名。逐图主图开启后,白底图只生成一次,其余分类按每张有效原图展开。
|
- 套图按分类配置生成图片,固定分类顺序为白底图、场景图、模特场景图、细节说明图、卖点图,之后是自定义分类;默认数量为白底图1、场景图2、模特场景图0、细节说明图0、卖点图2。新增分类默认数量为0,用户未主动配置时不增加生成任务或点数消耗。自定义分类名称非空、无空格、最多10字且不可重名。逐图主图开启后,白底图只生成一次,其余分类按每张有效原图展开。
|
||||||
|
- 套图提示词设置弹窗默认模板首行使用“生成目标:{生成目标}”。五个内置分类使用统一固定目标描述;自定义分类使用“生成自定义分类图片:分类名称。”,实际分类名称必须进入最终提示词。旧模板若仍使用 `{套图名称}` 与 `{补充描述}` 会继续读取和渲染,不静默覆盖用户文件;新模板与旧模板都由同一个 renderer 提供预览和 job prompt。
|
||||||
- 平台、国家地区、语言和比例以四个带独立标签的同行下拉展示,选项只显示真实值;四项都写进每个 job 的完整提示词,比例还透传到 cmhub 生图请求,不是装饰字段。已有项目保存自己的完整设置;未绑定商品的新任务在重启后采用 `config.json` 的最近四项选择。生成仍走 `image_studio_generation.run_jobs()` 的 submit → poll → download 管线。
|
- 平台、国家地区、语言和比例以四个带独立标签的同行下拉展示,选项只显示真实值;四项都写进每个 job 的完整提示词,比例还透传到 cmhub 生图请求,不是装饰字段。已有项目保存自己的完整设置;未绑定商品的新任务在重启后采用 `config.json` 的最近四项选择。生成仍走 `image_studio_generation.run_jobs()` 的 submit → poll → download 管线。
|
||||||
- 平台、站点、语言和比例四个生成设置下拉仅在闭合时拦截自身滚轮改值,并把滚动交给 `suiteConfigScroll`;展开时滚轮只浏览候选列表,不静默提交当前值。鼠标点击、方向键、回车和 `Alt+↓` 保持原有显式选择语义;账号、历史筛选和设置页等其它下拉不受影响。
|
- 平台、站点、语言和比例四个生成设置下拉仅在闭合时拦截自身滚轮改值,并把滚动交给 `suiteConfigScroll`;展开时滚轮只浏览候选列表,不静默提交当前值。鼠标点击、方向键、回车和 `Alt+↓` 保持原有显式选择语义;账号、历史筛选和设置页等其它下拉不受影响。
|
||||||
- 生成按钮按当前总数显示并在运行时切换为「停止生成」;确认停止后显示「正在停止...」,重复点击不再弹确认框。每轮生成用独立运行标识隔离旧信号,本轮全部 job 终态或线程结束时都会统一恢复按钮;最终 worker 信号缺失时由数据库终态看门狗兜底,不要求用户重启。停止会取消未开始任务,已提交任务停止本地等待并保留后续继续查询语义;客户端不承诺取消服务端任务或退回点数。T-643 后项目持久化当前生成轮次:常规新轮至少成功一张才替换主结果区,全部失败/取消保留上一当前轮;主结果按稳定槽位显示同轮最新 job,单张重试留在原槽位。旧版无轮次 job 临时显示为“旧版历史记录”,不按时间或图片数量猜测归属。成功图可预览、复制路径、打开目录、重新生成、移入项目废纸篓并撤销,失败卡显示脱敏中文摘要与重试入口。
|
- 生成按钮按当前总数显示并在运行时切换为「停止生成」;确认停止后显示「正在停止...」,重复点击不再弹确认框。每轮生成用独立运行标识隔离旧信号,本轮全部 job 终态或线程结束时都会统一恢复按钮;最终 worker 信号缺失时由数据库终态看门狗兜底,不要求用户重启。停止会取消未开始任务,已提交任务停止本地等待并保留后续继续查询语义;客户端不承诺取消服务端任务或退回点数。T-643 后项目持久化当前生成轮次:常规新轮至少成功一张才替换主结果区,全部失败/取消保留上一当前轮;主结果按稳定槽位显示同轮最新 job,单张重试留在原槽位。旧版无轮次 job 临时显示为“旧版历史记录”,不按时间或图片数量猜测归属。成功图可预览、复制路径、打开目录、重新生成、移入项目废纸篓并撤销,失败卡显示脱敏中文摘要与重试入口。
|
||||||
|
|||||||
+3
-1
@@ -3,7 +3,7 @@ id: T-654
|
|||||||
title: 扩展套图分类并统一生成目标提示词
|
title: 扩展套图分类并统一生成目标提示词
|
||||||
phase: 7
|
phase: 7
|
||||||
deps: [T-637]
|
deps: [T-637]
|
||||||
status: TODO
|
status: DONE
|
||||||
created: 2026-07-17
|
created: 2026-07-17
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -114,3 +114,5 @@ created: 2026-07-17
|
|||||||
## 执行记录
|
## 执行记录
|
||||||
|
|
||||||
- 2026-07-17:根据商品套图新增分类、生成目标提示词和自定义分类名缺失问题创建任务,待实现。
|
- 2026-07-17:根据商品套图新增分类、生成目标提示词和自定义分类名缺失问题创建任务,待实现。
|
||||||
|
- 2026-07-17:完成固定分类扩展、默认分类数量兼容、生成目标提示词、旧模板兼容和自定义分类名称注入;同步更新架构、路由与 API 文档。
|
||||||
|
- 2026-07-17:验证通过 `python -m ruff check app tests main.py`、`python -m compileall app main.py`、`python -m unittest discover -s tests`,共 579 个测试通过。
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ class PackagingTests(unittest.TestCase):
|
|||||||
self.assertIn("COLLECT(", spec)
|
self.assertIn("COLLECT(", spec)
|
||||||
self.assertIn('name="cmshopee"', normalized)
|
self.assertIn('name="cmshopee"', normalized)
|
||||||
suite_prompt = self.read_text("app/default_prompts/product_suite/base.txt")
|
suite_prompt = self.read_text("app/default_prompts/product_suite/base.txt")
|
||||||
self.assertIn("{套图名称}", suite_prompt)
|
self.assertIn("{生成目标}", suite_prompt)
|
||||||
|
self.assertNotIn("套图名称:", suite_prompt)
|
||||||
self.assertIn("{尺寸与长图规则}", suite_prompt)
|
self.assertIn("{尺寸与长图规则}", suite_prompt)
|
||||||
|
|
||||||
def test_build_script_blocks_user_data_in_release_output(self):
|
def test_build_script_blocks_user_data_in_release_output(self):
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ class ProductSuiteTests(unittest.TestCase):
|
|||||||
def test_defaults_and_per_image_total(self):
|
def test_defaults_and_per_image_total(self):
|
||||||
settings = product_suite.default_suite_settings()
|
settings = product_suite.default_suite_settings()
|
||||||
|
|
||||||
self.assertEqual(["白底图", "场景图", "卖点图"], product_suite.category_order(settings))
|
self.assertEqual(
|
||||||
|
["白底图", "场景图", "模特场景图", "细节说明图", "卖点图"],
|
||||||
|
product_suite.category_order(settings),
|
||||||
|
)
|
||||||
self.assertEqual(5, product_suite.suite_total_count(settings, 3))
|
self.assertEqual(5, product_suite.suite_total_count(settings, 3))
|
||||||
|
|
||||||
settings["per_image_primary"] = True
|
settings["per_image_primary"] = True
|
||||||
@@ -34,7 +37,10 @@ class ProductSuiteTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual("3:4", settings["ratio"])
|
self.assertEqual("3:4", settings["ratio"])
|
||||||
self.assertEqual(["白底图", "场景图", "卖点图", "尺寸图"], product_suite.category_order(settings))
|
self.assertEqual(
|
||||||
|
["白底图", "场景图", "模特场景图", "细节说明图", "卖点图", "尺寸图"],
|
||||||
|
product_suite.category_order(settings),
|
||||||
|
)
|
||||||
self.assertEqual(5, product_suite.suite_total_count(settings, 1))
|
self.assertEqual(5, product_suite.suite_total_count(settings, 1))
|
||||||
|
|
||||||
def test_recent_settings_only_keep_allowed_dropdown_values(self):
|
def test_recent_settings_only_keep_allowed_dropdown_values(self):
|
||||||
@@ -108,8 +114,14 @@ class ProductSuiteTests(unittest.TestCase):
|
|||||||
spec["prompt"].splitlines()[-1].startswith("本次生成比例:4:3")
|
spec["prompt"].splitlines()[-1].startswith("本次生成比例:4:3")
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertIn("套图名称:白底图,白底主图", specs[0]["prompt"])
|
self.assertIn(
|
||||||
self.assertIn("套图名称:场景图,生活化场景", specs[1]["prompt"])
|
"生成目标:生成 Shopee 台灣商品白底主圖,純白背景,商品清晰置中,不添加多餘文字。",
|
||||||
|
specs[0]["prompt"],
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"生成目标:生成生活化使用場景圖,展示商品在真實情境中的用途與氛圍,畫面自然可信。",
|
||||||
|
specs[1]["prompt"],
|
||||||
|
)
|
||||||
white_prompt = specs[0]["prompt"]
|
white_prompt = specs[0]["prompt"]
|
||||||
self.assertLess(white_prompt.index("重要尺寸要求"), white_prompt.index("重要禁用内容"))
|
self.assertLess(white_prompt.index("重要尺寸要求"), white_prompt.index("重要禁用内容"))
|
||||||
self.assertLess(white_prompt.index("重要禁用内容"), white_prompt.index("价格信息规则"))
|
self.assertLess(white_prompt.index("重要禁用内容"), white_prompt.index("价格信息规则"))
|
||||||
@@ -153,9 +165,33 @@ class ProductSuiteTests(unittest.TestCase):
|
|||||||
"draft_hidden",
|
"draft_hidden",
|
||||||
)
|
)
|
||||||
rendered = product_suite.render_product_suite_prompt(default_text, context)
|
rendered = product_suite.render_product_suite_prompt(default_text, context)
|
||||||
self.assertIn("套图名称:尺寸图", rendered)
|
self.assertIn("生成目标:生成自定义分类图片:尺寸图。", rendered)
|
||||||
self.assertNotIn("draft_", rendered)
|
self.assertNotIn("draft_", rendered)
|
||||||
self.assertNotIn("白底主图", rendered)
|
self.assertNotIn("套图名称:", rendered)
|
||||||
|
|
||||||
|
legacy_template = default_text.replace(
|
||||||
|
"生成目标:{生成目标}",
|
||||||
|
"套图名称:{套图名称}{补充描述}",
|
||||||
|
)
|
||||||
|
self.assertEqual([], product_suite.product_suite_prompt_errors(legacy_template))
|
||||||
|
legacy_rendered = product_suite.render_product_suite_prompt(legacy_template, context)
|
||||||
|
self.assertIn("套图名称:尺寸图,生成自定义分类图片:尺寸图。", legacy_rendered)
|
||||||
|
|
||||||
|
def test_category_descriptions_are_fixed_and_custom_name_is_explicit(self):
|
||||||
|
expected = {
|
||||||
|
"白底图": "生成 Shopee 台灣商品白底主圖,純白背景,商品清晰置中,不添加多餘文字。",
|
||||||
|
"场景图": "生成生活化使用場景圖,展示商品在真實情境中的用途與氛圍,畫面自然可信。",
|
||||||
|
"模特场景图": "生成模特或手持使用情境圖,畫面自然可信,商品為主角。",
|
||||||
|
"细节说明图": "生成商品細節特寫說明圖,突出材質、做工、接口、紋理或關鍵細節。",
|
||||||
|
"卖点图": "生成賣點詳解圖,使用繁體中文短文案呈現核心優勢,版面乾淨。",
|
||||||
|
}
|
||||||
|
for category, description in expected.items():
|
||||||
|
self.assertEqual(description, product_suite.category_description(category))
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
"生成自定义分类图片:结构展示。",
|
||||||
|
product_suite.category_description("结构展示"),
|
||||||
|
)
|
||||||
|
|
||||||
def test_product_suite_optional_item_and_reference_variables(self):
|
def test_product_suite_optional_item_and_reference_variables(self):
|
||||||
template = prompts.load_default_product_suite_prompt() + (
|
template = prompts.load_default_product_suite_prompt() + (
|
||||||
|
|||||||
@@ -330,7 +330,10 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual("白底图", dialog.category_combo.currentData())
|
self.assertEqual("白底图", dialog.category_combo.currentData())
|
||||||
self.assertTrue(dialog.preview_edit.isReadOnly())
|
self.assertTrue(dialog.preview_edit.isReadOnly())
|
||||||
self.assertIn("套图名称:白底图,白底主图", dialog.preview_edit.toPlainText())
|
self.assertIn(
|
||||||
|
"生成目标:生成 Shopee 台灣商品白底主圖,純白背景,商品清晰置中,不添加多餘文字。",
|
||||||
|
dialog.preview_edit.toPlainText(),
|
||||||
|
)
|
||||||
self.assertIn("40小时续航", dialog.preview_edit.toPlainText())
|
self.assertIn("40小时续航", dialog.preview_edit.toPlainText())
|
||||||
sizes = dialog.splitter.sizes()
|
sizes = dialog.splitter.sizes()
|
||||||
self.assertLessEqual(abs(sizes[0] - sizes[1]), 12)
|
self.assertLessEqual(abs(sizes[0] - sizes[1]), 12)
|
||||||
@@ -462,6 +465,8 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
expected = {
|
expected = {
|
||||||
"白底图": (1, "白底主图,多角度呈现商品细节"),
|
"白底图": (1, "白底主图,多角度呈现商品细节"),
|
||||||
"场景图": (2, "生活化场景展示商品使用方式"),
|
"场景图": (2, "生活化场景展示商品使用方式"),
|
||||||
|
"模特场景图": (0, "模特或手持场景展示商品真实使用"),
|
||||||
|
"细节说明图": (0, "突出材质、做工和关键细节"),
|
||||||
"卖点图": (2, "突出核心卖点和差异化优势"),
|
"卖点图": (2, "突出核心卖点和差异化优势"),
|
||||||
}
|
}
|
||||||
self.assertEqual(list(expected), list(tab.category_rows))
|
self.assertEqual(list(expected), list(tab.category_rows))
|
||||||
|
|||||||
Reference in New Issue
Block a user