Appearance
功能规格说明:模型选择
创建日期:2026-04-16(2026-09-15 起 webview 侧入口由 /model 命令改为输入卡片底栏的模型选择器)
用户场景与测试 (必填)
用户故事:CLI 交互式切换模型(优先级:P2)
作为 CLI 用户,我希望输入 /model 并从列表中选择不同的 AI 模型,以便我可以在不重启的情况下更改后续交互的模型。
为什么是这个优先级:TUI 没有输入卡片底栏这块常驻位置,模型入口只能是命令;桌面端与插件端已改为底栏选择器(见下一条故事),所以 /model 现在只保留在 CLI。
验收场景:
- 假设用户输入
/model并按下 Enter,则ModelSelectorUI 组件出现,列出所有已配置的模型。 - 假设模型列表已显示,当用户使用 Up/Down 方向键导航并按下 Enter 时,则所选模型成为活动模型,选择器关闭。
- 假设模型列表已显示,当用户按下 Escape 时,则选择器关闭而不更改模型。
- 假设模型列表已显示,则当前活动模型以绿色标记
(current),光标▶指示焦点项。 - 假设模型清单包含多个模型,则列表必须按模型名的字典序(大小写不敏感)排列,且当前模型不钉顶——它的位置由
(current)标记与初始光标表达;当用户切换模型后重新打开/model,则各项位置与切换前一致(清单与 webview 选择器同源,顺序口径一致)。
用户故事:模型选择的生效范围与持久化(优先级:P1)
作为用户,我希望切换后当前会话立刻改用新模型,并且这次选择能记住,以便下次开会话不用再选一遍。
验收场景:
- 假设用户切换了模型,则发起切换的那个会话的 agent 立即改用新模型,后续 AI 调用都走它;已经产生的对话内容不受影响。
- 假设用户切换了模型且落盘成功,则选择同时写入用户级
~/.wave/settings.json的顶层model字段,此后新建的会话默认使用它,重启应用后仍保持。 - 假设落盘失败(权限、只读文件系统等),则本会话仍然生效(只这一层失败被降级为不持久化),不得因为写盘失败而让切换本身失效。
- 假设桌面端分屏存在多个并行会话,当用户在一个会话中切换模型时,则只由该会话的 agent 发起切换;其它会话的 agent 在自己的配置重载前不受影响。
- 假设agent 正在流式输出,当用户切换模型时,则允许切换,进行中的输出继续使用原模型,新模型自下一次交互生效。
用户故事:输入卡片底栏切换模型(桌面端 + 插件)(优先级:P1)
作为桌面端/插件用户,我希望在输入卡片底栏左侧直接看到并切换当前模型,以便在提问前就能确认这次对话用哪个模型,涉及截图与设计稿时也能一眼挑出能读图的那个。
为什么是这个优先级:模型名原来只有发过 /model 才看得到,等于「提问前看不见、也切不了」;底栏是每次发送都会看的位置。
独立测试:配置多个模型后打开底栏选择器,验证列出全部模型、当前项带选中态、能读图的模型带「视觉」标识;选中另一个模型,验证按钮立刻回显新名字、该会话后续交互使用新模型;再打开斜杠命令弹窗,验证已没有 /model 这一项。
验收场景:
- 假设存在活跃会话,则输入卡片底栏左侧(「+」与「/」两个图标按钮之后)必须有一个模型选择器,按钮上显示当前会话使用的模型名(底栏放不下时收成图标,见下一条故事)。
- 假设用户点开选择器,则必须向上展开模型列表(与权限模式下拉同向,避免被视口底部裁掉),完整列出所有可选模型,每项显示模型名。
- 假设某模型支持视觉(可读图片),则该项必须带「视觉」标识;不支持的模型不得有该标识。判据由宿主下发(见下一条故事),页面不自己按名字推断。
- 假设模型列表已展开,则当前模型所在项必须带选中态(勾选图标 + 高亮),且默认聚焦该项。
- 假设用户选中另一个模型,则该模型立即成为当前会话使用的模型、按钮回显新名字、列表关闭;不改变当前会话已产生的对话内容。
- 假设用户选中的是当前模型,则只关闭列表,不得重复发起切换请求。
- 假设模型列表已展开,当用户按 ↑/↓ 移动、按 Enter 选中、按 Esc 关闭、按 Tab 离开,则行为必须与权限模式下拉一致;Esc 必须把焦点还给触发按钮且不更改模型。
- 假设清单尚未返回或这台机器一个模型都没配,则底栏不得渲染一个点不开的空控件(按钮整体不出现)。
- 假设用户切换了会话,则必须重新拉取该会话的模型清单与当前模型(不同会话的 agent 可以带自己的模型)。
- 假设用户在远控页(手机)点选模型,则必须先确认一次(切换写的是这台电脑的用户级配置,会影响本机所有会话及此后新开的会话),确认后才切换;取消则模型不变。
- 假设用户查看斜杠命令弹窗,则「系统指令」分组中不得再有
/model;手动输入/model只作为普通消息发给模型,不再打开任何选择界面。
用户故事:底栏放不下时模型与权限收成图标档(桌面端 + 插件 + 远控页)(优先级:P2)
作为桌面端/插件/远控页用户,我希望输入卡片底栏被挤窄时模型与权限两档自动收成图标,以便控件之间不重叠、权限文案不折行冲出底栏,同时这两个入口仍然点得开。
为什么是这个优先级:底栏是每次发送都会看的位置,一挤就重叠是可见的破相。桌面端的「预览全屏」浮卡(420px)、插件的 VS Code 侧边栏(默认 350–400px)与被预览/计划预览挤窄的编辑器列都会触到这条线:模型按钮是底栏里唯一可收缩的一档,一挤就被压得比自己的文字还窄,文字直接压到右侧的上下文用量指示器上;再窄则「跳过权限确认」这类长文案折行、冲出 40px 高的底栏。远控页是同一缺陷的第三端:手机档(触控设备)把「+」「/」放大到 40×40、正文抬到 15px 之后,390px 宽的一行里两个文字档必然放不下——模型名一长就把权限按钮压得比自己的文案还窄,「修改前询问」折成两行冲出轨道。三端共用一个判据、一套收法,只在图标尺寸上按各自密度分档,正是这件事不做成三份实现的原因。
独立测试:把 webview 窗口拖窄到模型名压到上下文用量指示器上为止,验证模型与权限同时变成图标(模型为目标图标、权限为当前模式图标)、文字与下拉箭头消失、行内无任何重叠;再把窗口拖宽,验证两档自动退回文字档。桌面端与插件端各验一遍;再以触控视口(390×844,pointer: coarse)打开远控页,验证两档同样收成图标、图标为 40×40 且行内无重叠、权限文案不折行,把视口拉宽到放得下时同样自动退回文字档。三端都顺带比一次颜色与交互面:图标档下模型图标与「+」「/」的 computed 颜色必须相等,四个按钮的圆角相同、模型与权限的 hover/按下不带边框。
验收场景:
- 假设底栏的实际可用宽度放不下文字档的自然宽度(按当前模型名、权限文案、上下文用量指示器与各图标按钮的实际内容量计算,不是按固定窗口断点),当出现这种情况,则模型选择器与权限模式选择器必须同时收成图标档:只留图标(模型 → 目标图标,权限 → 当前模式的图标),隐藏模型名、模型下拉箭头与权限文案。
- 假设已处于图标档,则两个图标的点击行为必须与文字档完全一致(展开同一个向上展开的菜单),并各自带可见气泡(「切换模型 · <模型名>」/「权限:<模式名>」);图标按钮大小与本宿主底栏的图标按钮同档(桌面端 32×32,插件端 22×22,远控页的触控档 40×40 且与「+」「/」同档)。
- 假设使底栏变窄的原因消失(窗口变宽、面板关闭、模型名或权限文案变短、上下文用量指示器消失等),则必须自动退回文字档,无需用户任何操作。
- 假设处于任意宽度,则底栏各控件之间不得互相重叠(模型名/权限文案不得压到上下文用量指示器或相邻控件上),权限文案不得折行。
- 假设是插件端(VS Code / JetBrains)或远控页的底栏被挤窄,则行为必须与桌面端一致:不得只在桌面端生效、也不得把它做成另一种收法——三端同一判据、同一收法,差异只在图标尺寸的密度档。
- 假设用户在远控页(触控设备)看到的是图标档,则点图标必须向上展开与文字档同一个列表(模型清单 / 权限四档),选中后的行为也与文字档一致(模型仍按上一条故事场景 10 先确认一次)。触屏没有悬停,气泡不出现不影响可辨识性:权限图标的字形本身就区分四档(盾牌/闪电/计划/问号),打开列表即可看到每项名称与当前勾选。
- 假设处于图标档,则模型图标必须与本宿主底栏的图标按钮(「+」「/」)取同一个图标色,不得因为模型按钮在文字档下是文字控件而继承它的次级文字色:插件端与远控页的图标色来自宿主图标 token(比文字 token 亮一档),桌面端两者同为 Figma 图标色,视觉上不变。权限图标不在此列 —— 它按权限模式上色(跳过确认时为警示色),这是有意的。
- 假设处于图标档,则这一行四个图标按钮(模型、权限、「+」、「/」)的交互面也必须是同一套:默认态无可见边框、圆角同档,hover / 按下 / 菜单展开时只变底色(同一个 hover 面);不得给模型或权限继续保留文字档那套「底色 + 1px focusBorder 蓝框」。文字档不受影响(带框 hover 是文字下拉控件的既有设计)。三端都由同一组变量派生(图标色 / 圆角 / hover 面各只声明一处),换宿主时不需要逐条比对。
用户故事:模型清单与能力标识由宿主下发(优先级:P2)
作为用户,我希望可选模型清单、默认模型与「能否读图」都由服务端下发,以便换模型供应商时页面不用跟着改。
验收场景:
- 假设模型在
settings.json(用户级与项目级)或环境变量中已配置,则宿主下发的清单必须聚合这些来源(顶层model标量、models字段的全部键、WAVE_MODEL),与 CLI 同源;假设配置里有models目录、而model标量或环境变量指向的名字不在目录里(平台侧改过模型别名后,旧名字只剩在settings.json的model里),则清单只取目录里的名字,失效的旧名字不作为可选项下发。 - 假设当前会话使用的模型由解析链决定(参数覆盖 > 配置 > 环境变量),则宿主必须把解析结果作为「当前模型」一并下发,页面不用自己推算默认值。
- 假设某模型在
models[<name>].capabilities.vision里显式声明了false,则该项的下发数据必须标记为不支持视觉;未声明时按默认(支持)标记——与 agent 处理图片输入时的实际行为保持一致。 - 假设清单发生变化(如配置重载后),则下一次拉取的清单必须反映新值。
- 假设清单包含多个模型,则下发顺序必须按模型名的字典序(大小写不敏感)排列,当前模型不单独置顶——「当前在哪」由勾选标识与打开菜单时的初始高亮表达;当用户切换模型后再次打开清单,则各项位置与切换前一致(不得因当前模型变化而重排)。
边界情况
- 只配置了一个模型怎么办? 选择器仍然可以展开,只有一项且带选中态。
- 清单里没有当前模型怎么办? 没有
models目录时(只配了WAVE_MODEL等)清单恒包含当前模型;有目录时清单以目录为准,当前模型名不在目录里(改别名后的旧名字)就不出现在清单里,此时按钮仍回显解析出的当前模型,只是没有对应勾选项。 - 切换后页面与宿主不一致怎么办? 按钮按用户的选择立即回显,宿主下次下发的当前模型为准;两者不该长期分叉。
- CLI 侧呢? TUI 没有底栏选择器,模型入口仍是
/model命令;斜杠命令表里的/model只属于 CLI,不下发给桌面端与插件。