feat(HEL-884): 找到素材占用位置并打通删除路径 #23

Manually merged
leefer merged 0 commits from feat/hel-884-locate-canvas-refs into leefer/hel-731-studio-project 2026-10-27 00:20:01 +08:00
Owner

闭环

知道在哪 → 直接定位 → 明确处理占用 → 确能完成删除。

变更

后端

  • GET /api/library/media/usage?path=&offset=(只读):逐条列出仍在使用该文件的画布卡片——画布可读名/ID、卡片标题/ID、所属项目或会话上下文、生成中状态、以及删除该卡片的真实后果(deleting_card_deletes_file + 否则给出 file_keep_reason)。纯 SQL 直读,不 seed 实体、不物化产物。引用多时分页(每页 50,带 valid_total)。
  • 孤儿引用:_path_on_canvas JOIN canvas——canvas 行已不存在的节点(FK 级联契约下的死元数据,任何入口不可达)不再计入占用、不再挡删除;usage 接口将其单列为 invalid(带总数、无链接、不静默清除)。线上历史孤儿数据是否清理单独报总管,不随部署批清。
  • DELETE /api/canvas/{id}/nodes/{id}?keep_file=true:独立、最小、原文件保全的"仅解除引用"操作。默认行为不变(最后一份引用且在素材库范围内时连文件一起删——UI 如实告知,不包装成无损)。
  • 生成中卡片删除保护:产物卡挂着的 job 仍在 pending/running 时 deleteNode 返回 409(jobs 表为权威,陈旧 node.status 不误伤),防止完成事件把删除的卡片复活。

前端

  • 素材库删除被保留(on_canvas)时自动弹出占用详情弹层:每个引用一张卡(画布「名」· 项目/会话「名」· 卡片 #ID),操作:定位 / 删除卡片…(确认文案准确说明文件会否一并删除)/ 仅移除卡片(确认说明原文件保留);处理完一个其余保留,最后一个解除后文件随卡删除或回到"现在可以删除"完成闭环;失效引用如实列出不计占用;大量引用可分页展开,不静默截断。
  • 定位:跳 /canvas/{id}?locate={node_id},卡片居中(按图数据+实测卡片盒直接算 viewport 写回——store.fitView 的 nodes 回写会与图重建 effect 成环)+ 金色高亮环 20s + "返回素材库"胶囊;后退回素材库保持原选择(sessionStorage 持久化)。
  • 7 语言 i18n 键;沿用既有 Modal/Button/主题样式,未新增依赖、未改主题/布局。

自测

  • 后端:新增 test_library_usage.py(跨画布多引用/孤儿不再占用且如实上报/只读不 seed/反斜杠路径/生成中标记/分页顺序/参数校验)+ test_canvas_delete.py 扩展(keep_file 原文件保全并回到可删、生成中 409→done 放行、jobs 权威覆盖陈旧状态、孤儿不算引用);全套 1077 过 / 8 失败均为 base 既有(同 base 实核),Ruff 通过。
  • 前端:svelte-check 0 错 0 警、lint(prettier+eslint)通过、test 通过、build 成功。
  • 端到端(e2e/run-hel884.py + e2e/hel884.cjs,隔离库+自建媒体+真实浏览器,32 项全过):孤儿直接可删、占用弹层双引用齐全、定位到正确画布正确卡片且视口外卡居中、返回保持选择、删一卡另一卡保留文件、最后引用随卡删文件、仅移除保全文件后弹层内完成删除、生成中无操作入口、取消不变、刷新后列表与画布均不复活。全程未调真实 LLM/GPU/收费服务;未触碰线上数据。

边界

不合并部署;线上历史孤儿数据不动(如需修数据另报);项目引用与别处有效引用保护保持;路径比较沿用既有分隔符规范化,未放宽边界/权限。

## 闭环 知道在哪 → 直接定位 → 明确处理占用 → 确能完成删除。 ## 变更 ### 后端 - **`GET /api/library/media/usage?path=&offset=`(只读)**:逐条列出仍在使用该文件的画布卡片——画布可读名/ID、卡片标题/ID、所属项目或会话上下文、生成中状态、以及删除该卡片的真实后果(`deleting_card_deletes_file` + 否则给出 `file_keep_reason`)。纯 SQL 直读,不 seed 实体、不物化产物。引用多时分页(每页 50,带 `valid_total`)。 - **孤儿引用**:`_path_on_canvas` JOIN canvas——canvas 行已不存在的节点(FK 级联契约下的死元数据,任何入口不可达)不再计入占用、不再挡删除;usage 接口将其单列为 `invalid`(带总数、无链接、不静默清除)。线上历史孤儿数据是否清理单独报总管,不随部署批清。 - **`DELETE /api/canvas/{id}/nodes/{id}?keep_file=true`**:独立、最小、原文件保全的"仅解除引用"操作。默认行为不变(最后一份引用且在素材库范围内时连文件一起删——UI 如实告知,不包装成无损)。 - **生成中卡片删除保护**:产物卡挂着的 job 仍在 pending/running 时 deleteNode 返回 409(jobs 表为权威,陈旧 node.status 不误伤),防止完成事件把删除的卡片复活。 ### 前端 - 素材库删除被保留(on_canvas)时自动弹出**占用详情**弹层:每个引用一张卡(画布「名」· 项目/会话「名」· 卡片 #ID),操作:**定位** / **删除卡片…**(确认文案准确说明文件会否一并删除)/ **仅移除卡片**(确认说明原文件保留);处理完一个其余保留,最后一个解除后文件随卡删除或回到"现在可以删除"完成闭环;失效引用如实列出不计占用;大量引用可分页展开,不静默截断。 - **定位**:跳 `/canvas/{id}?locate={node_id}`,卡片居中(按图数据+实测卡片盒直接算 viewport 写回——store.fitView 的 nodes 回写会与图重建 effect 成环)+ 金色高亮环 20s + "返回素材库"胶囊;后退回素材库**保持原选择**(sessionStorage 持久化)。 - 7 语言 i18n 键;沿用既有 Modal/Button/主题样式,未新增依赖、未改主题/布局。 ## 自测 - 后端:新增 `test_library_usage.py`(跨画布多引用/孤儿不再占用且如实上报/只读不 seed/反斜杠路径/生成中标记/分页顺序/参数校验)+ `test_canvas_delete.py` 扩展(keep_file 原文件保全并回到可删、生成中 409→done 放行、jobs 权威覆盖陈旧状态、孤儿不算引用);全套 1077 过 / 8 失败均为 base 既有(同 base 实核),Ruff 通过。 - 前端:svelte-check 0 错 0 警、lint(prettier+eslint)通过、test 通过、build 成功。 - 端到端(`e2e/run-hel884.py` + `e2e/hel884.cjs`,隔离库+自建媒体+真实浏览器,32 项全过):孤儿直接可删、占用弹层双引用齐全、定位到正确画布正确卡片且视口外卡居中、返回保持选择、删一卡另一卡保留文件、最后引用随卡删文件、仅移除保全文件后弹层内完成删除、生成中无操作入口、取消不变、刷新后列表与画布均不复活。全程未调真实 LLM/GPU/收费服务;未触碰线上数据。 ## 边界 不合并部署;线上历史孤儿数据不动(如需修数据另报);项目引用与别处有效引用保护保持;路径比较沿用既有分隔符规范化,未放宽边界/权限。
leefer added 1 commit 2026-10-26 23:49:15 +08:00
- GET /api/library/media/usage: read-only occupancy report per file —
  every live canvas card with canvas title, project/session scope, and
  the real consequence of deleting that card (deletes_file / keep reason)
- _path_on_canvas joins canvas: orphan nodes (canvas row gone, FK-cascade
  dead metadata) no longer count as occupancy; they are reported as
  invalid references with totals instead of blocking deletion
- canvas deleteNode: keep_file=true release-only variant (card removed,
  file always preserved); artifact cards with a live pending/running job
  refuse deletion (409) so completion events cannot resurrect them
- library UI: occupancy modal on kept(on_canvas) with locate / delete
  card (accurate impact text) / release-only actions, per-path free-file
  delete closing the loop, invalid-reference note, offset paging for
  large counts, library selection persisted across canvas round-trips
- canvas UI: ?locate=<node> deep-link centers the card (computed
  viewport, no store.fitView — its nodes write-back looped the graph
  rebuild effect), gold ring highlight + dismiss, back-to-library chip
- i18n keys for all 7 languages; e2e runner + 32-check real-browser
  regression on isolated data

Co-authored-by: multica-agent <github@multica.ai>
leefer manually merged commit 679969bc5b into leefer/hel-731-studio-project 2026-10-27 00:20:01 +08:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: leefer/aigc-home#23