feat(product-suite): persist current generation rounds

This commit is contained in:
chengma
2026-07-16 23:27:25 +08:00
parent c146c0b41d
commit f3defdeb95
11 changed files with 806 additions and 16 deletions
+1
View File
@@ -442,6 +442,7 @@ data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新
- 启动时恢复未软删除、至少含一条资产或生成任务的草稿为独立中文“临时草稿”标签,按最近更新时间排序。关闭非空草稿可选择保留、软删除或取消;软删除不物理删除图片目录。③「更新蝦皮」只处理正式任务,不接受临时草稿。
- 第六 Tab 的多个 `SuiteTaskState` 各自保留 generation/pull/import/AI/download worker 与线程引用;切换任务不取消任务。多个任务可并行,但 `image_studio_generation` 使用进程级 semaphore 保证所有套图任务合计最多5个 cmhub 在途 job。线程还在运行时关闭任务只请求协作式停止,模块级引用保留到 `QThread.finished`,不得提前销毁线程对象;下载前后均检查停止信号,停止后的临时文件不入资产库。
- T-639 后每轮套图生成使用仅存在内存的 `run_token` 隔离迟到信号,progress/finished/cancelled/failed 通过主线程绑定槽统一处理;正常 worker 结果、`QThread.finished` 和本轮 job 连续两次全部终态看门狗共同进入幂等 finalize。GUI 只按本轮明确 `job_ids` 判断完成,不用历史图片数量;即使最终信号丢失也会恢复按钮,旧线程引用仍保留到真实结束。停止为协作式:调度循环约每200ms检查标记并取消未开始 future,提交/轮询在有界请求返回后停止;requests 在流式数据块边界取消,Windows curl 由隐藏窗口 `Popen` 有界 terminate/kill。已有 `task_id` 的停止任务保留 resume,不假设服务端任务被取消或点数退回。
- T-643 后 `image_studio_projects.current_generation_round_key` 是⑥主结果区的持久化当前轮次;每次常规「生成套图」创建独立 UUID `generation_round_key`,并为每条 `image_studio_jobs` 写入稳定的 `generation_slot_index`。内存 `run_token` 仍只用于线程和迟到信号隔离,不承担业务轮次语义。只有一轮中至少一条 job 成功时,才在同一 SQLite 事务内将该轮提升为当前轮;全失败或全取消保留原当前轮,部分成功则保留成功、失败槽位供后续重试。单张重试新建 job 但继承原轮次和槽位,当前展示按同轮同槽位最新 job、槽位顺序读取;所有尝试、错误、计费和输出资产均保留。旧 job 的轮次/槽位保持 NULL,查询层统一作为“旧版历史记录”,不以时间、文件名或数量猜测轮次;下一次成功的新格式整轮才建立当前轮。
提示词管理:
+1
View File
@@ -409,6 +409,7 @@ export_project_selection(project_id, parent_dir, existing_mode="fail", path=None
- 项目唯一键为账号别名 + 商品 ID;图片文件默认在 `data/images/pool/<slug>/<item_id>/` 下分 `originals/generated/exports`,删除生成图进入项目内 `.trash` 并可撤销。
- `image_studio_projects.suite_settings_json` 保存平台/国家/语言/比例/逐图主图/分类数量;`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 并按“旧版历史记录”兼容,不猜测轮次边界。
- 拉取蝦皮原主图只读:复用 `editor.open_product(..., bring_to_front=False)` 和 `editor.read_product_image_urls()`,不上传、不拖拽、不点击更新。
- 原图下载走 `image_studio_images` 的公网 URL、大小、Content-Type、重定向和 PIL 解码校验;只在用户单击时落盘。
- `remove_original_assets_if_unused()` 会先校验整批原图的项目归属、资产类型及 job/终选引用,再在单个事务中删除资产行并连续重排 `source_order`;任一图片不可删除时整批不变,本地源文件和蝦皮线上图片始终保留。
+1 -1
View File
@@ -205,7 +205,7 @@
- 「拉取蝦皮主图」复用只读 CDP,读取 URL 后由最多2个下载 worker 后台落盘;不改标题/封面、不拖拽、不点击更新。拉取、下载期间其余界面和其他任务仍可操作。
- 套图只有一个图片类型,不再展示详情图、终选盘或模板 CRUD。默认分类为白底图1、场景图2、卖点图2;自定义分类名称非空、无空格、最多10字且不可重名。逐图主图开启后,白底图只生成一次,其余分类按每张有效原图展开。
- 平台、国家地区、语言和比例以四个带独立标签的同行下拉展示,选项只显示真实值;四项都写进每个 job 的完整提示词,比例还透传到 cmhub 生图请求,不是装饰字段。已有项目保存自己的完整设置;未绑定商品的新任务在重启后采用 `config.json` 的最近四项选择。生成仍走 `image_studio_generation.run_jobs()` 的 submit → poll → download 管线。
- 生成按钮按当前总数显示并在运行时切换为「停止生成」;确认停止后显示「正在停止...」,重复点击不再弹确认框。每轮生成用独立运行标识隔离旧信号,本轮全部 job 终态或线程结束时都会统一恢复按钮;最终 worker 信号缺失时由数据库终态看门狗兜底,不要求用户重启。停止会取消未开始任务,已提交任务停止本地等待并保留后续继续查询语义;客户端不承诺取消服务端任务或退回点数。结果区显示本轮或历史 job;成功图可预览、复制路径、打开目录、重新生成、移入项目废纸篓并撤销,失败卡显示脱敏中文摘要与重试入口。
- 生成按钮按当前总数显示并在运行时切换为「停止生成」;确认停止后显示「正在停止...」,重复点击不再弹确认框。每轮生成用独立运行标识隔离旧信号,本轮全部 job 终态或线程结束时都会统一恢复按钮;最终 worker 信号缺失时由数据库终态看门狗兜底,不要求用户重启。停止会取消未开始任务,已提交任务停止本地等待并保留后续继续查询语义;客户端不承诺取消服务端任务或退回点数。T-643 后项目持久化当前生成轮次:常规新轮至少成功一张才替换主结果区,全部失败/取消保留上一当前轮;主结果按稳定槽位显示同轮最新 job,单张重试留在原槽位。旧版无轮次 job 临时显示为“旧版历史记录”,不按时间或图片数量猜测归属。成功图可预览、复制路径、打开目录、重新生成、移入项目废纸篓并撤销,失败卡显示脱敏中文摘要与重试入口。
- AI帮写和生图按任务独立运行。AI帮写期间若用户改过卖点,返回后必须确认才覆盖;全部用户可见错误隐藏 URL/接口路径和敏感信息。
- ⑥只管理本地图片资产,不自动上传或修改蝦皮;③线上更新边界不受影响。旧 `ImageStudioTab` 留作代码兼容但不再作为主窗口入口。
+14 -11
View File
@@ -3,7 +3,7 @@ id: T-643
title: 商品套图持久化当前生成轮次
phase: 7
deps: [T-642]
status: TODO
status: DONE
created: 2026-07-16
---
@@ -92,15 +92,15 @@ T-640 的单张失败重试继续新建 job,但必须:
## 验收要点
- [ ] 商品完成一轮生成后,关闭软件并重新打开相同店铺和商品 ID,主结果区恢复同一轮、同一顺序的图片。
- [ ] 再生成一整轮后,主界面只展示新当前轮,旧轮 job、图片、错误和计费记录仍保留。
- [ ] 新轮全部失败或全部取消时,原当前轮继续显示;失败轮可以被历史查询读取。
- [ ] 新轮部分成功时切换为当前轮,成功和失败槽位均可见,失败槽位可继续单张重试。
- [ ] 单张失败重试沿用原轮次和槽位,其他当前图片不消失、不换序。
- [ ] 程序重启后重试结果仍占据原槽位,原失败 job 仍存在。
- [ ] 旧数据库可以自动迁移;旧 job 不被猜测性拆分、删除或覆盖。
- [ ] 当前轮和历史轮查询不会跨店铺、跨商品或跨项目串数据。
- [ ] 不影响生成停止、迟到信号隔离、恢复未完成 cmhub 任务和本地图片保存。
- [x] 商品完成一轮生成后,关闭软件并重新打开相同店铺和商品 ID,主结果区恢复同一轮、同一顺序的图片。
- [x] 再生成一整轮后,主界面只展示新当前轮,旧轮 job、图片、错误和计费记录仍保留。
- [x] 新轮全部失败或全部取消时,原当前轮继续显示;失败轮可以被历史查询读取。
- [x] 新轮部分成功时切换为当前轮,成功和失败槽位均可见,失败槽位可继续单张重试。
- [x] 单张失败重试沿用原轮次和槽位,其他当前图片不消失、不换序。
- [x] 程序重启后重试结果仍占据原槽位,原失败 job 仍存在。
- [x] 旧数据库可以自动迁移;旧 job 不被猜测性拆分、删除或覆盖。
- [x] 当前轮和历史轮查询不会跨店铺、跨商品或跨项目串数据。
- [x] 不影响生成停止、迟到信号隔离、恢复未完成 cmhub 任务和本地图片保存。
## 测试
@@ -138,4 +138,7 @@ git diff --check
## 执行记录
- 待执行。
- 2026-07-16:为 `image_studio_projects` 增加 `current_generation_round_key`,为 `image_studio_jobs` 增加 `generation_round_key`、`generation_slot_index` 及组合索引;旧数据库原位补列,既有 job 保持 NULL 作为旧版历史记录,不猜测轮次。
- 2026-07-16:新增轮次当前值、原子成功提升、轮次摘要、当前槽位和完整尝试查询 API;常规生成写入 UUID 轮次和稳定槽位,单张重试继承原轮次和槽位。⑥重新绑定或重启后从持久化当前轮恢复结果;全失败/全取消恢复原当前轮,部分成功切换新轮。
- 2026-07-16:生成轮次的 GUI 测试改为在 GUI 事件循环中等待 `QThread` 实际退出,避免在主线程直接阻塞 `thread.quit()` 的排队事件;连续运行 5 次均通过。
- 验证:`py -3.10 -m unittest tests.test_image_studio tests.test_workers tests.test_product_suite_gui`(75 项通过);`py -3.10 -m unittest discover -s tests`(554 项通过);`py -3.10 -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`git diff --check` 均通过。