docs: add product suite generation history tasks
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
---
|
||||
id: T-643
|
||||
title: 商品套图持久化当前生成轮次
|
||||
phase: 7
|
||||
deps: [T-642]
|
||||
status: TODO
|
||||
created: 2026-07-16
|
||||
---
|
||||
|
||||
## 问题 / 背景
|
||||
|
||||
⑥「商品套图」当前把 `SuiteTaskState.current_job_ids` 作为“本轮结果”的唯一依据。该字段只存在于 GUI 内存中:
|
||||
|
||||
- 软件重启、关闭任务后重新输入商品 ID,无法恢复上次正在使用的一轮生成结果;
|
||||
- `image_studio_jobs` 只有单张生成 job,没有持久化的轮次和槽位字段;
|
||||
- 「历史生成」当前只能把当前项目全部 job 混在同一个结果网格中,无法可靠区分整轮生成、单张失败重试和更早历史;
|
||||
- 如果直接隐藏「历史生成」,已有项目重新打开后可能显示“还没有生成结果”,但数据库和本地图片实际仍然存在。
|
||||
|
||||
产品目标是:每个店铺商品项目在主界面默认只展示一轮“当前结果”,同时保留以前的 job、图片、错误和计费记录,供后续历史弹窗查看。不能通过创建时间间隔、文件夹名称或图片数量猜测轮次。
|
||||
|
||||
## 方案
|
||||
|
||||
### 1. 为生成 job 增加持久化轮次与槽位
|
||||
|
||||
修改 SQLite schema、迁移和 `app/image_studio.py` 数据模型:
|
||||
|
||||
- `image_studio_projects` 增加可空 `current_generation_round_key`,表示主界面当前使用的生成轮次;
|
||||
- `image_studio_jobs` 增加 `generation_round_key`,同一次整轮生成创建的 job 使用同一个不可复用 UUID;
|
||||
- `image_studio_jobs` 增加 `generation_slot_index`,按本轮生成规格顺序从 0 开始稳定编号;
|
||||
- 为 `(project_id, generation_round_key, generation_slot_index, id)` 增加适合查询当前槽位和历史轮次的索引;
|
||||
- 数据类、建库和增量迁移同步兼容旧数据库。
|
||||
|
||||
不把 GUI `run_token` 直接作为长期业务主键。轮次 key 可以在本轮开始时由 GUI 创建并传给 worker,但持久化语义是“生成轮次”,不承担迟到信号隔离职责。
|
||||
|
||||
### 2. 整轮生成创建新轮次
|
||||
|
||||
修改 `app/gui/tabs/product_suite.py`、`app/gui/workers.py` 和生成服务调用:
|
||||
|
||||
- 正常点击「生成套图」时创建新的 `generation_round_key`;
|
||||
- worker 创建每条 job 时同时写入该轮次 key 和稳定槽位;
|
||||
- 活动运行仍使用现有 `generation_run_token` 做线程/迟到信号隔离,不改变 T-639/T-640 的终态看门狗;
|
||||
- 当前会话内继续用 `current_job_ids` 实时展示本轮 job,避免等待整轮完成后才显示;
|
||||
- 本轮至少有一个成功结果时,把项目 `current_generation_round_key` 原子更新为新轮次;
|
||||
- 本轮全部失败、全部取消或未创建有效结果时,保留原来的当前轮,失败轮仍进入历史记录;
|
||||
- 部分成功时允许新轮成为当前轮,主界面保留成功和失败槽位,用户可继续按 T-640 单张重试。
|
||||
|
||||
切换当前轮不得删除、覆盖或改写旧轮次 job、计费、错误和输出资产。
|
||||
|
||||
### 3. 单张重试留在原轮次和原槽位
|
||||
|
||||
T-640 的单张失败重试继续新建 job,但必须:
|
||||
|
||||
- 继承原 job 的 `generation_round_key`;
|
||||
- 继承原 job 的 `generation_slot_index`;
|
||||
- 当前轮每个槽位默认取该轮次、该槽位最新创建的 job;
|
||||
- 重试成功、再次失败或取消都只替换该槽位在主界面的当前版本,其他槽位和轮次不变;
|
||||
- 原失败 job 和每次重试 job 全部保留,供历史详情和问题审计使用。
|
||||
|
||||
成功图片的主动“重新生成”若后续开放,也必须明确是替换当前槽位还是新增变体;本任务不扩大该入口。
|
||||
|
||||
### 4. 已有项目和旧版 job 兼容
|
||||
|
||||
旧数据库中的 job 没有可靠的轮次边界,不得按时间差或图片数量猜测:
|
||||
|
||||
- 迁移后旧 job 使用明确的 `legacy` 兼容轮次语义,或保持空值并由查询层统一映射为“旧版历史记录”;
|
||||
- 若项目只有旧版 job、没有新格式当前轮,主界面可以临时展示“旧版历史记录”,避免用户误以为图片丢失;
|
||||
- 用户完成下一次新格式整轮生成后,项目切换到新轮次,旧 job 只在历史记录中展示;
|
||||
- 文件缺失时保留 job/asset 记录并显示不可用状态,不自动删除历史。
|
||||
|
||||
### 5. 当前结果查询与恢复
|
||||
|
||||
在 `app/image_studio.py` 提供结构化 API:
|
||||
|
||||
- 设置或读取项目当前轮次;
|
||||
- 按项目列出生成轮次摘要;
|
||||
- 按轮次和槽位读取当前有效 job;
|
||||
- 按轮次读取全部尝试记录,供 T-644 历史弹窗使用。
|
||||
|
||||
商品项目绑定、草稿恢复、任务切换和软件重启后:
|
||||
|
||||
- 从项目持久化的当前轮次恢复 `current_job_ids`;
|
||||
- 按槽位顺序展示,不使用 `updated_at` 排序打乱图片位置;
|
||||
- 当前轮不存在、job 被软删除或图片文件缺失时给出稳定空态/缺失态,不抛出裸英文错误;
|
||||
- `generation_job_ids` 仍只表示当前活动请求,不从历史轮次恢复。
|
||||
|
||||
### 6. 数据一致性和诊断
|
||||
|
||||
- 设置当前轮次、读取轮次槽位和单张重试替换必须校验 project/job 归属;
|
||||
- 不允许把其他账号、商品项目或草稿项目的轮次设为当前轮;
|
||||
- 当前轮 key、job 数量、成功/失败数量可写诊断日志,不记录提示词全文、图片 URL、密钥或 Cookie;
|
||||
- schema 变化同步更新 `docs/04-architecture.md`、`docs/api.md` 和 `docs/routes.md`。
|
||||
|
||||
## 验收要点
|
||||
|
||||
- [ ] 商品完成一轮生成后,关闭软件并重新打开相同店铺和商品 ID,主结果区恢复同一轮、同一顺序的图片。
|
||||
- [ ] 再生成一整轮后,主界面只展示新当前轮,旧轮 job、图片、错误和计费记录仍保留。
|
||||
- [ ] 新轮全部失败或全部取消时,原当前轮继续显示;失败轮可以被历史查询读取。
|
||||
- [ ] 新轮部分成功时切换为当前轮,成功和失败槽位均可见,失败槽位可继续单张重试。
|
||||
- [ ] 单张失败重试沿用原轮次和槽位,其他当前图片不消失、不换序。
|
||||
- [ ] 程序重启后重试结果仍占据原槽位,原失败 job 仍存在。
|
||||
- [ ] 旧数据库可以自动迁移;旧 job 不被猜测性拆分、删除或覆盖。
|
||||
- [ ] 当前轮和历史轮查询不会跨店铺、跨商品或跨项目串数据。
|
||||
- [ ] 不影响生成停止、迟到信号隔离、恢复未完成 cmhub 任务和本地图片保存。
|
||||
|
||||
## 测试
|
||||
|
||||
- `tests/test_db.py`:
|
||||
- 新建数据库字段与索引;
|
||||
- 旧数据库增量迁移;
|
||||
- 重复初始化幂等。
|
||||
- `tests/test_image_studio.py`:
|
||||
- 当前轮设置/读取和项目归属校验;
|
||||
- 轮次摘要、槽位当前 job、全部尝试查询;
|
||||
- 旧版 job 兼容。
|
||||
- `tests/test_workers.py`:
|
||||
- 整轮 job 写入相同轮次和稳定槽位;
|
||||
- 单张重试继承轮次和槽位。
|
||||
- `tests/test_product_suite_gui.py`:
|
||||
- 项目重新绑定和重启恢复当前轮;
|
||||
- 新轮成功、部分成功、全失败/取消时的当前轮切换;
|
||||
- 单张重试跨重启保持槽位。
|
||||
- 运行:
|
||||
|
||||
```bash
|
||||
py -3.10 -m unittest discover -s tests
|
||||
py -3.10 -m ruff check app tests main.py
|
||||
py -3.10 -m compileall app main.py
|
||||
git diff --check
|
||||
```
|
||||
|
||||
## 边界(不改什么)
|
||||
|
||||
- 不删除旧轮次 job、生成图片、错误、扣点或余额记录。
|
||||
- 不通过扫描目录、文件名、时间差或图片数量猜测轮次。
|
||||
- 不在本任务实现历史弹窗、历史图片删除或“恢复历史轮为当前轮”。
|
||||
- 不修改 cmhub API、模型别名、提示词组装、并发、超时、轮询和下载协议。
|
||||
- 不修改 CDP、蝦皮主图拉取、①导入采集、②AI生成、③更新蝦皮、④账号管理或⑤设置。
|
||||
|
||||
## 执行记录
|
||||
|
||||
- 待执行。
|
||||
@@ -0,0 +1,143 @@
|
||||
---
|
||||
id: T-644
|
||||
title: 商品套图历史生成按轮次弹窗展示
|
||||
phase: 7
|
||||
deps: [T-643]
|
||||
status: TODO
|
||||
created: 2026-07-16
|
||||
---
|
||||
|
||||
## 问题 / 背景
|
||||
|
||||
T-643 完成后,⑥「商品套图」主结果区默认只展示当前生成轮次,并可在软件重启后恢复。原「历史生成」按钮当前是可选中按钮,会直接把当前项目全部 job 混入主结果网格:
|
||||
|
||||
- 主界面在“本轮”和“全部历史”之间切换,用户容易失去当前工作上下文;
|
||||
- 历史 job 没有按生成轮次、时间和状态分组;
|
||||
- 旧失败、单张重试和后续新一轮图片混排,难以对比;
|
||||
- “返回本轮”模式依赖 `SuiteTaskState.show_history` 内存状态,增加生成、重试和刷新时的分支。
|
||||
|
||||
参考 `D:\chengma\虾皮圈电商图生成器源码` 的「历史生成」模块,其按批次倒序展示、数量统计、缩略图网格、预览和打开目录的交互值得复用;但当前项目必须以 SQLite 的项目、轮次、job 和 asset 为事实来源,不能照搬扫描文件夹和直接删除文件的实现。
|
||||
|
||||
## 方案
|
||||
|
||||
### 1. 历史按钮改为打开弹窗
|
||||
|
||||
修改 `app/gui/tabs/product_suite.py`:
|
||||
|
||||
- 「历史生成」改为普通按钮,不再 `setCheckable(True)`;
|
||||
- 点击后打开当前商品项目的「历史生成记录」弹窗;
|
||||
- 删除主结果区的 `show_history` 模式切换和「返回本轮」文案;
|
||||
- 主结果区始终读取 T-643 的当前轮次,生成、单张重试和刷新不再切换历史状态;
|
||||
- 未选择店铺、未绑定商品项目或没有生成记录时,给出明确中文提示,不打开空白弹窗。
|
||||
|
||||
按钮仍位于顶部项目操作区,不改变「打开结果文件夹」「添加图片」和账号/商品 ID 控件顺序。
|
||||
|
||||
### 2. 弹窗限定当前店铺和商品
|
||||
|
||||
新增独立 PySide6 历史对话框组件,标题和上下文至少显示:
|
||||
|
||||
- 店铺名称/账号别名;
|
||||
- 商品 ID;临时草稿显示“临时草稿”,不伪造商品 ID;
|
||||
- 轮次数量和可用图片数量;
|
||||
- 最近刷新时间或刷新按钮。
|
||||
|
||||
弹窗只查询当前 `project_id`,不得展示其他店铺、商品或已软删除项目的数据。切换商品任务后再次打开,应使用新项目上下文;同一窗口不得残留上一商品缩略图。
|
||||
|
||||
### 3. 按轮次倒序展示
|
||||
|
||||
使用 T-643 的结构化轮次查询,不扫描图片目录:
|
||||
|
||||
- 轮次按创建时间倒序;
|
||||
- 每轮标题显示生成时间、总数、成功/失败/取消数量;
|
||||
- 当前使用轮次显示「当前」标签,便于与历史轮次对比;
|
||||
- 旧版迁移记录显示「旧版历史记录」,不伪造生成时间或批次;
|
||||
- 每轮图片按稳定槽位顺序展示;
|
||||
- 单张重试默认展示该槽位当前版本;可在 tooltip 或详情中说明重试次数,不把同一槽位的每次失败尝试都平铺成多张主图片;
|
||||
- 全失败或无可用图片的轮次仍保留轮次头和状态说明,不能静默消失。
|
||||
|
||||
参考项目的“类型标签 + 时间 + 数量 + 图片网格”视觉层级可以借鉴,但保持当前桌面应用的颜色、8px 以内圆角、字体和卡片密度,不复制 Web CSS。
|
||||
|
||||
### 4. 图片卡片和预览
|
||||
|
||||
第一版历史弹窗为只读查看:
|
||||
|
||||
- 单击选中图片,双击打开现有自适应大图预览;
|
||||
- 右键提供「预览」「复制路径」「打开所在文件夹」;
|
||||
- 本地文件缺失时显示「图片文件不可用」占位,保留生成时间、类型和状态;
|
||||
- tooltip 可显示图片类型、轮次时间、状态和重试次数;
|
||||
- 不显示接口 URL、完整提示词、Cookie、密钥或裸英文技术错误。
|
||||
|
||||
历史弹窗第一版不提供删除、撤销删除、重新生成、设为当前轮或替换终选图。参考项目的直接文件删除不能照搬,因为当前资产可能被 job、终选或导出流程引用。
|
||||
|
||||
### 5. 性能和窗口生命周期
|
||||
|
||||
- 历史轮次和 job 从 SQLite 分页或分段读取,初次建议加载最近 20 轮,底部提供「加载更多」;
|
||||
- 缩略图按可见区域延迟加载或限制解码尺寸,不直接把原始大图全部载入内存;
|
||||
- 刷新时保持滚动位置或当前轮展开状态,不重复创建无法回收的图片对象;
|
||||
- 同一商品重复点击按钮时复用或聚焦已有弹窗,避免打开多个相同窗口;
|
||||
- 关闭弹窗必须释放缩略图、信号和线程引用,不出现 `QThread: Destroyed while thread is still running`;
|
||||
- 历史弹窗打开期间生成任务可以继续运行;点击刷新后读取新状态,不阻塞 cmhub worker。
|
||||
|
||||
若第一版没有引入后台缩略图线程,也必须使用受限尺寸的同步加载并验证常见历史数量下不会明显冻结 GUI。
|
||||
|
||||
### 6. 空态与异常边界
|
||||
|
||||
- 无历史:显示「暂无历史生成记录,完成套图生成后会自动出现在这里」;
|
||||
- 只有当前轮:正常显示当前轮,不使用“没有历史”误导用户;
|
||||
- 文件缺失:只标记图片不可用,不删除 DB;
|
||||
- 数据库读取失败:弹窗保留并显示中文错误与「重试」;
|
||||
- 当前项目被软删除或任务关闭:关闭弹窗或切换为不可操作状态,不继续读取错误项目;
|
||||
- 用户可见文案全部使用中文。
|
||||
|
||||
同步更新 `docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`;如新增效果图,统一保存到 `docs/ui/` 并登记 `docs/ui/README.md`。
|
||||
|
||||
## 验收要点
|
||||
|
||||
- [ ] 点击「历史生成」后弹出当前店铺、当前商品的历史生成窗口,主结果区仍保持当前轮。
|
||||
- [ ] 按钮不再出现选中状态或「返回本轮」文字。
|
||||
- [ ] 历史按轮次倒序分组,显示时间、数量、状态和当前轮标签。
|
||||
- [ ] 同一轮单张重试不会平铺成新的整轮;当前槽位图片和重试次数表达清楚。
|
||||
- [ ] 软件重启后可查看 T-643 持久化的当前轮和更早历史轮。
|
||||
- [ ] 不会展示其他店铺、商品或软删除项目的图片。
|
||||
- [ ] 双击图片自适应预览;复制路径、打开所在文件夹正常。
|
||||
- [ ] 文件缺失显示占位,不删除 job/asset 或导致弹窗崩溃。
|
||||
- [ ] 历史较多时可加载更多,打开和滚动不会一次解码全部原图导致 GUI 长时间冻结。
|
||||
- [ ] 生成任务运行时可打开和刷新历史弹窗,生成停止、重试和结果刷新不回归。
|
||||
- [ ] 历史弹窗没有删除、设为当前轮或重新生成等破坏性入口。
|
||||
|
||||
## 测试
|
||||
|
||||
- `tests/test_image_studio.py`:
|
||||
- 项目轮次分页和倒序;
|
||||
- 当前轮标签、状态统计和槽位去重;
|
||||
- 跨项目隔离、旧版历史和缺失资产。
|
||||
- `tests/test_product_suite_gui.py`:
|
||||
- 历史按钮为普通按钮并打开弹窗;
|
||||
- 主结果区不再切换 `show_history`;
|
||||
- 商品上下文、轮次分组、当前标签、空态和错误态;
|
||||
- 双击预览、复制路径、打开目录;
|
||||
- 重复点击复用窗口、关闭任务后的窗口收尾;
|
||||
- 生成期间刷新历史不影响 worker。
|
||||
- 如拆出独立对话框测试文件,覆盖分页/加载更多和缩略图受限加载。
|
||||
- 运行:
|
||||
|
||||
```bash
|
||||
py -3.10 -m unittest discover -s tests
|
||||
py -3.10 -m ruff check app tests main.py
|
||||
py -3.10 -m compileall app main.py
|
||||
git diff --check
|
||||
```
|
||||
|
||||
在包含多轮、部分失败、单张重试和缺失文件的测试项目上人工检查窗口尺寸、滚动、预览和 Windows 100% / 125% / 150% 显示缩放。
|
||||
|
||||
## 边界(不改什么)
|
||||
|
||||
- 不扫描生成目录推断历史轮次,不把文件系统当作主数据源。
|
||||
- 不在历史弹窗删除图片、清空轮次、恢复历史轮为当前轮或触发重新生成。
|
||||
- 不修改生成提示词、模型、并发、扣点、超时、轮询、下载或文件保存格式。
|
||||
- 不修改 CDP、蝦皮主图拉取、①导入采集、②AI生成、③更新蝦皮、④账号管理或⑤设置。
|
||||
- 不照搬参考项目的 Web 技术栈、全局历史导航或直接文件删除逻辑。
|
||||
|
||||
## 执行记录
|
||||
|
||||
- 待执行。
|
||||
Reference in New Issue
Block a user