Files
cmshoppe/docs/tasks/T-650.md
T

82 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-650
title: 商品套图AI帮写的付费前价格确认
phase: 7
deps: [T-647]
status: DONE
created: 2026-07-17
---
## 问题 / 背景
⑥「商品套图」的「AI帮写」会将当前商品前 1 至 8 张可用本地原图一次提交给 cmhub 图片理解接口,读取等待最长可达 120 秒,并可能产生点数消耗。当前点击按钮后会立即创建 `ProductSuiteAiWriteWorker`;用户只能在接口返回后看到实际扣点和余额,没有机会在发起付费请求前确认本次理解范围与预估成本。
cmhub 的模型目录可能提供图片理解别名的 `prices[].points_cost`,但这类信息目前只供⑤设置页刷新下拉时使用,⑥没有可复用的短期价格缓存。不能根据图片张数自行把单次价格乘以图片数,也不能把最终接口返回的实际扣点提前当作确定事实。
## 方案
### 1. 在发起图片理解前取得可用价格信息
- 为 cmhub 模型目录增加进程内短期缓存,缓存键至少包含规整后的网关地址和 `vision_alias`,不得包含 API Key;缓存只保存公开模型元数据,不写入 `config.json`、SQLite、运行日志或导出文件;
- ⑤设置页刷新模型列表与⑥AI帮写共用该缓存。缓存缺失或超过合理短期时限时,⑥使用独立后台 worker 调用既有 `ai.fetch_cmhub_models()` 读取模型目录;查询本身不调用图片理解接口、不提交图片、不扣点,GUI 不得卡顿;
- 只接受 `operation_type=vision`、`requires_image=true`、`pricing_status=priced` 且别名等于当前配置 `vision_alias` 的模型价格;
- 仅当模型目录能明确给出本次单个图片理解请求唯一、无条件歧义的 `points_cost` 时,显示「预计扣点:X 点」;多档条件价格、未定价、字段缺失或目录读取失败时,不得猜测、相加或按图片数乘算;
- 价格不可用时仍允许用户在明确提示「暂时无法取得预计扣点,实际以网关返回为准」后自行决定是否继续,绝不自动提交请求;不因价格目录暂时不可用而静默绕过确认。
### 2. 增加显式中文确认框
在 `ProductSuiteTab.start_ai_write()` 完成现有本地图片可用性校验和前 8 张截取后、创建 worker 前增加确认流程:
- 标题为「开始AI帮写」;正文明确本次会理解当前商品前 `N` 张可用商品原图(最多 8 张),并生成「商品卖点与要求」;
- 价格可用时显示「预计扣点:X 点,实际以网关返回为准」;价格不可用时显示对应的无法确认说明;不显示 API URL、模型别名、图片路径、base64、Key 或上游原始错误;
- 明确说明开始后可取消本地等待,但已提交到网关的图片理解请求仍可能产生扣点;
- 使用「开始AI帮写」和「取消」两个中文按钮,默认和 Esc/关闭窗口均为取消;不提供“以后不再提示”选项;
- 用户取消、关闭确认框或价格读取阶段取消时,不创建 `ProductSuiteAiWriteWorker`、不调用 `analyze_product_images()`、不修改卖点、不会产生图片理解请求;
- 用户确认后重新检查当前任务仍有效、仍未在运行且选中的前 `N` 张本地图片仍可用,再复用现有 worker、冲突覆盖确认、取消、计时、实际计费回显和自动保存逻辑。
### 3. 并发、状态和最终计费边界
- 同一套图任务在“读取价格”或“等待确认”期间必须防止重复点击创建多个查询 worker、多个确认框或多个图片理解请求;切换任务、关闭任务和程序退出时正确取消/释放未完成的价格读取 worker,避免 `QThread: Destroyed while thread is still running`;
- 价格查询失败只显示脱敏中文原因;用户可取消,或在价格未知提示下再次显式选择开始;不得把失败写成 AI帮写失败、不得覆盖已有卖点;
- 成功完成后仍以 cmhub 响应 metadata 中的 `points_cost` 和 `points_balance` 作为唯一实际计费结果;预估值只用于确认,不写数据库、不参与余额扣减或成功/失败判定;
- 既有 AI帮写取消只在安全边界停止本地等待的语义保持不变,不承诺撤销已发往 cmhub 的请求或退回点数。
### 4. 测试与文档
- `tests/test_product_suite_gui.py`:覆盖已知价格时确认框显示前 8 张和预估点数;点击取消不会创建图片理解 worker;确认后只创建一次 worker 且图片顺序不变;价格未知时文案准确、仍须二次显式确认;重复点击、切换任务和关闭窗口不产生重复请求;
- `tests/test_ai.py` / 新增纯逻辑测试:模型目录价格筛选只接受有效图片理解别名;唯一价格、多个条件价格、未定价、缺字段和缓存过期的处理正确;不按图片数乘算;
- 同步更新 `docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`,说明AI帮写预估价格、确认和实际计费的边界;
- 运行:
```bash
py -3.10 -m unittest tests.test_ai tests.test_product_suite_gui tests.test_workers
py -3.10 -m unittest discover -s tests
py -3.10 -m ruff check app tests main.py
py -3.10 -m compileall app main.py
git diff --check
```
## 验收要点
- [ ] 点击「AI帮写」不再立即提交图片理解请求,而是先展示本次前 `N` 张原图范围和付费确认。
- [ ] 已知唯一单次价格时显示「预计扣点:X 点」,且不按 `N` 张图片进行倍乘;最终仍以接口返回的实际扣点为准。
- [ ] 价格未知、目录不可用或价格有歧义时不伪造具体点数,用户仍可在清晰说明下选择继续或取消。
- [ ] 取消、Esc 或关闭确认框不会创建图片理解 worker、修改卖点或提交 cmhub 请求。
- [ ] 确认后沿用一次请求提交前 8 张同商品原图、现有冲突覆盖确认、取消、超时和实际计费展示。
- [ ] 价格查询与确认不阻塞 GUI,不重复发起模型目录读取或 AI帮写请求,关闭窗口不遗留线程。
- [ ] 用户可见文案均为中文,且不暴露图片路径、接口 URL、模型别名、Key、base64 或上游原始错误。
- [ ] 不影响②AI生成、⑥正式套图生图、历史、导出、Shopee CDP、采集、更新蝦皮、账号管理或设置保存。
## 边界(不改什么)
- 不修改 cmhub 的图片理解、生图、标题生成接口、模型别名配置、超时、重试、并发或计费协议。
- 不新增余额预扣、余额硬阻断、自动充值、自动重试或“以后不再确认”开关。
- 不把模型价格、预估点数或 API Key 持久化到 SQLite、配置文件、日志、Excel 或导出文件。
- 不修改商品原图的前 8 张顺序、原图勾选无关语义、AI帮写输出格式、卖点自动保存或覆盖冲突确认。
## 执行记录
- 2026-07-17:根据AI帮写耗时和付费前确认需求创建任务。待实现。
- 2026-07-17:开始实现共享模型目录缓存和AI帮写付费前确认。
- 2026-07-17:已实现 `cmhub_models` 进程内模型目录缓存、设置页与商品套图共用缓存、后台目录读取 worker,以及默认取消的「开始AI帮写」付费确认。仅唯一无条件的图片理解价格显示预计扣点,实际扣点继续以网关响应 metadata 为准;关闭任务和应用会取消目录读取 worker。验证:`py -3.10 -m unittest discover -s tests`(574 项通过)、`py -3.10 -m ruff check app/cmhub_models.py app/gui/__init__.py app/gui/workers.py app/gui/tabs/product_suite.py tests/test_cmhub_models.py tests/test_workers.py tests/test_product_suite_gui.py`、`py -3.10 -m compileall app main.py`、`git diff --check` 通过。