WorkBuddy_多模型支持与切换4_模型选择器在哪里
模型选择器在哪里
摘要
在使用各类 AI 对话产品(如 ChatGPT、Claude、通义千问、DeepSeek 等)时,很多用户都会遇到一个共同的困惑:“我想换一个模型,但模型选择器到底在哪里?” 本文从对话界面底部的切换入口出发,系统梳理模型选择器的常见位置、交互路径、状态管理逻辑,并给出前端实现一个"底部模型切换器"的完整代码示例,帮助开发者和用户 alike 彻底搞清楚这个看似简单却经常让人迷路的 UI 元素。
引言
随着大模型能力的细分,一个 AI 产品往往同时提供多个模型:快速模型、推理模型、长上下文模型、多模态模型……用户需要在对话过程中随时切换。于是"模型选择器"(Model Selector / Model Switcher)成为对话界面的标配组件。
但奇怪的是,这个组件的位置并不统一:
- 有的产品把它放在输入框左下角,一个小小的下拉按钮;
- 有的放在输入框右下角,和发送按钮并排;
- 有的藏在顶部标题栏,需要点击当前模型名才能展开;
- 还有的干脆收进了"设置"里,让用户找半天。
本文的核心观点是:对于绝大多数现代对话产品,模型选择器最标准、最高频的位置就是对话输入区(底部)的切换入口。理解这个入口的设计逻辑与操作路径,既能帮助用户快速上手,也能帮助前端开发者正确实现它。
下面我们分多个小节展开。
一、为什么模型选择器通常在"底部"
要回答"在哪里",先要回答"为什么在那里"。
对话界面的视觉重心是消息流,而用户的操作重心是输入框。模型选择是一个"高频但轻量"的操作——用户可能在一轮对话中切换两三次,但它不像发送消息那样是核心动作。因此产品设计上通常遵循以下原则:
- 靠近操作点:切换模型的目的通常是"接下来这条消息用哪个模型回答",所以选择器应该紧贴输入框。
- 不打断阅读:放在顶部会与标题栏、会话列表争夺空间;放在底部输入区则天然属于"输入动作"的一部分。
- 状态可见:用户需要随时知道"当前用的是哪个模型",底部常驻的标签比折叠菜单更直观。
这就解释了为什么你在大多数产品里,目光往输入框下方或内部一瞥,就能找到模型选择器。
二、对话界面底部的三种典型入口形态
虽然都在底部,但具体形态有差异。常见的有三类:
2.1 输入框内左下角的下拉按钮
这是最常见的一种。输入框内部左侧有一个带模型名的小胶囊按钮,点击后向上弹出模型列表。
┌─────────────────────────────────────────────┐
│ 请描述你的问题…… │
│ │
│ [ GPT-4o ▾ ] [ 发送 ] │
└─────────────────────────────────────────────┘
↑
模型选择器入口
2.2 输入框下方的工具条
部分产品把模型选择器放在输入框外部下方的一行工具条里,与"联网搜索""上传文件"等按钮并列。
┌─────────────────────────────────────────────┐
│ 请描述你的问题…… │
└─────────────────────────────────────────────┘
[ 模型:Claude ▾ ] [ 🌐 联网 ] [ 📎 上传 ]
2.3 输入框右下角的图标按钮
移动端或极简风格产品中,模型选择器可能被压缩成一个图标(如 ✨ 或 ⚙),点击后从底部弹出抽屉(Bottom Sheet)。
无论哪种形态,操作路径都是一致的:定位输入区 → 找到当前模型标识 → 点击展开 → 选择目标模型 → 收起并生效。
三、完整的操作路径拆解
以最常见的"左下角下拉"为例,一次完整的切换路径如下:
| 步骤 | 用户动作 | 界面反馈 |
|---|---|---|
| 1 | 视线移到输入框左下角 | 显示当前模型名,如 GPT-4o |
| 2 | 点击该按钮 | 向上弹出模型列表,当前项高亮 |
| 3 | 浏览列表 | 每项显示模型名、简介、能力标签 |
| 4 | 点击目标模型 | 列表收起,按钮文字更新 |
| 5 | 发送下一条消息 | 请求体中携带新模型标识 |
关键细节在于第 4 步的状态同步:选择器不仅要更新自己的显示,还要把新模型写入全局状态,供后续请求使用。这也是很多实现出 bug 的地方——UI 变了,但请求还发的是旧模型。
四、用代码实现一个底部模型选择器
下面用 React + TypeScript 实现一个可运行的底部模型选择器,覆盖"展开、选择、状态同步"的完整逻辑。
4.1 模型数据结构与全局状态
// types.ts
export interface ModelOption {
id: string; // 请求时使用的模型标识
name: string; // 展示名称
description: string; // 一句话简介
tag?: string; // 能力标签,如 "推理"、"长文本"
}
export const MODEL_OPTIONS: ModelOption[] = [
{ id: 'gpt-4o', name: 'GPT-4o', description: '通用快速模型', tag: '推荐' },
{ id: 'o1-preview', name: 'o1-preview', description: '深度推理模型', tag: '推理' },
{ id: 'gpt-4-turbo', name: 'GPT-4 Turbo', description: '超长上下文', tag: '长文本' },
];
4.2 选择器组件
// ModelSelector.tsx
import React, { useState, useRef, useEffect } from 'react';
import { MODEL_OPTIONS, ModelOption } from './types';
interface Props {
value: string; // 当前模型 id
onChange: (id: string) => void; // 切换回调
}
export const ModelSelector: React.FC<Props> = ({ value, onChange }) => {
const [open, setOpen] = useState(false);
const wrapperRef = useRef<HTMLDivElement>(null);
// 当前选中的模型对象,用于展示名称
const current: ModelOption =
MODEL_OPTIONS.find((m) => m.id === value) ?? MODEL_OPTIONS[0];
// 点击外部关闭弹层
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
setOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const handleSelect = (id: string) => {
onChange(id); // 同步到全局状态
setOpen(false); // 收起弹层
};
return (
<div className="model-selector" ref={wrapperRef}>
{/* 触发按钮:位于输入框左下角 */}
<button
type="button"
className="model-trigger"
aria-haspopup="listbox"
aria-expanded={open}
onClick={() => setOpen((v) => !v)}
>
<span className="model-name">{current.name}</span>
<span className={`arrow ${open ? 'up' : 'down'}`}>▾</span>
</button>
{/* 向上弹出的模型列表 */}
{open && (
<ul className="model-list" role="listbox">
{MODEL_OPTIONS.map((m) => (
<li
key={m.id}
role="option"
aria-selected={m.id === value}
className={`model-item ${m.id === value ? 'active' : ''}`}
onClick={() => handleSelect(m.id)}
>
<div className="item-head">
<span className="item-name">{m.name}</span>
{m.tag && <span className="item-tag">{m.tag}</span>}
</div>
<div className="item-desc">{m.description}</div>
</li>
))}
</ul>
)}
</div>
);
};
4.3 输入区整合与请求携带模型
// ChatInput.tsx
import React, { useState } from 'react';
import { ModelSelector } from './ModelSelector';
export const ChatInput: React.FC = () => {
const [model, setModel] = useState('gpt-4o');
const [text, setText] = useState('');
const handleSend = async () => {
if (!text.trim()) return;
// 关键:请求体中携带当前选中的模型
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model, message: text }),
});
if (!res.ok) {
console.error('请求失败', res.status);
return;
}
setText('');
};
return (
<div className="chat-input">
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="请描述你的问题……"
/>
{/* 底部工具条:左侧模型选择器,右侧发送按钮 */}
<div className="input-toolbar">
<ModelSelector value={model} onChange={setModel} />
<button className="send-btn" onClick={handleSend}>
发送
</button>
</div>
</div>
);
};
4.4 配套样式(保证弹层向上展开)
/* model-selector.css */
.model-selector {
position: relative; /* 弹层定位基准 */
display: inline-block;
}
.model-trigger {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border: 1px solid #ddd;
border-radius: 16px;
background: #fff;
cursor: pointer;
}
/* 关键:弹层向上展开,避免被输入框遮挡 */
.model-list {
position: absolute;
bottom: calc(100% + 8px);
left: 0;
width: 260px;
margin: 0;
padding: 6px;
list-style: none;
background: #fff;
border: 1px solid #e5e5e5;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.model-item {
padding: 8px 10px;
border-radius: 8px;
cursor: pointer;
}
.model-item:hover,
.model-item.active {
background: #f2f6ff;
}
.item-tag {
margin-left: 6px;
padding: 0 6px;
font-size: 12px;
color: #2f6bff;
background: #e8f0ff;
border-radius: 4px;
}
以上代码可直接运行于任意 React + TypeScript 工程。核心要点有三个:弹层用 bottom: 100% 向上展开、点击外部自动关闭、选择后通过 onChange 同步到全局状态并随请求发送。
五、找不到模型选择器时的排查清单
作为用户,如果你在某个产品里找不到模型选择器,可以按以下顺序排查:
- 看输入框内部:左下角是否有带模型名的小按钮?很多产品默认折叠,只显示当前模型。
- 看输入框下方:是否有工具条,模型名与"联网""上传"等按钮并列?
- 看顶部标题栏:部分产品(尤其是移动端)把模型名放在会话标题旁,点击可切换。
- 看设置页:少数产品把默认模型放在"设置 → 模型"里,切换需进入设置。
- 确认权限:部分企业版或教育版账号被限制了模型切换权限,选择器会被直接隐藏。
作为开发者,如果你实现的选择器"用户找不到",多半是可见性不足:按钮没有显示当前模型名、颜色与背景对比度太低,或者被输入框遮挡。建议始终在触发按钮上展示当前模型名称,而不是一个抽象图标。
六、设计建议与常见坑
- 始终展示当前模型名,不要只放一个 ✨ 图标,否则用户不知道自己在用什么模型。
- 弹层方向要正确:底部选择器的弹层必须向上展开,否则会被屏幕边缘裁切。
- 状态单一来源:模型 id 只应存在于一个全局状态里,选择器和请求逻辑都从它读取,避免"UI 显示 A、请求发送 B"。
- 切换后给出轻提示:例如在消息流中插入一条"已切换到 GPT-4o"的系统提示,让用户确认切换生效。
- 移动端用底部抽屉:小屏下向上弹层空间不足,改用 Bottom Sheet 更符合手势习惯。
总结
回到最初的问题:模型选择器在哪里?
答案是——它几乎总是位于对话界面底部的输入区:要么在输入框内部左下角,要么在输入框下方的工具条中,移动端则常以图标形式出现、点击后弹出底部抽屉。它的操作路径高度统一:定位当前模型标识 → 点击展开列表 → 选择目标模型 → 状态同步并随下一条请求发送。
对于用户,记住"往输入框附近找"就能快速定位;对于开发者,本文给出的 React + TypeScript 示例覆盖了数据定义、弹层交互、状态同步与请求携带的完整链路,可直接作为实现参考。一个小小的底部切换入口,背后是可见性设计、状态管理与请求契约三者的协同——把它做对,用户体验会顺畅很多。
更多推荐


所有评论(0)