Files

114 lines
7.1 KiB
Markdown
Raw Permalink Normal View History

2026-07-16 17:45:06 +08:00
---
id: T-640
title: 商品套图单张失败重试保持当前轮结果
phase: 7
deps: [T-639]
status: DONE
2026-07-16 17:45:06 +08:00
created: 2026-07-16
---
## 问题 / 背景
⑥「商品套图」一轮生成中有单张图片失败时,用户点击失败图片卡片的「重试」,当前实现会把这次单张操作当成一轮全新的套图生成:
- `retry_job()` 重新组装单条 spec 后直接调用完整的 `start_generation()`;
- `start_generation()` 会清空 `current_job_ids`、重置本轮进度并退出历史视图;
- worker 会新建一条 job,随后 GUI 只把这个新 job 识别为当前轮;
- 原本同轮已经成功的图片会从「本轮」结果区消失,用户看到的是只有一张图片的“第二轮生成”。
新建 job 保留失败历史、计费和诊断记录是正确的,但「失败重试」在产品语义上应当是修复当前轮中的失败槽位,不应表现为重新开始一整轮。成功图片的「重新生成」与失败图片的「重试」也应保持不同语义。
## 方案
### 1. 分离活动生成 job 与当前结果 job
修改 `app/gui/tabs/product_suite.py` 的 `SuiteTaskState`:
- `current_job_ids` 继续表示「本轮结果区当前有效的图片槽位」;
- 新增仅用于当前运行生命周期的 `generation_job_ids`,表示本次实际提交、被进度/停止/终态看门狗跟踪的 job;
- 正常整轮生成时同时重建两组 ID;
- 单张失败重试时只清空 `generation_job_ids`,保留其他 `current_job_ids`;
- T-639 的终态快照、停止、线程结束和看门狗统一只读取 `generation_job_ids`,不得把当前轮其他历史成功 job 重复计入本次重试汇总。
这些字段只保存在 GUI 内存,不修改 SQLite schema。
### 2. 单张重试替换失败槽位
为 `start_generation()` 增加明确的单张重试上下文:
- `retry_job()` 传入原失败 job ID,而不是把单条 spec 当作普通整轮生成;
- worker 仍创建新的 job,不覆盖或删除原失败 job;
- worker 首次返回新 job ID 后,在 `current_job_ids` 中把原失败 ID 原位替换为新 job ID;
- 当前轮其他成功 job ID 保持不变;
- 如果重试再次失败或被停止,新的失败/停止 job 继续占据该槽位,用户可以再次重试;
- 「历史生成」继续列出原失败 job 与每次新建的重试 job,保留完整审计记录。
当前结果区按 `current_job_ids` 的槽位顺序展示,避免新建重试 job 因数据库更新时间排序而跳到第一张。
### 3. 区分重试与整轮生成反馈
单张失败重试期间:
- 进度只统计本次重试的 1 张,不重新统计当前轮全部图片;
- 不清空当前轮结果区,不切换成只有一张图片的新一轮;
- 完成弹窗使用「图片重试完成」或「图片重试已停止」等中文文案;
- 汇总显示本次重试成功、失败、停止数量,不使用「本轮共 1 张」误导用户这是第二轮套图;
- 状态栏使用「图片重试成功 / 图片重试失败 / 图片重试已停止」;
- 原整轮「商品套图生成完成」与停止反馈保持不变。
从「历史生成」发起重试时保持历史视图,避免自动跳到一个无法表达原历史轮次的临时“本轮”;新 job 会按历史排序显示。
### 4. 操作边界
- 只有 `failed/expired/cancelled` 卡片显示的「重试」使用当前轮修复语义;
- 成功图片右键「重新生成」继续视为主动新增变体,不在本任务中改成失败槽位替换;
- 重试会重新调用 cmhub,可能再次扣点;不伪装为恢复原请求;
- 不删除旧失败 job、旧错误、计费记录或已成功图片;
- 不修改 cmhub 请求、并发上限、提示词、图片下载、套图数量和项目存储结构。
## 验收要点
- [ ] 一轮 N 张中有 1 张失败,点击该卡片「重试」后,当前结果区仍保留其余 N-1 张成功图片。
- [ ] 新 job ID 到达后,原失败卡片在原位置被新重试卡片替换,当前结果区仍为 N 个槽位。
- [ ] 重试成功后当前结果区显示 N 张成功图片,不表现为只有 1 张图片的第二轮。
- [ ] 重试再次失败或停止时,该槽位显示最新状态,并可再次重试。
- [ ] 原失败 job 和新重试 job 都可在「历史生成」中查看。
- [ ] 本次重试的进度、停止、线程结束和终态看门狗只统计新 job,不重复统计当前轮其他成功 job。
- [ ] 从历史视图点击重试时保持历史视图,新 job 正常进入历史列表。
- [ ] 正常整轮生成、历史生成、手动停止、T-639 终态兜底和旧 `run_token` 隔离不回归。
- [ ] 用户可见文案全部为中文,不暴露接口路径、图片 URL、提示词全文或密钥。
## 测试
- `tests/test_product_suite_gui.py`:
- 当前轮失败卡片重试后保留其他成功 job;
- 新 job 原位替换失败 job,结果顺序稳定;
- 活动 job 快照只包含重试 job;
- 重试完成使用独立中文汇总;
- 历史视图发起重试时不自动退出历史;
- 正常整轮生成仍重建当前结果。
- 运行:
```bash
py -3.10 -m unittest tests.test_product_suite_gui
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
```
## 边界(不改什么)
- 不修改 SQLite schema,不新增 `retry_of_job_id` 或持久化轮次表。
- 不删除或覆盖原失败 job,不修改历史计费记录。
- 不修改 cmhub API、提交/轮询/下载协议和失败重试次数。
- 不修改套图提示词模板、分类数量、比例、原图管理或导出逻辑。
- 不修改 CDP、蝦皮主图拉取、①导入采集、②AI生成、③更新蝦皮、④账号管理或⑤设置。
## 执行记录
- 2026-07-16:`SuiteTaskState` 新增内存态 `generation_job_ids`、`generation_mode` 与 `generation_retry_job_id`。`generation_job_ids` 专门供当前活动请求的进度、停止、线程结束和终态看门狗使用;`current_job_ids` 只表示当前结果区的有效槽位,解决单张重试时旧成功 job 被错误纳入本次汇总的问题。未修改 SQLite schema。
- 2026-07-16:失败、过期或停止卡片的「重试」改为显式单张重试模式。worker 仍新建 job 保留历史和计费记录;新 job ID 返回后会原位替换当前结果区中的旧失败 job,其他成功图片保持不变。当前结果改按 `current_job_ids` 顺序展示;历史视图继续展示原失败与每次重试记录,从历史发起重试时不会强制退出历史视图。
- 2026-07-16:重试运行状态、停止确认、进度文案和完成弹窗改为「图片重试」语义;重试成功、失败、停止和未完整结束分别给中文反馈。正常整轮生成、停止、T-639 `run_token` 隔离与终态兜底保持原行为。
- 2026-07-16:补齐当前轮原位替换、历史记录保留、活动快照只统计新 job、重试再次失败仍可继续重试、历史视图保持和真实 QThread 完整退出测试。相关 42 项测试通过;在不包含用户未提交默认提示词改名的临时隔离 worktree 中,全量 535 项 unittest 通过,Ruff、`compileall` 与 `git diff --check` 均通过。