From 717ed2e35f331bd17c9d6c23b549b5d89bd8bca9 Mon Sep 17 00:00:00 2001 From: chengma Date: Thu, 16 Jul 2026 18:30:49 +0800 Subject: [PATCH] docs: add product suite context and pull tasks --- docs/tasks/T-641.md | 132 +++++++++++++++++++++++++++++++++++++ docs/tasks/T-642.md | 154 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 docs/tasks/T-641.md create mode 100644 docs/tasks/T-642.md diff --git a/docs/tasks/T-641.md b/docs/tasks/T-641.md new file mode 100644 index 0000000..8d8e969 --- /dev/null +++ b/docs/tasks/T-641.md @@ -0,0 +1,132 @@ +--- +id: T-641 +title: 商品套图最近上下文恢复与执行前确认 +phase: 7 +deps: [T-640] +status: TODO +created: 2026-07-16 +--- + +## 问题 / 背景 + +⑥「商品套图」当前存在三类容易造成误操作或内容丢失的体验问题: + +1. 软件重启后不会恢复最后选择的店铺。`product_suite.last_settings` 目前只保存平台、站点、语言和比例;新建首个任务会回退到账号列表第一项,用户可能在未注意店铺变化时拉取错误商品。 +2. 「商品卖点与要求」只有在已经建立正式项目或临时草稿、取得 `project_id` 后才会自动保存到 SQLite。用户只选择店铺并输入卖点、尚未添加图片或拉取主图时,内容仍只在内存中,关闭软件后会丢失。 +3. 「拉取蝦皮主图」只有本地已经存在图片时才弹覆盖确认;首次拉取会直接启动。「生成套图」也只有数量超过阈值时才确认。两个操作分别涉及店铺/商品上下文和 cmhub 点数消耗,缺少稳定的执行前复核。 + +本任务负责最近上下文、未完成草稿和执行前确认;拉取过程中停止、迟到结果隔离及清理本轮图片由 T-642 单独处理。 + +## 方案 + +### 1. 持久化最后选择的店铺 + +修改 `app/appconfig.py`、`app/gui/tabs/product_suite.py` 和 `docs/04-architecture.md`: + +- 在 `config.json` 的 `product_suite` 下新增 `last_account_alias`,只保存账号别名,不保存账号密码、Cookie 或登录态。 +- 用户完成有效店铺切换后立即保存该别名;被弹窗取消或因项目绑定规则拒绝的切换不得覆盖最近值。 +- 软件启动后的第一个新任务优先选择 `last_account_alias`。 +- 同一次运行中新建任务继续优先继承当前任务的店铺,保持现有工作流。 +- 最近账号已删除、被软删除或不在当前账号列表时,回退到第一个可用账号,并将无效值清理或覆盖。 +- 已绑定项目、恢复的临时草稿仍以项目自己的 `account_alias` 为准,不能被全局最近值覆盖。 + +### 2. 将未绑定卖点保存为可恢复草稿 + +卖点是商品级业务内容,不能作为全局默认文本复制给其他商品。修改 `app/gui/tabs/product_suite.py`,沿用现有 `image_studio_projects.draft_prompt`: + +- 当前任务尚无 `project_id` 时,用户选择了有效店铺且输入的卖点 `strip()` 后非空,在文本稳定约 `800ms` 后创建一次临时草稿并保存 `draft_prompt`。 +- 不因第一个字符立即创建项目;连续输入只创建一个草稿、只在防抖稳定后落库。 +- 切换任务、关闭任务、关闭软件、开始拉取、添加图片或开始生成前,必须同步刷新卖点;如内容非空且尚无项目,创建草稿并保存,避免用户在防抖到期前关闭造成丢失。 +- 空白卖点、无有效店铺或程序化回填不得创建草稿。 +- 自动创建的草稿沿用现有恢复机制,重启后恢复为「临时草稿」任务,并显示原店铺与卖点。 +- 用户把卖点清空且该草稿没有原图、job、selection 或其他业务内容时,关闭任务可继续使用现有空草稿清理语义,避免积累无内容项目。 +- 新建另一个普通任务时卖点默认留空;只有用户明确使用现有“继承当前任务”行为时才允许同一次运行内继承,不能从 `config.json` 全局复制上一个商品卖点。 + +### 3. 拉取蝦皮主图始终显示上下文确认 + +修改 `pull_main_images()`: + +- 完成店铺、商品 ID、项目绑定和运行状态校验后,无论本地是否已有图片,都必须先弹确认框。 +- 确认内容至少包含: + - 店铺显示名与别名; + - 商品 ID; + - 当前可用商品原图数量; + - 操作说明「读取蝦皮主图并在后台下载,不会修改蝦皮线上商品」。 +- 当前已有图片时,额外说明蝦皮原图列表会刷新,本地手动添加图片会保留。 +- 按钮使用中文「确认拉取」「取消」;用户取消时不得创建 worker、启动 Chrome、打开商品页或修改数据库。 +- 不显示账号密码、Cookie、完整本地路径或其他敏感信息。 + +T-642 完成后,运行中的同一按钮会切换为停止入口;本任务只负责空闲状态下的执行前确认。 + +### 4. 生成套图始终显示成本与参数确认 + +修改正常整轮 `start_generation()` 流程: + +- 在完成模板校验、项目/原图/卖点校验并构造准确 `specs` 后,创建 job 和启动 worker 前始终弹确认框,不再只对超过数量阈值的任务确认。 +- 确认内容至少包含: + - 店铺显示名与别名; + - 商品 ID;临时草稿显示「未绑定商品」; + - 可用商品原图数量; + - 「每张上传图分别作为主图生成」是/否; + - 白底图、场景图、卖点图及自定义分类的生成数量; + - 图片比例与最终生成总数; + - 「本次生成会消耗 cmhub 点数」提示。 +- 提示词正文不在确认框完整展示,避免超长文本和敏感业务内容占满弹窗;可显示「商品卖点已填写」或截断摘要。 +- 按钮使用中文「确认生成」「返回修改」。 +- 用户返回修改时不得创建 job、提交 cmhub 或产生点数消耗。 +- T-640 的失败卡片单张「重试」不弹完整套图确认框,继续使用独立重试语义;如后续需要重试扣点确认,应另设简短确认,不复用整轮参数弹窗。 + +### 5. 配置与文档兼容 + +- `product_suite.last_settings` 继续只负责平台、站点、语言和比例。 +- `last_account_alias` 与 `last_settings` 同级保存,不把商品 ID、卖点、图片路径或密钥写入 `config.json`。 +- 旧配置缺少 `last_account_alias` 时保持兼容,不影响启动。 +- 同步更新 `docs/04-architecture.md` 中 `product_suite` 配置 schema、最近上下文优先级和草稿保存事实。 + +## 验收要点 + +- [ ] 选择店铺后关闭并重启软件,首个新任务恢复最后有效店铺。 +- [ ] 最近店铺已删除时自动回退,不出现无效下拉项或启动异常。 +- [ ] 已绑定项目和恢复草稿始终显示项目自己的店铺,不被最近店铺覆盖。 +- [ ] 未添加图片、未输入商品 ID 时,仅输入非空卖点并关闭软件,重启后可在恢复的临时草稿中看到原卖点。 +- [ ] 空白输入和程序化回填不会创建临时草稿;同一次连续输入只创建一个草稿。 +- [ ] 新建无关商品任务不会自动带入上一个商品卖点。 +- [ ] 首次点击「拉取蝦皮主图」也会显示店铺、商品 ID 和只读操作确认,取消后不启动 worker/Chrome。 +- [ ] 有本地图片时确认框说明刷新范围,并明确本地手动添加图片保留。 +- [ ] 正常点击「生成套图」始终显示店铺、商品 ID、原图数、每图主图开关、分类数量、比例、总数和点数提示。 +- [ ] 取消生成确认后不创建 `image_studio_jobs`,不调用 cmhub。 +- [ ] 单张失败重试保持 T-640 行为,不弹整轮生成确认。 +- [ ] 所有用户可见文本为中文,日志和弹窗不暴露密钥、Cookie、接口路径或完整提示词。 + +## 测试 + +- `tests/test_appconfig.py`: + - `last_account_alias` 默认值、保存、旧配置兼容与无效类型归一化。 +- `tests/test_product_suite_gui.py`: + - 最近店铺跨重启恢复、账号失效回退、项目店铺优先; + - 无项目卖点防抖创建草稿、关闭前同步保存、空文本不建草稿、新任务不串卖点; + - 首次/已有图片拉取确认及取消不启动; + - 正常生成确认内容与取消不建 job; + - 单张失败重试不走整轮确认。 +- 运行: + +```bash +py -3.10 -m unittest tests.test_appconfig 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 +``` + +## 边界(不改什么) + +- 不把商品卖点作为全局默认值保存到 `config.json`。 +- 不保存账号密码、Cookie、登录态、商品图片路径或 cmhub Key。 +- 不修改 SQLite schema,继续复用 `image_studio_projects.draft_prompt` 和现有草稿恢复机制。 +- 不实现拉取中停止、下载取消、迟到结果隔离或清理本轮新增图片,这些属于 T-642。 +- 不修改 cmhub API、图片生成协议、并发上限、计费规则或单张重试行为。 +- 不修改 Shopee/CDP 选择器、商品更新流程、①导入采集、②AI生成、③更新蝦皮、④账号管理或⑤设置。 + +## 执行记录 + +- 待执行。 diff --git a/docs/tasks/T-642.md b/docs/tasks/T-642.md new file mode 100644 index 0000000..6aa8da4 --- /dev/null +++ b/docs/tasks/T-642.md @@ -0,0 +1,154 @@ +--- +id: T-642 +title: 商品套图拉取主图可停止与本轮图片清理 +phase: 7 +deps: [T-641] +status: TODO +created: 2026-07-16 +--- + +## 问题 / 背景 + +⑥「商品套图」拉取蝦皮主图时,按钮只显示「正在拉取...」。再次点击只提示任务正在运行,不能请求停止: + +- `pull_main_images()` 在 `state.pull_worker is not None` 时直接返回; +- `ImageStudioPullImagesWorker.execute()` 没有检查 `BaseWorker.should_cancel()`; +- URL 读取完成后会继续把资产加入下载队列,活动下载和排队下载也没有本轮拉取归属; +- 旧 worker 的迟到完成信号可能在用户停止或开始下一次拉取后继续刷新项目和下载图片; +- 用户无法选择停止后保留还是清除本次已经拉取的图片。 + +本任务在 T-641 的拉取前上下文确认基础上,建立可响应停止的拉取生命周期。清理范围必须严格限制为本轮新增的蝦皮图片,不能误删原有图片或用户本地上传图片。 + +## 方案 + +### 1. 为每轮拉取增加独立运行上下文 + +修改 `app/gui/tabs/product_suite.py` 的 `SuiteTaskState`: + +- 增加不可复用的 `pull_run_token`; +- 增加 `pull_stop_requested`; +- 记录拉取开始前的 `pull_before_asset_ids`; +- 记录本轮返回或新增的 `pull_asset_ids`; +- 记录用户停止后选择的清理策略:`keep` 或 `clear_current`; +- URL 读取 worker、下载队列、活动下载和完成信号都必须能够关联当前 `state.key + pull_run_token`。 + +开始下一轮拉取前重建这些内存字段;不修改 SQLite schema,不把 run token 持久化。 + +### 2. 拉取按钮改为可停止状态机 + +空闲时按钮显示「拉取蝦皮主图」,按 T-641 先做上下文确认。 + +运行时: + +- 按钮显示「停止拉取」并保持可点击; +- 点击后弹中文三选一确认框: + - 「停止并保留」:停止后保留本轮已经加入列表的图片; + - 「停止并清除本次新增」:停止后只移除本轮新增的蝦皮图片; + - 「继续拉取」:关闭弹窗,不设置取消标记。 +- 选择停止后设置 `pull_stop_requested`,调用当前 URL worker 和本轮下载 worker 的 `cancel()`,按钮显示「正在停止...」。 +- 停止处理中重复点击不再弹窗,只提示「正在停止当前拉取任务」。 +- 停止完成后恢复「拉取蝦皮主图」,并显示保留数、清理数和失败数的中文汇总。 + +不使用 `QThread.terminate()`、Python 线程强杀或结束 Chrome 进程。 + +### 3. URL 读取链路支持协作式取消 + +修改 `app/gui/workers.py`、`app/image_studio.py` 及必要的 editor/CDP 调用边界: + +- `ImageStudioPullImagesWorker.execute()` 在开始前、账号/项目校验后、打开商品页前后、读取图片 URL 前后检查 `should_cancel()`。 +- 将可选 `should_stop` 回调沿现有 `pull_remote_main_image_urls()` 调用链传递;默认 `None` 保持其他调用兼容。 +- 已发出的 CDP/HTTP 请求不做不安全强杀,最迟在当前有界请求返回后的安全边界停止。 +- 本轮自动新开的商品详情 tab 继续按现有生命周期关闭;停止不得关闭用户原本已打开的普通 tab。 +- 取消结果必须返回明确 `cancelled=True`,不能记成普通拉取失败。 +- 不修改 Shopee 页面选择器、登录判断、风控处理或自动登录边界。 + +### 4. 下载队列按拉取轮次隔离并支持停止 + +当前 `state.download_queue` / `state.downloads` 也可能包含用户点击未下载缩略图触发的下载,停止本轮拉取不能一并取消无关任务。 + +- 为本轮拉取产生的 asset ID 建立独立集合或在队列项中携带 `pull_run_token`。 +- URL worker 返回后,只有 token 仍是当前活动轮次且未停止时,才把本轮图片加入下载队列。 +- 停止时只取消属于当前 token 的排队下载和活动下载;用户此前手动触发的其他下载继续运行。 +- `ImageStudioDownloadOriginalWorker` 继续在重试前后检查取消,并为底层远程下载增加可选 `should_stop`;流式下载在数据块边界停止、关闭 response、丢弃临时文件。 +- 停止后到达的旧下载完成信号只做线程引用清理,不得重新把已清理图片加入当前列表或覆盖新一轮状态。 +- 拉取和下载停止采用有界等待与 `QThread.finished` 兜底,避免 `QThread: Destroyed while thread is still running`。 + +### 5. 只清理本轮新增蝦皮图片 + +选择「停止并清除本次新增」时: + +- 清理候选集合以 `pull_asset_ids - pull_before_asset_ids` 为准; +- 只允许处理带蝦皮远程 URL、属于当前项目、由本轮同步得到的 `original` 资产; +- 本地手动导入图片、拉取前已有图片、其他轮次图片不得进入清理集合; +- 复用 `remove_original_assets_if_unused()` 或等价事务入口校验项目归属、资产类型和 job/selection 引用; +- 已被生成 job 或终选引用的图片不得猜测性删除,保留并在汇总中提示; +- 清理只影响软件中的本轮资产记录和展示,不修改蝦皮线上图片; +- 第一版沿用现有原图移除边界,不删除用户本地源文件,也不为本任务扩大到磁盘垃圾清理。 + +若停止发生在 URL 资产入库前,清理集合可以为空;仍应正常完成停止,不报错。 + +### 6. 迟到信号隔离与统一收尾 + +- progress、finished、cancelled、failed、下载完成和 `QThread.finished` 处理前校验 `state.key + pull_run_token`。 +- 旧轮次迟到结果不得清空新 worker、恢复错误按钮文字、追加下载队列或弹出旧汇总。 +- 建立幂等 finalize:正常完成、用户停止、worker 失败和线程结束兜底只收尾一次。 +- 关闭任务或关闭软件时请求停止;线程引用保留到真实结束,不能提前销毁。 +- 诊断日志只记录 token 短值、项目 ID、商品 ID、开始前数量、本轮数量、保留/清理/失败数量和耗时,不记录 Cookie、图片 URL、账号密码或完整本地路径。 + +## 验收要点 + +- [ ] 拉取开始后按钮显示「停止拉取」,可点击并弹出三种中文选择。 +- [ ] 选择「继续拉取」后任务不停止,按钮和进度保持运行状态。 +- [ ] 选择停止后按钮显示「正在停止...」,重复点击不重复弹窗。 +- [ ] URL 读取开始前停止时不会打开商品页或新增资产。 +- [ ] CDP 请求进行中停止时,在当前有界请求返回后退出,不继续读取或排队下载。 +- [ ] 停止会取消本轮排队下载和活动下载,但不取消用户此前手动触发的无关下载。 +- [ ] 「停止并保留」保留已完成的本轮图片。 +- [ ] 「停止并清除本次新增」只移除本轮新增蝦皮图片;拉取前已有图片和本地手动导入图片不受影响。 +- [ ] 本轮新增图片已被 job/selection 引用时不删除,并给出中文汇总提示。 +- [ ] 旧轮次迟到 URL/下载结果不会覆盖新一轮状态或重新显示已清理图片。 +- [ ] 正常拉取完成后按钮恢复,继续按现有逻辑后台下载并显示结果。 +- [ ] 关闭任务、关闭软件和停止过程中不出现 `QThread: Destroyed while thread is still running`。 +- [ ] 不修改蝦皮线上商品,不自动登录,不绕过验证码或风控。 + +## 测试 + +- `tests/test_product_suite_workers.py` 或 `tests/test_workers.py`: + - URL worker 开始前、CDP 返回后取消; + - 取消结果与普通失败区分。 +- `tests/test_product_suite_gui.py`: + - 按钮空闲/停止/正在停止状态; + - 三选一确认语义; + - 当前 token 信号处理与旧 token 忽略; + - 停止只取消本轮下载; + - 保留与清除本轮新增图片; + - 被引用图片拒绝清理; + - finalize 幂等和线程结束兜底。 +- `tests/test_image_studio.py`、`tests/test_image_studio_images.py`: + - `should_stop` 调用边界; + - 清理集合项目归属、远程图片类型和引用保护; + - 下载取消关闭 response、清理临时文件。 +- 运行: + +```bash +py -3.10 -m unittest tests.test_product_suite_gui tests.test_product_suite_workers tests.test_image_studio tests.test_image_studio_images +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 +``` + +涉及真实 CDP 取消边界时,在测试商品 `51100639510` 上验证:开始拉取后停止,确认本轮自动新建 tab 能在安全边界关闭;无法实跑时在执行记录中如实说明。 + +## 边界(不改什么) + +- 不修改 SQLite schema,不持久化 run token 或停止策略。 +- 不删除蝦皮线上图片,不删除拉取前已有图片或用户手动导入的本地图片。 +- 不删除被 job/selection 引用的资产,不做猜测性清理。 +- 不使用线程强杀、`QThread.terminate()` 或结束 Chrome 进程。 +- 不修改 Shopee/CDP 选择器、登录策略、验证码/风控边界或商品更新流程。 +- 不修改 cmhub、图片生成、提示词模板、套图分类数量、比例、导出逻辑、①导入采集、②AI生成、③更新蝦皮、④账号管理或⑤设置。 + +## 执行记录 + +- 待执行。