refactor(navigation): simplify main tab labels
This commit is contained in:
+36
-36
@@ -40,7 +40,7 @@ cmhub_request_url(base_url, endpoint) -> str # 先规整 base_url,再拼 /ap
|
||||
response_timeout(config=None) -> int # = resolution_timeouts[resolution](返回超时,随分辨率)
|
||||
```
|
||||
|
||||
`default_config()` / `load_config()` 包含 `shopee_update` 执行配置段:历史/调试兼容测试商品 ID、更新内容模式 `update_mode`、每批最大更新条数、内部兼容 `dry_run`、同时更新蝦皮账号数 `max_parallel_accounts`。普通正式更新不再用测试商品 ID 或旧真实提交开关阻断当前筛选结果;封面是否参与本轮更新由③「更新内容」下拉决定;线上提交前的显式确认边界是③「开始更新」确认弹窗。`product_suite.last_settings` 只保存⑥最近选择的平台/站点/语言/比例,供未绑定商品的新任务初始化;已有项目自己的 `suite_settings_json` 优先。`config.json` 不保存 AI Key;写入 `api_key` / `*_key` / `token` / `*_token` / `password` / `*_password` 等敏感字段时抛 `ConfigError`。普通产品默认 cmhub,AI Key 存 `data/config/cmhub.json`;`data/config/ai_models.json` 仅为 direct 内部兼容路径。T-538 后,配置中默认仍保存 `chrome_user_data_dir`、`images`、`cmshopee.db` 等相对值,运行时解析到 `data/` 下,保持免安装目录可移动。
|
||||
`default_config()` / `load_config()` 包含 `shopee_update` 执行配置段:历史/调试兼容测试商品 ID、更新内容模式 `update_mode`、每批最大更新条数、内部兼容 `dry_run`、同时更新蝦皮账号数 `max_parallel_accounts`。普通正式更新不再用测试商品 ID 或旧真实提交开关阻断当前筛选结果;封面是否参与本轮更新由③「更新内容」下拉决定;线上提交前的显式确认边界是③「开始更新」确认弹窗。`product_suite.last_settings` 只保存商品套图最近选择的平台/站点/语言/比例,供未绑定商品的新任务初始化;已有项目自己的 `suite_settings_json` 优先。`config.json` 不保存 AI Key;写入 `api_key` / `*_key` / `token` / `*_token` / `password` / `*_password` 等敏感字段时抛 `ConfigError`。普通产品默认 cmhub,AI Key 存 `data/config/cmhub.json`;`data/config/ai_models.json` 仅为 direct 内部兼容路径。T-538 后,配置中默认仍保存 `chrome_user_data_dir`、`images`、`cmshopee.db` 等相对值,运行时解析到 `data/` 下,保持免安装目录可移动。
|
||||
|
||||
敏感信息展示/日志辅助:
|
||||
|
||||
@@ -60,8 +60,8 @@ save_cmhub_config(config, path="data/config/cmhub.json") -> dict
|
||||
get_cmhub_api_key(path="data/config/cmhub.json", masked=False) -> str
|
||||
```
|
||||
|
||||
`data/config.json` 只保存 `ai.backend`、`ai.cmhub.base_url/title_alias/image_alias/vision_alias/connect_timeout` 等非密钥配置;`vision_alias` 默认 `vision-standard`,供⑥「AI帮写」独立使用。T-529 后普通设置页固定保存 `ai.backend=cmhub`,不暴露后端切换;`data/config/cmhub.json` 必须 gitignore,展示时打码,不写日志/导出。
|
||||
AI 模型清单(`data/config/ai_models.json`,含本地明文密钥,已建;UI 由 ⑤ 设置复用):
|
||||
`data/config.json` 只保存 `ai.backend`、`ai.cmhub.base_url/title_alias/image_alias/vision_alias/connect_timeout` 等非密钥配置;`vision_alias` 默认 `vision-standard`,供商品套图中的「AI帮写」独立使用。T-529 后普通设置页固定保存 `ai.backend=cmhub`,不暴露后端切换;`data/config/cmhub.json` 必须 gitignore,展示时打码,不写日志/导出。
|
||||
AI 模型清单(`data/config/ai_models.json`,含本地明文密钥,已建;UI 由 设置复用):
|
||||
|
||||
```python
|
||||
default_ai_models_config() -> dict
|
||||
@@ -179,7 +179,7 @@ export_copy(batch_id, out_dir_or_path, path=None) -> dict
|
||||
# 退路:另存带旧字段的副本,不动原文件;目录输出时生成 *_cmshopee回写.xlsx
|
||||
```
|
||||
|
||||
列模板见 [架构 5.3](04-architecture.md);别名以“别名”列为权威,必须与 ④ 账号管理中的账号别名一致。`shopee待处理任务模板.xlsx` 是可提交的标准空模板;运营填写后的 Excel 副本属于业务数据,不提交。
|
||||
列模板见 [架构 5.3](04-architecture.md);别名以“别名”列为权威,必须与 账号管理中的账号别名一致。`shopee待处理任务模板.xlsx` 是可提交的标准空模板;运营填写后的 Excel 副本属于业务数据,不提交。
|
||||
|
||||
## config 模块(`app/config.py`,已建)
|
||||
|
||||
@@ -194,7 +194,7 @@ ensure_user_data_dir(slug, root=None, config=None) -> str
|
||||
|
||||
## accounts 模块(`app/accounts.py`,已建)
|
||||
|
||||
账号管理服务层,供 ④ Tab 调用;不操作 QWidget,不自动登录/填密码。
|
||||
账号管理服务层,供 账号管理 Tab 调用;不操作 QWidget,不自动登录/填密码。
|
||||
|
||||
```python
|
||||
class AccountError(RuntimeError): ...
|
||||
@@ -223,7 +223,7 @@ login_status_text(status) -> str
|
||||
- `debug_port` 在账号服务层按账号唯一校验;默认端口取 `debug_port_range` 中第一个未占用端口。
|
||||
- `delete_account()` 只删除 DB 账号记录,不删除本地 user-data-dir,避免误删登录态。
|
||||
- `launch_for_login()` 是幂等入口。先检查 `chrome.is_running(account.debug_port)`;若该账号 CDP 端口已响应,不再调用 `subprocess.Popen`,而是复用已打开的账号 Chrome,优先激活/打开 `https://<region_host>/portal/` 或卖家中心登录 tab,并返回 `reused=true`,且不会改写已有普通页面。若端口未响应,才启动带该账号 user-data-dir、CDP 端口和唯一卖家中心初始 URL 的 Chrome;CDP 就绪后短时等待并复用初始 target,只有无安全可复用 page target 时才兜底创建一次。返回值除 `action/pid/target_id/url/created_tab` 外,还带 `initial_url/startup_target_reused/startup_page_navigated/page_target_count/fallback_reason/target_probe_error` 供日志诊断。不会读取、填写或提交密码,不绕过验证码。
|
||||
- ③ 更新蝦皮的账号预检不会自动调用 `launch_for_login()`;Chrome 未启动/端口不可达时只返回阻断原因,由 GUI 提示用户去④手动打开账号浏览器并登录。T-105b 的复用逻辑只作用于④用户主动点击「启动登录」这一入口。
|
||||
- ③ 更新蝦皮的账号预检不会自动调用 `launch_for_login()`;Chrome 未启动/端口不可达时只返回阻断原因,由 GUI 提示用户前往账号管理手动打开账号浏览器并登录。T-105b 的复用逻辑只作用于用户在账号管理中主动点击「启动登录」这一入口。
|
||||
- `create_shortcut()` 生成 `.lnk`,目标/参数复用 `chrome.build_launch_args()`,不包含密码。
|
||||
- `detect_login()` 复用 `editor.login_status()`;检测为已登录时更新 `last_login_at`。
|
||||
|
||||
@@ -305,7 +305,7 @@ gen_cover(cover_prompt, old_cover_path, out_path, resolution=None, jpg_quality=N
|
||||
# 支持返回 url / data URL / b64_json,按 resolution resize 并以 jpg_quality 保存 JPEG,返回路径;新生成默认写入 `image_dir/<batch_id>/<slug>/<task_id>_<item_id>_new.jpg`,历史 DB 已存路径继续按原路径读取
|
||||
|
||||
analyze_product_images(instruction, context, image_paths, config=None, cmhub_config_path="data/config/cmhub.json", on_event=None) -> dict
|
||||
# 仅⑥商品套图AI帮写使用;backend 必须为 cmhub。按输入顺序校验1..8张本地图片(单张<=10MiB、合计<=32MiB),在一次 POST /api/v1/analyze/images 中作为同商品多角度证据集发送;固定提示词要求一份商品级联合分析,不输出逐图编号,并按商品概述/可确认卖点/人群场景/套图画面/待确认信息组织文本。返回 {text, image_count, metadata};metadata 只允许 alias/model_used/points_cost/points_balance/call_id。
|
||||
# 仅商品套图AI帮写使用;backend 必须为 cmhub。按输入顺序校验1..8张本地图片(单张<=10MiB、合计<=32MiB),在一次 POST /api/v1/analyze/images 中作为同商品多角度证据集发送;固定提示词要求一份商品级联合分析,不输出逐图编号,并按商品概述/可确认卖点/人群场景/套图画面/待确认信息组织文本。返回 {text, image_count, metadata};metadata 只允许 alias/model_used/points_cost/points_balance/call_id。
|
||||
|
||||
generate_batch(tasks, prompts, ai_cfg=None, on_progress=None, should_stop=None) -> dict
|
||||
fetch_cmhub_models(base_url, api_key, connect_timeout=10, read_timeout=30) -> list[dict]
|
||||
@@ -320,11 +320,11 @@ fetch_cmhub_models(base_url, api_key, connect_timeout=10, read_timeout=30) -> li
|
||||
要点:
|
||||
|
||||
- `backend=direct`:内部兼容/手工回滚路径;标题用 `default_text_model`、封面用 `default_image_model`(`appconfig.get_model` 取定义,含 url/key/api_type)。
|
||||
- `backend=cmhub`:普通产品默认路径;标题调用 `POST /api/v1/generate/title`;②批量封面生成调用 `POST /api/v1/generate/image/tasks` + `GET /api/v1/generate/image/tasks/{task_id}`,模型字段使用 `ai.cmhub.title_alias/image_alias`,Key 来自 `data/config/cmhub.json`。⑥「AI帮写」单独调用 `POST /api/v1/analyze/images`,只使用 `ai.cmhub.vision_alias`,不得回退或混用生文/生图别名;同一商品项目的原图在一条请求内联合理解,用户勾选状态不参与选图。`gen_cover()` 单独调用没有任务/DB 上下文,第一版保留旧同步 `POST /api/v1/generate/image` 兼容路径。
|
||||
- `backend=cmhub`:普通产品默认路径;标题调用 `POST /api/v1/generate/title`;②批量封面生成调用 `POST /api/v1/generate/image/tasks` + `GET /api/v1/generate/image/tasks/{task_id}`,模型字段使用 `ai.cmhub.title_alias/image_alias`,Key 来自 `data/config/cmhub.json`。商品套图中的「AI帮写」单独调用 `POST /api/v1/analyze/images`,只使用 `ai.cmhub.vision_alias`,不得回退或混用生文/生图别名;同一商品项目的原图在一条请求内联合理解,用户勾选状态不参与选图。`gen_cover()` 单独调用没有任务/DB 上下文,第一版保留旧同步 `POST /api/v1/generate/image` 兼容路径。
|
||||
- 标题提示词组装:`gen_title()` 的 direct 与 cmhub 路径共用标题 prompt 规则。若标题提示词包含 `{旧标题}`,生成前替换为该任务旧标题,不再自动追加旧标题块;若不包含 `{旧标题}`,保持旧行为自动追加“旧标题:...”块。两种情况都会追加“请只返回新标题,不要解释。”输出约束;其它 `{...}` 原样保留。
|
||||
- `fetch_cmhub_models()` 调 `GET /api/v1/models` 返回别名清单,供⑤设置页动态下拉、⑥AI帮写和⑥正式套图生成的付费前确认共用;Base URL 会先规整为网关根,HTTP 404 映射为 `not_found` 并提示检查 Base URL 或实例是否部署 `/api/v1/models`。GUI 只在进程内按规整网关地址和别名短期缓存公开模型元数据,不缓存 API Key,也不写入配置、SQLite、日志或导出。AI帮写仅在当前 `vision_alias` 命中 `operation_type=vision`、`requires_image=true`、`pricing_status=priced` 且有唯一无条件 `points_cost` 时显示预计扣点;正式套图同样只在当前 `image_alias` 命中唯一无条件的图片生图价格时显示单张和按最终 `specs` 计算的总价。其它情况只提示实际以网关返回为准,预估值不参与扣减或成功判定。
|
||||
- `fetch_cmhub_models()` 调 `GET /api/v1/models` 返回别名清单,供设置页动态下拉、商品套图 AI帮写和商品套图正式生成的付费前确认共用;Base URL 会先规整为网关根,HTTP 404 映射为 `not_found` 并提示检查 Base URL 或实例是否部署 `/api/v1/models`。GUI 只在进程内按规整网关地址和别名短期缓存公开模型元数据,不缓存 API Key,也不写入配置、SQLite、日志或导出。AI帮写仅在当前 `vision_alias` 命中 `operation_type=vision`、`requires_image=true`、`pricing_status=priced` 且有唯一无条件 `points_cost` 时显示预计扣点;正式套图同样只在当前 `image_alias` 命中唯一无条件的图片生图价格时显示单张和按最终 `specs` 计算的总价。其它情况只提示实际以网关返回为准,预估值不参与扣减或成功判定。
|
||||
- `api_type=chat/auto` 走 OpenAI-compatible chat JSON;`api_type=images_edits` 走 multipart form。
|
||||
- direct 连接超时参考模型 `connect_timeout_seconds`;**返回超时 = 模型 `timeout_seconds` 或 `appconfig.response_timeout()`(随分辨率:512/1k/2k/4k → 180/240/360/600)**。cmhub 使用 `requests timeout=(connect, read)`,connect 来自 `ai.cmhub.connect_timeout`;cmhub 生文读取等待固定600秒,⑥图片理解读取等待固定120秒且一次读超时不重试,提示结果未确认;②批量生图异步 submit 读取等待36秒、poll 单次读取等待15秒、本地总预算900秒,图片下载读取等待900秒;`gen_cover()` 旧同步兼容路径仍用900秒读取等待。
|
||||
- direct 连接超时参考模型 `connect_timeout_seconds`;**返回超时 = 模型 `timeout_seconds` 或 `appconfig.response_timeout()`(随分辨率:512/1k/2k/4k → 180/240/360/600)**。cmhub 使用 `requests timeout=(connect, read)`,connect 来自 `ai.cmhub.connect_timeout`;cmhub 生文读取等待固定600秒,商品套图图片理解读取等待固定120秒且一次读超时不重试,提示结果未确认;②批量生图异步 submit 读取等待36秒、poll 单次读取等待15秒、本地总预算900秒,图片下载读取等待900秒;`gen_cover()` 旧同步兼容路径仍用900秒读取等待。
|
||||
- 并发数/重试/分辨率/jpg 质量来自 `appconfig.ai_config()`;标题/图片并发会被夹到 1..5,失败重试次数会被夹到 0..10,兼容旧配置中的超限值;Key 本地明文存储,但不入日志、不导出。cmhub 响应的 `points_cost/points_balance/call_id` 不改变返回值,通过 `on_event` metadata 事件上报,GUI 余额/计费展示留给 T-528。
|
||||
- 标题快、图片慢:分两段、各用各自并发数;失败按 `retry` 重试,仍失败记 error 不阻塞其余。cmhub 批量生图使用内部实际并发 `min(image_concurrency, 5)` 约束 submit+poll 在途数;已有 `tasks.image_task_id` 时直接 GET 续查,不二次 submit;submit 前先持久化 `image_task_key`,submit 成功立即持久化 `image_task_id`;poll `failed/expired` 会清空二者,poll 超时/用户停止/程序退出则保留二者供下次续查。拿到 `image_url` 后交给独立下载/保存线程池,下载线程数与实际生图并发一致且最大 5;T-548 后图片下载后端由 `ai.cmhub.download_with_curl` 控制,默认 `auto`:Windows 且检测到系统 curl 时优先用 curl 下载,否则回退 requests;curl 失败也会自动回退 requests。下载前仍执行公网 URL 校验,curl 使用 `-K` 临时配置文件传入 URL,不把带 token 的 `image_url` 放进命令行参数;`use_system_proxy=false` 时 curl 加 `--noproxy "*"`。下载失败最多安全重试 3 次,只复用同一个 `image_url`,不会重新调用 cmhub 生图接口;下载总耗时超过 20 秒时写“图片下载较慢”警告;direct 兼容路径暂保持原 `image_concurrency` 语义。
|
||||
- cmhub 图片地址兼容:优先递归读取 `image_url` / `image_url.url` / `images[]` / `image_urls[]` 等字段;若返回 `/generated/...` 或 `generated/...` 相对路径,先按 cmhub Base URL 补全为完整 `http(s)` URL,再进入原有公网 URL 安全校验和下载。
|
||||
@@ -363,7 +363,7 @@ render_prompt(template_text, task) -> str
|
||||
|
||||
## 商品套图模块(`app/product_suite.py` + `app/image_studio*.py`,已建)
|
||||
|
||||
商品套图是 ⑥ Tab 的本地图片项目工作区,不自动上传蝦皮、不修改线上商品。底层继续复用旧 AI工场的数据表与生成服务,避免迁移既有项目数据。
|
||||
商品套图是主窗口中的本地图片项目工作区,不自动上传蝦皮、不修改线上商品。底层继续复用旧 AI工场的数据表与生成服务,避免迁移既有项目数据。
|
||||
|
||||
```python
|
||||
# app/image_studio.py
|
||||
@@ -424,8 +424,8 @@ export_generation_round(project_id, generation_round_key, parent_dir, path=None,
|
||||
- 项目唯一键为账号别名 + 商品 ID;图片文件默认在 `data/images/pool/<slug>/<item_id>/` 下分 `originals/generated/exports`,删除生成图进入项目内 `.trash` 并可撤销。
|
||||
- `image_studio_projects.suite_settings_json` 保存平台/国家/语言/比例/逐图主图/分类数量;`draft_prompt` 保存商品卖点。有效原图上限16张,missing 历史不占名额。
|
||||
- T-643 后 `image_studio_projects.current_generation_round_key` 记录项目主界面的当前生成轮次;`image_studio_jobs.generation_round_key` 与 `generation_slot_index` 记录整轮归属和稳定展示槽位。`promote_generation_round_if_success()` 只会在同项目、同轮已存在成功 job 时原子切换当前轮;`list_generation_rounds()` 返回轮次摘要,`list_generation_round_current_jobs()` 返回每个槽位最新尝试,`list_generation_round_attempts()` 返回完整尝试历史。旧 job 保持 NULL 并按“旧版历史记录”兼容,不猜测轮次边界。
|
||||
- T-646 后 `list_global_generation_rounds()` 是⑥跨项目历史的唯一查询入口:仅返回未软删除项目的轮次摘要,按创建时间倒序并支持账号/店铺、商品 ID、项目 ID、`limit`/`offset` 筛选;正常轮次只统计每个稳定槽位的最新有效 job,完整尝试数仅用于计算重试数,NULL 轮次保留“旧版历史记录”语义。`ProductSuiteGlobalHistoryDialog` 组合该查询和 `list_generation_round_current_jobs()`,默认读取30轮,显示受限缩略图并可按轮预览;窗口不写业务数据,也不改变当前轮次。`ProductSuiteHistoryExportWorker` 在后台调用 `export_generation_round()`,只复制该轮成功且存在的输出 asset 到用户选择目录中新建的安全子目录;同名目录追加稳定序号,不覆盖外部文件,部分文件失败只汇总中文结果,源 asset 保持不变。
|
||||
- T-648 后 `get_successful_generation_history_summary()` 是⑥常规新一轮生成前的只读判据:它只查询未软删除当前项目的最新有效成功 job,返回成功轮数、成功图片数和最近成功时间;正常轮次按稳定槽位取最新尝试,NULL 旧版轮次整体按一轮计数,只有失败/取消/进行中的 job 返回零摘要。该 API 不扫描文件目录、不创建 job/asset/轮次,也不改变当前轮;GUI 的“查看历史”仅调用全局历史窗口并临时启用当前商品筛选。
|
||||
- T-646 后 `list_global_generation_rounds()` 是商品套图跨项目历史的唯一查询入口:仅返回未软删除项目的轮次摘要,按创建时间倒序并支持账号/店铺、商品 ID、项目 ID、`limit`/`offset` 筛选;正常轮次只统计每个稳定槽位的最新有效 job,完整尝试数仅用于计算重试数,NULL 轮次保留“旧版历史记录”语义。`ProductSuiteGlobalHistoryDialog` 组合该查询和 `list_generation_round_current_jobs()`,默认读取30轮,显示受限缩略图并可按轮预览;窗口不写业务数据,也不改变当前轮次。`ProductSuiteHistoryExportWorker` 在后台调用 `export_generation_round()`,只复制该轮成功且存在的输出 asset 到用户选择目录中新建的安全子目录;同名目录追加稳定序号,不覆盖外部文件,部分文件失败只汇总中文结果,源 asset 保持不变。
|
||||
- T-648 后 `get_successful_generation_history_summary()` 是商品套图常规新一轮生成前的只读判据:它只查询未软删除当前项目的最新有效成功 job,返回成功轮数、成功图片数和最近成功时间;正常轮次按稳定槽位取最新尝试,NULL 旧版轮次整体按一轮计数,只有失败/取消/进行中的 job 返回零摘要。该 API 不扫描文件目录、不创建 job/asset/轮次,也不改变当前轮;GUI 的“查看历史”仅调用全局历史窗口并临时启用当前商品筛选。
|
||||
- T-649 后 `list_global_history_accounts()` 只返回含 job 且未软删除项目的历史账号别名,供历史窗口补齐已删除账号的可选项。`list_global_generation_rounds(account_alias=...)` 以 `projects.account_alias = ?` 精确筛选;传入该参数时优先于旧兼容参数 `account_query`,后者仅保留给既有调用的模糊查询。当前账号显示文本来自账号管理,历史缺失账号只显示明确的历史标识,UI 不通过显示文本反查筛选值。
|
||||
- 拉取蝦皮原主图只读:复用 `editor.open_product(..., bring_to_front=False)` 和 `editor.read_product_image_urls()`,不上传、不拖拽、不点击更新。
|
||||
- 原图下载走 `image_studio_images` 的公网 URL、大小、Content-Type、重定向和 PIL 解码校验;只在用户单击时落盘。
|
||||
@@ -441,55 +441,55 @@ export_generation_round(project_id, generation_round_key, parent_dir, path=None,
|
||||
```python
|
||||
# GUI 入口
|
||||
main() -> int # 创建 QApplication + MainWindow
|
||||
class MainWindow(QMainWindow) # QTabWidget: ①②③④⑤⑥;支持注入 db_path/config/config_path/ai_models_path 便于测试
|
||||
class MainWindow(QMainWindow) # QTabWidget: ①、②、③、账号管理、设置、商品套图;支持注入 db_path/config/config_path/ai_models_path 便于测试
|
||||
class CollectTab(QWidget) # ① 导入采集:导入 Excel + 汇总栏 + QTableView 任务列表 + 未匹配略过标记
|
||||
class GenerateTab(QWidget) # ② AI生成:提示词管理 + 筛选任务 + 生成封面图片成本开关 + 开始/停止生成 + 新旧封面预览 + AI生成运行日志
|
||||
class ApplyTab(QWidget) # ③ 更新蝦皮:筛选已生成任务 + 检查本轮更新 + 缺失内容校验 + 确认后分批真实更新 + 运行日志
|
||||
class SettingsTab(QWidget) # ⑤ 设置:cmhub 网关配置 + 响应式三列布局 + 角色/生成参数/路径端口 + 蝦皮更新安全 + 未保存状态追踪
|
||||
class ProductSuiteTab(QWidget) # ⑥ 商品套图:多任务、原图、结构配置、AI帮写、cmhub生成、历史结果
|
||||
class SettingsTab(QWidget) # 设置:cmhub 网关配置 + 响应式三列布局 + 角色/生成参数/路径端口 + 蝦皮更新安全 + 未保存状态追踪
|
||||
class ProductSuiteTab(QWidget) # 商品套图:多任务、原图、结构配置、AI帮写、cmhub生成、历史结果
|
||||
class ImageStudioTab(QWidget) # 旧AI工场兼容实现;主窗口不再创建
|
||||
class CollectWorker(BaseWorker) # ① 后台采集:账号就绪预检 -> editor.collect -> db.set_collected/mark_skipped/mark_failed
|
||||
class GenerateWorker(BaseWorker) # ② 后台生成:ai.generate_batch -> db.set_generated/set_generated_cover/mark_failed + 进度
|
||||
class ApplyWorker(BaseWorker) # ③ 后台更新:账号就绪预检 -> 检查或按批调用 editor.apply_task(...) -> db.set_applied/mark_skipped
|
||||
class WriteBackWorker(BaseWorker) # ①/③ 后台回写:旧字段或更新结果写回原 Excel
|
||||
class AIModelTestWorker(BaseWorker) # ⑤ 后台测试 AI 模型连接:appconfig.test_ai_model
|
||||
class ImageStudioPullImagesWorker(BaseWorker) # ⑥ 后台只读拉蝦皮原主图 URL;安全边界协作停止
|
||||
class ImageStudioDownloadOriginalWorker(BaseWorker)# ⑥ 后台下载远程原图
|
||||
class ImageStudioGenerateJobsWorker(BaseWorker) # ⑥ 后台提交/查询/下载 cmhub 生图 job
|
||||
class ImageStudioResumeJobsWorker(BaseWorker) # ⑥ 后台恢复已有 task_id 的生图 job
|
||||
class ImageStudioExportWorker(BaseWorker) # ⑥ 后台导出终选 JPEG
|
||||
class ProductSuiteImportImagesWorker(BaseWorker) # ⑥ 后台校验并复制本地/剪贴板商品原图
|
||||
class ProductSuiteGenerateWorker(BaseWorker) # ⑥ 按套图job规划提交/查询/下载
|
||||
class ProductSuiteAiWriteWorker(BaseWorker) # ⑥ 后台用本地原图调用图片理解,生成商品卖点与画面要求
|
||||
class AIModelTestWorker(BaseWorker) # 设置 后台测试 AI 模型连接:appconfig.test_ai_model
|
||||
class ImageStudioPullImagesWorker(BaseWorker) # 商品套图 后台只读拉蝦皮原主图 URL;安全边界协作停止
|
||||
class ImageStudioDownloadOriginalWorker(BaseWorker)# 商品套图 后台下载远程原图
|
||||
class ImageStudioGenerateJobsWorker(BaseWorker) # 商品套图 后台提交/查询/下载 cmhub 生图 job
|
||||
class ImageStudioResumeJobsWorker(BaseWorker) # 商品套图 后台恢复已有 task_id 的生图 job
|
||||
class ImageStudioExportWorker(BaseWorker) # 商品套图 后台导出终选 JPEG
|
||||
class ProductSuiteImportImagesWorker(BaseWorker) # 商品套图 后台校验并复制本地/剪贴板商品原图
|
||||
class ProductSuiteGenerateWorker(BaseWorker) # 商品套图 按套图job规划提交/查询/下载
|
||||
class ProductSuiteAiWriteWorker(BaseWorker) # 商品套图 后台用本地原图调用图片理解,生成商品卖点与画面要求
|
||||
class CMHubModelCatalogWorker(BaseWorker) # 后台读取公开模型目录并填充进程内缓存,不提交图片、不扣点
|
||||
class ProductSuiteHistoryExportWorker(BaseWorker) # ⑥ 后台复制一轮历史成功生成图
|
||||
class ProductSuiteHistoryExportWorker(BaseWorker) # 商品套图 后台复制一轮历史成功生成图
|
||||
class TaskTableModel(QAbstractTableModel) # 任务表格模型:账号/别名/商品ID/阶段;未匹配别名显示“略过”
|
||||
class GenerateTaskTableModel(QAbstractTableModel) # ② 任务表格模型:店铺/商品ID/旧标题/新标题/状态;generated/未提交/非运行中新标题可本地编辑
|
||||
class ApplyTaskTableModel(QAbstractTableModel) # ③ 任务表格模型:店铺/商品ID/新标题/新封面/阶段/结果;保持只读,重置更新状态走右键菜单
|
||||
class AccountsTab(QWidget) # ④ 账号管理:表格 + 增删改 + 启动登录 + 检测登录 + 快捷方式
|
||||
class AccountsTab(QWidget) # 账号管理:表格 + 增删改 + 启动登录 + 检测登录 + 快捷方式
|
||||
class AccountDialog(QDialog) # 账号编辑弹窗;密码 QLineEdit.Password
|
||||
TAB_TITLES: list[str] # 固定 Tab 顺序
|
||||
TAB_STYLE: str # 顶层 Tab 栏防误点样式:最小宽度/padding/间距/当前态
|
||||
```
|
||||
|
||||
T-523 后 GUI 已从旧 `app/gui.py` 拆为 `app/gui/` 包:`__init__.py` 负责旧导入路径兼容与 `main()`;`main_window.py` 放 `MainWindow`;`models.py` 放 3 个 TableModel;`widgets.py` 放色板、空状态卡、批次总览和日志 helper;`workers.py` 放具体 GUI worker;`tabs/` 下按 ①~⑥ 拆分各 Tab。对外仍保留 `from app import gui`、`from app.gui import MainWindow/CollectTab/GenerateWorker/...`。
|
||||
T-523 后 GUI 已从旧 `app/gui.py` 拆为 `app/gui/` 包:`__init__.py` 负责旧导入路径兼容与 `main()`;`main_window.py` 放 `MainWindow`;`models.py` 放 3 个 TableModel;`widgets.py` 放色板、空状态卡、批次总览和日志 helper;`workers.py` 放具体 GUI worker;`tabs/` 下按六个主 Tab 拆分。对外仍保留 `from app import gui`、`from app.gui import MainWindow/CollectTab/GenerateWorker/...`。
|
||||
|
||||
`MainWindow` 已实现六 Tab、① 导入采集任务列表、② AI生成布局/提示词/开始生成/停止/封面对照预览、③ 更新蝦皮筛选列表与检查/确认后分批真实更新、④ 账号管理、⑤ cmhub 设置、⑥ 商品套图生成。缺 PySide6 时 `main()` 返回 1 并输出明确提示。
|
||||
`MainWindow` 已实现六 Tab、① 导入采集任务列表、② AI生成布局/提示词/开始生成/停止/封面对照预览、③ 更新蝦皮筛选列表与检查/确认后分批真实更新、账号管理、设置中的 cmhub 配置、商品套图生成。缺 PySide6 时 `main()` 返回 1 并输出明确提示。
|
||||
|
||||
主 Tab 栏必须在 `MainWindow` 初始化时应用 `TAB_STYLE`:6 个 Tab 不使用 Qt 默认紧凑宽度,需保证点击区域稳定、间距清晰、当前 Tab 高亮明显。该样式属于全局导航基础,不归后续业务 Tab 任务重复实现。
|
||||
|
||||
④ 账号管理要点:
|
||||
账号管理要点:
|
||||
|
||||
- 表格列:账号名、别名、地区、端口、登录状态、备注;不展示密码。
|
||||
- 弹窗字段:账号名、别名、地区、调试端口、密码、备注、slug、数据目录;密码框使用打码显示;首次保存/变更非空密码前弹窗提示“本地明文保存”。
|
||||
- 「启动登录」必须幂等:新增账号后不自动启动;若该账号 Chrome/CDP 已打开,则复用现有窗口并打开/激活卖家中心登录 tab,不重复启动 Chrome,也不覆盖普通页面;未打开时用卖家中心初始 URL 新启动并复用初始 page target,异常时最多兜底新建一个 tab。启动期间禁用账号选择及增删改/登录相关操作,连续触发只记 `ignored` 日志;成功或失败均恢复控件。`chrome_launch` 日志记录初始 target 是否复用、是否兜底建 tab 和原因。「检测登录」用 worker 跑 `accounts.detect_login()` 并刷新状态列;若当前 URL 跳到 `accounts.shopee.tw/seller/login`,状态必须显示未登录;「快捷方式」调用 `accounts.create_shortcut()` 生成默认桌面 `.lnk`。
|
||||
|
||||
⑤ 设置当前要点(T-501):
|
||||
设置当前要点(T-501):
|
||||
|
||||
- `SettingsTab` 使用居中内容区 + 适度左右留白布局,当前留白已从 T-506 初始实现缩短到约 40%;实现上使用最大内容宽度和自适应 margin,避免固定像素导致小屏挤压。各设置组默认响应式 3 列表单:短字段占 1 格,长字段(URL/API Key/路径)跨 2 格或 3 格,窄窗口降为 2 列/1 列。点击「保存设置」成功后,调用 `QMessageBox.information` 弹出“设置已保存”轻量提示框,同时保留状态栏提示。T-531 已完成:`save_app_settings()` 返回 bool,成功写 `data/config.json` + `data/config/cmhub.json` 后清 dirty,失败保留 dirty 并让调用方阻止离开。
|
||||
- `SettingsTab` 的 cmhub 网关配置:Base URL 保存/刷新前规整为网关根;API Key 单独读写 `data/config/cmhub.json`;别名下拉来自 `fetch_cmhub_models()`,按 `operation_type` 分生文/生图/图片理解并过滤未计价别名,其中图片理解额外要求 `requires_image=true`;显示托管档位、展示名、扣点和需参考图提示。已保存但当前模型列表不可用的图片理解别名必须保留并标明暂不可用;「测试连接/查余额」调用 cmhub models + balance helper;保存设置固定写 `ai.backend=cmhub`。`refresh_cmhub_models()` / `test_cmhub_connection()` 使用输入框实时值但不得自动保存,成功文案提醒用户保存。`is_dirty()` / `discard_unsaved_changes()` / `_suspend_dirty`(或等价机制)用于 T-531:用户编辑置脏,程序化回填不置脏,放弃时重新加载 `data/config.json` + `data/config/cmhub.json` 并回填控件。
|
||||
- T-532 要求 `SettingsTab._on_cmhub_finished()` 从 worker payload 的 `balance` / user/account 字段提取 cmhub 账号身份,成功文案优先显示 `cmhub 账号「<账号名>」连接成功:...`;当前 `/balance` 结构兼容 `{ "user": "cmhub_user", "points_balance": 88, "account": { "username": "cmhub_user", "display_name": "主账号" } }`,显示名优先 `account.display_name`,再兜底 `account.username` / `user` / 顶层常见字段;账号字段缺失时保持 `cmhub 连接成功:...`。显示名必须脱敏处理邮箱,且不得把 API Key、token 或完整敏感响应写入 GUI、run log 或诊断日志。
|
||||
- `MainWindow` 已负责⑤设置页离开守卫:切 Tab 与 `closeEvent` 发现 `SettingsTab.is_dirty()` 时弹保存/放弃/取消;保存成功后继续,保存失败或取消时回到⑤。由于 `QTabWidget.currentChanged` 是切换后信号,需维护上一个 index,并用 `_reverting_tab_change` 或等价 guard 防止 `setCurrentIndex()` 递归。
|
||||
- `MainWindow` 已负责设置页离开守卫:切 Tab 与 `closeEvent` 发现 `SettingsTab.is_dirty()` 时弹保存/放弃/取消;保存成功后继续,保存失败或取消时回到设置。由于 `QTabWidget.currentChanged` 是切换后信号,需维护上一个 index,并用 `_reverting_tab_change` 或等价 guard 防止 `setCurrentIndex()` 递归。
|
||||
- 模型详情字段按 3 个组件一组排列:启用、类别、api_type、连接超时等短字段一格;服务商名、模型 ID 视宽度占一格或两格;网址、密钥跨整行或跨 2/3 列。
|
||||
- 模型数据读写复用 `appconfig.list_ai_models(..., reveal_api_key=True)`、`add_ai_model()`、`update_ai_model()`、`delete_ai_model()`;保存时保留现有 `extra_body` 与 `timeout_seconds`。
|
||||
- 密钥字段使用 `QLineEdit.Password` 打码显示;首次保存/变更非空 Key 前弹窗提示“本地明文保存”;明文只写入已 gitignore 的 `data/config/ai_models.json`,不得进入日志/导出。
|
||||
@@ -509,14 +509,14 @@ T-523 后 GUI 已从旧 `app/gui.py` 拆为 `app/gui/` 包:`__init__.py` 负
|
||||
- 任务列表使用 `QTableView + TaskTableModel`,列为:账号、别名、商品ID、阶段。
|
||||
- 账号列优先显示匹配到的 `accounts.account_name`;未匹配账号时保留 Excel 输入账号名。
|
||||
- 别名未匹配 `accounts.alias` 时列表阶段列显示“略过”;点击「采集旧标题/旧封面」后由 `CollectWorker` 逐条写库为 `skipped`,原因 `别名未匹配账号`。
|
||||
- 「采集旧标题/旧封面」通过 `CollectWorker` 后台执行,只处理 `stage=imported` 的任务;采集前先做账号就绪预检。无账号、当前批次匹配账号未启动 CDP 端口或未登录时,返回 `blocked=True`,GUI 弹窗汇总并跳转/引导去④账号管理,不进入逐条采集、不写 skipped/failed。预检通过后,已匹配任务调用 `editor.collect()` 下载旧封面到 `image_dir/<batch_id>/<slug>/<task_id>_<item_id>_old.jpg` 并 `db.set_collected()`;别名未匹配任务仍逐条 `mark_skipped`;单条失败 `mark_failed(..., "collect", error)` 后继续。采集完成且本轮成功采集数量大于 0 时,自动触发当前批次旧字段回写;锁文件失败时只提示,不回滚 SQLite。
|
||||
- 「采集旧标题/旧封面」通过 `CollectWorker` 后台执行,只处理 `stage=imported` 的任务;采集前先做账号就绪预检。无账号、当前批次匹配账号未启动 CDP 端口或未登录时,返回 `blocked=True`,GUI 弹窗汇总并跳转/引导去账号管理,不进入逐条采集、不写 skipped/failed。预检通过后,已匹配任务调用 `editor.collect()` 下载旧封面到 `image_dir/<batch_id>/<slug>/<task_id>_<item_id>_old.jpg` 并 `db.set_collected()`;别名未匹配任务仍逐条 `mark_skipped`;单条失败 `mark_failed(..., "collect", error)` 后继续。采集完成且本轮成功采集数量大于 0 时,自动触发当前批次旧字段回写;锁文件失败时只提示,不回滚 SQLite。
|
||||
- 采集打开商品页时,若本轮自动新建 tab,采集完成后会关闭该 tab,并在最多 2 秒内确认 target 已从 `/json` 消失;确认超时只写 warning/诊断,不把采集成功改成失败。若失败发生在 `open_product()` 内部且尚未返回 `cdp`,也要关闭本轮自动新建 tab;若复用用户已打开的商品页,只断开 CDP 连接不关闭页面。
|
||||
- 采集中途登录检测必须快速跳过正在销毁的旧商品 target,改连其他有效 Shopee 页面。Cookie API 调用失败返回 `LOGIN_CHECK_TARGET_UNAVAILABLE`,只有 Cookie API 成功返回空会话时才返回 `NO_SESSION_COOKIE`;两者都不按明确掉登录批量略过,显式 `LOGIN_PAGE` 仍按账号需登录处理。retry/recovered 运行日志包含当前任务 ID 和商品 ID,避免与上一条采集成功日志混淆。
|
||||
- 采集打开商品页失败时,`CollectWorker` 应把 `open_product()` 捕获到的 Shopee toast 文案写入 `run_log_events` 和 `tasks.last_error`;商品 ID 失效、无权限、店铺不匹配等场景不得只显示泛化超时。① `TaskTableModel` 的“阶段”列只在 `last_error` 明确为商品失效类错误时显示“商品失效”,否则仍按 `status=failed` 显示“失败”;底层不新增 `stage` 枚举。
|
||||
|
||||
- 「停止」调用 worker 的协作式 `cancel()`,已开始的单条跑到安全边界后结束。
|
||||
- 「回写旧数据到 Excel」通过 `WriteBackWorker` 后台调用 `excel.write_back()`,把已采集旧标题/旧封面路径按原 Excel 行定位写回;该按钮主要作为自动回写失败后的手动重试入口。原文件被占用时弹窗提示关闭后重试,SQLite 采集结果不回滚。
|
||||
- 采集前由 `CollectWorker` 做账号就绪预检:无账号、当前批次匹配账号未启动 CDP 端口或未登录时,返回 `blocked=True`,GUI 弹窗汇总并跳转/引导去④账号管理;不无提示批量启动所有账号 Chrome。匹配账号未登录属于预检阻断,不是逐条 skipped。
|
||||
- 采集前由 `CollectWorker` 做账号就绪预检:无账号、当前批次匹配账号未启动 CDP 端口或未登录时,返回 `blocked=True`,GUI 弹窗汇总并跳转/引导去账号管理;不无提示批量启动所有账号 Chrome。匹配账号未登录属于预检阻断,不是逐条 skipped。
|
||||
|
||||
② AI生成当前要点(T-302/T-302p/T-303/T-303b + 诊断日志补丁):
|
||||
|
||||
@@ -544,7 +544,7 @@ T-523 后 GUI 已从旧 `app/gui.py` 拆为 `app/gui/` 包:`__init__.py` 负
|
||||
- 用户点否/取消时不执行、不改库;用户点是后才创建 `ApplyWorker` 做真实提交。
|
||||
- `ApplyWorker` 只处理当前筛选结果里 `stage=generated`、状态为 `success/pending/failed`,且满足当前 `update_mode` 所需内容的任务;只更新标题时不替换封面,只更新封面时不改标题。已更新和略过记录仅查看,不会再次提交,除非用户先用 T-404a 的「重置更新状态」把选中记录退回可更新。
|
||||
- 检查本轮更新:不做账号登录预检,不调用 `editor.apply_task()`,不写任务状态,不回写 Excel;只把每条“将更新/将略过”写入运行日志并弹汇总。
|
||||
- 真实更新前先做账号就绪预检:无账号、当前筛选结果匹配账号 Chrome 未启动、CDP 端口不可访问、未登录,或本轮涉及账号调试端口冲突时,返回 `blocked=True`,GUI 弹窗汇总并跳转/引导去④账号管理;预检不通过时不调用 `editor.apply_task()`、不写失败状态、不自动启动 Chrome。
|
||||
- 真实更新前先做账号就绪预检:无账号、当前筛选结果匹配账号 Chrome 未启动、CDP 端口不可访问、未登录,或本轮涉及账号调试端口冲突时,返回 `blocked=True`,GUI 弹窗汇总并跳转/引导去账号管理;预检不通过时不调用 `editor.apply_task()`、不写失败状态、不自动启动 Chrome。
|
||||
- 预检通过后默认串行;若 `max_parallel_accounts>1`,按账号分组并行执行,不同账号可同时跑,同一账号内仍串行。每条执行 `db.mark_running(..., "apply")` → `editor.apply_task(account, task)` → `db.set_applied()`;成功推进 `stage=applied/status=success/committed=1`,失败保持原 stage、`status=failed/committed=0/last_error`,单条失败继续下一条。
|
||||
- 真实更新打开商品页失败时,`ApplyWorker` 应把 `open_product()` 捕获到的 Shopee toast 文案上浮到③可见运行日志、`run_log_events` 和任务失败原因;若失败发生在 `open_product()` 内部,本轮自动新建 tab 要关闭,复用用户已有 tab 不关闭;本地 `data/logs/` 可保存失败现场 HTML/toast JSON 片段供开发排查,但必须脱敏。
|
||||
|
||||
@@ -588,7 +588,7 @@ run_worker(worker: BaseWorker, thread_name=None, start=True) -> QThread
|
||||
- 采集、AI 生成、更新、Excel 回写都通过 worker 执行,用 signal 回传进度。
|
||||
- 每个 worker/线程按需创建自己的 SQLite connection,不跨线程共享连接。
|
||||
- ③ 的批量确认弹窗在 GUI 主线程完成;用户确认后才创建 `ApplyWorker`。
|
||||
- `ApplyWorker` 支持检查、默认串行和按账号并行;真实更新按 `max_items_per_run` 分批调用 `editor.apply_task(...)`,逐条 `set_applied()`,失败继续;账号未就绪或端口冲突时整体阻断并引导④,不进入逐条提交,也不静默启动账号 Chrome。点击停止为协作式停止:当前商品完成后不再开始新商品或下一批。
|
||||
- `ApplyWorker` 支持检查、默认串行和按账号并行;真实更新按 `max_items_per_run` 分批调用 `editor.apply_task(...)`,逐条 `set_applied()`,失败继续;账号未就绪或端口冲突时整体阻断并引导账号管理,不进入逐条提交,也不静默启动账号 Chrome。点击停止为协作式停止:当前商品完成后不再开始新商品或下一批。
|
||||
- `WriteBackWorker` 默认 `mode="old"` 回写旧字段;③ 使用 `mode="results"` 回写新标题/新封面/更新状态,支持单批次或多批次列表。
|
||||
- `execute()` 未捕获异常会发 `failed(-1, error)` 与 `finished({"ok": False, "error": ...})`;普通单行失败由业务 worker 自己发 `failed(task_id, error)` 后继续处理。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user