---
url: /wave-agent/specs/ui/model-selection.md
description: >-
  CLI 的 `/model`
  命令与桌面端/插件/远控页的输入卡片底栏模型选择器：清单与视觉标识由宿主下发，选中即时生效；底栏放不下时模型与权限在三端同一套收法里收成图标档
---

# 功能规格说明：模型选择

**创建日期**：2026-04-16（2026-09-15 起 webview 侧入口由 `/model` 命令改为输入卡片底栏的模型选择器）

## 用户场景与测试 *（必填）*

### 用户故事：CLI 交互式切换模型（优先级：P2）

作为 CLI 用户，我希望输入 `/model` 并从列表中选择不同的 AI 模型，以便我可以在不重启的情况下更改后续交互的模型。

**为什么是这个优先级**：TUI 没有输入卡片底栏这块常驻位置，模型入口只能是命令；桌面端与插件端已改为底栏选择器（见下一条故事），所以 `/model` 现在**只保留在 CLI**。

**验收场景**：

1. **假设**用户输入 `/model` 并按下 Enter，**则** `ModelSelector` UI 组件出现，列出所有已配置的模型。
2. **假设**模型列表已显示，**当**用户使用 Up/Down 方向键导航并按下 Enter 时，**则**所选模型成为活动模型，选择器关闭。
3. **假设**模型列表已显示，**当**用户按下 Escape 时，**则**选择器关闭而不更改模型。
4. **假设**模型列表已显示，**则**当前活动模型以绿色标记 `(current)`，光标 `▶` 指示焦点项。
5. **假设**模型清单包含多个模型，**则**列表必须按模型名的字典序（大小写不敏感）排列，且当前模型不钉顶——它的位置由 `(current)` 标记与初始光标表达；**当**用户切换模型后重新打开 `/model`，**则**各项位置与切换前一致（清单与 webview 选择器同源，顺序口径一致）。

***

### 用户故事：模型选择的生效范围与持久化（优先级：P1）

作为用户，我希望切换后当前会话立刻改用新模型，并且这次选择能记住，以便下次开会话不用再选一遍。

**验收场景**：

1. **假设**用户切换了模型，**则**发起切换的那个会话的 agent 立即改用新模型，后续 AI 调用都走它；已经产生的对话内容不受影响。
2. **假设**用户切换了模型且落盘成功，**则**选择同时写入用户级 `~/.wave/settings.json` 的顶层 `model` 字段，此后新建的会话默认使用它，重启应用后仍保持。
3. **假设**落盘失败（权限、只读文件系统等），**则**本会话仍然生效（只这一层失败被降级为不持久化），不得因为写盘失败而让切换本身失效。
4. **假设**桌面端分屏存在多个并行会话，**当**用户在一个会话中切换模型时，**则**只由该会话的 agent 发起切换；其它会话的 agent 在自己的配置重载前不受影响。
5. **假设**agent 正在流式输出，**当**用户切换模型时，**则**允许切换，进行中的输出继续使用原模型，新模型自下一次交互生效。

***

### 用户故事：输入卡片底栏切换模型（桌面端 + 插件）（优先级：P1）

作为桌面端/插件用户，我希望在输入卡片底栏左侧直接看到并切换当前模型，以便在提问前就能确认这次对话用哪个模型，涉及截图与设计稿时也能一眼挑出能读图的那个。

**为什么是这个优先级**：模型名原来只有发过 `/model` 才看得到，等于「提问前看不见、也切不了」；底栏是每次发送都会看的位置。

**独立测试**：配置多个模型后打开底栏选择器，验证列出全部模型、当前项带选中态、能读图的模型带「视觉」标识；选中另一个模型，验证按钮立刻回显新名字、该会话后续交互使用新模型；再打开斜杠命令弹窗，验证已没有 `/model` 这一项。

**验收场景**：

1. **假设**存在活跃会话，**则**输入卡片底栏左侧（「+」与「/」两个图标按钮之后）必须有一个模型选择器，按钮上显示当前会话使用的模型名（底栏放不下时收成图标，见下一条故事）。
2. **假设**用户点开选择器，**则**必须向上展开模型列表（与权限模式下拉同向，避免被视口底部裁掉），完整列出所有可选模型，每项显示模型名。
3. **假设**某模型支持视觉（可读图片），**则**该项必须带「视觉」标识；不支持的模型不得有该标识。判据由宿主下发（见下一条故事），页面不自己按名字推断。
4. **假设**模型列表已展开，**则**当前模型所在项必须带选中态（勾选图标 + 高亮），且默认聚焦该项。
5. **假设**用户选中另一个模型，**则**该模型立即成为当前会话使用的模型、按钮回显新名字、列表关闭；不改变当前会话已产生的对话内容。
6. **假设**用户选中的是当前模型，**则**只关闭列表，不得重复发起切换请求。
7. **假设**模型列表已展开，**当**用户按 ↑/↓ 移动、按 Enter 选中、按 Esc 关闭、按 Tab 离开，**则**行为必须与权限模式下拉一致；Esc 必须把焦点还给触发按钮且不更改模型。
8. **假设**清单尚未返回或这台机器一个模型都没配，**则**底栏不得渲染一个点不开的空控件（按钮整体不出现）。
9. **假设**用户切换了会话，**则**必须重新拉取该会话的模型清单与当前模型（不同会话的 agent 可以带自己的模型）。
10. **假设**用户在远控页（手机）点选模型，**则**必须先确认一次（切换写的是这台电脑的用户级配置，会影响本机所有会话及此后新开的会话），确认后才切换；取消则模型不变。
11. **假设**用户查看斜杠命令弹窗，**则**「系统指令」分组中不得再有 `/model`；手动输入 `/model` 只作为普通消息发给模型，不再打开任何选择界面。

***

### 用户故事：底栏放不下时模型与权限收成图标档（桌面端 + 插件 + 远控页）（优先级：P2）

作为桌面端/插件/远控页用户，我希望输入卡片底栏被挤窄时模型与权限两档自动收成图标，以便控件之间不重叠、权限文案不折行冲出底栏，同时这两个入口仍然点得开。

**为什么是这个优先级**：底栏是每次发送都会看的位置，一挤就重叠是可见的破相。桌面端的「预览全屏」浮卡（420px）、插件的 VS Code 侧边栏（默认 350–400px）与被预览/计划预览挤窄的编辑器列都会触到这条线：模型按钮是底栏里唯一可收缩的一档，一挤就被压得比自己的文字还窄，文字直接压到右侧的上下文用量指示器上；再窄则「跳过权限确认」这类长文案折行、冲出 40px 高的底栏。**远控页是同一缺陷的第三端**：手机档（触控设备）把「+」「/」放大到 40×40、正文抬到 15px 之后，390px 宽的一行里两个文字档必然放不下——模型名一长就把权限按钮压得比自己的文案还窄，「修改前询问」折成两行冲出轨道。三端共用一个判据、一套收法，只在图标尺寸上按各自密度分档，正是这件事不做成三份实现的原因。

**独立测试**：把 webview 窗口拖窄到模型名压到上下文用量指示器上为止，验证模型与权限同时变成图标（模型为目标图标、权限为当前模式图标）、文字与下拉箭头消失、行内无任何重叠；再把窗口拖宽，验证两档自动退回文字档。桌面端与插件端各验一遍；再以触控视口（390×844，`pointer: coarse`）打开远控页，验证两档同样收成图标、图标为 40×40 且行内无重叠、权限文案不折行，把视口拉宽到放得下时同样自动退回文字档。三端都顺带比一次颜色与交互面：图标档下模型图标与「+」「/」的 computed 颜色必须相等，四个按钮的圆角相同、模型与权限的 hover/按下不带边框。

**验收场景**：

1. **假设**底栏的实际可用宽度放不下文字档的自然宽度（按当前模型名、权限文案、上下文用量指示器与各图标按钮的实际内容量计算，**不是**按固定窗口断点），**当**出现这种情况，**则**模型选择器与权限模式选择器必须同时收成图标档：只留图标（模型 → 目标图标，权限 → 当前模式的图标），隐藏模型名、模型下拉箭头与权限文案。
2. **假设**已处于图标档，**则**两个图标的点击行为必须与文字档完全一致（展开同一个向上展开的菜单），并各自带可见气泡（「切换模型 · <模型名>」/「权限：<模式名>」）；图标按钮大小与本宿主底栏的图标按钮同档（桌面端 32×32，插件端 22×22，远控页的触控档 40×40 且与「+」「/」同档）。
3. **假设**使底栏变窄的原因消失（窗口变宽、面板关闭、模型名或权限文案变短、上下文用量指示器消失等），**则**必须自动退回文字档，无需用户任何操作。
4. **假设**处于任意宽度，**则**底栏各控件之间不得互相重叠（模型名/权限文案不得压到上下文用量指示器或相邻控件上），权限文案不得折行。
5. **假设**是插件端（VS Code / JetBrains）或远控页的底栏被挤窄，**则**行为必须与桌面端一致：不得只在桌面端生效、也不得把它做成另一种收法——三端同一判据、同一收法，差异只在图标尺寸的密度档。
6. **假设**用户在远控页（触控设备）看到的是图标档，**则**点图标必须向上展开与文字档同一个列表（模型清单 / 权限四档），选中后的行为也与文字档一致（模型仍按上一条故事场景 10 先确认一次）。触屏没有悬停，气泡不出现不影响可辨识性：权限图标的字形本身就区分四档（盾牌/闪电/计划/问号），打开列表即可看到每项名称与当前勾选。
7. **假设**处于图标档，**则**模型图标必须与本宿主底栏的图标按钮（「+」「/」）取同一个图标色，不得因为模型按钮在文字档下是文字控件而继承它的次级文字色：插件端与远控页的图标色来自宿主图标 token（比文字 token 亮一档），桌面端两者同为 Figma 图标色，视觉上不变。权限图标不在此列 —— 它按权限模式上色（跳过确认时为警示色），这是有意的。
8. **假设**处于图标档，**则**这一行四个图标按钮（模型、权限、「+」、「/」）的**交互面**也必须是同一套：默认态无可见边框、圆角同档，hover / 按下 / 菜单展开时只变底色（同一个 hover 面）；不得给模型或权限继续保留文字档那套「底色 + 1px focusBorder 蓝框」。文字档不受影响（带框 hover 是文字下拉控件的既有设计）。三端都由同一组变量派生（图标色 / 圆角 / hover 面各只声明一处），换宿主时不需要逐条比对。

***

### 用户故事：模型清单与能力标识由宿主下发（优先级：P2）

作为用户，我希望可选模型清单、默认模型与「能否读图」都由服务端下发，以便换模型供应商时页面不用跟着改。

**验收场景**：

1. **假设**模型在 `settings.json`（用户级与项目级）或环境变量中已配置，**则**宿主下发的清单必须聚合这些来源（顶层 `model` 标量、`models` 字段的全部键、`WAVE_MODEL`），与 CLI 同源；**假设**配置里有 `models` 目录、而 `model` 标量或环境变量指向的名字不在目录里（平台侧改过模型别名后，旧名字只剩在 `settings.json` 的 `model` 里），**则**清单只取目录里的名字，失效的旧名字不作为可选项下发。
2. **假设**当前会话使用的模型由解析链决定（参数覆盖 > 配置 > 环境变量），**则**宿主必须把解析结果作为「当前模型」一并下发，页面不用自己推算默认值。
3. **假设**某模型在 `models[<name>].capabilities.vision` 里显式声明了 `false`，**则**该项的下发数据必须标记为不支持视觉；未声明时按默认（支持）标记——与 agent 处理图片输入时的实际行为保持一致。
4. **假设**清单发生变化（如配置重载后），**则**下一次拉取的清单必须反映新值。
5. **假设**清单包含多个模型，**则**下发顺序必须按模型名的字典序（大小写不敏感）排列，当前模型不单独置顶——「当前在哪」由勾选标识与打开菜单时的初始高亮表达；**当**用户切换模型后再次打开清单，**则**各项位置与切换前一致（不得因当前模型变化而重排）。

***

### 边界情况

* **只配置了一个模型怎么办？** 选择器仍然可以展开，只有一项且带选中态。
* **清单里没有当前模型怎么办？** 没有 `models` 目录时（只配了 `WAVE_MODEL` 等）清单恒包含当前模型；有目录时清单以目录为准，当前模型名不在目录里（改别名后的旧名字）就不出现在清单里，此时按钮仍回显解析出的当前模型，只是没有对应勾选项。
* **切换后页面与宿主不一致怎么办？** 按钮按用户的选择立即回显，宿主下次下发的当前模型为准；两者不该长期分叉。
* **CLI 侧呢？** TUI 没有底栏选择器，模型入口仍是 `/model` 命令；斜杠命令表里的 `/model` 只属于 CLI，不下发给桌面端与插件。
