fix: 支持删除图片异常批次

This commit is contained in:
hh
2026-07-15 17:52:29 +08:00
parent ff0a93677a
commit 755e210951
9 changed files with 531 additions and 51 deletions
@@ -0,0 +1,150 @@
# 图片生成无法删除失败图片 BUG TODO
> 状态:Step 2 方案已确认,待进入实施
> 页面:图片生成入口 `/asset-factory` 所进入的模特上身图、平台套图、图片创作
> 协作规则:一次只执行一个 Step;在确认问题表现前,不排查、不修改代码。
## 1. 待确认的问题表现
用户提供的三个入口截图显示:
- 模特上身图、平台套图与图片创作都可在失败/生成批次的“更多”菜单中看到“删除当前批次”;
- 失败批次没有实际图片资产,页面只显示“这张生成失败”的占位格;
- 点击删除的目标是整个失败批次,而非某一张已生成的图片;预期应当让该失败批次不再显示,且刷新、切换商品或重新进入页面后也不应恢复。
## 2. 初步目标
让失败批次能够被可靠删除:删除后立即从当前页面消失,且后端不会再将该失败任务回放到工作台;正常成图删除、生成中任务、重跑与积分结算不受影响。
## 3. 后续步骤(待现象确认后执行)
### Step 1:只读定位删除链路
- 定位图片生成页面中失败图片的删除入口、可见条件和请求;
- 定位后端删除接口及其对失败任务、资产和批次状态的处理;
- 先给出根因假设与最小修复范围,不修改代码。
状态:已完成,未改业务代码。
排查结论:这是“删除资产”被错误地当作“删除批次/任务”的问题,失败批次没有资产,因此没有任何后端记录被删除。
1. 三个图片生成入口共用 `core/frontend/src/routes/ai-tools.tsx``ImageWorkbenchPage`;失败格由“批次数量大于成功资产数”在前端补出,并不对应一张 `Asset`
2. 当前确认删除逻辑只收集 `batch.results.map(a => a.id)` 后调用 `DELETE /api/assets/<asset-id>/`。纯失败批次的 `results` 为空,所以请求列表为空,**点击确认不会发送任何删除请求**;前端只是暂时把卡片从内存中移除。
3. 模特上身图/平台套图在挂载、切换商品时会请求 `GET /api/ai/tasks/workbench/`。该接口会继续返回失败的 `AITask`,前端将其还原为 `failed` 批次,因此删除后的失败卡会在刷新、切换商品或重新进入页面时恢复。
4. 图片创作使用对话任务恢复,但失败任务同样没有资产;当前删除路径同样不具备删除任务的能力。
关键位置:
| 环节 | 位置 | 证据 |
| --- | --- | --- |
| 失败格生成 | `core/frontend/src/routes/ai-tools.tsx` `renderBatchGrid` | 无成图且批次已终态时,渲染“这张生成失败”占位 |
| 当前删除动作 | 同文件 `doConfirmedDelete` | 只调用 `api.deleteAsset()`;失败批次 `results=[]`,不会请求接口 |
| 工作台恢复 | 同文件 `batchesFromWorkbenchTasks` 与恢复 effect | 失败任务被还原成失败批次 |
| 后端回放 | `core/backend/apps/ai/views.py` `AITaskViewSet.workbench` | 查询未排除已删除任务,且当前不存在工作台任务/批次删除接口 |
本 Step 的结论不涉及模型、生成请求、积分或视觉样式;尚未实施修复。
### Step 2:确认修复方案
- 明确要修复的状态与边界;
- 确认删除后页面、任务记录与批次卡应如何呈现;
- 经确认后再实施。
状态:已完成,已确认“失败/异常批次整批可恢复;全成功批次维持现状”,待实施。
#### 2.1 确认的行为边界
| 场景 | 修复后行为 |
| --- | --- |
| 纯失败批次(无成图资产) | 整批进入垃圾桶的“图片异常批次”分区;卡片立即消失,刷新/切换/重新进入后不恢复;可整批恢复 |
| 全部成功批次(有成图且无失败) | 保持当前行为:每张成图进入既有“资产”分区,按图片恢复;不创建批次垃圾桶记录 |
| 同一批含失败、成功或补图/重跑任务 | 视为异常批次:以真实 `batch_id` 为范围整体删除和整体恢复,不留下孤立失败格或补图;已成功图片不单独出现在“资产”分区 |
| 生成中、排队中、后处理中 | 不提供批次删除;不尝试取消供应商请求、不改任务状态、不触发额外退款逻辑 |
| 图片创作中的某一批 | 只删除该批任务,保留同一对话的其它批次与对话本身 |
| 单张成功图片删除 | 沿用现有资产删除与垃圾桶恢复逻辑;本次不改变 |
`compensating` 在前端当前被视为终态,但文案为“回滚中”;本次按安全边界把它与其它在途状态一样禁止删除,待积分回滚完成并转为 `failed``cancelled` 后,用户才可删除。
#### 2.2 确认的最小实现方案
1.`AITaskViewSet` 增加仅用于**异常图片批次**的“删除当前批次 / 批次垃圾桶 / 恢复批次 / 彻底删除批次”接口。前端只提交批次中的一个**锚点任务 ID**;后端从该任务自行确认团队、图片模式、是否属于独立工作台,并以其真实 `batch_id` 查出同批任务。旧数据没有 `batch_id` 时只处理该锚点任务。
2. 接口仅接受 `succeeded``failed``cancelled` 三种已结束状态;任何在途任务返回可见的拒绝结果,且不修改数据。
3. 后端先判断整批任务是否含 `failed``cancelled`:没有失败的全成功批次继续走现有逐张资产软删;含失败的异常批次才进入新的批次软删流程。这样全成功批次的现有删除与资产垃圾桶行为不变。
4. 异常批次的删除、恢复和彻底删除均在同一数据库事务中处理该批全部 `AITask` 与其已生成 `Asset`:删除标记 `is_deleted=True`,恢复统一复原,彻底删除再写入 `purged_at`。失败任务没有资产时,仍作为可恢复的批次记录存在。
5. 在现有全局垃圾桶新增“图片异常批次”分区,复用已有列表行、恢复、彻底删除、清空和全部恢复交互;不新建垃圾桶页面或视觉组件。批次行显示原提示词、模式、张数、删除时间;缩略图优先使用该批次所属商品的商品图(模特上身图、平台套图及关联商品的图片创作均适用),商品不存在或无可用商品图时回退现有“无图”占位。商品图只作展示引用,不复制、不删除、不影响商品本身。
6. 为避免异常批次出现两个恢复入口:该批任务处于删除状态时,其成功图片不单独列入“资产”分区;只有全成功批次、资产库删除或单图删除,才继续出现在“资产”分区。异常批次恢复会恢复该批的图片和任务记录;单图恢复维持现有语义。
7. `GET /api/ai/tasks/workbench/` 与图片创作对话的 `tasks` 读取接口均过滤 `is_deleted=False``purged_at is null`,防止删除的异常任务再次被回放。
8. 前端批次数据补存完整任务 ID 列表(当前恢复批次只保留卡片 ID,无法稳定定位后端锚点);点击“删除当前批次”后先等待接口成功,再从本地列表移除。请求失败时保留卡片,并复用应用既有通知提示失败原因。
9. “更多 → 删除当前批次”只在批次已结束时显示;现有确认弹窗文字改为“删除后可在垃圾桶恢复”。不新增弹窗、颜色、按钮或样式。
#### 2.3 用户操作变化
生成页面的主流程不变:仍是“更多 → 删除当前批次 → 确认删除”。用户只会在需要反悔时,多一个恢复路径:
```text
全局侧栏「垃圾桶」
→ 「图片异常批次」分区
→ 恢复(整批任务与图片回到原图片生成入口)
```
“全部恢复”和“清空垃圾桶”会包含这个异常批次分区,行为与商品、资产等现有分区一致;全成功批次则继续由“资产”分区按图片恢复。
#### 2.4 明确不在本次范围
- 不取消或中断供应商正在执行的图片生成;
- 不改变已预扣/已结算/失败补偿的积分逻辑;
- 不新增失败图片的单格删除按钮(失败格无可删除资产,删除语义仍是“删除当前批次”);
- 不删除图片创作的整条对话,也不改变对话垃圾桶;
- 不修改模型路由、提示词、生成结果、页面视觉或共享 CSS。
#### 2.5 实施验收与回归
1. 纯失败的模特上身图、平台套图、图片创作批次均可删除;刷新、切换商品/对话及重新进入页面后不再显示。
2. 一批中含成功图与失败图时,删除后整批不回显,并仅出现在“图片异常批次”分区;恢复后任务和全部图片一并恢复。
3. 含“重跑/补图”的同一 `batch_id` 会整体删除,不留下孤立失败格或历史补图。
4. 生成中、排队中、后处理中与回滚中的批次没有删除入口;任务继续按既有流程完成或失败。
5. 非当前团队、项目内任务、非图片生成任务不可通过新接口删除。
6. “全部恢复 / 清空垃圾桶”正确包含图片异常批次;异常批次删除的图片不会与资产分区重复出现;全成功批次仍仅出现在资产分区。
7. 图片异常批次优先展示所属商品图;未关联商品、商品已不可用或无商品图时稳定回退“无图”占位,不影响恢复和彻底删除。
8. 后端定向测试覆盖上述状态、团队隔离、删除→垃圾桶→恢复→工作台回放、彻底删除与重复隐藏;前端执行 TypeScript/生产构建检查。测试全部使用模拟任务,不发起真实模型调用或积分消耗。
### Step 3:实施与验证
- 按已确认方案完成最小改动;
- 验证失败图片可删除,正常图片与其他生成流程不受影响。
状态:已完成。
实施结果:
1. 图片工作台的“删除当前批次”现在提交批次锚点任务给后端统一判断。全成功批次继续按原有逻辑将成图逐张移入“资产”垃圾桶;含 `failed``cancelled` 任务的批次会作为一个可恢复的异常批次删除。
2. 新增“图片异常批次”的删除、恢复、彻底删除与垃圾桶读取接口。异常批次恢复时,任务记录、成功成图和失败卡会一起恢复;同一 `batch_id` 的补图/重跑任务也一起处理。删除的异常任务已从工作台和图片创作对话的历史读取中排除,刷新后不会复活。
3. 全局垃圾桶新增“图片异常批次”分区,复用现有列表行、恢复、彻底删除、全部恢复和清空垃圾桶交互;优先显示所属商品图,无法取得时沿用“无图”占位。异常批次中的成功图不会在“资产”分区重复显示;全成功批次保持原有资产分区行为。
4. 生成中、排队中、处理中或回滚中的批次由后端拒绝删除;前端只为已结束批次展示“删除当前批次”。单张图片删除、整条图片创作对话删除、模型调用、积分扣除与补偿逻辑未改动。
5. 页面未新增 CSS、颜色、圆角或组件;继续使用既有垃圾桶行、缩略图、按钮、Toast 与确认弹窗。确认弹窗文案已明确说明“删除后可在垃圾桶恢复”。
验证:
- 图片批次定向回归 `9 / 9` 通过:纯/部分失败批次删除、恢复、彻底删除、商品图缩略、全成功批次原行为、图片创作对话回放、在途拒删与团队隔离;
- AI 模块完整回归 `58 / 58` 通过;
- 前端 TypeScript 与生产构建通过;
- `git diff --check` 通过;
- 所有测试使用 SQLite 和模拟模型,不产生真实模型调用或积分消耗。
### Step 4:页面回归与交付检查
状态:已完成(自动化回归)。
- 已复查删除入口仅在批次结束后显示;`回滚中` 现与生成、排队、处理中一样视为在途状态,不显示删除入口,待最终转为 `failed``cancelled` 后才可删除。
- 已复查垃圾桶沿用既有列表行:异常批次使用商品图缩略图,无法取得时使用现有无图占位;不新增页面、Tab、组件或 CSS。
- 前端生产构建再次通过,`git diff --check` 通过。
- 未创建真实失败生成任务,未消耗积分;浏览器连接在本机运行时初始化时受限,实际页面点击验收留待开发环境中已有失败批次时按 Step 2.5 的清单执行。
#### 真实页面验收(2026-07-15
状态:通过。
1. 模特上身图中的 1 张失败批次可通过「更多 → 删除当前批次」进入删除流程;
2. 删除后,垃圾桶出现「图片异常批次 · 1」分区。该行展示关联商品图缩略图、原提示词、`模特上身图 · 1 张` 与删除时间;
3. 点击恢复后,失败批次重新出现在原商品的模特上身图工作台,仍显示失败格;
4. 本次未点击彻底删除或清空垃圾桶,保留数据的可恢复性。