终端体验:语音、多语言与视觉

MiMo 在 TUI 里新增的两种语音输入模式与 voice_input 协议、七份界面词典与按时区猜语言、星空与背景图、侧栏插件和一批新斜杠命令。

作者 David更新于 第 32 篇(共 33 篇)

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 行。按子目录看(修改文件的新增行是逐行比对的粗估):

子目录新增文件(行)修改文件(新增行)主要内容
i18n10(4399)无七种语言的界面词典
component17(1996)16(+1605)登录、模态、背景图等对话框,星空背景
util11(1442)7(+247)语音、VAD、系统语言检测、拼音
routes3(39)10(+2204)会话页新增约 1850 行,现有 3826 行
context4(413)42(+968)语言、视觉模式、MiMo 主题;修改的文件里 32 个是主题 JSON,只改了 $schema 地址
根目录无5(+964)app.tsx 注册的新命令,thread.ts 与 worker.ts 的启动与监听
feature-plugins5(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_input tool 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-context100上游已有。MiMo 加了生效的上下文上限与生成速度
internal:sidebar-cwd125当前工作目录,主目录缩写成 ~
internal:sidebar-instructions150已加载的指令文件,如 AGENTS.md、CLAUDE.md
internal:sidebar-goal350目标条件与裁判的最新结论,见 Goal
internal:sidebar-task400进行中、待办、受阻的任务,外加最近完成的 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

下一篇:回顾——回顾整本专栏,把前面各篇讲到的机制串起来。

本页目录