TL;DR
我做的 AI 角色聊天应用 (Kotonia) 号称是 voice-first (语音优先)。结果手机打开一看,屏幕底部正中央横亘着一个 96 像素的巨大麦克风按钮,配文「点击开始对话」。
这不是 bug,是结构性的自我矛盾。一个号称 voice-first 的产品,把「进入语音模式」的按钮摆在屏幕中央,UI 就在暗中宣告:「语音其实是个可选项」。
这篇记录从用户的一句「手机端界面坏了」开始的 bug 调查,最终如何演变成一场关于产品理念与 UI 一致性的拷问。
起点只是一个普通的 mobile CSS bug
第一条报告就这么一句:
角色聊天页面在带头像的时候,移动端布局坏了。查一下。
定位下去发现 .voice-grid 在移动端是单列布局,两个子元素 (头像区和聊天区) 都设了 height: 100%。CSS Grid 默认 align-content: stretch,于是高度直接被五五分:
- 头像区 → 全身只能看到上半截,脸被挤在正中间显得很小
- 聊天区 → transcript 设了
flex: 1,但底部 mic 按钮 (96px) + composer + 状态文字 + hint 占了约 250px,留给文字阅读的空间只剩 90px
两边都没法用。
第一步结构修复: B 方案 (Reels 语法)
懒省事的话用 grid-template-rows: minmax(40vh, 45vh) 1fr 就行——头像上面,聊天下面 (A 方案)。但我同时抛出了 B 方案:
头像做满屏背景,transcript / 控件作为半透明 overlay 叠在上面。Reels / TikTok 那套竖屏视频语法。
用户选了 B。我实现完发了截图,回信是这样:
屏幕 80% 都被对话和 overlay 占了的话,可爱的头像就算努力跟你说话也没法专心欣赏。
也就是说,即使切到 B 布局,主角应该是语音 + 头像,但屏幕的主角依然是文字信息——又一层矛盾。话题从物理 UI 切到了产品哲学。
巨型麦克风按钮的可疑性
用户的下一句话是决定性的 (原文几乎照搬):
底部那个按钮做这么大,目的不就是让"开始对话"看起来很显眼吗?要是一开始就处于待命状态启动,这个按钮和"点击开始对话"的文字和那些无用空间都能省掉。
翻译成产品语言: 一个号称 voice-first 的产品,把「进入语音模式」的按钮放在屏幕中央,就是在自相矛盾。
因为:
- voice-first 意味着语音是默认状态,而不是选项
- 既然是默认状态,「进入语音模式」这个动作根本不该存在
- 屏幕中央的巨型按钮其实在宣告:「语音是只有按下这个按钮才会运作的副功能」
- 这不是 voice-first,这是 voice-as-feature (语音作为附加功能)
Pi.ai 和 Sesame 的沉浸式体验之所以成功,正是因为「上来就 voice on、UI 极简」。Kotonia 想往那个方向靠,就得先把大按钮干掉。
三项重设计决策
跟用户来回交换意见之后,定下了下面这套方案:
1. 完全砍掉巨型麦克风按钮
底部的 MicButton、状态文字 (<p>tapToStart</p>)、hint (<p>VAD 自动监听中</p>) 全部删除。底部只留 composer (文本输入框) 和发送按钮。
2. 状态 + 开始/停止 整合到右上角 (statusPill)
<button onClick={handleSessionToggle} className="voice-statusPill">
<span className="voice-statusPill-dot" />
<span>{voiceState === "stopped" ? "START" : voiceState.toUpperCase()}</span>
</button>
一个 pill 同时承担三件事:
- 显示状态 (LISTENING / THINKING / SPEAKING 用脉动小点表示)
- 点击启动 / 停止
- 之前头像右上的「状态 badge」原本就是重复的,现在一并合并
3. cinema / chat 双模切换
localStorage 持久化,右上角 🎬 / 💬 切换按钮:
- cinema (默认): 不显示 transcript。头像占屏幕 9 成。底部只有 composer + 必要时显示的 chip
- chat: transcript 以
max-height: 48vh+ backdrop-blur 叠在底部。用于想回看对话的时候
「想读对话内容的人」和「想专注于头像的人」用同一个 UI 同时服务,双模切换是最直接的解法 (也考虑过用透明渐变让中央透明,但这会把可读性变成二值化体验,所以否决)。
4. 启动触发器是「用户表达意愿的那一刻」
大按钮砍了,那么 session 什么时候启动?答案是:
- 点 suggestion chip → 自动
startSession() - composer 提交 → 自动
startSession() - 点击右上 statusPill → 直接进入 voice (适合不想点 chip 的人)
把「表达意愿」和「启动 session」压缩到一个动作里。「为了进入语音模式而点的前置按钮」彻底消失了。
最终成品

右上是 START pill (停止时是橙色小点,运行中根据状态在蓝/紫/橙之间脉动) 和 🎬 ⇄ 💬 的模式切换。底部是 chip + composer 的细条。中央 9 成是头像,完全不被打扰。
「voice-first 产品」这个说法,终于和 UI 一致了。
学到了什么
"voice-first" 不是技术栈选择,而是 UI 空间分配的问题
用 Whisper、关心 TTS 质量、调 LLM 延迟——这些是支撑 voice-first 的技术,但不是 voice-first 本身。
真正决定能否自称 voice-first 的,是 UI 上语音是否占据画面中央,文本输入是否被作为「辅助手段」缩小处理。这个空间分配决策才是名号的资格证。
如果有「进入语音模式」的按钮,那这个主张就是假的
这套测试在 voice 之外也适用:
- AI 聊天产品里如果有大大的「向 AI 提问」按钮,那不是 AI-first,是 AI-as-feature
- 实时产品里如果有「开启实时」按钮,那不是 realtime-first
- 协作产品里如果有「切换到协作模式」按钮,那不是 collab-first
凡是号称自己是「产品核心」的东西,根本不该需要被开启。它本来就应该已经在那儿。
Bug 报告是产品哲学的试金石
整件事最开始就是一个普通的 CSS bug 报告。但是抽象层级一级一级往上爬:「移动端 CSS 坏了」→「B 布局」→「依然占了 8 成」→「巨型按钮疑案」→「voice-first 到底是个啥?」
一个人做产品,很少有机会自问「这个 UI 元素现在还配得上它占据的空间吗」。用户的 bug 报告里,除了表面的 UI 问题,有时会夹带一种「UI 正在背叛产品本意」的隐性信号。如果能听到那个信号,判断可能会比原始工单往上跳三个抽象层级。
每次自查「产品理念和 UI 一致吗」的时候,能怀疑掉一个巨型按钮,自称 voice-first 这件事就能从场面话变成认真说得出口的承诺,大概吧。
Kotonia 可以在 kotonia.ai 体验。手机上打开 /chat/voice,巨型按钮已经消失,头像应该占了屏幕 9 成。
