Files
cmshoppe/docs/tasks/T-636.md
T

171 lines
12 KiB
Markdown
Raw 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.
---
id: T-636
title: 商品套图支持无商品ID临时草稿与正式绑定
status: DONE
phase: 7
deps: [T-622, T-635]
created: 2026-07-16
---
# T-636 商品套图支持无商品ID临时草稿与正式绑定
## 问题 / 背景
⑥「商品套图」当前要求先选择账号并输入纯数字商品 ID,才能创建 `image_studio_projects` 项目并导入本地图片。用户点击「添加图片」选完文件后,`_start_import()` 才尝试绑定项目;商品 ID 为空时会中止导入。实际运营中经常先取得商品素材、后确认蝦皮商品 ID,这个限制会阻断本地选图和生图准备。
需求是:未输入正式商品 ID 时也能选择、拖入或粘贴图片;程序使用内部随机英文标识保存为临时草稿。输入正式数字商品 ID 后,草稿应原地绑定,已经添加的图片、提示词、生成记录和选择结果全部保留。临时草稿不能拉取蝦皮主图。
不能只把随机字符串直接当作普通 `item_id` 使用:当前项目唯一键是 `账号 + 商品ID`,图片目录也包含 `item_id`。若直接把临时 ID 改成正式 ID,后续文件会写入新目录,旧图片仍留在临时目录;当前 GUI 切换商品 ID 还会直接清空项目绑定,造成用户看不到刚添加的图片。
## 目标
1. 账号可用但商品 ID 为空时,用户可以正常添加本地图片并建立可持久化的临时草稿项目。
2. 内部临时 ID 与正式纯数字商品 ID 明确区分,但内部值不得显示给用户、传给模型或写入用户日志。
3. 草稿绑定正式商品 ID 时保持同一个 `project_id`,不丢失图片、设置、任务、生成结果和选择记录。
4. 临时草稿允许本地图片管理、AI 帮写和套图生成,但禁止拉取蝦皮主图。
5. 临时草稿关闭或程序重启后仍可恢复,不产生无法访问的孤立项目。
## 实现方案
### 1. 显式草稿状态和稳定存储键
- 为 `image_studio_projects` 增加独立绑定状态,例如 `binding_state`:
- `draft`:未绑定正式商品;
- `bound`:已绑定正式数字商品 ID。
- 增加稳定的 `storage_key`。项目图片目录改为优先使用 `storage_key`,仅对迁移前或异常旧数据回退到 `item_id`。
- 数据库迁移必须幂等:
- 既有项目统一回填为 `binding_state = 'bound'`;
- 既有项目的 `storage_key` 回填为原 `item_id`,保证现有图片目录完全不变;
- 不修改、搬动或重新编码既有图片文件。
- 临时项目内部 `item_id` 使用保留前缀加 UUID,例如 `draft_<uuid4 hex>`;同时依靠 `binding_state` 判断草稿,不允许业务代码仅用“是否为数字”猜测项目状态。
- `item_id` 继续保持非空及现有 `UNIQUE(account_alias, item_id)` 约束;正式 ID 仍沿用当前纯数字校验,不在本任务中增加未经验证的长度规则。
- 在 `app/image_studio.py` 集中提供草稿创建、草稿判断和正式绑定服务,GUI 不直接拼接随机 ID 或执行 SQL。
### 2. 无商品 ID 添加本地图片
- 仍要求选择一个有效账号;本任务只放宽商品 ID,不创建无账号项目。
- 点击「添加图片」时,即使商品 ID 为空也应先打开文件选择窗口:
- 用户取消选择时不创建草稿;
- 用户选中有效文件后才创建草稿并导入;
- 当前任务已经绑定草稿时继续复用同一个草稿,不重复创建项目。
- 拖入文件和粘贴剪贴板图片使用相同规则,在收到有效图片载荷后按需创建草稿。
- 如果首次导入的文件全部无效或导入全部失败,不保留没有资产、没有任务的空草稿;回滚新建项目或按现有安全语义软删除。
- 保留 T-635 的 16 张上限、重复图片判断、后台导入、真实数量、勾选、排序、预览和删除行为。
- GUI 状态必须分离:商品 ID 输入框保存用户输入的正式 ID;内部 `draft_<uuid>` 只存在于项目服务和数据库,不得回填到输入框。
### 3. 商品 ID 提示
- 在「添加图片」按钮右侧增加状态 label。
- 商品 ID 为空、当前项目为草稿或输入格式不正确时显示 `请输入正确的商品ID`。
- 使用警示语义色,不使用失败红;这是允许继续添加和生成的待完善状态,不是运行失败。
- 正式绑定成功后隐藏该提示,避免长期占用工具栏空间;tooltip 可显示当前已绑定的正式商品 ID。
- 在 `1180x760` 和项目支持的最小窗口尺寸下,提示文字不得挤压账号、商品 ID 和「拉取蝦皮主图」控件;必要时设置合理的最小宽度和收缩优先级。
### 4. 草稿绑定正式商品
- 当前任务已有草稿时,用户输入合法数字 ID 并结束编辑,弹出中文确认:
```text
将当前临时草稿绑定到商品 <商品ID> 吗?
已添加图片和生成记录会继续保留。
```
- 确认后调用统一服务原地绑定:
- `project_id`、`storage_key`、资产、job、selection、提示词和套图设置保持不变;
- 只更新项目正式 `item_id`、`binding_state` 和更新时间;
- 因 `storage_key` 不变,不移动目录,也不批量改写资产 `local_path`。
- 绑定必须在事务中先检查同账号是否已经存在目标正式项目,包括可恢复的软删除项目。
- 目标正式项目已经存在时不得覆盖、自动合并或猜测用户意图;保持当前草稿不变,并提示该商品项目已存在。第一版不实现跨项目图片和生成历史合并。
- 用户取消确认、输入非数字 ID 或绑定失败时,草稿和所有本地数据保持原样。
- 当前正式项目切换到另一个正式商品 ID 的既有行为不借本任务改变;只有 `draft -> bound` 走原地绑定语义。
### 5. 拉取与生成边界
- 临时草稿可以执行:添加/删除/排序/预览本地图片、编辑提示词、AI 帮写、生成套图、查看历史结果和打开结果文件夹。
- 草稿执行 AI 帮写或生图时,提示词上下文使用 `未绑定商品` 或省略商品 ID;绝不能把内部 `draft_<uuid>` 发送给 cmhub。
- 点击「拉取蝦皮主图」时必须同时满足项目已经 `bound` 且商品 ID 为纯数字。
- 草稿状态点击拉取时,在创建 worker、启动 Chrome 或调用 CDP 之前中止,并弹出:
```text
当前为临时项目,无法拉取蝦皮主图。
请先输入正式商品ID后再试。
```
- 拉取按钮可保持可点击以解释原因,但 tooltip 必须预先说明 `需要先绑定正式商品ID`;不得产生 Chrome、网络、数据库拉取副作用。
- 本任务不修改③「更新蝦皮」的高风险边界,也不允许临时 ID 进入更新任务。
### 6. 草稿生命周期与恢复
- 草稿项目持久化在 SQLite,不使用仅存在于内存的临时对象或系统临时目录。
- 程序启动时恢复仍为 `draft`、未软删除且至少包含一项资产或生成任务的草稿,每个草稿恢复为独立套图任务标签;商品 ID 输入框保持空白,标签使用中文草稿名称,不显示内部 ID。
- 多个草稿按最近更新时间排序,不能把不同草稿自动合并到同一任务。
- 关闭未绑定草稿标签时提供三个明确选择:`保留草稿`、`删除草稿`、`取消`:
- 保留草稿:只关闭当前标签,下次启动恢复;
- 删除草稿:沿用项目软删除,不物理删除图片目录;
- 取消:不关闭标签。
- 空草稿不恢复;本任务不得在每次点击、取消文件选择或普通界面刷新时产生空项目。
### 7. 日志与错误边界
- 用户日志只记录 `临时草稿`、账号显示名、正式商品 ID 和操作结果,不输出内部临时 ID、绝对目录、数据库 SQL 或接口路径。
- 数据库迁移、草稿创建、正式绑定或恢复失败时使用中文错误外壳;原草稿数据不能因失败被清空。
- 正式绑定服务需要可重复调用:已经绑定到同一正式 ID 时返回当前项目,不重复更新;绑定到不同 ID 时按冲突规则拒绝。
## 验收标准
- [ ] 已选择账号、商品 ID 为空时,点击「添加图片」可以选择文件;取消选择不创建项目。
- [ ] 选中有效图片后自动创建草稿并成功显示缩略图,商品 ID 输入框仍为空,内部临时 ID 不可见。
- [ ] 空列表拖入或粘贴图片同样可以创建/复用草稿;16 张上限和后台导入状态正常。
- [ ] 首次导入全部失败时不留下空草稿。
- [ ] 「添加图片」右侧在空值、草稿和非法输入状态显示 `请输入正确的商品ID`,正式绑定后隐藏。
- [ ] 草稿可以完成 AI 帮写和套图生成,cmhub 请求及用户日志中不包含 `draft_` 临时 ID。
- [ ] 草稿点击「拉取蝦皮主图」显示指定中文提示,不创建 worker、不启动 Chrome、不执行 CDP。
- [ ] 草稿绑定不存在冲突的正式数字 ID 后,`project_id`、`storage_key`、原图、生成记录、选择结果、提示词及设置全部保持不变。
- [ ] 绑定后继续添加或生成的文件仍写入同一稳定项目目录;旧图片路径有效,打开结果文件夹可以访问完整结果。
- [ ] 同账号目标正式项目已存在时绑定被安全拒绝,正式项目和草稿都不被覆盖或合并。
- [ ] 取消绑定、非法 ID、数据库失败时草稿内容保持原样。
- [ ] 保留的非空草稿在程序重启后恢复为独立任务;关闭时的保留、软删除和取消语义正确。
- [ ] 既有正式项目迁移后目录、图片、项目查询、拉取和生成行为不变。
- [ ] ③「更新蝦皮」、cmhub 协议和 CDP 选择器不受影响。
## 测试要求
- `tests/test_image_studio.py`:覆盖 schema 幂等迁移、既有项目回填、草稿 ID 唯一性、显式状态、稳定存储键、原地绑定、重复调用和正式项目冲突。
- `tests/test_image_studio_images.py`:覆盖草稿导入目录、绑定前后目录不变、既有项目路径兼容和首次导入全失败清理。
- `tests/test_product_suite_gui.py`:覆盖无 ID 文件选择/取消、拖放、粘贴、状态 label、草稿恢复、关闭三选项、绑定确认和绑定失败保持状态。
- worker/GUI 测试覆盖草稿拉取提前中止,无 Chrome/CDP/worker 副作用;覆盖草稿生图 prompt 不包含内部 ID。
- 保留 T-622、T-631、T-635 的任务切换、图片管理、生成和布局回归。
## 文档同步
- 实现时同步更新 `docs/04-architecture.md` 中 `image_studio_projects` schema、项目唯一性、目录键和草稿/正式绑定边界。
- 若新增配置字段或启动恢复行为,同步更新对应配置说明;不得修改冻结的 `docs/06-tasks.md`。
## 验证
```bash
py -3.10 -m unittest tests.test_image_studio
py -3.10 -m unittest tests.test_image_studio_images
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
```
## 非目标
- 不允许没有账号的匿名草稿。
- 不合并两个项目的图片、job、selection 或生成历史。
- 不在线验证用户输入的数字商品 ID 是否真实存在;真实存在性继续由拉取流程确认。
- 不修改 cmhub API、图片生成模型、计费、并发、超时或下载协议。
- 不修改③「更新蝦皮」的提交、确认、上传、拖拽和结果判定逻辑。
- 不物理删除用户图片目录。
## 执行记录
- 2026-07-16:`image_studio_projects` 增加并迁移 `storage_key`、`binding_state`;实现临时草稿创建、原地正式绑定、冲突检查、空草稿软删除和启动恢复查询。既有正式项目回填为 `bound`,图片目录稳定使用 `storage_key`。
- 2026-07-16:⑥商品套图支持无商品 ID 的本地图片导入、AI 帮写和生成;临时草稿不会把内部 `draft_` 标识传入 cmhub 上下文。草稿拉取蝦皮主图在 worker、Chrome/CDP 前阻断;输入数字 ID 后经确认原地绑定,关闭非空草稿支持保留、软删除或取消。
- 2026-07-16:补充数据迁移、目录稳定性、导入失败清理、提示词脱敏、草稿恢复和 GUI 绑定/拉取边界测试;在 `1180x760` 进行离屏布局检查,无控件重叠。
- 验证(隔离工作树):`py -3.10 -m unittest discover -s tests`(507 通过)、`py -3.10 -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`git diff --check` 全部通过。