Files
cmshoppe/docs/06-tasks.md
T

73 KiB
Raw Blame History

任务看板(Tasks)

把目标拆成小步、可独立交付的任务,让 AI 一步一步开发,避免一次生成整个项目。

使用规则

  1. 一次只做一个任务:每轮只领取一个 TODO 且依赖均 DONE 的任务,取最靠前的。
  2. 做完即停:完成、自测通过、改 DONE 后汇报。
  3. 不跳步:依赖未完成不开工。
  4. 完成定义:以 编码规则 验证清单为准。
  5. 动手前读 00-ai-start-here.md、05-coding-rules.md、current-state.md、04-architecture.md(尤其第七节已验证事实、第二节流水线)。
  6. 完成后同步本文状态,追加 ../progress.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① 批次筛选旁加「删除批次」按钮,仅在选中具体批次(非「全部批次」)时可用,运行中禁用。二次确认弹窗显示批次标签、任务数、已上线任务数并提示「软删除只隐藏本地批次,不会回滚蝦皮 线上修改」;图片默认保留,不做自动清理,后续若需要清理图片另做独立工具。确认后软删除、刷新①/②/③列表和批次筛选 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生成和③ 更新蝦皮筛选栏增加商品ID输入框;输入内容按包含匹配 item_id,清空表示全部;当前筛选结果和「开始生成」/「开始更新」范围都受其约束;③确认弹窗显示商品ID筛选条件 DONE

Phase 4 · 更新蝦皮(③)

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生成和③ 更新蝦皮列表无法重置选中记录的问题:③「重置更新状态」保留 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,确认框必须醒目提示“线上已提交过,本地重置不会回滚蝦皮,重复更新会再次提交线上”,并保留 committed 历史事实/运行日志;运行中禁用;两类重置都写 run_type=reset 运行日志,不触碰蝦皮、不回写 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 角色下拉按类别过滤;普通默认 cmhub 模式下返回超时只读展示实际口径「标题 600 秒 / 图片 650 秒」,分辨率只控制生图尺寸;direct 兼容路径仍按 resolution_timeouts 自动;持久化 config.json DONE
T-501c Tab⑤ 设置 · 蝦皮更新安全开关 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 导入/回写、③ 更新蝦皮步骤级日志、④ Chrome 启动/登录检测、⑤ AI模型测试连接;数据库日志保存业务可读步骤和错误,本地 log 保存脱敏 traceback;不得记录 Cookie、密码、API Key、token DONE
T-506 正式使用批量更新体验:③ 检查本轮更新 + 分批更新全部 + ⑤ 设置页三列布局 T-501c, T-504 ③ 增加「检查本轮更新」按钮替代用户可见 dry-run;「开始更新」对当前筛选结果按每批最大更新条数自动分批,确认弹窗显示总数/每批/预计批次,停止为当前商品安全结束后不再开始新任务;⑤ 设置页使用居中内容区,左右留白已缩短为 T-506 初始实现约 40%,模型详情/角色与生成参数/路径与端口/蝦皮更新安全均使用响应式三列布局,长字段跨列;「多账号并行更新」与「最大并行账号数」合并为同一个横向组件,最大并行账号数紧跟其后且不换行 DONE
T-507 正式批量更新:移除普通流程测试商品 ID 限制 T-506 普通正式更新不再以 test_item_id 阻断当前筛选结果;③ 确认弹窗不再显示测试商品 ID;⑤ 普通设置页隐藏测试商品 ID,仅保留历史/调试兼容字段;仍保留允许真实提交、允许更新封面、分批、二次确认、账号就绪预检和运行日志 DONE
T-508 ③ 更新蝦皮生产化操作区 T-401, T-404a, T-506 「开始更新」作为主操作视觉强化;「重置更新状态」从底部按钮移到任务表右键菜单/高级入口,与批量更新物理分离,运行中禁用;更新安全拦截弹窗写明具体开关并提供「前往设置」深链到⑤,不改 Shopee/CDP 更新流程 DONE
T-509 ② 新标题人工微调 T-303, T-401 ②任务表「新标题」列可编辑;仅允许已生成、未提交线上、非运行中任务;编辑写回 tasks.new_title,保留 stage=generated,清空 last_error,状态回到可更新;不触碰蝦皮/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(保留文字);③ 更新蝦皮 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。⑤ 视觉分区:将高频「蝦皮更新安全 / 执行模式」前置成独立分区,低频「基础设施(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生成和③更新蝦皮进入模块或启动新一轮任务时仍显示上一次运行日志,用户容易把旧失败、旧进度误认为本轮状态。方案:点击②「开始生成」、③「检查本轮更新」或③「开始更新」时,先清空对应界面的可见日志显示区,并写入本轮开始摘要;运行中只追加本轮日志,完成后保留本轮日志。只清空界面文本,不删除 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/连接超时重试,生图读超时绝不自动重发;T-535 后 cmhub 生图请求和图片下载读取等待统一固定 650s。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/别名下拉、模型详情、生成参数、路径端口、蝦皮更新安全等)接 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 条数,并提示「线上已提交、本地重置不回滚蝦皮、重生成后再更新会再次提交线上」+ 重生成会消耗 cmhub 点数;⑤ 运行中禁用;批量写 run_type=reset 运行日志,不触碰蝦皮、不回写 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。2026-07-07 补充:cmhub 生图请求与图片下载读取等待统一固定 650 秒,读超时仍不自动重发;⑤设置页「返回超时」标签在普通默认 cmhub 模式下展示「标题 600 秒 / 图片 650 秒」,避免用户误以为分辨率下拉仍会改变 cmhub 等待时间;direct 兼容路径保持现状。补 tests/test_ai.py 断言标题请求 timeout 为 (connect_timeout, 600)、生图请求和下载 timeout 为 (connect_timeout, 650),补 tests/test_gui.py 断言 cmhub 标签固定展示实际口径、direct 仍展示分辨率映射;不改配置 schema、cmhub HTTP 协议、DB、Excel 或 Shopee/CDP DONE
T-545 ② cmhub 生图和下载并发上限 5 + 独立下载线程池 T-535, T-533, T-519 背景:实测 10 图片并发时,cmhub 后台单条生成小于 200 秒,但本地下载 /media/generated/images/*.png 常见 24~185 秒且有连接失败;如果每个生图线程同时负责“等待 cmhub 返回 + 下载 + 转 JPEG + 保存”,下载慢会占住生图线程,后续任务排队。方案:cmhub 模式下实际生图请求并发 = min(ai.image_concurrency, 5);拿到 image_url 后把下载/转 JPEG/保存交给独立下载线程池,下载线程数与实际生图请求并发一致、同样最大 5;不新增用户可见配置项,⑤仍只保留「图片并发」。②开始日志必须显示用户设置图片并发、cmhub 实际生图并发和下载并发;分段日志继续记录“cmhub 已返回 image_url / 下载完成 / 本地保存完成”的耗时。下载失败按当前任务失败记录,但不得重新调用 cmhub 生图接口导致重复扣点;cover_done/generated_done 必须等下载保存成功并写 DB 后才计数;停止逻辑继续取消未开始项,运行中请求/下载允许自然完成或失败。direct 兼容路径暂不改变。补 tests/test_ai.py 覆盖并发上限和下载线程池不阻塞后续生图提交、下载失败不重复扣点;补 tests/test_gui.py 覆盖开始日志显示实际并发 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
T-546 cmhub 客户端共享 Session + 连接池 + 代理处理(补 T-545 未覆盖的下载慢根因) T-545, T-526 现象:实测生图拿到 image_url 后本地下载单条约 191 秒、而同一 URL 用 curl 约 10 秒;日志伴随 connect_timeout: 连接 cmhub 超时。T-545 已做「生图并发上限 5 + 独立下载线程池」解决“下载堵住生图槽位”的流水线问题,但未根治单条下载在并发争抢下变慢。根因(代码核实):① cmhub 所有 HTTP(_cmhub_call_once 的生成/models/balance、_download_cmhub_image 的下载)都是裸 requests.get/requests.request、无共享 Session,每个任务全新 TCP+TLS,叠加同步生成长连接(单条 60260s)与 connect_timeout 触发的重试,形成连接风暴/连接饥饿;② requests 默认 trust_env=True 读 HTTP(S)_PROXY/ALL_PROXY,若系统带慢代理会拖累,而干净窗口的 curl 直连快。方案:① cmhub 所有 HTTP 统一走一个模块级共享 requests.Session,挂 HTTPAdapter(pool_connections/pool_maxsize),池大小 ≥(实际生图并发 + 下载并发)以免连接不足排队、复用连接减少握手与 connect_timeout;生成与下载共享该 Session(requests.Session 跨线程发请求安全,但连接池要够大);② 代理处理——诊断日志脱敏记录 cmhub 请求是否经代理;对 cmhub 请求提供明确策略(可配 trust_env=False 或显式 proxies),避免误走慢代理,默认可先保持读环境但提供关闭开关;③ 复测口径——Session+连接池到位后单条下载耗时应回落到与 curl 同量级;若在 T-545 的并发上限 5 下仍显著慢,评估把 cmhub 生图默认并发再降(12);④ 确认 connect_timeout 重试退避不加剧连接风暴。诊断建议:可先临时把图片并发设 1 复测以区分“并发争抢”与“代理”。边界:只改 app/ai.py 的 cmhub HTTP 客户端层(+ 可选 config 代理项)+ 诊断日志 + 相关文档;不改 cmhub 协议、T-545 的并发/下载池语义、生成编排、DB、Excel、CDP/Shopee。测试:tests/test_ai.py 覆盖共享 Session 被复用(mock 同一 session 多次调用)、代理配置被尊重、连接池大小设定;现有 cmhub 用例保持绿。落地:app/ai.py 加模块级 _cmhub_session()(HTTPAdapter 池 CMHUB_HTTP_POOL_SIZE=32)+ _apply_cmhub_proxy();生成/下载/models/balance 统一走该 Session;ai.cmhub.use_system_proxy 默认 false(绕过系统代理,公网网关直连),_cmhub_runtime 读取并应用、fetch_cmhub_models/balance 加同名参数;test_ai.py 补 3 项新测试并把原 18 处 requests mock 改到共享 Session;243 单测全绿。待 Windows 实网复测确认下载提速(troubleshooting 已加「cmhub 图片下载很慢」排障节与代理定位命令) DONE
T-548 cmhub 图片下载改用系统 curl(Win10+ 自带) T-546, T-538 背景:图片下载慢的另一手段——用 curl(用户的快基准)下载。curl 在 Windows 10+ 自带(C:\Windows\System32\curl.exe),无需捆绑(不用项目里 wget/ 那个 2022 老 wget,其 OpenSSL 太旧且捆绑二进制易被杀软误报);应用底线本就是 Win10+(PySide6/Qt6 不支持 Win7)。前置说明:若 T-546 的代理绕过已让 requests 下载回落到 curl 同量级,本任务可作可选/降级;此处按用户要求落地。方案:① 只改图片下载这一步(app/ai.py _download_cmhub_image),cmhub 生成/models/balance 仍走 requests 共享 Session。② 后端选择:新增 config ai.cmhub.download_with_curl(auto/true/false,默认 auto=Windows 且检测到 curl 用 curl,否则回退 requests);shutil.which("curl") 或固定 System32\curl.exe 检测;非 Windows / 无 curl / curl 执行失败 → 自动回退现有 requests 下载(保证 Linux/Mac/CI/无 curl 环境和现有测试不受影响)。③ 安全防护(硬要求):a) SSRF 不可绕过——调 curl 前仍执行 _assert_public_http_url() 拦内网/回环 IP,用 subprocess.run([...], shell=False) 参数列表、不拼 shell;b) token 不泄漏——image_url 带 ?token=...,直接进命令行会出现在进程列表(tasklist)和日志,改用 curl -K <临时配置文件>(把 url = "..." 写进临时文件传入、用完删)避免 URL 进程列表暴露,日志一律脱敏、不打印完整 URL/命令;c) 绕过代理——按 use_system_proxy:false 时加 --noproxy "*"(这才是 curl 快的关键,与 T-546 一致),true 时不加。④ 超时/大小:--connect-timeout <connect> + --max-time <read≈650>;下载到临时文件、读回字节后仍复用现有 _save_jpeg 重编码;保留 20MB 上限(--max-filesize + 落盘后大小复核)。⑤ 重试:沿用现有 _download_cmhub_image_with_retry(3 次),只替换内层下载实现;curl 非零退出=失败、走现有重试语义。⑥ 打包:curl 系统自带、不捆绑、不改 spec;docs/packaging.md 注明依赖 Win10+ 自带 curl。测试:tests/test_ai.py mock subprocess.run——断言 curl 参数含 --noproxy/超时/-K 临时配置(URL 不在 argv)、SSRF 校验先于 curl、curl 失败回退 requests、非 Windows/无 curl 自动回退;现有下载用例保持绿。边界:只改 _download_cmhub_image 下载后端 + config 项 + 诊断脱敏 + 打包文档;不改生成/API 调用、_save_jpeg、并发/重试语义、DB、Excel、CDP/Shopee 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-542 GUI 用户可见 shopee/Shopee 文案统一改为「蝦皮」 T-523, T-537 需求:运行程序后,GUI 上用户能看到的 shopee / Shopee 字符统一改成中文「蝦皮」,例如主 Tab「③ 更新shopee」改为「③ 更新蝦皮」,设置分区「Shopee 更新安全 / 执行模式」改为「蝦皮更新安全 / 执行模式」,引导、弹窗、状态栏、运行日志、空状态、右键/确认文案里的“更新 Shopee / 未打开 Shopee / 不回滚 Shopee”等也改为「蝦皮」。范围:只改用户可见文案与 GUI 测试断言,包括 app/gui/widgets.py 的 TAB_TITLES、MainWindow 里依赖 Tab 文案的判断、tabs/apply.py、tabs/settings.py、tabs/collect.py、tabs/generate.py、gui/workers.py 中的可见提示和相关 tests/test_gui.py;同时修正 app/editor.py 里会冒到 GUI 的自定义错误文案。边界:不得改内部标识符、配置键、DB 字段、文件名、URL、域名、CDP 选择器和第三方原始错误,例如 shopee_update、cmshopee、shopee待处理任务模板.xlsx、seller.shopee.tw、.shopee-image-manager、window.__cmshopee_*、cmshopee.db 必须保持原样;代码注释/技术文档中指平台事实可继续写 Shopee。验收:`rg -n "更新shopee Shopee 更新安全
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 之后 DONE
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 DONE
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
T-543 状态栏语义色与统一提示入口 T-511, T-523, T-536 需求:当前左下角状态栏提示几乎都通过 statusBar().showMessage(...) 直接写入,颜色一致,用户很难快速区分“已保存/已完成”“正在处理”“没有可生成任务”“未登录/数据目录不可写”等不同严重程度。方案:① 在 MainWindow 增加统一入口 show_status(message, level="muted"),使用 docs/ui-color-design.md 已定语义色板映射 muted/info/success/warning/danger,内部先设置状态栏文字颜色再 showMessage(),普通/就绪提示必须重置为 muted 或默认色,避免上一条红/绿状态残留;② 逐步替换主窗口和 ①②③④⑤ Tab 传入的 status_callback=self.statusBar().showMessage 以及直接调用,新增代码必须显式传 level,不长期依赖中文关键词猜测;③ 颜色口径:普通/导航/就绪用 muted 或默认,进行中用 info,保存/完成/回写成功用 success,当前筛选无任务、请先采集/配置等用户可处理问题用 warning,失败、阻断、未登录、数据目录不可写、点数不足等中止当前动作的问题用 danger;④ 状态栏只改文字色,不做大面积背景,不替代弹窗、空状态、按钮禁用、运行日志或任务状态列;⑤ 复用 app/gui/widgets.py 语义色常量,不新增第二套颜色;⑥ GUI 单测覆盖 show_status() 各等级颜色、普通提示会清掉前一条错误颜色,以及至少一个保存成功/无任务/失败路径调用正确等级。边界:只改 GUI 提示层和测试;不改业务流程、DB schema、AI HTTP、Excel、Shopee/CDP、运行日志持久化 DONE
T-544 启动时检查强制升级(第一版:提示下载,不自动覆盖) T-540, T-538, T-524 需求:程序启动后先请求服务器版本接口;如果线上版本高于当前程序版本且服务端要求强制升级,则必须弹窗阻断进入主界面,用户只能下载新版或退出。第一版方案:① 新增 app/update_check.py,读取当前版本 APP_VERSION,请求固定版本接口(后续可放配置/构建常量),超时短且错误中文化;② 服务端返回建议结构:latest_version、min_supported_version、force_update、download_url、sha256、message。比较规则:当前版本 < min_supported_version 必须升级;或 force_update=true 且当前版本 < latest_version 必须升级;版本比较使用语义化数字段比较,不用字符串字典序;③ 启动流程在 appconfig.prepare_data_dir() 成功后、创建 MainWindow 前执行版本检查;命中强制升级时弹中文阻断框,显示当前版本/线上版本/升级说明,按钮只保留「下载新版」和「退出程序」,点击下载用系统浏览器打开 download_url 后退出或保持阻断,不允许继续进入主界面;④ 网络失败、接口超时、JSON 非法、版本字段缺失时第一版允许进入软件,只在本地诊断日志记录/可选状态提示,不因服务器或用户断网导致全员不可用;只有服务端明确返回强制升级才阻断;⑤ 第一版不自动下载并覆盖 cmshopee.exe 或 _internal/,不删除/覆盖 data/,不做 Launcher、后台替换、增量补丁、签名校验或回滚;用户仍按打包文档手动下载 zip、关闭程序、覆盖程序文件和 _internal/、保留 data/;⑥ 单测覆盖版本比较、强制升级判定、网络失败允许进入、非法响应允许进入、启动流程强制阻断弹窗和打开下载链接。边界:只做启动检查与强制提示,不改 DB schema、业务流程、AI、Excel、Shopee/CDP,不自动替换任何本地文件 DONE
T-547 ⑤ 并发/重试上限收紧(标题/图片并发≤5、失败重试≤10) T-517, T-545 需求:把「标题并发数」「图片并发数」上限设为 5、「失败重试次数」上限设为 10。现状:app/gui/tabs/settings.py 里 title_concurrency_spin/image_concurrency_spin 范围 (1,64)、retry_spin 范围 (0,20);默认 4/4/2;cmhub 生图内部已按 min(image_concurrency,5) 限流(T-545),本任务把用户可选上限与之对齐。方案:① UI 范围——title_concurrency_spin.setRange(1,5)、image_concurrency_spin.setRange(1,5)、retry_spin.setRange(0,10);默认 4/4/2 不变(在新范围内)。② 真实约束(不只 UI)——app/appconfig.py 加载/保存 ai 段时把 title_concurrency/image_concurrency 夹到 1..5、retry 夹到 0..10,兼容旧 config.json 里的超限值(如 image_concurrency=64 归一到 5、retry=20 归一到 10),避免手工配置或历史配置绕过上限。③ 生成编排语义不变:T-545 的 min(image_concurrency,5) 仍成立、与新上限一致;标题阶段用 title_concurrency(现≤5)。测试:test_gui 断言三个 spin 的新范围;test_appconfig 断言超限 config 值被夹紧、正常值不变。边界:只改⑤设置页 spin 范围 + appconfig 夹紧/校验 + 单测;不改默认值、生成编排逻辑、cmhub HTTP、DB、Excel、CDP/Shopee DONE

里程碑

  • M1:editor(含采集)模块化、config.json + SQLite + AI 模型清单后端 + 测试基座就绪(Phase 0)。
  • M2:账号管理可用、能登录与检测(Phase 1)。
  • M3:导入 + 采集 + 回写旧数据(Phase 2)。
  • M4:AI 生成新标题/新封面(Phase 3)。
  • M5:对已生成任务弹窗批量确认后更新蝦皮 + 回写结果(Phase 4);真实线上单条冒烟延后到设置安全开关完成后执行。

待办池(Backlog)

  • 标题规则/提示词模板库。
  • 上传弹裁剪框商品的自动裁剪。
  • 账号登录态健康巡检。
  • AI 生成图的合规/质量自检。
  • (已提升为 T-518)② 左栏提示词区组件密度优化:封面模板低频动作(另存为/重命名/删除)收敛进②本页「模板操作」菜单/小按钮;第一版不移入⑤,不改提示词数据结构。
  • 界面深色主题:为 docs/ui-color-design.md 语义色板另出深色等义映射(当前只服务浅色)。
  • (已提升为 T-543)状态栏语义色:依据 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)。