# X-ASR 真实实时上屏技术路径与风险报告 状态:未来候选方案,不进入当前版本开发 日期:2026-06-07 关联基线:`v2.1.30-build20260607.1127-xasr-live-caption` ## 1. 结论 真实实时上屏可以做,但不建议放进当前版本。下一次如要继续推进,应作为 X-ASR 专属实验功能开发,默认关闭,不影响 SenseVoice、Qwen3-ASR、Moonshine、Parakeet 的现有“录完后最终粘贴”路径。 当前已经具备做实验版的基础: - X-ASR 已通过 `StreamingEngine` / `StreamingSession` 持续产出 partial 文本。 - Recorder 已支持录音中增量读取 PCM 样本。 - 浮窗实时字幕已经验证为基本可用。 - X-ASR 最终识别已加入尾部静音 padding 和最后非空结果保护。 - 现有最终粘贴流程仍可作为失败兜底。 仍不具备直接做成默认功能的原因: - 真实实时上屏需要持续修改当前目标 App 的文本内容,不只是识别模型问题。 - 不同目标 App 对粘贴、删除、选区、撤销栈、焦点变化和安全权限的处理不同。 - 一旦 partial 回改处理不稳,可能把用户正在编辑的文本改乱。 ## 2. 目标与非目标 ### 2.1 目标 实现 X-ASR 专属的实验模式: ```text 按下热键 -> 开始录音 -> X-ASR 持续输出 partial -> partial 实时写入当前输入框 松开热键 -> X-ASR final 校准 -> 输入框留下最终文本 异常发生 -> 停止实时上屏 -> 回退到当前最终粘贴流程 ``` 用户可感知价值: - 边说边看到文字进入输入框。 - 长句输入等待感降低。 - 中英混输时用户能更早发现识别方向是否正确。 - 让 X-ASR 的流式能力区别于 SenseVoice/Qwen3-ASR 的传统听写体验。 ### 2.2 非目标 第一版不做: - 不影响 SenseVoice、Qwen3-ASR、Moonshine、Parakeet。 - 不把实时上屏设为默认。 - 不做所有 App 的完美兼容。 - 不做复杂文本编辑器协议适配。 - 不在 App Store 正式发布线启用。 ## 3. 隔离策略 真实实时上屏必须同时满足以下条件才启用: ```text SelectedModelID == x-asr-zh-en-960ms Engine implements StreamingEngine Config.RealtimeInsertExperimental == true 当前录音模式允许实验上屏 辅助功能权限可用 ``` 任何条件不满足,走原路径: ```text 录音 -> StopAndGetSamples -> Recognize -> PostProcess -> userdict -> Paste ``` 建议新增配置: ```go type Config struct { RealtimeInsertExperimental bool `json:"RealtimeInsertExperimental"` } ``` 建议前端展示为实验开关,不放在普通模型卡片主流程里: ```text X-ASR 实验功能 [ ] 边说边写入当前输入框 ``` 开关说明只表达风险,不应承诺所有 App 都稳定兼容。 ## 4. 技术路径 ### 4.1 保留现有最终粘贴路径 第一版不要删除现有最终粘贴逻辑。真实实时上屏失败时必须能回到当前已验证路径。 当前基线: - `privatevoice.src/internal/engine/engine.go` 已有 `StreamingEngine` 和 `StreamingSession`。 - `privatevoice.src/internal/audio/recorder.go` 已有 `ReadSamplesSince()`。 - `privatevoice.src/app.go` 已有录音中 live caption loop。 - `privatevoice.src/internal/engine/engine_darwin.go` 的 X-ASR final 已做 padding。 下一步应新增一个独立组件,而不是把实时输入逻辑塞进 engine: ```text app.go -> live streaming loop -> RealtimeInserter -> input backend ``` ### 4.2 新增 RealtimeInserter 建议新增包: ```text privatevoice.src/internal/realtimeinput/ ``` 核心接口: ```go type Inserter interface { Start(target Target) error Update(partial string) error Finish(final string) error Cancel() error } ``` 内部状态: ```go type Session struct { targetAppID string targetWindowID string insertedText string lastPartial string startedAt time.Time totalEdits int fallbackRequired bool } ``` 第一版只要求“对自己插入的文本负责”,不要尝试理解用户输入框里已有内容。 ### 4.3 partial 更新策略 优先采用增量 diff,不每次全删全贴。 规则: 1. 对 `lastPartial` 和 `nextPartial` 求最长公共前缀。 2. 如果 `nextPartial` 只是追加,直接输入新增部分。 3. 如果尾部回改,删除上一段尾部,再输入新尾部。 4. 如果回改跨度超过阈值,停止实时上屏,回退最终粘贴。 建议阈值: ```text 单次删除字符数 <= 24 累计删除字符数 <= 80 单次 partial 长度 <= 300 更新时间间隔 >= 120 ms ``` 示例: ```text last: 我想去苹果市场 next: 我想去 App Store 公共前缀: 我想去 删除: 苹果市场 输入: App Store ``` ### 4.4 输入动作策略 第一版建议只用最保守的模拟输入动作: - 插入文本:复用现有 paste/type backend。 - 删除文本:优先模拟 `DeleteBackward` / `Backspace` 指定次数。 - final 校准:按 diff 修正尾部,不直接清空整段。 不要第一版就引入复杂剪贴板替换和选区控制混合策略,否则 QA 面会扩大。 需要记录: ```text 本次实时上屏插入了多少字符 删除了多少字符 最后一次 partial 是什么 最终 final 是什么 是否触发 fallback ``` ### 4.5 焦点与目标 App 保护 开始录音时记录当前目标: ```text frontmost app bundle id frontmost window title / pid ``` 每次准备写入前检查目标是否仍一致。 如果目标变化: ```text 停止实时上屏 不再修改输入框 继续录音和最终识别 松开后是否最终粘贴需谨慎:默认不粘贴,或提示/记录 fallback ``` 第一版可以采用更保守策略: ```text 焦点变化 -> 取消本次实时上屏 -> 不做 final 粘贴 ``` 这样可以避免文字进入错误 App。 ### 4.6 final 校准 松开热键后: 1. 停止接受新的 partial。 2. 调用 X-ASR `Finish()` 或现有 final `Recognize()`。 3. 对输入框中已插入文本做 final diff。 4. 只修正本次 session 插入的尾部。 5. 完成后清理 session 状态。 如果 final 和 partial 差异过大: ```text 不做大规模删除 保留已上屏内容 记录错误 必要时显示“请检查文本” ``` ## 5. 主要风险 ### 5.1 目标 App 行为差异 不同 App 对模拟输入的处理不同: - 有的 App 对粘贴稳定,但对多次退格慢。 - 有的 App 会把每次 partial 更新写入撤销栈。 - 有的 App 在富文本输入框中会自动改格式。 - 有的网页输入框会拦截粘贴事件。 - 有的 Electron App 对焦点和输入事件处理延迟明显。 影响范围只在 X-ASR 实时上屏开启时,不影响其他模型。 ### 5.2 partial 回改导致文本错乱 流式模型可能修正前文。小幅尾部修正可处理,大幅回改风险高。 风险例子: ```text partial 1: 今天下午开会讨论苹果市场 partial 2: 今天下午开会讨论 App Store 上架材料 ``` 如果删除跨度大,模拟删除容易误删用户原有文本或删不干净。 ### 5.3 焦点变化 用户说话时可能切换窗口、点击别处、系统弹窗抢焦点。实时上屏如果继续写入,会进入错误位置。 必须有焦点检查和快速停止。 ### 5.4 剪贴板和撤销栈 如果实时上屏依赖粘贴: - 可能短暂覆盖用户剪贴板。 - 目标 App 的撤销栈可能变成多次小操作。 - 用户按 Cmd+Z 的结果可能不符合预期。 第一版应优先复用现有剪贴板保护逻辑,并记录是否能够恢复剪贴板。 ### 5.5 权限与安全 实时上屏依赖辅助功能权限。权限失效时: - 不能继续模拟输入。 - 必须停止实时上屏。 - 不能影响录音和最终识别。 ### 5.6 性能与功耗 实时上屏比“录完识别”多了持续 decode 和持续 UI/input 操作。 需要观察: - CPU 占用 - 内存增长 - 长句 3-5 分钟输入稳定性 - 浮窗动画是否卡顿 - 目标 App 是否出现输入延迟 ## 6. 降风险设计 第一版必须具备以下保护: - 默认关闭。 - 只对 `x-asr-zh-en-960ms` 生效。 - 单 session 最大实时插入长度限制。 - partial 大幅回改时停止实时上屏。 - 焦点变化时停止实时上屏。 - 删除失败或权限失败时停止实时上屏。 - 保留最终识别兜底。 - 日志记录每次 fallback 原因。 推荐 fallback reason: ```text focus_changed large_partial_rewrite delete_failed paste_failed permission_missing session_timeout target_app_unsupported ``` ## 7. QA 计划 ### 7.1 目标 App 矩阵 第一轮至少覆盖: | 类型 | App / 场景 | 目的 | |---|---|---| | 系统纯文本 | TextEdit 纯文本模式 | 基础输入、删除、final 校准 | | 系统富文本 | Notes / TextEdit 富文本 | 富文本输入稳定性 | | 浏览器 | Safari / Chrome textarea | 网页输入框 | | Electron | VS Code / 飞书 / Slack | 高频工作 App | | 聊天 | 微信 / 飞书输入框 | 中文即时通讯 | | 开发者工具 | VS Code editor | 英文和符号混输 | 第二轮再扩展: - Word - Pages - App Store Connect 网页表单 - 终端 - 全屏和 Split View ### 7.2 用例矩阵 必须覆盖: - 按住热键短句输入。 - 按住热键长句输入。 - 点按模式开始/停止。 - 录音中立即取消。 - 录音中切换焦点。 - partial 小幅追加。 - partial 小幅尾部回改。 - partial 大幅回改触发 fallback。 - 中英混输。 - 英文技术词。 - 中文长句标点。 - 连续快速触发两次。 - 辅助功能权限缺失。 ### 7.3 验收标准 进入下一阶段前至少满足: - 其他模型路径完全不变。 - X-ASR 实验开关关闭时行为完全等同当前版本。 - X-ASR 开关打开时,基础纯文本 App 连续 20 次输入不乱序、不误删。 - 焦点变化不会把文字写到错误 App。 - fallback 后不会继续实时修改目标输入框。 - final 校准不会删除用户原本已有文本。 - QA 日志能明确每次 fallback 原因。 ## 8. 建议实施顺序 ### Phase 1:不可见底座 - 新增 `RealtimeInsertExperimental` 配置,默认 false。 - 新增 `realtimeinput` 包和纯单元测试。 - 实现 diff 算法、删除/插入计划生成。 - 不接真实 App 输入。 ### Phase 2:受控输入实验 - 只在 TextEdit 纯文本模式手工开启。 - 只支持追加,不支持回改删除。 - 验证焦点检查和 session 生命周期。 ### Phase 3:尾部回改和 final 校准 - 加入有限删除。 - 加入 final diff。 - 大幅回改 fallback。 ### Phase 4:App 矩阵 QA - 扩展到浏览器、聊天、Electron。 - 补 QA 记录和兼容表。 - 决定是否进入产品 UI。 ## 9. 需要保留的产品判断 X-ASR 的价值不是替代现有模型,而是提供另一种输入体验: ```text SenseVoice / Qwen3-ASR / Moonshine / Parakeet: 更适合录完后最终识别 X-ASR: 更适合探索实时反馈、实时字幕、实时上屏 ``` 因此,真实实时上屏应当是 X-ASR 的实验性增强,不应成为所有模型的共享默认行为。 ## 10. 下一次开工检查清单 - [ ] 当前代码仍保留 `StreamingEngine` / `StreamingSession`。 - [ ] X-ASR profile 仍为实验模型,未进入正式 App Store 线。 - [ ] `v2.1.30-build20260607.1127-xasr-live-caption` 可作为回滚基线。 - [ ] 新增配置默认关闭,并可在 UI 或调试入口打开。 - [ ] 先完成 diff 纯单元测试,再接真实 input backend。 - [ ] 先做 TextEdit 纯文本 QA,再扩展目标 App。