Files
cmshoppe/docs/06-tasks.md
T

158 lines
39 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` 不符合版本时用 `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-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`、清空 `last_error`,把任务退回 `stage=collected/status=success`,默认不删除本地新封面文件,可在确认框提示路径;③ 增加「重置更新状态」按钮,仅作用当前选中单条,保留 `new_title/new_cover_path`,确认后把本地状态退回可更新(`stage=generated/status=pending`、清空 `last_error`),用于重复测试上传/提交。若记录 `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 |
## Phase 8 · 工程基础设施后续(`docs/engineering-review.md`)
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| 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 |
## 里程碑
- 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` 语义色板另出深色等义映射(当前只服务浅色)。
- 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)。