终端体验:语音、多语言与视觉
MiMo 在 TUI 里新增的两种语音输入模式与 voice_input 协议、七份界面词典与按时区猜语言、星空与背景图、侧栏插件和一批新斜杠命令。
MiMo Code 的终端界面仍在 packages/opencode/src/cli/cmd/tui,和底座 v1.14.19 同一个位置。OpenCode 后来把界面拆成了独立的 packages/tui 包,终端界面一篇写的是拆包之后的 v1.18.30,文件路径对不上,但骨架相同:OpenTUI 渲染、SolidJS 组件、KV 里存本地偏好、内置功能用插件插槽拼装。本篇只讲 MiMo 在这套骨架上加的东西。
改动分布
与 v1.14.19 的同名目录相比,TUI 新增 54 个文件、修改 105 个、原样保留 40 个,删除 1 个(主题 opencode.json)。新增文件里有一个 283349 字节的 ten_vad.wasm,其余 53 个文本文件合计 8711 行。按子目录看(修改文件的新增行是逐行比对的粗估):
| 子目录 | 新增文件(行) | 修改文件(新增行) | 主要内容 |
|---|---|---|---|
i18n | 10(4399) | 无 | 七种语言的界面词典 |
component | 17(1996) | 16(+1605) | 登录、模态、背景图等对话框,星空背景 |
util | 11(1442) | 7(+247) | 语音、VAD、系统语言检测、拼音 |
routes | 3(39) | 10(+2204) | 会话页新增约 1850 行,现有 3826 行 |
context | 4(413) | 42(+968) | 语言、视觉模式、MiMo 主题;修改的文件里 32 个是主题 JSON,只改了 $schema 地址 |
| 根目录 | 无 | 5(+964) | app.tsx 注册的新命令,thread.ts 与 worker.ts 的启动与监听 |
feature-plugins | 5(302) | 10(+322) | 侧栏插件 |
| 其他 | 4(120 行与 wasm) | 15(+336) | 语音资源、按压交互、插件 API |
输入框 component/prompt/index.tsx 新增约 880 行,现有 2105 行,语音输入的状态机就在这里。上游 TUI 没有词典,界面文字直接写在代码里;MiMo 的 TUI 源码里有 305 处以 tui. 为前缀查词典的调用,分布在 30 个文件中,这也是修改文件多的原因之一。
语音输入
怎么用
/voice开关语音输入,打开后输入框右侧出现一个 Voice 按钮,点一下开始录音、再点一下停止(packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx:813)。/voice-control在“快速输入”与“智能编辑”两种模式间切换,/voice-send允许在智能编辑模式里说“发送”来提交(prompt/index.tsx:848、prompt/index.tsx:831)。这三个开关都存在本地 KV 里,默认全关。- 模型由配置的
voice.asr_model与voice.control_model指定,缺省分别是xiaomi/mimo-v2.5-asr与xiaomi/mimo-v2.5(packages/opencode/src/config/config.ts:393)。换成别的提供方时,只要它有 API key 并能解析出baseURL即可:先取提供方的options.baseURL,再取该提供方第一个模型的 API 地址,xiaomi最后兜底为https://api.xiaomimimo.com/v1(packages/opencode/src/cli/cmd/tui/util/voice.ts:21)。 - 需要命令行录音工具:macOS 用
sox或rec,Linux 用arecord或sox,Windows 只认sox,统一录成 16 kHz、单声道、16 位的原始 PCM(voice.ts:59)。
语音不走内置 LLM 服务的 /v1 接口,而是 TUI 直接带着提供方的密钥请求 <baseURL>/chat/completions,并加上 X-Mimo-Source 头。
录音与切段
录音进程的标准输出按 16 位样本对齐后喂给 RealtimeVAD(voice.ts:116)。VAD 用的是 Agora 开源的 TEN VAD,编译成 WebAssembly 随包分发,许可证为 Apache 2.0(packages/opencode/src/cli/cmd/tui/asset/TEN_VAD_LICENSE)。它每 256 个样本(16 kHz 下 16 毫秒)算一次语音概率(packages/opencode/src/cli/cmd/tui/util/vad.ts:4),参数缺省为:进入说话的阈值 0.8、维持说话的阈值 0.7、向前补 0.6 秒、静默 0.8 秒切段、单段最长 60 秒(vad.ts:91)。智能编辑模式把静默时长放宽到 1.2 秒(prompt/index.tsx:308)。
切段判定在 vad.ts:202:
if (prob >= this.endThreshold) {
this.silenceStartS = null
this.sumPositiveS += hopS
} else if (this.silenceStartS === null) {
this.silenceStartS = chunkOffsetS
}
const shouldCut =
(this.silenceStartS !== null && chunkOffsetS - this.silenceStartS >= this.minSilenceS) ||
(chunkOffsetS - this.activeStartS >= this.maxSegmentS)短于 0.2 秒的片段直接丢弃(vad.ts:216),初始化时先喂 25 帧静音(vad.ts:108)。每切出一段就立即发请求,不必等录音结束:快速输入的各段并发转写,智能编辑的各段排队依次处理(prompt/index.tsx:324)。录音期间按钮在等待说话、说话中(带计时)与处理中三种状态之间切换(prompt/index.tsx:1890)。
两种模式
快速输入调用 ASR 模型,请求里只有一段 input_audio(WAV 编成 data URL)和 asr_options,语言设为自动,超时 30 秒(voice.ts:202)。转写结果插到这段录音送去转写时光标所在的位置,有选区就替换选区;插在文末时,若原文以英文句点、问号或叹号结尾就先补一个空格(packages/opencode/src/cli/cmd/tui/util/voice-edit.ts:81)。快速输入没有发送命令,说“发送”也只是插入这两个字。
智能编辑让多模态模型理解意图。请求由系统提示词、一段描述输入框现状的 JSON 和录音组成,并登记唯一的工具 voice_input(voice.ts:367)。输入框现状有两种写法:有可靠光标时拆成 before_cursor、selection、after_cursor 三段,输入框没有焦点时只给整段文本。工具参数是一个可选的 operation 加一个可选的 send,operation 三选一(voice.ts:303):
| 动作 | 含义 |
|---|---|
insert | 在光标处插入片段,或替换选区 |
set | 给出完整的新文本,光标放到末尾 |
set_with_cursor | 给出三段式的完整新文本,用来移动光标或保留选区 |
系统提示词 voice-input.txt 的规则用英文写成,例子以中文口语为主。核心约束在第 3 行(packages/opencode/src/cli/cmd/tui/util/voice-input.txt:3):
Your ONLY valid reply is a
voice_inputtool call. Never write text in the content field. Never put JSON in the content field. Your entire response must be exactly one tool call — no preamble, no explanation, no JSON object as text.
它还反复强调,用户说出的绝大多数话是要交给编码 Agent 的内容,应当转写后插入,模型不要自己去执行;只有明确针对输入框的删改、清空、移动光标和发送才算命令。
回复不合规(没有调用工具、调用了多次、工具名不对、参数不是合法 JSON 或不符合 schema)时,把模型上一条回复与错误说明附在对话后面重发,最多重试 2 次,即一段录音最多发 3 次请求(voice.ts:490);有工具调用时错误说明以 tool 角色回复,否则以 user 角色补一条(voice.ts:401)。send 只在打开了 /voice-send 时生效,关着时直接丢弃,不触发重试。
结果回来后,若输入框的内容在请求期间被用户改过,这次编辑整个作废并提示“输入内容已变化,语音编辑已跳过”(prompt/index.tsx:342)。快速输入则退而求其次,把转写结果追加到当前文本末尾。内容没变时的插入走编辑器自己的 insertText,粘贴块、文件引用这类标记得以保留;set 与 set_with_cursor 整段重写,这些标记随之清空(prompt/index.tsx:204)。
与设计文档对照
仓库里的 docs/compose/spec/voice-control-tool-protocol.md 记录了这次从旧 JSON 协议迁到 voice_input 工具协议的设计,状态为已交付。对照代码,唯一的工具、不设 tool_choice、协议错误最多重试 2 次、两种模式的静默时长、输入被改过时丢弃编辑、ASR 请求格式不变,都与文档一致。有一处细节不同:文档正文写的是协议出错时追加 assistant 消息和一条 user 角色的错误说明(docs/compose/spec/voice-control-tool-protocol.md:73),代码在有工具调用时改用 tool 角色回复,文档开头的交付记录里把它列为评审后的修改(voice-control-tool-protocol.md:15)。
多语言
v1.14.19 的 TUI 文字都是写死的英文。MiMo 加了 i18n 目录,放 en、zh、zht、ja、ru、fr、es 七份界面词典,每份五六百行;界面组件包 @mimo-ai/ui 另有 17 种语言的词典(packages/ui/src/i18n)。加载时两者合并,组件包的词条优先,缺失的键回落到英文(packages/opencode/src/cli/cmd/tui/context/language.tsx:15)。韩语、德语等十种语言只有组件包词典,TUI 自己的文字仍显示英文。
语言偏好存在 KV 的 locale 里,缺省为 auto(language.tsx:54),用 /language(别名 /lang)切换(packages/opencode/src/cli/cmd/tui/app.tsx:1122)。auto 的判定顺序值得注意:先看时区,再看环境变量(packages/opencode/src/cli/cmd/tui/util/system-locale.ts:190):
export function detectSystemLocale(): Locale {
const tz = detectTimezoneLocale()
if (tz) return tz
for (const env of ["LC_ALL", "LC_MESSAGES", "LANG", "LANGUAGE"] as const) {
const value = process.env[env]
if (!value) continue
for (const raw of value.split(":")) {
const cleaned = raw.replace(/[.@].*$/, "").replace(/_/g, "-").toLowerCase()
if (!cleaned || cleaned === "c" || cleaned === "posix") continue
const match = matchers.find((m) => m.test(cleaned))
if (match) return match.locale
}
}
try {
const intl = Intl.DateTimeFormat().resolvedOptions().locale.toLowerCase()
const match = matchers.find((m) => m.test(intl))
if (match) return match.locale
} catch {}
return "en"
}时区表只覆盖有 TUI 词典的语言:Asia/Shanghai 等内地时区判为简体中文,香港、澳门、台北判为繁体中文,此外还有日语、俄语、法语、西班牙语与英语的时区列表(system-locale.ts:175)。所以系统语言是英文、时区在上海的用户,默认看到的是中文界面,需要手动 /language 改回来。
命令行里 TUI 之外的提示(比如登录流程)走另一个更简单的翻译函数,只带 en、zh、ja、fr、ru 五份词典,不读 KV 偏好,只看系统检测结果(packages/opencode/src/cli/i18n.ts:8)。内置技能与 init、review、dream、distill、goal、deep-research 这几个内置命令的描述也会翻译(packages/opencode/src/cli/cmd/tui/i18n/slash-command.ts:1),内置技能还可以通过词典登记中文斜杠别名(packages/opencode/src/cli/cmd/tui/i18n/skill.ts:11)。
还有一个方便中文用户的细节:通用列表选择框 DialogSelect 的模糊搜索多了一个拼音键,“切换会话”可以用 qiehuanhuihua、qie huan hui hua 或首字母 qhhh 搜到,不必切换输入法(packages/opencode/src/cli/cmd/tui/util/pinyin.ts:11、packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx:104)。
视觉
- 丰富与极简。
/vivid在vivid与minimal两种视觉模式间切换,缺省为vivid;动效只在vivid且 KV 的animations_enabled为真时播放(packages/opencode/src/cli/cmd/tui/context/visual.ts:14)。受它控制的有星空背景、流星、Logo 扫光和进行中标记的动画。 - 星空背景。首页默认铺一层星空:每个字符格以 0.00394 的概率放一颗星,每 200 毫秒随机改变一批星的亮度,每 8 秒划过一颗持续 3.6 秒的流星(
packages/opencode/src/cli/cmd/tui/component/starry-background.tsx:5)。流星用盲文字符的 2×4 点阵画出亚字符精度的斜线与圆头(starry-background.tsx:61)。纯文本终端里不画。 - 背景图。
/background列出配置目录下backgrounds/里的 PNG 与 JPEG,也能输入路径导入,光标移动时即时预览,取消则恢复原设置(packages/opencode/src/cli/cmd/tui/component/dialog-image-list.tsx:13)。在 kitty、Ghostty、WezTerm 里 PNG 走 kitty 图形协议,交给终端按整屏尺寸绘制(packages/opencode/src/cli/cmd/tui/util/image-protocol.ts:3);其他情况用上半块字符▀的前景色和背景色各画半行像素,按铺满屏幕的方式裁切,图片颜色以 45% 的比例混进主题背景色(packages/opencode/src/cli/cmd/tui/component/background-image.tsx:117)。 - Logo 与主题。
/logo切换首页 Logo 的样式(app.tsx:928)。默认主题从上游的opencode换成mimocode(packages/opencode/src/cli/cmd/tui/context/theme.tsx:194),主色由上游暗色的#fab283、亮色的#3b7dd8改成暗色#FF6A00、亮色#C45200的橙色(packages/opencode/src/cli/cmd/tui/context/theme/mimocode.json:12)。
侧栏插件
侧栏内容仍由内置插件按 order 往插槽里填。MiMo 新增了四个,并改了上游的上下文面板(packages/opencode/src/cli/cmd/tui/plugin/internal.ts:21):
| 插件 | order | 显示 |
|---|---|---|
internal:sidebar-context | 100 | 上游已有。MiMo 加了生效的上下文上限与生成速度 |
internal:sidebar-cwd | 125 | 当前工作目录,主目录缩写成 ~ |
internal:sidebar-instructions | 150 | 已加载的指令文件,如 AGENTS.md、CLAUDE.md |
internal:sidebar-goal | 350 | 目标条件与裁判的最新结论,见 Goal |
internal:sidebar-task | 400 | 进行中、待办、受阻的任务,外加最近完成的 3 个,见树状任务 |
生成速度由 packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/tps.ts 计算:流式输出期间,把已收到的正文与推理文本按每 4 个字符一个 token 估算,除以从消息创建到现在的秒数,不足 0.5 秒不显示,每秒刷新;输出完成后改用提供方报告的输出与推理 token 数,除以消息从创建到完成的时长(packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx:38)。两种算法的起点都是消息创建时间,包含等待首个 token 的时间。上下文上限一行显示生效的上限,若上限来自配置(比如用 /context-limit 设过),还会附上模型窗口的大小(context.tsx:97)。新插件里 cwd 与指令文件两块走了词典,Goal 与 Tasks 两块的标题仍是写死的英文。
其他新命令与对话框
与 v1.14.19 相比,TUI 在命令面板里登记的斜杠命令从 26 个增加到 49 个(服务端的 /goal、/dream 这类命令不在此列)。除了上文的语音、语言、视觉命令,以及模型、提供方与提示词路由里讲过的 /login、/logout、/modalities,还有:
| 命令 | 作用 | 出处 |
|---|---|---|
/context-limit | 为当前模型设一个比窗口小的上下文上限,档位 200K、300K、500K、1M 或自定义,写进 compaction.max_context,见压缩、修剪与请求前缀 | app.tsx:715 |
/permission-timeout | 权限询问的等待时限:不限、30 秒、1、2、5、10 分钟,超时自动拒绝 | app.tsx:763 |
/skip-permissions、/never-ask | 自动放行权限请求;让 Agent 不再提问、自行决策(权限请求除外) | app.tsx:747、app.tsx:728 |
/force-agent | 绕过会话中的锁定强制切换模式 | app.tsx:693 |
/worktree | 列出、创建并切换到 git 工作树,目标在服务端工作目录之外时报错而不退出 | app.tsx:893 |
/workflows | 查看当前会话的 Workflow 运行,每秒刷新计数,选中后进入全屏详情页,见 Workflow 运行时 | app.tsx:599 |
/btw | 向正在工作的会话提一个旁问,不打断它 | packages/opencode/src/cli/cmd/tui/routes/session/index.tsx:698 |
/recover | 继续一个被中断的回合 | routes/session/index.tsx:541 |
/revoke-consent | 撤销免费模型的用户协议同意 | prompt/index.tsx:796 |
/doc、/dark、/light | 打开文档;直接切换到深色或浅色 | app.tsx:1006、app.tsx:961 |
下一篇:回顾——回顾整本专栏,把前面各篇讲到的机制串起来。