diff --git a/docs/tasks/T-677.md b/docs/tasks/T-677.md new file mode 100644 index 0000000..300b94d --- /dev/null +++ b/docs/tasks/T-677.md @@ -0,0 +1,119 @@ +--- +id: T-677 +title: ⑤生成网关来源切换(默认网关/自定义网关) +phase: 7 +deps: [T-517, T-529] +status: TODO +created: 2026-07-20 +--- + +# T-677 ⑤生成网关来源切换(默认网关/自定义网关) + +## 问题 / 背景 + +T-529 把普通用户的 AI 后端收口为 cmhub:⑤ 隐藏「AI 后端」下拉,保存固定写 `ai.backend=cmhub`。当前生文、生图、图片理解全部只走 cmhub 托管并扣点。 + +现在需要给用户一条自定义(OpenAI 兼容)网关的逃生口,用于 cmhub 托管上游不稳或用户自备算力的场景。相关代码事实: + +- `direct` 路径并未删除,且本身就是 OpenAI 规范:`app/ai.py` 生文走 `chat/completions`,生图支持 `api_type` = `chat` / `images_edits` / `auto`(`app/appconfig.py:41`),`appconfig` 会自动补 endpoint;配置 schema 与校验在 `app/appconfig.py`(`list_ai_models` / 模型规范化)。 +- ⑤ 的自定义模型 UI(模型列表、模型详情、direct 角色选择)也都还在,只是被 `_on_backend_changed()` 无条件 `setVisible(False)` 硬藏(`app/gui/tabs/settings.py:580-584`),`backend_combo` 同样常隐。 +- 因此本任务的主要工作是**恢复并规范双面板切换**,不是新建自定义配置体系。 + +覆盖面不对称是本任务最大的风险点:cmhub 有生文/生图/图片理解三个角色,外加 AI工场;`direct` 只有生文、生图两条路径。 + +- `app/ai.py` 的 `analyze_product_images()` 在非 cmhub 时直接抛错(⑥商品套图AI帮写); +- `app/image_studio_generation.py` 全文只有 cmhub 分支,其 `_runtime()` 直接取 `ai._cmhub_runtime()`,**完全不看 `ai.backend`**。 + +若只改 UI 不补联动,用户切到「自定义网关」后 AI工场仍会用 cmhub Key 继续跑、继续扣点——用户以为切走了而费用照扣,属于资金口径错误,必须在本任务同版本内处理。 + +## 方案 + +### 1. ⑤ 网关分区改为来源选择 + +- `app/gui/tabs/settings.py` 第一段 `_section_title("cmhub 网关", "settingsAiModelSectionTitle")` 文案改为「生成网关」,**保留该 section title**(⑤ 是长页面,分区标题与「角色与生成参数」「蝦皮更新执行」「基础设施」并列承担导航作用,不可直接删掉换控件)。 +- 在该分区标题所在位置增加**互斥选中态**的来源选择器:「默认网关」/「自定义网关」。 + - 用两个 `setCheckable(True)` 的按钮加入同一 `QButtonGroup`(`setExclusive(True)`)实现分段选择器外观,或等价的两个 `QRadioButton`; + - **不得使用普通 `QPushButton`**:这是状态选择不是动作,普通按钮无法表达"当前选中项",也无法表达"点击即生效还是保存生效"; + - 两个控件各自设置 `objectName`(如 `gatewayDefaultButton` / `gatewayCustomButton`)供 GUI 测试选择。 +- 选择器只改 pending 状态并切换面板显隐,**落盘发生在「保存设置」时**;选择器需接入 `_connect_dirty_signals()`,切换触发未保存提示,与⑤其他控件行为一致。载入配置时在 `_dirty_tracking_suspended()` 内设置选中态。 + +### 2. 恢复双面板显隐 + +- `_on_backend_changed()`(`app/gui/tabs/settings.py:579-587`)去掉硬编码隐藏,改为按当前来源双向显隐: + - 默认网关 → 显示 `cmhub_panel`,隐藏 `model_picker_panel`、`model_detail_section_title`、`model_detail_panel`、`direct_role_panel`; + - 自定义网关 → 相反。 +- 自定义侧复用现有面板与控件,不新建:模型列表、模型详情(名称/接口地址/模型ID/API Key/api_type/超时/启用)、`direct_role_panel` 的默认文本模型与默认图像模型选择、以及现有模型测试按钮(`AIModelTestWorker`)。 +- 两套配置的**测试按钮与结果标签必须分开**,自定义侧不得复用 `cmhub_result_label`。 +- 别名下拉(`cmhub_title_alias_combo` / `cmhub_image_alias_combo` / `cmhub_vision_alias_combo`)属于 `cmhub_panel`,随面板整体显隐即可。 + +### 3. 配置读写与迁移 + +- 沿用现有 `ai.backend` 字段与取值 `cmhub` / `direct`(`appconfig.AI_BACKENDS`、`app/ai.py` 的 `_ai_backend()` 校验保持不变),**不新增第三种取值、不新增并行的来源字段**。 +- `_collect_settings_values()` 中 `backend = "cmhub"` 的硬编码(`app/gui/tabs/settings.py:751`)改为读选择器当前值。 +- **两套配置并存、切换不清空**:`data/config/cmhub.json` 与 `data/config/ai_models.json` 互不影响,来回切换后原配置仍在。 +- 存量迁移天然成立:老配置 `backend=direct` 载入后选中「自定义网关」,`backend=cmhub` 或缺失按现状选中「默认网关」。 +- 沿用 v3.1 约定:**允许先保存不完整配置**,②生成时再提示"去⑤补齐",切换来源时不弹阻断式校验。 + +### 4. 必须同版本处理的四个联动 + +1. **AI工场入口拦截**:`ai.backend != "cmhub"` 时,AI工场(⑥)生成入口置灰或拦截并提示「AI工场仅支持默认网关」,**禁止在自定义网关下继续用 cmhub Key 提交任务扣点**。 +2. **⑥商品套图AI帮写**:`analyze_product_images()` 的现有抛错行为保持(不会静默扣费),但⑤自定义面板需增加说明行,明确「图片理解与AI工场仅默认网关支持」,避免用户切换后靠报错才发现。 +3. **「批量生成前检查余额」**(`cmhub_check_balance_checkbox`):自定义网关下隐藏——点数余额是默认网关概念。 +4. **点数展示文案**:②运行日志/状态栏在自定义网关下不得显示 `0 点`或空白,应显示「自定义网关(不计点数,费用由服务商收取)」;默认网关下 `points_cost` / `points_balance` 展示保持不变。 + +### 5. ⑤ 面板内文案收敛 + +⑤ 面板内用户可见文案中的 `cmhub` 字样收敛为「默认网关」口径,包括分区标题、测试按钮与结果提示、明文保存警告(`PLAINTEXT_CMHUB_API_KEY_WARNING`,`app/gui/tabs/settings.py:17`)。`app/ai.py` 中的中文错误消息(如"连接 cmhub 超时")数量多、属于另一层,本任务不改。 + +### 6. 效果图 + +按 `AGENTS.md` 约定出 `docs/ui/tab5-settings-T677.svg`(⑤ 是改版,已有 `tab5-settings.svg`),在 `docs/ui/README.md` 登记,随本任务文档一起提交。 + +## 验收要点 + +- [ ] ⑤ 生成网关分区显示互斥选中态的「默认网关」/「自定义网关」,当前选中项视觉可辨;两者均有 `objectName`。 +- [ ] 切换来源立即切面板显隐并标记未保存;未点「保存设置」时不落盘,取消/重新载入后恢复原选择。 +- [ ] 选默认网关时只显示 cmhub 配置(Base URL / API Key / 三个别名 / 连接超时 / 检查余额 / 测试连接·查余额);选自定义网关时只显示自定义模型列表、模型详情与默认文本/图像模型选择。 +- [ ] 保存后 `config.json` 的 `ai.backend` 正确写入 `cmhub` / `direct`;`cmhub.json` 与 `ai_models.json` 在来回切换保存后内容均不丢失。 +- [ ] 载入 `backend=direct` 的存量配置时选中「自定义网关」并正确回填自定义模型;缺失或 `cmhub` 时选中「默认网关」。 +- [ ] 自定义网关下②生文、②生图可用;⑥AI工场入口被拦截并提示仅默认网关支持,**不会用 cmhub Key 提交任务或扣点**。 +- [ ] 自定义网关下「批量生成前检查余额」不可见;②日志/状态栏显示"不计点数"口径而非 `0 点`;默认网关下点数与余额展示不回归。 +- [ ] 自定义配置不完整时可保存,②生成时给出"去⑤补齐"的可读提示,不崩溃。 +- [ ] ⑤ 面板内可见文案不再出现 `cmhub` 字样(`app/ai.py` 错误消息不在本次范围)。 +- [ ] `tests/test_gui.py` 覆盖:选择器互斥与默认选中、面板显隐、dirty 与保存生效、存量 `backend=direct` 载入、AI工场拦截、余额控件显隐、点数文案分支;现有 backend 相关断言同步更新不回归。 +- [ ] `docs/ui/tab5-settings-T677.svg` 已产出并在 `docs/ui/README.md` 登记。 + +## 测试与文档 + +- 更新 `docs/cmhub-integration-design.md`:补一条修订说明,记录 T-529 的"普通 UI 不提供后端切换"在本任务被有限放开,及自定义网关的定位与覆盖边界。 +- 更新 `docs/routes.md` / `docs/api.md` 中⑤设置与生成来源的描述。 +- 运行: + +```bash +py -3.10 -m unittest discover -s tests -p "test_gui.py" +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工场(`app/image_studio_generation.py`)和图片理解(`analyze_product_images()`)新增自定义网关路径;本任务只做入口拦截与提示。 +- 不实现方案中经 cmhub 代理透传的正式 BYOK(T-596 / T-599~T-602 仍按其门禁推进);本任务的自定义网关是**客户端直连的过渡形态**,正式商业版需迁移到网关代理并重新收口 direct 入口。 +- 不修改 `ai_models.json` 的 schema 与 `API_TYPES` 取值,不新增 provider 适配。 +- 不改 `app/ai.py` 的 cmhub 请求协议、重试与超时策略、异步 submit/poll 状态机、幂等键。 +- 不改 `app/ai.py` 中文错误消息里的 `cmhub` 字样。 +- 不改 SQLite schema、Chrome/CDP、Excel 回写、蝦皮更新流程。 +- 不涉及卡密、设备绑定、订阅授权(见 Obsidian《卡密设备绑定与套餐授权方案》,另行拆任务)。 + +## 关联 + +- T-529:默认 cmhub 网关并隐藏 AI 后端选择(本任务有限放开其 UI 收口)。 +- T-517:⑤设置分区(分区标题与布局约定)。 +- T-596:AI工场自定义模型(BYOK)总设计与启动门禁(`status: BLOCKED`,正式形态,本任务不解除其门禁)。 +- Obsidian《卡密设备绑定与套餐授权方案》第五节 §9、第六节 §5:正式发行版需移除普通用户可通过配置开启的 direct 直连路径。 + +## 执行记录 + +(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。)