2026-06-26 15:30:37 +08:00
# 架构设计
> 本文讲“怎么把技术栈搭起来”:模块职责、存储模型、流水线、CDP 已验证事实、开发顺序。
> 具体用了哪些库 / 平台,见 [技术栈](03-tech-stack.md)。
## 一、系统结构
2026-07-17 12:24:05 +08:00
Windows 本地桌面自动化工具,无后端服务。当前正式 GUI 显示 6 个工作流 Tab;商品套图使用新的原生 PySide6 界面,并复用既有 `image_studio_*` 数据与生图服务。
2026-06-26 15:30:37 +08:00
```text
运营(人)
|
v
2026-07-14 09:53:13 +08:00
GUI( PySide6 QTabWidget,当前显示 6 Tab)
2026-07-17 12:24:05 +08:00
① 导入采集 ② AI生成 ③ 更新蝦皮 账号管理 设置 商品套图
2026-06-26 15:30:37 +08:00
|
v
核心模块(Python)
2026-07-07 14:12:32 +08:00
├── appconfig 读应用配置 data/config.json( Chrome 路径、目录根、AI 配置、端口范围…)
2026-06-26 15:30:37 +08:00
├── db SQLite 读写:账号、任务、各阶段结果(cmshopee.db)
├── excel openpyxl 导入输入列 / 回写输出列到原 Excel
├── config 账号 ↔ user-data-dir 绑定、slug、目录创建
2026-06-27 10:30:45 +08:00
├── accounts 账号 CRUD 服务:生成 slug/目录、启动登录、检测登录
2026-06-26 15:30:37 +08:00
├── chrome 按账号拼启动参数、启动/探测 Chrome、生成快捷方式
├── cdp CDP 客户端(连接、找/开 tab、执行 JS、拖拽)
2026-07-18 16:34:37 +08:00
├── editor 登录检测 / 检测商品状态 / 采集旧标题旧封面 / 改标题 / 换封面 / 点更新
├── product_status 商品状态代码、显示和分组规则
2026-07-17 12:24:05 +08:00
├── ai 文本生成(提示词+旧标题→新标题)/ 图像生成(提示词+旧封面→新封面)/ 商品原图理解(商品套图AI帮写)
2026-07-14 09:53:13 +08:00
├── image_studio 商品套图项目/资产/job数据服务(兼容旧AI工场终选)
├── product_suite 套图设置归一化、数量计算、完整提示词与job规划
├── image_studio_images 远程原图安全下载、本地原图导入、生成图废纸篓
2026-07-11 14:26:01 +08:00
├── image_studio_generation cmhub 托管多图异步 submit/poll/download 编排
└── image_studio_export 终选图片本地 JPEG 转码、目录安全导出
2026-06-26 15:30:37 +08:00
|
v
2026-07-04 17:59:23 +08:00
Google Chrome(每账号独立 --user-data-dir + --remote-debugging-port) + AI 服务(默认 cmhub 网关;direct 仅内部兼容/回滚)
2026-06-26 15:30:37 +08:00
|
v
Shopee 卖家中心页面 / 本地图片目录
```
真实组件:
2026-07-14 09:53:13 +08:00
- GUI 入口:根目录 `main.py` 调用 `app/gui/` 包(PySide6 + `QMainWindow` + `QTabWidget` ,当前正式界面显示 6 Tab);包入口 `app/gui/__init__.py` 提供 `main()` 并兼容 `from app import gui` / `from app.gui import MainWindow` ;也支持 `python -m app` 。第六 Tab 只创建 `ProductSuiteTab` ;旧 `ImageStudioTab` 保留内部兼容,但不加入顶层 `QTabWidget` 。
- 核心模块统一放在正式代码包 `app/` : `appconfig.py` 、`db.py` 、`excel.py` 、`config.py` 、`accounts.py` 、`chrome.py` 、`editor.py` 、`workers.py` 、`ai.py` 、`prompts.py` 、`image_studio.py` 、`product_suite.py` 、`image_studio_images.py` 、`image_studio_generation.py` 、`image_studio_export.py` ; CDP 底座迁入 `app/cdp.py` (当前根目录 `cdp.py` 为已验证来源)。
2026-06-26 15:30:37 +08:00
- 已验证脚本(重构进模块):`prototypes/demo.py` 、`prototypes/set_title.py` 、`prototypes/set_cover.py` 、`prototypes/get_title.py` 、`prototypes/cookies.py` 、`prototypes/inspect_images.py` 、`prototypes/grab.py` 。
2026-07-07 14:12:32 +08:00
- 外部依赖:本机 Google Chrome; Shopee; AI 服务(文本+图像;普通产品默认 cmhub 网关,由 `data/config.json` 的 `ai.cmhub` + `data/config/cmhub.json` 配置;direct 直连模型清单仅作为内部兼容/手工回滚路径保留);`openpyxl` 。
2026-06-26 15:30:37 +08:00
## 二、流水线(核心)
2026-06-26 16:27:33 +08:00
每个任务(Excel 行)依次走过 4 个业务阶段,状态字段 `stage` 贯穿全程:
2026-06-26 15:30:37 +08:00
```text
imported → collected → generated → applied
(导入 Excel) (采集旧数据) (AI 生成新数据) (改 Shopee 并提交)
① Tab ① Tab ② Tab ③ Tab
```
- **imported**: openpyxl 解析输入列入库。
- **collected**:只读打开商品页,读旧标题、下载旧封面到本地,写 `old_title/old_cover_path` ,回写 Excel 旧字段。
2026-07-13 15:22:15 +08:00
- **generated**:AI 按②「生成内容」下拉生成新标题和/或新封面;选择只生成标题时 `new_cover_path=NULL` ;选择只生成封面时不调用生文,封面标题上下文取 `new_title or old_title` ,只写 `new_cover_path` ,因此“有新封面、无新标题”是合法组件状态;选择生成标题和封面时可按缺失组件增量补齐。**不设逐条人工审核阶段**,生成成功即可进入 ③;③ 再按「更新内容」下拉决定只更新标题、只更新封面或更新图文。
2026-06-26 15:55:28 +08:00
- **applied**:③ 点击「开始更新」后弹窗确认当前筛选范围和任务数量;确认后打开编辑页换标题+封面,逐条点「更新」提交线上,回写结果。
2026-06-26 15:30:37 +08:00
任意阶段失败 → `stage` 不前进、记 `error` 、`result/skipped` ,不影响其他任务。
## 三、职责划分
2026-07-17 12:24:05 +08:00
**GUI(当前显示 6 Tab) ** :见 [routes.md ](routes.md )。只做交互与预览,不写业务逻辑;耗时操作走 PySide6 `QObject` worker + `QThread` ,用 signal 回主线程刷新 UI。**① 采集点击后会为本轮匹配到的账号自动确保 Chrome 就绪:已打开则复用,未打开才启动;随后只检测登录态,未登录账号的任务跳过并汇总提示前往账号管理人工登录。③ 更新蝦皮仍是线上提交高风险链路:执行前只检测账号 Chrome/CDP/登录态,不自动启动缺失账号 Chrome。商品套图的只读拉图、原图下载/导入、AI帮写和 cmhub 生成均在独立 worker 中运行;多任务可并行,且不会自动上传蝦皮。**
2026-06-26 15:30:37 +08:00
**核心模块**
2026-07-07 14:12:32 +08:00
- `appconfig` :读写 `data/config.json` ( Chrome 路径、`chrome_user_data_dir` 根、图片目录、AI 配置、端口、DB 路径)并把默认相对路径解析到 `data/` 。
2026-06-26 15:30:37 +08:00
- `db` :SQLite 读写账号、任务、各阶段结果;建表/迁移。
- `excel` :openpyxl 读输入列、把输出列回写原 Excel(处理文件锁)。
- `config` :账号 ↔ user-data-dir 绑定;slug;目录创建。
2026-06-27 10:30:45 +08:00
- `accounts` :账号 CRUD 服务;生成目录、启动登录、检测登录;不自动登录/填密码。
2026-06-26 15:30:37 +08:00
- `chrome` :拼接启动命令、启动、探测端口、(可选)生成快捷方式。
- `cdp` :连接调试端口、找/开 tab、执行 JS、拖拽、注入文件。
2026-07-20 09:41:55 +08:00
- `editor` :登录检测、读取商品状态、**采集**(读旧标题、下载旧封面)、改标题、换封面、点更新。
2026-07-18 16:34:37 +08:00
- `product_status` :商品状态代码 `normal/unlisted/reviewing/unknown` 的归一化、中文显示、EDS 提示分类和下游任务分组;①②③只能复用该模块,不各自判断。
2026-07-17 12:24:05 +08:00
- `ai` : `gen_title(prompt, old_title)` 、`gen_cover(prompt, old_cover_path)` 、`analyze_product_images(instruction, context, image_paths)` ;前两者分别负责②标题/生图,后者只供商品套图中的「AI帮写」调用 cmhub 图片理解接口,读取1至8张按 `source_order` 排序的本地商品原图并返回可编辑卖点与白名单计费元数据。
2026-07-17 11:48:25 +08:00
- `cmhub_models` :格式化 cmhub 模型别名,并维护仅进程内有效的短期模型目录缓存;缓存键使用规整网关地址和别名,不含 API Key,不写入配置、SQLite、日志或导出文件。
2026-06-26 15:30:37 +08:00
**存储(同一事实只存一处)**
2026-07-07 14:12:32 +08:00
- 应用配置(模型选择、生成参数、目录、Chrome 路径)→ `data/config.json` 。
- AI 模型清单(direct 内部兼容模式 url/模型/密钥/类型/连接超时)→ `data/config/ai_models.json` (API Key 本地明文保存,必须 gitignore,UI 打码显示;普通设置页不再暴露 direct 切换入口)。
- cmhub 网关 Key → `data/config/cmhub.json` , schema `{ "api_key": "..." }` ; `config.json` 只保存 Base URL、别名和超时,不保存 Key。
2026-07-17 11:59:07 +08:00
- cmhub 模型目录与 AI帮写/正式套图预估价格 → 仅内存短期缓存;预估值只供用户确认,实际扣点仍以网关响应 metadata 为准。
2026-07-07 14:12:32 +08:00
- 业务数据(账号、任务、各阶段结果)→ SQLite `data/cmshopee.db` 。
- 图片(采集的旧封面、AI 生成的新封面)→ `data/images/` (路径记在 DB)。
2026-07-17 12:24:05 +08:00
- 提示词 → 标题当前工作文本存单文件 `data/title_prompt.txt` ;标题命名模板存 `data/prompts/title/<名称>.txt` ;封面命名模板存 `data/prompts/cover/<名称>.txt` ;旧 AI工场模板目录 `data/prompts/image_studio/<名称>.txt` 仅保留兼容,商品套图直接把卖点文本与结构化设置保存在项目表。
2026-07-07 14:12:32 +08:00
- 登录态 → 各账号 `data/chrome_user_data_dir/<slug>/` 。
T-538 后统一数据根为 `data/` :打包版默认 `<exe目录>/data` ,源码运行默认项目根 `data/` 。`config.json` 中 `user_data_root` 、`image_dir` 、`db_path` 默认仍保存为 `chrome_user_data_dir` 、`images` 、`cmshopee.db` 等相对值,运行时由 `appconfig` 解析到 `data/` 下;绝对路径作为高级自定义仍按原值使用。启动时会迁移 T-524 旧包的 exe 顶层数据到 `data/` ,并检测 `data/` 可写。
2026-06-26 15:30:37 +08:00
## 四、多账号隔离方案(决策)
2026-07-14 17:22:52 +08:00
采用**每账号独立 user-data-dir**(非 Chrome profile)。`--remote-debugging-port` 绑定在 user-data-dir/进程上,profile 方案无法每账号独立 CDP、串号风险高。启动主路径用程序 `subprocess` 直启(`--remote-debugging-port` + `--remote-allow-origins=*` + `--user-data-dir` );冷启动登录入口还追加 `--no-first-run` 、`--no-default-browser-check` ,并把经过校验的 `https://<region_host>/portal/` 作为唯一启动 URL 放在参数末尾,避免全新 profile 的 `chrome://intro/` 另开首次运行窗口。可选生成 `.lnk` 快捷方式(PowerShell `WScript.Shell` ,参数写在「目标」字段),快捷方式不携带上述冷启动专用参数和 URL,保持原行为。
2026-06-26 15:30:37 +08:00
2026-07-17 12:24:05 +08:00
同一账号重复点击账号管理中的「启动登录」不得重复执行 `subprocess.Popen` 。正确流程是先探测该账号 `debug_port` 的 `/json/version` :端口未响应才按上述启动参数新开 Chrome;CDP 就绪后在最多 2 秒的有界预算内等待初始卖家中心 page target,出现后直接激活。若只出现唯一的 `about:blank` 、Chrome 新标签页或全新 profile 的 `chrome://intro/` ,则导航并复用该 target;只有完全没有安全可复用 page target 时才兜底创建一次,并返回 `fallback_reason` 。端口已响应时复用该账号现有 Chrome/CDP,优先激活卖家中心/登录 tab;没有时才新增一个 tab,绝不改写或关闭用户已有普通页面。账号管理界面在启动期间禁用账号选择和操作,同账号连续触发写 `result=ignored reason=launch_in_progress` ,不进入第二次底层启动。
2026-07-06 15:01:50 +08:00
2026-06-26 15:30:37 +08:00
## 五、数据模型
2026-07-07 14:12:32 +08:00
### 5.1 应用配置 `data/config.json`
2026-06-26 15:30:37 +08:00
```json
{
"chrome_path" : "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" ,
"user_data_root" : "chrome_user_data_dir" ,
"image_dir" : "images" ,
"db_path" : "cmshopee.db" ,
"default_debug_port" : 9222 ,
"debug_port_range" : [ 9222 , 9260 ],
"cdp_ready_timeout" : 60 ,
"ai" : {
"default_text_model" : "GPT-5.5 文本" ,
"default_image_model" : "Nano Banana 2" ,
2026-07-02 15:17:11 +08:00
"generate_cover" : false ,
2026-07-09 17:44:03 +08:00
"generate_mode" : "title" ,
2026-07-04 17:59:23 +08:00
"backend" : "cmhub" ,
2026-07-04 15:13:15 +08:00
"cmhub" : {
"base_url" : "" ,
"title_alias" : "" ,
"image_alias" : "" ,
2026-07-17 09:09:09 +08:00
"vision_alias" : "vision-standard" ,
2026-07-08 11:19:09 +08:00
"connect_timeout" : 66 ,
2026-07-04 15:13:15 +08:00
"check_balance_before_batch" : false
},
2026-06-26 15:30:37 +08:00
"title_concurrency" : 4 ,
"image_concurrency" : 4 ,
"retry" : 2 ,
"jpg_quality" : 90 ,
"resolution" : "1k" ,
"resolution_timeouts" : { "512" : 180 , "1k" : 240 , "2k" : 360 , "4k" : 600 }
2026-06-29 09:18:03 +08:00
},
"shopee_update" : {
"test_item_id" : "51100639510" ,
2026-07-09 17:44:03 +08:00
"update_mode" : "title" ,
2026-06-29 09:18:03 +08:00
"max_items_per_run" : 1 ,
2026-06-29 10:25:09 +08:00
"dry_run" : false ,
2026-07-10 10:12:15 +08:00
"max_parallel_accounts" : 1
2026-07-14 15:16:09 +08:00
},
"product_suite" : {
2026-07-16 19:12:21 +08:00
"last_account_alias" : "" ,
2026-07-14 15:16:09 +08:00
"last_settings" : {
"platform" : "Shopee" ,
"country" : "中国台湾" ,
"language" : "繁体中文" ,
"ratio" : "1:1"
}
2026-06-26 15:30:37 +08:00
}
}
```
`ai` 段只放**选择 + 全局生成参数**:
2026-07-17 12:24:05 +08:00
- `backend` :内部字段,取值仍支持 `cmhub` / `direct` ;普通产品默认 `cmhub` ,设置页不再展示「AI 后端」label 或 direct/cmhub 下拉,保存设置固定写 `cmhub` 。`direct` 仅保留为内部兼容/手工回滚路径。
- `cmhub` : cmhub 网关配置,`base_url` 为网关根地址,保存和请求前会规整为 scheme+host(+port),去掉 `/api` 、`/api/v1` 、其它路径、查询串和片段;`title_alias` / `image_alias` / `vision_alias` 分别对应②标题、②与商品套图正式生图、商品套图中的「AI帮写」图片理解,`vision_alias` 缺失时旧配置迁移为 `vision-standard` , `connect_timeout` 为连接超时;API Key 不在此处保存。设置页展示三类别名和扣点提示,其中图片理解下拉只接受 `operation_type=vision` 、`requires_image=true` 且已定价的模型;已保存但暂不可用的值保留并明确提示。商品套图正式生成仍只使用已保存的生图 alias,不展示 OpenAI Key、Provider URL、上游接口路径或直连模型 slug。
- `default_text_model` / `default_image_model` :仅 direct 内部兼容模式下引用 `ai_models.json` 里的模型名(标题用文本模型、封面用图像模型);普通 cmhub 模式不读取这些模型定义,设置页不再展示标题/图片模型角色下拉。
2026-07-09 17:44:03 +08:00
- `generate_mode` :②「生成内容」下拉的主字段,取值 `title` / `cover` / `title_cover` ,分别表示只生成标题、只生成封面、生成标题和封面;默认 `title` ,避免用户无意产生封面生成成本。
- `generate_cover` :旧兼容字段;保存配置时仍写回,值由 `generate_mode` 推导。旧配置 `false` 会迁移为 `title` , `true` 会迁移为 `title_cover` 。GUI 和生成逻辑以 `generate_mode` 为准。
2026-06-26 15:30:37 +08:00
- `resolution` :当前分辨率,下拉 `512 / 1k / 2k / 4k` 。
2026-07-17 12:24:05 +08:00
- `resolution_timeouts` :direct 内部兼容路径使用的分辨率 → **等待大模型返回超时(秒)** 映射。普通默认 `cmhub` 模式下,分辨率只控制生图尺寸;设置页「返回超时」只读展示实际等待口径:标题 600 秒 / 图片 900 秒,不再随分辨率切换显示 180/240/360/600。
2026-07-07 14:12:32 +08:00
- direct 内部兼容模式模型本身的定义(url/key/类型/连接超时…)在 `data/config/ai_models.json` ,见 5.1b。
- 密钥不在 `config.json` : cmhub API Key 存于 `data/config/cmhub.json` ;direct 内部兼容模式每个模型的 `api_key` 存于 `data/config/ai_models.json` 。两者均本地明文保存、保存/变更时弹窗提示、UI 打码、gitignore、不入日志/导出。
2026-06-26 15:30:37 +08:00
2026-07-10 10:12:15 +08:00
`shopee_update` 段放**③ 更新蝦皮执行参数**:
2026-06-29 09:18:03 +08:00
2026-07-01 08:35:46 +08:00
- `test_item_id` :历史/调试兼容字段;默认 `51100639510` 。普通正式更新不再以该字段限制商品 ID,也不因当前筛选结果包含非测试商品而阻断;后续如需要调试模式,可单独启用测试商品限制。
2026-07-09 17:44:03 +08:00
- `update_mode` :③「更新内容」下拉的主字段,取值 `title` / `cover` / `title_cover` ,分别表示只更新标题、只更新封面、更新标题和封面;默认 `title` 。
2026-07-01 08:35:46 +08:00
- `max_items_per_run` :每批最大更新任务数;默认 `1` ,当前筛选结果超过该值时自动分批,不再按总数阻断。
2026-07-01 14:30:49 +08:00
- `dry_run` :内部兼容字段;普通用户界面不展示该开关,③「检查本轮更新」按钮触发检查模式,只写运行日志,不打开 Shopee、不点击「更新」、不改任务状态;默认 `false` 。
2026-07-10 10:12:15 +08:00
- `max_parallel_accounts` :最多同时执行的账号数,范围 `1..5` ; `1` 表示逐个账号串行,`>=2` 表示按账号分组并行,同一账号内仍按任务串行;默认 `1` 。旧配置中的并行布尔开关为关闭时会迁移为 `max_parallel_accounts=1` ,开启时会保留旧数量并夹紧到 `1..5` ,保存后不再写回旧并行布尔字段。
2026-06-29 09:18:03 +08:00
2026-07-10 10:12:15 +08:00
该段不是替代 ③ 确认弹窗的常驻授权;③「开始更新」仍必须弹窗确认,用户点是后才执行。`dry_run=true` 时不会真实提交;`dry_run=false` 时以③确认弹窗作为线上提交前的唯一显式确认边界。普通正式更新不读取 `test_item_id` 做阻断。旧配置中的真实提交、封面更新、成功关页等开关只作迁移兼容读取,保存后不再写回。
2026-06-29 09:18:03 +08:00
2026-07-17 12:24:05 +08:00
`product_suite.last_account_alias` 只保存商品套图最近一次有效选择的账号别名。软件重启后的第一个新任务优先恢复该账号;账号已删除或不可用时回退到第一个可用账号。已有账号+商品 ID 项目和恢复的临时草稿始终以 SQLite 中自己的 `account_alias` 为准,不被最近账号覆盖。
2026-07-16 19:12:21 +08:00
2026-07-17 12:24:05 +08:00
`product_suite.last_settings` 只保存商品套图最近一次选择的平台、站点、语言和比例,作为软件重启后第一个未绑定商品任务的默认值。已有账号+商品 ID 项目仍以 SQLite `image_studio_projects.suite_settings_json` 为准;同一次运行中新任务优先继承当前任务;绑定已有项目后再由项目设置覆盖最近默认。旧配置缺少该段或值不在当前选项集合时,分别回退到 `Shopee / 中国台湾 / 繁体中文 / 1:1` 。`product_suite` 配置不保存商品 ID、卖点提示词、图片路径或密钥。
2026-07-14 15:16:09 +08:00
2026-07-07 14:12:32 +08:00
### 5.1b AI 模型清单 `data/config/ai_models.json`
2026-06-26 15:30:37 +08:00
2026-07-04 17:59:23 +08:00
模型定义清单("有哪些模型"),与 `config.json` 的 `ai` 段("选了哪个 + 全局参数")职责分开。该文件只用于内部兼容 `backend=direct` ;普通用户默认 `backend=cmhub` ,生文/生图使用 cmhub 别名,不读取此文件。
2026-06-26 15:30:37 +08:00
```json
{
"models" : [
{
"name" : "Nano Banana 2" , // 服务商/模型名,唯一,作下拉显示与引用键
"category" : "image" , // text | image —— 决定它出现在“标题/图片大模型”哪个下拉
"enabled" : true ,
"url" : "https://api.vectorengine.ai/v1/chat/completions" ,
"model" : "gemini-3.1-flash-image-preview" , // 模型 ID
2026-06-29 10:05:16 +08:00
"api_key" : "***" , // 密钥:本地明文存、保存/变更时提示、UI 打码、不入日志/导出、不进版本库
2026-06-26 15:30:37 +08:00
"api_type" : "auto" , // chat | images_edits | auto —— 决定请求构造方式
"connect_timeout_seconds" : 30 , // 连接该服务超时(每模型,默认 30)
"timeout_seconds" : 0 , // 返回超时:0/留空 = 运行时按 resolution_timeouts 取值
"extra_body" : {}
}
]
}
```
关键事实:
- **`category` (文本/图像)是必需的**:角色下拉据此过滤(标题下拉只列 text、图片下拉只列 image),防止错配。
- 约束:**至少各有一个 text 与一个 image 模型**;下拉默认最少一项、删到剩一项时禁用「删除」。
- `connect_timeout_seconds` (连接超时)属于**模型**;返回超时由分辨率映射决定(不在模型上单设)。
- `api_type` 反映不同 API 形状(`chat` /`images_edits` /`auto` ),请求构造按它分支。
2026-06-29 10:05:16 +08:00
- `name` 唯一;`api_key` 本地明文保存、保存/变更时弹窗提示、打码显示。
- 日志/状态/导出不得含密码或 API Key;结构化对象统一先过 `appconfig.sanitize_for_log()` ,自由文本只允许在掌握明文值时用 `appconfig.redact_secrets()` 替换。
2026-06-26 15:30:37 +08:00
2026-07-07 14:12:32 +08:00
### 5.1c cmhub Key `data/config/cmhub.json`
2026-07-04 15:13:15 +08:00
```json
{ "api_key" : "sk_cmhub_xxx" }
```
- 文件必须 gitignore,不提交;UI 展示打码。
2026-07-04 17:59:23 +08:00
- `appconfig.load_cmhub_config()` 缺文件时返回空 Key;普通产品默认 backend 仍为 cmhub,但未配置 Key/Base URL/别名时生成阶段会给出清晰配置错误,不静默回退 direct。
2026-07-17 12:24:05 +08:00
- `backend=cmhub` 但 Base URL、API Key 或别名缺失时,`app/ai.py` 抛 `CMHubError(code="cmhub_not_configured")` ,提示去设置配置,不静默回退 direct; cmhub HTTP 404 映射为 `CMHubError(code="not_found")` ,提示检查 Base URL 或实例是否已部署 `/api/v1/models` 。
2026-07-11 14:49:39 +08:00
- cmhub `/models` 如返回 `display_name/tags/recommended_for/tier/prices` ,GUI 优先用这些字段生成中文档位说明;缺少这些字段时按 alias/tag 的保守规则兜底到“默认档”。客户端不得把 OpenAI 原始模型名作为默认执行事实。
2026-07-04 15:13:15 +08:00
2026-06-26 15:30:37 +08:00
### 5.2 SQLite `cmshopee.db`
```sql
2026-06-26 16:27:33 +08:00
-- 批次(一次导入动作)
CREATE TABLE batches (
id TEXT PRIMARY KEY , -- batch_id,如 20260626_153000_xxxx
source_files_json TEXT NOT NULL , -- 导入文件绝对路径列表(JSON)
status TEXT NOT NULL DEFAULT 'active' , -- active/done/partial/failed
note TEXT ,
created_at TEXT NOT NULL ,
2026-07-01 09:41:10 +08:00
updated_at TEXT NOT NULL ,
deleted_at TEXT , -- T-206 软删除时间;默认业务查询排除
deleted_reason TEXT -- 软删除原因/来源
2026-06-26 16:27:33 +08:00
);
2026-07-17 12:24:05 +08:00
-- 账号(账号管理)
2026-06-26 15:30:37 +08:00
CREATE TABLE accounts (
id INTEGER PRIMARY KEY ,
account_name TEXT NOT NULL , -- Shopee 登录账号名(展示/参考)
alias TEXT UNIQUE NOT NULL , -- 别名,Excel 用它匹配
region_host TEXT NOT NULL ,
slug TEXT UNIQUE NOT NULL , -- user-data-dir 子目录名 [a-z0-9_]
user_data_dir TEXT NOT NULL ,
debug_port INTEGER NOT NULL ,
2026-06-29 10:05:16 +08:00
password TEXT , -- 本地明文,仅参考,不自动登录;保存/变更时提示;UI 打码,gitignore
2026-06-26 15:30:37 +08:00
note TEXT ,
created_at TEXT NOT NULL ,
2026-06-26 16:27:33 +08:00
updated_at TEXT NOT NULL ,
2026-06-26 15:30:37 +08:00
last_login_at TEXT
);
-- 任务 + 各阶段结果(贯穿流水线)
CREATE TABLE tasks (
2026-06-26 16:27:33 +08:00
id INTEGER PRIMARY KEY ,
batch_id TEXT NOT NULL REFERENCES batches ( id ),
-- Excel 回写定位
source_file TEXT NOT NULL , -- 展示用原路径
source_file_abs TEXT NOT NULL , -- 绝对路径,作为回写分组依据
source_sheet TEXT NOT NULL , -- sheet 名
source_row INTEGER NOT NULL , -- Excel 行号(1-based)
row_key TEXT NOT NULL UNIQUE , -- batch/file/sheet/row 组成,防重复导入
2026-06-26 15:30:37 +08:00
-- 输入列(Excel)
2026-06-26 16:27:33 +08:00
account_name TEXT ,
alias TEXT NOT NULL ,
item_id TEXT NOT NULL ,
2026-07-18 16:34:37 +08:00
-- 商品状态快照(采集页面探测,不宣称实时在售)
2026-07-20 09:41:55 +08:00
product_status TEXT , -- normal/unlisted/reviewing/unknown;上线前活动历史 NULL 启动时迁移为 normal
2026-07-18 16:34:37 +08:00
product_status_note TEXT , -- 归一化 EDS 标题/说明摘要,最多 2000 字符
product_status_at TEXT , -- 本次页面状态探测时间
2026-06-26 15:30:37 +08:00
-- 采集输出(程序写,改前快照)
2026-06-26 16:27:33 +08:00
old_title TEXT ,
old_cover_path TEXT , -- 旧封面本地图片路径
2026-06-26 15:30:37 +08:00
-- AI 输出
2026-06-26 16:27:33 +08:00
new_title TEXT ,
new_cover_path TEXT , -- 新封面本地图片路径
2026-07-09 00:07:09 +08:00
image_task_id TEXT , -- cmhub 异步生图任务ID;用于重启/重试续查
image_task_key TEXT , -- 本次生图提交幂等键;用于 submit 网络抖动重发
2026-06-26 15:30:37 +08:00
-- 应用
2026-06-26 16:27:33 +08:00
committed INTEGER NOT NULL DEFAULT 0 , -- 是否成功点「更新」提交
stage TEXT NOT NULL DEFAULT 'imported' , -- imported/collected/generated/applied
status TEXT NOT NULL DEFAULT 'pending' , -- pending/running/success/failed/skipped/cancelled
last_error TEXT ,
collect_attempts INTEGER NOT NULL DEFAULT 0 ,
generate_attempts INTEGER NOT NULL DEFAULT 0 ,
apply_attempts INTEGER NOT NULL DEFAULT 0 ,
imported_at TEXT NOT NULL ,
collected_at TEXT ,
generated_at TEXT ,
applied_at TEXT ,
updated_at TEXT NOT NULL ,
UNIQUE ( batch_id , source_file_abs , source_sheet , source_row )
2026-06-26 15:30:37 +08:00
);
2026-06-26 16:27:33 +08:00
CREATE INDEX idx_tasks_batch_stage_status ON tasks ( batch_id , stage , status );
CREATE INDEX idx_tasks_alias ON tasks ( alias );
CREATE INDEX idx_tasks_item ON tasks ( item_id );
2026-06-26 15:30:37 +08:00
```
2026-06-29 10:25:09 +08:00
运行日志(T-504):
```sql
CREATE TABLE run_logs (
id INTEGER PRIMARY KEY ,
run_type TEXT NOT NULL , -- apply 等
dry_run INTEGER NOT NULL DEFAULT 0 ,
status TEXT NOT NULL DEFAULT 'running' , -- running/done/blocked/cancelled
total INTEGER NOT NULL DEFAULT 0 ,
done INTEGER NOT NULL DEFAULT 0 ,
success_count INTEGER NOT NULL DEFAULT 0 ,
skipped_count INTEGER NOT NULL DEFAULT 0 ,
failed_count INTEGER NOT NULL DEFAULT 0 ,
options_json TEXT ,
summary_json TEXT ,
started_at TEXT NOT NULL ,
finished_at TEXT
);
CREATE TABLE run_log_events (
id INTEGER PRIMARY KEY ,
run_id INTEGER NOT NULL REFERENCES run_logs ( id ) ON DELETE CASCADE ,
task_id INTEGER ,
alias TEXT ,
item_id TEXT ,
level TEXT NOT NULL DEFAULT 'info' ,
message TEXT NOT NULL ,
created_at TEXT NOT NULL
);
```
2026-07-01 15:32:27 +08:00
### 5.2c 诊断日志分层(T-207 + T-505 已接入全流程)
2026-06-29 17:49:45 +08:00
当前问题:① 采集旧标题/旧封面出现单条失败时,现有代码只把该任务写成 `status=failed` 、`last_error=<错误>` ,并在状态栏显示失败计数;但没有记录“卡在哪一步”,也没有完整 traceback,后续 debug 难以判断是打开商品页、页面就绪、读标题、读封面 URL、下载图片还是写库/回写失败。
采用两层日志:
2026-07-01 15:32:27 +08:00
- **SQLite 运行日志(业务可读)**:复用 `run_logs/run_log_events` ,用于 GUI 查看和运营排查。`run_type` 扩展到 `collect/generate/import/write_back/apply/chrome_launch/login_check/ai_model_test` 。事件必须带 `task_id/alias/item_id` (能拿到时),`message` 统一包含 `step=<步骤>` 、结果和简短错误;不新增敏感字段,不写 Cookie、密码、API Key、token。
2026-07-07 14:12:32 +08:00
- **本地诊断 log 文件(开发调试)**:写入 `data/logs/cmshopee.log` 或按日期滚动文件,保存脱敏后的 traceback、异常类型、步骤、耗时和必要上下文。`data/logs/` 必须 gitignore;自由文本异常进入日志前要用 `appconfig.redact_secrets()` ,结构化 payload 先过 `sanitize_for_log()` 。
2026-06-29 17:49:45 +08:00
优先级:
2026-07-18 16:46:59 +08:00
1. **T-207/T-662b 已接入 ① 采集** :记录批次开始/结束、账号预检、每个商品开始/成功/失败/略过;关键步骤覆盖 `preflight` 、`open_product` 、`wait_ready` 、`read_product_status` 、`read_title` 、`read_cover` 、`download_cover` 、`db_write` 、`excel_write_back` 。运行日志选项记录本轮范围,汇总记录四类商品状态与按范围略过数。失败时 DB 事件保存最后步骤和简短错误,本地 `data/logs/cmshopee.log` 保存完整脱敏 traceback。
2026-07-07 14:12:32 +08:00
2. ** ② AI生成已先补诊断**: `GenerateWorker` 创建 `run_type=generate` ,按任务记录标题/封面阶段事件;关键步骤覆盖 `title_submit` 、`load_text_model` 、`title_build_request` 、`title_request` 、`title_parse_response` 、`cover_prompt_render` 、`cover_submit` 、`cover_validate_input` 、`load_image_model` 、`cover_build_request` 、`cover_request` 、`cover_parse_response` 、`cover_save` 、`db_write` 。失败时任务 `last_error` 、DB 运行日志和本地 `data/logs/cmshopee.log` 都写脱敏错误。
2026-07-17 12:24:05 +08:00
3. **T-505 已扩展全流程** : Excel 导入创建 `run_type=import` ,记录选中文件、缺列、脏行、入库统计;Excel 回写创建 `run_type=write_back` ,记录文件写入、文件锁/保存异常和行数;③ 更新蝦皮 的 `run_type=apply` 覆盖安全/账号预检、Chrome/CDP 检查、登录检测、打开商品页、改标题、换封面、点更新、写库;账号管理中的「启动登录」创建 `run_type=chrome_launch` ,账号管理中的「检测登录」创建 `run_type=login_check` ;设置中的 AI 模型测试连接创建 `run_type=ai_model_test` 。失败时本地 `data/logs/cmshopee.log` 写脱敏 traceback,业务日志和状态栏只写脱敏短错误。
2026-07-02 21:46:32 +08:00
4. **T-404b 已接入商品页失败 toast 捕获** :①采集和③更新在 `open_product` /等待详情页就绪失败时,不应只报等待超时。进入/刷新商品编辑页后应捕获 `.eds-toasts .eds-toast__content` 的文本和 `outerHTML` ,记录当前 URL、时间、可见状态,并在关键元素超时时把最近错误 toast 作为用户可读失败原因写入 `last_error` 、`run_log_events` 与本地诊断日志;例如商品 ID 失效时提示 `please input correct product id` 。若 toast 明确属于商品失效/商品不存在/无权限类错误,底层仍写 `stage=imported/status=failed/last_error=商品失效:<原始toast>` ,只在①导入采集列表“阶段”显示“商品失效”;其他打开失败仍显示“失败”。若失败发生在 `open_product()` 内部且 `cdp` 尚未返回上层,`open_product()` 必须自行清理本轮自动新建 tab;复用用户已有 tab 只断开 CDP,不关闭页面。失败现场可保存 HTML/toast JSON 片段,但不得记录 Cookie、密码、token。
2026-07-08 18:24:07 +08:00
5. **T-563 已接入①采集登录检测容错** :采集前预检和采集中途登录检测遇到 `NO_SESSION_COOKIE` 、`LOGIN_CHECK_FAILED` 、CDP 短暂读取异常时,应短暂重试并记录 reason、URL、Cookie 名称和重试次数;连续不确定仍不得把同账号后续任务批量标记为 `skipped` 。只有明确检测到登录页 URL / `LOGIN_PAGE` 时,才把账号加入需补登录列表并整组略过后续任务。日志不得记录 Cookie 值。
2026-07-02 21:46:32 +08:00
2026-06-29 17:49:45 +08:00
原则:数据库日志给运营和 GUI 看“哪个商品、哪个步骤、为什么失败”;本地 log 文件给开发看完整错误栈。两者都必须脱敏。
2026-06-26 15:30:37 +08:00
关键事实:
- `alias` 是账号↔任务**唯一关联键**;找不到账号 → stage=skipped,error=别名未匹配,最后弹窗汇总。
- `account_name` 仅展示/参考;匹配以 `alias` 为准。
2026-06-26 16:27:33 +08:00
- `source_file_abs/source_sheet/source_row` 是回写 Excel 的权威定位;即使商品 ID 重复,也按原行回写。
- `row_key` 防止同一批次内重复导入同一行。
2026-07-20 09:41:55 +08:00
- `product_status/product_status_note/product_status_at` :①每次打开商品编辑页后写入的状态快照;`normal` 只表示没有已知异常横幅,不等于实时“在售”。商品状态功能上线时间 `2026-07-18T16:34:37` 前的活动历史批次中,NULL/空状态在 `init_db()` 时按产品决策迁移为 `normal` ,附「历史批次默认按架上商品处理(未实时检测)」备注且保持 `product_status_at=NULL` ;上线时点及之后的 NULL 与非法值按 `unknown` 处理。
2026-06-26 15:30:37 +08:00
- `old_title/old_cover_path` :程序**采集阶段抓取**的快照(输出)。
2026-07-13 15:22:15 +08:00
- `new_title/new_cover_path` :**AI 生成**的两个独立组件结果(输出)。③只更新标题要求 `new_title` ,只更新封面要求 `new_cover_path` ,更新图文才同时要求二者;只生成封面允许 `new_title=NULL` ,已采集的 `old_title` 只作为封面prompt语义参考,不回填 `new_title` 。不设逐条确认阶段。
2026-06-26 16:27:33 +08:00
- `stage` 表示已完成到哪个业务阶段;`status` 表示当前处理结果。失败时 `stage` 保持在最后成功阶段,`status=failed` ,错误写 `last_error` 。
2026-06-26 15:55:28 +08:00
- `committed` :③ 用户确认批量更新弹窗后逐条点「更新」提交;该字段记录提交是否成功。
2026-06-26 15:30:37 +08:00
- 各字段**每阶段处理完立即写回 SQLite**(实时落库);阶段结束后批量回写 Excel。
2026-06-26 16:27:33 +08:00
### 5.2b SQLite 并发与连接规则
- `db.connect()` 统一设置:`PRAGMA foreign_keys=ON` 、`PRAGMA journal_mode=WAL` 、`PRAGMA busy_timeout=5000` 、`PRAGMA synchronous=NORMAL` 。
- SQLite connection 不跨线程共享;每个 PySide6 worker / 后台线程按需创建自己的 connection。
- 写操作使用短事务:单条任务完成即提交,避免长时间占用写锁。
- 写库遇到 locked/busy:按短间隔重试,超过 busy timeout 后返回明确错误并通过 signal 通知 GUI。
- GUI 不直接缓存“事实状态”;写库成功后由 worker 发 `row_updated` , GUI 再刷新对应行。
- Excel 回写以 SQLite 为事实来源;原 Excel 被锁时不回滚 SQLite,只提示关闭后重试或另存副本。
2026-06-26 15:30:37 +08:00
### 5.3 Excel 模板
2026-06-27 11:29:48 +08:00
标准空模板文件保留在仓库根目录:`shopee待处理任务模板.xlsx` ,单工作表名为 `待处理任务` 。运营使用时应复制一份再填写,填写后的业务 Excel 含商品数据,不提交版本库。
2026-06-26 15:30:37 +08:00
| 列 | 含义 | 输入/输出 |
| --- | --- | --- |
| 账号名 | Shopee 账号名(展示) | 输入 |
| 别名 | 关联 accounts.alias(匹配键) | 输入 |
| 商品id | item_id | 输入 |
| 旧标题 | 采集到的旧标题 | 输出,回写 |
| 旧封面图片路径 | 采集到的旧封面本地路径 | 输出,回写 |
| 新标题 | AI 生成的新标题 | 输出,回写 |
| 新封面图片路径 | AI 生成的新封面本地路径 | 输出,回写 |
| 更新状态 | 处理结果(成功/失败/跳过+原因) | 输出,回写 |
- 输入列(账号名/别名/商品id)运营填;其余为程序各阶段输出,**回写到原 Excel**(被锁→提示重试/另存副本)。
- 别名以此列为匹配权威,不解析文件名。
2026-07-17 12:24:05 +08:00
- 标准模板推荐前三列表头固定为:`账号名 | 别名 | 商品id` 。不要使用 `账号` 、`店铺名` 、`店名` 代替匹配列;`别名` 必须与 账号管理中的 `accounts.alias` 一致。
2026-06-26 16:37:58 +08:00
- 导入容错:缺少必需列(别名/商品id)时拒绝整个文件,记入 `file_errors` ,不导入该文件任何行。
- 脏行处理:单行缺别名/商品id、商品id 格式错误等逐行跳过,计入 `invalid` ,不阻塞同文件其他有效行。
2026-06-26 15:30:37 +08:00
### 5.4 本地图片目录
```text
2026-07-07 14:12:32 +08:00
data/images/<batch_id>/<slug>/<task_id>_<item_id>_old.<ext> # 采集下载的旧封面
data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新封面
2026-06-26 15:30:37 +08:00
```
2026-07-07 14:12:32 +08:00
路径记入 DB 后以 `old_cover_path/new_cover_path` 为权威;上传新封面使用 DB 中的本地绝对路径(Windows 绝对路径传 `setFileInputFiles` )。历史数据里的 `images/<slug>/<item_id>_*.jpg` 路径继续有效,不做强制迁移;T-538 后新采集/新生成默认写入 `data/images/` 下的按批次细分目录。
2026-06-26 15:30:37 +08:00
## 六、关键流程细节
2026-06-26 16:09:09 +08:00
### 6.0 GUI 线程模型(PySide6)
- 主线程只运行 `QApplication` 、窗口、表格、弹窗和状态刷新;不得在主线程执行 CDP、AI、Excel 回写、图片下载等耗时任务。
2026-07-20 09:41:55 +08:00
- 每类耗时流程封装为 `QObject` worker: `CollectWorker` 、`GenerateWorker` 、`ApplyWorker` 、`ImportWorker` 、`WriteBackWorker` 。
2026-06-26 16:09:09 +08:00
- Worker 通过 signal 向 GUI 汇报:`progress` 、`row_updated` 、`log` 、`failed` 、`finished` 、`cancelled` ;GUI 槽函数只做 UI 刷新和按钮状态切换。
- Worker 不直接操作任何 Qt widget,不弹窗;需要用户确认的动作(如 ③ 开始更新确认)必须在主线程先完成,再启动 worker。
- `停止` 使用协作式取消:GUI 调用 worker 的 cancel 标记;worker 在任务间/重试前检查,取消未开始项,正在执行的单条任务跑到安全边界后结束。
- SQLite connection 不跨线程共享;每个 worker/线程按需创建自己的连接,写库后发 signal 通知 UI 刷新。
2026-06-26 15:30:37 +08:00
### 6.1 采集(① Tab,只读)
2026-07-18 16:34:37 +08:00
- 用账号 Chrome 打开商品页,等就绪,先读商品状态,再读旧标题(标题输入框 value)。
2026-07-14 12:08:12 +08:00
- `open_product` 先复用已打开的同商品 tab;没有才新建商品编辑页 tab。采集完成后只关闭本次程序自动新建的商品 tab,并在最多 2 秒内轮询 `/json` 确认该 target ID 已消失;确认超时只记警告并保留采集成功结果。用户原本已经打开的 tab 不关闭、不等待。`CDP.close()` 只断开 WebSocket 控制连接,不等于关闭浏览器 tab。
2026-07-11 22:30:19 +08:00
- ①采集调用 `open_product(..., bring_to_front=False)` ,不主动执行 `Page.bringToFront` ;新建商品 tab 时尝试 `Target.createTarget(background=true)` 降低 Chrome 抢焦点概率,若当前 Chrome/CDP 不接受该参数则退回普通新建 tab。③更新是上传、拖拽和线上提交流程,每条任务均以前台方式打开或激活当前商品 tab,优先保障页面交互稳定;后台态失败安全恢复逻辑仅为兼容直接调用保留,正常③批量路径不依赖它。
2026-07-14 12:08:12 +08:00
- 采集前和采集中途的登录检测必须区分“明确未登录”和“暂时不确定”。明确 `LOGIN_PAGE` / 登录页 URL 才整组略过该账号后续任务;`NO_SESSION_COOKIE` 、检测超时或 CDP 短暂异常只记录为不确定并继续尝试采集当前商品,不得级联跳过同账号剩余任务。Cookie API 调用失败不能伪装成空 Cookie;登录检测若连到正在关闭的旧商品 target,应在本次检测预算内重新枚举并改连其他有效 Shopee 页面。
- 若商品 ID 失效、无权限或店铺不匹配导致商品编辑页无法就绪,`open_product` 必须读取/捕获 Shopee toast,把最近错误文案写入采集失败原因和诊断日志,不能只返回泛化超时。①列表只在明确捕获商品失效类 toast 时把“阶段”显示为“商品失效”;底层 `stage` 不新增中文值。若这个失败发生在后台只读、程序自动新建的商品 tab 内,`open_product` 要关闭并执行同样的有界 target 消失确认;复用用户已有 tab 不关闭。③前台更新打开失败仍沿用既有清理路径,不引入额外等待。
2026-07-18 16:34:37 +08:00
- 商品状态只读取 `.eds-alert.eds-alert--warning` 内的 `.eds-alert-title/.eds-alert-desc` ,不得依赖 Vue `data-v-*` 。`審核中/审核中` 归为 `reviewing` , `您的商品未上架` 归为 `unlisted` ,没有 warning 为 `normal` ,未识别横幅、DOM 异常或非法返回为 `unknown` 。保存归一化摘要而非页面 HTML;状态探测异常只写脱敏诊断,不伪造技术采集失败。
2026-07-18 16:46:59 +08:00
- 每次点击采集先在主线程选择本轮范围,策略值为 `normal_only/all` ,默认 `normal_only` ,不跨轮记忆。两种范围都会逐条重新打开页面检测并独立保存状态,不能用旧数据库状态预过滤;`normal_only` 仅正常商品读取标题和封面,其他三类状态记为业务略过并保留旧内容,`all` 则四类状态均走现有采集流程。范围略过不是技术失败,汇总分别统计状态和略过数。
2026-07-20 09:41:55 +08:00
- 不提供历史商品状态重检按钮或批量打开详情页的补检流程。历史默认迁移只在数据库初始化时执行,条件为活动批次创建时间严格早于上线时间且状态为空;已有四类状态、软删除批次、上线时点及之后的空状态均不改写。
2026-07-02 21:46:32 +08:00
2026-07-07 14:12:32 +08:00
- 旧封面:取第一张 itembox 的 `img.src` ( CDN 链接),下载到 `data/images/<batch_id>/<slug>/<task_id>_<item_id>_old.jpg` 。
2026-06-26 15:30:37 +08:00
- 写 `old_title/old_cover_path` 、stage=collected;批量回写 Excel 旧字段。
2026-06-27 14:47:08 +08:00
- 采集任务结束且本轮有成功采集行时,自动触发当前批次旧字段回写;原 Excel 被锁时不影响 SQLite 结果,提示关闭后重试,并保留手动「回写旧数据到 Excel」入口。
2026-06-26 15:30:37 +08:00
### 6.2 AI 生成(② Tab)
2026-07-13 15:22:15 +08:00
单个「开始生成」按钮,按本轮「生成内容」模式生成标题、封面或图文:
2026-06-26 15:30:37 +08:00
2026-07-18 17:39:10 +08:00
- 点击后先从当前筛选结果按生成模式构造真实候选,再在主线程按 `normal_only/all` 选择范围:①②共用最小宽度 520px、纵向全宽按钮的中文范围选择框。①按钮文案为“采集架上商品”(检测结果为 `normal` )和“采集全部商品”;②按钮文案为“生成架上商品”(仅 `normal` )和“生成全部商品”。“全部商品”用警示橙色描边表示扩大范围/可能增加成本,不使用删除红色或 `DestructiveRole` 。它才允许未上架、审核中、状态未知(历史 `NULL` 同未知)商品进入 Worker。范围确认显示四类数量;取消、无候选或确认期间候选指纹变化时不创建 Worker、不请求 AI、不改变 DB。警示选择不跨轮记忆,且没有非正常候选时禁用。范围只决定本轮生成,不修改商品状态,也不授权③更新。
2026-07-18 16:57:57 +08:00
- 共享 `product_status.build_generation_plan()` 对候选按稳定任务 ID 去重并冻结 `task_id/updated_at/product_status/new_title/new_cover_path/生成内容模式` 指纹。`GenerateWorker` 接收确认后的精确任务清单和范围代码,在 `normal_only` 下再次防御性排除非正常状态,`ai.generate_batch()` 只能收到最终执行清单。`run_logs.options` 与汇总记录选择范围、四类数量、按范围排除数和计划指纹;用户日志只显示中文范围摘要。
2026-07-04 17:59:23 +08:00
1. **并发生成标题** :线程池大小 = `title_concurrency` ,调 `gen_title(标题提示词, old_title)` → new_title。默认 `backend=cmhub` ,调用 `POST /api/v1/generate/title` 并使用 `title_alias` ; `backend=direct` 仅内部兼容时使用 `default_text_model` 。
2026-07-13 15:22:15 +08:00
2. **若②「生成内容」包含封面** :接着并发生成封面,线程池大小 = `image_concurrency` ,调 `generate_batch()` 的封面阶段 → 新图存 `data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.jpg` 。选择 `cover` 时处理缺新封面且存在 `new_title or old_title` 的任务,优先用新标题、否则只在prompt上下文中用旧标题回退;选择 `title_cover` 时仍先补新标题,再按缺失组件补封面,不使用旧标题绕过失败的生文。默认 `backend=cmhub` 的②批量生成走异步任务接口:先 `POST /api/v1/generate/image/tasks` 提交并预扣点,带 `Idempotency-Key` 与 `X-Client-Version` , submit 成功后立即把 `task_id` 写入 `tasks.image_task_id` ;再循环 `GET /api/v1/generate/image/tasks/{task_id}` 轮询,`succeeded` 后取 `result.image_url` 并安全下载转本地 JPEG。若本地已有 `image_task_id` ,下次开始生成直接续查,不重新 submit、不重复扣点。`gen_cover()` 单独直接调用无 DB 上下文,仍保留旧同步 `POST /api/v1/generate/image` 兼容路径;`backend=direct` 仅内部兼容时使用 `default_image_model` 。用户日志只展示“cmhub 托管档位 / 别名 / 扣点 / 余额 / call_id”,不得输出 cmhub 内部接口路径、Provider 请求体、Authorization、Key 或完整 prompt。
2026-07-09 00:07:09 +08:00
- T-545/T-564 已实现:cmhub 模式下不再直接按用户填写的 `image_concurrency` 全量打到网关;实际 submit+poll 在途并发 = `min(image_concurrency, 5)` 。拿到 `image_url` 后交给独立下载/保存线程池,下载线程数量与实际生图并发一致,同样最大 5。这样批量时“下一批生图任务”和“上一批图片下载/保存”可以流水线并行,但不会对 cmhub 生图任务接口或 `/media/generated/images/*.png` 静态下载打出超过 5 的并发。direct 兼容路径暂不改变。
- cmhub 生图连接超时取 `ai.cmhub.connect_timeout` (默认 66 秒);异步 submit 读取等待 30 秒,poll 单次读取等待 15 秒,本地总等待预算 900 秒,撞预算或用户停止时保留 `image_task_id/image_task_key` 供下次续查;poll 返回 `failed/expired` ( cmhub 已退点)时清空 `image_task_id/image_task_key` ,后续重试会生成新的幂等键并重新 submit。下载层读取等待 900 秒,最多安全重试 3 次,只复用同一个 `image_url` ,不重新请求 cmhub 生图;下载总耗时超过 20 秒时写“图片下载较慢”警告。`backend=direct` 兼容路径仍按模型 `timeout_seconds` 或 `resolution_timeouts[resolution]` ( 512→180/1k→240/2k→360/4k→600)取返回超时。
2026-07-08 09:26:25 +08:00
- T-548 已实现:cmhub 图片下载后端新增 `ai.cmhub.download_with_curl` ( `auto` /`true` /`false` ,默认 `auto` )。Windows 且检测到系统 curl 时优先用 curl 下载,否则或 curl 执行失败时回退 requests;生成、models、balance 仍走共享 requests Session。curl 下载前仍执行公网 URL 校验;URL 写入临时 curl 配置文件并通过 `-K` 传入,避免带 token 的 `image_url` 出现在进程命令行;`use_system_proxy=false` 时 curl 加 `--noproxy "*"` 。
2026-07-08 00:41:44 +08:00
- cmhub 返回的完整 `image_url` 默认只在内存中临时用于下载,不写入 `tasks` 或 `run_log_events` 。本机调试时可设置环境变量 `CMSHOPEE_DEBUG_CMHUB_IMAGE_URL=1` ,②本轮可见运行日志会显示脱敏后的 URL 调试行,且该行不持久化到 SQLite。
2026-07-09 17:44:03 +08:00
3. **若②「生成内容」为只生成标题** :标题成功后立即写 `new_title` , `new_cover_path=NULL` ,不渲染封面提示词、不调用 `gen_cover()` 、不创建本地新封面文件。
2026-06-26 15:30:37 +08:00
- 失败重试:每次调用失败按 `retry` 次重试,仍失败则记 error(不阻塞其余)。
2026-07-13 15:22:15 +08:00
- 只生成标题模式在标题成功后立即 `set_generated(task_id, new_title, existing_cover_path)` 写库;只生成封面模式不调用生文,封面成功通过 `set_generated_cover(task_id, new_cover_path)` 只写封面并保留 `new_title` (包括保持NULL);生成标题和封面模式先保存标题,封面成功再用组件级helper写封面。三种模式都实时落库,停止或崩溃不丢已生成结果。
2026-06-26 15:30:37 +08:00
- **「停止」**:取消未开始的任务,正在跑的少量完成或中断;停止后可再次「开始生成」对剩余继续。
2026-07-09 17:44:03 +08:00
- 进度:标题和图片两条进度分开显示;只生成标题时图片进度显示本轮未生成/0 张,并在运行日志写明本轮生成内容。
2026-07-13 15:22:15 +08:00
- 任一组件生成后 `stage=generated` ;**不设逐条人工审核阶段**。若只有标题,③可选择只更新标题;若只有封面,③可选择只更新封面,②标题状态仍为待生成,后续补标题会保留已有封面且不重复生图。双击任务弹窗查看旧封面、新封面和历史候选图;T-577 后弹窗内「重置图片」只清当前任务 `new_cover_path` 并归档旧图,不启动单条 `GenerateWorker` ,用户退出后用状态筛选「待生成」批量补生成封面。②「重置生成结果」提供标题/封面/全部的多选或当前筛选范围重置,默认不删除本地新封面文件;已生成且未提交线上的新标题可在②表格本地微调。
2026-07-17 12:24:05 +08:00
- 并发数、重试、分辨率、jpg 质量、模型/Key 均来自设置(`data/config.json` 的 `ai` 段;Key 存 `data/config/cmhub.json` 或 direct 兼容清单)。T-547 后标题并发和图片并发都限制为 1..5,失败重试次数限制为 0..10;旧 `config.json` 或手工配置的超限值会在加载/保存时夹紧。设置仍只展示一个「图片并发」设置;cmhub 模式下②运行日志显示“图片并发 X,cmhub实际生图并发 Y,下载并发 Y”。
2026-07-17 15:35:28 +08:00
- 商品套图固定使用设置保存的 cmhub 生图 alias。T-651 后,常规新一轮生成先完成历史确认,再按最终 `build_job_specs()` 在后台读取或复用短期模型目录缓存,最后才显示数量和扣点确认;逐图主图开启时白底图仅使用第一张原图,其他分类按每张原图展开。固定分类顺序为白底图、场景图、模特场景图、细节说明图、卖点图;默认数量为1、2、0、0、2,新增分类不因升级自动产生任务。只有当前生图别名存在唯一无条件 `points_cost` 时显示单张与总预估点数,总数严格按 planned job 数量计算;目录不可用或价格有条件时不显示数字。确认默认、Esc 或关闭均取消,计划在读取期间发生变化时不提交旧 specs;单图重试和恢复不进入该常规确认。预估不落库、不预扣,实际扣点仍以 job 的 cmhub 响应为准。T-637 后套图提示词事实来源分为:安装包只读默认 `app/default_prompts/product_suite/base.txt` 、用户全局模板 `data/prompts/product_suite/base.txt` 、`app/product_suite.py` 中的分类目标、结构化上下文与只读规则常量。用户模板首次缺失或为空时,必须先按完整占位符契约校验内置模板,再通过原子写入初始化;已有用户模板不被升级静默覆盖。模板无效时在创建 project/job/worker 和调用 cmhub 前阻断新一轮生成,单张历史重试继续使用原 `image_studio_jobs.prompt` 快照。
2026-07-17 16:59:50 +08:00
- T-658a 后商品套图异步生图提交统一使用 `images` 数组,单图也使用单元素 `{"image_base64": ...}` ,不再提交顶层 `image_base64` 。提交层最多传 8 张本地图片,单图原文件上限10MiB、编码后总输入上限32MiB;超限在请求前以中文错误阻断,后续多图参考的资产快照和提示词语义由 T-658b/T-658c 负责。
2026-07-17 17:03:40 +08:00
- T-658b 后 `image_studio_jobs.reference_asset_ids` 以有序 JSON 图片 ID 列表冻结每个商品套图 job 的参考图;新一轮未勾选逐图主图时,第1张为主图、后续最多7张写入快照,勾选时写空数组。历史 `NULL` 行继续按单图任务处理。恢复或重试只读 job 快照,不回读当前原图列表;参考图资产或本地文件缺失时提交前失败,不静默减少提交数量。
2026-07-17 17:10:13 +08:00
- T-658d 后逐图主图 checkbox 右侧显示可换行中文 helper,说明多SKU应逐图生成、同商品多角度应作为参考图一同提交。常规生成确认框从最终 specs 计算主图/参考图数量;未开启逐图主图且可用原图超过8张时,明确第1张主图加前7张参考图的上限和忽略数量。确认总点数仍只按 job 请求数计算,不因单请求图片数量重复计算。
2026-07-17 12:24:05 +08:00
- T-647 后商品套图中的「AI帮写」不是②标题生成的复用入口:它使用 `vision_alias` 调 `POST /api/v1/analyze/images` ,把当前项目中1至8张可用本地原图按 `source_order` 在一次请求中发送;超过8张时明确只使用前8张,原图勾选状态不改变该输入集合。视觉提示词将这些图片定义为同一商品项目的多角度、细节、包装或场景证据集,要求产出一份商品级联合分析,不按“图1/图2”逐图罗列;结果按商品概述、可确认卖点、适用人群与场景、套图画面要求、待确认或避免编造的信息组织,并把可见差异明确为待确认项,不强行合并为事实。单图上限10MiB、总计上限32MiB,缺图、未下载、超限或未配置别名时不发请求且保留用户现有卖点。读取等待固定120秒、连接等待沿用设置;读超时或网络中断只提示“结果未确认”,不自动重发。成功只显示图片张数、扣点和余额,不显示路径、base64、接口URL、完整提示词或上游原始响应;取消只在请求前/返回后协作生效,不强杀网络线程。
2026-07-17 17:27:08 +08:00
- `product_suite.render_product_suite_prompt()` 是弹窗预览和真实生成的唯一 renderer;`build_job_specs()` 建立本轮 specs 前只读取一次用户模板并冻结,每个 job 保存最终完整 prompt,运行中修改模板只影响下一轮。新模板使用 `{生成目标}` ,其值为内置分类固定目标描述或含实际名称的“生成自定义分类图片:分类名称。”;同时保留 `{套图名称}` 、`{补充描述}` 及平台/地区/语言/比例、可选商品ID/主参考图序号、参考图规则、商品卖点和四个只读规则。旧模板必须同时包含 `{套图名称}` 与 `{补充描述}` 才能按兼容路径继续使用;必需变量缺失、未知/未闭合花括号、只读规则变量未独占一行都视为无效。`product_suite_reference_rule()` 是 `{参考图规则}` 的唯一事实来源,变量值都以 `参考图规则:` 开头:逐图主图开启时为单图主参考规则;未开启时固定为“使用第一張上傳圖作為主商品圖,其餘圖片只作為參考。”,不随实际参考图数量变化。提示词弹窗与 job 共用 renderer;实际提交的参考图数量仍由冻结的 `reference_asset_ids` 决定。默认模板独占一行,不额外重复标签或序号。四个只读规则覆盖尺寸与长图(含禁止多宫格拼接)、政治标识、价格和尺码;商品主体一致性与禁止编造并入参考图规则。比例仍同时传入 `image_studio_generation.run_jobs(aspect_ratio=...)` ,进入 cmhub 请求与输出资产元数据。
2026-07-17 12:24:05 +08:00
- `image_studio_projects.suite_settings_json` 持久化套图设置,旧数据库由 `db.init_db()` 原位补列,默认 `{}` ; `draft_prompt` 继续保存卖点文本。`image_studio_assets` 中有效商品原图最多16张,历史 missing 记录不占有效名额;手工原图不会因再次同步蝦皮 URL 被误标 missing。商品套图原图列表的批量勾选只保存在当前 `SuiteTaskState` 对应的界面上下文,不写库;批量移除由 `remove_original_assets_if_unused()` 一次校验项目归属、原图类型和 job/终选引用,并在单个 SQLite 事务中删除资产行、连续重排 `source_order` 。服务不删除本地文件或蝦皮线上图片,任一资产校验失败时整批回滚。
2026-07-16 11:10:51 +08:00
- T-636 起,`image_studio_projects` 增加 `binding_state` ( `draft` / `bound` )和稳定 `storage_key` 。既有项目迁移为 `bound` ,并以原 `item_id` 回填 `storage_key` ;项目目录改用 `storage_key` ,因此临时草稿绑定正式商品 ID 后不移动目录、不改写已有资产路径。草稿内部使用 `draft_<uuid>` 作为仅数据库可见的非空 `item_id` ,GUI 输入框始终保持空白,用户日志和 cmhub 提示词只使用“临时草稿”或“未绑定商品”,不得暴露该内部值。
2026-07-17 12:24:05 +08:00
- 在商品套图中,已选账号但未填写商品 ID 时允许输入卖点、导入、拖入或粘贴本地图片。非空卖点在现有防抖稳定后会创建一个可恢复临时草稿并保存到 `image_studio_projects.draft_prompt` ;首次有效图片导入也会创建草稿。空白卖点、取消选择和全部导入失败不保留空草稿。只有卖点的草稿同样属于可恢复业务内容;卖点清空且没有资产/job 时仍可按空草稿规则清理。草稿可管理本地图片、AI 帮写、生成套图、查看历史和打开结果目录,但在创建 worker、启动 Chrome 或执行 CDP 前禁止「拉取蝦皮主图」。输入合法数字商品 ID 后,经确认原地绑定同一个 `project_id` ;资产、job、selection、提示词、套图设置和 `storage_key` 均保持不变。若同账号目标 ID(含软删除项目)已存在则拒绝覆盖或合并。
- 商品套图拉取蝦皮主图使用独立内存 `pull_run_token` 覆盖 URL 读取和本轮后台下载。运行中按钮提供「停止并保留 / 停止并清除本次新增 / 继续拉取」;停止采用协作式安全边界,不强杀线程、Chrome 或已发出的 CDP/HTTP 请求。选择清除时只移除本轮新增、属于当前项目且未被 job/selection 引用的远程原图,并恢复拉取前已有原图的状态和顺序;本地手动导入图片、拉取前已有图片、用户文件和蝦皮线上图片不删除。旧 token 的 URL/下载迟到结果不得覆盖新一轮状态。
2026-07-16 11:10:51 +08:00
- 启动时恢复未软删除、至少含一条资产或生成任务的草稿为独立中文“临时草稿”标签,按最近更新时间排序。关闭非空草稿可选择保留、软删除或取消;软删除不物理删除图片目录。③「更新蝦皮」只处理正式任务,不接受临时草稿。
2026-07-14 09:53:13 +08:00
- 第六 Tab 的多个 `SuiteTaskState` 各自保留 generation/pull/import/AI/download worker 与线程引用;切换任务不取消任务。多个任务可并行,但 `image_studio_generation` 使用进程级 semaphore 保证所有套图任务合计最多5个 cmhub 在途 job。线程还在运行时关闭任务只请求协作式停止,模块级引用保留到 `QThread.finished` ,不得提前销毁线程对象;下载前后均检查停止信号,停止后的临时文件不入资产库。
2026-07-16 17:12:41 +08:00
- T-639 后每轮套图生成使用仅存在内存的 `run_token` 隔离迟到信号,progress/finished/cancelled/failed 通过主线程绑定槽统一处理;正常 worker 结果、`QThread.finished` 和本轮 job 连续两次全部终态看门狗共同进入幂等 finalize。GUI 只按本轮明确 `job_ids` 判断完成,不用历史图片数量;即使最终信号丢失也会恢复按钮,旧线程引用仍保留到真实结束。停止为协作式:调度循环约每200ms检查标记并取消未开始 future,提交/轮询在有界请求返回后停止;requests 在流式数据块边界取消,Windows curl 由隐藏窗口 `Popen` 有界 terminate/kill。已有 `task_id` 的停止任务保留 resume,不假设服务端任务被取消或点数退回。
2026-07-17 12:24:05 +08:00
- T-643 后 `image_studio_projects.current_generation_round_key` 是商品套图主结果区的持久化当前轮次;每次常规「生成套图」创建独立 UUID `generation_round_key` ,并为每条 `image_studio_jobs` 写入稳定的 `generation_slot_index` 。内存 `run_token` 仍只用于线程和迟到信号隔离,不承担业务轮次语义。只有一轮中至少一条 job 成功时,才在同一 SQLite 事务内将该轮提升为当前轮;全失败或全取消保留原当前轮,部分成功则保留成功、失败槽位供后续重试。单张重试新建 job 但继承原轮次和槽位,当前展示按同轮同槽位最新 job、槽位顺序读取;所有尝试、错误、计费和输出资产均保留。旧 job 的轮次/槽位保持 NULL,查询层统一作为“旧版历史记录”,不以时间、文件名或数量猜测轮次;下一次成功的新格式整轮才建立当前轮。
- T-646 后商品套图主结果区仍只显示项目的当前轮次;「历史生成」改为全局非模态只读窗口,默认跨所有未软删除商品项目按轮次创建时间倒序读取(同一时间以稳定 job ID 补序),首屏及每次翻页最多30轮。窗口只读取 SQLite 的轮次摘要、每槽最新有效 job 和受限尺寸缩略图,不扫描图片目录或一次加载原图;店铺/账号、商品 ID 与“仅当前商品”筛选都在查询层完成,当前商品快捷筛选默认关闭。正常 `generation_round_key` 一轮一行,单张重试只更新该槽位当前状态并计入重试数,不新增历史行;NULL 轮次继续作为“旧版历史记录”兼容,不猜测轮次边界。每行最多显示5张缩略图,余量显示 `+N` ;文件缺失只显示中文占位,不删除 DB。双击缩略图从被点图片打开该轮所有可用图片的自适应原尺寸浏览;“导出本轮”在后台仅复制成功且本地存在的输出 asset 到用户选择目录下安全命名的新子目录,确定性追加序号避免覆盖,不移动/重命名/删除内部 asset。窗口不提供批量导出、删除、重试、设为当前轮或重新生成;关闭商品任务不关闭全局窗口,应用退出时协作停止导出并释放窗口资源,生成 worker 不受历史窗口影响。
2026-07-17 10:47:44 +08:00
- T-648 后,用户发起一轮常规「生成套图」前,服务层仅根据 SQLite 中当前项目每个稳定槽位的最新有效成功 job(NULL 旧版轮次仍按一轮兼容)汇总成功轮数、图片数和最近成功时间;不扫描生成目录。若摘要非空,GUI 先显示默认取消的「已有套图生成记录」三选一确认:查看历史仅打开同一全局历史窗口并临时勾选“仅当前商品”,不创建轮次或请求;继续生成新一轮才进入原有费用确认,并在最终确认后生成新的 UUID 轮次;取消或关闭不写 job、asset、轮次或计费。单张重试、恢复未完成任务及只有失败/取消记录的项目不经过该确认。
2026-07-17 12:24:05 +08:00
- T-649 后,历史窗口的店铺筛选改为结构化下拉:首项固定为「全部店铺」,当前账号按「账号名(账号别名)」显示;未软删除历史项目中仍存在、但账号管理已删除的别名按「历史店铺:别名(账号已删除)」保留。下拉值始终是稳定的 `account_alias` ,查询使用 `projects.account_alias = ?` 精确匹配;商品 ID 与“仅当前商品”可叠加,刷新时保留仍存在的选择。商品套图顶部不再提供打开内部生成目录的按钮,用户导出只从历史记录的「导出本轮」进入外部目标目录;结果卡的单图文件操作保持不变。
2026-06-26 15:30:37 +08:00
提示词管理:
2026-07-10 11:53:51 +08:00
- **标题提示词**: `data/title_prompt.txt` 是当前工作文本,「保存标题提示词」写入该文件;软件启动时加载该文件回显到输入框(缺失则空)。标题命名模板另存于 `data/prompts/title/*.txt` ,左侧模板行提供下拉、新建、保存模板、另存为、重命名、删除;模板只负责被用户选中后载入编辑框,或把当前编辑框内容保存为命名模板,不改变生成读取路径,也不在启动时覆盖 `title_prompt.txt` 。T-549 后标题提示词支持 `{旧标题}` 占位符:若提示词含 `{旧标题}` ,生成前替换为该任务旧标题且不再自动追加旧标题块;若不含,则保持旧行为自动追加“旧标题:...”块。两种情况都会保留“请只返回新标题,不要解释。”输出约束。
2026-07-17 18:24:58 +08:00
- **封面提示词**:多模板。左侧模板行提供下拉(读 `data/prompts/cover/*.txt` )、新建、保存模板、模板操作(另存为/重命名/删除);重名校验、删除二次确认、删空给默认。T-661 后内置首次播种资源为 `app/default_prompts/cover/cover_prompt.txt` ,全新用户初始化为 `data/prompts/cover/cover_prompt.txt` ,下拉显示 `cover_prompt` ;已有任意用户模板(包括历史 `papa1.txt` )不覆盖、不重命名、不迁移。
2026-07-17 16:12:03 +08:00
- **商品套图提示词**:单一全局基础模板,用户文件为 `data/prompts/product_suite/base.txt` ,内置恢复源为 `app/default_prompts/product_suite/base.txt` 。商品套图中的「商品卖点与要求」标题行提供「提示词设置」;弹窗左侧编辑模板、右侧按白底图或其他当前分类只读预览最终请求,默认模板首行是“生成目标:{套图名称},{生成目标}”,编辑使用 200ms 防抖。保存与「恢复默认」共用同一个 UTF-8 原子写入服务;恢复默认会先校验内置资源并经确认立即落盘。用户可移动只读规则占位符位置,但不能修改规则内容或删除必需变量;预览和新 job prompt 必须逐字一致。旧版用户模板中的 `{套图名称}` 与 `{补充描述}` 继续兼容,不被启动升级静默替换。
2026-07-08 10:07:47 +08:00
- **变量**:标题提示词本阶段只支持 `{旧标题}` ,左侧按钮「插入旧标题」在标题提示词光标处插入 `{旧标题}` 。封面提示词支持占位符 `{旧标题}` 、`{新标题}` 、`{商品id}` 、`{店铺}` ,生成前用该任务真实值替换(`render_prompt` )。「插入标题」= 在封面提示词光标处插入 `{新标题}` ;「预览」= 用某条任务的值替换封面变量后展示,确认实际发送给 AI 的内容。
2026-06-26 15:30:37 +08:00
### 6.3 应用更新(③ Tab)
2026-06-26 15:55:28 +08:00
- ③ 顶部筛选确定本次作用范围;点击「开始更新」后弹窗展示筛选条件、任务数量和“将提交线上”的风险提示。
2026-07-20 09:41:55 +08:00
- ③ 左下角提供「更新内容」下拉:`只更新标题` / `只更新封面` / `更新标题和封面` 。点击「开始更新」或「检查本轮更新」时,`product_status.build_apply_plan()` 先把候选按商品状态冻结:仅 `normal` 可继续检查 `new_title` / `new_cover_path` , `unlisted` 、`reviewing` 、`unknown` 与未满足历史迁移条件的 NULL 均从 Worker 输入剔除;再对正常商品分出可执行项和内容缺失项。状态功能上线前活动历史批次的空状态由 `init_db()` 已迁移为默认 `normal` ,可直接进入内容完整性检查。状态异常与内容缺失仅在确认框、状态栏和本轮日志列为预检排除,不打开其 Chrome、不写其失败状态、Excel 或运行内 worker 统计。无可执行项时,优先展示真实商品状态原因;只有没有状态异常时才沿用「更新内容未生成」文案。
2026-07-18 17:10:12 +08:00
- 计划指纹覆盖 `task_id/updated_at/product_status/new_title/new_cover_path/更新模式` ;用户确认后重新计算,任何变化均废弃旧计划并要求重新开始。`ApplyWorker` 仍在执行层再次验证 `product_status=normal` ,防止直接构造 Worker 绕过 GUI 预检。①、②本轮的“所有状态”范围选择不向③传递更新授权。
2026-07-09 17:44:03 +08:00
- ③ 提供「检查本轮更新」按钮:只读取当前筛选结果和写运行日志,不打开 Shopee、不提交、不改任务状态;检查汇总展示总数、店铺分布、每批最大条数、预计批次数、更新内容和略过原因。
2026-07-10 10:12:15 +08:00
- 弹确认前先读取 `data/config.json` 的 `shopee_update` 执行参数。普通正式更新不再检查 `test_item_id` 或旧真实提交开关,当前筛选结果可以包含多个真实商品 ID。`max_items_per_run` 作为每批最大任务数,当前筛选总数超过该值时自动分批,不再按总数阻断。
- 用户在③确认弹窗点「是/确认」才开始批量更新;点「否/取消」不执行、不改库。
2026-07-17 12:24:05 +08:00
- 真实更新前必须做账号就绪预检:按当前筛选结果汇总需要的账号;无账号、账号 Chrome 未启动、CDP 端口不可访问、未登录或本轮账号端口冲突时,整体返回 `blocked` 并由 GUI 弹窗列出账号/原因、引导去账号管理。预检不通过时不创建商品编辑页、不调用 `editor.apply_task()` 、不写失败状态、不自动调用「启动登录」或静默打开 Chrome。
2026-07-09 17:44:03 +08:00
- 对确认后的**已生成(generated)任务**:按③「更新内容」模式执行 `open_product` → `change_title(new_title)` (选择标题时)→ `replace_cover(new_cover_path)` (选择封面时)→ `click_update` 提交。选择 `只更新标题` 时即使任务有 `new_cover_path` 也不会替换封面;选择 `只更新封面` 时即使任务有 `new_title` 也不会改标题。
2026-07-02 21:46:32 +08:00
- `open_product` 打开商品详情页失败时,要把页面 toast 中的错误原因上浮到③运行日志和任务失败原因;商品 ID 失效、无权限或店铺不匹配时应能看到 Shopee 原始提示,而不是只看到等待详情页超时;如果此时 tab 是本轮自动新建的,`open_product` 要负责关闭该失败 tab。
2026-06-30 11:44:27 +08:00
- `click_update` 点击页面「更新」后必须处理 Shopee 站点侧二次确认框。2026-06-29 真实测试商品实测:页面会出现 `.eds-modal__content` / `.eds-modal__box` ,标题为 `確定您要更新商品嗎?` ,正文提示建议优化,底部两个按钮分别是 `立即優化` 与主按钮 `更新` 。实现时只允许在标题匹配该确认框、且按钮位于可见 modal footer 内时点击 `button.eds-button--primary` / 文案 `更新` ;不得点击 `立即優化` 。如果弹窗出现但未成功点击主按钮,当前任务必须视为未提交失败,不得写 `committed=1` 。
2026-07-01 08:35:46 +08:00
- 更新封面时,`replace_cover()` 必须按“替换第一张”语义执行:无论当前商品图片是 8 张还是 9 张,只要本次有新封面,就先确认该任务已有本地旧封面备份(`old_cover_path` 非空且文件存在),再删除当前线上第一张图、等待图片管理器稳定、上传新图并拖到第一位。缺失备份时不删除线上第一张图,直接返回明确错误,要求先回到①采集旧封面或修复本地备份。
2026-07-17 12:24:05 +08:00
- 默认串行、单条失败继续;设置「同时更新蝦皮账号」设为 `1` 时逐个账号执行,设为 `2..5` 时按账号分组并行,不同账号可同时跑,同一账号内仍串行。真实更新前检查本轮账号 `debug_port` ,端口冲突直接阻断。
2026-07-01 14:30:49 +08:00
- 「检查本轮更新」只写 `run_logs/run_log_events` 和弹窗/状态栏检查汇总,不调用 `editor.apply_task()` ,不做账号登录预检,不写任务状态,不回写 Excel。
- 真实更新按每批最大条数分批执行,每条立即写 SQLite;全部完成回写 Excel(新字段+状态)+ 弹窗汇总。真实更新与检查都写运行日志,日志 payload 走脱敏工具。
2026-07-01 08:35:46 +08:00
- 分批更新停止语义为协作式停止:点击停止后设置取消标记;当前正在执行的商品跑到安全边界后写库结束,不再开始新商品,也不进入下一批。未开始任务保持原状态,后续可继续。
2026-07-07 14:58:54 +08:00
- T-404a 已在③提供「重置更新状态」:仅当前选中单条,保留 `new_title/new_cover_path` ,本地退回 `stage=generated/status=pending` 以便重复测试上传/提交;若 `committed=1` ,必须提示线上已提交过、本地重置不回滚蝦皮、重复更新会再次提交,并保留 committed 历史事实/运行日志。
2026-07-10 10:12:15 +08:00
- 若商品页是本轮程序自动新建,`apply_task()` 结束时成功/失败都关闭该商品编辑页;成功提交后关闭前等待 2 秒,便于 Shopee 成功状态渲染。复用用户已有 tab 时只断开 CDP,不关闭页面。`open_product` 内部打开失败的新建 tab 仍由 `open_product` 自行关闭。
2026-06-26 15:30:37 +08:00
### 6.4 登录检测
2026-07-14 12:08:12 +08:00
- 无 Shopee tab 时打开卖家中心根地址 `https://<region_host>/` (默认 `https://seller.shopee.tw/` );不自动登录。重定向到登录页返回明确 `LOGIN_PAGE` ;页面 target 可用且 Cookie API 至少成功一次、但确实缺少 `SPC_ST` /`SPC_U` 时返回 `NO_SESSION_COOKIE` ; websocket/target 正在关闭、整个检测周期一次 Cookie 都未成功读取时返回 `LOGIN_CHECK_TARGET_UNAVAILABLE` ,界面显示“登录状态暂不可确认”,不得误报账号退出登录。
- 多个 Shopee page target 并存时优先稳定的卖家中心 portal 页面,再尝试商品页,显式登录页最后判定;单个 target 连接或 Cookie 读取失败后立即重枚举/改连其他未尝试 target,不等待完整 8 秒才交给外层重试。①采集的 retry/recovered 日志必须带当前任务 ID、商品 ID;关闭上一条商品页的 target 仍短暂出现在 `/json` 时,不得把上一条 URL 误记为下一条账号掉线。
2026-07-17 12:24:05 +08:00
- 账号管理中的「启动登录」只负责准备该账号独立 user-data-dir + CDP 端口的 Chrome,供用户人工登录;该入口必须幂等:若端口已响应,复用现有账号 Chrome 并打开/激活卖家中心登录 tab,不再新开 Chrome;若端口未响应,才用卖家中心初始 URL 启动 Chrome,并优先复用本轮初始 page target,避免空白窗口后再建第二页。返回结果用 `created_tab/startup_target_reused/startup_page_navigated/page_target_count/fallback_reason` 说明实际路径,`chrome_launch` 日志同步记录且不得包含密码、Cookie 或 token。「检测登录」只验证当前 Chrome/CDP/会话 Cookie 是否可用。① 采集的预检会复用同一幂等启动能力,自动确保本轮匹配账号 Chrome 就绪但不自动登录;③ 更新的预检只检测,不自动启动缺失浏览器。若商品详情页或卖家中心重定向到 `accounts.shopee.tw/seller/login` ,必须按登录页处理,返回 `LOGIN_PAGE` 并在 GUI 显示未登录。
- T-585 后新配置的 `chrome_path` 默认留空。程序在数据目录就绪、强制升级检查通过后、创建主窗口前,仅当当前路径为空或不可启动时,按当前用户/本机 Windows App Paths(含 64/32 位视图)、标准 Chrome 目录和 `PATH` 自动寻找 `chrome.exe` ;命中才持久化归一化绝对路径,并在状态栏显示“已自动定位 Chrome”。有效的自定义/便携版路径和可从 PATH 启动的 `chrome.exe` 不覆盖;不扫描整盘、不检测 Edge 或 Chromium。设置的“自动检测”只填入输入框并标记未保存,仍由用户点“保存设置”确认写入。
2026-06-26 15:30:37 +08:00
## 七、CDP 已验证事实(务必遵守)
| 难点 | 已验证结论 |
| --- | --- |
| Chrome 启动参数 | 全关后带 `--remote-debugging-port=<port> --remote-allow-origins=* --user-data-dir=<dir>` ;缺 allow-origins 则 WebSocket 403 |
| 代理干扰 | 清除 `*_proxy` ( requests `trust_env=False` ),否则连本地 CDP 超时 |
| WebSocket Origin | `websocket-client` `suppress_origin=True` |
2026-07-17 12:24:05 +08:00
| 关闭连接 vs 关闭 tab | `CDP.close()` 只关闭 WebSocket;需要关闭浏览器页面时必须调用浏览器 target 关闭接口。①采集和商品套图只读商品页关闭本轮自动新建 target 后,最多等待 2 秒确认 target 从 `/json` 消失;超时只记诊断,不覆盖成功结果,复用的用户已有 tab 不关闭。③ 更新时程序自动新建的商品页成功/失败都关闭,成功提交且确认跳回商品列表页时关闭前等待 2 秒;③ 复用用户已有商品页时不关闭页面 |
2026-07-14 12:08:12 +08:00
| 登录 target 竞态 | `/json/close/<target_id>` 返回成功不代表 target 已立即从 `/json` 消失。登录检测不得固定使用枚举到的第一个 Shopee page;连接或 Cookie API 因 target 销毁失败时应快速重选有效页面。只有 Cookie API 成功返回且确实缺会话 Cookie 才是 `NO_SESSION_COOKIE` ;一次都未成功读取是 `LOGIN_CHECK_TARGET_UNAVAILABLE` |
2026-07-17 12:24:05 +08:00
| 前台激活 | ①采集和商品套图只读打开商品页时不主动 `Page.bringToFront` ;新建 tab 尝试 `Target.createTarget(background=true)` ,不支持时退回普通新建。③更新真实提交每条任务都以前台方式新建或激活商品 tab,并执行 `Page.bringToFront` ,保障上传、图片管理器刷新和拖拽排序稳定;后台态封面恢复逻辑仅保留给兼容直接调用,不作为正常③批量路径 |
2026-07-14 16:31:35 +08:00
| SPA 就绪 | 不用 load 事件;轮询“唯一商品名称输入框 + 至少一张主图 itembox + 唯一主图上传输入框”三者都在。脚本返回标题命中数、主图/上传入口状态和当前 URL 的就绪快照;超时错误必须指出具体缺失组件,不能只报泛化超时 |
| 商品页错误 toast | Shopee 错误提示使用 `.eds-toasts` / `.eds-toast__content` ,可能很快隐藏或 `display:none` 。打开商品页/等待 SPA 就绪前应注入 `MutationObserver` 或等价监听,把 toast 文本、`outerHTML` 、当前 URL、时间、可见状态保存到页面缓存(如 `window.__cmshopee_toasts` );等待详情页关键元素超时时,再兜底读取当前 DOM 中的 toast。明确商品失效/不存在/无权限类 toast 即使已经隐藏,也优先成为 `open_product` 失败原因并驱动①阶段列显示“商品失效”;其他普通 toast 只有仍可见且属于当前页面 URL 时,才以“页面提示(可能无关)”附加在就绪快照后。已隐藏的物流、备货、库存、价格等编辑校验提示不得覆盖真正缺失的就绪组件;网络、CDP、未登录、页面超时、风控等其他失败仍显示“失败” |
2026-07-18 16:34:37 +08:00
| 商品状态警示 | 编辑页状态只读取 `.eds-alert.eds-alert--warning` 内的 `.eds-alert-title/.eds-alert-desc` ,不得使用 `data-v-*` 。`審核中/审核中` → `reviewing` , `您的商品未上架` → `unlisted` ;无 warning → `normal` ;未识别 warning、DOM 异常或非法响应 → `unknown` 。多个横幅按 DOM 顺序取第一个白名单命中;只保存归一化文本摘要,不保存整段 HTML |
2026-07-02 21:46:32 +08:00
2026-07-14 16:31:35 +08:00
| 标题输入框 | 主定位为 `data-product-edit-field-unique-id="name"` 业务字段内唯一可见 `input.eds-input__input` ,不再用标题字符数判断身份;仅当该业务字段根不存在时,才回退旧 XPath `//input[@class='eds-input__input' and string-length(@modelvalue)>24]` 。主定位命中多个时明确失败,不猜测写入 |
2026-06-26 15:30:37 +08:00
| 写标题 | 原生 setter + 派发 `input` /`change` ; `value` ==`modelvalue` ==新值 |
2026-07-14 16:31:35 +08:00
| 主图范围与读旧封面 | 主定位为 `data-product-edit-field-unique-id="images"` 内的 `.shopee-image-manager` ,其中可拖拽且 `data-draggable=true` 的第一张 itembox `img.src` 是旧封面(`susercontent` CDN);只有业务字段根不存在时才回退旧全局图片管理器/itembox 定位 |
2026-07-14 09:53:13 +08:00
| 商品套图原主图读取 | 复用 `open_product(..., bring_to_front=False)` 后台只读打开商品详情页,使用已验证 itembox 顺序读取全部主图 `img.src` 并返回 `{index, src}` ;不要求上传 input 之外的新选择器、不改标题/封面、不拖拽、不点击更新;URL 读取完成后再由最多2个图片下载 worker 落盘;本轮自动新建 tab 按采集规则关闭,复用用户已有 tab 不关闭 |
2026-07-14 16:31:35 +08:00
| 上传输入框 | 与主图读取共用 `images` 业务字段内同一个 `.shopee-image-manager` ,从中取唯一 `.shopee-image-manager__upload input[type=file]` ;上传前先点击同一 manager 内 `.shopee-image-manager__upload` 上传块以模拟人工选择图片入口,短暂等待后重新获取 input,再用 `DOM.setFileInputFiles` 传 Windows 路径并派发 `input` /`change` |
2026-07-01 08:35:46 +08:00
| 上传成功 | 上传前先等图片管理器稳定。注意分两种状态:未满 9 张时,上传前要求图片 src 连续稳定、无 loading/blob、上传 input 存在且未禁用;满 9 张时,删除第一张之前只要求现有图片列表稳定,不得要求上传 input 可用,因为 Shopee 可能因满格隐藏/禁用上传入口;删除成功后再要求上传 input 恢复可用。上传后等新图 src 为 `susercontent` 。若手动上传成功但自动上传一直转圈,优先检查是否绕过了上传块点击导致 Shopee 前端上传队列未完整初始化;代码应走“点击上传块 → 等待 → 重新取 input → `DOM.setFileInputFiles` ”的人工等价路径。T-404 补丁后超时失败会返回 `upload_state` ,区分仍在转圈(`UPLOAD_STILL_PROCESSING` )、图片上传错误(`UPLOAD_PAGE_ERROR` )、裁剪弹窗(`UPLOAD_CROP_REQUIRED` )和上传入口未恢复(`UPLOAD_INPUT_NOT_READY` );上传阶段只能把图片管理器内错误或图片/文件/上传相关 toast 归为封面上传错误,物流/备货等页面级校验错误不能阻断封面上传,应留到点击「更新」提交阶段处理;`有1張重複的圖片` / `重複` / `重复` / `duplicate` 属于封面上传错误,必须立即失败并提示新封面与现有商品图片重复 |
2026-06-26 15:30:37 +08:00
| 封面=第一位 | `Input.dispatchMouseEvent` 拖到第一位,落点 `第一张.left - 0.30*宽` |
2026-07-01 08:35:46 +08:00
| 换封面删除 | 更新封面统一先删当前第一张,不再只限满 9 张;先确认本地旧封面备份存在,再点第一张删除(`.shopee-image-manager__icon--delete` 或同类 delete 标记)并在可见 dialog/modal/popover 内点删除/确认按钮。关键顺序:删除前只等当前图片列表稳定,不检查上传 input;删除后不能只看数量减少,必须等图片管理器达到删除后数量、无 busy/blob、上传 input 恢复并短暂稳定,再重新获取 input 上传新图、确认取得 Shopee CDN 地址后拖到第一位;备份缺失则拒绝删除。已在 9 图测试商品 `29671243750` 上实测不提交流程,8 图商品也按同一替换语义删除第一张后再上传 |
| 更新按钮 | 页面主更新按钮为 `button.eds-button` 中 `<span>更新</span>` ;③ 批量确认后逐条点提交;禁用态(校验未过)记为失败。2026-06-29 实测点击后会弹 Shopee 站点侧确认框:可见 `.eds-modal__content` / `.eds-modal__box` ,标题 `確定您要更新商品嗎?` , footer 中 `立即優化` 为次按钮,`更新` 为 `eds-button--primary` 主按钮;代码必须点击确认框内主按钮 `更新` 才算提交,不点 `立即優化` 。2026-06-30 实测确认成功后会跳回 `https://seller.shopee.tw/portal/product/list/all?operationSortBy=modified_time` 商品列表页,代码需记录 `post_update.url/redirected_to_list` 作为提交后观测结果;若随后要关闭本轮自动新开 tab,必须先暂停 2 秒再关闭。判断优先级:跳转到 `/portal/product/list/` 是强成功信号,应优先于残留/短暂 error toast;只有在未跳转列表页、无成功 toast,且错误 toast 持续存在时,才判 `POST_UPDATE_ERROR` 。未处理确认框时不得认为已提交 |
2026-07-02 22:38:08 +08:00
| 登录检测 | 重定向到登录页(含 `accounts.shopee.tw/seller/login` )或缺 `SPC_ST` /`SPC_U` → 未登录 |
2026-06-26 15:30:37 +08:00
2026-07-10 10:12:15 +08:00
高风险动作(删除线上封面、点更新提交、AI 图上线)先在测试商品验证。删除线上第一张封面前必须已有本地旧封面备份,不能在备份缺失时盲删线上图片。注意:本设计无逐条人工审核阶段、无常驻提交开关;③ 点击「开始更新」后必须弹窗确认,确认后才把当前筛选结果中的 AI 标题/封面提交线上。新图本地留档+回写 Excel 是事后追溯手段。
2026-06-26 15:30:37 +08:00
## 八、推荐开发顺序
2026-07-07 14:12:32 +08:00
1. **地基** :先建 `app/` 包、`app/cdp.py` 、`app/__main__.py` 、根目录 `main.py` ;再做 `app/editor.py` (含采集)、`app/appconfig.py` +`data/config.json` 、`app/db.py` 、`.gitignore` 。
2026-07-17 12:24:05 +08:00
2. **账号与启动** (账号管理):`config` 建目录、`chrome` 启动器/快捷方式、登录保活与检测、账号 CRUD。
2026-06-26 15:30:37 +08:00
3. **导入采集** (①):`excel` 导入、采集旧标题/旧封面、回写。
2026-06-26 15:55:28 +08:00
4. **AI 生成** (②):`ai` 模块、提示词、对照预览。
2026-07-07 14:58:54 +08:00
5. **更新蝦皮** (③):对已生成任务弹窗批量确认后换标题+封面、提交、回写。
2026-07-17 12:24:05 +08:00
6. **设置** + 首次未配账号引导保护。
2026-06-26 15:30:37 +08:00
## 九、项目结构建议
```text
cmshopee/
├── docs/
2026-06-26 17:05:18 +08:00
├── app/
│ ├── __init__.py
│ ├── __main__.py # 支持 python -m app
│ ├── cdp.py # CDP 底座(由根目录 cdp.py 迁入)
2026-06-27 10:30:45 +08:00
│ ├── appconfig.py / db.py / excel.py / config.py / accounts.py / chrome.py
2026-06-26 17:05:18 +08:00
│ ├── editor.py / ai.py / prompts.py / gui.py / workers.py
2026-07-13 11:58:05 +08:00
│ ├── release_manifest.py # 发布包文件清单、zip哈希与服务端元数据模板
2026-07-13 12:02:47 +08:00
│ ├── update_installer.py # 自动升级安全下载、解压、manifest校验与同盘暂存
2026-07-13 12:07:59 +08:00
│ ├── updater_entry.py # 独立更新器入口、事务根项目切换、journal与回滚
2026-07-13 12:12:58 +08:00
│ ├── gui/update_dialog.py # 强制升级模态进度、下载worker与重启编排
2026-07-13 12:23:46 +08:00
│ ├── update_health.py # 新版启动健康标记与失败版本熔断
2026-06-26 17:05:18 +08:00
├── main.py # GUI 启动入口:from app.gui import main
2026-06-27 11:29:48 +08:00
├── shopee待处理任务模板.xlsx # 标准空 Excel 模板,可提交;业务填写后的副本不提交
2026-07-07 14:12:32 +08:00
├── data/ # 用户本地数据根(整体 gitignore;打包更新时保留)
│ ├── config.json # 应用配置(模型选择/生成参数/路径)
│ ├── config/ai_models.json # direct AI 模型清单(含密钥)
│ ├── config/cmhub.json # cmhub API Key(含密钥)
│ ├── cmshopee.db # SQLite(账号/任务/结果)
│ ├── chrome_user_data_dir/ # 各账号 Chrome 配置(含登录态)
│ ├── images/ # 旧封面/新封面本地图片
2026-07-10 11:43:35 +08:00
│ ├── title_prompt.txt # 标题当前工作文本(启动回显)
│ └── prompts/
│ ├── title/<名称>.txt # 标题提示词命名模板
2026-07-11 12:35:42 +08:00
│ ├── cover/<名称>.txt # 封面提示词命名模板
2026-07-14 09:53:13 +08:00
│ └── image_studio/<名称>.txt # 旧AI工场模板兼容目录,商品套图不依赖
2026-06-26 15:30:37 +08:00
└── prototypes/ # 已验证原型/探查脚本(demo/set_*/get_title/cookies/inspect_images/grab/1.py)
2026-06-26 17:05:18 +08:00
# 逻辑待并入 app/editor.py 后清理;见 prototypes/README.md
2026-06-26 15:30:37 +08:00
```
2026-07-07 14:12:32 +08:00
> `data/`、旧布局的 `config.json`、`config/ai_models.json`、`config/cmhub.json`、`cmshopee.db`、`chrome_user_data_dir/`、`images/` 含配置/密钥/凭证/业务数据,必须 gitignore。`shopee待处理任务模板.xlsx` 是标准空模板,可以提交;运营填写后的 Excel 副本属于业务数据,不提交。
2026-06-26 15:30:37 +08:00
## 十、架构纪律
2026-07-13 11:58:05 +08:00
### 10.1 发布与自动升级边界
2026-07-13 17:36:46 +08:00
- `app/version.py` 是应用版本唯一来源;`app/release_manifest.py` 生成 `package-manifest.json` 和 `release-metadata<APP_VERSION>.json` ,构建脚本不得自行维护另一套 hash 或路径规则。版本化元数据与对应 zip 一一匹配,同版本可覆盖、其他历史版本保留,旧无版本文件在构建时清理。
2026-07-13 11:58:05 +08:00
- 发布包格式固定为 `cmshopee-portable-v1` ,入口为 `cmshopee.exe` ,程序依赖集中在 `_internal/` 。manifest 覆盖除自身外的所有程序文件,并记录规范化相对路径、字节数和 SHA-256。
- 发布包只能包含程序根项目,`data/` 和 `.cmshopee-update/` 永远在替换边界之外。第一阶段预留签名字段,但 SHA-256 只负责传输完整性,不等同于发布者身份认证。
- T-615 只提供可验证发布契约;启动门禁仍保持 T-544 的人工下载行为,直到后续下载、独立更新器、事务替换和失败熔断任务全部接入。
2026-07-13 16:19:03 +08:00
- T-616 的下载暂存根固定为安装目录下 `.cmshopee-update/` ,与 `data/` 完全隔离。T-623 为兼容对象存储/CDN外链,临时允许任意公网 HTTP/HTTPS 域名或公网 IP 及跨域、跨协议重定向,但版本接口继续使用受信任 HTTPS;初始地址、每次跳转和最终地址均拒绝本机、内网及非 HTTP(S) 目标。远程zip必须通过声明大小、整包SHA-256、安全zip路径和包内manifest逐文件校验,才写 `pending.json` ;HTTP 不降低任何安装校验,也不替换运行中程序文件。
2026-07-13 12:07:59 +08:00
- T-617 的独立 `cmshopee-updater.exe` 必须先复制到系统临时目录运行,并等待主程序退出。替换粒度是manifest允许的程序根项目,旧根先整体移动到同盘backup,新根再整体移入;事务锁防止并发更新,journal记录每次移动,任一步失败逆序恢复。`data/` 、更新管理目录与未知安装根项目永不进入替换清单。
2026-07-13 12:12:58 +08:00
- T-618 在创建 `MainWindow` 前显示强制升级 `QDialog` ;下载与校验只能在 `QObject + QThread` worker中执行,线程结束前保留引用,取消时等待part清理。独立更新器进程成功创建后才退出旧主程序;强制版本后续失败保持阻断,只有版本接口本身不可达/非法继续失败放行。
2026-07-13 12:23:46 +08:00
- T-619 要求新版按事务写 `process_started` 、`main_window_ready` 、`environment_blocked` 健康标记。更新器只在主窗口就绪后认定升级成功并清理成功备份;无标记/早期退出/超时则事务回滚并按目标版本+包hash熔断。环境阻断保留新版,不把本地数据目录、配置或Chrome问题误判成坏发布包。
2026-07-13 17:17:25 +08:00
- T-624 以 cmhub 实际响应作为版本接口边界:服务端只需提供版本、下载地址、zip SHA-256、准确大小、强制策略、发布说明和发布时间;包格式与更新器协议由客户端内置,并在下载解压后通过包内manifest验证,不要求版本接口重复返回。
2026-07-13 11:58:05 +08:00
2026-06-26 15:30:37 +08:00
- CDP 交互事实变化同步第七节。
2026-06-26 17:05:18 +08:00
- 正式代码只放 `app/` 包;根目录只保留 `main.py` 、配置/数据目录、文档和原型目录,不新增正式业务模块。
- `app` 包内模块优先用相对导入(如 `from .cdp import CDP` );根入口 `main.py` 用 `from app.gui import main` 。
2026-07-07 14:12:32 +08:00
- 存储边界:应用设置→`data/config.json` ,账号/任务/结果→SQLite,图片→本地目录并记路径于 DB,登录态→user-data-dir;同一事实只存一处。
2026-06-26 15:30:37 +08:00
- 别名是账号↔任务唯一关联键。
2026-06-29 10:05:16 +08:00
- 密码与 AI Key 本地明文保存,保存/变更时弹窗提示;UI 打码、不外传、不写日志/导出;不自动登录。
2026-06-26 15:55:28 +08:00
- AI 生成内容直接进入 ③ 更新候选;③ 批量确认后提交,新图本地留档 + 回写 Excel 以备追溯。
2026-07-01 09:41:10 +08:00
- 批次删除是软删除:写 `batches.deleted_at/deleted_reason` ,不物理删除 `batches/tasks` ;默认 `list_batches/list_tasks/get_task` 与 ①/②/③ 页面和执行入口都排除已删除批次。
2026-06-26 15:30:37 +08:00
- 高风险模块先单独验证,再接入流水线。
2026-06-26 16:09:09 +08:00
- GUI 只通过 signal/slot 接收 worker 进度;禁止后台线程直接操作 Qt widget 或共享 SQLite connection。