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

12 KiB
Raw Blame History

id, title, status, phase, deps, created
id title status phase deps created
T-636 商品套图支持无商品ID临时草稿与正式绑定 DONE 7
T-622
T-635
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 并结束编辑,弹出中文确认:

    将当前临时草稿绑定到商品 <商品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 之前中止,并弹出:

    当前为临时项目,无法拉取蝦皮主图。
    请先输入正式商品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。

验证

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 全部通过。