Files
yingqing/docs/bug_todo/图片生成-无法删除失败图片-bug-todo.md

157 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 图片生成无法删除失败图片 BUG TODO
> 最终状态:已完成(2026-07-16
>
> 补充修复:三种图片生成模式的单图删除、失败格重跑与页面回填已统一。重跑图会绑定其替代的失败格;删除该补图后,对应失败格不会在刷新或切换页面后复活。历史补图记录也按原始批次张数回显,不会出现“最多 4 张却显示 5 张”。
>
> 验证:后端重跑关联回归测试通过;前端 TypeScript 与生产构建通过;单图仍进入原有资产垃圾桶,整批删除与“图片异常批次”流程保持不变。
> 状态: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. 本次未点击彻底删除或清空垃圾桶,保留数据的可恢复性。