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

14 KiB
Raw Blame History

id, title, phase, deps, status, created
id title phase deps status created
T-677 ⑤生成网关来源切换(默认网关/自定义网关) 7
T-517
T-529
TODO 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 同样常隐。
  • 因此本任务的主要工作是恢复并规范双面板切换,不是新建自定义配置体系。

覆盖面不对称是本任务最大的风险点:默认网关有生文/生图/图片理解三个角色,外加商品套图;direct 只有生文、生图两条路径。当前正式第六 Tab 是「商品套图」,旧 ImageStudioTab 仅作内部兼容,不能只写“AI工场入口拦截”而遗漏真实可见入口。

  • app/ai.py 的 analyze_product_images() 在非 cmhub 时直接抛错(⑥商品套图AI帮写);
  • app/image_studio_generation.py 全文只有 cmhub 分支,其 _runtime() 直接取 ai._cmhub_runtime(),完全不看 ai.backend。

若只改 UI 不补联动,用户切到「自定义网关」后商品套图仍会用默认网关 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 约定:允许先保存不完整配置,②生成时再提示"去⑤补齐",切换来源时不弹阻断式校验。
  • 保存语义必须明确且不混淆:来源选择器仅修改待保存状态,只有点击「保存设置」才切换运行来源;既有「新增 / 保存 / 删除模型」仍立即写入 ai_models.json,不会等待「保存设置」。用户点击放弃设置时,只恢复来源和普通设置字段,不回滚已经单独保存的模型 CRUD。
  • api_type=auto 不是自动探测接口:当前 direct 实现只有 images_edits 走图片编辑请求,chat 与 auto 都走对话请求。本任务不新增 images/generations、provider 适配或接口探测;自定义网关 UI 应将用户可见字段改为「接口类型」,并在帮助文案中按现有三种行为说明,不得泛称所有 OpenAI 兼容服务均可用。

3a. 运行配置冻结与保存竞争

  • ② GenerateWorker、⑥商品套图生成与 AI 帮写开始时必须冻结本轮 AI 配置快照;运行中打开⑤并保存来源切换,不得让同一批任务前后混用默认网关和自定义网关。
  • ⑤保存来源成功后,主窗口需通知⑥刷新来源能力状态;⑥再次显示时也应重新检查当前来源。可以用 SettingsTab 的保存成功 signal/callback 与 MainWindow 转发实现,但不得依赖用户重启程序。
  • 即使 UI 刷新遗漏,worker/service 层仍必须按创建时的冻结来源执行,不能读取被后续保存原地修改的共享 config。

4. 必须同版本处理的六个联动

  1. ⑥商品套图入口拦截:ai.backend != "cmhub" 时,正式可见的 ProductSuiteTab「生成套图」、图片重试和「AI帮写」入口置灰并说明「商品套图仅支持默认网关」;保存来源切换后立即刷新,切入⑥时也再次刷新。旧 ImageStudioTab 即使不在主 Tab 栏,也不得绕过该限制。
  2. ⑥防御性服务拦截:UI 置灰不足以作为费用边界。ProductSuiteGenerateWorker 必须在创建 job 前拒绝非默认网关;image_studio_generation.run_jobs() 也要在实际取默认网关 runtime 前拒绝非默认网关,确保内部调用、迟到回调或未来入口不能用默认网关 Key 提交任务。ProductSuiteAiWriteWorker 与 analyze_product_images() 保持非默认网关不可用;UI 必须在读取默认网关模型目录、展示扣点确认、提交图片理解前拦截,而不是等待运行后报错。
  3. ⑥商品套图说明:⑤自定义面板增加说明行,明确「图片理解与商品套图仅默认网关支持」;拦截提示使用同一中文口径,不显示技术实现或 cmhub Key 信息。
  4. ②自定义网关预检:允许保存不完整配置,但点击②开始生成时、创建 GenerateWorker 前按本轮“只生成标题 / 只生成封面 / 生成图文”检查所需 direct 模型是否已启用、类别匹配、模型地址、模型 ID、API Key、接口类型齐全。失败时中文提示去⑤补齐,且不创建 worker、不发请求、不逐条改任务状态。不要等 _role_model() 在批量运行中逐条失败。
  5. 「批量生成前检查余额」(cmhub_check_balance_checkbox):自定义网关下隐藏——点数余额是默认网关概念。
  6. 点数展示文案:②自定义网关每轮开始时在运行日志和状态栏各明确一次显示「自定义网关(不计点数,费用由服务商收取)」,并清空/隐藏上轮默认网关余额;不得显示 0 点、空白余额或沿用旧余额。默认网关下 points_cost / points_balance 展示保持不变。

5. ⑤ 面板内文案收敛

⑤ 面板内以及⑤触发的状态栏用户可见文案中的 cmhub 字样收敛为「默认网关」口径,包括分区标题、Base URL/API Key placeholder 与帮助、刷新/测试按钮、连接中/成功/失败结果、明文保存警告(PLAINTEXT_CMHUB_API_KEY_WARNING,app/gui/tabs/settings.py:17)。内部类名、objectName、配置键、日志字段和 app/ai.py 的中文错误消息(如"连接 cmhub 超时")不改。

6. 效果图

按 AGENTS.md 约定出 docs/ui/tab5-settings-T677.svg(⑤ 是改版,已有 tab5-settings.svg),在 docs/ui/README.md 登记,随本任务文档一起提交。

验收要点

  • ⑤ 生成网关分区显示互斥选中态的「默认网关」/「自定义网关」,当前选中项视觉可辨;两者均有 objectName。
  • 切换来源立即切面板显隐并标记未保存;未点「保存设置」时不落盘,取消/重新载入后恢复原选择。
  • 选默认网关时只显示默认网关配置(Base URL / API Key / 三个别名 / 连接超时 / 检查余额 / 测试连接·查余额);选自定义网关时只显示自定义模型列表、模型详情与默认文本/图像模型选择。
  • 保存后 config.json 的 ai.backend 正确写入 cmhub / direct;cmhub.json 与 ai_models.json 在来回切换保存后内容均不丢失。
  • 载入 backend=direct 的存量配置时选中「自定义网关」并正确回填自定义模型;缺失或 cmhub 时选中「默认网关」。
  • 自定义网关下②生文、②生图可用;⑥商品套图的生成、重试和 AI 帮写入口被拦截并提示仅默认网关支持,旧 ImageStudioTab 也不能绕过;worker/service 防线证明非默认网关不会创建默认网关 job、读取默认网关模型目录、提交请求或扣点。
  • ②/⑥运行开始后保存来源切换,不影响已启动任务的来源快照;保存完成后⑥按钮状态立即刷新,重新进入⑥时也正确刷新。
  • 自定义网关下「批量生成前检查余额」不可见;②日志/状态栏显示"不计点数"口径而非 0 点,且清除旧余额;默认网关下点数与余额展示不回归。
  • 自定义配置不完整时可保存;②按本轮生成内容在创建 worker 前给出"去⑤补齐"的可读提示,不发请求、不改任务状态、不崩溃。
  • 自定义模型 CRUD 的立即保存与来源选择的待保存语义均有明确中文反馈;放弃设置不回滚已保存模型,但恢复未保存来源选择。
  • auto 接口类型的实际请求行为有测试覆盖,不宣称自动探测或未实现的 images/generations 支持。
  • ⑤ 面板及⑤触发的状态栏可见文案不再出现 cmhub 字样;内部标识和 app/ai.py 错误消息不在本次范围。
  • tests/test_gui.py 覆盖:选择器互斥与默认选中、面板显隐、dirty 与保存生效、存量 backend=direct 载入、模型立即保存与来源待保存语义、②预检、⑥入口/worker拦截、余额控件显隐、点数文案分支、运行配置快照;tests/test_ai.py 覆盖 direct 文本/图片、auto 的既有请求路径与缺配置报错;现有 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 中⑤设置与生成来源的描述。
  • 运行:
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

边界(不改什么)

  • 不给商品套图(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 字样。
  • 不新增 images/generations、接口自动探测或 provider 专用适配;自定义网关仅覆盖现有 direct 文本/图片请求能力。
  • 不改 SQLite schema、Chrome/CDP、Excel 回写、蝦皮更新流程。
  • 不涉及卡密、设备绑定、订阅授权(见 Obsidian《卡密设备绑定与套餐授权方案》,另行拆任务)。

关联

  • T-529:默认 cmhub 网关并隐藏 AI 后端选择(本任务有限放开其 UI 收口)。
  • T-517:⑤设置分区(分区标题与布局约定)。
  • T-596:AI工场自定义模型(BYOK)总设计与启动门禁(status: BLOCKED,正式形态,本任务不解除其门禁)。
  • Obsidian《卡密设备绑定与套餐授权方案》第五节 §9、第六节 §5:正式发行版需移除普通用户可通过配置开启的 direct 直连路径。

执行记录

(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。)