# 「身体档案」输入框语音输入 设计 > 2026-06-10 · 在「身体档案」(`HealthExportSheet`)底部聊天输入框加端侧语音听写,复用 `SpeechDictationService`,识别文字实时流进输入框。 ## 背景 「身体档案」composer 是聊天式输入(提问/诉求 → 发送 → LLM 对话/生成报告)。与日记不同,这里输入的内容马上交给 LLM,**不需要"整理"加工**;口述原话直接进输入框即正确行为(类似系统键盘听写)。 ## 决策(已与用户确认) | 维度 | 决定 | |---|---| | 交互 | 听写直接流进输入框:点 mic 开始,实时上屏;再点停止;用户自查后手动发送 | | LLM | 不调用(无整理步骤、不自动发送) | | 复用 | `SpeechDictationService`(**@State 持有**,防视图重建丢实例)、权限 alert 文案、3 分钟看门狗、onDisappear abort | | UI | mic 按钮放 TextField 与发送键之间;`isAvailable == false` 隐藏;录音中变红色停止态(脉冲动画) | ## 组件 ### 1. `SpeechDictationService.merge(prefix:partial:)`(新,static 纯函数) 听写文本拼接规则,唯一可单测的逻辑: - `prefix` 为空 → 返回 `partial` - `prefix` 以空白/换行结尾 → `prefix + partial` - 其余 → `prefix + " " + partial` ### 2. `HealthExportSheet` 改动 - `@State dictation` + `isDictating` + `dictationPrefix` + 看门狗 Task - 点 mic:申请权限(拒绝 → alert 跳设置,与日记同文案)→ 记录 `dictationPrefix = draftQuestion` → start,每个 partial:`draftQuestion = merge(prefix:partial:)` - 再点:`stop()`,最终稿同 merge 落定;**stop 返回空时保留输入框现状**(partial 已实时在框里,天然兜底,不提示「没听清」) - 3 分钟看门狗自动停(防麦克风悬挂) ## 冲突防护 - 录音中:TextField 与发送按钮、「生成整理报告」按钮禁用(防手输与 partial 互相覆盖、防录音中发送) - `isAnswering / isGeneratingReport` 时 mic 禁用 - `onDisappear` abort ## 测试 - `merge(prefix:partial:)` 3 个单测(空前缀 / 空白结尾前缀 / 普通前缀) - 真机手测:听写上屏、停止落定、已有文字保留、权限拒绝、3 分钟自动停 ## 不做(YAGNI) 快捷问答弹窗 / 个人资料 Form 等其他输入处的语音;自动发送;录音面板;LLM 整理。