Files
cmshoppe/docs/06-tasks.md
T

171 lines
59 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.
# 任务看板(Tasks)
> 把目标拆成小步、可独立交付的任务,让 AI 一步一步开发,避免一次生成整个项目。
## 使用规则
1. **一次只做一个任务**:每轮只领取一个 `TODO` 且依赖均 `DONE` 的任务,取最靠前的。
2. **做完即停**:完成、自测通过、改 `DONE` 后汇报。
3. **不跳步**:依赖未完成不开工。
4. **完成定义**:以 [编码规则](05-coding-rules.md) 验证清单为准。
5. **动手前**读 `00-ai-start-here.md`、`05-coding-rules.md`、`current-state.md`、`04-architecture.md`(尤其第七节已验证事实、第二节流水线)。
6. **完成后**同步本文状态,追加 [`../progress.md`](../progress.md),覆盖 [`current-state.md`](current-state.md)。
`current-state.md` 只记录按以上规则计算出的当前快照;如果它和本文任务顺序/状态冲突,以本文为准并先修正文档。
## 状态图例
`TODO` · `DOING`(同一时间最多 1 个)· `DONE` · `BLOCKED`
---
## Phase 0 · 地基(模块化 + 存储)
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-000 | 正式代码包结构:创建 `app/`、迁入 `cdp.py` 为 `app/cdp.py`、新增 `app/__init__.py`、`app/__main__.py`、根入口 `main.py`、最小 `app/gui.py` 占位入口,并修正 prototypes 导入 | - | `python -m compileall app main.py` 通过;`python -m app`/`python main.py` 可进入入口(本机 `python` 若为 isolated 且 `-m app` 不搜索当前目录,可用 `python main.py` 或 `py -3 -m app`);GUI 未完成时给明确提示并退出;`prototypes/demo.py` 可从项目根导入 `app.cdp` | DONE |
| T-001 | `app/editor.py`:改标题/换封面/点更新/登录检测/**采集(读旧标题+旧封面下载)**/apply_task,复用 `app/cdp.py` | T-000 | 函数可调用,在测试商品跑通;与 `prototypes/demo.py` 行为一致 | DONE |
| T-002 | `app/appconfig.py` + `config.json`(含 image_dir、ai 选择/参数段、端口等默认值;不含 AI Key) | T-000 | 读写正常;不存在则写默认;AI Key 留给 `config/ai_models.json`/T-501 | DONE |
| T-003 | `app/db.py` + SQLite 建表(batches/accounts/tasks,含 Excel 行定位、状态、时间戳、重试字段) | T-000 | `init_db` 幂等;`connect` 设置 WAL/busy_timeout/foreign_keys;账号/批次/任务/各 set_* 可用;schema 同架构 5.2 | DONE |
| T-004 | `.gitignore`:排除 `config.json`、`config/ai_models.json`、`cmshopee.db`、`chrome_user_data_dir/`、`images/` | T-002, T-003 | 配置、密钥、凭证、业务数据、图片不被提交 | DONE |
| T-005 | AI 模型清单后端:`config/ai_models.json` 读写 + category 过滤 + 测试连接 | T-002 | 本地明文 api_key;UI/API 打码显示;日志脱敏;至少 text/image 各一个;`get_model` 返回调用所需字段 | DONE |
| T-006 | 单元测试基座:`tests/` + appconfig/db/excel/prompts 最小测试 | T-002, T-003 | `python -m unittest discover -s tests` 可跑;不依赖真实 Shopee/AI;临时文件在测试目录清理 | DONE |
## Phase 1 · 账号管理(④)
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-101 | `config` 生成 slug + 创建 `chrome_user_data_dir/<slug>` | T-003 | 别名→唯一 slug;目录按需建;路径绝对化 | DONE |
| T-102 | `app/chrome.py` 启动器:拼参数并启动、探测端口 | T-101, T-002 | 含三参数;端口就绪可探测 | DONE |
| T-103 | 首次登录保活 + 登录检测 `is_logged_in` | T-102, T-001 | 关闭再启动免重登;登录/未登录判断准确 | DONE |
| T-103b | 登录检测补充识别 Shopee accounts 登录页 | T-103 | 问题:打开商品详情页或卖家中心时,未登录账号可能跳转到 `https://accounts.shopee.tw/seller/login...`,④「检测登录」和①/③账号预检必须把该 URL 明确判为未登录,而不是依赖泛化 `/login` 规则或误判为无 Cookie。方案:在登录页 URL marker 中显式加入 `accounts.shopee.tw/seller/login` / Shopee accounts 域名下 `/seller/login` 类规则;`login_status()` 返回 `logged_in=false/reason=LOGIN_PAGE/url=<实际URL>`;④状态列显示“未登录”,①/③预检继续阻断并引导去④,不自动登录、不填密码、不绕过验证码。同步 editor 登录检测单测覆盖该真实 URL | DONE |
| T-104 | PySide6 五 Tab 主窗口骨架(`QMainWindow` + `QTabWidget`,5 Tab 空壳) | T-002 | 五个 Tab 按顺序可切换;启动不阻塞;基础状态栏可用 | DONE |
| T-104b | PySide6 worker 基类与线程启动工具(`BaseWorker` + `QThread` 包装) | T-104 | signals: progress/log/row_updated/failed/finished/cancelled;取消标记可用;worker 不直接操作 QWidget | DONE |
| T-105 | Tab④ 账号增删改(账号名/别名/端口/密码本地明文仅参考)+ 启动登录 + 检测登录 + 顶部 Tab 栏防误点样式 | T-104, T-101, T-103 | 增删改入库、建目录;密码字段 UI 打码;状态列刷新;不自动登录/填密码;5 个 Tab 有足够宽度/间距与当前态高亮,不易误点 | DONE |
| T-105b | ④「启动登录」复用已打开 Chrome,防止同账号重复开窗口 | T-105, T-102, T-523 | Bug:同一账号已打开 Chrome 后再次点击「启动登录」,当前链路仍从 `AccountsTab.launch_login()` → `accounts.launch_for_login()` → `chrome.launch_chrome()` → `subprocess.Popen()`,没有先检查该账号 `debug_port` 是否已有 CDP 响应;`login_statuses[alias]="已启动"` 只是界面显示,不是幂等保护。方案:把④启动入口改为幂等流程,先调用 `chrome.is_running(account.debug_port)`;若端口已响应,不再 `Popen`,通过现有 CDP 能力复用该账号 Chrome,优先激活/打开 `https://<region_host>/portal/` 或卖家中心登录 tab,并写 `run_type=chrome_launch` 事件 `result=reused`,状态栏提示“该账号 Chrome 已打开,已复用现有窗口”;若端口未响应,才调用 `chrome.launch_chrome()` 并等待 CDP 就绪,事件 `result=launched`。不得自动填写密码、不得绕过登录/验证码、不得影响①/③账号预检“不自动启动 Chrome”的规则。测试覆盖:端口已运行时不调用 `subprocess.Popen`,端口未运行时仍启动;GUI 重复点击同账号不会新增 Chrome 进程,run log/status 文案区分复用与新启动 | DONE |
| T-106 | 可选:为账号生成桌面快捷方式 | T-102 | `.lnk` 目标含该账号参数;双击进对应账号 | DONE |
## Phase 2 · 导入采集(①)
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-201 | `app/excel.py` 导入:解析多文件输入列入库 | T-003 | 按模板解析账号名/别名/商品id;记录 source_file_abs/source_sheet/source_row/row_key;缺必需列则拒绝整文件并记 file_errors;脏行逐行跳过计 invalid;写 batches/tasks | DONE |
| T-202 | Tab① 任务列表 + 导入按钮 + 别名匹配标记 | T-201, T-105 | `QTableView` 显示账号/别名/商品id/阶段;未匹配标“略过” | DONE |
| T-202b | Tab① 导入汇总栏 | T-202 | 导入后显示 文件数/解析行数/有效/无效/匹配(按账号)/未匹配;未匹配可点击筛出 | DONE |
| T-203 | 采集旧标题+旧封面(只读),下载图片,立即写库 | T-202, T-001, T-104b | 通过 worker 执行;逐条 set_collected;旧封面下载到 `images/<batch_id>/<slug>/<task_id>_<item_id>_old.jpg`;别名未匹配逐条略过记原因;匹配账号未登录由 T-205 预检阻断,不写 skipped/failed | DONE |
| T-204 | 回写旧字段到原 Excel(含文件锁处理) | T-203, T-201 | `excel.write_back()` 按源文件/工作表/行号回写旧标题、旧封面;`export_copy()` 另存副本;Tab① 用 `WriteBackWorker` 后台回写,文件被占用时提示关闭后重试 | DONE |
| T-204b | 采集完成后自动回写旧字段到 Excel | T-204 | `CollectWorker` 完成后自动触发 `excel.write_back()` 回写当前批次旧字段;成功时状态栏/日志提示“已回写”;原文件被锁时不影响 SQLite,提示关闭后点「回写旧数据到 Excel」手动重试或另存副本 | DONE |
| T-205 | 首次未配账号 / Chrome 未启动 / 未登录的引导保护 | T-105, T-203 | 无账号、匹配账号未启动 CDP 端口或未登录时,① 执行按钮禁用或采集前汇总提示,并引导去④;可提供“打开账号管理/启动登录”入口,但不无提示批量启动所有账号 Chrome | DONE |
| T-205b | 采集后关闭程序自动新建的商品编辑页 tab | T-203 | `open_product` 区分复用旧 tab 与本次新建 tab;`CDP.close()` 仍只断开连接;采集完成后只关闭本次自动新建的商品页,不关闭用户原本打开的 tab;失败/异常也执行清理;③ 更新流程暂不自动关页,失败时保留现场便于排查 | DONE |
| T-206 | Tab① 删除指定批次(软删除) | T-202, T-104, T-404 | 现状:导入后批次只增不减,下拉框无限膨胀、导错文件/测试导入无法清理。方案:`db.delete_batch(batch_id)` 改为软删除,在事务中给 `batches.deleted_at/deleted_reason` 写值,不物理删除 `batches/tasks`;默认 `list_batches/list_tasks` 以及①/②/③页面、筛选、采集、生成、更新、回写等业务入口都排除已删除批次,删除后用户不能在软件页面查看、筛选或再次调用该批任务。函数返回任务数、其中已上线(`committed=1`)任务数、关联封面图片绝对路径;Tab① 批次筛选旁加「删除批次」按钮,仅在选中具体批次(非「全部批次」)时可用,运行中禁用。二次确认弹窗显示批次标签、任务数、已上线任务数并提示「软删除只隐藏本地批次,不会回滚 Shopee 线上修改」;图片默认保留,不做自动清理,后续若需要清理图片另做独立工具。确认后软删除、刷新①/②/③列表和批次筛选 | DONE |
| T-207 | ① 采集诊断日志(run_logs + 本地 log) | T-203, T-503, T-504 | 问题:采集少量商品时单条失败只能看到 `tasks.last_error` 和状态栏计数,无法定位卡在打开商品页/页面就绪/读标题/读封面/下载图片/写库/回写哪一步。方案:`CollectWorker` 创建 `run_type=collect` 的运行日志,逐任务写 `run_log_events`(含 `task_id/alias/item_id` 和 `step=preflight/open_product/wait_ready/read_title/read_cover/download_cover/db_write/excel_write_back`);同时新增 gitignore 的 `logs/` 本地滚动日志,写脱敏 traceback 与耗时;GUI 至少能查看最近一次采集运行日志;失败仍按现有语义写 `status=failed/last_error`,不影响其他任务继续 | DONE |
## Phase 3 · AI 生成(②)
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-301 | 确定 AI 服务商/模型并接入 `app/ai.py`(`gen_title`/`gen_cover`,带重试/分辨率/jpg质量) | T-005 | 从 `config/ai_models.json` 读取模型与本地明文 Key;`gen_cover` 支持 resolution+jpg_quality;失败按 retry 重试;错误明确;日志脱敏 | DONE |
| T-302 | Tab② 左右布局:左提示词(标题/封面),右按批次/店铺/状态筛选 + 任务列表 | T-301, T-203 | 左 ~1/4 提示词多行;右筛选+列表(店铺/商品id/旧标题/新标题/状态) | DONE |
| T-302p | `app/prompts.py` + Tab② 提示词管理 | T-302 | 标题保存/启动回显 title_prompt.txt;封面多模板(下拉+新建/保存/另存为/重命名/删除,存 prompts/cover/);插入 `{新标题}`;预览变量替换;render_prompt 接入生成 | DONE |
| T-303 | Tab② 开始生成(单按钮)+ 停止 + 进度:**先并发标题再并发图片** | T-302, T-104b | `generate_batch` 先 title_concurrency 并发标题、再 image_concurrency 并发图片;worker/signal 回传进度;每条 set_generated 立即写库;停止可取消未开始项;进度 标题/封面/失败 计数;双击弹窗看新旧封面 | DONE |
| T-303b | ②/③ 商品ID筛选 | T-303, T-401 | ② AI生成和③ 更新shopee筛选栏增加商品ID输入框;输入内容按包含匹配 `item_id`,清空表示全部;当前筛选结果和「开始生成」/「开始更新」范围都受其约束;③确认弹窗显示商品ID筛选条件 | DONE |
## Phase 4 · 更新 shopee(③)
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-401 | Tab③ 列出已生成任务 + 按批次/店铺/商品ID/状态筛选 + 开始更新确认弹窗 | T-303 | 顶部批次/店铺/商品ID/状态筛选;「开始更新」仅作用于当前筛选结果;弹窗显示筛选条件/任务数/线上提交风险;取消不执行;状态=失败可重试;无常驻提交开关 | DONE |
| T-402 | 串行执行 apply:批量确认后换标题+换封面+点更新提交,单条失败继续,立即写库 | T-401, T-001, T-104b | 确认后 worker 串行执行并逐条 set_applied;失败继续;未确认时不调用 apply;真实更新前检查本轮账号 Chrome/CDP/登录态,未启动或未登录时整体阻断并提示去④,不自动打开 Chrome | DONE |
| T-403 | 回写结果到原 Excel(新标题/新封面/更新状态)+ 结束弹窗汇总 | T-402, T-204 | 回写原文件(锁处理);弹窗 成功/失败/略过 | DONE |
| T-404a | ②/③ 选中记录重置(联调重试辅助) | T-303, T-403, T-504 | 初版解决 ② AI生成和③ 更新shopee列表无法重置选中记录的问题:③「重置更新状态」保留 `new_title/new_cover_path`,把本地状态退回可更新(`stage=generated/status=pending`、清空 `last_error`),用于重复测试上传/提交。②「重置生成结果」后续已由 T-534 升级,不再按初版退回 `collected` 或仅作用单选;当前以 T-534 为准:支持多选/当前筛选结果,确认框可选「重置标题 / 重置封面 / 重置全部」,只清对应 `new_title` 或 `new_cover_path` 与 `last_error`,保持 `stage=generated/status=success`,由 T-533 后续只补缺失组件;只重置封面时必须保留 `new_title` 和 T-509 手动标题。若记录 `committed=1`,确认框必须醒目提示“线上已提交过,本地重置不会回滚 Shopee,重复更新会再次提交线上”,并保留 committed 历史事实/运行日志;运行中禁用;两类重置都写 `run_type=reset` 运行日志,不触碰 Shopee、不回写 Excel,后续由手动回写同步本地状态 | DONE |
| T-404 | 真实 Shopee 单条更新冒烟验收 | T-403, T-501c | 已完成真实验收:2026-07-01 用户手动导入包含 5 个商品 ID 的 Excel,完成 AI 标题/图片生成,并通过 Tab③「开始更新」流程更新到 Shopee;覆盖真实编辑页、标题/封面更新、Shopee 站点侧确认框、提交后结果判断与本地状态链路。后续若 Shopee 页面结构变化,仍需按 CDP 规则补回归验证 | DONE |
| T-404b | 商品详情页加载失败 toast 自动捕获 | T-404, T-505 | 问题:③ 更新或①采集打开商品详情页时,如果商品 ID 已失效、无权限或店铺不匹配,Shopee 可能只弹出很快消失的错误 toast,例如 `please input correct product id`;用户来不及复制 HTML,GUI 也可能只报“商品详情页加载失败/等待超时”,无法判断真实原因。方案:在 `open_product()`/等待商品详情页就绪前安装或启用 toast 捕获,监听并兜底读取 `.eds-toasts .eds-toast__content`,保存最近 toast 的文本、`outerHTML`、当前 URL、时间和可见状态到页面内缓存;当标题输入框/图片管理器/上传入口等关键元素超时或页面不是商品编辑页时,优先把最近错误 toast 提升为失败原因,写入 `tasks.last_error`、`run_logs/run_log_events` 和本地 `logs/` 诊断日志,并可保存失败现场 HTML/toast JSON 片段用于排查。若 toast 明确属于商品失效/商品不存在/无权限类错误,则底层仍保持 `stage=imported/status=failed/last_error=商品失效:<原始toast>`,①导入采集列表“阶段”列显示“商品失效”;其他打开失败仍显示“失败”。如果失败发生在 `open_product()` 内部、尚未把 `cdp` 返回给上层,仍必须由 `open_product()` 自己清理:本轮自动新建的商品 tab 要断开 CDP 后关闭浏览器 target;复用用户原本打开的 tab 只断开 CDP,不关闭页面。不得记录 Cookie、密码、token;不绕过 Shopee 校验,不改变上传、删图、提交逻辑。同步 editor/CDP mock 和 GUI model 测试覆盖短暂 toast、隐藏 toast、商品 ID 失效错误文案上浮、自动新开 tab 在商品失效时关闭、复用 tab 不关闭、仅明确失效 toast 才显示“商品失效” | DONE |
## Phase 5 · 设置与收尾
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-501 | Tab⑤ 设置 · AI 模型管理 UI(master-detail):模型下拉 + 新增/删除 + 详情(服务商名/类别/api_type/模型ID/网址/密钥打码/连接超时) + 测试连接 | T-005, T-104 | 复用 T-005 后端;name 唯一;类别 text/image;至少各一个;密钥本地明文保存、UI 打码;测试连接可用 | DONE |
| T-501b | Tab⑤ 设置 · 角色与生成参数:标题大模型/图片大模型下拉、分辨率(512/1k/2k/4k)、返回超时随分辨率、并发/重试/jpg质量、路径/端口 | T-501 | 角色下拉按类别过滤;分辨率选后返回超时按 resolution_timeouts 自动;持久化 config.json | DONE |
| T-501c | Tab⑤ 设置 · Shopee 更新安全开关 | T-501b | 配置测试商品 ID(历史/调试字段)、是否允许真实提交、是否允许更新封面、单次最大更新条数、成功后是否自动关闭本次新开编辑 tab;默认关闭真实提交和封面更新;Tab③ 执行前读取并拦截不符合安全配置的更新 | DONE |
| T-502 | 换封面:删第一张再上传 | T-001 | 已实现备份校验、删第一张、确认弹窗、再上传和拖首位;更新封面统一先删当前第一张,不再只限满 9 张;删除前必须确认该任务已有本地旧封面备份(`old_cover_path` 存在且文件存在),缺失则拒绝删除并报错;已在 9 图测试商品 `29671243750` 上实测不提交流程 | DONE |
| T-503 | 敏感信息本地明文存储提示与日志脱敏 | T-105, T-501 | 首次保存密码/API Key 时提示“本地明文保存”;UI 打码;日志/导出不含密码/API Key;文档说明 `config.json`/`config/ai_models.json`/DB/user-data-dir/images 必须 gitignore | DONE |
| T-504 | 多账号并行 / dry-run / 运行日志(V2) | T-402 | 端口不冲突;dry-run 只预览;操作留痕 | DONE |
| T-505 | 全流程诊断日志扩展 | T-207, T-504 | 在 T-207 采集日志机制稳定后,已扩展到② AI生成、Excel 导入/回写、③ 更新shopee步骤级日志、④ Chrome 启动/登录检测、⑤ AI模型测试连接;数据库日志保存业务可读步骤和错误,本地 log 保存脱敏 traceback;不得记录 Cookie、密码、API Key、token | DONE |
| T-506 | 正式使用批量更新体验:③ 检查本轮更新 + 分批更新全部 + ⑤ 设置页三列布局 | T-501c, T-504 | ③ 增加「检查本轮更新」按钮替代用户可见 dry-run;「开始更新」对当前筛选结果按每批最大更新条数自动分批,确认弹窗显示总数/每批/预计批次,停止为当前商品安全结束后不再开始新任务;⑤ 设置页使用居中内容区,左右留白已缩短为 T-506 初始实现约 40%,模型详情/角色与生成参数/路径与端口/Shopee 更新安全均使用响应式三列布局,长字段跨列;「多账号并行更新」与「最大并行账号数」合并为同一个横向组件,最大并行账号数紧跟其后且不换行 | DONE |
| T-507 | 正式批量更新:移除普通流程测试商品 ID 限制 | T-506 | 普通正式更新不再以 `test_item_id` 阻断当前筛选结果;③ 确认弹窗不再显示测试商品 ID;⑤ 普通设置页隐藏测试商品 ID,仅保留历史/调试兼容字段;仍保留允许真实提交、允许更新封面、分批、二次确认、账号就绪预检和运行日志 | DONE |
| T-508 | ③ 更新shopee生产化操作区 | T-401, T-404a, T-506 | 「开始更新」作为主操作视觉强化;「重置更新状态」从底部按钮移到任务表右键菜单/高级入口,与批量更新物理分离,运行中禁用;更新安全拦截弹窗写明具体开关并提供「前往设置」深链到⑤,不改 Shopee/CDP 更新流程 | DONE |
| T-509 | ② 新标题人工微调 | T-303, T-401 | ②任务表「新标题」列可编辑;仅允许已生成、未提交线上、非运行中任务;编辑写回 `tasks.new_title`,保留 `stage=generated`,清空 `last_error`,状态回到可更新;不触碰 Shopee/CDP/Excel,③ 使用修改后的标题 | DONE |
| T-510 | ③ 检查本轮更新文案统一 | T-506, T-508 | 将用户可见「预览本轮更新」统一改为「检查本轮更新」,按钮、确认框、状态栏、完成弹窗、运行日志和文档一致;保留内部 `dry_run` 字段与执行语义,不改 Shopee/CDP 更新流程 | DONE |
| T-511 | 语义色板 + ①②③任务状态列上色 | T-202, T-303, T-401 | 依据 `docs/ui-color-design.md`。在 `app/gui.py` 顶部定义统一语义色板常量(success `#1a7f37`/danger `#cf222e`/info `#0969da`/pending `#9a6700`/muted `#6e7781`/warning `#bc4c00`),全局引用不散落硬编码。①②③ 三个 TableModel 在 `Qt.ForegroundRole` 按内部 `stage/status` 返回 `QColor`,不要按中文显示文案硬匹配:完成态=success、失败=danger、略过/取消=muted、待处理=pending、进行中=info;不给普通行刷底色(失败行浅底 `#ffebe9` 可选、仅此一种)。保持文字+颜色双编码,不只靠颜色。跑 `test_gui.py` 无回归 | DONE |
| T-512 | ③高风险按钮上色 + ①导入校验数字标红 | T-511, T-508, T-202b | ③「开始更新」(写线上)改用 warning `#bc4c00` 系强调(描边或填充;第一版不强制图标,避免 Windows/Qt 字体渲染不稳),与②安全流程 info 蓝主操作区分;「停止」保持中性。①导入汇总栏:未匹配数 >0 时用 danger 且保持现有按钮可点击筛出,=0 保持中性;无效行数 >0 时只标红提示,不承诺在任务表筛出,因为脏行未入库,后续若要点击查看需另做「导入错误明细」。颜色取自 T-511 色板常量 | DONE |
| T-513 | 登录点 / ③Tab危险标识 / 破坏性按钮上色 | T-511, T-105, T-206 | ④登录状态 `●` 已登录=success、未登录/未检测=danger 或 muted(保留文字);③ 更新shopee Tab 若做危险标识,不依赖 QSS 硬选第 3 个 Tab,优先用 `QTabWidget.setTabIcon(2, ...)` 设置克制 warning 小圆点图标,或第一版先跳过 Tab 标识;不整条刷红。破坏性按钮「删除批次」「删除账号」文字/描边用 danger,与普通灰按钮区分(二次确认弹窗仍是主防线)。颜色取自 T-511 色板常量 | DONE |
| T-514 | ①②③ 首次空状态引导卡片 | T-205 | 依据 `docs/ux-review.md` P1。①②③ 增加轻量空状态引导卡片,不改采集/生成/更新流程和既有预检拦截逻辑。无账号时三页均显示「第一步:前往『④账号管理』配置并登录账号」并提供「前往账号管理」按钮;有账号但无任务时,①提示导入 Excel,②提示先完成①采集,③提示先完成②生成;账号/任务就绪后卡片自动隐藏,老用户无感。卡片只做 UI 引导,不替代执行前账号 Chrome/CDP/登录态检查 | DONE |
| T-515 | 批次阶段进度总览 | T-206, T-401 | 依据 `docs/ux-review.md` P1。加轻量批次进度总览:①②③在当前批次/全部批次摘要区下方显示同一组阶段计数,按当前批次筛选聚合总数、导入、已采集、已生成、已更新、失败、略过。计数口径:失败/略过优先按 `status=failed/skipped` 统计;已更新按 `stage=applied` 且非失败/略过;已生成按 `stage=generated` 且非失败/略过;已采集按 `stage=collected` 且非失败/略过;导入按其余未进入后续阶段任务统计。数据由现有 `db.list_tasks(batch_id=...)` 聚合,不新增表;已软删除批次不计入;只做 UI 总览,不改采集/生成/更新执行流程 | DONE |
| T-516 | ①筛选对齐②③ | T-202, T-303b | 依据 `docs/ux-review.md` P2。① 导入采集补齐与②③一致的店铺、商品ID、状态筛选,保持三个列表页筛选心智一致。筛选栏顺序为批次/店铺/商品ID/状态;当前筛选结果用于①表格显示与「采集旧标题/旧封面」作用范围;导入汇总栏、批次进度总览、未匹配数量仍按当前批次全量任务统计,未匹配按钮继续筛出当前筛选结果中的未匹配任务;不改导入、采集、回写、CDP 或 Shopee 更新逻辑 | DONE |
| T-517 | ⑤设置分区 + 清理兼容字段 | T-506, T-507 | 依据 `docs/ux-review.md` P2。⑤ 视觉分区:将高频「Shopee 更新安全 / 执行模式」前置成独立分区,低频「基础设施(Chrome路径/端口/DB路径)」独立后置;彻底隐藏 `test_item_id`、`dry_run` 用户入口,不再占用可见表单位置,但保留 `config.json` 内部兼容字段与③「检查本轮更新」语义。只改⑤设置页 UI 分区、保存/加载展示和 GUI 测试,不改配置 schema,不改 Shopee/CDP 更新流程 | DONE |
| T-518 | ②左栏提示词区组件密度优化 | T-302p, T-303, T-517 | 依据 `docs/ux-review.md` P2。② AI生成左栏保留高频「模板选择、编辑内容、保存当前模板/开始生成」可见入口;将封面模板低频操作「另存为、重命名、删除」收敛进一个「模板操作」菜单/小按钮,避免左栏按钮堆叠和误点。第一版不迁移到⑤设置,不改 prompts 数据结构,不改 AI 生成 HTTP、DB schema、Excel、Shopee/CDP 流程;删除仍保留现有二次确认,保存/重命名/删除行为和错误处理不变。同步 GUI 测试覆盖菜单入口、动作触发和旧低频按钮不再平铺 | DONE |
| T-519 | ②AI生成长任务进度条 + 用户可读滚动日志 | T-303, T-505, T-518 | 问题:批量生成几十/上百个标题或图片超时重试时,仅底部文字 `进度:标题x/y · 封面x/y · 失败z` 和技术型结构化日志不足以让用户判断剩余量、是否卡住、慢在哪一步。方案:②底部操作区增加标题/图片两条独立 `QProgressBar`,分两行显示 `标题 x/y`、`图片 x/y`,标题阶段结束后保持 100%,图片进度按本轮总任务口径显示并在旁边保留失败数;现有「AI生成运行日志」改为用户可读自动滚动日志,显示本轮开始、标题/图片开始与成功、商品ID/店铺、图片超时/重试、失败原因、停止请求和完成汇总。日志不得显示 API Key、密码、Cookie、token、完整请求体、base64 图片或超长 prompt;保留 DB `run_logs/run_log_events` 脱敏记录。只改②生成 UI、`GenerateWorker`/AI 生成事件文案和 GUI/AI 单测,不改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程 | DONE |
| T-520 | ②AI生成封面可选生成开关 | T-519, T-501b | 问题:图片生成成本明显高于标题生成,当前②「开始生成」默认标题和图片都生成,用户只想改标题时也会调用图片模型,成本不可控;⑤「允许更新封面」只控制③线上提交阶段,不能替代②生成阶段的成本选择。方案:②AI生成页增加「生成封面图片(成本较高)」复选框,状态持久化到 `config.json` 的 `ai.generate_cover`,默认 `false`;点击「开始生成」时读取该开关:关闭时只并发生成标题,标题成功后立即 `db.set_generated(task_id, new_title, NULL)`,任务进入 `generated` 并可在③只更新标题,不调用 `gen_cover()`、不渲染封面提示词、不创建新封面文件,图片进度和运行日志明确显示本轮未生成图片;开启时保持现有标题后图片两段流程。③的 `allow_cover_update` 仍只控制线上更新阶段,若任务无 `new_cover_path`,③跳过封面更新且不因未开启「允许更新封面」阻断。只改 `config.json` 的 AI 段、②生成 UI、`GenerateWorker`/`ai.generate_batch()` 编排和 `appconfig/ai/gui` 单测;不改 DB schema、AI HTTP 协议、Excel、Shopee/CDP 更新流程 | DONE |
## Phase 6 · 工程基础设施(`docs/engineering-review.md`)
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-521 | 依赖清单(锁版本 requirements) | T-006 | 依据 `docs/engineering-review.md` P0。新增锁版本的 `requirements.txt`(或 `pyproject.toml`)声明 PySide6/openpyxl/websocket-client/requests 及版本,与 `docs/03-tech-stack.md` 依赖纪律对齐;README/文档补安装说明;不引入新运行时依赖、不改业务代码 | DONE |
| T-522 | CI:自动跑语法 + 单元/ GUI 测试 | T-006, T-521 | 依据 `docs/engineering-review.md` P1。加 GitHub Actions,在 push/PR 上按 `requirements.txt` 安装依赖并跑 `python -m compileall app main.py` + `python -m unittest discover -s tests`(含 PySide6 环境下的 GUI 测试,`QT_QPA_PLATFORM=offscreen`);不连真实 Shopee/AI;失败即红灯,把"改完必跑测试"变强制门禁 | DONE |
| T-523 | 拆分 `app/gui.py` 为 `app/gui/` 包 | T-511, T-514, T-515, T-516, T-517 | 依据 `docs/engineering-review.md` P0。把 6317 行 God-file 拆为包:`models.py`(3 个 TableModel)、`tabs/`(①~⑤各一文件)、`workers.py`(Generate/Apply/Collect/WriteBack/AccountLoginCheck/AIModelTest 从 gui 挪出,与 `app/workers.py` 基类归拢)、`widgets.py`(色板常量、空状态卡、批次总览等 helper)、`main_window.py`。纯结构重构、对外行为与公开符号不变(保留 `from app import gui` 及 `MainWindow`/各 Tab/Worker 的导入路径或提供兼容再导出),保留 PySide6 缺失优雅降级;68 个 GUI 测试全绿、不删减断言 | DONE |
| T-523a | ②/③ 新一轮运行前清空界面日志显示 | T-519, T-505, T-523 | 问题:②AI生成和③更新shopee进入模块或启动新一轮任务时仍显示上一次运行日志,用户容易把旧失败、旧进度误认为本轮状态。方案:点击②「开始生成」、③「检查本轮更新」或③「开始更新」时,先清空对应界面的可见日志显示区,并写入本轮开始摘要;运行中只追加本轮日志,完成后保留本轮日志。只清空界面文本,不删除 SQLite `run_logs/run_log_events` 或本地 `logs/` 诊断日志;进入页面默认可显示“本轮日志会在开始运行后显示”,历史日志查看作为后续低优先级入口,不自动混入当前运行界面。同步 GUI 测试覆盖新一轮运行不会混入旧日志 | DONE |
| T-524 | PyInstaller 打包为免安装 exe | T-521 | 依据 `docs/engineering-review.md` P1。新增 PyInstaller spec/脚本,产出 Windows onedir 免安装 `.exe`;打包排除并绝不内置 `config.json`/`config/ai_models.json`/`cmshopee.db*`/`chrome_user_data_dir/`/`images/`/`logs/`/`prompts/`/`title_prompt.txt` 等本地数据与密钥;打包版工作目录切到 exe 所在目录,首次运行按现有默认值在本地生成配置;`scripts/build_exe.ps1` 可构建并校验发布目录,`docs/packaging.md` 说明分发和用户手动覆盖更新步骤 | DONE |
## Phase 7 · AI 网关对接(`docs/cmhub-integration-design.md`)
> 业务优先级调整:先完成 cmhub 生文/生图网关对接;T-525(ruff)顺延到 Phase 8,不作为 T-526 的阻塞项。
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-526 | `app/ai.py` + `appconfig` 接入 cmhub backend | T-301, T-303, T-520 | 依据 `docs/cmhub-integration-design.md` v3.2。`config.json` 的 `ai` 段加 `backend`(`cmhub`/`direct`)+ `cmhub` 子段(`base_url`/`title_alias`/`image_alias`/`connect_timeout` 等);T-526 实现时保留 `direct` 兼容路径;T-529 后普通产品默认 `backend=cmhub`,⑤不再暴露后端切换;`backend=cmhub` 但 `base_url`/Key 缺失时须抛清晰「请去⑤配置 cmhub」错误(`CMHubError`/`AIError`),不崩溃、不静默直连。cmhub API Key 固定存 `config/cmhub.json`(schema `{ "api_key": "..." }`),新增读写/打码 helper 并把该文件加入 `.gitignore`;日志脱敏。`gen_title`/`gen_cover` **返回值不变**,内层按 backend 分流并保留 `direct`;计费元数据不塞进返回值,允许给二者**新增一个可选事件回调参数**(如 `on_meta`/`on_event`)承载,属向后兼容加参,`generate_batch` 显式传回调不受影响。cmhub 分支:生文 `POST /api/v1/generate/title` 体 `{prompt,model:别名,resolution?}`、取 `titles[0]`、空则 `AIError`;生图 `POST /api/v1/generate/image` 体 `{prompt,model:别名,image_base64:<旧封面>,resolution,aspect_ratio:"1:1"}`、拿 `image_url` 后**立即下载**再走 `_save_jpeg`;`resolution` 归一大写 `512/1K/2K/4K`。新增 `CMHubError(AIError)`,带 `code/status/retryable/retry_after`,错误按 `code` 优先分支(`insufficient_points`/`unauthorized`/`account_disabled`/`bad_request`/`model_not_allowed`/`no_pricing_rule`/`content_blocked`/`upstream_error`/`rate_limited`,未知 code 当不可重试);cmhub HTTP helper 需区分 connect/read timeout(优先用 `requests timeout=(connect, read)`),只对 502/429/连接超时重试,生图读超时绝不自动重发,读超时按分辨率封顶 600s。`points_cost`/`points_balance`/`call_id` 不改返回值,通过 `on_step`/事件回调上报;T-526 只保证 metadata 事件完整传出,T-528 再由 GUI worker 脱敏写 run_logs 和余额展示。`image_url` 下载必须限制 http/https、拒绝内网/回环地址、校验域名解析后的 IP 仍不是内网/回环/本机地址,并设置超时和大小上限。新增 `fetch_cmhub_models(base_url, api_key)` helper 调 `GET /api/v1/models` 返回别名清单(`alias/operation_type/requires_image/pricing_status/prices`)供 T-527 渲染下拉,错误脱敏。不碰 editor/cdp/chrome/accounts/excel/db,也不改 ①③④流程。`tests/test_ai.py` 加 cmhub mock(titles 列表、image_url 下载、安全下载、错误码与重试、读超时不重发、metadata 事件)、`tests/test_appconfig.py` 加 schema 和 key 文件 helper,direct 用例保持绿 | DONE |
| T-527 | ⑤设置 cmhub 网关面板 | T-526, T-517 | 依据 `docs/cmhub-integration-design.md` v3.2。⑤ AI 设置按 `backend` 切换:cmhub 模式显示「网关 Base URL + API Key(打码,提示从网页端复制、仅显示一次)+ 生文别名 + 生图别名 + 测试连接/查余额」;**别名从 `GET /api/v1/models`(T-526 的 `fetch_cmhub_models`)动态拉取渲染下拉**,按 `operation_type` 分生文/生图,过滤 `pricing_status="unpriced"` 的别名,可展示单价与 `requires_image` 提示,选中值持久化到 `ai.cmhub.title_alias/image_alias`(网关临时不可达时回退已存值);不写死别名。direct 模式保留现有 AI 模型 master-detail。保存写 `config.json` 的 `ai` 段与 `config/cmhub.json`;切换 backend 时不删除 legacy `config/ai_models.json`。测试连接/查余额经后台 worker 调 cmhub(复用 `AIModelTestWorker` 思路或新增 worker),错误必须脱敏并给用户可读提示。同步 GUI 设置测试;不改 Shopee/CDP 流程 | DONE |
| T-528 | ② 计费错误提示 + 余额展示 | T-526, T-527, T-303 | 依据 `docs/cmhub-integration-design.md` v3.2。② AI生成页把 cmhub 计费失败态显式化:通过 `CMHubError.code` 识别 `insufficient_points`,弹明确提示「点数不足,请先充值」并引导去网页端充值,本轮未开始任务可提前中止,不靠中文字符串匹配、不淹没在失败计数里;用 T-526 成功响应事件里的 `points_balance` 刷新②页剩余点数显示,`/balance` 仅作手动刷新/可选批量前预检;`points_cost`/`call_id` 记入脱敏 run_logs。只改② UI、`GenerateWorker` 事件处理/文案及 GUI 单测;不改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程 | DONE |
| T-529 | 默认 cmhub 网关并隐藏 AI 后端选择 | T-528, T-527 | 产品收口:后续普通用户只使用 cmhub 网关,不再在⑤设置页暴露「AI 后端」label 与 direct/cmhub 下拉框。方案:`DEFAULT_CONFIG.ai.backend` 改为 `cmhub`;⑤设置页默认直接展示 cmhub 网关配置(Base URL、API Key、生文/生图别名、刷新别名、测试连接/查余额),隐藏 direct 模型选择、模型详情、标题/图片模型角色下拉和「AI 后端」下拉;保存设置固定写 `ai.backend=cmhub`,允许先保存不完整 cmhub 配置,②生成时仍由 `app/ai.py` 对缺 Base URL/Key/别名给出「请去⑤配置 cmhub」错误。direct 代码、`config/ai_models.json` helper 与 direct 单测保留为内部兼容/手工回滚路径,但普通 UI 不提供切换入口。同步 `appconfig`、⑤设置页、②余额默认显示和 GUI/appconfig 单测;不改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程 | DONE |
| T-530 | cmhub Base URL 规整 + 404 明确提示 | T-526, T-527 | 现象:手动填入 Base URL 后点「刷新别名」提示 notfound(HTTP 404)。核对确认 cmshopee 请求已达标(`GET /api/v1/models` + Bearer,对齐对接文档 §4.4 与 cmhub `ModelsView` 路由,见 `docs/troubleshooting.md`「cmhub 刷新别名 notfound」与 `docs/cmhub-integration-design.md` v3.4);404 根因是 Base URL 带多余 `/api(/v1)` 路径导致双拼、或所连实例未部署 `/api/v1/models`。方案:保存/请求前规整 Base URL——去掉结尾的 `/`、`/api`、`/api/v1` 等多余路径段,只保留 scheme+host(+port)(`appconfig.cmhub_request_url()` 或保存时统一处理),并在⑤输入框旁给出「只填网关根,如 https://host」提示;`_cmhub_code_for_status` 给 404 一个明确码/中文提示(如「cmhub 接口不存在,请检查 Base URL 或该实例是否已部署 /api/v1/models」),不再透传生涩英文原文。补 `test_appconfig`(Base URL 规整:带 `/api/v1`、带尾斜杠、带路径均归一到根)与 `test_ai`(404 → 明确提示)单测;不改 AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程 | DONE |
| T-531 | ⑤ 设置未保存状态追踪 + 离开确认 | T-530, T-529, T-527 | 现象:在⑤填写 cmhub Base URL/API Key(或改任何设置)后,没有任何"未保存"提示;切换到其它 Tab、关闭窗口都不拦截,输入静默丢失。且「刷新别名/测试连接」用输入框实时文本、不落盘,成功后易误以为已保存。采用**方式 B(显式保存 + 脏状态追踪 + 离开确认)**,不改自动保存(API Key/半份 cmhub 配置不宜自动写盘)。方案:① 脏追踪——⑤ 所有可编辑控件(cmhub Base URL/API Key/别名下拉、模型详情、生成参数、路径端口、Shopee 更新安全等)接 `textChanged`/`currentIndexChanged`/`toggled`/`valueChanged` 置 `self._dirty=True`;「保存设置」成功后清 `False`;`save_app_settings()` 或等价保存入口必须返回成功/失败,失败时保留 dirty 并阻止离开。② 程序化回填保护——加载配置、保存后重载、刷新别名写回下拉、`_populate_app_settings()` / `_populate_cmhub_alias_combos()` 等必须用 `_suspend_dirty` 或 `QSignalBlocker` 避免误置脏;用户手动输入后由刷新/测试触发的 Base URL 规整仍应保持 dirty,提醒其保存。③ 离开拦截——`main_window` 切 Tab 与 `closeEvent` 若发现⑤ `_dirty` 为真,弹三选一「保存 / 放弃 / 取消」:保存则调保存入口且成功后再切/关;放弃必须重新从 `config.json` + `config/cmhub.json` 载入并回填控件,再清 dirty,不能只清标志;取消则留在⑤不切不关。`QTabWidget.currentChanged` 是切换后信号,实现时需维护上一 Tab index,并用 `_reverting_tab_change`/类似 guard 防止 `setCurrentIndex()` 递归触发。④ 视觉提示——「保存设置」旁显示「● 未保存更改」,保存或放弃回填后消失。⑤ 「刷新别名/测试连接」成功后提示语补「别名已拉取,记得点『保存设置』持久化」,**不**顺带偷偷保存 url/key(保持显式保存一致性)。只改 ⑤ 设置页、`main_window` Tab 切换/关闭守卫和 GUI 单测(脏标志置位/清除、切 Tab 保存/放弃/取消/保存失败分支、程序化回填不置脏、放弃会还原输入、currentChanged 回退不递归);不改配置 schema、AI HTTP 协议、DB schema、Excel、Shopee/CDP 流程 | DONE |
| T-532 | ⑤ cmhub 连接成功提示显示账号名 | T-531, T-527, T-528 | 问题:⑤「测试连接/查余额」或刷新别名成功后,界面只提示 `cmhub 连接成功:...`,用户无法确认当前 API Key 实际属于哪个 cmhub 账号,尤其多账号或换 Key 时容易误判。方案:成功 payload 中若 `/api/v1/balance` 返回可识别账号信息,则在结果 label 和状态栏优先显示 `cmhub 账号「<账号名>」连接成功:生文别名 X 个,生图别名 X 个;余额 Y;别名已拉取,记得点『保存设置』持久化`;当前接口结构按 `{ "user": "cmhub_user", "points_balance": 88, "account": { "username": "cmhub_user", "display_name": "主账号" } }` 兼容,显示名优先取 `account.display_name/name/account_name/username/email/id`,再兜底 `user` 字符串或 `user.name/account_name/username/email/id`、顶层 `display_name/name/username/email/id`;邮箱需要脱敏展示;没有账号字段、仅刷新别名不查余额或接口未返回用户信息时,保留现有 `cmhub 连接成功:...` 文案。不得把 API Key、token、完整邮箱或原始敏感响应写入 GUI、run log、诊断日志;不改变 cmhub HTTP 协议、配置 schema、保存逻辑、AI 生成流程或 Shopee/CDP 流程。同步 GUI 单测覆盖有账号名、当前接口结构、只有邮箱、无账号名四种成功提示 | DONE |
| T-533 | ② 增量生成:按缺失组件补生成(先标题后封面分离) | T-303, T-520, T-509 | 现象:先关闭「生成封面图片」只生成了标题(`stage=generated/status=success`、`new_cover_path` 为空),之后勾选封面再点「开始生成」,提示「当前筛选结果没有待生成任务」,无法补生成封面;若改用「重置生成结果」会连标题一起清掉、重生成时白扣一次标题点并覆盖 T-509 手动改的标题。根因:`ai.is_generatable_task` 把 `generated+success` 一律判为不可生成,且 `generate_batch` 对每条 eligible 任务都先重生成标题再生成封面。方案:把「开始生成」改为**按缺失组件增量补齐**——① 判定放宽:`generated+success` 且(封面开关 ON 且 `new_cover_path` 为空)判为可生成(只补封面);② `generate_batch` 组件级跳过:`new_title` 已存在则**跳过标题生成**(复用现有标题,不再调 cmhub 生文、不扣标题点、**不覆盖用户手动标题**),封面开关 ON 且 `new_cover_path` 为空才生成封面;③ 进度计数分组件——`title_total`=缺标题任务数、`cover_total`=(封面开关 ON 时)缺封面任务数,不再用 `len(eligible)` 一刀切;④「没有待生成任务」提示只在**按当前设置真的无缺口**(标题全有 且(封面关 或 封面全有))时出现;⑤ 幂等/点数:全组件齐备的任务不纳入、不重复调用扣点。保持 `set_generated` 写库、run_logs、诊断日志、停止取消未开始项语义不变;不改 cmhub HTTP 协议、DB schema、Excel、Shopee/CDP 流程。单测覆盖:只补封面(跳过标题、标题点不增、手动标题不被覆盖)、混合批(部分缺标题+部分缺封面)、无缺口提示无任务、封面关时标题态任务不纳入 | DONE |
| T-534 | ② 重置增强:多选/筛选范围 + 按组件(标题/封面/全部)重置 | T-533, T-404a, T-509 | 现状:②「重置生成结果」只能对单条、且整条全清(`db.reset_generated` 同时清 `new_title` 和 `new_cover_path` 退回 collected)。真实诉求是精准重置——一批标题不满意但封面 OK(只重标题、保封面、不重扣封面点)、或封面差但标题 OK(只重封面、**保留 `new_title` 保护 T-509 手动标题**、不重扣标题点);且需要作用于多条。方案:与 T-533 的组件模型咬合——① 后端 `db.reset_generated` 加组件开关 `reset_title`/`reset_cover`(`delete_file` 仅在重置封面时可选),只清对应字段与 `last_error`,stage 保持 `generated`,由 T-533 泛化后的 `is_generatable_task`(缺任一必需组件即可生成)识别缺口后由「开始生成」只补被清组件;② UI 重置入口给三选项「重置标题 / 重置封面 / 重置全部」;③ 作用域从单条扩到**多选(表格改 `ExtendedSelection`)或当前筛选结果**,与③「作用于当前筛选结果」心智一致;④ 二次确认框显示将重置的标题/封面条数、其中 `committed=1` 条数,并提示「线上已提交、本地重置不回滚 Shopee、重生成后再更新会再次提交线上」+ 重生成会消耗 cmhub 点数;⑤ 运行中禁用;批量写 `run_type=reset` 运行日志,不触碰 Shopee、不回写 Excel。**只重置封面时绝不改 `new_title`**。不改 cmhub HTTP 协议、Excel、Shopee/CDP 流程。单测覆盖:只重标题保留封面、只重封面保留标题(含手动标题不被清)、重置全部、多选/筛选批量、committed 提示、运行中禁用 | DONE |
| T-535 | ② cmhub 生成标题读取等待固定 600 秒 | T-526, T-533 | 问题:当前 cmhub 生文请求的读取等待时间复用 `ai.resolution_timeouts`,会跟随当前分辨率变化;默认 `1k=240s`,若上游模型排队或响应较慢,标题生成容易先超时。方案:cmhub `gen_title()` 的 `title_request` 读取等待固定使用 600 秒(等同 4k 上限),不再跟随当前分辨率;连接超时仍使用 `ai.cmhub.connect_timeout`,重试次数仍使用 `ai.retry`。封面生成与图片下载继续按分辨率读取等待,direct 兼容路径保持现状。补 `tests/test_ai.py` 断言标题请求 timeout 为 `(connect_timeout, 600)`,不改配置 schema、cmhub HTTP 协议、DB、Excel、Shopee/CDP 或 GUI 交互 | DONE |
| T-536 | GUI 按钮圆角全局统一 | T-512, T-513, T-523 | 现象:只有③「开始更新」及少数上色按钮(删除批次/删除账号/未匹配(n))有 `border-radius: 4px`,其余按钮走原生渲染显直角——不一致。根因:圆角是 T-512/T-513 给按钮上色时顺带写进 QSS 的副产品,不是全局形状决策;一旦给 `QPushButton` 设 stylesheet 就放弃原生渲染,才补了 radius/border。方案(**方式 A:全局统一圆角**):① 在 `app/gui/widgets.py` 抽一个**共享按钮基础样式常量/helper**(统一 `border-radius`,如 4px,与卡片 6px 圆角语言协调),并**接管按钮的全部视觉状态**——normal/hover/pressed/disabled/focus 的背景、边框都定义好,避免全局设 QSS 后按钮变扁平方块、丢 hover 反馈;② 在主窗口/app 级用全局 `QPushButton` QSS 应用该基础样式,让**所有按钮共享同一圆角**;③ warning(`startUpdateButton`)与 danger(`_danger_outline_button_style`:删除批次/删除账号/未匹配)按钮改为**只叠加颜色**,复用共享基础样式的圆角/内边距/状态,不再各自重写 radius/border——杜绝“上色=顺带圆角”的隐性耦合;④ 以 Windows 为主目标做一次视觉自测(hover/按下/禁用不劣于原生)。只改 GUI 样式层(`widgets.py` + 主窗口全局 QSS + 各上色按钮引用),不改任何按钮的启用/禁用逻辑、行为、业务流程、DB、Excel、Shopee/CDP。GUI 单测至少断言上色按钮仍带各自语义色且不再各自硬写 radius(改为引用共享样式);圆角外观本身以人工视觉验收为准 | DONE |
## Phase 8 · 工程基础设施后续(`docs/engineering-review.md`)
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-541 | 打包版主窗口初始位置与小屏适配 | T-523, T-537, T-540 | Bug:在 Windows 10 虚拟机/小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` 或 release 包时,主窗口可能贴到屏幕左上角且左边/标题栏显示不全,看不到「蝦皮圈優化助手 v<APP_VERSION>」标题,用户无法拖动窗口到中间。根因:`MainWindow.__init__()` 只调用 `self.resize(1180, 760)`,没有根据 `QApplication.primaryScreen().availableGeometry()` 限制初始尺寸、没有居中、没有保证窗口 frame/title bar 在可用屏幕范围内;PyInstaller 本身不是根因,小屏、高 DPI、任务栏占用、多显示器或 VM 分辨率更容易触发。方案:① 抽一个主窗口初始布局 helper(如 `_fit_and_center_window(window, preferred_size=(1180,760), margin=40)`),读取当前屏幕 `availableGeometry()`;② 初始宽高不超过可用区域减安全边距,保留合理最小值(如 960x640,若屏幕更小则继续适配);③ `resize()` 后按可用区域中心 `move()`,确保 `frameGeometry()` 左上角不小于可用区域左上角,标题栏完整可见;④ offscreen/headless 测试环境不依赖真实屏幕时可跳过或用 mock geometry;⑤ 不保存/恢复历史窗口位置,避免把坏坐标持久化。同步 `docs/packaging.md` 打包验收:在目标 VM/小分辨率环境启动 release exe,窗口标题栏必须完整可见且可拖动。GUI 单测覆盖小屏 mock 下尺寸被限制、窗口被居中/夹进可用区域;不改业务流程、DB、AI、Excel、CDP/Shopee、打包产物结构 | DONE |
| T-525 | 引入 ruff(lint + format)+ 可选 pre-commit | T-006 | 依据 `docs/engineering-review.md` P1。加 `ruff` 配置(lint + format),先以现状为基线不做大规模风格重排,只开启安全规则(未用 import/变量、明显错误);可选 `.pre-commit-config.yaml`;不改业务逻辑;CI(T-522)可串入 ruff 检查。数据模型渐进上 mypy 作为后续可选。当前因业务优先级调整顺延到 T-526~T-528 之后 | TODO |
| T-537 | 品牌名「蝦皮圈優化助手」(显示层中文品牌,cmshopee 保留为代号/标识符) | T-523 | 需求:项目/软件正式定名「蝦皮圈優化助手」。**原则**:产品**显示/品牌名** = 「蝦皮圈優化助手」;`cmshopee` 保留为**代码库/仓库/技术代号**,一切**标识符不改**——遵项目「文档与 UI 文案用中文、标识符用英文」规则,且避免非 ASCII 文件名破坏现有用户数据。**改(显示/品牌层)**:① `app/gui/main_window.py` 窗口标题 `setWindowTitle("cmshopee")` → `"蝦皮圈優化助手"`(任务栏/标题栏显示中文品牌);② 文档产品名散文 `docs/00-ai-start-here.md`、`docs/README.md`、`AGENTS.md`(及 vision/brief 若有)把「cmshopee 是一个…」改为「蝦皮圈優化助手(代号 cmshopee)是一个…」,明确品牌名并保留代号;③ 空的 `README.md` 补品牌标题行;④ 可选:`app/gui/__init__.py` PySide6 缺失启动失败提示带上品牌名。**不改(技术标识符,保留 cmshopee)**:`app` 包名、`cmshopee.exe` 打包产物名、`cmshopee.db`(+`-wal/-shm`)/`db_path` 默认值、`cmshopee.spec`、`cmshopee.log`、`config.json`/`config/*.json` 文件名、slug/`chrome_user_data_dir`、仓库目录;`app/editor.py` 注入页面的 JS 全局 `window.__cmshopee_*`(JS 标识符须 ASCII)、`app/ai.py` multipart boundary `----cmshopee-*`、`app/workers.py` `thread._cmshopee_worker`、`app/chrome.py` 快捷方式内部前缀 `cmshopee-`。**决策点**:`app/excel.py` 回写文件名后缀 `_cmshopee回写` 为用户可见产物名——本任务**默认保留**(改动会影响已生成副本命名习惯),如需改「_優化助手回写」另行确认。**影响**:不改 db/exe/spec/包名 → 打包、更新流程、现有用户数据零影响。验收:窗口标题栏/任务栏/README/相关文档显示「蝦皮圈優化助手」;`cmshopee.db`/exe/包名不变;GUI 单测若断言窗口标题须同步更新;`python -m compileall app main.py` + `python -m unittest discover -s tests` 全绿。边界:纯显示/文档层,不改任何文件名、schema、流程、CDP/Shopee 逻辑 | DONE |
| T-538 | 打包产物用户数据收进 `data/` 子文件夹(便携方案 1) | T-524 | 问题:当前 onedir 产物把用户数据(`config.json`、`config/ai_models.json`、`config/cmhub.json`、`cmshopee.db(+-wal/-shm)`、`chrome_user_data_dir/`、`images/`、`logs/`、`prompts/`、`title_prompt.txt`)生成在 exe 顶层,和程序文件/依赖文件混放:更新易误删数据、放进 Program Files 只读目录会写失败/被 VirtualStore 重定向、程序与数据关注点混杂。方案(保持便携):① **统一数据根**——所有用户数据落到 exe 同级的单个 `data/` 子文件夹;`app/appconfig.py` 用显式 `data_dir`(默认 `<exe目录>/data`,源码运行时可为项目根/`data`)解析并返回绝对路径,**替代现在 `main.py` 的 chdir**(chdir 影响全进程相对路径/文件对话框默认目录,脆弱);文件名保持不变(仍是 `cmshopee.db` 等),只是移入 `data/`。② **旧布局迁移**——新版首次运行若在 exe 顶层发现旧的 `config.json`/`cmshopee.db` 等,自动迁移进 `data/`(或明确提示手动迁移),不得对老用户生成空 `data/` 丢失原数据。③ **可写性检测**——启动时若 `data_dir` 不可写(如放在 Program Files),弹清晰错误「请把程序放到可写目录,勿放 Program Files」并优雅退出,不静默失败。④ **发布只含程序**——`cmshopee.spec`/`scripts/build_exe.ps1` 构建检查确保不打包 `data/` 或任何用户数据;发布 zip 不含 `data/`,从而「整包解压覆盖」也安全(覆盖程序文件、不碰 `data/`);T-540 后当前打包脚本固定 PyInstaller 6.11.1,产物为 `cmshopee.exe` + `_internal/` onedir,具体版本和 release 命名由 T-540 维护。⑤ **更新流程前提**——原地更新=换程序留 `data/`,依赖:DB 迁移幂等且只向前(现 `init_db`+`ALTER TABLE ADD COLUMN`)、`config` 向前兼容(`load_config` 补默认值)。同步更新 `docs/packaging.md`(目录结构、首次运行数据位置、§5 更新步骤简化为「覆盖新版程序文件和依赖文件,`data/` 不动」)。测试:`test_appconfig` 断言各路径解析进 `data_dir`、可写性检测行为、旧布局迁移;`test_packaging` 断言产物不含 `data/`/用户数据。边界:只改路径解析层(`appconfig` 路径 + `main.py` 数据根 + 迁移/可写检测)、spec/构建脚本、打包文档;不改文件名、DB schema、业务逻辑、AI/Excel、CDP/Shopee 流程 | DONE |
| T-539 | ⑤ 隐藏数据路径设置(设置简化,非防逆向) | T-538, T-517 | 背景:T-538 后用户数据固定收进 exe 同级 `data/`,⑤「路径与端口」里的 DB 路径(`dbPathEdit`)、图片目录(`imageDirEdit`)、账号数据根目录(`userDataRootEdit`)已不该由用户改动——改了会指向 `data/` 之外导致数据分裂/丢失。**定性说明**:本任务是**设置简化**,与 T-517/T-529 隐藏 `test_item_id`/`dry_run` 同一思路;**不是防逆向手段**——`cmshopee.db` 在磁盘上任何 SQLite 浏览器可直接打开,隐藏 UI 字段对逆向无意义,防逆向由另行任务(发布包加固/价值上移 cmhub)承担。方案:① ⑤普通设置页**隐藏** `db_path`/`image_dir`/账号数据根三个输入框(不再占表单位置),`config.json` 对应字段保留为内部兼容——手工编辑配置仍生效(内部回滚路径),加载/保存不丢字段;② **保留可见**:Chrome 路径(每台机器安装位置不同,真实配置需求)与默认端口/端口区间/CDP 就绪超时(端口冲突需可调);③ 首次运行/迁移后各路径默认解析到 `data/` 下(沿用 T-538 的 `data_dir` 规则),无需用户感知。同步 `docs/routes.md` ⑤ 说明与 GUI 设置单测(隐藏字段不再可见、config 字段仍读写、手工配置值仍被尊重)。边界:只改⑤设置页展示层与文档;不改 `config.json` schema、路径解析逻辑(T-538 范围)、DB、业务流程、CDP/Shopee | TODO |
| T-540 | 打包版本号与 release 产物命名统一 | T-537, T-538, T-524 | 参考 `D:\chengma\cmbot` 的轻量版本控制方式,但本阶段不引入 Launcher/manifest/自动更新。方案:① 新增 `app/version.py`,统一维护 `APP_NAME="蝦皮圈優化助手"`、`APP_CODE_NAME="cmshopee"`、`APP_VERSION`,作为 GUI 标题、打包脚本、release 目录、zip 文件名和 `version.txt` 的唯一版本源;② 主窗口标题改为 `蝦皮圈優化助手 v<APP_VERSION>`,PySide6 缺失提示继续读中文品牌名;③ `scripts/build_exe.ps1` 从 `app/version.py` 读取版本,并固定通过 Windows Python Launcher 调用 `py -3.10`,返回版本必须是 `3.10.x`;④ PyInstaller 仍输出 `dist\cmshopee\` 作为中间产物,再组装 `release\cmshopee-<APP_VERSION>\` 和 `release\cmshopee-<APP_VERSION>-portable.zip`,发布目录内写 `version.txt`(无 BOM)和中文 `README.txt`;⑤ 发布目录/zip 仍不得包含 `data/`、DB、配置、图片、日志、Chrome 登录态或运营 Excel;当前 PyInstaller 6.11.1 onedir 结构必须包含 `cmshopee.exe` + `_internal\`,脚本会校验 `_internal\` 存在;⑥ 测试覆盖版本文件解析、窗口标题版本、构建脚本 release 命名/排除数据/固定 `py -3.10` 打包。边界:只做手动发版版本号一致性和 release 组装;不做在线更新、启动器、manifest、sha256 下载校验、强制更新或自动替换程序目录 | DONE |
## 里程碑
- M1:editor(含采集)模块化、config.json + SQLite + AI 模型清单后端 + 测试基座就绪(Phase 0)。
- M2:账号管理可用、能登录与检测(Phase 1)。
- M3:导入 + 采集 + 回写旧数据(Phase 2)。
- M4:AI 生成新标题/新封面(Phase 3)。
- M5:对已生成任务弹窗批量确认后更新 Shopee + 回写结果(Phase 4);真实线上单条冒烟延后到设置安全开关完成后执行。
## 待办池(Backlog)
- 标题规则/提示词模板库。
- 上传弹裁剪框商品的自动裁剪。
- 账号登录态健康巡检。
- AI 生成图的合规/质量自检。
- (已提升为 T-518)② 左栏提示词区组件密度优化:封面模板低频动作(另存为/重命名/删除)收敛进②本页「模板操作」菜单/小按钮;第一版不移入⑤,不改提示词数据结构。
- 界面深色主题:为 `docs/ui-color-design.md` 语义色板另出深色等义映射(当前只服务浅色)。
- 状态栏语义色:依据 `docs/ui-color-design.md` 的状态栏建议,后续统一 `MainWindow.show_status(message, level)`,用 muted/info/success/warning/danger 区分普通、进行中、成功、需处理、失败提示;只改状态栏文字色,不做大面积背景,不替代弹窗/空状态/运行日志。
- DB 版本化迁移:引入 `PRAGMA user_version` + 有序迁移列表,替代当前 ad-hoc `ALTER TABLE ADD COLUMN`(`docs/engineering-review.md` P2)。
- CDP/editor 自动回归:录制 DOM 快照 / mock-CDP 夹具做选择器形状回归,针对 T-404 高频故障源(`docs/engineering-review.md` P2)。
- 宽异常审计:确认 `app/` 中 88 处 `except Exception` 均落诊断日志、无静默吞异常(`docs/engineering-review.md` P2)。