@bittersmilezzz/dsh-asr-voice 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -0
  3. package/cordis.patch.yml +32 -0
  4. package/lib/asr-models.js +83 -0
  5. package/lib/asr-models.js.map +1 -0
  6. package/lib/client.js +6706 -0
  7. package/lib/client.js.map +1 -0
  8. package/lib/http.js +113 -0
  9. package/lib/http.js.map +1 -0
  10. package/lib/index.js +193 -0
  11. package/lib/index.js.map +1 -0
  12. package/lib/key-ref.js +25 -0
  13. package/lib/key-ref.js.map +1 -0
  14. package/lib/optimize.js +168 -0
  15. package/lib/optimize.js.map +1 -0
  16. package/lib/presets.js +98 -0
  17. package/lib/presets.js.map +1 -0
  18. package/lib/realtime-dashscope.js +211 -0
  19. package/lib/realtime-dashscope.js.map +1 -0
  20. package/lib/realtime-host.js +413 -0
  21. package/lib/realtime-host.js.map +1 -0
  22. package/lib/realtime-provider.js +143 -0
  23. package/lib/realtime-provider.js.map +1 -0
  24. package/lib/realtime-tts.js +179 -0
  25. package/lib/realtime-tts.js.map +1 -0
  26. package/lib/settings.js +155 -0
  27. package/lib/settings.js.map +1 -0
  28. package/lib/stats.js +30 -0
  29. package/lib/stats.js.map +1 -0
  30. package/lib/transcribe.js +274 -0
  31. package/lib/transcribe.js.map +1 -0
  32. package/lib/types/asr-models.d.ts +30 -0
  33. package/lib/types/asr-models.d.ts.map +1 -0
  34. package/lib/types/http.d.ts +41 -0
  35. package/lib/types/http.d.ts.map +1 -0
  36. package/lib/types/index.d.ts +75 -0
  37. package/lib/types/index.d.ts.map +1 -0
  38. package/lib/types/key-ref.d.ts +9 -0
  39. package/lib/types/key-ref.d.ts.map +1 -0
  40. package/lib/types/optimize.d.ts +41 -0
  41. package/lib/types/optimize.d.ts.map +1 -0
  42. package/lib/types/presets.d.ts +64 -0
  43. package/lib/types/presets.d.ts.map +1 -0
  44. package/lib/types/realtime-dashscope.d.ts +41 -0
  45. package/lib/types/realtime-dashscope.d.ts.map +1 -0
  46. package/lib/types/realtime-host.d.ts +95 -0
  47. package/lib/types/realtime-host.d.ts.map +1 -0
  48. package/lib/types/realtime-provider.d.ts +76 -0
  49. package/lib/types/realtime-provider.d.ts.map +1 -0
  50. package/lib/types/realtime-tts.d.ts +42 -0
  51. package/lib/types/realtime-tts.d.ts.map +1 -0
  52. package/lib/types/settings.d.ts +143 -0
  53. package/lib/types/settings.d.ts.map +1 -0
  54. package/lib/types/stats.d.ts +29 -0
  55. package/lib/types/stats.d.ts.map +1 -0
  56. package/lib/types/transcribe.d.ts +46 -0
  57. package/lib/types/transcribe.d.ts.map +1 -0
  58. package/lib/types/tsconfig.client.tsbuildinfo +1 -0
  59. package/package.json +146 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["Switch","jsxRuntime"],"sources":["../src/client/locales.ts","../src/client/styles.ts","../src/presets.ts","../src/key-ref.ts","../src/client/config.ts","../src/client/hotkey.ts","../src/client/settings-card.tsx","../src/client/draft.ts","../src/client/engine.ts","../src/client/pcm.ts","../src/client/recorder.ts","../src/client/optimize.ts","../src/client/animate.ts","../src/client/icons.tsx","../src/client/long-press.ts","../src/client/button-route.ts","../src/client/turn-guard.ts","../src/client/capture.ts","../src/client/vad.ts","../src/client/rms-floor.ts","../src/client/realtime-cloud.ts","../src/client/realtime-cloud-transport.ts","../src/client/realtime.ts","../src/client/speech-out.ts","../src/client/voice-chat.tsx","../src/client/voice-button.tsx","../src/client/index.ts"],"sourcesContent":["/** dsh-asr-voice — client 词典(zh / en)。 键按「三步向导 → 高级 → 录音链路」分块。设置卡片改成 draft + 保存后, 旧的即时写回文案(saveFailed / configSaveFailed)与「获取模型」按钮组已删除。 */\n\n/** 词典键(设置卡片 + 录音按钮 + 预览卡共用)。 */\nexport type LocaleKey =\n // 卡片抬头\n 'cardTitle' | 'cardCopy' | 'readOnlyDoc' | 'howTo' | 'stepEngineTitle' | 'engineAuto' |\n 'engineBrowser' | 'engineCloud' | 'engineHintAuto' | 'engineHintBrowser' | 'engineHintCloud' | 'stepProviderTitle' |\n 'stepProviderHint' | 'cloudPresetCustom' | 'addProvider' | 'removeProvider' | 'providersEmpty' | 'stepKeyTitle' |\n 'keyChecking' | 'keyQueryFailed' | 'keyConfigured' | 'keyNeedsValue' | 'keyNameNeeded' | 'keySave' |\n 'keySaving' | 'keySavedHint' | 'keySaveFailed' | 'keyKeepHint' | 'keyKeepPlaceholder' | 'keyPastePlaceholder' |\n 'testConnection' | 'testAndSave' | 'testBusy' | 'testOk' | 'testFail' | 'save' |\n 'savingHint' | 'savedHint' | 'saveNotApplied' | 'unsavedHint' | 'discard' | 'advancedTitle' |\n 'advancedHint' | 'advancedCollapse' | 'groupAsr' | 'providerNameLabel' | 'providerNameDesc' | 'providerListLabel' |\n 'providerListDesc' | 'cloudBaseUrlLabel' | 'cloudBaseUrlDesc' | 'cloudModelLabel' | 'cloudModelDesc' | 'cloudModelPicked' |\n 'cloudModeLabel' | 'cloudModeDesc' | 'cloudModeAuto' | 'cloudModeTranscriptions' | 'cloudModeChat' | 'fetchModelsPick' |\n 'fetchModelsEmpty' | 'groupOptimize' | 'optimizeModeLabel' | 'optimizeHeuristic' | 'optimizeLlm' | 'optimizePreviewLabel' |\n 'optimizePreviewDesc' | 'optimizingHint' | 'transcribingHint' | 'cancelBusy' | 'optimizeFailedKeep' | 'llmDefaultHint' |\n 'llmProviderLabel' | 'llmModelLabel' | 'llmCurrentDefault' | 'llmModelsEmpty' | 'languageLabel' | 'languageAuto' |\n 'groupBehavior' | 'autoSendLabel' | 'autoSendDesc' | 'silenceStopLabel' | 'silenceStopDesc' | 'holdToTalkLabel' |\n 'holdToTalkDesc' | 'textModeLabel' | 'textModeDesc' | 'textModeReplace' | 'textModeAppend' | 'copyToClipboardLabel' |\n 'copyToClipboardDesc' | 'hotkeyLabel' | 'hotkeyDesc' | 'hotkeyPlaceholder' | 'hotkeyClear' | 'maxRecordMsLabel' |\n 'maxRecordMsDesc' | 'silenceMsLabel' | 'silenceMsDesc' | 'silenceRmsLabel' | 'silenceRmsDesc' | 'groupRealtime' |\n 'realtimeEnableLabel' | 'realtimeEnableDesc' | 'realtimeTtsLabel' | 'realtimeTtsDesc' | 'realtimeTtsBrowser' | 'realtimeTtsOff' |\n 'realtimeHotkeyLabel' | 'realtimeHotkeyDesc' | 'bargeInLabel' | 'bargeInDesc' | 'realtimeSettleMsLabel' | 'realtimeSettleMsDesc' |\n 'realtimeTailMsLabel' | 'realtimeTailMsDesc' | 'realtimeEngineLabel' | 'realtimeEngineDesc' | 'realtimeEngineBrowser' | 'realtimeEngineSegmented' |\n 'realtimeEngineCloud' | 'realtimeProviderLabel' | 'realtimeProviderDesc' | 'realtimeTtsCloud' | 'realtimeTtsVoiceLabel' | 'realtimeTtsVoiceDesc' |\n 'vadRmsAutoLabel' | 'vadRmsAutoDesc' | 'vadFrameMsLabel' | 'vadFrameMsDesc' | 'vadRmsLabel' | 'vadRmsDesc' |\n 'vadSilenceMsLabel' | 'vadSilenceMsDesc' | 'vadPrerollMsLabel' | 'vadPrerollMsDesc' | 'vadMinSpeechMsLabel' | 'vadMinSpeechMsDesc' |\n 'vadMaxSegmentMsLabel' | 'vadMaxSegmentMsDesc' | 'vadMaxPendingLabel' | 'vadMaxPendingDesc' | 'realtimeMaxSessionLabel' | 'realtimeMaxSessionDesc' |\n 'realtimeFirstSentenceLabel' | 'realtimeFirstSentenceDesc' | 'realtimeWatchdogLabel' | 'realtimeWatchdogDesc' | 'chatTitle' | 'chatListeningTitle' |\n 'chatThinkingTitle' | 'chatSpeakingTitle' | 'chatWebSpeechFallback' | 'chatThinkingHint' | 'chatSpeakingHint' | 'chatInterrupt' |\n 'chatEndedLimit' | 'chatNoReply' | 'chatNoTts' | 'chatGap' | 'errSegmentedNeedsCloud' | 'errSegmentedUnsupported' |\n 'errSegmentedUnreachable' | 'errRealtimeProvider' | 'dismiss' | 'loadFailed' | 'micTitle' | 'micTitleHoldChat' | 'recordingTitle' | 'transcribingTitle' |\n 'optimizingTitle' | 'errNoMic' | 'errRecorderStart' | 'errNoSound' | 'errNoSpeechSupport' | 'errWebSpeechNetwork' | 'errCloudNotConfigured' |\n 'noSpeechDetected' | 'fallbackToCloud' | 'errTranscribe' | 'errOptimize' | 'previewTitle' | 'previewOriginal' |\n 'previewOptimized' | 'previewConfirm' | 'previewCancel' | 'groupStats' | 'statsTitle' | 'statsCount' |\n 'statsChars' | 'statsLastAt' | 'statsEmpty'\nexport type LocaleDict = Record<LocaleKey, string>\n\n/** 词典绑定后的翻译函数。 */\nexport type LocaleT = (key: LocaleKey, vars?: Record<string, string | number>) => string\n\n/** 悬停提示语言:跟随**操作系统**语言(navigator.language),与 DSH 界面语言解耦—— 界面设成中文但系统是英文时,按钮 tooltip 仍按系统显示英文(反之亦然)。 */\nexport function systemLanguage(): 'zh' | 'en' {\n const lang = typeof navigator !== 'undefined' ? navigator.language : ''\n return lang.toLowerCase().startsWith('zh') ? 'zh' : 'en'\n}\n\nexport const zh: LocaleDict = {\n cardTitle: '语音输入',\n cardCopy: '开口成文:识别、优化、填入草稿。',\n readOnlyDoc: '宿主当前不接受配置写入(文档只读或连接未就绪),下面的控件已禁用。',\n howTo: '怎么办:① 选「云端」→ ② 点一个服务商 → ③ 按密钥那行的提示走,就完事了。',\n\n stepEngineTitle: '识别方式',\n engineAuto: '自动(推荐)',\n engineBrowser: '仅浏览器(无需配置)',\n engineCloud: '云端(更准,需要 key)',\n engineHintAuto: '先用浏览器内置识别,不可用时自动切到云端。什么都没配过就选这个。',\n engineHintBrowser: '完全在浏览器里识别:免费、不需要任何 key。准确率一般,且只有 Chrome / Edge 支持。',\n engineHintCloud: '调用 OpenAI-compatible 云端识别:准确率和多语种更好,需要一把 API key(下一步起)。',\n\n stepProviderTitle: '服务商',\n stepProviderHint: '点一个即选中,BaseURL、模型和调用通道会自动填好。「自定义」可接任意 OpenAI-compatible 端点。',\n cloudPresetCustom: '自定义',\n addProvider: '+ 自定义服务商',\n removeProvider: '删除',\n providersEmpty: '还没有云端服务商,点「+ 自定义服务商」加一行。',\n\n stepKeyTitle: '密钥与自检',\n keyChecking: '正在查询本机凭据…',\n keyQueryFailed: '凭据状态查询失败',\n keyConfigured: '已使用 DSH 凭据 {ref},不用再填。',\n keyNeedsValue: '尚未配置密钥:把 {ref} 粘贴到下面并保存。',\n keyNameNeeded: '先给这个服务商起个显示名——密钥引用名由它派生。',\n keySave: '保存密钥',\n keySaving: '保存中…',\n keySavedHint: '密钥已写入 DSH 凭据 {ref}。',\n keySaveFailed: '密钥保存失败',\n keyKeepHint: '密钥只写入 DSH 凭据:不进配置文件、不回显、不经过这个页面读取。',\n keyKeepPlaceholder: '留空即不改动',\n keyPastePlaceholder: '粘贴 API key',\n testConnection: '测试连接',\n testAndSave: '保存并测试',\n testBusy: '测试中…',\n testOk: '连接正常,取到 {n} 个模型。',\n testFail: '测试失败',\n\n save: '保存',\n savingHint: '保存中…',\n savedHint: '已保存。',\n saveNotApplied: '「{section}」写回后读回不一致,可能没落盘:请重试或看宿主日志。',\n unsavedHint: '有未保存的更改。',\n discard: '放弃更改',\n\n advancedTitle: '高级',\n advancedHint: '展开:BaseURL / 模型 / 通道 / 多服务商 / 语言 / 优化 / 快捷键 / 用量',\n advancedCollapse: '已展开,点这里收起',\n groupAsr: '识别引擎',\n providerNameLabel: '显示名',\n providerNameDesc: '这个服务商的密钥存在 DSH 凭据 {ref} 下。自定义服务商的引用名由显示名派生——改名等于换一把 key。',\n providerListLabel: '全部服务商',\n providerListDesc: '选中即切换当前使用的供应商;删除只移除这一行配置,不会动 DSH 凭据。',\n cloudBaseUrlLabel: 'Base URL',\n cloudBaseUrlDesc: 'OpenAI-compatible 根地址,通常以 /v1 结尾。预置已填好,改这里即接入自建或代理端点。',\n cloudModelLabel: '模型',\n cloudModelDesc: '识别模型名。想知道端点里到底有哪些模型,用第 ③ 步的「测试连接」。',\n cloudModelPicked: '「测试连接」成功后,这里会列出该端点真实可用的模型。',\n cloudModeLabel: '调用通道',\n cloudModeDesc: '音频走哪条协议:whisper 式转写接口,或 chat + input_audio。选「自动」按模型名判定。',\n cloudModeAuto: '自动(按模型名判定)',\n cloudModeTranscriptions: 'whisper 式 /audio/transcriptions',\n cloudModeChat: 'chat + input_audio(MiMo/Qwen-ASR)',\n fetchModelsPick: '选择模型',\n fetchModelsEmpty: '该端点没有返回可用模型,请检查 Base URL、密钥和模型名。',\n\n groupOptimize: '提示词优化',\n optimizeModeLabel: '优化方式',\n optimizeHeuristic: '本地启发式(免费、离线)',\n optimizeLlm: 'LLM 重写(默认用当前所选模型)',\n optimizePreviewLabel: '优化结果先预览确认',\n optimizePreviewDesc: '关闭(默认):停止录音立即把清洗版文本填入草稿,LLM 优化在后台完成后自动替换(不覆盖你的编辑);开启:等优化完成弹出预览卡,确认后再填入。',\n optimizingHint: '优化中…草稿已填入,可直接编辑或发送',\n transcribingHint: '识别中…',\n cancelBusy: '取消(丢掉本次语音)',\n optimizeFailedKeep: '优化失败,已保留草稿中的清洗版文本',\n llmDefaultHint: '默认使用当前所选 LLM;可在此指定 DSH 已配置的模型。',\n llmProviderLabel: '模型提供方',\n llmModelLabel: '模型',\n llmCurrentDefault: '当前所选(默认)',\n llmModelsEmpty: '该提供方暂无可用模型',\n languageLabel: '识别语言',\n languageAuto: '自动(跟随浏览器/系统)',\n\n groupBehavior: '交互行为',\n autoSendLabel: '识别后自动发送',\n autoSendDesc: '开启后说完即发(push-to-talk 风格),关闭则填入草稿待确认。',\n silenceStopLabel: '静音自动停止',\n silenceStopDesc: '关闭(默认):只有手动点击/快捷键结束录音,点停止即整段去识别;开启:静音持续下方「静音判定时长」后自动结束。',\n holdToTalkLabel: '按住说话',\n holdToTalkDesc: '开启后按住快捷键说话、松开结束;关闭为点击开始、再点结束(静音自动结束由「静音自动停止」开关决定)。',\n textModeLabel: '文本输入',\n textModeDesc: '完整替换:清空草稿后填入;末尾追加:在已有文字后插入。',\n textModeReplace: '完整替换(默认)',\n textModeAppend: '在已有文字后追加',\n copyToClipboardLabel: '自动复制到剪贴板',\n copyToClipboardDesc: '识别优化后自动把结果复制到系统剪贴板,方便粘贴到其它地方。',\n hotkeyLabel: '快捷键',\n hotkeyDesc: '点击后按新组合键(如 Ctrl+Shift+Space);留空关闭。语音对话进行中此键不生效。',\n hotkeyPlaceholder: '点击录制快捷键',\n hotkeyClear: '清除',\n maxRecordMsLabel: '单次录音上限',\n maxRecordMsDesc: '单位毫秒。到点自动结束并送识别,避免忘记关麦一直录。',\n silenceMsLabel: '静音判定时长',\n silenceMsDesc: '单位毫秒。连续安静这么久就判定说完了(需先开启「静音自动停止」)。',\n silenceRmsLabel: '静音阈值',\n silenceRmsDesc: '0~1 的响度比例,低于它算安静。环境嘈杂就调高,说话很轻就调低。',\n\n groupRealtime: '实时语音对话',\n realtimeEnableLabel: '启用语音对话',\n realtimeEnableDesc: '开启后长按麦克风按钮即可进入:边说边上屏,停顿即发起回合,并把回复朗读出来。默认开启。',\n realtimeTtsLabel: '回复播报',\n realtimeTtsDesc: '用浏览器内置语音把 agent 的回复读出来;选「不播报」则只上屏文字。',\n realtimeTtsBrowser: '浏览器语音(默认)',\n realtimeTtsCloud: '云端 TTS(Qwen,需 key)',\n realtimeTtsOff: '不播报',\n realtimeTtsVoiceLabel: '云端音色',\n realtimeTtsVoiceDesc: 'qwen3-tts-flash-realtime 的系统音色名(如 Cherry),key 复用 DSH 凭据 DASHSCOPE_API_KEY。',\n realtimeHotkeyLabel: '对话快捷键',\n bargeInLabel: '语音插话(全双工,默认关)',\n bargeInDesc: '播报回复期间继续收音:你开口说话(持续超过回声门)就直接打断朗读、取消当前回合。默认关——浏览器 AEC 在虚拟设备上实测不生效(0.42 dB),真机回环复测通过前先保持半双工最稳;仅按句切段引擎(segmented)支持。',\n realtimeHotkeyDesc: '点击后按新组合键,用于开始/结束对话或打断播报;留空表示只能用按钮长按。与上面的录音快捷键互不影响。',\n realtimeSettleMsLabel: '断句等待',\n realtimeSettleMsDesc: '单位毫秒。识别文字停止变化这么久,就认为这句说完并上屏。',\n realtimeTailMsLabel: '收尾延时',\n realtimeTailMsDesc: '单位毫秒。上屏前再多等一会儿,接住最后一个词的迟到结果;0 表示不等。',\n realtimeEngineLabel: '实时引擎',\n realtimeEngineDesc: 'Web Speech 逐字出字、不花配额;按句转写用本地静音检测切句,每句走一次已配置的云端转写,出字会慢一拍;云端实时把采集帧上行到本机实时通道,服务端判回合。',\n realtimeEngineBrowser: 'Web Speech(逐字,免费)',\n realtimeEngineSegmented: '按句转写(本地切句 + 云端识别)',\n realtimeEngineCloud: '云端实时(服务端判回合)',\n realtimeProviderLabel: '实时服务商',\n realtimeProviderDesc: '云端实时走哪个 provider。选「内置模拟」用 host 端假 provider(开发态,不花配额);选阿里云百炼接真云端 qwen3-asr-flash-realtime(服务端 VAD 判回合,key 复用 DSH 凭据 DASHSCOPE_API_KEY)。',\n vadRmsAutoLabel: '有声阈值自动校准',\n vadRmsAutoDesc: '按设备噪声底自动调整实际判阈值(下限仍是你设置的「有声阈值」):安静环境更灵、嘈杂环境不乱切句,换设备免重校。',\n vadFrameMsLabel: '采集帧长',\n vadFrameMsDesc: '单位毫秒。每帧的时长,越小越省延迟、越大越省调度。',\n vadRmsLabel: '有声阈值',\n vadRmsDesc: '0~1 的 RMS。按你的设备噪声底调:偏低会把键盘呼吸当话,偏高会切掉轻声句尾。',\n vadSilenceMsLabel: '切句静音',\n vadSilenceMsDesc: '单位毫秒。安静这么久算一句说完,也是每句上屏的固定延迟。',\n vadPrerollMsLabel: '段前缓冲',\n vadPrerollMsDesc: '单位毫秒。把开口前的一小段一起送上去,否则第一个音节必被切掉。',\n vadMinSpeechMsLabel: '最短语音',\n vadMinSpeechMsDesc: '单位毫秒。实际发声短于此不成为一句,不该为咳嗽花一次配额。',\n vadMaxSegmentMsLabel: '最长单句',\n vadMaxSegmentMsDesc: '单位毫秒。说个不停也到这个长度就出字,同时封顶单次上传大小。',\n vadMaxPendingLabel: '待转写队列',\n vadMaxPendingDesc: '最多攒几句待转写。转写慢过说话时丢最早的一句。',\n realtimeMaxSessionLabel: '单次对话上限',\n realtimeMaxSessionDesc: '单位毫秒。到点自动结束这次对话并交还麦克风,避免忘了关而一直听。',\n realtimeFirstSentenceLabel: '首句最少字数',\n realtimeFirstSentenceDesc: '播报时第一句先攒到这么多字才起音,避免一两个短词就开始读。',\n realtimeWatchdogLabel: '播报兜底超时',\n realtimeWatchdogDesc: '单位毫秒。浏览器迟迟不回「播完」事件时,最迟过这么久也按播完处理。',\n\n chatTitle: '语音对话',\n chatListeningTitle: '正在听…点击结束对话',\n chatThinkingTitle: '思考中…点击打断',\n chatWebSpeechFallback: '浏览器语音识别不可用,已自动改用云端识别(按句切段)。请再说一遍。',\n chatSpeakingTitle: '朗读中…点击打断',\n chatThinkingHint: '等回复…',\n chatSpeakingHint: '朗读中…',\n chatInterrupt: '打断',\n chatEndedLimit: '已达单次对话上限,对话自动结束',\n chatNoReply: '这句没有发起回合,继续听',\n chatNoTts: '当前浏览器不支持语音播报,只显示字幕',\n chatGap: '转写跟不上,已丢掉最早的一句,字幕可能不完整',\n errSegmentedNeedsCloud: '按句转写需要先配好云端 ASR(设置 → 语音转文字 → 云端供应商)',\n errSegmentedUnsupported: '当前浏览器不支持实时音频采集(需支持 AudioWorklet),请改用 Web Speech 引擎',\n errSegmentedUnreachable: '转写服务连续失败,本次对话已结束',\n errRealtimeProvider: '云端实时通道已断开,本次对话已结束。请检查网络,以及所选服务商的密钥与额度。',\n\n dismiss: '关闭',\n loadFailed: '加载失败',\n micTitle: '语音输入',\n micTitleHoldChat: '语音输入(长按开始语音对话)',\n recordingTitle: '录音中…点击结束',\n transcribingTitle: '识别中…点击取消',\n optimizingTitle: '优化中…点击取消',\n errNoMic: '未检测到麦克风',\n errRecorderStart: '录音启动失败:麦克风可能被其他程序占用或已被系统回收,请关闭占用麦克风的应用后重试。',\n errNoSound: '未检测到声音:录音为静音,未发送识别。请检查麦克风权限、系统输入音量,并在浏览器地址栏站点设置/授权弹窗中把输入设备选为「内置麦克风」(虚拟音频设备常被误选导致静音)',\n errNoSpeechSupport: '当前浏览器不支持 Web Speech,请改用云端 ASR(Chrome/Edge 均支持)。',\n errWebSpeechNetwork: '浏览器语音识别网络不可用(服务可能被网络屏蔽),已请改用云端 ASR。',\n errCloudNotConfigured: '云端 ASR 未配置:到设置选服务商、填 Base URL,密钥放在 DSH 凭据里。',\n noSpeechDetected: '未检测到语音',\n fallbackToCloud: '浏览器语音识别不可用,已自动切换云端 ASR',\n errTranscribe: '识别失败',\n errOptimize: '优化失败',\n previewTitle: '提示词优化预览',\n previewOriginal: '原始转写',\n previewOptimized: '优化后',\n previewConfirm: '填入并发送',\n previewCancel: '取消',\n groupStats: '用量统计',\n statsTitle: 'ASR 用量',\n statsCount: '已识别 {n} 次',\n statsChars: '累计 {n} 字符',\n statsLastAt: '最近 {time}',\n statsEmpty: '暂无用量数据',\n}\n\nexport const en: LocaleDict = {\n cardTitle: 'Voice Input',\n cardCopy: 'Speak to prompt — recognized, optimized, delivered.',\n readOnlyDoc: 'The host is not accepting config writes right now (read-only document or connection not ready), so the controls below are disabled.',\n howTo: 'What to do: ① pick Cloud → ② click a provider → ③ follow the hint on the key row. That is all.',\n\n stepEngineTitle: 'Recognition engine',\n engineAuto: 'Auto (recommended)',\n engineBrowser: 'Browser only (no setup)',\n engineCloud: 'Cloud (more accurate, needs a key)',\n engineHintAuto: 'Tries the built-in browser recognition first and falls back to cloud when it is unavailable. Pick this if you have configured nothing yet.',\n engineHintBrowser: 'Runs entirely in the browser: free, no key at all. Average accuracy, and only Chrome / Edge support it.',\n engineHintCloud: 'Calls an OpenAI-compatible cloud recognizer: better accuracy and languages, and it needs an API key (next step).',\n\n stepProviderTitle: 'Provider',\n stepProviderHint: 'Click to select — Base URL, model and endpoint mode fill themselves. “Custom” takes any OpenAI-compatible endpoint.',\n cloudPresetCustom: 'Custom',\n addProvider: '+ Custom provider',\n removeProvider: 'Remove',\n providersEmpty: 'No cloud provider yet — click “+ Custom provider” to add one.',\n\n stepKeyTitle: 'Key & self-test',\n keyChecking: 'Checking local credentials…',\n keyQueryFailed: 'Credential status query failed',\n keyConfigured: 'Using DSH credential {ref} — nothing to fill in.',\n keyNeedsValue: 'No key yet: paste {ref} below and save.',\n keyNameNeeded: 'Give this provider a display name first — the credential ref is derived from it.',\n keySave: 'Save key',\n keySaving: 'Saving…',\n keySavedHint: 'Key written to DSH credential {ref}.',\n keySaveFailed: 'Failed to save the key',\n keyKeepHint: 'The key is written only into DSH credentials: never into the settings document, never echoed back to this page.',\n keyKeepPlaceholder: 'Leave blank to keep it',\n keyPastePlaceholder: 'Paste API key',\n testConnection: 'Test connection',\n testAndSave: 'Save & test',\n testBusy: 'Testing…',\n testOk: 'Connection OK — {n} models listed.',\n testFail: 'Test failed',\n\n save: 'Save',\n savingHint: 'Saving…',\n savedHint: 'Saved.',\n saveNotApplied: '“{section}” did not match after the write-back — it may not have persisted. Retry or check the host log.',\n unsavedHint: 'Unsaved changes.',\n discard: 'Discard',\n\n advancedTitle: 'Advanced',\n advancedHint: 'Expand: Base URL / model / endpoint mode / providers / language / optimization / hotkey / usage',\n advancedCollapse: 'Expanded — click to collapse',\n groupAsr: 'Recognition engine',\n providerNameLabel: 'Display name',\n providerNameDesc: 'This provider reads its key from DSH credential {ref}. For a custom provider the ref is derived from the display name — renaming it means a different key.',\n providerListLabel: 'All providers',\n providerListDesc: 'Select one to make it current; removing a row only deletes that config entry, never the DSH credential.',\n cloudBaseUrlLabel: 'Base URL',\n cloudBaseUrlDesc: 'OpenAI-compatible root, usually ending in /v1. Presets fill it; edit here to point at a self-hosted or proxied endpoint.',\n cloudModelLabel: 'Model',\n cloudModelDesc: 'Recognition model name. Use “Test connection” in step ③ to see what the endpoint actually offers.',\n cloudModelPicked: 'After a successful “Test connection”, the models really available on this endpoint are listed here.',\n cloudModeLabel: 'Endpoint mode',\n cloudModeDesc: 'Which protocol the audio goes over: Whisper-style transcriptions, or chat + input_audio. “Auto” decides by model name.',\n cloudModeAuto: 'Auto (by model name)',\n cloudModeTranscriptions: 'Whisper-style /audio/transcriptions',\n cloudModeChat: 'Chat + input_audio (MiMo/Qwen-ASR)',\n fetchModelsPick: 'Pick a model',\n fetchModelsEmpty: 'The endpoint returned no usable model. Check Base URL, key and model name.',\n\n groupOptimize: 'Prompt optimization',\n optimizeModeLabel: 'Optimization mode',\n optimizeHeuristic: 'Local heuristic (free, offline)',\n optimizeLlm: 'LLM rewrite (uses current model by default)',\n optimizePreviewLabel: 'Preview optimized text before inserting',\n optimizePreviewDesc: 'Off (default): the cleaned text fills the draft immediately after recording; the background LLM rewrite replaces it when done (never overrides your edits). On: wait for the rewrite, confirm in a preview card.',\n optimizingHint: 'Optimizing… draft is ready — edit or send now',\n transcribingHint: 'Transcribing…',\n cancelBusy: 'Cancel (discard this utterance)',\n optimizeFailedKeep: 'Optimization failed; the cleaned draft was kept',\n llmDefaultHint: 'Uses the current model by default; pick a configured DSH model below.',\n llmProviderLabel: 'Provider',\n llmModelLabel: 'Model',\n llmCurrentDefault: 'Current model (default)',\n llmModelsEmpty: 'No models available for this provider',\n languageLabel: 'Recognition language',\n languageAuto: 'Auto (follows browser/system)',\n\n groupBehavior: 'Behavior',\n autoSendLabel: 'Auto-send after recognition',\n autoSendDesc: 'When on, the prompt is submitted right after recognition (push-to-talk style). When off, it fills the draft for confirmation.',\n silenceStopLabel: 'Auto-stop on silence',\n silenceStopDesc: 'Off (default): recording ends only when you stop it manually — everything you said goes to recognition at once. On: ends automatically once you stay quiet for the silence window below.',\n holdToTalkLabel: 'Hold to talk',\n holdToTalkDesc: 'When on, hold the hotkey to talk and release to stop. When off, click to start and click again to stop (silence auto-stop is controlled by its own toggle).',\n textModeLabel: 'Text insertion',\n textModeDesc: 'Replace: clear the draft then fill. Append: insert after existing text.',\n textModeReplace: 'Replace draft (default)',\n textModeAppend: 'Append to existing text',\n copyToClipboardLabel: 'Auto-copy to clipboard',\n copyToClipboardDesc: 'Copy the recognized/optimized result to the system clipboard automatically.',\n hotkeyLabel: 'Hotkey',\n hotkeyDesc: 'Click, then press a new combo (e.g. Ctrl+Shift+Space). Clear to disable. Inert while a voice chat is running.',\n hotkeyPlaceholder: 'Click to record hotkey',\n hotkeyClear: 'Clear',\n maxRecordMsLabel: 'Max recording length',\n maxRecordMsDesc: 'Milliseconds. Recording stops and is sent for recognition at the limit, so a forgotten-open mic cannot run forever.',\n silenceMsLabel: 'Silence window',\n silenceMsDesc: 'Milliseconds of quiet that counts as \"done talking\" (needs the auto-stop toggle above).',\n silenceRmsLabel: 'Silence threshold',\n silenceRmsDesc: 'Loudness ratio from 0 to 1; anything below counts as quiet. Raise it in noisy rooms, lower it if you speak softly.',\n\n groupRealtime: 'Realtime voice chat',\n realtimeEnableLabel: 'Enable voice chat',\n realtimeEnableDesc: 'When on, hold the mic button to start: live captions while you speak, a turn starts on your pause, and the reply is read back. On by default.',\n realtimeTtsLabel: 'Speak replies',\n realtimeTtsDesc: 'Reads the agent reply aloud with the browser built-in voice. Choose \"off\" for captions only.',\n realtimeTtsBrowser: 'Browser speech (default)',\n realtimeTtsCloud: 'Cloud TTS (Qwen, needs key)',\n realtimeTtsOff: 'Do not speak',\n realtimeTtsVoiceLabel: 'Cloud voice',\n realtimeTtsVoiceDesc: 'A qwen3-tts-flash-realtime system voice name (e.g. Cherry); reuses the DASHSCOPE_API_KEY credential.',\n realtimeHotkeyLabel: 'Chat hotkey',\n bargeInLabel: 'Barge-in (full duplex, off by default)',\n bargeInDesc: 'Keep listening while the reply is spoken: your voice (sustained past the echo gate) cuts the TTS and cancels the turn. Off by default — Chromium AEC measured 0.42 dB on virtual devices, so half-duplex stays the default until a real acoustic-loop retest; segmented engine only.',\n realtimeHotkeyDesc: 'Click, then press a new combo to start, stop or interrupt the chat. Clear to rely on the mic button long-press only. Independent from the recording hotkey above.',\n realtimeSettleMsLabel: 'Turn settle time',\n realtimeSettleMsDesc: 'Milliseconds. Once the transcript stops changing for this long, the turn is considered finished and put on screen.',\n realtimeTailMsLabel: 'Turn tail delay',\n realtimeTailMsDesc: 'Milliseconds. Waits a bit longer before submitting so the last word\\'s late result still makes it in. 0 means no extra wait.',\n realtimeEngineLabel: 'Realtime engine',\n realtimeEngineDesc: 'Web Speech streams words as you speak and costs nothing; per-sentence mode segments on local silence and sends each sentence to your configured cloud ASR, so captions land one round trip late.',\n realtimeEngineBrowser: 'Web Speech (word by word, free)',\n realtimeEngineSegmented: 'Per-sentence (local segmentation + cloud ASR)',\n realtimeEngineCloud: 'Cloud realtime (server-side turn detection)',\n realtimeProviderLabel: 'Realtime provider',\n realtimeProviderDesc: 'Which provider drives cloud realtime. \"Built-in simulation\" uses the host-side fake provider (dev mode, no quota); Alibaba Cloud Qwen connects to real qwen3-asr-flash-realtime (server-side VAD, reuses the DASHSCOPE_API_KEY credential).',\n vadRmsAutoLabel: 'Auto-calibrate speech threshold',\n vadRmsAutoDesc: 'Raise the effective threshold above your device noise floor automatically (your \"Speech threshold\" stays the lower bound): more responsive when quiet, no phantom sentences when noisy — no recalibration after switching devices.',\n vadFrameMsLabel: 'Capture frame',\n vadFrameMsDesc: 'Milliseconds per captured audio frame. Smaller is snappier, larger is cheaper to schedule.',\n vadRmsLabel: 'Speech threshold',\n vadRmsDesc: 'RMS from 0 to 1, tuned to your device noise floor. Too low turns keyboard clicks into sentences; too high clips quiet sentence endings.',\n vadSilenceMsLabel: 'Segment silence',\n vadSilenceMsDesc: 'Milliseconds of silence that end a sentence — also the fixed delay before each caption appears.',\n vadPrerollMsLabel: 'Pre-roll',\n vadPrerollMsDesc: 'Milliseconds kept from before speech starts. Without it the first syllable is always cut off.',\n vadMinSpeechMsLabel: 'Min speech',\n vadMinSpeechMsDesc: 'Milliseconds of actual voicing a segment needs to count. Coughs should not spend ASR quota.',\n vadMaxSegmentMsLabel: 'Max sentence length',\n vadMaxSegmentMsDesc: 'Milliseconds. An endless monologue still gets captions at this length, and it caps the upload size.',\n vadMaxPendingLabel: 'Transcription queue',\n vadMaxPendingDesc: 'How many sentences may wait for transcription. When ASR falls behind, the oldest is dropped.',\n realtimeMaxSessionLabel: 'Max conversation length',\n realtimeMaxSessionDesc: 'Milliseconds. The conversation ends on its own at the limit and the microphone is handed back, so an unattended session cannot keep listening.',\n realtimeFirstSentenceLabel: 'First sentence min length',\n realtimeFirstSentenceDesc: 'When speaking a reply, the first sentence waits until it has this many characters, so reading does not start on a two-word fragment.',\n realtimeWatchdogLabel: 'Broadcast watchdog',\n realtimeWatchdogDesc: 'Milliseconds. If the browser never reports the utterance as finished, treat it as done after this long.',\n\n chatTitle: 'Voice chat',\n chatListeningTitle: 'Listening… click to end the chat',\n chatThinkingTitle: 'Thinking… click to interrupt',\n chatWebSpeechFallback: 'Browser speech is unavailable — auto-switched to cloud recognition (per-sentence). Please say it again.',\n chatSpeakingTitle: 'Speaking… click to interrupt',\n chatThinkingHint: 'Waiting for the reply…',\n chatSpeakingHint: 'Reading the reply…',\n chatInterrupt: 'Interrupt',\n chatEndedLimit: 'Reached the max conversation length — the chat ended by itself',\n chatNoReply: 'That line did not start a turn, still listening',\n chatNoTts: 'This browser cannot speak replies — captions only',\n chatGap: 'Transcription fell behind — the oldest sentence was dropped, captions may be incomplete',\n errSegmentedNeedsCloud: 'Per-sentence mode needs a configured cloud ASR provider (Settings → Voice → Cloud providers)',\n errSegmentedUnsupported: 'This browser cannot capture live audio (AudioWorklet is required) — switch back to the Web Speech engine',\n errSegmentedUnreachable: 'Transcription failed repeatedly, this conversation ended',\n errRealtimeProvider: 'The cloud realtime channel closed; this conversation ended. Check your network and the provider key/quota.',\n\n dismiss: 'Dismiss',\n loadFailed: 'Load failed',\n micTitle: 'Voice input',\n micTitleHoldChat: 'Voice input (hold to start voice chat)',\n recordingTitle: 'Recording… click to stop',\n transcribingTitle: 'Transcribing… click to cancel',\n optimizingTitle: 'Optimizing… click to cancel',\n errNoMic: 'No microphone detected',\n errRecorderStart: 'Recording failed to start: the microphone may be in use by another app or was reclaimed by the system. Close the app using it and try again.',\n errNoSound: 'No sound detected: the recording was silent and was not sent. Check the mic permission, system input volume, and pick the built-in microphone as the input device in the browser site settings / permission prompt (virtual audio devices are often selected by mistake and record silence)',\n errNoSpeechSupport: 'Web Speech is not supported by this browser; switch to cloud ASR (Chrome/Edge support it).',\n errWebSpeechNetwork: 'Browser speech recognition network is unavailable (the service may be blocked); switch to cloud ASR.',\n errCloudNotConfigured: 'Cloud ASR is not configured: pick a provider and set the Base URL in settings; the key lives in DSH credentials.',\n noSpeechDetected: 'No speech detected',\n fallbackToCloud: 'Browser speech unavailable; switched to cloud ASR',\n errTranscribe: 'Transcription failed',\n errOptimize: 'Optimization failed',\n previewTitle: 'Prompt optimization preview',\n previewOriginal: 'Raw transcript',\n previewOptimized: 'Optimized',\n previewConfirm: 'Fill & send',\n previewCancel: 'Cancel',\n groupStats: 'Usage stats',\n statsTitle: 'ASR usage',\n statsCount: '{n} transcriptions',\n statsChars: '{n} chars total',\n statsLastAt: 'last at {time}',\n statsEmpty: 'No usage data yet',\n}\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** dsh-asr-voice unified copy (flat string keys). */\n 'asr-voice': string\n }\n}\n\n/** 系统语言词典(按钮 tooltip 等「随系统不随界面」的文案共用)。 */\nexport function systemDict(): LocaleDict {\n return systemLanguage() === 'zh' ? zh : en\n}\n","/** dsh-asr-voice — client 样式。\n * 全部用 DSH 主题 CSS 变量(--dsw-*),随明暗主题自适应;data 标签\n * `@bittersmilezzz/dsh-asr-voice`(`data-plugin-css` 为\n * `@bittersmilezzz/dsh-asr-voice/client`)唯一,避免与其它插件样式冲突(独立性契约)。\n * GSAP 驱动的波纹/过渡由 animate.ts 写内联 transform/opacity,本表只提供\n * 基础布局、主题变量与降级关键帧。\n */\n\nexport const CSS = `\n/* 主题变量挂 :root(页面没有 [data-plugin] 属性的容器元素——那是 <style> 标签的\n data 属性,不作用于渲染树;挂错选择器会让 --dshav-* 全部未定义 → 背景透明)。\n --dshav-* 前缀唯一,挂全局不影响其它插件(独立性契约)。 */\n:root {\n --dshav-accent: var(--dsw-alias-state-business-primary, #4f8cff);\n --dshav-accent-soft: color-mix(in srgb, var(--dshav-accent) 18%, transparent);\n --dshav-danger: var(--dsw-alias-state-error-primary, #e5484d);\n --dshav-bg: var(--dsw-alias-bg-base, #ffffff);\n --dshav-bg-layer: var(--dsw-alias-bg-layer-1, #f7f7f8);\n --dshav-border: var(--dsw-alias-border-l1, #e6e6e9);\n --dshav-text: var(--dsw-alias-label-primary, #1f1f23);\n --dshav-text-2: var(--dsw-alias-label-secondary, #5c5c66);\n --dshav-text-3: var(--dsw-alias-label-caption, #8b8b95);\n /* Motion tokens(microanimations 规范:进入缓、退出快,非对称) */\n --dshav-ease-enter: cubic-bezier(.2, 0, 0, 1);\n --dshav-ease-exit: cubic-bezier(.3, 0, .8, .15);\n --dshav-ease-bounce: cubic-bezier(.34, 1.56, .64, 1);\n --dshav-dur-enter: 250ms;\n --dshav-dur-exit: 180ms;\n}\n\n/* ── 录音按钮(conversation.input.right) ─────────────────────────── */\n.dshav-mic-wrap { position: relative; display: inline-flex; align-items: center; justify-content: center;}\n/* 悬停气泡:不依赖浏览器原生 title(自动化/嵌入浏览器会禁用它),\n 文案由组件按系统语言提供;hover/focus 都显示,键盘可达。 */\n.dshav-tooltip {\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n z-index: 60;\n max-width: min(260px, calc(100vw - 40px));\n padding: 4px 9px;\n border-radius: 7px;\n background: var(--dsw-alias-bg-layer-3, var(--dshav-bg-layer));\n border: 1px solid var(--dsw-alias-border-l2, var(--dshav-border));\n box-shadow: var(--dsw-shadow-lv3, 0 4px 16px rgba(0, 0, 0, .12));\n color: var(--dsw-alias-label-secondary, var(--dshav-text-2));\n font-size: 12px;\n line-height: 1.4;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition: opacity .14s ease, visibility .14s ease;\n}\n.dshav-mic-wrap:hover .dshav-tooltip,\n.dshav-mic-wrap:focus-within .dshav-tooltip { opacity: 1; visibility: visible;}\n@media (prefers-reduced-motion: reduce) {\n .dshav-tooltip { transition: none; }\n}\n.dshav-mic-button {\n position: relative;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 9px;\n background: transparent;\n color: var(--dshav-text-2);\n cursor: pointer;\n padding: 0;\n /* 一个按钮同时承担「点」与「长按」:触屏上必须关掉双击缩放与长按选中,\n 否则按住 450ms 会被浏览器判成长按选词/缩放,手势根本传不到我们手里。 */\n touch-action: manipulation;\n user-select: none;\n -webkit-user-select: none;\n transition: color .18s ease, background .18s ease, transform .14s var(--dshav-ease-bounce, cubic-bezier(.34,1.56,.64,1));\n}\n.dshav-mic-button:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.05)); color: var(--dshav-text);}\n.dshav-mic-button:hover svg { transform: scale(1.1); }\n.dshav-mic-button:active { transform: scale(.9); }\n.dshav-mic-button svg { width: 15px; height: 15px; display: block; transition: transform .2s ease;}\n.dshav-mic-button[data-state='recording'] { color: var(--dshav-danger); background: color-mix(in srgb, var(--dshav-danger) 12%, transparent);}\n.dshav-mic-button[data-state='recording'] .dshav-rec-dot { animation: dshav-blink 1.1s ease-in-out infinite;}\n.dshav-mic-button[data-state='transcribing'],\n.dshav-mic-button[data-state='optimizing'] { color: var(--dshav-accent);}\n.dshav-mic-button:disabled { opacity: .45; cursor: default;}\n\n/* ── 同一个按钮的对话态(复用上面的按钮壳,只有状态色不同) ─────────── */\n.dshav-mic-button[data-mode='chat'][data-state='listening'] { color: var(--dshav-danger); background: color-mix(in srgb, var(--dshav-danger) 12%, transparent);}\n.dshav-mic-button[data-mode='chat'][data-state='listening'] .dshav-rec-dot { animation: dshav-blink 1.1s ease-in-out infinite;}\n.dshav-mic-button[data-mode='chat'][data-state='thinking'] { color: var(--dshav-accent);}\n.dshav-mic-button[data-mode='chat'][data-state='speaking'] { color: var(--dshav-accent); background: var(--dshav-accent-soft);}\n/* 长按可用提示:右上角一颗静态小点。两个动作共用一个按钮,不给点线索的话\n 「长按能开对话」没有任何可发现性(tooltip 只在悬停时才出现)。 */\n.dshav-mic-button[data-chat='ready']::after {\n content: '';\n position: absolute;\n top: 4px;\n right: 4px;\n width: 3px;\n height: 3px;\n border-radius: 50%;\n background: var(--dshav-accent);\n opacity: .5;\n transition: opacity .18s ease;\n}\n.dshav-mic-button[data-chat='ready']:hover::after { opacity: 1; }\n/* 已经在对话里:这颗点没有信息量了(按钮本身已经换了图标与状态色)。 */\n.dshav-mic-button[data-mode='chat']::after { opacity: 0; }\n/* 字幕行:这里上屏的文字就是主角,给它比状态提示更宽的可视区(截断在 JS 做)。 */\n.dshav-hotkey-hint[data-kind='caption'] { max-width: min(520px, calc(100vw - 120px));}\n.dshav-hotkey-hint[data-kind='caption'][data-state='speaking'] .dshav-hint-text { color: var(--dshav-accent);}\n\n/* 录音实心点(呼吸)。 */\n.dshav-rec-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: currentColor;\n display: block;\n}\n\n/* 录音波纹:多层呼吸光环,GSAP 驱动 scale/opacity(此处放降级静态环)。 */\n.dshav-wave { position: absolute; inset: 0; border-radius: 9px; pointer-events: none;}\n.dshav-wave-ring {\n position: absolute;\n inset: -2px;\n border-radius: 11px;\n border: 2px solid var(--dshav-danger);\n opacity: 0;\n box-shadow: 0 0 12px 0 color-mix(in srgb, var(--dshav-danger) 45%, transparent);\n /* 录音中持续缩放动画:提升为合成层,避免每帧重绘(transform 走 GPU)。 */\n will-change: transform, opacity;\n}\n.dshav-wave-ring[data-ring='1'] { inset: -2px; }\n.dshav-wave-ring[data-ring='2'] { inset: -6px; }\n.dshav-wave-ring[data-ring='3'] { inset: -10px; }\n\n/* 状态提示条:从按钮右侧滑入 + 呼吸点 + 频谱条。 */\n.dshav-hotkey-hint {\n position: absolute;\n right: 34px;\n top: 50%;\n transform: translateY(-50%);\n z-index: 30;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 10px;\n /* 关键:包含块是 28px 的按钮容器(.dshav-mic-wrap position:relative),\n shrink-to-fit 会被压到 min-content——中文每字可断行 → 一字一行纵向溢出。\n width:max-content 让宽度由内容决定,再由 max-width 封顶折行。 */\n width: max-content;\n max-width: min(420px, calc(100vw - 120px));\n overflow: hidden;\n border: 1px solid var(--dsw-alias-border-l2, var(--dshav-border));\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-3, var(--dshav-bg-layer));\n color: var(--dsw-alias-label-secondary, var(--dshav-text-2));\n font-size: 12px;\n line-height: 1;\n white-space: nowrap;\n box-shadow: var(--dsw-shadow-lv3, 0 4px 16px rgba(0,0,0,.12));\n animation: dshav-hint-in .22s var(--dshav-ease-exit, cubic-bezier(.3,0,.8,.15));\n}\n.dshav-hotkey-hint[data-kind='err'] {\n color: var(--dshav-danger);\n /* 错误信息允许换行:诊断详情(设备/浏览器)不能被 nowrap 截断 */\n white-space: normal;\n line-height: 1.35;\n max-width: min(480px, calc(100vw - 120px));\n}\n/* err/notice 的文本包进 span 后必须可换行:\n 1) flex 子项默认按 min-content 收缩——中文每字可断行 → 每个字单独一行(纵向溢出);\n 必须 min-width: 0 + flex: 1 让文本占满剩余宽度再折行。\n 2) 长 token(设备名/URL)不炸行:overflow-wrap: anywhere。 */\n.dshav-hotkey-hint[data-kind='err'] .dshav-hint-text,\n.dshav-hotkey-hint[data-kind='notice'] .dshav-hint-text {\n flex: 1 1 auto;\n min-width: 0;\n max-width: none;\n white-space: normal;\n line-height: 1.35;\n overflow: visible;\n text-overflow: clip;\n overflow-wrap: anywhere;\n}\n.dshav-hotkey-hint[data-kind='err'] .dshav-hint-dismiss { align-self: flex-start; }\n.dshav-hint-dismiss {\n flex: none;\n margin-left: 2px;\n padding: 0 2px;\n border: none;\n background: transparent;\n color: inherit;\n font-size: 13px;\n line-height: 1;\n opacity: .55;\n cursor: pointer;\n border-radius: 4px;\n}\n.dshav-hint-dismiss:hover { opacity: 1; background: var(--dsw-alias-fill-2, rgba(128,128,128,.18)); }\n.dshav-hotkey-hint .dshav-dot {\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dshav-danger);\n animation: dshav-blink 1.1s ease-in-out infinite;\n}\n.dshav-hint-text { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}\n\n/* 频谱条:CSS 变量 --level(0~1)驱动柱高;每柱 --bar 系数错落。 */\n.dshav-spectrum {\n --level: .2;\n display: inline-flex;\n align-items: flex-end;\n gap: 2px;\n height: 16px;\n padding: 1px 0;\n}\n.dshav-bar {\n width: 3px;\n border-radius: 2px;\n background: var(--dshav-danger);\n height: calc(var(--level) * 16px * var(--bar));\n opacity: .85;\n transition: height 60ms linear;\n}\n\n/* 转圈(transcribing / optimizing)。 */\n.dshav-spinner {\n flex: none;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n border: 2px solid color-mix(in srgb, var(--dshav-accent) 25%, transparent);\n border-top-color: var(--dshav-accent);\n animation: dshav-spin .7s linear infinite;\n}\n\n/* ── 预览卡(LLM 优化:原始 → 优化) ──────────────────────────────── */\n.dshav-preview {\n position: fixed;\n left: 50%;\n bottom: 84px;\n transform: translateX(-50%);\n z-index: 1200;\n width: min(460px, calc(100vw - 32px));\n box-sizing: border-box;\n padding: 12px 12px 10px;\n border: 1px solid var(--dshav-border);\n border-radius: 14px;\n background: var(--dshav-bg);\n box-shadow: var(--dsw-shadow-lv3, 0 10px 32px rgba(0,0,0,.18));\n display: flex;\n flex-direction: column;\n gap: 10px;\n animation: dshav-preview-in .22s var(--dshav-ease-enter, ease-out);\n}\n.dshav-preview-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--dshav-text);\n}\n/* 标题麦克风固定小尺寸(SVG 无 width/height 默认渲染 300px,必须显式约束) */\n.dshav-preview-title svg { width: 15px; height: 15px; color: var(--dshav-accent); display: block;}\n.dshav-preview-body { display: flex; flex-direction: column; gap: 6px;}\n.dshav-preview-block {\n display: flex;\n flex-direction: column;\n gap: 3px;\n padding: 8px 10px;\n border-radius: 9px;\n background: var(--dshav-bg-layer);\n border: 1px solid var(--dshav-border);\n}\n/* 优化后区块轻微强调,突出「要用的结果」 */\n.dshav-preview-block[data-role='optimized'] {\n background: color-mix(in srgb, var(--dshav-accent) 6%, var(--dshav-bg-layer));\n border-color: color-mix(in srgb, var(--dshav-accent) 22%, var(--dshav-border));\n}\n.dshav-preview-label { font-size: 10.5px; font-weight: 600; color: var(--dshav-text-3); letter-spacing: .03em;}\n.dshav-preview-text {\n margin: 0;\n font-size: 13px;\n line-height: 1.55;\n color: var(--dshav-text);\n max-height: 96px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n/* 原始:次级色即可,不用删除线(划线=删除感,反而看不清) */\n.dshav-preview-text[data-role='original'] { color: var(--dshav-text-2); }\n/* 优化后:主色加粗突出 */\n.dshav-preview-text[data-role='optimized'] { color: var(--dshav-text); font-weight: 500; }\n.dshav-preview-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px;}\n.dshav-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n border: none;\n border-radius: 18px;\n cursor: pointer;\n font: inherit;\n font-size: 14px;\n line-height: 22px;\n height: 36px;\n padding: 0 14px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n color: var(--dsw-alias-label-primary);\n background: transparent;\n transition: background .15s ease, color .15s ease;\n}\n.dshav-button:disabled { cursor: not-allowed; opacity: .4; }\n.dshav-button-sm {\n height: 28px;\n font-size: 12px;\n line-height: 18px;\n padding: 0 10px;\n border-radius: 14px;\n}\n.dshav-button-primary { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground);}\n.dshav-button-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover); }\n.dshav-button-outline { border: 1px solid var(--dsw-alias-border-l2); background: transparent;}\n.dshav-button-outline:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n.dshav-button-ghost:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n.dshav-button:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px;}\n\n/* ── 设置卡片(对齐官方 PluginCard:折叠 + hover/cardOpen/focus) ── */\n.dshav-card {\n box-sizing: border-box;\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n.dshav-card:hover { border-color: var(--dsw-alias-label-dimmed); }\n.dshav-card.dshav-card-open { background: var(--dsw-alias-bg-layer-2); border-color: var(--dsw-alias-label-dimmed);}\n.dshav-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.dshav-header:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px;}\n.dshav-headtext {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dshav-name { font-size: 15px; font-weight: 600; line-height: 1.4; color: var(--dsw-alias-label-primary); }\n.dshav-desc { margin: 0; font-size: 13px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }\n.dshav-chevron { flex: none; color: var(--dsw-alias-label-tertiary); transition: transform .16s;}\n.dshav-chevron.dshav-open { transform: rotate(180deg); }\n.dshav-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding-bottom: 8px;}\n.dshav-group { display: flex; flex-direction: column; }\n/* 分组之间用与字段一致的分割线(官方 border-l2)。 */\n.dshav-group + .dshav-group { border-top: 1px solid var(--dsw-alias-border-l2); }\n.dshav-groupTitle {\n margin: 14px 0 2px;\n font-size: 13px;\n font-weight: 600;\n line-height: 20px;\n color: var(--dsw-alias-label-secondary);\n}\n/* 字段:垂直布局(label / control / hint),字段间 border-top —— 对齐官方 fields */\n.dshav-field-item { display: flex; flex-direction: column; gap: 6px; padding: 12px 0;}\n.dshav-field-item + .dshav-field-item { border-top: 1px solid var(--dsw-alias-border-l2); }\n.dshav-field-head { display: flex; align-items: center; gap: 8px; }\n.dshav-field-label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dshav-field-control { display: flex; width: 100%; min-width: 0; }\n.dshav-field-hint { margin: 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }\n.dshav-stack { display: flex; flex-direction: column; }\n.dshav-field { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }\n/* 开关行:官方 Switch 与文本同行(对齐官方设置面板控件) */\n.dshav-toggle {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dshav-toggle input[type='radio'] {\n flex: none;\n width: 16px;\n height: 16px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n/* 条件禁用(如 barge-in 仅 segmented 引擎可用):整行降透明度并去掉指针 */\n.dshav-field-disabled { opacity: 0.55; pointer-events: none;}\n/* ── 原生控件:跟随 DSH 主题(--dsw-alias-*),与官方设置页一致 ── */\n.dshav-field select,\n.dshav-field input[type='text'],\n.dshav-field input[type='password'] {\n box-sizing: border-box;\n height: 34px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-3);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dshav-field select:focus-visible,\n.dshav-field input:focus-visible { outline: none; border-color: var(--dsw-alias-brand-primary);}\n.dshav-field input::placeholder { color: var(--dsw-alias-label-dimmed); }\n.dshav-field input:disabled,\n.dshav-field select:disabled { opacity: .6; cursor: default; }\n/* select:原生外观 + 主题 chevron(与官方 selectInput 一致) */\n.dshav-field select {\n appearance: none;\n max-width: 240px;\n min-width: 180px;\n padding-right: 32px;\n cursor: pointer;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 12px center;\n background-size: 12px 12px;\n}\n.dshav-field input[type='text'],\n.dshav-field input[type='password'] { flex: 1; min-width: 0; width: auto; }\n.dshav-status { font-size: 12px; color: var(--dshav-text-2); min-height: 16px; }\n.dshav-status[data-kind='err'] { color: var(--dshav-danger); }\n.dshav-status[data-kind='ok'] { color: var(--dshav-accent); }\n\n/* ── 三步向导 ─────────────────────────────────────────────────────── */\n.dshav-step { display: flex; flex-direction: column; gap: 8px; padding: 14px 0 16px;}\n.dshav-step-head { display: flex; align-items: center; gap: 8px; }\n.dshav-step-index {\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: color-mix(in srgb, var(--dshav-accent) 14%, transparent);\n color: var(--dshav-accent);\n font-size: 12px;\n line-height: 1;\n}\n.dshav-step-title { font-size: 14px; font-weight: 600; line-height: 20px; color: var(--dsw-alias-label-primary);}\n/* chip 单选行:换行排布,选中态用主色描边 + 浅底(不靠颜色单独表意) */\n.dshav-chips { display: flex; flex-wrap: wrap; gap: 8px; }\n.dshav-chip {\n box-sizing: border-box;\n height: 30px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 15px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 13px;\n cursor: pointer;\n transition: border-color .15s ease, background .15s ease, color .15s ease;\n}\n.dshav-chip:hover:not(:disabled) { border-color: var(--dsw-alias-label-dimmed); color: var(--dsw-alias-label-primary); }\n.dshav-chip[data-selected='true'] {\n border-color: var(--dshav-accent);\n background: color-mix(in srgb, var(--dshav-accent) 10%, transparent);\n color: var(--dshav-accent);\n font-weight: 600;\n}\n.dshav-chip:disabled { opacity: .5; cursor: default; }\n.dshav-chip:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px;}\n/* 密钥已就绪的一行(绿色 ✓ 文案,非输入态) */\n.dshav-ok-line {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: 6px;\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dshav-accent);\n}\n/* 保存条:状态文案在左,按钮在右,常驻避免用户以为没生效 */\n.dshav-actions { display: flex; align-items: center; gap: 8px; padding: 12px 0;}\n.dshav-actions > p { flex: 1; min-width: 0; }\n/* 高级折叠:整行可点,展开后列出全部既有字段 */\n.dshav-advanced-toggle {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n padding: 14px 0 10px;\n border: none;\n background: none;\n font: inherit;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n text-align: left;\n cursor: pointer;\n}\n.dshav-advanced-toggle:hover { color: var(--dsw-alias-label-primary); }\n.dshav-advanced-toggle:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; border-radius: 6px;}\n.dshav-provider-list { display: flex; flex-direction: column; gap: 8px; }\n.dshav-provider-row { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.dshav-provider-row > label { flex: 1; min-width: 0; }\n\n@keyframes dshav-blink {\n 0%, 100% { opacity: 1; }\n 50% { opacity: .25; }\n}\n@keyframes dshav-hint-in {\n from { opacity: 0; transform: translate(8px, -50%); }\n to { opacity: 1; transform: translate(0, -50%); }\n}\n@keyframes dshav-spin {\n to { transform: rotate(360deg); }\n}\n@keyframes dshav-preview-in {\n from { opacity: 0; transform: translate(-50%, 10px); }\n to { opacity: 1; transform: translate(-50%, 0); }\n}\n@media (prefers-reduced-motion: reduce) {\n .dshav-mic-button,\n .dshav-mic-button svg,\n .dshav-mic-button .dshav-rec-dot,\n .dshav-hotkey-hint .dshav-dot,\n .dshav-spinner { animation: none !important; transition: none !important; }\n .dshav-hotkey-hint { animation: none !important; }\n .dshav-preview { animation: none !important; }\n}\n`\n","/** dsh-asr-voice — 云端 ASR 预置表(host 与 client 共享,双半区各自编译)。\n * 全部走 OpenAI-compatible `/audio/transcriptions` 端点,统一字段:\n * baseUrl + apiKey + model\n * 预置只是「快捷填充」,用户可任意改 baseUrl / model(自定义端点天然兼容\n * 本地/私有部署的 OpenAI-compatible ASR 服务,如 local-ai)。\n * 跨平台说明:云端 ASR 是纯 HTTP,macOS / Windows 行为一致。\n */\n\n/** 云端 ASR 调用通道:whisper 式 multipart /audio/transcriptions,或 chat-completions input_audio(MiMo / Qwen-ASR)。 */\nexport type CloudAsrMode = 'auto' | 'transcriptions' | 'chat'\n\n/** 一个云端 ASR 预置。 */\nexport interface CloudPreset {\n /** 稳定 id(settings 里存的 preset 值)。 */\n id: string\n /** 设置页显示名。 */\n label: string\n /** OpenAI-compatible base URL(不含 /audio/transcriptions)。 */\n baseUrl: string\n /** 默认模型(可改)。 */\n defaultModel: string\n /** 调用通道(auto = 按模型名自动判定;transcriptions = whisper 式 /audio/transcriptions;chat = chat.completions input_audio)。 */\n mode: CloudAsrMode\n /** 简介(设置页提示)。 */\n hint: string\n}\n\n/** 内置预置:OpenAI / Groq(国际)+ 硅基流动 / 小米 MiMo / 通义 Qwen-ASR(国产)。 */\nexport const CLOUD_PRESETS: readonly CloudPreset[] = [\n {\n id: 'openai',\n label: 'OpenAI Whisper',\n baseUrl: 'https://api.openai.com/v1',\n defaultModel: 'whisper-1',\n mode: 'transcriptions',\n hint: 'OpenAI 官方 /audio/transcriptions',\n },\n {\n id: 'groq',\n label: 'Groq Whisper',\n baseUrl: 'https://api.groq.com/openai/v1',\n defaultModel: 'whisper-large-v3',\n mode: 'transcriptions',\n hint: 'Groq 高速推理,whisper-large-v3',\n },\n {\n id: 'siliconflow',\n label: '硅基流动 SiliconFlow',\n baseUrl: 'https://api.siliconflow.cn/v1',\n defaultModel: 'FunAudioLLM/SenseVoiceSmall',\n mode: 'transcriptions',\n hint: '国产,SenseVoice 系语音识别',\n },\n {\n id: 'mimo',\n label: '小米 MiMo',\n baseUrl: 'https://api.xiaomimimo.com/v1',\n defaultModel: 'mimo-v2.5-asr',\n mode: 'chat',\n hint: '小米 MiMo-V2.5-ASR(OpenAI 兼容 chat input_audio;key 复用 DSH 凭据 MIMO_API_KEY)',\n },\n {\n id: 'dashscope',\n label: '通义/阿里云百炼 Qwen-ASR',\n baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n defaultModel: 'qwen3-asr-flash',\n mode: 'chat',\n hint: '国产,Qwen-ASR(compatible-mode OpenAI 兼容 chat input_audio)',\n },\n]\n\n/** 按 id 取预置(找不到返回 undefined)。 */\nexport function presetById(id: string): CloudPreset | undefined {\n return CLOUD_PRESETS.find((p) => p.id === id)\n}\n\n/** 实时转写 provider 预置(I5:真云端 provider 行)。\n * 与 CLOUD_PRESETS 独立:实时走 WebSocket(wss://…/api-ws/v1/realtime),\n * 不是 OpenAI-compatible HTTP。凭据复用同名官方 LLM provider(keyPreset 指回\n * CLOUD_PRESETS 里的预置 id,`keyRefFor` 因此派生成 `<PRESET>_API_KEY`)。\n */\nexport interface RealtimePreset {\n /** 稳定 id(settings `realtime.provider` 存的值;'' = 未配置走内置模拟)。 */\n id: string\n /** 设置页显示名。 */\n label: string\n /** WebSocket 根地址(不含 model query,由 defaultModel 拼上)。 */\n wssUrl: string\n /** 默认实时模型(可改)。 */\n defaultModel: string\n /** 凭据复用哪个 CLOUD_PRESETS 预置(keyRefFor 派生引用名)。 */\n keyPreset: string\n /** 简介(设置页提示)。 */\n hint: string\n}\n\n/** 内置实时预置:阿里云百炼 Qwen-ASR Realtime(服务端 VAD 断句)。 */\nexport const REALTIME_PRESETS: readonly RealtimePreset[] = [\n {\n id: 'builtin',\n label: '内置模拟(开发)',\n wssUrl: '',\n defaultModel: '',\n keyPreset: 'openai',\n hint: 'I3/I4 开发态:host 用能量 VAD 假 provider 驱动整条管道,不花配额',\n },\n {\n id: 'dashscope-realtime',\n label: '阿里云百炼 Qwen-ASR Realtime',\n wssUrl: 'wss://dashscope.aliyuncs.com/api-ws/v1/realtime',\n defaultModel: 'qwen3-asr-flash-realtime',\n keyPreset: 'dashscope',\n hint: '服务端 VAD 判回合;key 复用 DSH 凭据 DASHSCOPE_API_KEY(约 ¥1.19/音频小时)',\n },\n]\n\n/** 按 id 取实时预置(找不到返回 undefined)。 */\nexport function realtimePresetById(id: string): RealtimePreset | undefined {\n return REALTIME_PRESETS.find((p) => p.id === id)\n}\n\n/** 预置默认 id。 */\nexport const DEFAULT_PRESET_ID = 'openai'\n\n/** 云端 ASR 端点(拼在 baseUrl 之后,whisper 式通道用)。 */\nexport const TRANSCRIBE_PATH = '/audio/transcriptions'\n\n/** chat-completions 通道端点(拼在 baseUrl 之后)。 */\nexport const CHAT_COMPLETIONS_PATH = '/chat/completions'\n\n/** 上传/代理的音频大小上限(字节),防滥用。 */\nexport const MAX_AUDIO_BYTES = 25 * 1024 * 1024\n\n/** 模型名含这些词 → 判定为「音频/ASR 大模型」走 chat input_audio 通道。 */\nconst CHAT_MODE_MODEL_RE = /(^|[/_.-])(asr|audio|omni)([/_.-]|$)|sensevoice/i\n\n/** 按模型名自动判定调用通道:chat 音频大模型 vs whisper 式 /audio/transcriptions。 */\nexport function autoModeForModel(model: string): CloudAsrMode {\n return CHAT_MODE_MODEL_RE.test(model) ? 'chat' : 'transcriptions'\n}\n\n/** 解析最终调用通道:显式模式优先,auto 按模型名判定。 */\nexport function resolveAsrMode(mode: string, model: string): CloudAsrMode {\n if (mode === 'chat' || mode === 'transcriptions') return mode\n return autoModeForModel(model)\n}\n","/** dsh-asr-voice — API key 的凭据引用名(host / client 共用的唯一真相)。\n * key 既不存 settings 也不进浏览器:统一落在 DSH credentials 服务里,按引用名读取。\n * 预置供应商刻意沿用官方 LLM provider 的命名(`<PROVIDER>_API_KEY`,派生方式见\n * packages/client/ui-settings-models/src/client/store.ts 的 deriveKeyRef),因此用户\n * 在 DSH 里配过 OpenAI / Groq / MiMo / 百炼的 LLM,ASR 直接复用同一把 key,一次都不用填。\n * 自定义供应商带一个可读 name,派生成 `ASR_VOICE_<NAME>_API_KEY`,不与其它插件撞名。\n * 引用名受网关校验:必须是 POSIX 环境变量形状 `^[A-Za-z_][A-Za-z0-9_]*$`\n * (packages/host/apiproxy/src/api/credentials.schema.ts),故非字母数字一律折叠成 `_`。\n */\nimport { CLOUD_PRESETS } from './presets.ts'\n\n/** 可复用官方 LLM 凭据的预置 id 集合(其余一律按自定义处理)。 */\nconst PRESET_IDS = new Set(CLOUD_PRESETS.map((p) => p.id))\n\n/** 任意标签 → 合法引用名片段(无可用字符时返回空串,由调用方决定退路)。 */\nfunction slug(raw: string): string {\n return raw.toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_+|_+$/g, '')\n}\n\n/** 一个参与引用派生的供应商身份(settings 行里不含任何密钥材料)。 */\nexport interface KeyRefSource {\n preset: string\n name: string\n id: string\n}\n\n/** 求某供应商的 API key 引用名。 @param p - 供应商身份(预置 id、显示名、行 id)。 @returns credentials 服务里的引用名。 */\nexport function keyRefFor(p: KeyRefSource): string {\n if (PRESET_IDS.has(p.preset)) return `${slug(p.preset)}_API_KEY`\n const fromName = slug(p.name)\n const label = fromName !== '' ? fromName : slug(p.id)\n return `ASR_VOICE_${label === '' ? 'CUSTOM' : label}_API_KEY`\n}\n","/** dsh-asr-voice — client 配置模型。\n * 配置权威源是 host settings 服务(namespace `asr-voice`),本模块持有运行时快照\n * `config`(录音链路同步读取)与设置页的编辑草稿。**API key 不在此处也不在\n * settings**:它落 DSH credentials 服务,本模块只提供按引用名读写的那几个函数。\n * 两条铁律,都是踩过坑得来的:\n * 1. 宿主快照一律先脱冻再用。`SettingsScopeController.derive()` 把 host 值喂进\n * immer `produce`,产出的快照连同数组元素是**深度冻结**的(所有构建,不只是\n * dev)。按引用存进可变快照,之后每一次行内编辑都是严格模式下的冻结写——\n * 抛 TypeError 并被 React onChange 吞掉,界面表现成「下拉点了没反应」。\n * 2. 草稿只 rebuild,不 mutate。所有 `with*` 函数返回新对象,行对象永不原地改。\n * settingsScope / credentials 都是「可选服务」:按当前 DSH client 规范由 apply 里的\n * scoped inject 传入(拿不到就只更新本地快照),而不是列成插件级硬依赖。\n */\n// Type-only: pulls the settings domain's Context merge (ctx.settingsScope).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { SettingsScope, SettingsScopeBinder } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { SegmentedTuning } from './realtime.ts'\nimport { keyRefFor, type KeyRefSource } from '../key-ref.ts'\nimport { DEFAULT_PRESET_ID, presetById } from '../presets.ts'\n\n/** 单个云端 ASR 供应商配置(无密钥:key 在 credentials,见 keyRefOf)。 */\nexport interface CloudProviderConfig {\n id: string\n preset: string\n /** 显示名;自定义供应商的凭据引用名由它派生。 */\n name: string\n baseUrl: string\n model: string\n mode: string\n}\n\n/** 统一配置对象:与 host settings schema 结构一致(刻意不含 apiKey)。 */\nexport interface AsrVoiceConfig {\n asr: {\n provider: 'auto' | 'browser' | 'cloud'\n cloud: {\n /** 多供应商列表(v0.2)。 */\n providers: CloudProviderConfig[]\n /** 当前使用的供应商 id(空 = 取第一个)。 */\n active: string\n /** ── 兼容旧单配置(v0.1 遗留,读取回退用) ── */\n preset: string\n baseUrl: string\n model: string\n mode: string\n }\n }\n optimize: {\n mode: 'heuristic' | 'llm'\n /** llm 模式入框方式:false = 立即填入清洗版 + 后台优化替换;true = 预览卡确认后填入。 */\n preview: boolean\n llm: {\n provider: string\n model: string\n }\n }\n language: string\n behavior: {\n autoSend: boolean\n /** 静音自动停止(默认关 = 手动结束录音)。 */\n silenceStop: boolean\n holdToTalk: boolean\n hotkey: string\n textMode: 'replace' | 'append'\n copyToClipboard: boolean\n /** 单次录音最长时长(毫秒)。 */\n maxRecordMs: number\n /** 静音判定阈值(RMS,0~1)。 */\n silenceRms: number\n /** 静音持续多久即自动停止(毫秒)。 */\n silenceMs: number\n }\n realtime: {\n /** 实时语音对话总开关。 */\n enabled: boolean\n /** 实时引擎:browser(Web Speech 逐字)/ segmented(本地 VAD 按句 + 整段转写通道)/ cloud(PCM 上行 host 实时通道,服务端 VAD 判回合)。 */\n engine: 'browser' | 'segmented' | 'cloud'\n /** 云端实时 provider(engine=cloud 时;builtin = 内置模拟,dashscope-realtime = 阿里云百炼)。 */\n provider: string\n /** 回复播报:browser(speechSynthesis)/ cloud(云端 TTS)/ off(只出字)。 */\n tts: 'browser' | 'off' | 'cloud'\n /** 云端 TTS 音色(仅 tts=cloud 生效)。 */\n ttsVoice: string\n /** 进出实时模式的快捷键('' = 关闭)。 */\n hotkey: string\n /** 语音插话(D19,默认关):播报回复期间恢复收音,人声持续超出回声门才打断。 */\n bargeIn: boolean\n turn: {\n /** 转写文字静默多久算「说完了」(毫秒)。 */\n settleMs: number\n /** 静音窗口之后再宽限这么久才提交(毫秒)。 */\n tailMs: number\n }\n /** 声学切段参数(仅 engine=segmented)。 */\n vad: {\n /** 采集帧长(毫秒)。 */\n frameMs: number\n /** RMS 有声阈值(0~1);rmsAuto 开启时它是下限,实际判据随噪声底抬升。 */\n rms: number\n /** 自动校准:阈值 = max(rms, 静音期噪声底 × 裕量),换设备免重校。 */\n rmsAuto: boolean\n /** 连续静音多久切一段(毫秒)。 */\n silenceMs: number\n /** 段前保留(毫秒)。 */\n prerollMs: number\n /** 短于此不成为一段(毫秒)。 */\n minSpeechMs: number\n /** 单段长度上限(毫秒)。 */\n maxSegmentMs: number\n /** 待转写队列上限(不含在途那段)。 */\n maxPending: number\n }\n /** 单次对话上限(毫秒)。 */\n maxSessionMs: number\n speech: {\n /** 首句最少字数。 */\n firstSentenceMinChars: number\n /** 朗读看门狗(毫秒)。 */\n utteranceWatchdogMs: number\n }\n }\n}\n\n/** 顶层可写段(`settings.mutate` 按顶层路径寻址,写回也按这一段一段来)。 */\nexport type ConfigSection = 'asr' | 'optimize' | 'language' | 'behavior' | 'realtime'\n\n/** 对象形态的顶层段(language 是标量,走 withLanguage)。 */\nexport type ConfigObjectSection = Exclude<ConfigSection, 'language'>\n\n/** 配置默认值(与 host schema 的 default 一致)。 */\nexport const DEFAULTS: AsrVoiceConfig = {\n asr: { provider: 'auto', cloud: { providers: [], active: '', preset: 'openai', baseUrl: '', model: '', mode: 'auto' } },\n optimize: { mode: 'llm', preview: false, llm: { provider: '', model: '' } },\n language: 'auto',\n behavior: { autoSend: false, silenceStop: false, holdToTalk: false, hotkey: 'Ctrl+Shift+Space', textMode: 'replace', copyToClipboard: true, maxRecordMs: 120_000, silenceRms: 0.02, silenceMs: 2_500 },\n realtime: { enabled: true, engine: 'browser', provider: 'builtin', tts: 'browser', ttsVoice: 'Cherry', hotkey: 'Ctrl+Shift+Alt+Space', bargeIn: false, turn: { settleMs: 900, tailMs: 300 }, vad: { frameMs: 40, rms: 0.02, rmsAuto: true, silenceMs: 700, prerollMs: 200, minSpeechMs: 250, maxSegmentMs: 8_000, maxPending: 3 }, maxSessionMs: 600_000, speech: { firstSentenceMinChars: 12, utteranceWatchdogMs: 60_000 } },\n}\n\n/** 运行时配置快照:初始为默认值,scope 订阅与写回共同维护。 */\nexport const config: AsrVoiceConfig = structuredClone(DEFAULTS)\n\n/** 配置变更订阅(模块级,供组件 useSyncExternalStore / 事件驱动重渲染)。 */\nconst listeners = new Set<() => void>()\n\n/** 订阅配置变更,返回退订函数。 */\nexport function subscribeConfig(fn: () => void): () => void {\n listeners.add(fn)\n return () => { listeners.delete(fn) }\n}\n\n/** settings namespace(host schema 注册与 client 绑定共用同一个名字)。 */\nexport const ASR_VOICE_NS = 'asr-voice'\n\n/** host settings scope 的写路径(官方 SettingsScopeBinder.bind 的返回类型)。 */\nexport type SettingsScopeLike<T> = SettingsScope<T>\n\n/** credentials RPC 的单条视图(脱敏后的配置态,永不含密钥本身)。 */\nexport interface CredentialStateLike {\n configured: boolean\n source?: string | undefined\n writable: boolean\n}\n\ntype RpcOutcome<T> = { ok: true; value: T } | { ok: false; error?: { message?: string } }\n\n/** DSH credentials 域的 client 面(alpha.3 官方 `ctx.remote.credentials` 的最小形状)。\n * 与旧 `connection.api.credentials`(describe({refs}) 返回 {result})形状不同:\n * 这里 describe 直接收 refs 数组、返回 { ok, value }。host 半区解析键与展示\n * 语义不变,只是 client 通道换了载体(见 src/client/index.ts 的注入)。\n */\nexport interface CredentialsApiLike {\n describe(refs: string[]): Promise<{ ok: true; value: Record<string, CredentialStateLike> } | { ok: false; error?: { message?: string } }>\n set(ref: string, value: string): Promise<{ ok: true } | { ok: false; error?: { message?: string } }>\n unset(ref: string): Promise<{ ok: true } | { ok: false; error?: { message?: string } }>\n}\n\n/** 旧版 `connection.api.credentials` 形状(仅作回退,见 index.ts 的适配器)。 */\nexport interface LegacyCredentialsApiLike {\n describe(payload: { refs: string[] }): Promise<{ result: RpcOutcome<{ credentials?: Record<string, CredentialStateLike> }> }>\n set(payload: { ref: string; value: string }): Promise<{ result: RpcOutcome<unknown> }>\n unset(payload: { ref: string }): Promise<{ result: RpcOutcome<unknown> }>\n}\n\ntype CredentialsDescribeOutcome = { ok: true; value: Record<string, CredentialStateLike> } | { ok: false; error?: { message?: string } }\ntype CredentialsWriteOutcome = { ok: true } | { ok: false; error?: { message?: string } }\n\n/** 把旧版凭据通道适配成新版形状(alpha.3 前的老运行时仍暴露旧面时兜底)。 */\nexport function adaptLegacyCredentials(legacy: LegacyCredentialsApiLike | undefined): CredentialsApiLike | undefined {\n if (legacy === undefined) return undefined\n const writeOutcome = (r: RpcOutcome<unknown>): CredentialsWriteOutcome =>\n r.ok ? { ok: true } : r.error === undefined ? { ok: false } : { ok: false, error: r.error }\n return {\n describe: async (refs: string[]): Promise<CredentialsDescribeOutcome> => {\n const response = await legacy.describe({ refs })\n if (!response.result.ok) {\n return response.result.error === undefined\n ? { ok: false }\n : { ok: false, error: response.result.error }\n }\n return { ok: true, value: response.result.value.credentials ?? {} }\n },\n set: async (ref: string, value: string): Promise<CredentialsWriteOutcome> =>\n writeOutcome((await legacy.set({ ref, value })).result),\n unset: async (ref: string): Promise<CredentialsWriteOutcome> =>\n writeOutcome((await legacy.unset({ ref })).result),\n }\n}\n\n/** host settings scope 的写路径(apply 时绑定;未绑定则只更新本地快照)。 */\nlet voiceScope: SettingsScopeLike<AsrVoiceConfig> | undefined\nlet credentialsApi: CredentialsApiLike | undefined\n\n/** 广播配置变更(设置卡片/录音按钮监听,驱动重渲染)。\n * 单个 listener 抛错不得中断其余(某处渲染异常不应让整次广播失效)。 */\nexport function announce(): void {\n for (const fn of listeners) {\n try {\n fn()\n } catch {\n // 隔离:继续通知下一个 listener\n }\n }\n}\n\n/** 是否普通数据对象(数组与 null 都不算)。 */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction str(value: unknown): string {\n return typeof value === 'string' ? value : ''\n}\n\n/** 数值字段:非有限数(NaN/Infinity/字符串/缺省)一律退回本地值。 */\nfunction num(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : fallback\n}\n\n/** 逐字段重建供应商行:只取本客户端认识的字段(宿主多出来的键一律不带走)。 */\nfunction normalizeProvider(row: unknown): CloudProviderConfig {\n const src = isPlainObject(row) ? row : {}\n const preset = str(src.preset)\n const mode = str(src.mode)\n return {\n id: str(src.id),\n preset: preset === '' ? 'openai' : preset,\n name: str(src.name),\n baseUrl: str(src.baseUrl),\n model: str(src.model),\n mode: mode === '' ? 'auto' : mode,\n }\n}\n\n/** 把 host 快照并回本地 config(只覆盖认识的键)。 每个进来的对象/数组都先落成自己的拷贝:宿主值是深度冻结的,漏一个引用进来就是 一处「改了不生效」的静默故障(见文件头铁律 1)。 */\nexport function mergeHostValue(value: Partial<AsrVoiceConfig>): void {\n if (!isPlainObject(value)) return\n // 铁律:本地默认的形状永不被外来快照顶掉。realtimeTuning() 这类消费方直接解构\n // turn / speech,一个 null 或形状漂移的字段顶进默认值就是整条链路崩溃;\n // 类型不符(含 null)一律视为「宿主没写这一项」,沿用本地值。\n const assign = (target: Record<string, unknown>, src: Record<string, unknown>): void => {\n for (const key of Object.keys(target)) {\n const next = src[key]\n const current = target[key]\n if (isPlainObject(current)) {\n if (isPlainObject(next)) assign(current, next)\n continue\n }\n if (Array.isArray(current)) {\n if (Array.isArray(next)) target[key] = key === 'providers' ? next.map(normalizeProvider) : structuredClone(next)\n continue\n }\n if (typeof current === 'number') {\n target[key] = num(next, current)\n continue\n }\n if (typeof next === typeof current) target[key] = next\n }\n }\n assign(config as unknown as Record<string, unknown>, value as unknown as Record<string, unknown>)\n}\n\n/** 结构等价(忽略键序):写回校验用它,避免 {a,b} 与 {b,a} 被误判成没落盘。 */\nfunction jsonEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false\n return a.every((item, index) => jsonEqual(item, b[index]))\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {\n if (!jsonEqual(a[key], b[key])) return false\n }\n return true\n }\n return false\n}\n\n/** 绑定 host settings scope 并订阅:首次读取当前值,之后 scope 变化回写本地快照并广播。 @param binder - settingsScope 服务的 binder(SettingsScopeBinder)。 @returns 订阅 disposer(随 fiber 清理)。 */\n/** 绑定 host settings scope 并订阅:首次读取当前值,之后 scope 变化回写本地快照并广播。 @param binder - settingsScope 服务(官方 SettingsScopeBinder,随 fiber 注入)。 @returns 订阅 disposer(随 fiber 清理)。 */\nexport function bindConfigScope(binder: SettingsScopeBinder): () => void {\n const scope = binder.bind<AsrVoiceConfig>({ namespace: ASR_VOICE_NS })\n voiceScope = scope\n const applySnapshot = (): void => {\n const value = scope.getSnapshot().value\n if (value !== undefined) {\n mergeHostValue(value)\n announce()\n }\n }\n const unsub = scope.subscribe(applySnapshot)\n applySnapshot()\n return unsub\n}\n\n/** 绑定 credentials 域(可选:拿不到时设置页只显示「本机未启用凭据服务」)。 统一收新形状(alpha.3 `remote.credentials`);旧形状由调用方经 {@link adaptLegacyCredentials} 显式转换后再绑。 */\nexport function bindCredentialsApi(api: CredentialsApiLike | undefined): void {\n credentialsApi = api\n}\n\n/** 设置页是否具备写回宿主的能力。 */\nexport function settingsWritable(): boolean {\n return voiceScope?.getSnapshot().writable ?? false\n}\n\n// ── 草稿:设置页只编辑这份副本,按「保存」才过线 ─────────────────────────\n\n/** 复制一份当前配置作为可编辑草稿(与宿主快照完全脱开,随便改都不碰冻结)。 */\nexport function newDraft(): AsrVoiceConfig {\n return structuredClone(config)\n}\n\n/** 浅并一层对象段,返回新草稿。 */\nexport function withSection<K extends ConfigObjectSection>(\n draft: AsrVoiceConfig, key: K, patch: Partial<AsrVoiceConfig[K]>,\n): AsrVoiceConfig {\n return { ...draft, [key]: { ...draft[key], ...patch } }\n}\n\n/** 整段录音链路的时长与静音参数(从配置快照取,recorder 不认 settings 层)。 */\nexport interface RecordBehavior {\n /** 单次录音最长时长(毫秒)。 */\n maxRecordMs: number\n /** 是否静音自动停止。 */\n silenceStop: boolean\n /** 静音判定阈值(RMS,0~1)。 */\n silenceRms: number\n /** 静音持续多久即自动停止(毫秒)。 */\n silenceMs: number\n}\n\n/** 取当前配置的录音参数快照(配置 → recorder 的唯一搬运处)。 */\nexport function recordBehavior(source: AsrVoiceConfig = config): RecordBehavior {\n const { maxRecordMs, silenceStop, silenceRms, silenceMs } = source.behavior\n return { maxRecordMs, silenceStop, silenceRms, silenceMs }\n}\n\n/** 实时语音对话参数(会话、引擎、播报共用同一份快照)。 */\nexport interface RealtimeTuning {\n /** 按钮是否存在。 */\n enabled: boolean\n /** 实时引擎。 */\n engine: 'browser' | 'segmented' | 'cloud'\n /** 回复播报方式。 */\n tts: 'browser' | 'off' | 'cloud'\n /** 云端 TTS 音色(仅 tts=cloud 生效)。 */\n ttsVoice: string\n /** 对话快捷键('' = 关闭)。 */\n hotkey: string\n /** 语音插话(默认关):播报期间恢复收音,人声持续超出回声门才打断。 */\n bargeIn: boolean\n /** 转写静默多久算说完(毫秒)。 */\n settleMs: number\n /** 说完后再宽限这么久才提交(毫秒)。 */\n tailMs: number\n /** 单次对话上限(毫秒)。 */\n maxSessionMs: number\n /** 首句最少字数。 */\n firstSentenceMinChars: number\n /** 单句朗读看门狗(毫秒)。 */\n utteranceWatchdogMs: number\n /** 识别语言(同时用于挑选朗读音色)。 */\n language: string\n /** 引擎参数(两引擎共用同一份回合判定,segmented 另有声学切段)。 */\n segmented: SegmentedTuning\n}\n\n/** 取当前配置的实时对话快照(配置 → 会话/引擎/播报的唯一搬运处)。 */\nexport function realtimeTuning(source: AsrVoiceConfig = config): RealtimeTuning {\n const { enabled, engine, tts, ttsVoice, hotkey, bargeIn, turn, vad, maxSessionMs, speech } = source.realtime\n return {\n enabled, engine, tts, ttsVoice, hotkey, bargeIn, maxSessionMs, language: source.language,\n settleMs: turn.settleMs, tailMs: turn.tailMs,\n firstSentenceMinChars: speech.firstSentenceMinChars,\n utteranceWatchdogMs: speech.utteranceWatchdogMs,\n segmented: {\n settleMs: turn.settleMs,\n tailMs: turn.tailMs,\n frameMs: vad.frameMs,\n maxPending: vad.maxPending,\n vad: {\n rms: vad.rms,\n // 老宿主快照(schema 升级前)没有 rmsAuto:缺省按开启回退。\n rmsAuto: vad.rmsAuto ?? true,\n silenceMs: vad.silenceMs,\n prerollMs: vad.prerollMs,\n minSpeechMs: vad.minSpeechMs,\n maxSegmentMs: vad.maxSegmentMs,\n },\n },\n }\n}\n\n/** 改识别语言(顶层标量段)。 */\nexport function withLanguage(draft: AsrVoiceConfig, language: string): AsrVoiceConfig {\n return { ...draft, language }\n}\n\n/** 写整个 providers 列表(保持 active 指向还在的行)。 */\nexport function withProviders(draft: AsrVoiceConfig, providers: CloudProviderConfig[], active?: string): AsrVoiceConfig {\n const nextActive = active ?? (providers.some((p) => p.id === draft.asr.cloud.active) ? draft.asr.cloud.active : providers[0]?.id ?? '')\n return withSection(draft, 'asr', { cloud: { ...draft.asr.cloud, providers, active: nextActive } })\n}\n\n/** 按 id 打补丁到某一供应商行(重建行对象,永不原地改)。 */\nexport function patchProvider(draft: AsrVoiceConfig, id: string, patch: Partial<CloudProviderConfig>): AsrVoiceConfig {\n const rows = draft.asr.cloud.providers\n const index = rows.findIndex((p) => p.id === id)\n if (index === -1) {\n // 目标还是 v0.1 的合成行(列表为空):直接 map 命中不了任何行,改动静默消失。\n const synthesized = draftActiveProvider(draft)\n if (synthesized.id !== id) return draft\n return withProviders(draft, [...rows, { ...synthesized, ...patch }], id)\n }\n return withProviders(draft, rows.map((p, i) => (i === index ? { ...p, ...patch } : p)))\n}\n\n/** 选预置:连带把 baseUrl / model / mode 刷成该预置的推荐值。 */\nexport function pickPreset(draft: AsrVoiceConfig, id: string, presetId: string): AsrVoiceConfig {\n const preset = presetById(presetId)\n if (!preset) return patchProvider(draft, id, { preset: presetId })\n return patchProvider(draft, id, { preset: presetId, baseUrl: preset.baseUrl, model: preset.defaultModel, mode: preset.mode })\n}\n\n/** 新增一行供应商,返回新草稿与新行 id。 presetId 命中内置预置时按预置填充;未命中('custom')建一行空白自定义端点。 */\nexport function addProvider(draft: AsrVoiceConfig, presetId = DEFAULT_PRESET_ID, name = ''): { draft: AsrVoiceConfig; id: string } {\n const preset = presetById(presetId)\n const id = newProviderId()\n const row: CloudProviderConfig = preset\n ? { id, preset: preset.id, name: preset.label, baseUrl: preset.baseUrl, model: preset.defaultModel, mode: preset.mode }\n : { id, preset: 'custom', name, baseUrl: '', model: '', mode: 'auto' }\n return { draft: withProviders(draft, [...draft.asr.cloud.providers, row], id), id }\n}\n\n/** 删除一行供应商。 */\nexport function removeProvider(draft: AsrVoiceConfig, id: string): AsrVoiceConfig {\n return withProviders(draft, draft.asr.cloud.providers.filter((p) => p.id !== id))\n}\n\n/** 取「active 指向的供应商,缺省取第一行,全空合成 legacy 行」(草稿与运行时快照共用)。 */\nfunction pickActiveProvider(cloud: AsrVoiceConfig['asr']['cloud']): CloudProviderConfig {\n return cloud.providers.find((p) => p.id === cloud.active) ?? cloud.providers[0]\n ?? { id: 'legacy', preset: cloud.preset, name: '', baseUrl: cloud.baseUrl, model: cloud.model, mode: cloud.mode }\n}\n\n/** 草稿里当前生效的供应商(无行时按旧单配置合成一行 legacy,id 固定以便凭据引用稳定)。 */\nexport function draftActiveProvider(draft: AsrVoiceConfig): CloudProviderConfig {\n return pickActiveProvider(draft.asr.cloud)\n}\n\n/** 把 v0.1 旧单配置在**草稿里**落成一行 providers(id 固定 'legacy',凭据引用名随之 稳定)。宿主端本来就优先读 providers,所以这一步只影响编辑中的这份副本,不自动写回。 */\nexport function withLegacyMaterialized(draft: AsrVoiceConfig): AsrVoiceConfig {\n const cloud = draft.asr.cloud\n if (cloud.providers.length > 0 || cloud.baseUrl.trim() === '') return draft\n const row: CloudProviderConfig = {\n id: 'legacy', preset: cloud.preset, name: '', baseUrl: cloud.baseUrl, model: cloud.model, mode: cloud.mode,\n }\n return withProviders(draft, [row], 'legacy')\n}\n\n/** 某供应商的 API key 凭据引用名。 */\nexport function keyRefOf(p: KeyRefSource): string {\n return keyRefFor(p)\n}\n\n/** 保存草稿:只把真正改过的顶层段写回 host,然后**读回校验**。\n * `SettingsScope.set` 会把失败吞掉并重载宿主状态(promise 成功不代表落盘),所以\n * 判定标准只能是写完之后宿主那边到底剩什么——这也正是官方设置卡的做法。\n * @returns 未落盘的段名,undefined 表示全部落定。\n */\nexport async function writeDraft(draft: AsrVoiceConfig): Promise<ConfigSection | undefined> {\n const scope = voiceScope\n // 比对基准是**宿主真相**,不是本地快照:本函数会把草稿并进本地快照,用本地快照\n // 当基准会让「写回被吞掉后再点一次保存」算出零变更,于是静默报成功。\n const host = scope?.getSnapshot().value\n // realtime 必须在内:设置卡的实时对话组(引擎/TTS/VAD/快捷键…)也走本函数保存,\n // 漏了它整段改动只在本地快照生效,宿主回声一到就打回原形,重启后更是全丢。\n const changed = (['asr', 'optimize', 'language', 'behavior', 'realtime'] as const)\n .filter((key) => !jsonEqual(host?.[key] ?? config[key], draft[key]))\n if (scope === undefined) {\n // 没有宿主通道(settings 服务未挂载):本地快照仍然生效,只是重启后回到旧值。\n mergeHostValue(draft)\n announce()\n return undefined\n }\n for (const key of changed) await scope.set(key, draft[key])\n mergeHostValue(draft)\n announce()\n const resolved = scope.getSnapshot().value\n return resolved === undefined ? undefined : changed.find((key) => !jsonEqual(resolved[key], draft[key]))\n}\n\n// ── 密钥:只经 credentials 通道,值不回流到 config ──────────────────────\n\n/** 一个供应商的密钥配置态(写视图:只知道有没有、能不能写、来自哪)。 */\nexport interface KeyState {\n ref: string\n configured: boolean\n writable: boolean\n source: string\n /** 查询本身失败时的原始原因(null 表示查询成功)。 */\n failure: string | null\n}\n\n/** 解析一次凭据 describe 的「失败原因」({ok,value} 形状直接返回 error)。 */\nfunction describeFailure(response: CredentialsDescribeOutcome): string | null {\n if (response.ok) return null\n return response.error?.message ?? 'credential request rejected'\n}\n\n/** 查某供应商的密钥是否已配置。 */\nexport async function readKeyState(p: KeyRefSource): Promise<KeyState> {\n const ref = keyRefFor(p)\n if (credentialsApi === undefined) {\n return { ref, configured: false, writable: false, source: '', failure: 'credentials service unavailable' }\n }\n try {\n const response = await credentialsApi.describe([ref])\n const failure = describeFailure(response)\n if (failure !== null) return { ref, configured: false, writable: true, source: '', failure }\n const view = response.ok ? response.value[ref] : undefined\n return {\n ref,\n configured: view?.configured ?? false,\n writable: view?.writable ?? true,\n source: view?.source ?? '',\n failure: null,\n }\n } catch (error) {\n return { ref, configured: false, writable: true, source: '', failure: error instanceof Error ? error.message : String(error) }\n }\n}\n\n/** 写入或清除某供应商的密钥。\n * @param p - 供应商身份(决定引用名)。\n * @param value - 新密钥;空串表示清除。\n * @returns 原始失败原因,undefined 表示成功。\n */\nexport async function saveKey(p: KeyRefSource, value: string): Promise<string | undefined> {\n const ref = keyRefFor(p)\n if (credentialsApi === undefined) return 'credentials service unavailable'\n const key = value.trim()\n try {\n const response = key === '' ? await credentialsApi.unset(ref) : await credentialsApi.set(ref, key)\n if (!response.ok) return response.error?.message ?? 'credential request rejected'\n // 读回校验:只读来源(如环境变量)能拒绝落盘而不报错。\n const state = await readKeyState(p)\n if (key !== '' && !state.configured) return state.failure ?? `credential ${ref} did not persist`\n return undefined\n } catch (error) {\n return error instanceof Error ? error.message : String(error)\n }\n}\n\n/** 当前生效供应商是否已配到可调用云端 ASR 的程度(密钥不再在本地快照里,由服务端判定)。 */\nexport function cloudConfigured(): boolean {\n return activeCloudProvider().baseUrl.trim() !== ''\n}\n\n/** 运行时快照里当前生效的云端供应商(录音链路只关心「配好了没」)。 */\nfunction activeCloudProvider(): CloudProviderConfig {\n return pickActiveProvider(config.asr.cloud)\n}\n\n/** 生成供应商唯一 id。 */\nfunction newProviderId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n if (c?.randomUUID) return c.randomUUID()\n return `p-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n}\n","/** dsh-asr-voice — client 快捷键解析与匹配(跨平台)。 */\n\n/** 解析后的组合键规格。 */\nexport interface HotkeySpec {\n ctrl: boolean\n alt: boolean\n shift: boolean\n meta: boolean\n key: string\n}\n\n/** 无修饰键时是否放行(单键 hotkey):只允许 F 功能键(F1~F24)。\n * 字母/数字/符号/空格/回车/方向键等在文本输入里会打出字符或移动焦点,\n * 而 client 的监听挂在 window capture(不豁免输入框),命中后 preventDefault\n * 会把字符吞掉——单字母快捷键 = 打字劫持。F 键不产生字符(浏览器默认行为\n * 如刷新/全屏被 preventDefault 接管,是用户自选的配置),故保留。 */\nexport function bareKeyAllowed(key: string): boolean {\n return /^F([1-9]|1\\d|2[0-4])$/.test(key)\n}\n\n/** 把规范字符串(如 \"Ctrl+Shift+Space\")解析为规格;空/非法返回 null。 */\nexport function parseHotkey(spec: string): HotkeySpec | null {\n if (!spec || spec.trim() === '') return null\n const parts = spec.split('+').map((s) => s.trim()).filter((s) => s !== '')\n const out: HotkeySpec = { ctrl: false, alt: false, shift: false, meta: false, key: '' }\n for (const part of parts) {\n const p = part.toLowerCase()\n if (p === 'ctrl' || p === 'control') out.ctrl = true\n else if (p === 'alt' || p === 'option') out.alt = true\n else if (p === 'shift') out.shift = true\n else if (p === 'meta' || p === 'cmd' || p === 'command' || p === 'win' || p === 'super') out.meta = true\n else out.key = normalizeKey(part)\n }\n if (out.key === '') return null\n // 无修饰键时只放行 F 功能键(见 bareKeyAllowed):单字母/数字/符号/空格/回车/\n // 方向键会劫持打字。旧配置里误录的单键(如 \"A\")解析返回 null = 快捷键失效,\n // 宁可失效也不吞用户输入。\n if (!out.ctrl && !out.alt && !out.shift && !out.meta && !bareKeyAllowed(out.key)) return null\n return out\n}\n\n/** 主键规范化(与设置卡片录制器一致)。 */\n/** 主键规范化(设置卡片录制器共用):空格→Space、单字符大写、方向键/特殊键统一短名。 */\nexport function normalizeKey(key: string): string {\n if (key === ' ') return 'Space'\n if (key.length === 1) return key.toUpperCase()\n const map: Record<string, string> = {\n ArrowUp: 'Up', ArrowDown: 'Down', ArrowLeft: 'Left', ArrowRight: 'Right',\n Enter: 'Enter', Tab: 'Tab', Backspace: 'Backspace', Escape: 'Escape', Spacebar: 'Space',\n }\n return map[key] ?? key\n}\n\n/** 判断键盘事件是否命中规格。Ctrl 兼容 Cmd(macOS 上 Control/Command 都算)。 */\nexport function matchHotkey(e: KeyboardEvent, spec: HotkeySpec): boolean {\n if (e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift' || e.key === 'Meta') return false\n const ctrl = e.ctrlKey || e.metaKey\n const wantCtrl = spec.ctrl || spec.meta\n const ctrlOk = ctrl === wantCtrl\n const altOk = e.altKey === spec.alt\n const shiftOk = e.shiftKey === spec.shift\n // 录制器把 Cmd 记作 Ctrl(keyCombo 中 ctrlKey||metaKey → 'Ctrl'),\n // 所以 spec.ctrl=true 时须同时接受 Ctrl 和 Cmd,与注释「Ctrl 兼容 Cmd」一致。\n const metaOk = spec.ctrl ? true : e.metaKey === spec.meta\n return ctrlOk && altOk && shiftOk && metaOk && normalizeKey(e.key) === spec.key\n}\n","/** dsh-asr-voice — client 设置卡片(settings.plugin.item, key: 'asr-voice')。\n * 三步向导(① 识别方式 → ② 服务商 → ③ 密钥与自检)+ 默认折叠的「高级」。\n * 卡片只编辑一份本地草稿,按「保存」才过线(写回后读回校验,不信 promise);\n * API key 单独走 credentials 域,既不进草稿也不进浏览器 DOM。\n */\nimport * as react from 'react'\n// Type-only: pulls the ui-settings-plugins SlotMap merge (the settings.plugin.item card seat).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\nimport { Switch } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { CLOUD_PRESETS, presetById, REALTIME_PRESETS } from '../presets.ts'\nimport {\n addProvider, draftActiveProvider, keyRefOf, newDraft, patchProvider, pickPreset,\n readKeyState, removeProvider, saveKey, settingsWritable, subscribeConfig, withLanguage,\n withLegacyMaterialized, withProviders, withSection, writeDraft,\n type AsrVoiceConfig, type CloudProviderConfig, type ConfigSection, type KeyState,\n} from './config.ts'\nimport type { LocaleKey, LocaleT } from './locales.ts'\nimport { bareKeyAllowed, normalizeKey } from './hotkey.ts'\n\n/** 设置卡片 props(settings.plugin.item 注入 + 翻译函数)。 */\nexport interface SettingsCardProps {\n t: LocaleT\n}\n\n/** 卡片内的一条提示(idle / 进行中 / 成功 / 失败,统一渲染在动作行下方)。 */\ninterface Notice {\n kind: 'busy' | 'ok' | 'err'\n text: string\n}\n\n/** 模块级模型目录缓存(同会话 60s 内复用,避免重复拉取 /api/asr-voice/models)。 */\nlet modelsCache: DshProviderEntry[] | null = null\nlet modelsCacheAt = 0\nconst MODELS_CACHE_TTL_MS = 60_000\n\n/** 订阅配置变更,驱动重渲染。 */\nfunction useConfigVersion(): number {\n const [v, bump] = react.useReducer((x: number) => x + 1, 0)\n react.useEffect(() => subscribeConfig(bump), [])\n return v\n}\n\n/** 统一字段容器(垂直布局:label / control / hint,与官方 fields 一致)。\n * a11y:label 用 htmlFor 关联控件,id 由 useId 生成后经 render-prop 传给调用方\n * 挂到真实控件上;屏幕阅读器可读出字段名,点击标题聚焦控件。\n * control(ids) 收到 { controlId, labelId }:普通控件挂 controlId;\n * 组合控件(radio 组等)用 labelId 做 aria-labelledby 命名分组。 */\ninterface FieldIds { controlId: string; labelId: string }\nfunction Field({ title, desc, control }: { title: string; desc?: string | undefined; control: (ids: FieldIds) => react.ReactNode }): react.ReactElement {\n const controlId = react.useId()\n const labelId = react.useId()\n return (\n <div className=\"dshav-field-item\">\n <div className=\"dshav-field-head\">\n <label className=\"dshav-field-label\" id={labelId} htmlFor={controlId}>{title}</label>\n </div>\n <div className=\"dshav-field-control\">{control({ controlId, labelId })}</div>\n {desc ? <p className=\"dshav-field-hint\">{desc}</p> : null}\n </div>\n )\n}\n\n/** 开关字段:官方 Switch 与标题同行(对齐官方设置面板控件),desc 作 hint。 */\nfunction ToggleRow({ title, desc, checked, onChange, disabled }: { title: string; desc?: string; checked: boolean; onChange: () => void; disabled?: boolean }): react.ReactElement {\n return (\n <div className={disabled ? 'dshav-field-item dshav-field-disabled' : 'dshav-field-item'}>\n <div className=\"dshav-toggle\">\n <Switch checked={checked} onChange={onChange} label={title} disabled={disabled === true} />\n <span>{title}</span>\n </div>\n {desc ? <p className=\"dshav-field-hint\">{desc}</p> : null}\n </div>\n )\n}\n\n/** 数值输入字段(min/max 与 host schema 的同一组约束)。\n * 本地 string state 允许逐字输入 \"0.\" 等中间态,blur 时 clamp 回写。 */\nfunction NumberRow({ title, desc, value, onChange, min, max, step = 1 }: {\n title: string\n desc?: string\n value: number\n onChange: (v: number) => void\n min: number\n max: number\n step?: number\n}): react.ReactElement {\n const [editValue, setEditValue] = react.useState<string>(String(value))\n // value prop 从外部变化(清空/重置)时同步本地,但编辑中不覆盖。\n const [isFocused, setFocused] = react.useState(false)\n react.useEffect(() => {\n if (!isFocused) setEditValue(String(value))\n }, [value, isFocused])\n const commit = react.useCallback((): void => {\n const n = Number(editValue)\n if (editValue === '' || !Number.isFinite(n)) {\n setEditValue(String(value))\n return\n }\n const clamped = Math.min(max, Math.max(min, n))\n setEditValue(String(clamped))\n onChange(clamped)\n }, [editValue, value, min, max, onChange])\n return (\n <Field\n title={title}\n desc={desc}\n control={({ controlId }) => (\n <div className=\"dshav-field\">\n <input\n id={controlId}\n type=\"number\"\n value={editValue}\n min={min}\n max={max}\n step={step}\n onFocus={() => setFocused(true)}\n onBlur={() => { setFocused(false); commit() }}\n onChange={(e: react.ChangeEvent<HTMLInputElement>) => {\n setEditValue(e.target.value)\n // 合法且有限时即刻回传(如其他 UI 联动),非法值保留在本地。\n if (e.target.value !== '' && /^-?\\d*\\.?\\d*$/.test(e.target.value)) {\n const n = Number(e.target.value)\n if (Number.isFinite(n)) onChange(Math.min(max, Math.max(min, n)))\n }\n }}\n />\n </div>\n )}\n />\n )\n}\n\n/** 文本输入字段。 */\nfunction TextRow({ title, desc, value, onChange, type = 'text', placeholder }: {\n title: string\n desc?: string\n value: string\n onChange: (v: string) => void\n type?: 'text' | 'password'\n placeholder?: string\n}): react.ReactElement {\n return (\n <Field\n title={title}\n desc={desc}\n control={({ controlId }) => (\n <div className=\"dshav-field\">\n <input\n id={controlId}\n type={type}\n value={value}\n placeholder={placeholder ?? ''}\n spellCheck={false}\n autoComplete=\"off\"\n onChange={(e: react.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}\n />\n </div>\n )}\n />\n )\n}\n\n/** 选择字段。 */\nfunction SelectRow({ title, desc, value, options, onChange }: {\n title: string\n desc?: string\n value: string\n options: { value: string; label: string }[]\n onChange: (v: string) => void\n}): react.ReactElement {\n return (\n <Field\n title={title}\n desc={desc}\n control={({ controlId }) => (\n <div className=\"dshav-field\">\n <select id={controlId} value={value} onChange={(e: react.ChangeEvent<HTMLSelectElement>) => onChange(e.target.value)}>\n {options.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}\n </select>\n </div>\n )}\n />\n )\n}\n\n/** 步骤标题:① ② ③ + 一句话说明。 */\nfunction Step({ index, title, desc, children }: { index: string; title: string; desc?: string | undefined; children: react.ReactNode }): react.ReactElement {\n return (\n <div className=\"dshav-step\">\n <div className=\"dshav-step-head\">\n <span className=\"dshav-step-index\">{index}</span>\n <span className=\"dshav-step-title\">{title}</span>\n </div>\n {desc ? <p className=\"dshav-field-hint\">{desc}</p> : null}\n {children}\n </div>\n )\n}\n\n/** 一行 chip 单选(点即选中并联动,替代原先层层条件展开的下拉)。 */\nfunction Chips({ items, label, t }: {\n items: { key: string; label: string; selected: boolean; onSelect: () => void; disabled?: boolean }[]\n label: string\n t: LocaleT\n}): react.ReactElement {\n return (\n <div className=\"dshav-chips\" role=\"radiogroup\" aria-label={label}>\n {items.map((item) => (\n <button\n key={item.key}\n type=\"button\"\n role=\"radio\"\n aria-checked={item.selected}\n className=\"dshav-chip\"\n data-selected={item.selected ? 'true' : undefined}\n disabled={item.disabled ?? false}\n onClick={item.onSelect}\n >\n {item.label}\n </button>\n ))}\n {items.length === 0 ? <span className=\"dshav-field-hint\">{t('providersEmpty')}</span> : null}\n </div>\n )\n}\n\n/** 快捷键录制器:点击后捕获下一组组合键;支持清除。 */\nfunction HotkeyRecorder({ inputId, value, onChange, t }: { inputId: string; value: string; onChange: (v: string) => void; t: LocaleT }): react.ReactElement {\n const [arming, setArming] = react.useState(false)\n\n const handleKeyDown = (e: react.KeyboardEvent<HTMLInputElement>): void => {\n if (!arming) return\n e.preventDefault()\n e.stopPropagation()\n const combo = keyCombo(e)\n if (combo !== '') {\n onChange(combo)\n setArming(false)\n } else if (e.key === 'Escape') {\n setArming(false)\n }\n }\n\n return (\n <div className=\"dshav-field\">\n <input\n id={inputId}\n type=\"text\"\n readOnly\n placeholder={t('hotkeyPlaceholder')}\n value={arming ? '' : value}\n onFocus={() => setArming(true)}\n onBlur={() => setArming(false)}\n onKeyDown={handleKeyDown}\n />\n <button\n type=\"button\"\n className=\"dshav-button dshav-button-outline dshav-button-sm\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => onChange('')}\n >\n {t('hotkeyClear')}\n </button>\n </div>\n )\n}\n\n/** 把键盘事件转成规范组合键字符串(修饰键 + 主键,跨平台)。\n * 无修饰键时只录 F 功能键:单字母/数字/符号/空格/回车/方向键会劫持输入\n * (parseHotkey 同口径拒绝,两端一致)。 */\nfunction keyCombo(e: react.KeyboardEvent): string {\n const parts: string[] = []\n if (e.ctrlKey || e.metaKey) parts.push('Ctrl')\n if (e.altKey) parts.push('Alt')\n if (e.shiftKey) parts.push('Shift')\n if (e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift' || e.key === 'Meta' || e.key === 'Escape') return ''\n if (parts.length === 0 && !bareKeyAllowed(e.key)) return ''\n parts.push(normalizeKey(e.key))\n return parts.join('+')\n}\n\n/** DSH 已配置模型条目(来自 /api/asr-voice/models)。 */\ninterface DshModelEntry { id: string; name: string }\ninterface DshProviderEntry { provider: string; name: string; models: DshModelEntry[] }\n\n/** 优化模型选择器:从 DSH 已配置模型列表选择(留空 = 当前所选 LLM)。 */\nfunction ModelPicker({ t, provider, model, onProvider, onModel }: {\n t: LocaleT\n provider: string\n model: string\n onProvider: (v: string) => void\n onModel: (v: string) => void\n}): react.ReactElement {\n const [providers, setProviders] = react.useState<DshProviderEntry[] | null>(null)\n const [status, setStatus] = react.useState<'loading' | 'ok' | 'err'>('loading')\n // 模块级缓存:模型目录同一会话内不频繁变化,卡片重复挂载/刷新时避免重复拉取。\n const load = react.useCallback(async () => {\n if (modelsCache !== null && Date.now() - modelsCacheAt < MODELS_CACHE_TTL_MS) {\n setProviders(modelsCache)\n setStatus('ok')\n return\n }\n setStatus('loading')\n try {\n // 超时兜底:host /models 链路挂起时 UI 不能永久 loading(catch → err 提示)。\n const res = await fetch('/api/asr-voice/models', { cache: 'no-store', signal: AbortSignal.timeout(30_000) })\n const data = (await res.json().catch(() => ({}))) as { ok?: boolean; providers?: DshProviderEntry[]; reason?: string }\n if (!res.ok || data.ok !== true || data.providers === undefined) throw new Error(data.reason || 'load failed')\n modelsCache = data.providers\n modelsCacheAt = Date.now()\n setProviders(data.providers)\n } catch {\n setStatus('err')\n }\n }, [])\n react.useEffect(() => { void load() }, [load])\n\n const modelOptions = providers?.find((p) => p.provider === provider)?.models ?? []\n\n return (\n <>\n <Field\n title={t('llmProviderLabel')}\n control={({ controlId }) => (\n <div className=\"dshav-field\">\n <select id={controlId} value={provider} onChange={(e: react.ChangeEvent<HTMLSelectElement>) => onProvider(e.target.value)}>\n <option value=\"\">{t('llmCurrentDefault')}</option>\n {(providers ?? []).map((p) => <option key={p.provider} value={p.provider}>{p.name}</option>)}\n </select>\n </div>\n )}\n />\n <Field\n title={t('llmModelLabel')}\n control={({ controlId }) => (\n <div className=\"dshav-field\">\n <select\n id={controlId}\n value={model}\n disabled={provider === ''}\n onChange={(e: react.ChangeEvent<HTMLSelectElement>) => onModel(e.target.value)}\n >\n <option value=\"\">{t('llmCurrentDefault')}</option>\n {modelOptions.map((m) => <option key={m.id} value={m.id}>{m.name}</option>)}\n </select>\n </div>\n )}\n />\n {status === 'err' ? <p className=\"dshav-field-hint\">{t('loadFailed')}</p> : null}\n {status === 'ok' && provider !== '' && modelOptions.length === 0 ? <p className=\"dshav-field-hint\">{t('llmModelsEmpty')}</p> : null}\n </>\n )\n}\n\n/** 用量统计展示(/api/asr-voice/stats,低优先级)。 */\nfunction UsageStats({ t }: { t: LocaleT }): react.ReactElement {\n const [stats, setStats] = react.useState<{ count: number; chars: number; lastAt: number | null; lastProvider: string } | null>(null)\n react.useEffect(() => {\n let live = true\n const load = async (): Promise<void> => {\n try {\n const res = await fetch('/api/asr-voice/stats', { cache: 'no-store', signal: AbortSignal.timeout(10_000) })\n const data = (await res.json().catch(() => ({}))) as { ok?: boolean; stats?: typeof stats }\n if (live && res.ok && data.ok === true && data.stats) setStats(data.stats)\n } catch { /* ignore */ }\n }\n void load()\n // 只在页面可见时轮询:标签页切走/隐藏后暂停,回来立即补一次。\n const tick = (): void => { if (!document.hidden) void load() }\n const timer = window.setInterval(tick, 5000)\n const onVisible = (): void => { if (!document.hidden) void load() }\n document.addEventListener('visibilitychange', onVisible)\n return () => { live = false; window.clearInterval(timer); document.removeEventListener('visibilitychange', onVisible) }\n }, [])\n if (stats === null) return <p className=\"dshav-field-hint\">{t('statsEmpty')}</p>\n const lastAt = stats.lastAt ? new Date(stats.lastAt).toLocaleTimeString() : null\n return (\n <div className=\"dshav-field-item\">\n <div className=\"dshav-field-head\"><span className=\"dshav-field-label\">{t('statsTitle')}</span></div>\n <p className=\"dshav-field-hint\">\n {t('statsCount', { n: stats.count })} · {t('statsChars', { n: stats.chars })}\n {stats.count > 0 && lastAt ? ` · ${t('statsLastAt', { time: lastAt })}` : ''}\n {stats.lastProvider ? ` · ${stats.lastProvider}` : ''}\n </p>\n </div>\n )\n}\n\n\n/** 段名 → 分组标题(保存失败时告诉用户到底是哪一段没落盘)。 */\nconst SECTION_TITLE: Record<ConfigSection, LocaleKey> = {\n asr: 'groupAsr', optimize: 'groupOptimize', language: 'languageLabel', behavior: 'groupBehavior', realtime: 'groupRealtime',\n}\n\n/** 设置卡片:外层折叠与其他插件卡一致(header + chevron + 条件 body)。 */\nexport function VoiceSettingsCard({ t }: SettingsCardProps): react.ReactElement {\n const version = useConfigVersion()\n const [open, setOpen] = react.useState(false)\n const [showAdvanced, setShowAdvanced] = react.useState(false)\n const [draft, setDraft] = react.useState<AsrVoiceConfig>(() => withLegacyMaterialized(newDraft()))\n const [dirty, setDirty] = react.useState(false)\n const [notice, setNotice] = react.useState<Notice | null>(null)\n const [keyInput, setKeyInput] = react.useState('')\n const [keyBusy, setKeyBusy] = react.useState(false)\n const [keyState, setKeyState] = react.useState<KeyState | null>(null)\n const [tested, setTested] = react.useState<{ models: DshModelEntry[] } | null>(null)\n const [testing, setTesting] = react.useState(false)\n\n const writable = settingsWritable()\n const provider = draftActiveProvider(draft)\n const ref = keyRefOf(provider)\n const cloudMode = draft.asr.provider !== 'browser'\n\n const edit = (fn: (current: AsrVoiceConfig) => AsrVoiceConfig): void => {\n setDraft(fn)\n setDirty(true)\n setNotice(null)\n }\n\n // 宿主快照更新且本地没有未保存编辑 → 草稿跟随权威源。\n react.useEffect(() => {\n if (!dirty) setDraft(withLegacyMaterialized(newDraft()))\n }, [version, dirty])\n\n // 密钥态按引用名查(provider 对象每次编辑都是新的,按对象依赖会疯狂发请求)。\n react.useEffect(() => {\n let live = true\n setKeyState(null)\n void readKeyState(provider).then((state) => { if (live) setKeyState(state) })\n return () => { live = false }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ref])\n\n /** 写回草稿;返回是否全部落盘(读回校验失败会给出段名)。 */\n const commit = async (): Promise<boolean> => {\n if (!dirty) return true\n setNotice({ kind: 'busy', text: t('savingHint') })\n const failed = await writeDraft(draft)\n if (failed !== undefined) {\n setNotice({ kind: 'err', text: `${t('saveNotApplied', { section: t(SECTION_TITLE[failed]) })}` })\n return false\n }\n setDirty(false)\n setNotice({ kind: 'ok', text: t('savedHint') })\n return true\n }\n\n /** 保存密钥(先落草稿,行不存在时不能往对应引用里写 key)。 */\n const commitKey = async (): Promise<void> => {\n setKeyBusy(true)\n if (dirty && !(await commit())) { setKeyBusy(false); return }\n const reason = await saveKey(provider, keyInput)\n setKeyBusy(false)\n if (reason !== undefined) {\n setNotice({ kind: 'err', text: `${t('keySaveFailed')}:${reason}` })\n return\n }\n setKeyInput('')\n setKeyState(await readKeyState(provider))\n setNotice({ kind: 'ok', text: t('keySavedHint', { ref }) })\n }\n\n/** 测试连接 = 用该供应商列一次模型。 选它而不是录一段音:不用麦克风、不打扰人,且一次性验掉 key + baseUrl + 网络三件事, 返回的模型还能直接填进高级里的模型选择。 */\n const testConnection = async (): Promise<void> => {\n setTesting(true)\n if (dirty && !(await commit())) { setTesting(false); return }\n try {\n const res = await fetch(`/api/asr-voice/asr-models?providerId=${encodeURIComponent(provider.id)}`, { cache: 'no-store', signal: AbortSignal.timeout(30_000) })\n const data = (await res.json().catch(() => ({}))) as { ok?: boolean; models?: DshModelEntry[]; reason?: string }\n if (!res.ok || data.ok !== true || !Array.isArray(data.models)) throw new Error(data.reason ?? 'request failed')\n if (data.models.length === 0) {\n setNotice({ kind: 'err', text: t('fetchModelsEmpty') })\n } else {\n setTested({ models: data.models })\n setNotice({ kind: 'ok', text: t('testOk', { n: data.models.length }) })\n }\n } catch (error) {\n setNotice({ kind: 'err', text: `${t('testFail')}:${error instanceof Error ? error.message : String(error)}` })\n } finally {\n setTesting(false)\n }\n }\n\n /** 选预置:已有对应行就切过去,否则新建一行并设为当前。 */\n const choosePreset = (presetId: string): void => {\n edit((current) => {\n const existing = current.asr.cloud.providers.find((p) => p.preset === presetId)\n if (existing === undefined) return addProvider(current, presetId).draft\n // 已有行只切当前,不覆盖用户改过的端点;Base URL 空了才补回预置值。\n const filled = existing.baseUrl.trim() === '' ? pickPreset(current, existing.id, presetId) : current\n return withProviders(filled, filled.asr.cloud.providers, existing.id)\n })\n setTested(null)\n }\n\n const presetChips = CLOUD_PRESETS.map((preset) => ({\n key: preset.id,\n label: preset.label,\n selected: provider.preset === preset.id,\n onSelect: () => choosePreset(preset.id),\n disabled: !writable,\n }))\n const customChips = draft.asr.cloud.providers\n .filter((p) => presetById(p.preset) === undefined)\n .map((p) => ({\n key: `row-${p.id}`,\n label: p.name.trim() === '' ? t('cloudPresetCustom') : p.name,\n selected: p.id === provider.id,\n onSelect: () => { edit((current) => withProviders(current, current.asr.cloud.providers, p.id)); setTested(null) },\n disabled: !writable,\n }))\n const addChip = {\n key: 'add-custom',\n label: t('addProvider'),\n selected: false,\n onSelect: () => { edit((current) => addProvider(current, 'custom', t('cloudPresetCustom')).draft) },\n disabled: !writable,\n }\n\n const engineChips = [\n { key: 'auto', label: t('engineAuto'), selected: draft.asr.provider === 'auto', onSelect: () => edit((c) => withSection(c, 'asr', { provider: 'auto' as const })), disabled: !writable },\n { key: 'browser', label: t('engineBrowser'), selected: draft.asr.provider === 'browser', onSelect: () => edit((c) => withSection(c, 'asr', { provider: 'browser' as const })), disabled: !writable },\n { key: 'cloud', label: t('engineCloud'), selected: draft.asr.provider === 'cloud', onSelect: () => edit((c) => withSection(c, 'asr', { provider: 'cloud' as const })), disabled: !writable },\n ]\n\n const needKey = keyState !== null && !keyState.configured && keyState.failure === null\n const keyNameMissing = presetById(provider.preset) === undefined && provider.name.trim() === ''\n\n return (\n <li className={'dshav-card' + (open ? ' dshav-card-open' : '')}>\n <button type=\"button\" className=\"dshav-header\" aria-expanded={open} onClick={() => setOpen(!open)}>\n <span className=\"dshav-headtext\">\n <span className=\"dshav-name\">{t('cardTitle')}</span>\n <span className=\"dshav-desc\">{t('cardCopy')}</span>\n </span>\n <svg className={'dshav-chevron' + (open ? ' dshav-open' : '')} width={16} height={16} viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M3.5 5.75 8 10.25l4.5-4.5\" stroke=\"currentColor\" strokeWidth={1.5} strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </button>\n {open ? (\n <div className=\"dshav-body\">\n {!writable ? <p className=\"dshav-field-hint\" role=\"alert\">{t('readOnlyDoc')}</p> : null}\n {cloudMode && provider.baseUrl.trim() === '' ? (\n <p className=\"dshav-field-hint\">{t('howTo')}</p>\n ) : null}\n\n <div className=\"dshav-group\">\n <Step index=\"①\" title={t('stepEngineTitle')} desc={t(draft.asr.provider === 'browser' ? 'engineHintBrowser' : draft.asr.provider === 'cloud' ? 'engineHintCloud' : 'engineHintAuto')}>\n <Chips items={engineChips} label={t('stepEngineTitle')} t={t} />\n </Step>\n </div>\n\n {cloudMode ? (\n <>\n <div className=\"dshav-group\">\n <Step index=\"②\" title={t('stepProviderTitle')} desc={t('stepProviderHint')}>\n <Chips items={[...presetChips, ...customChips, addChip]} label={t('stepProviderTitle')} t={t} />\n {presetById(provider.preset) !== undefined ? (\n <p className=\"dshav-field-hint\">{presetById(provider.preset)?.hint}</p>\n ) : null}\n </Step>\n </div>\n\n <div className=\"dshav-group\">\n <Step index=\"③\" title={t('stepKeyTitle')} desc={keyNameMissing ? t('keyNameNeeded') : undefined}>\n {keyState === null ? (\n <p className=\"dshav-field-hint\">{t('keyChecking')}</p>\n ) : keyState.failure !== null ? (\n <p className=\"dshav-field-hint\" role=\"alert\">{t('keyQueryFailed')}:{keyState.failure}</p>\n ) : keyState.configured ? (\n <p className=\"dshav-ok-line\">\n ✓ {t('keyConfigured', { ref })}\n {keyState.source !== '' ? <span className=\"dshav-field-hint\"> · {keyState.source}</span> : null}\n </p>\n ) : (\n <p className=\"dshav-field-hint\">{t('keyNeedsValue', { ref })}</p>\n )}\n {!keyNameMissing ? (\n <div className=\"dshav-field\">\n <input\n type=\"password\"\n aria-label={t('stepKeyTitle')}\n value={keyInput}\n placeholder={keyState?.configured === true ? t('keyKeepPlaceholder') : t('keyPastePlaceholder')}\n spellCheck={false}\n autoComplete=\"off\"\n disabled={!writable || keyState?.writable === false}\n onChange={(e: react.ChangeEvent<HTMLInputElement>) => setKeyInput(e.target.value)}\n />\n <button\n type=\"button\"\n className=\"dshav-button dshav-button-outline dshav-button-sm\"\n disabled={keyBusy || !writable || keyInput.trim() === ''}\n onClick={() => { void commitKey() }}\n >\n {keyBusy ? t('keySaving') : t('keySave')}\n </button>\n </div>\n ) : null}\n {needKey && keyInput.trim() === '' ? <p className=\"dshav-field-hint\">{t('keyKeepHint')}</p> : null}\n <div className=\"dshav-field\">\n <button\n type=\"button\"\n className=\"dshav-button dshav-button-primary dshav-button-sm\"\n disabled={testing || !writable || provider.baseUrl.trim() === ''}\n onClick={() => { void testConnection() }}\n >\n {testing ? t('testBusy') : dirty ? t('testAndSave') : t('testConnection')}\n </button>\n </div>\n </Step>\n </div>\n </>\n ) : null}\n\n <div className=\"dshav-group\">\n <div className=\"dshav-actions\">\n <p className=\"dshav-status\" aria-live=\"polite\" data-kind={notice === null ? undefined : notice.kind === 'busy' ? undefined : notice.kind === 'ok' ? 'ok' : 'err'}>{notice?.text ?? ''}</p>\n {dirty ? (\n <button type=\"button\" className=\"dshav-button dshav-button-outline dshav-button-sm\" disabled={!writable} onClick={() => { setDraft(withLegacyMaterialized(newDraft())); setDirty(false); setNotice(null) }}>\n {t('discard')}\n </button>\n ) : null}\n <button type=\"button\" className=\"dshav-button dshav-button-primary dshav-button-sm\" disabled={!dirty || !writable} onClick={() => { void commit() }}>\n {t('save')}\n </button>\n </div>\n {dirty ? <p className=\"dshav-field-hint\">{t('unsavedHint')}</p> : null}\n </div>\n\n <div className=\"dshav-group\">\n <button type=\"button\" className=\"dshav-advanced-toggle\" aria-expanded={showAdvanced} onClick={() => setShowAdvanced(!showAdvanced)}>\n <span>{t('advancedTitle')}</span>\n <span className=\"dshav-field-hint\">{showAdvanced ? t('advancedCollapse') : t('advancedHint')}</span>\n </button>\n {showAdvanced ? (\n <div className=\"dshav-stack\">\n {cloudMode ? (\n <>\n <TextRow\n title={t('providerNameLabel')}\n desc={t('providerNameDesc', { ref })}\n value={provider.name}\n onChange={(v) => { edit((c) => patchProvider(c, provider.id, { name: v })); setTested(null) }}\n />\n <TextRow\n title={t('cloudBaseUrlLabel')}\n desc={t('cloudBaseUrlDesc')}\n value={provider.baseUrl}\n onChange={(v) => { edit((c) => patchProvider(c, provider.id, { baseUrl: v })); setTested(null) }}\n />\n {tested === null ? (\n <TextRow title={t('cloudModelLabel')} desc={t('cloudModelDesc')} value={provider.model} onChange={(v) => edit((c) => patchProvider(c, provider.id, { model: v }))} />\n ) : (\n <SelectRow\n title={t('cloudModelLabel')}\n desc={t('cloudModelPicked')}\n value={provider.model}\n options={[{ value: provider.model, label: provider.model === '' ? t('fetchModelsPick') : provider.model }, ...tested.models.map((m) => ({ value: m.id, label: m.name }))]}\n onChange={(v) => edit((c) => patchProvider(c, provider.id, { model: v }))}\n />\n )}\n <SelectRow\n title={t('cloudModeLabel')}\n desc={t('cloudModeDesc')}\n value={provider.mode}\n options={[\n { value: 'auto', label: t('cloudModeAuto') },\n { value: 'transcriptions', label: t('cloudModeTranscriptions') },\n { value: 'chat', label: t('cloudModeChat') },\n ]}\n onChange={(v) => edit((c) => patchProvider(c, provider.id, { mode: v }))}\n />\n {draft.asr.cloud.providers.length > 1 ? (\n <Field\n title={t('providerListLabel')}\n desc={t('providerListDesc')}\n control={({ controlId, labelId }) => (\n <div className=\"dshav-provider-list\" id={controlId} role=\"radiogroup\" aria-labelledby={labelId}>\n {draft.asr.cloud.providers.map((p) => (\n <div className=\"dshav-provider-row\" key={p.id}>\n <label className=\"dshav-toggle\">\n <input\n type=\"radio\"\n name=\"dshav-active-provider\"\n checked={p.id === draft.asr.cloud.active}\n disabled={!writable}\n onChange={() => edit((c) => withProviders(c, c.asr.cloud.providers, p.id))}\n />\n <span>{rowLabel(p, t)}</span>\n </label>\n <button\n type=\"button\"\n className=\"dshav-button dshav-button-outline dshav-button-sm\"\n disabled={!writable}\n onClick={() => edit((c) => removeProvider(c, p.id))}\n >\n {t('removeProvider')}\n </button>\n </div>\n ))}\n </div>\n )}\n />\n ) : null}\n </>\n ) : null}\n\n <SelectRow\n title={t('languageLabel')}\n value={draft.language}\n options={[\n { value: 'auto', label: t('languageAuto') },\n { value: 'zh-CN', label: '中文(简体)' },\n { value: 'en-US', label: 'English (US)' },\n ]}\n onChange={(v) => edit((c) => withLanguage(c, v))}\n />\n\n <span className=\"dshav-groupTitle\">{t('groupOptimize')}</span>\n <SelectRow\n title={t('optimizeModeLabel')}\n value={draft.optimize.mode}\n options={[\n { value: 'heuristic', label: t('optimizeHeuristic') },\n { value: 'llm', label: t('optimizeLlm') },\n ]}\n onChange={(v) => edit((c) => withSection(c, 'optimize', { mode: v === 'llm' ? 'llm' : 'heuristic' }))}\n />\n {draft.optimize.mode === 'llm' ? (\n <div className=\"dshav-stack\">\n <p className=\"dshav-field-hint\">{t('llmDefaultHint')}</p>\n <ModelPicker\n t={t}\n provider={draft.optimize.llm.provider}\n model={draft.optimize.llm.model}\n onProvider={(v) => edit((c) => withSection(c, 'optimize', { llm: { provider: v, model: '' } }))}\n onModel={(v) => edit((c) => withSection(c, 'optimize', { llm: { ...c.optimize.llm, model: v } }))}\n />\n <ToggleRow\n title={t('optimizePreviewLabel')}\n desc={t('optimizePreviewDesc')}\n checked={draft.optimize.preview}\n onChange={() => edit((c) => withSection(c, 'optimize', { preview: !c.optimize.preview }))}\n />\n </div>\n ) : null}\n\n <span className=\"dshav-groupTitle\">{t('groupBehavior')}</span>\n <ToggleRow title={t('autoSendLabel')} desc={t('autoSendDesc')} checked={draft.behavior.autoSend} onChange={() => edit((c) => withSection(c, 'behavior', { autoSend: !c.behavior.autoSend }))} />\n <ToggleRow title={t('silenceStopLabel')} desc={t('silenceStopDesc')} checked={draft.behavior.silenceStop} onChange={() => edit((c) => withSection(c, 'behavior', { silenceStop: !c.behavior.silenceStop }))} />\n <ToggleRow title={t('holdToTalkLabel')} desc={t('holdToTalkDesc')} checked={draft.behavior.holdToTalk} onChange={() => edit((c) => withSection(c, 'behavior', { holdToTalk: !c.behavior.holdToTalk }))} />\n <SelectRow\n title={t('textModeLabel')}\n desc={t('textModeDesc')}\n value={draft.behavior.textMode}\n options={[\n { value: 'replace', label: t('textModeReplace') },\n { value: 'append', label: t('textModeAppend') },\n ]}\n onChange={(v) => edit((c) => withSection(c, 'behavior', { textMode: v === 'append' ? 'append' : 'replace' }))}\n />\n <ToggleRow title={t('copyToClipboardLabel')} desc={t('copyToClipboardDesc')} checked={draft.behavior.copyToClipboard} onChange={() => edit((c) => withSection(c, 'behavior', { copyToClipboard: !c.behavior.copyToClipboard }))} />\n <Field title={t('hotkeyLabel')} desc={t('hotkeyDesc')} control={({ controlId }) => <HotkeyRecorder inputId={controlId} value={draft.behavior.hotkey} onChange={(v) => edit((c) => withSection(c, 'behavior', { hotkey: v }))} t={t} />} />\n <NumberRow title={t('maxRecordMsLabel')} desc={t('maxRecordMsDesc')} value={draft.behavior.maxRecordMs} min={5_000} max={600_000} step={1_000} onChange={(v) => edit((c) => withSection(c, 'behavior', { maxRecordMs: v }))} />\n <NumberRow title={t('silenceMsLabel')} desc={t('silenceMsDesc')} value={draft.behavior.silenceMs} min={200} max={60_000} step={100} onChange={(v) => edit((c) => withSection(c, 'behavior', { silenceMs: v }))} />\n <NumberRow title={t('silenceRmsLabel')} desc={t('silenceRmsDesc')} value={draft.behavior.silenceRms} min={0} max={1} step={0.005} onChange={(v) => edit((c) => withSection(c, 'behavior', { silenceRms: v }))} />\n\n <span className=\"dshav-groupTitle\">{t('groupRealtime')}</span>\n <ToggleRow title={t('realtimeEnableLabel')} desc={t('realtimeEnableDesc')} checked={draft.realtime.enabled} onChange={() => edit((c) => withSection(c, 'realtime', { enabled: !c.realtime.enabled }))} />\n <SelectRow\n title={t('realtimeEngineLabel')}\n desc={t('realtimeEngineDesc')}\n value={draft.realtime.engine}\n options={[\n { value: 'browser', label: t('realtimeEngineBrowser') },\n { value: 'segmented', label: t('realtimeEngineSegmented') },\n { value: 'cloud', label: t('realtimeEngineCloud') },\n ]}\n onChange={(v) => edit((c) => withSection(c, 'realtime', { engine: v === 'segmented' ? 'segmented' : v === 'cloud' ? 'cloud' : 'browser' }))}\n />\n {draft.realtime.engine === 'cloud' && (\n <SelectRow\n title={t('realtimeProviderLabel')}\n desc={t('realtimeProviderDesc')}\n value={draft.realtime.provider}\n options={REALTIME_PRESETS.map((p) => ({ value: p.id, label: p.label }))}\n onChange={(v) => edit((c) => withSection(c, 'realtime', { provider: v }))}\n />\n )}\n <SelectRow\n title={t('realtimeTtsLabel')}\n desc={t('realtimeTtsDesc')}\n value={draft.realtime.tts}\n options={[\n { value: 'browser', label: t('realtimeTtsBrowser') },\n { value: 'cloud', label: t('realtimeTtsCloud') },\n { value: 'off', label: t('realtimeTtsOff') },\n ]}\n onChange={(v) => edit((c) => withSection(c, 'realtime', { tts: v === 'off' ? 'off' : v === 'cloud' ? 'cloud' : 'browser' }))}\n />\n {draft.realtime.tts === 'cloud' && (\n <TextRow title={t('realtimeTtsVoiceLabel')} desc={t('realtimeTtsVoiceDesc')} value={draft.realtime.ttsVoice} onChange={(v) => edit((c) => withSection(c, 'realtime', { ttsVoice: v }))} />\n )}\n <Field title={t('realtimeHotkeyLabel')} desc={t('realtimeHotkeyDesc')} control={({ controlId }) => <HotkeyRecorder inputId={controlId} value={draft.realtime.hotkey} onChange={(v) => edit((c) => withSection(c, 'realtime', { hotkey: v }))} t={t} />} />\n <ToggleRow title={t('bargeInLabel')} desc={t('bargeInDesc')} checked={draft.realtime.bargeIn && draft.realtime.engine === 'segmented'} onChange={() => edit((c) => withSection(c, 'realtime', { bargeIn: !c.realtime.bargeIn }))} disabled={draft.realtime.engine !== 'segmented'} />\n <NumberRow title={t('realtimeSettleMsLabel')} desc={t('realtimeSettleMsDesc')} value={draft.realtime.turn.settleMs} min={200} max={10_000} step={100} onChange={(v) => edit((c) => withSection(c, 'realtime', { turn: { ...c.realtime.turn, settleMs: v } }))} />\n <NumberRow title={t('realtimeTailMsLabel')} desc={t('realtimeTailMsDesc')} value={draft.realtime.turn.tailMs} min={0} max={5_000} step={100} onChange={(v) => edit((c) => withSection(c, 'realtime', { turn: { ...c.realtime.turn, tailMs: v } }))} />\n <ToggleRow title={t('vadRmsAutoLabel')} desc={t('vadRmsAutoDesc')} checked={draft.realtime.vad.rmsAuto} onChange={() => edit((c) => withSection(c, 'realtime', { vad: { ...c.realtime.vad, rmsAuto: !c.realtime.vad.rmsAuto } }))} />\n <NumberRow title={t('vadFrameMsLabel')} desc={t('vadFrameMsDesc')} value={draft.realtime.vad.frameMs} min={10} max={500} step={10} onChange={(v) => edit((c) => withSection(c, 'realtime', { vad: { ...c.realtime.vad, frameMs: v } }))} />\n <NumberRow title={t('vadRmsLabel')} desc={t('vadRmsDesc')} value={draft.realtime.vad.rms} min={0} max={1} step={0.005} onChange={(v) => edit((c) => withSection(c, 'realtime', { vad: { ...c.realtime.vad, rms: v } }))} />\n <NumberRow title={t('vadSilenceMsLabel')} desc={t('vadSilenceMsDesc')} value={draft.realtime.vad.silenceMs} min={200} max={5_000} step={100} onChange={(v) => edit((c) => withSection(c, 'realtime', { vad: { ...c.realtime.vad, silenceMs: v } }))} />\n <NumberRow title={t('vadPrerollMsLabel')} desc={t('vadPrerollMsDesc')} value={draft.realtime.vad.prerollMs} min={0} max={1_000} step={50} onChange={(v) => edit((c) => withSection(c, 'realtime', { vad: { ...c.realtime.vad, prerollMs: v } }))} />\n <NumberRow title={t('vadMinSpeechMsLabel')} desc={t('vadMinSpeechMsDesc')} value={draft.realtime.vad.minSpeechMs} min={100} max={3_000} step={50} onChange={(v) => edit((c) => withSection(c, 'realtime', { vad: { ...c.realtime.vad, minSpeechMs: v } }))} />\n <NumberRow title={t('vadMaxSegmentMsLabel')} desc={t('vadMaxSegmentMsDesc')} value={draft.realtime.vad.maxSegmentMs} min={1_000} max={30_000} step={500} onChange={(v) => edit((c) => withSection(c, 'realtime', { vad: { ...c.realtime.vad, maxSegmentMs: v } }))} />\n <NumberRow title={t('vadMaxPendingLabel')} desc={t('vadMaxPendingDesc')} value={draft.realtime.vad.maxPending} min={1} max={20} onChange={(v) => edit((c) => withSection(c, 'realtime', { vad: { ...c.realtime.vad, maxPending: v } }))} />\n <NumberRow title={t('realtimeMaxSessionLabel')} desc={t('realtimeMaxSessionDesc')} value={draft.realtime.maxSessionMs} min={30_000} max={3_600_000} step={30_000} onChange={(v) => edit((c) => withSection(c, 'realtime', { maxSessionMs: v }))} />\n <NumberRow title={t('realtimeFirstSentenceLabel')} desc={t('realtimeFirstSentenceDesc')} value={draft.realtime.speech.firstSentenceMinChars} min={1} max={200} onChange={(v) => edit((c) => withSection(c, 'realtime', { speech: { ...c.realtime.speech, firstSentenceMinChars: v } }))} />\n <NumberRow title={t('realtimeWatchdogLabel')} desc={t('realtimeWatchdogDesc')} value={draft.realtime.speech.utteranceWatchdogMs} min={1_000} max={300_000} step={1_000} onChange={(v) => edit((c) => withSection(c, 'realtime', { speech: { ...c.realtime.speech, utteranceWatchdogMs: v } }))} />\n\n <span className=\"dshav-groupTitle\">{t('groupStats')}</span>\n <UsageStats t={t} />\n </div>\n ) : null}\n </div>\n </div>\n ) : null}\n </li>\n )\n}\n\n/** 供应商行显示名(列表里区分同名预置)。 */\nfunction rowLabel(p: CloudProviderConfig, t: LocaleT): string {\n const preset = presetById(p.preset)\n const base = p.name.trim() !== '' ? p.name : preset?.label ?? t('cloudPresetCustom')\n return p.baseUrl.trim() === '' ? base : `${base} · ${p.baseUrl.replace(/^https?:\\/\\//, '')}`\n}\n","/** dsh-asr-voice — 草稿合并纯函数(无 DOM/React 依赖,node --test 直测)。 */\n\n/** append 模式:把新识别文本接到既有草稿末尾。\n * 语义与两处原实现(voice-button finalize / voice-chat commitTurn)逐字一致:\n * - 草稿为空 → 直接返回新文本(不补前置空格);\n * - 草稿已有内容且不以空格/换行结尾 → 用一个空格分隔;\n * - 草稿已以空格/换行结尾 → 原样拼接(不重复加分隔)。\n */\nexport function appendDraftText(existing: string, text: string): string {\n if (existing === '') return text\n return `${existing}${/[ \\n]$/.test(existing) ? '' : ' '}${text}`\n}\n","/** dsh-asr-voice — 引擎决策纯函数(无 DOM/React 依赖,node --test 直测)。 */\n\n/** 实际启动的识别引擎。 */\nexport type AsrEngine = 'browser' | 'cloud'\n\n/** 配置态 provider 三态。 */\nexport type AsrProviderChoice = 'auto' | 'browser' | 'cloud'\n\n/** 解析最终引擎:auto = 浏览器优先(Web Speech 可用时),否则回落到已配置的云端;\n * 云端也未配置时仍回退浏览器(失败路径由调用方给错误提示,而不是决策层死锁)。\n */\nexport function resolveEngine(\n provider: AsrProviderChoice,\n webSpeechSupported: boolean,\n cloudReady: boolean,\n): AsrEngine {\n if (provider === 'cloud') return 'cloud'\n if (provider === 'browser') return 'browser'\n // auto\n if (!webSpeechSupported) return cloudReady ? 'cloud' : 'browser'\n return 'browser'\n}\n\n/** 云端兜底判定:auto 模式下浏览器引擎启动/运行失败(错误码 recoverable 命中)且云端已配置。\n * recoverable 缺省 true——同步启动抛错路径没有错误码,按「可兜底」处理(与原 catch 分支一致)。 */\nexport function shouldFallbackToCloud(\n engine: AsrEngine,\n provider: AsrProviderChoice,\n cloudReady: boolean,\n recoverable = true,\n): boolean {\n return engine === 'browser' && provider === 'auto' && cloudReady && recoverable\n}\n","/** dsh-asr-voice — PCM 数学(纯函数,模块顶层不碰 DOM,可被 node --test 直接跑源码)。\n * 整段转写路径(`recorder.ts` 的 blobToWav16k)与实时上行路径共用这一套下混 /\n * 重采样 / 峰值 / 归一 / 量化实现:静音守卫的判据必须只有一份真相,否则两条路径\n * 会在「设备到底有没有采到声」上给出不一致的结论。\n */\n\n/** 上行统一采样率:ASR 上游通用要求 16 kHz 单声道。 */\nexport const PCM_SAMPLE_RATE = 16_000\n\n/** 采集 / 解码用 AudioContext:一律**请求 16 kHz**,让浏览器的高质量重采样器直接把\n * 设备采样率(桌面 Chrome 多为 48k)转到上行要求的 16k。\n *\n * 为什么不请求:音频图按设备采样率跑,就得自己 `resampleLinear` —— 而 48k→16k 的\n * ratio 恰是整数 3,线性插值退化成「每 3 个点取 1 个」的裸抽取,**没有抗混叠低通**,\n * 8 kHz 以上的内容(齿音、环境噪声)会折叠进语音带,直接伤识别率。请求 16k 后\n * `decodeAudioData` 与音频图都直接产出 16k,`resampleLinear` 成为空转。\n *\n * 浏览器不支持指定采样率(或直接抛错)时回落默认采样率;调用方**永远读回\n * `ctx.sampleRate` 再决定要不要自行重采样**,不要假定请求被采纳。\n *\n * 这是本模块唯一的 DOM 接触点:函数体内才碰 `window`,模块顶层与其它导出仍是纯\n * 函数,node --test 直跑源码不受影响。 */\nexport function createPcmAudioContext(latencyHint?: 'balanced' | 'interactive' | 'playback'): AudioContext {\n const windowLike = window as unknown as {\n AudioContext?: typeof AudioContext\n webkitAudioContext?: typeof AudioContext\n }\n const AudioCtor = windowLike.AudioContext ?? windowLike.webkitAudioContext\n if (!AudioCtor) throw new Error('audio context unavailable')\n try {\n return latencyHint === undefined\n ? new AudioCtor({ sampleRate: PCM_SAMPLE_RATE })\n : new AudioCtor({ latencyHint, sampleRate: PCM_SAMPLE_RATE })\n } catch {\n return latencyHint === undefined ? new AudioCtor() : new AudioCtor({ latencyHint })\n }\n}\n\n/** 归一化目标幅度(0.9 ≈ 接近满幅但不触顶)。 */\nconst NORMALISE_TARGET = 0.9\n\n/** 归一化增益上限:防噪声底被过度放大。 */\nconst NORMALISE_MAX_GAIN = 4\n\n/** 峰值低于此值视为无信号,不做归一(避免把纯噪声放大 4 倍)。 */\nconst NORMALISE_MIN_PEAK = 0.0001\n\n/** 静音守卫阈值:归一化前的真实峰值低于此值 → 认定确实没录到声。 */\nexport const SILENCE_PEAK_FLOOR = 0.005\n\n/** 静音守卫(ground truth):转换后 PCM 的真实峰值趋零,说明采集链路没拿到声音, 不该发上游(上游会对静音幻觉出 \"yeah\" / \"no text\")。 */\nexport function isSilentPeak(peak: number): boolean {\n return peak >= 0 && peak < SILENCE_PEAK_FLOOR\n}\n\n/** 多声道等权下混为单声道。 */\nexport function downmixToMono(channels: readonly Float32Array[], length: number): Float32Array {\n const mono = new Float32Array(length)\n if (channels.length === 0) return mono\n // 等权增益提出内层循环:长音频每采样少一次除法(实时 16k 帧路径每帧都在跑)。\n const gain = 1 / channels.length\n for (const data of channels) {\n const n = Math.min(length, data.length)\n for (let i = 0; i < n; i++) mono[i] = (mono[i] ?? 0) + (data[i] ?? 0) * gain\n }\n return mono\n}\n\n/** 线性插值重采样到 targetRate(调用方永不假定源采样率,浏览器可能忽略请求的 sampleRate)。 */\nexport function resampleLinear(src: Float32Array, sourceRate: number, targetRate: number): Float32Array {\n const srcLen = src.length\n const outLen = Math.max(1, Math.round(srcLen * targetRate / sourceRate))\n const out = new Float32Array(outLen)\n const ratio = sourceRate / targetRate\n for (let i = 0; i < outLen; i++) {\n const pos = i * ratio\n const i0 = Math.floor(pos)\n const i1 = Math.min(i0 + 1, srcLen - 1)\n const frac = pos - i0\n out[i] = (src[i0] ?? 0) * (1 - frac) + (src[i1] ?? 0) * frac\n }\n return out\n}\n\n/** 绝对值峰值(归一化与静音守卫的共同输入)。 */\nexport function peakAbs(src: Float32Array): number {\n let peak = 0\n for (let i = 0; i < src.length; i++) {\n const a = Math.abs(src[i] ?? 0)\n if (a > peak) peak = a\n }\n return peak\n}\n\n/** 峰值归一化增益:把偏轻的麦克风录音放大到接近满幅,受 NORMALISE_MAX_GAIN 约束。 */\nexport function normaliseGain(peak: number): number {\n return peak > NORMALISE_MIN_PEAK ? Math.min(NORMALISE_MAX_GAIN, NORMALISE_TARGET / peak) : 1\n}\n\n/** 一个 Float32 采样 → 限幅后的 16-bit 有符号整数(gain 在此一并应用)。\n * 两条路径共用同一量化,正负半轴不对称是 int16 本身的取值范围决定\n * (负端多一个 -32768)。\n * 必须四舍五入:向零截断会让所有非零采样一律靠近零,对本插件专门放大的安静录音\n * 是系统性衰减。\n */\nexport function quantiseInt16(sample: number, gain: number): number {\n const s = Math.max(-1, Math.min(1, sample * gain))\n return s < 0 ? Math.round(s * 0x8000) : Math.round(s * 0x7fff)\n}\n\n/** Float32 采样 → 完整 16-bit 单声道 WAV 字节(44 字节头 + data)。 增益在写采样时一并应用:长录音 outLen 可达数十万采样,多一遍独立增益遍历 是解码之后真实可感的 CPU 开销。 */\nexport function encodeWav16MonoPcm(samples: Float32Array, sampleRate: number, gain: number): Uint8Array<ArrayBuffer> {\n const dataLen = samples.length * 2\n const buf = new ArrayBuffer(44 + dataLen)\n const view = new DataView(buf)\n const writeStr = (off: number, s: string): void => {\n for (let i = 0; i < s.length; i++) view.setUint8(off + i, s.charCodeAt(i))\n }\n writeStr(0, 'RIFF'); view.setUint32(4, 36 + dataLen, true); writeStr(8, 'WAVE')\n writeStr(12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true)\n view.setUint16(22, 1, true)\n view.setUint32(24, sampleRate, true)\n view.setUint32(28, sampleRate * 2, true)\n view.setUint16(32, 2, true)\n view.setUint16(34, 16, true)\n writeStr(36, 'data'); view.setUint32(40, dataLen, true)\n for (let i = 0; i < samples.length; i++) {\n view.setInt16(44 + i * 2, quantiseInt16(samples[i] ?? 0, gain), true)\n }\n return new Uint8Array(buf)\n}\n\n/** `AnalyserNode.getByteTimeDomainData` 缓冲的 RMS(0~1,无符号 8-bit 以 128 为零位)。 实时电平表与静音自动停止共用。 */\nexport function rmsFromByteTimeDomain(bytes: Uint8Array): number {\n if (bytes.length === 0) return 0\n let sum = 0\n for (let i = 0; i < bytes.length; i++) {\n const v = ((bytes[i] ?? 128) - 128) / 128\n sum += v * v\n }\n return Math.sqrt(sum / bytes.length)\n}\n\n/** Float32 采样的 RMS(0~1)。本地 VAD 判的是 AudioWorklet 直出的采样,不是 AnalyserNode 的 8-bit 缓冲——两者的判据不能互抄,否则同一个阈值在电平表和 切段器上会给出不同的「有没有在说话」。 */\nexport function rmsOfFloat(src: Float32Array): number {\n if (src.length === 0) return 0\n let sum = 0\n for (let i = 0; i < src.length; i++) {\n const v = src[i] ?? 0\n sum += v * v\n }\n return Math.sqrt(sum / src.length)\n}\n","/** dsh-asr-voice — client 录音引擎。\n * 两种引擎,统一 `VoiceRecorder` 接口:\n * - browser:Web Speech API(webkitSpeechRecognition),实时转写、浏览器本地、\n * 免 key;Chrome/Edge 双平台支持。\n * - cloud:getUserMedia + MediaRecorder 采集音频,停止后把原始字节 POST 到\n * host /api/asr-voice/transcribe,由服务端转发云端 ASR(key 不进浏览器)。\n * 两者都带:最长录音上限、interim 文本回调、状态回调;cloud 引擎可选静音自动停止\n * (默认关 = 手动关麦,点停止整段去 ASR,见 behavior.silenceStop)。\n */\nimport {\n PCM_SAMPLE_RATE, createPcmAudioContext, downmixToMono, encodeWav16MonoPcm, isSilentPeak,\n normaliseGain, peakAbs, resampleLinear, rmsFromByteTimeDomain,\n} from './pcm.ts'\nimport type { RecordBehavior } from './config.ts'\n\n/** 云端转写请求超时(毫秒):上游不可达/卡住时不把 UI 永远钉在「识别中」。 */\nconst TRANSCRIBE_TIMEOUT_MS = 60_000\n\n/** 录音状态。 */\nexport type RecordState = 'recording' | 'transcribing'\n\n/** 浏览器语音识别的最小接口面(webkitSpeechRecognition)。 */\nexport interface SpeechRecognitionLike {\n lang: string\n continuous: boolean\n interimResults: boolean\n maxAlternatives: number\n onstart: (() => void) | null\n onend: (() => void) | null\n onerror: ((event: { error: string }) => void) | null\n onresult: ((event: {\n resultIndex: number\n results: {\n length: number\n item(index: number): {\n isFinal: boolean\n length: number\n item(j: number): { transcript: string }\n }\n }\n }) => void) | null\n start(): void\n stop(): void\n abort(): void\n}\n\n/** 浏览器是否可用 Web Speech API。 */\nexport function isWebSpeechSupported(): boolean {\n return typeof window !== 'undefined' && 'webkitSpeechRecognition' in window\n}\n\n// 麦克风采集:用浏览器默认约束(AEC/降噪/自动增益交给 Chromium 处理)。\n// 注意:显式关闭 echoCancellation/noiseSuppression/autoGainControl 在部分 macOS\n// 设备上会直接采到纯静音(AEC 兼做时钟/重采样适配),故不在此处改约束;\n// 设备级静音由「静音守卫」(基于转换后 WAV 的真实峰值)兜底识别。\n\n/** 麦克风是否可用(权限 + 设备)。 */\nexport async function hasMicrophone(): Promise<boolean> {\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) return false\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true })\n for (const track of stream.getTracks()) track.stop()\n return true\n } catch {\n return false\n }\n}\n\n/** 统一录音控制器。 */\nexport interface VoiceRecorder {\n /** 开始录音(幂等)。 */\n start(): void\n /** 手动停止;resolve 原始转写文本(cloud 引擎会先上传识别)。 */\n stop(): Promise<string>\n /** 放弃本次录音(不 resolve / 丢弃)。 */\n abort(): void\n /** 实时 interim 文本回调(浏览器引擎实时;云端引擎为空)。 */\n onInterim: ((text: string) => void) | null\n /** 状态回调(recording → transcribing)。 */\n onState: ((state: RecordState) => void) | null\n /** 实时音量回调(0~1 RMS;cloud 为真实音量,browser 为模拟能量)。 */\n onLevel: ((rms: number) => void) | null\n/** 转写完成回调:无论 stop 由谁触发(手动点击 / 静音自动停止 / 超时自动停止), 结果文本统一经此送达 UI;被 abort(打断)则不触发。 */\n onDone: ((text: string) => void) | null\n /** 转写失败回调(网络/上游/静音守卫等;被 abort 不触发)。 */\n onFail: ((error: unknown) => void) | null\n}\n\n/** 语言参数:auto → 返回 undefined(交给浏览器/服务端默认)。 */\nfunction resolveLang(language: string): string | undefined {\n if (!language || language === 'auto') return undefined\n return language\n}\n\n/** 装饰性电平:Web Speech 引擎不暴露音频流,用平滑随机波形近似语音起伏驱动频谱条。 只用于视觉反馈,绝不参与任何静音/回合判定。返回幂等的停止函数。 */\nexport function startLevelSimulation(emit: (level: number) => void): () => void {\n let raf = 0\n let level = 0.05\n let phase = Math.random() * Math.PI * 2\n const loop = (): void => {\n phase += 0.16 + Math.random() * 0.12\n const base = 0.24 + 0.16 * Math.sin(phase)\n const burst = Math.random() < 0.07 ? Math.random() * 0.45 : 0\n const next = Math.min(1, Math.max(0.02, base + burst + Math.random() * 0.12))\n level += (next - level) * 0.32\n emit(level)\n raf = requestAnimationFrame(loop)\n }\n raf = requestAnimationFrame(loop)\n return () => {\n if (raf) cancelAnimationFrame(raf)\n raf = 0\n }\n}\n\n/** 浏览器引擎:Web Speech API。 */\nfunction createBrowserRecorder(language: string, onError: (msg: string) => void, behavior: RecordBehavior): VoiceRecorder {\n const Ctor = (window as unknown as { webkitSpeechRecognition: new () => SpeechRecognitionLike }).webkitSpeechRecognition\n if (!Ctor) {\n throw new Error('browser: webkitSpeechRecognition unavailable')\n }\n const recognition = new Ctor()\n const lang = resolveLang(language)\n if (lang) recognition.lang = lang\n recognition.continuous = true\n recognition.interimResults = true\n recognition.maxAlternatives = 1\n\n let finalText = ''\n let interim = ''\n let stopped = false\n let cancelled = false\n let delivered = false\n let endResolve: ((text: string) => void) | null = null\n let maxTimer: ReturnType<typeof setTimeout> | null = null\n let stopLevelSim: (() => void) | null = null\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const recorder: VoiceRecorder = { onInterim: null, onState: null, onLevel: null, onDone: null, onFail: null } as any\n\n /** 一次性送达结果:settle 与 stop() 双入口都可能命中,用 delivered 防重复。 */\n const deliver = (text: string): void => {\n if (delivered || cancelled) return\n delivered = true\n recorder.onDone?.(text)\n }\n\n /** 浏览器引擎无音频流,用平滑的模拟能量驱动频谱(装饰性,视觉近似语音起伏)。 */\n const startLevelSim = (): void => {\n stopLevelSim ??= startLevelSimulation((level) => { recorder.onLevel?.(level) })\n }\n const stopLevelSimNow = (): void => {\n stopLevelSim?.()\n stopLevelSim = null\n }\n\n const emitInterim = (): void => {\n const text = `${finalText}${finalText && interim ? ' ' : ''}${interim}`.trim()\n recorder.onInterim?.(text)\n }\n\n const settle = (): void => {\n if (stopped) return\n stopped = true\n stopLevelSimNow()\n if (maxTimer) clearTimeout(maxTimer)\n // 被 abort(打断)时不送结果;正常结束时经 onDone 把文本送回 UI,\n // 让「手动停止 / 自动停止」两条路径都能收敛到同一处消费。\n if (!cancelled && endResolve) {\n const text = finalText.trim()\n endResolve(text)\n endResolve = null\n deliver(text)\n }\n }\n\n recognition.onstart = () => { recorder.onState?.('recording') }\n\n recognition.onresult = (event) => {\n let finalChunk = ''\n let interimChunk = ''\n for (let i = event.resultIndex; i < event.results.length; i++) {\n const result = event.results.item(i)\n const transcript = result.item(0)?.transcript ?? ''\n if (result.isFinal) finalChunk += transcript\n else interimChunk += transcript\n }\n if (finalChunk) finalText = `${finalText}${finalText && finalChunk ? ' ' : ''}${finalChunk}`\n if (interimChunk) interim = interimChunk\n else if (!finalChunk) interim = ''\n emitInterim()\n }\n\n recognition.onerror = (event) => {\n // 无论哪种错误都 settle:避免 UI 一直停在「录音中」(网络错误等会先于 onend 到来)。\n if (event.error === 'not-allowed' || event.error === 'service-not-allowed') {\n settle()\n onError('mic-denied')\n } else if (event.error === 'no-speech') {\n // 静音结束:当作正常结束(轻提示,非错误)。\n settle()\n onError('no-speech')\n } else if (event.error === 'aborted') {\n // 主动 abort:正常结束。\n settle()\n } else if (event.error === 'network') {\n // 中国网络下 Chrome Web Speech 走 Google 服务器常被屏蔽 → network 错误。\n settle()\n onError('network')\n } else {\n settle()\n onError(event.error || 'unknown')\n }\n }\n\n recognition.onend = () => settle()\n\n recorder.start = () => {\n if (stopped) return\n startLevelSim()\n maxTimer = setTimeout(() => { try { recognition.stop() } catch { /* noop */ } }, behavior.maxRecordMs)\n try {\n recognition.start()\n } catch {\n // already started\n }\n }\n\n recorder.stop = () => {\n if (stopped) {\n const text = finalText.trim()\n deliver(text)\n return Promise.resolve(text)\n }\n return new Promise<string>((resolve) => {\n endResolve = (text) => resolve(text)\n try {\n recognition.stop()\n } catch {\n settle()\n }\n })\n }\n\n recorder.abort = () => {\n cancelled = true\n // 挂起的 stop() 必须 settle:abort 后 settle() 的 cancelled 分支不再 resolve,\n // 不在这里把 endResolve 收掉,stop() 的 promise 会永久挂起。语义保持 cancelled\n // (resolve 空结果,但不触发 deliver/onDone 的正常完成路径)。\n if (endResolve) { endResolve(''); endResolve = null }\n try {\n recognition.abort()\n } catch {\n /* noop */\n }\n settle()\n }\n\n return recorder\n}\n\n/** 分析用 AudioContext(电平表):懒创建、跨录音复用——AudioContext 创建开销大\n * 且系统资源有限,反复 new/close 会抖动;录音开始接新流、结束断流即可。 */\nlet meterCtx: AudioContext | null = null\nfunction getMeterCtx(): AudioContext | null {\n if (meterCtx !== null) return meterCtx\n try {\n const windowLike = window as unknown as {\n AudioContext?: typeof AudioContext\n webkitAudioContext?: typeof AudioContext\n }\n const AudioCtor = windowLike.AudioContext ?? windowLike.webkitAudioContext\n meterCtx = AudioCtor ? new AudioCtor() : null\n return meterCtx\n } catch {\n return null\n }\n}\n\n/** 模块级:电平表当前 source 与 rAF 句柄,供录音结束断流/停帧。 */\nlet meterSources: MediaStreamAudioSourceNode[] = []\nlet meterRaf: { cancel(): void } | null = null\n/** 录音结束后停止电平表(断流 + 停帧;AudioContext 保留复用)。 */\nfunction stopLevelMeter(): void {\n meterRaf?.cancel()\n meterRaf = null\n for (const src of meterSources) {\n try { src.disconnect() } catch { /* noop */ }\n }\n meterSources = []\n}\n\n/** 云端引擎:MediaRecorder 采集 → host 代理转写。 */\nfunction createCloudRecorder(language: string, onError: (msg: string) => void, behavior: RecordBehavior): VoiceRecorder {\n const recorder: VoiceRecorder = {\n onInterim: null, onState: null, onLevel: null, onDone: null, onFail: null,\n start: () => {}, stop: () => Promise.resolve(''), abort: () => {},\n }\n let stream: MediaStream | null = null\n let mediaRecorder: MediaRecorder | null = null\n let chunks: Blob[] = []\n let maxTimer: ReturnType<typeof setTimeout> | null = null\n let stopPromise: Promise<string> | null = null\n /** 挂起 stop() 的 resolve 句柄:abort() 时强制 resolve(''),保证 stop() 一定 settle\n * (abort 后 onstop 的 cancelled 分支不再 resolve/reject;reject 各分支直接走\n * executor 里的 reject,无需在此捕获)。 */\n let stopResolve: ((text: string) => void) | null = null\n let active = false\n let cancelled = false\n // 授权弹窗挂起(getUserMedia 未返回)期间收到 stop/abort:start 的 post-await\n // 检查点据此放弃本次会话,避免「UI 已停、麦克风却恢复录音」的状态错位。\n let stopRequested = false\n /** 当前转写请求的 AbortController:abort() 时可取消在途的 host 请求。 */\n let transcribeController: AbortController | null = null\n\n /** 停掉当前麦克风流的所有轨道(onstop/abort/错误各分支共用的收尾)。 */\n const stopStream = (): void => {\n if (stream !== null) for (const t of stream.getTracks()) t.stop()\n }\n\n /** 把转换前的原始录音抓一份到 host(静音/异常短结果诊断用),失败静默。 */\n const captureDiagnostic = (blob: Blob): void => {\n if (blob.size <= 0) return\n void fetch('/api/asr-voice/transcribe?capture=1', {\n method: 'POST',\n headers: { 'content-type': blob.type || 'audio/webm' },\n body: blob,\n }).catch(() => {})\n }\n\n const pickMime = (): string => {\n // webm/opus 优先:Chrome 对 MediaRecorder 产出的 mp4(AAC) 解码不稳定(decodeAudioData\n // 可能解出错误/静音数据 → 上游收到垃圾音频返回空或幻觉文本)。上游统一收\n // blobToWav16k 转换后的 WAV,中间格式只影响解码可靠性——webm 在 Chromium 最稳,\n // mp4 留给不产 webm 的浏览器(Safari)。\n const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg;codecs=opus']\n for (const m of candidates) {\n if (MediaRecorder.isTypeSupported(m)) return m\n }\n return ''\n }\n\n/** 实时音量电平(驱动频谱条)。始终启用(能直观看出麦克风是否采到声);\n * 仅当 behavior.silenceStop 开启时附带静音自动停止逻辑。\n * 注意:静音判定不依赖此处(Web Audio 双消费/挂起会误读),改由 onstop 里\n * 基于「转换后 WAV 的真实峰值」判定,此处只做实时反馈。\n */\n const startLevelMeter = (): void => {\n try {\n const audioCtx = getMeterCtx()\n if (audioCtx === null || stream === null) return\n // 复用上下文时先断开旧的 source(避免叠流)。\n for (const src of meterSources) {\n try { src.disconnect() } catch { /* noop */ }\n }\n meterSources = []\n const source = audioCtx.createMediaStreamSource(stream)\n const analyser = audioCtx.createAnalyser()\n analyser.fftSize = 1024\n source.connect(analyser)\n meterSources.push(source)\n const data = new Uint8Array(analyser.fftSize)\n let silentSince: number | null = null\n let raf = 0\n const loop = (): void => {\n if (!active) { raf = 0; return }\n analyser.getByteTimeDomainData(data)\n const rms = rmsFromByteTimeDomain(data)\n // 实时音量(0~1,放大到可视范围)\n recorder.onLevel?.(Math.min(1, rms * 4))\n if (behavior.silenceStop) {\n if (rms < behavior.silenceRms) {\n if (silentSince === null) silentSince = performance.now()\n else if (performance.now() - silentSince > behavior.silenceMs) {\n raf = 0\n void recorder.stop().catch(() => {})\n return\n }\n } else {\n silentSince = null\n }\n }\n raf = requestAnimationFrame(loop)\n }\n raf = requestAnimationFrame(loop)\n // 供录音结束路径取消 rAF(避免 stop 后仍跑一帧空转)。\n meterRaf = { cancel: () => { if (raf) cancelAnimationFrame(raf); raf = 0 } }\n } catch {\n // 音频分析不可用:频谱静默,录音仍正常\n }\n }\n\n /** 当前输入设备的 Chrome 标签(诊断信息,如 \"MacBook Pro 麦克风\")。 */\n const currentInputLabel = (): string => {\n try {\n return stream?.getAudioTracks()[0]?.label ?? ''\n } catch {\n return ''\n }\n }\n\n recorder.start = async () => {\n if (active) return\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {\n onError('no-mic')\n return\n }\n let s: MediaStream\n try {\n s = await navigator.mediaDevices.getUserMedia({ audio: true })\n } catch {\n onError('mic-denied')\n return\n }\n // 授权弹窗挂起期间用户可能已取消(abort)或已按停止(finish):复检,避免\n // 「幽灵录音」——取消后恢复仍开始录音,或 UI 已置 transcribing 却照常开录,\n // 直到 120s 上限才自愈。stopRequested 分支经 onDone('') 把状态机干净收回 idle。\n if (cancelled || stopRequested) {\n stopRequested = false\n for (const t of s.getTracks()) t.stop()\n active = false\n if (cancelled) return\n recorder.onDone?.('')\n return\n }\n stream = s\n chunks = []\n active = true\n const mime = pickMime()\n try {\n mediaRecorder = mime ? new MediaRecorder(s, { mimeType: mime }) : new MediaRecorder(s)\n } catch {\n onError('recorder-unsupported')\n active = false\n for (const t of s.getTracks()) t.stop()\n return\n }\n mediaRecorder.ondataavailable = (e) => {\n if (e.data && e.data.size > 0) chunks.push(e.data)\n }\n stopPromise = new Promise<string>((resolve, reject) => {\n stopResolve = resolve\n mediaRecorder!.onstop = async () => {\n stopLevelMeter()\n if (cancelled) { active = false; stopStream(); return }\n const type = mediaRecorder?.mimeType?.split(';')[0]?.trim() || 'audio/webm'\n const blob = new Blob(chunks, { type })\n recorder.onState?.('transcribing')\n try {\n // MiMo/Qwen-ASR 等 chat 通道只收 wav/mp3,whisper 式也兼容 wav →\n // 统一把浏览器录音(webm/m4a/ogg)解码重编码成 16kHz 单声道 WAV 再上传。\n let audio = blob\n let wavPeak = -1 // -1 = 转换失败,无法判定\n try {\n const r = await blobToWav16k(blob)\n if (cancelled) { active = false; stopStream(); return }\n audio = r.wav\n wavPeak = r.peak\n } catch {\n // 解码失败:退回原始 blob,让上游报错(避免转换本身卡死录音)。\n }\n // 静音守卫(ground truth):转换后的 WAV 真实峰值趋零 → MediaRecorder 确实没录到声,\n // 不发 ASR(避免对静音幻觉出 \"yeah\"/\"no text\");原始录音也抓一份供诊断对比。\n if (isSilentPeak(wavPeak)) {\n captureDiagnostic(blob)\n active = false\n stopStream()\n const label = currentInputLabel()\n // 附上浏览器可见的全部输入设备 + 内核标识,一眼看出是否选错/非主流内核。\n let devices = ''\n try {\n const list = await navigator.mediaDevices.enumerateDevices()\n devices = list\n .filter((d) => d.kind === 'audioinput' && d.label !== '')\n .map((d) => d.label)\n .slice(0, 5)\n .join('、')\n } catch { /* 枚举失败不影响主错误 */ }\n const ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''\n const br = /Edg\\//.test(ua) ? 'Edge' : /Chrome\\//.test(ua) ? 'Chrome' : /Firefox\\//.test(ua) ? 'Firefox' : '未知内核'\n const extra = [label, devices, `浏览器:${br}`].filter(Boolean).join(' | ')\n const err = new Error(extra === '' ? 'no-sound' : `no-sound:${extra}`)\n recorder.onFail?.(err)\n stopResolve = null\n reject(err)\n return\n }\n const controller = new AbortController()\n transcribeController = controller\n const text = await transcribeViaHost(audio, language, controller.signal)\n transcribeController = null\n if (cancelled) { active = false; stopStream(); return }\n // 异常短结果(疑似静音/听错):把转换前的原始录音也抓一份到 host,\n // 与转换后的 WAV(host 侧 ≤8 字规则已存)对比定位是采集还是转码问题。\n if (text.trim().length <= 8) captureDiagnostic(blob)\n active = false\n stopStream()\n recorder.onDone?.(text)\n stopResolve = null\n resolve(text)\n } catch (error) {\n active = false\n stopStream()\n if (cancelled) return\n const err = error instanceof Error ? error : new Error(String(error))\n recorder.onFail?.(err)\n stopResolve = null\n reject(err)\n }\n }\n mediaRecorder!.onerror = () => {\n stopLevelMeter()\n active = false\n if (maxTimer) { clearTimeout(maxTimer); maxTimer = null }\n // 录音错误也要释放麦克风流,否则轨道保持活跃(麦克风常亮、占用输入设备)。\n stopStream()\n const err = new Error('recorder-error')\n // 不经 stop() 直接触发的 MediaRecorder 错误也要送达 onFail,否则 voice-button\n // 永久卡在 recording(后续 stop() 因 !active 只 resolve(''),无 onDone/onFail)。\n if (!cancelled) recorder.onFail?.(err)\n stopResolve = null\n reject(err)\n // onerror 可能在无 stop() 调用方时触发:标记 rejection 已消费,避免 unhandledrejection。\n stopPromise?.catch(() => {})\n }\n })\n try {\n mediaRecorder.start(250)\n } catch (error) {\n // start() 抛错(设备被其他程序抢占、内核拒绝、MediaRecorder 已 inactive 等):\n // 不包的话 active 仍为 true、轨道不释放、无 onError —— 表现为**麦克风常亮**,\n // 且界面卡在「录音中」等一个永不到来的 onstop。\n // 按「录音根本没起来」收尾:释放流 + 复位标志 + 送达错误码。\n // stop() 无需额外处理:它见 active=false 即入口早退返回空结果,不会挂起\n //(active 置位到 start() 之间全同步,没有别的代码能插进来先拿到 stopPromise)。\n active = false\n stopStream()\n onError('recorder-start-failed')\n return\n }\n recorder.onState?.('recording')\n // 电平表始终启用(频谱反馈 + 可选静音自动停止)。\n startLevelMeter()\n maxTimer = setTimeout(() => { void recorder.stop().catch(() => {}) }, behavior.maxRecordMs)\n }\n\n recorder.stop = () => {\n if (!active || !mediaRecorder || !stopPromise) {\n // 录音尚未真正开始(授权弹窗挂起):置标志让 start 的 post-await 检查放弃本次会话。\n stopRequested = true\n return Promise.resolve('')\n }\n active = false\n if (maxTimer) clearTimeout(maxTimer)\n if (mediaRecorder.state !== 'inactive') {\n try { mediaRecorder.stop() } catch { /* noop */ }\n }\n return stopPromise\n }\n\n recorder.abort = () => {\n // 打断:置取消标志 + 中止在途转写请求,onstop 流程各检查点会放弃送达结果。\n cancelled = true\n stopRequested = true\n active = false\n stopLevelMeter()\n if (maxTimer) clearTimeout(maxTimer)\n transcribeController?.abort()\n transcribeController = null\n // 挂起的 stop() 必须 settle:abort 后 onstop 的 cancelled 分支不再 resolve/reject,\n // 不在这里收掉 stopPromise 会永久挂起。语义保持 cancelled——resolve 空结果,\n // 但不触发 onDone 的正常完成路径。\n stopResolve?.('')\n stopResolve = null\n if (mediaRecorder && mediaRecorder.state !== 'inactive') {\n try { mediaRecorder.stop() } catch { /* noop */ }\n }\n stopStream()\n stopPromise = null\n }\n\n return recorder\n}\n\n/** 把浏览器录音 blob(webm/m4a/ogg…)解码重编码成 16kHz 单声道 16-bit PCM WAV。\n * MiMo-V2.5-ASR 只接受 wav/mp3(实测 webm/m4a 报 Param Incorrect);whisper 式\n * 通道也兼容 wav,故统一走 WAV。纯浏览器 Web Audio API,无外部依赖。\n * 返回转换结果 + 归一化前的原始峰值(供静音守卫做 ground-truth 判定)。\n */\n/** 懒加载复用的 AudioContext(避免每次录音新建/关闭,提升转码流畅性)。\n * **请求 16k**:`decodeAudioData` 按上下文采样率解码,于是解码结果直接就是 16k,\n * 后面那次 `resampleLinear` 成为空转——省掉的是「每 3 个点取 1 个」的裸抽取\n * (48k→16k 无抗混叠低通,会伤识别率,见 createPcmAudioContext)。 */\nlet sharedAudioCtx: AudioContext | null = null\nfunction getAudioContext(): AudioContext {\n if (sharedAudioCtx !== null) return sharedAudioCtx\n sharedAudioCtx = createPcmAudioContext()\n return sharedAudioCtx\n}\n\nasync function blobToWav16k(blob: Blob): Promise<{ wav: Blob; peak: number }> {\n const ctx = getAudioContext()\n const arrayBuffer = await blob.arrayBuffer()\n try {\n const audio = await ctx.decodeAudioData(arrayBuffer)\n const channels: Float32Array[] = []\n for (let ch = 0; ch < audio.numberOfChannels; ch++) channels.push(audio.getChannelData(ch))\n const mono = downmixToMono(channels, audio.length)\n const pcm = resampleLinear(mono, audio.sampleRate, PCM_SAMPLE_RATE)\n const peak = peakAbs(pcm)\n const bytes = encodeWav16MonoPcm(pcm, PCM_SAMPLE_RATE, normaliseGain(peak))\n return { wav: new Blob([bytes], { type: 'audio/wav' }), peak }\n } catch (error) {\n // 共享 context 解码失败(可能被用户手动关闭):重建一个再试一次。\n if (sharedAudioCtx !== null) {\n sharedAudioCtx = null\n try {\n return await blobToWav16k(blob)\n } catch {\n sharedAudioCtx = null\n }\n }\n throw error\n }\n}\n\n/** 宿主 API POST 骨架(transcribe/optimize 共用):外部信号桥接 + 超时 + {ok,text,reason} 解析 + AbortError→timeout 转换。 */\nexport async function postHostApi(\n path: string,\n init: { headers: Record<string, string>; body: BodyInit },\n timeoutMs: number,\n label: string,\n externalSignal?: AbortSignal,\n): Promise<string> {\n const controller = new AbortController()\n const onExternalAbort = (): void => controller.abort()\n if (externalSignal !== undefined) {\n if (externalSignal.aborted) controller.abort()\n else externalSignal.addEventListener('abort', onExternalAbort, { once: true })\n }\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const res = await fetch(path, { method: 'POST', ...init, signal: controller.signal })\n const data = (await res.json().catch(() => ({}))) as { ok?: boolean; text?: string; reason?: string }\n if (!res.ok || data.ok !== true || typeof data.text !== 'string') {\n throw new Error(data.reason || `${label} failed`)\n }\n return data.text\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new Error(`${label} timeout`)\n }\n throw error\n } finally {\n clearTimeout(timer)\n externalSignal?.removeEventListener('abort', onExternalAbort)\n }\n}\n\n/** 上传音频到 host 转写代理(带超时,防上游卡死钉住 UI)。实时按句引擎共用这一条通道。 */\nexport async function transcribeViaHost(blob: Blob, language: string, externalSignal?: AbortSignal): Promise<string> {\n const lang = resolveLang(language)\n const query = lang ? `?language=${encodeURIComponent(lang)}` : ''\n return postHostApi(\n `/api/asr-voice/transcribe${query}`,\n { headers: { 'content-type': blob.type || 'audio/webm' }, body: blob },\n TRANSCRIBE_TIMEOUT_MS, 'transcribe', externalSignal,\n )\n}\n\n/** 创建统一录音控制器。\n * @param engine - browser | cloud。\n * @param language - auto / zh-CN / en-US 等。\n * @param onError - 错误回调(错误码字符串)。\n * @param behavior - 时长与静音参数(来自 settings,见 config.recordBehavior)。\n */\nexport function createVoiceRecorder(\n engine: 'browser' | 'cloud',\n language: string,\n onError: (code: string) => void,\n behavior: RecordBehavior,\n): VoiceRecorder {\n if (engine === 'cloud') return createCloudRecorder(language, onError, behavior)\n return createBrowserRecorder(language, onError, behavior)\n}\n","/** dsh-asr-voice — client 提示词优化。\n * 两种模式:\n * - heuristic:本地启发式清洗(免费、离线、即时)——去语气词/口误、补标点、\n * 分段、拉丁语首字母大写。\n * - llm:把文本 POST 到 host /api/asr-voice/optimize,由服务端用配置的\n * OpenAI-compatible chat completions 重写(key 不进浏览器)。\n */\nimport { postHostApi } from './recorder.ts'\n\n/** 常见中文语气词/口头禅(按词删除,保守集合)。 */\nconst ZH_FILLERS = ['嗯嗯', '嗯', '呃呃', '呃', '啊那个', '那个那个', '那个', '这个这个', '这个', '就是说', '怎么说呢', '然后呢', '然后', '就是', '是吧', '对吧', '好不好', '明白了没']\n\n/** 常见英文语气词(整词删除,大小写不敏感)。不收 like/well 等实义词——\n * 整词删除会把 \"I like this\"→\"I this\"、\"as well as\"→\"as as\" 这类正常句子削坏。 */\nconst EN_FILLERS = ['um', 'uh', 'hmm', 'erm', 'you know', 'i mean']\n\n/** 规范化空白:折叠连续空白/换行,转为单个空格;保留段落间空行。 */\nfunction normalizeSpaces(text: string): string {\n return text\n .replace(/\\r\\n/g, '\\n')\n .replace(/[ \\t]+/g, ' ')\n .replace(/\\n{3,}/g, '\\n\\n')\n .replace(/ *\\n */g, '\\n')\n .trim()\n}\n\n/** 中文字符检测。 */\nfunction hasCjk(text: string): boolean {\n return /[\\u4e00-\\u9fff]/.test(text)\n}\n\n/** 删除中文语气词。 */\nfunction stripZhFillers(text: string): string {\n let out = text\n for (const f of ZH_FILLERS) {\n // 只删成词出现(前后是空白/标点/边界),避免误删正常词(如「然后」在长句中被保留)\n out = out.replace(new RegExp(`(^|[\\\\s,。!?、;:,.!?;:\\\\n])${escapeRegExp(f)}(?=[\\\\s,。!?、;:,.!?;:\\\\n]|$)`, 'g'), '$1')\n }\n return out\n}\n\n/** 删除英文语气词(整词)。 */\nfunction stripEnFillers(text: string): string {\n let out = ` ${text} `\n for (const f of EN_FILLERS) {\n out = out.replace(new RegExp(`\\\\s${escapeRegExp(f)}\\\\s`, 'gi'), ' ')\n }\n return out.trim()\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/** 补齐/修正标点:折叠重复标点、拉丁语单词间保证单空格、句末补句号。 */\nfunction fixPunctuation(text: string): string {\n let out = text\n // 重复标点折叠\n out = out.replace(/([,。!?;:,.!?;:]){2,}/g, '$1')\n // 中文标点后不加空格,英文标点后保证一个空格\n if (hasCjk(out)) {\n out = out.replace(/ *([,。!?;:]) */g, '$1')\n out = out.replace(/ *([,.;:!?]) *([\\u4e00-\\u9fff])/g, '$1 $2')\n } else {\n out = out.replace(/ *([,.;:!?]) */g, '$1 ')\n }\n out = out.replace(/ {2,}/g, ' ')\n // 句末补句号\n const trimmed = out.trimEnd()\n if (trimmed === '') return ''\n const last = trimmed[trimmed.length - 1]!\n if (!/[。!?.!?,,;;::]/.test(last)) {\n out = hasCjk(trimmed) ? `${trimmed}。` : `${trimmed}.`\n }\n return out\n}\n\n/** 拉丁语:每句首字母大写。 */\nfunction sentenceCase(text: string): string {\n if (hasCjk(text)) return text\n return text\n .replace(/(^|[.!?]\\s+)([a-z])/g, (_m, p1: string, p2: string) => p1 + p2.toUpperCase())\n}\n\n/** 按空行分段,每段作为独立段落(LLM 预览时保留)。 */\nfunction segment(text: string): string {\n return text\n .split(/\\n\\n+/)\n .map((p) => p.trim())\n .filter((p) => p !== '')\n .join('\\n\\n')\n}\n\n/** 本地启发式优化:清洗语气词/口误 → 折叠空白 → 修正标点 → 分段 → 拉丁语句首大写。 */\nexport function heuristicOptimize(raw: string): string {\n if (!raw || raw.trim() === '') return ''\n let text = normalizeSpaces(raw)\n text = stripZhFillers(text)\n text = stripEnFillers(text)\n text = normalizeSpaces(text)\n text = fixPunctuation(text)\n text = sentenceCase(text)\n text = segment(text)\n return text\n}\n\n/** DSH 已配置模型的 provider/model(可选;空 = 用当前所选 LLM)。 */\nexport interface OptimizeTarget {\n provider: string\n model: string\n}\n\n/** LLM 优化请求超时(毫秒):模型卡住/过慢时不把 UI 永远钉在「优化中」;超时按失败处理,保留草稿里的清洗版。 */\nconst OPTIMIZE_TIMEOUT_MS = 60_000\n\n/** 调用 host /api/asr-voice/optimize(用 DSH 已配置模型重写)。 */\nexport async function llmOptimize(text: string, target?: OptimizeTarget, externalSignal?: AbortSignal): Promise<string> {\n const body: { text: string; provider?: string; model?: string } = { text }\n if (target !== undefined && target.provider !== '' && target.model !== '') {\n body.provider = target.provider\n body.model = target.model\n }\n return postHostApi(\n '/api/asr-voice/optimize',\n { headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) },\n OPTIMIZE_TIMEOUT_MS, 'optimize', externalSignal,\n )\n}\n","/** dsh-asr-voice — 轻量 GSAP 风格动画模块。\n * 设计约束:构建环境离线,无 gsap 包可装,故提供 GSAP 兼容子集的本地实现\n * (`to` / `fromTo`),API 与调用方式对齐 GSAP,\n * 组件里只经本模块驱动动效。将来若引入真 GSAP,只需替换本文件内部实现,\n * 组件调用点不改。\n * 支持:CSS 属性(opacity/scale/x/y/rotate 等变换缩写)、duration/ease/delay、\n * repeat/yoyo、onUpdate/onComplete。基于 requestAnimationFrame。\n */\n\n/** 缓动函数签名。 */\ntype EaseFn = (t: number) => number\n\n/** 常用缓动(与 GSAP 同名)。 */\nconst EASES: Record<string, EaseFn> = {\n power1: (t) => t,\n 'power1.in': (t) => t * t,\n 'power1.out': (t) => 1 - (1 - t) * (1 - t),\n 'power1.inOut': (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2),\n 'power2.out': (t) => 1 - (1 - t) * (1 - t),\n 'power3.out': (t) => 1 - Math.pow(1 - t, 3),\n 'power2.inOut': (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2),\n 'back.out': (t) => { const c1 = 1.70158; const c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2) },\n 'elastic.out': (t) => {\n if (t === 0 || t === 1) return t\n const c4 = (2 * Math.PI) / 3\n return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1\n },\n}\n\n/** 解析 ease:名字 → 函数。 */\nfunction toEase(ease: string | EaseFn | undefined): EaseFn {\n if (typeof ease === 'function') return ease\n return EASES[ease ?? 'power1.out'] ?? EASES['power1.out']!\n}\n\n/** 单条 CSS 目标:数值属性名 → 起始值。 */\ntype NumProps = Record<string, number>\n\n/** 把 vars 里的数值属性与「起始值」提取出来(跳过控制键)。 */\nfunction extractNumeric(target: HTMLElement, vars: Record<string, unknown>, from?: Record<string, unknown>): NumProps {\n const control = new Set(['duration', 'delay', 'ease', 'onUpdate', 'onComplete', 'yoyo', 'repeat', 'onStart'])\n const props: NumProps = {}\n for (const [key, value] of Object.entries(vars)) {\n if (control.has(key)) continue\n if (typeof value !== 'number') continue\n let start = 0\n if (from && typeof from[key] === 'number') {\n start = from[key] as number\n } else {\n start = currentNumeric(target, key)\n }\n props[key] = start\n }\n return props\n}\n\n/** 读取元素当前数值属性(变换缩写从 computed transform 解析,其余读 computed style)。 */\nfunction currentNumeric(target: HTMLElement, key: string): number {\n const cs = getComputedStyle(target)\n if (key === 'x' || key === 'y') {\n const m = new DOMMatrixReadOnly(cs.transform)\n return key === 'x' ? m.m41 : m.m42\n }\n if (key === 'scale' || key === 'scaleX' || key === 'scaleY') {\n const m = new DOMMatrixReadOnly(cs.transform)\n if (key === 'scale') return m.a\n if (key === 'scaleX') return m.a\n return m.d\n }\n if (key === 'rotate') {\n const m = new DOMMatrixReadOnly(cs.transform)\n return Math.atan2(m.b, m.a) * (180 / Math.PI)\n }\n const raw = cs.getPropertyValue(key)\n const num = parseFloat(raw)\n return Number.isFinite(num) ? num : 0\n}\n\n/** 把数值属性应用到元素样式(变换缩写合并为 transform)。 */\nfunction applyNumeric(target: HTMLElement, props: NumProps, progress: number, end: NumProps, baseMatrix?: DOMMatrixReadOnly): void {\n const transformParts: string[] = []\n for (const [key, start] of Object.entries(props)) {\n const value = start + ((end[key] ?? start) - start) * progress\n if (key === 'x') { transformParts.push(`translateX(${value}px)`); continue }\n if (key === 'y') { transformParts.push(`translateY(${value}px)`); continue }\n if (key === 'scale') { transformParts.push(`scale(${value})`); continue }\n if (key === 'scaleX') { transformParts.push(`scaleX(${value})`); continue }\n if (key === 'scaleY') { transformParts.push(`scaleY(${value})`); continue }\n if (key === 'rotate') { transformParts.push(`rotate(${value}deg)`); continue }\n if (key === 'opacity') { target.style.opacity = String(value); continue }\n // 通用数值样式属性(px 单位)\n if (key in target.style) {\n (target.style as unknown as Record<string, string>)[key] = `${value}px`\n }\n }\n if (transformParts.length > 0) {\n // 只做一次基础位移保留(动画启动时缓存),避免每帧 getComputedStyle。\n if (baseMatrix !== undefined && (baseMatrix.m41 !== 0 || baseMatrix.m42 !== 0)) {\n transformParts.unshift(`translate(${baseMatrix.m41}px, ${baseMatrix.m42}px)`)\n }\n target.style.transform = transformParts.join(' ')\n }\n}\n\n/** 读取元素当前基础 transform 位移(补间开始时一次;无 translate 返回 undefined 跳过保留)。 */\nfunction baseTranslateOf(target: HTMLElement): DOMMatrixReadOnly | undefined {\n const cs = getComputedStyle(target)\n try {\n const m = new DOMMatrixReadOnly(cs.transform)\n return m.m41 !== 0 || m.m42 !== 0 ? m : undefined\n } catch {\n return undefined\n }\n}\n\n/** 一个补间。 */\nexport interface TweenHandle {\n kill(): void\n progress(): number\n isActive(): boolean\n onComplete(cb: () => void): void\n}\n\n/** 创建补间:从 from(或当前值)到 to。 */\nexport function tween(\n target: HTMLElement,\n to: Record<string, unknown>,\n from?: Record<string, unknown>,\n): TweenHandle {\n const duration = typeof to.duration === 'number' ? to.duration : 0.5\n const delay = typeof to.delay === 'number' ? to.delay : 0\n const ease = toEase(typeof to.ease === 'string' ? to.ease : undefined)\n const yoyo = to.yoyo === true\n const repeat = typeof to.repeat === 'number' ? to.repeat : 0\n const onUpdate = typeof to.onUpdate === 'function' ? to.onUpdate : undefined\n const onComplete = typeof to.onComplete === 'function' ? to.onComplete : undefined\n const onStart = typeof to.onStart === 'function' ? to.onStart : undefined\n\n const startProps = extractNumeric(target, to, from)\n const endProps: NumProps = {}\n for (const [key, value] of Object.entries(to)) {\n if (typeof value === 'number') endProps[key] = value\n }\n // 是否有 transform 缩写属性参与 → 需要保留基础位移时只读一次(补间期间\n // transform 由本补间独占写入,读取一次即可复用,避免每帧 getComputedStyle)。\n const hasTransform = Object.keys(startProps).some((k) =>\n k === 'x' || k === 'y' || k === 'scale' || k === 'scaleX' || k === 'scaleY' || k === 'rotate')\n const baseMatrix = hasTransform ? baseTranslateOf(target) : undefined\n\n let startTime: number | null = null\n /** delay 只在首帧应用一次:repeat/yoyo 的后续周期从当前帧重新起算,\n * 否则 `repeat: Infinity, delay: .24` 这类循环每圈都白等 delay。 */\n let delayApplied = false\n let cycles = 0\n let killed = false\n let progressVal = 0\n let completeCb: (() => void) | undefined\n\n const tick = (now: number): void => {\n if (killed) return\n if (startTime === null) {\n startTime = now + (delayApplied ? 0 : delay * 1000)\n delayApplied = true\n onStart?.()\n }\n if (now < startTime) {\n requestAnimationFrame(tick)\n return\n }\n const local = (now - startTime) / (duration * 1000)\n const t = Math.min(1, Math.max(0, local))\n const eased = ease(t)\n applyNumeric(target, startProps, eased, endProps, baseMatrix)\n progressVal = t\n onUpdate?.({ progress: t })\n const done = t >= 1\n const cycleDone = done\n if (cycleDone) {\n if (yoyo) {\n // 简单 yoyo:往返一次视为一个周期\n cycles += 1\n if (cycles > repeat) {\n finished()\n return\n }\n startTime = null\n // 反向播放:起止互换。只把 start 换成 end 会让 start===end,\n // 反向周期变成恒值空转(元素钉在终点不动)——end 必须同时换回原 start。\n for (const k of Object.keys(startProps)) {\n const tmp = startProps[k]!\n startProps[k] = endProps[k]!\n endProps[k] = tmp\n }\n requestAnimationFrame(tick)\n return\n }\n if (repeat > 0 && cycles < repeat) {\n cycles += 1\n startTime = null\n requestAnimationFrame(tick)\n return\n }\n finished()\n return\n }\n requestAnimationFrame(tick)\n }\n\n const finished = (): void => {\n if (killed) return\n killed = true\n onComplete?.()\n completeCb?.()\n }\n\n requestAnimationFrame(tick)\n\n return {\n kill: () => { killed = true },\n progress: () => progressVal,\n isActive: () => !killed,\n onComplete: (cb) => { completeCb = cb },\n }\n}\n\n/** GSAP 风格快捷方法。 */\nexport function to(target: HTMLElement, vars: Record<string, unknown>): TweenHandle {\n return tween(target, vars)\n}\n\n/** GSAP 风格 fromTo。 */\nexport function fromTo(target: HTMLElement, from: Record<string, unknown>, to: Record<string, unknown>): TweenHandle {\n return tween(target, to, from)\n}\n","/** dsh-asr-voice — 按钮与状态条共用的图标/装饰件。\n *\n * 单独成文件是为了打断循环依赖:合并成一个按钮后,麦克风按钮要复用对话模块的\n * 逻辑(voice-chat.tsx),而对话状态条又要用这里的图标——图标留在任一侧都会成环。\n * 这些组件没有任何状态与依赖,放中立位置最自然。\n */\nimport * as react from 'react'\n\n/** 频谱条柱数。 */\nexport const SPECTRUM_BARS = 12\n\n/** 麦克风图标。 */\nexport function MicIcon(): react.ReactElement {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"9\" y=\"2.5\" width=\"6\" height=\"11\" rx=\"3\" />\n <path d=\"M5 11a7 7 0 0 0 14 0\" />\n <path d=\"M12 18v3.5\" />\n </svg>\n )\n}\n\n/** 对话图标(声波气泡:与麦克风的实心咪头区分开)。 */\nexport function ChatIcon(): react.ReactElement {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M4 6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5v7a2.5 2.5 0 0 1-2.5 2.5H12l-4.5 3.5v-3.5H6.5A2.5 2.5 0 0 1 4 13.5z\" />\n <path d=\"M9 10v-1.5M12 11V7.5M15 10v-1.5\" />\n </svg>\n )\n}\n\n/** 录音状态图标(实心圆点,带呼吸)。 */\nexport function RecDot(): react.ReactElement {\n return <span className=\"dshav-rec-dot\" />\n}\n\n/** 转圈(transcribing / optimizing)。 */\nexport function Spinner(): react.ReactElement {\n return <span className=\"dshav-spinner\" aria-hidden=\"true\" />\n}\n\n/** 频谱条(12 根柱,CSS 变量 --bar 错落):memo 化——interim 文本每次变化重渲染按钮\n * (麦克风与对话共用同一个按钮)时柱子的虚拟 DOM 不再重建(柱形是静态的,\n * 仅高度由 CSS 变量 --level 在帧循环驱动)。 */\nexport const SpectrumBars = react.memo((): react.ReactElement => (\n <react.Fragment>\n {Array.from({ length: SPECTRUM_BARS }, (_, i) => (\n <span key={i} className=\"dshav-bar\" style={{ '--bar': String(0.35 + (i / (SPECTRUM_BARS - 1)) * 0.65) } as react.CSSProperties} />\n ))}\n </react.Fragment>\n))\n","/**\n * 长按判定(纯逻辑,无 DOM)。\n *\n * 一个按钮要承担两种动作时,「点」和「长按」只能在手势结束时才分出胜负:\n * 按住跨过阈值 → 立刻触发长按;随后的 click 必须被吞掉,否则长按会被当成\n * 点再执行一次(浏览器在长按后照常补发 click)。\n *\n * 分工刻意做成「长按自己管,短按交给原生 click」:\n * 短按不走 pointerup 而走 click,这样键盘 Enter/Space 触发的 click 天然可用,\n * 也不会出现「pointerup 触发一次 + click 再触发一次」的双发。\n */\n\n/** 定时器抽象:测试注入假时钟即可,无需真的等待。 */\nexport interface LongPressTimers {\n set: (fn: () => void, ms: number) => number\n clear: (handle: number) => void\n}\n\n/** 按住多久算长按。450ms 足够区分「点一下」与「按住」,又不会让人觉得迟钝。 */\nexport const DEFAULT_LONG_PRESS_MS = 450\n\nexport interface LongPressOptions {\n /** 按住超过该毫秒数算长按。 */\n thresholdMs: number\n /** 跨过阈值时触发,一次手势最多一次。 */\n onLongPress: () => void\n /**\n * 长按是否可用(如对话功能被关掉时返回 false)。\n * 返回 false 时按下不排定时器,于是按多久都只走短按分支——\n * 避免「长按被禁用,却把用户的点也吞掉」。\n */\n isLongPressAllowed?: () => boolean\n /** 定时器实现,默认用全局 setTimeout。 */\n timers?: LongPressTimers\n}\n\nexport interface LongPressGate {\n /** pointerdown:开始一次手势。重复按下(第二个指针)忽略。 */\n press: () => void\n /** pointerup:结束一次手势。 */\n release: () => void\n /** pointercancel / 卸载:丢弃手势,不触发回调。 */\n cancel: () => void\n /**\n * click 处理器开头调用:本次点击是否来自刚结束的长按。\n * 返回 true 时调用方必须直接 return。读取即复位。\n */\n shouldSuppressClick: () => boolean\n /** 当前是否处于按下状态。 */\n readonly pressed: boolean\n}\n\nconst defaultTimers: LongPressTimers = {\n set: (fn, ms) => globalThis.setTimeout(fn, ms),\n clear: (handle) => {\n globalThis.clearTimeout(handle)\n },\n}\n\nexport function createLongPressGate(options: LongPressOptions): LongPressGate {\n const timers = options.timers ?? defaultTimers\n let timer: number | null = null\n let pressed = false\n let fired = false\n // 跨过一次 release 仍要留到 click 才消费的标记。\n let suppressClick = false\n\n const clearTimer = (): void => {\n if (timer !== null) {\n timers.clear(timer)\n timer = null\n }\n }\n\n const press = (): void => {\n if (pressed) return\n pressed = true\n fired = false\n // 上一轮若被 cancel 打断(长按已触发但浏览器不会补 click),标记会滞留;\n // 新手势开始时清掉,避免它吞掉下一次真正的点击。\n suppressClick = false\n if (options.isLongPressAllowed !== undefined && !options.isLongPressAllowed()) return\n timer = timers.set(() => {\n timer = null\n fired = true\n options.onLongPress()\n }, options.thresholdMs)\n }\n\n const release = (): void => {\n if (!pressed) return\n pressed = false\n clearTimer()\n // 短按不在这里执行——留给随后原生的 click,保证只有一条执行路径。\n if (fired) suppressClick = true\n }\n\n const cancel = (): void => {\n // 先读后清:长按已触发时手势虽被浏览器接管,但动作确实发生过了,\n // 若此时残留一次 click 就会重复触发,所以要把吞掉标记留下。\n if (fired) suppressClick = true\n pressed = false\n fired = false\n clearTimer()\n }\n\n const shouldSuppressClick = (): boolean => {\n const hit = suppressClick\n suppressClick = false\n return hit\n }\n\n return {\n press,\n release,\n cancel,\n shouldSuppressClick,\n get pressed() {\n return pressed\n },\n }\n}\n","/** dsh-asr-voice — 单按钮手势 → 动作的路由(纯逻辑,无 DOM)。\n *\n * 一个按钮同时表达「点 = 转写」和「长按 = 对话」,最容易错的是长按之后浏览器\n * 照常补发的那个 click:不吞掉就会「开对话 + 顺手开始一次转写」。\n * 判定依赖三样东西——长按是否刚触发、对话是否在跑、录音链路在哪一档——\n * 全是从组件里读得出来的标量,所以抽成纯函数在这里锁住。\n */\n\n/** 录音链路状态机(与 voice-button.tsx 的 VoiceState 同源)。 */\nexport type MicPhase = 'idle' | 'recording' | 'transcribing' | 'optimizing'\n\n/** 一次点击该做什么。`none` = 吞掉,不产生任何动作。 */\nexport type ButtonAction = 'none' | 'chat' | 'begin' | 'finish' | 'cancel'\n\nexport interface ButtonPressInput {\n /** 本次 click 是否来自刚结束的长按(由长按门读一次即复位)。 */\n longPress: boolean\n /** 对话是否正在进行。 */\n chatActive: boolean\n /** 录音链路当前状态。 */\n micPhase: MicPhase\n}\n\nexport function routeButtonPress(input: ButtonPressInput): ButtonAction {\n // 长按优先:动作已经在跨阈值那一刻执行过了,这个 click 是浏览器的补充事件。\n if (input.longPress) return 'none'\n // 对话在跑:按钮整体归对话(结束对话 / 打断播报),绝不并到录音链路去。\n if (input.chatActive) return 'chat'\n if (input.micPhase === 'idle') return 'begin'\n if (input.micPhase === 'recording') return 'finish'\n // transcribing / optimizing:点 = 取消(与快捷键路径的「busy 时按一次即打断」一致)。\n return 'cancel'\n}\n","/** dsh-asr-voice — 回合文本闸门(纯逻辑,三引擎共用,node --test 可直跑)。\n * 实时路径不做提示词优化,噪声/幻觉必须拦在上屏之前。分段引擎的本地\n * VAD 可能把底噪切成段送上云(真机复现:无人说话时字幕持续出现「嗯」),\n * 云端对噪声段幻觉出语气词——这些回合既浪费 agent 配额,也污染会话。\n */\nexport function meaningfulTurn(text: string): boolean {\n const t = text.trim()\n const unit = t.replace(/[,。!?、,.!?\\s]/g, '')\n if (unit === '') return false\n // 纯语气词(中文 嗯/啊/哦/呃/额/唉/呀/哈/噢/喔 的任意组合与重复,英文\n // um/uh/emm/hmm/ah/oh/ok/okay 的任意重复)→ 无意义,丢弃。\n if (/^[嗯啊哦呃额唉呀哈噢喔]+$/.test(unit)) return false\n if (/^(um|uh|emm|hmm|ah|oh|ok|okay)+$/i.test(unit)) return false\n // 单字无论是否语气词一律不放行:一个汉字换一轮 agent 思考的代价远高于\n // 丢失一次口语化回复,而「没人说话却一直嗯」正是单字幻觉的高发形态。\n if (unit.length <= 1) return false\n return true\n}\n\n/** 识别器重启后把同一句又报了一遍:这类「重启回声」只该出现在上一回合交出后的\n * 很短窗口内(Chrome 静音自动结束 → 120ms 重启 → 立刻重报)。带时间盒判断:\n * 窗口内同句丢弃;窗口外同句是用户真的又说了一遍,必须放行,否则短句复述会被\n * 当回声吞掉。\n */\nexport const RESTART_ECHO_WINDOW_MS = 2_500\n\nexport function isRestartEcho(\n lastTurn: string,\n lastTurnAt: number,\n chunk: string,\n now: number,\n): boolean {\n if (lastTurn === '' || chunk !== lastTurn) return false\n const age = now - lastTurnAt\n return age >= 0 && age <= RESTART_ECHO_WINDOW_MS\n}","/** dsh-asr-voice — 麦克风 → 16 kHz 单声道 PCM 帧(AudioWorklet,port 回调驱动)。\n * 实时链路的采集底座,本地 VAD 引擎与后续 host 上行通道(I3/I4)共用它。分帧必须由\n * worklet port 回调驱动,**不能用 `setInterval` 排片**:后台标签页定时器被节流到 1/s\n * 会直接饿死上行。\n * 与整段模式相反,这里必须显式请求回声消除:播报期间我们自己的 TTS 会被同一支麦克风\n * 收回来,`{ echoCancellation: true }` 是唯一能白拿的抵消手段(`recorder.ts:59-62` 那组\n * 「故意不设」是另一条路径的教训——显式 `false` 在部分 macOS 设备上会得到纯静音)。\n */\nimport { PCM_SAMPLE_RATE, createPcmAudioContext, peakAbs, resampleLinear } from './pcm.ts'\n\n/** 采集失败码(由 UI 映射成文案)。 */\nexport type CaptureFailure = 'no-mic' | 'no-audio-context' | 'no-worklet' | 'silent-device'\n\n/** 设备链路失效判据:探测期内峰值连噪声底都没到,说明轨道根本没在产出数据 (协商失败时浏览器给的是数字零,而不是环境噪声)。阈值刻意远低于 `SILENCE_PEAK_FLOOR`:那是「用户没说话」的量级,这里是「没有声音」。 */\nconst DEAD_DEVICE_PEAK = 0.0005\n\n/** 判死前的观察窗口(毫秒):安静房间里前几秒没有峰值是常态,别急着报错。 */\nconst DEAD_DEVICE_PROBE_MS = 4_000\n\n/** worklet 处理器名。 */\nconst WORKLET_NAME = 'dshav-pcm-slicer'\n\n/** worklet 源码按行拼接:直接写一大段 JS 字面量会撞上本仓已知的输出静默截断坑。 改动这里必须回读 `lib/client.js` 确认落盘。 */\nconst WORKLET_LINES = [\n 'class DshavPcmSlicer extends AudioWorkletProcessor {',\n ' constructor(options) {',\n ' super()',\n ' const frameMs = (options && options.processorOptions && options.processorOptions.frameMs) || 40',\n ' this.n = Math.max(1, Math.round(sampleRate * frameMs / 1000))',\n ' this.buf = new Float32Array(this.n)',\n ' this.len = 0',\n ' }',\n ' process(inputs) {',\n ' const ch = inputs[0] ? inputs[0][0] : null',\n ' if (ch) {',\n ' for (let i = 0; i < ch.length; i++) {',\n ' this.buf[this.len++] = ch[i]',\n ' if (this.len >= this.n) {',\n ' const out = this.buf.slice(0)',\n ' this.port.postMessage(out, [out.buffer])',\n ' this.len = 0',\n ' }',\n ' }',\n ' }',\n ' return true',\n ' }',\n '}',\n `registerProcessor('${WORKLET_NAME}', DshavPcmSlicer)`,\n]\n\n/** 一次采集会话。 */\nexport interface PcmCapture {\n /** 停轨 + 断图 + 撤销模块 URL(幂等)。 */\n stop(): void\n/** 半双工门控:关轨道而不是丢帧——轨道 enabled=false 让设备真正停止产出, 播报期间既省电平表也少一份回声输入。 */\n setMuted(muted: boolean): void\n}\n\n/** 采集参数。 */\nexport interface PcmCaptureOptions {\n /** 每帧时长(毫秒)。 */\n frameMs: number\n /** 一帧 16k 单声道采样(已按需重采样)。 */\n onFrame(pcm: Float32Array): void\n /** 链路失败:调用后本会话已结束,不会再有 onFrame。 */\n onFail(code: CaptureFailure): void\n}\n\n/** 采集用 AudioContext:懒创建并跨会话复用(新建上下文的开销和数量上限都不划算)。 */\nlet captureCtx: AudioContext | null = null\n\nfunction getCaptureCtx(): AudioContext | null {\n if (captureCtx !== null) return captureCtx\n try {\n // 请求 16k(见 createPcmAudioContext):不请求就要自己抽点重采样,48k→16k\n // 会丢抗混叠低通。\n captureCtx = createPcmAudioContext('interactive')\n } catch {\n return null\n }\n return captureCtx\n}\n\n/** 是否具备 PCM 采集的基本条件(精确的 worklet 缺失只能在运行时报 `no-worklet`)。 */\nexport function isPcmCaptureSupported(): boolean {\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) return false\n const windowLike = window as unknown as { AudioContext?: unknown; webkitAudioContext?: unknown }\n return Boolean(windowLike.AudioContext ?? windowLike.webkitAudioContext)\n}\n\n/** 实时采集需要的约束:显式请求 AEC,其余交给浏览器默认。 */\nfunction micConstraints(): MediaStreamConstraints {\n return {\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n channelCount: 1,\n },\n video: false,\n }\n}\n\nconst NOOP_CAPTURE: PcmCapture = { stop: () => {}, setMuted: () => {} }\n\n/** 打开麦克风并按 `frameMs` 产出 16k 帧。\n * 不 reject:所有失败都以 `onFail(code)` 送达并返回一个空操作会话,调用方只有一条\n * 错误出口。前若干语句是同步的——Safari 只在用户激活上下文里允许建立音频会话,\n * 所以调用方必须在点击回调里同步调它。\n */\nexport async function startPcmCapture(options: PcmCaptureOptions): Promise<PcmCapture> {\n const ctx = getCaptureCtx()\n if (ctx === null) {\n options.onFail('no-audio-context')\n return NOOP_CAPTURE\n }\n void ctx.resume().catch(() => {})\n\n let stream: MediaStream\n try {\n stream = await navigator.mediaDevices.getUserMedia(micConstraints())\n } catch {\n options.onFail('no-mic')\n return NOOP_CAPTURE\n }\n\n if (typeof ctx.audioWorklet?.addModule !== 'function') {\n stream.getTracks().forEach((t) => t.stop())\n options.onFail('no-worklet')\n return NOOP_CAPTURE\n }\n\n const blobUrl = URL.createObjectURL(new Blob([WORKLET_LINES.join('\\n')], { type: 'application/javascript' }))\n let node: AudioWorkletNode | null = null\n let source: MediaStreamAudioSourceNode | null = null\n let mutedSink: GainNode | null = null\n let stopped = false\n let probeTimer: ReturnType<typeof setTimeout> | null = null\n /** 重采样只在浏览器没按 16k 交付时才做(Safari 会忽略请求的采样率)。 */\n const sourceRate = ctx.sampleRate\n let probePeak = 0\n let probeDone = false\n /** 半双工静音态:探针窗口内 setMuted(true) 会让轨道产出全零帧,探针会\n * 误判成「设备静音」把好端端的会话杀掉(4s 探针期内用户说完一句即 pause\n * 的短句对话必然命中)。静音期既不累积峰值,也把探针视为完成——静音守卫\n * (isSilentPeak)在段级继续兜底真实无声,这里只负责「链路是否产出过数据」。 */\n let muted = false\n\n const teardown = (): void => {\n if (stopped) return\n stopped = true\n if (probeTimer !== null) clearTimeout(probeTimer)\n probeTimer = null\n if (node) node.port.onmessage = null\n try { node?.disconnect() } catch { /* noop */ }\n try { mutedSink?.disconnect() } catch { /* noop */ }\n try { source?.disconnect() } catch { /* noop */ }\n node = null\n source = null\n mutedSink = null\n stream.getTracks().forEach((t) => t.stop())\n URL.revokeObjectURL(blobUrl)\n }\n\n function onMessage(event: MessageEvent<Float32Array>): void {\n if (stopped) return\n const raw = event.data\n const pcm = sourceRate === PCM_SAMPLE_RATE ? raw : resampleLinear(raw, sourceRate, PCM_SAMPLE_RATE)\n if (!probeDone && !muted) {\n probePeak = Math.max(probePeak, peakAbs(pcm))\n }\n options.onFrame(pcm)\n }\n\n try {\n await ctx.audioWorklet.addModule(blobUrl)\n node = new AudioWorkletNode(ctx, WORKLET_NAME, {\n numberOfInputs: 1,\n numberOfOutputs: 1,\n channelCount: 1,\n channelCountMode: 'explicit',\n channelInterpretation: 'discrete',\n processorOptions: { frameMs: options.frameMs },\n })\n source = ctx.createMediaStreamSource(stream)\n // worklet 必须有一条通向 destination 的路才会被拉起来;这条路上的增益为 0,\n // 绝不能再把麦克风原声还给扬声器,否则实时模式自激啸叫。\n mutedSink = ctx.createGain()\n mutedSink.gain.value = 0\n source.connect(node)\n node.connect(mutedSink)\n mutedSink.connect(ctx.destination)\n // 必须是 onmessage(或显式 port.start()):MessagePort 只 addEventListener 不会进入\n // actively receiving 状态,真机实测一帧都不投,整条链路表现为「设备静音」。\n node.port.onmessage = onMessage\n } catch {\n stream.getTracks().forEach((t) => t.stop())\n URL.revokeObjectURL(blobUrl)\n options.onFail('no-worklet')\n return NOOP_CAPTURE\n }\n\n probeTimer = setTimeout(() => {\n probeDone = true\n // 探针窗口内处于半双工静音:该会话已证明在收音(pause 前有产出),\n // 静音守卫在段级兜底,这里不再判死。\n if (stopped || muted || probePeak >= DEAD_DEVICE_PEAK) return\n teardown()\n options.onFail('silent-device')\n }, DEAD_DEVICE_PROBE_MS)\n\n return {\n stop(): void {\n teardown()\n },\n setMuted(next: boolean): void {\n muted = next\n stream.getAudioTracks().forEach((t) => { t.enabled = !next })\n },\n }\n}\n","/** dsh-asr-voice — 本地能量 VAD(纯函数,模块顶层不碰 DOM,可被 node --test 直接跑源码)。\n * 近实时引擎用它把连续麦克风切成一句一句,每句走已有的整段转写通道:不需要新协议,\n * 也不需要任何云商 key。判据只有 RMS,因此阈值是**设备噪声底**的函数——调高会切掉\n * 轻声句尾,调低会把呼吸和键盘当成话;两者都是可预期的失效,不是 bug。\n * 段边界规则:有声窗开启一段并带上 `prerollMs` 的段前缓冲(否则第一个音节必被切掉),\n * 连续静音 `silenceMs` 关闭一段,实际语音时长不足 `minSpeechMs` 的段直接丢弃(杂音不\n * 该花一次上游配额),说到 `maxSegmentMs` 还没停则强制轮换——它同时是单次上传体大小\n * 的上限。段内保留收尾静音:那段静音正是「这句说完了」的证据,省它省不出正确性。\n */\nimport { rmsOfFloat } from './pcm.ts'\n\n/** VAD 参数(来自 settings 的 realtime.vad.*,见 `config.ts`)。 */\nexport interface VadTuning {\n /** RMS 判有声的阈值(0~1),也是 rmsAuto 关闭时的唯一判据。 */\n rms: number\n /** 开启后阈值 = max(rms, 自适应噪声底 × 裕量),换设备免重校。 */\n rmsAuto?: boolean\n /** 连续静音多久切一段(毫秒)。 */\n silenceMs: number\n /** 段前保留多久的音频(毫秒)。 */\n prerollMs: number\n /** 实际语音短于此不成为一段(毫秒)。 */\n minSpeechMs: number\n /** 单段语音长度上限(毫秒)。 */\n maxSegmentMs: number\n}\n\n/** 自适应噪声底来源(`rms-floor.ts`):VAD 用它把阈值变成设备噪声底的函数。 */\nexport interface RmsFloorSource {\n /** 投喂一窗 RMS 与 VAD 当前是否判着有声;语音期帧不更新底噪。 */\n observe(rms: number, speechActive: boolean): void\n /** 当前建议阈值(未学到时 0 = 回退 tuning.rms)。 */\n readonly threshold: number\n}\n\n/** VAD 事件。 */\nexport interface VadEvents {\n /** 一段完整语音(16k Float32,含段前缓冲)。 */\n onSegment(pcm: Float32Array): void\n /** 有声/无声边界,供 UI 表示「正在听你说」。 */\n onSpeech(active: boolean): void\n}\n\n/** 帧驱动的状态机。 */\nexport interface EnergyVad {\n /** 投喂采集帧(任意长度,内部按分析窗切分)。 */\n feed(chunk: Float32Array): void\n /** 立即结束当前段并交出(pause/stop/上限时调用)。 */\n flush(): void\n /** 清空一切声学状态(不交出内容)。 */\n reset(): void\n readonly inSpeech: boolean\n}\n\n/** 分析窗长(毫秒)。固定不开放:窗长是判定的时间分辨率而不是偏好,放到设置里 就允许出现 `silenceMs < frameMs` 这类自相矛盾的组合。\n * **导出给调用方折算观测窗**:`rmsAuto` 的噪声底估计器按本窗长被投喂(每 20ms 一次,\n * 与采集帧长 `realtime.vad.frameMs` 无关),估计器用采集帧长折算窗口就会把真实观测窗\n * 缩到标称值的一半(2s → 1s),底噪估计随之变抖、也更易被开场的语音带偏。 */\nexport const VAD_WINDOW_MS = 20\n\n/** 把若干窗拼成一段连续采样。 */\nfunction concatWindows(windows: Float32Array[]): Float32Array {\n let n = 0\n for (const w of windows) n += w.length\n const out = new Float32Array(n)\n let off = 0\n for (const w of windows) {\n out.set(w, off)\n off += w.length\n }\n return out\n}\n\n/** 构造一个能量 VAD。`sampleRate` 必须是投喂帧的采样率(本项目为 PCM_SAMPLE_RATE)。 `floor` 可选:`tuning.rmsAuto` 时 VAD 每窗把 RMS 与语音期判定反馈给它,并用 `floor.threshold`(未学到时为 0)抬高/放低实际判据。 */\nexport function createEnergyVad(sampleRate: number, tuning: VadTuning, events: VadEvents, floor?: RmsFloorSource | null): EnergyVad {\n const windowSamples = Math.max(1, Math.round((sampleRate * VAD_WINDOW_MS) / 1000))\n const prerollWindows = Math.max(0, Math.round(tuning.prerollMs / VAD_WINDOW_MS))\n const silenceWindows = Math.max(1, Math.round(tuning.silenceMs / VAD_WINDOW_MS))\n const minSpeechWindows = Math.max(1, Math.round(tuning.minSpeechMs / VAD_WINDOW_MS))\n const maxSegmentWindows = Math.max(minSpeechWindows + 1, Math.round(tuning.maxSegmentMs / VAD_WINDOW_MS))\n\n const win = new Float32Array(windowSamples)\n let winLen = 0\n let preroll: Float32Array[] = []\n let segment: Float32Array[] = []\n /** 当前段内**有声**窗数:静音不计,所以它衡量的是「真说了多久」。 */\n let speechWindows = 0\n let silenceRun = 0\n let speaking = false\n\n const emitSegment = (): void => {\n const windows = segment\n const spoken = speechWindows\n segment = []\n speechWindows = 0\n if (windows.length === 0 || spoken < minSpeechWindows) return\n events.onSegment(concatWindows(windows))\n }\n\n const handleWindow = (w: Float32Array): void => {\n const rms = rmsOfFloat(w)\n const auto = tuning.rmsAuto === true && floor !== undefined && floor !== null\n floor?.observe(rms, speaking)\n // 学习期纪律:floor 未就绪(threshold === 0)时阈值回退用户基线,而噪声底高于\n // 基线时学习窗本身会被判成语音 → 估计器永远学不到、段被整体吃掉。所以在\n // threshold 就绪之前 VAD 只积累段前缓冲、不开段不说话(约一个观测窗长,默\n // 认 2s),学满后自动恢复正式判定。\n const learning = auto && floor?.threshold === 0\n if (learning) {\n preroll.push(w)\n if (preroll.length > prerollWindows) preroll.shift()\n return\n }\n const effectiveRms = auto ? Math.max(tuning.rms, floor!.threshold) : tuning.rms\n const voiced = rms > effectiveRms\n if (!speaking) {\n if (!voiced) {\n preroll.push(w)\n if (preroll.length > prerollWindows) preroll.shift()\n return\n }\n speaking = true\n silenceRun = 0\n speechWindows = 1\n segment = [...preroll, w]\n preroll = []\n events.onSpeech(true)\n return\n }\n segment.push(w)\n if (voiced) {\n speechWindows += 1\n silenceRun = 0\n } else {\n silenceRun += 1\n }\n if (silenceRun >= silenceWindows) {\n speaking = false\n emitSegment()\n events.onSpeech(false)\n return\n }\n if (speechWindows >= maxSegmentWindows) emitSegment()\n }\n\n /** 把攒着的半窗当一窗处理掉:那通常是用户刚说完的最后几十毫秒。 */\n const drainPartial = (): void => {\n if (winLen === 0) return\n const tail = win.slice(0, winLen)\n winLen = 0\n handleWindow(tail)\n }\n\n return {\n feed(chunk: Float32Array): void {\n for (let i = 0; i < chunk.length; i++) {\n win[winLen++] = chunk[i] ?? 0\n if (winLen < windowSamples) continue\n winLen = 0\n handleWindow(win.slice(0))\n }\n },\n flush(): void {\n drainPartial()\n if (!speaking) return\n speaking = false\n silenceRun = 0\n emitSegment()\n events.onSpeech(false)\n },\n reset(): void {\n const wasSpeaking = speaking\n winLen = 0\n preroll = []\n segment = []\n speechWindows = 0\n silenceRun = 0\n speaking = false\n if (wasSpeaking) events.onSpeech(false)\n },\n get inSpeech(): boolean {\n return speaking\n },\n }\n}\n","/** dsh-asr-voice — 自适应噪声底估计器(纯逻辑,模块顶层不碰 DOM,node --test 可直跑)。\n * 能量 VAD 的阈值是「设备噪声底」的函数,而噪声底随设备/环境变化(内置麦 vs 耳机麦、\n * 空调/风扇/键盘)。本估计器在**静音期**持续观测 RMS,维护一个分位数底噪估计,\n * 供两类消费者使用:\n * 1. `vad.rmsAuto`:segmented 引擎的 VAD 阈值 = max(用户基线, floor × margin)——\n * 噪声环境自动抬高阈值,安静环境自动放低,换设备免重校。\n * 2. barge-in 门控(D19):播放 TTS 期间以「播放窗 p25」估回声底,人声须显著\n * 超出才打断,不把机器人自己的声音当成说话。\n * 关键纪律:\n * - 只吃**静音期**的帧(调用方用 VAD 自身的 inSpeech 判定喂过来)——语音帧会\n * 污染分位数,让底噪被抬高。\n * - 新加入的帧是「观测值」,不是直接令 floor = 观测值:走环形缓冲分位数,\n * 呼吸/键盘这类瞬时尖峰拉不动中位数。\n * - threshold 未学到(缓冲未满)时返回 0,调用方回退用户配置的基线阈值。\n */\nexport interface RmsFloorTuning {\n /** 静音期观测窗长(毫秒):越短对噪声变化越灵敏,越短越容易在说话间隙学错。 */\n windowMs: number\n /** 分位数等级(0~1):越低取噪底越保守(阈值更贴近底),越高越激进。 */\n quantile: number\n /** 阈值裕量倍率:threshold = floor × margin。语音突发通常比底噪高一个量级。 */\n margin: number\n /** 帧长(毫秒),用于把 windowMs 折算成缓冲长度。 */\n frameMs: number\n}\n\n/** 估计器默认参数:2s 观测窗、p30、×3 裕量、40ms 帧。 */\nexport const DEFAULT_RMS_FLOOR_TUNING: RmsFloorTuning = {\n windowMs: 2_000,\n quantile: 0.3,\n margin: 3,\n frameMs: 40,\n}\n\n/** 噪声底估计器的最小面(VAD 与 barge-in 门控都只依赖它)。 */\nexport interface RmsFloorEstimator {\n /** 投喂一帧 RMS 与「这一帧是否处于语音期」。语音期帧不参与底噪学习。 */\n observe(rms: number, speechActive: boolean): void\n /** 当前阈值 = floor × margin;缓冲未满时返回 0(调用方回退基线)。 */\n readonly threshold: number\n /** 当前底噪估计(0 = 尚无观测)。 */\n readonly floor: number\n reset(): void\n}\n\n\n/** 环形缓冲 → 排序副本 → 分位数取值(两处消费者共用;插入排序对小窗口最快)。 */\nfunction quantileOf(buf: Float64Array, sorted: Float64Array, len: number, quantile: number): number {\n if (len === 0) return 0\n for (let i = 0; i < len; i++) sorted[i] = buf[i] ?? 0\n for (let i = 1; i < len; i++) {\n const v = sorted[i] ?? 0\n let j = i - 1\n while (j >= 0 && (sorted[j] ?? 0) > v) { sorted[j + 1] = sorted[j] ?? 0; j -= 1 }\n sorted[j + 1] = v\n }\n const idx = Math.min(len - 1, Math.max(0, Math.floor(len * quantile)))\n return sorted[idx] ?? 0\n}\n\n/** 构造估计器。`windowMs`/`quantile`/`margin` 来自 tuning。 */\nexport function createRmsFloorEstimator(tuning: RmsFloorTuning = DEFAULT_RMS_FLOOR_TUNING): RmsFloorEstimator {\n const cap = Math.max(4, Math.round(tuning.windowMs / tuning.frameMs))\n const buf = new Float64Array(cap)\n const sorted = new Float64Array(cap)\n let len = 0\n let head = 0\n\n const observe = (rms: number, speechActive: boolean): void => {\n if (speechActive || !Number.isFinite(rms) || rms <= 0) return\n buf[head] = rms\n head = (head + 1) % cap\n if (len < cap) len += 1\n }\n\n const floor = (): number => quantileOf(buf, sorted, len, tuning.quantile)\n\n return {\n observe,\n get floor(): number { return floor() },\n get threshold(): number {\n const f = floor()\n return len >= cap ? f * tuning.margin : 0\n },\n reset(): void {\n len = 0\n head = 0\n },\n }\n}\n\n/** barge-in 回声底门控:播放 TTS 期间估计「回声 + 噪声」的背景水平,人声须超过\n * `background × headroom` 并持续 `holdMs` 才判为一次打断(barge-in)。\n * 背景水平 = max(播放窗分位数, 静音底噪估计)。播放窗自带宽限期:刚 arm 时不触发,\n * 先把「TTS 外放在麦克风里的水平」学到,否则机器人开口第一句就会被自己的回声打断。\n */\nexport interface BargeInGate {\n /** 播放开始:清窗进入宽限,开始积累回声观测。 */\n arm(): void\n /** 播放结束(sink 排空/被打断):回到非门控状态,窗口清空。 */\n disarm(): void\n /** 喂一帧 RMS 与 VAD 语音期判定;返回 true = 判定为一次真正的打断。 */\n feed(rms: number, speechActive: boolean): boolean\n readonly armed: boolean\n}\n\nexport interface BargeInTuning {\n /** 宽限期(毫秒):arm 后这么久内不触发,用来学习回声水平。 */\n graceMs: number\n /** 回声观测窗长(毫秒)。 */\n windowMs: number\n /** 背景分位数(0~1):取回声背景的保守估计,人声尖峰不应抬高它。 */\n quantile: number\n /** 触发倍率:RMS > background × headroom 才算人声。 */\n headroom: number\n /** 持续超出多久才触发(毫秒):过滤键盘/关门等瞬态。 */\n holdMs: number\n /** 帧长(毫秒)。 */\n frameMs: number\n/** 最低背景门槛(RMS):低于它的帧是静音空隙(TTS 停顿/呼吸间),不参与背景\n * 学习——否则「有声-静音交替」窗口里 p25 会被静音帧拉低,TTS 本身的音量\n * 反而超出「背景」,把自己的回声当成打断。调用方传 VAD 的 `rms`(引擎侧\n * 已含 rmsAuto 抬升,语义即「设备上有声音的水平」)。\n */\n baselineRms?: number\n}\n\nexport const DEFAULT_BARGE_IN_TUNING: BargeInTuning = {\n graceMs: 800,\n windowMs: 1_600,\n quantile: 0.25,\n headroom: 3,\n holdMs: 350,\n frameMs: 40,\n baselineRms: 0.02,\n}\n\nexport function createBargeInGate(tuning: BargeInTuning = DEFAULT_BARGE_IN_TUNING): BargeInGate {\n const cap = Math.max(4, Math.round(tuning.windowMs / tuning.frameMs))\n const buf = new Float64Array(cap)\n const sorted = new Float64Array(cap)\n let len = 0\n let head = 0\n let armed = false\n /** 宽限期内已消耗的帧数。 */\n let graceLeft = 0\n /** 当前「持续超出背景」的帧数(超出持续了多久)。 */\n let overRun = 0\n /** 静音期底噪(不用单独估计器:把低于背景的观测当噪声学进窗口,天然含底噪)。 */\n let background = 0\n\n const push = (rms: number): void => {\n // 静音空隙(TTS 停顿/呼吸)低于基线,不学——否则 p25 被拉低,回声自身反而\n // 会超出「背景」把自己当成人声打断(真机探针抓出的缺陷)。\n if (rms < (tuning.baselineRms ?? 0)) return\n // 只学「不太高」的观测:语音尖峰会拉高背景,让人声更难触发。\n if (background > 0 && rms > background * tuning.headroom) return\n buf[head] = rms\n head = (head + 1) % cap\n if (len < cap) len += 1\n }\n\n const updateBackground = (): void => {\n background = quantileOf(buf, sorted, len, tuning.quantile)\n }\n\n const feed = (rms: number, _speechActive: boolean): boolean => {\n if (!armed) return false\n if (graceLeft > 0) {\n push(rms)\n graceLeft -= 1\n if (graceLeft === 0) updateBackground()\n return false\n }\n push(rms)\n // 周期刷新背景(每 8 帧一次,避免每帧全排序)。\n if ((head % 8) === 0) updateBackground()\n // 背景未学到(全静音窗)时不判 over:空背景会让任何环境声都「超底」而误断。\n const over = background > 0 && rms > background * tuning.headroom\n if (!over) { overRun = 0; return false }\n overRun += 1\n if (overRun < Math.max(1, Math.round(tuning.holdMs / tuning.frameMs))) return false\n // 一次性事件:触发即 disarm,调用方负责在下一句播放时重新 arm。\n armed = false\n len = 0\n head = 0\n overRun = 0\n return true\n }\n\n return {\n arm(): void {\n armed = true\n len = 0\n head = 0\n graceLeft = Math.max(1, Math.round(tuning.graceMs / tuning.frameMs))\n overRun = 0\n background = 0\n },\n disarm(): void {\n armed = false\n len = 0\n head = 0\n graceLeft = 0\n overRun = 0\n },\n feed,\n get armed(): boolean { return armed },\n }\n}","/** dsh-asr-voice — 实时转写引擎·云端通道(cloud)。\n * I4 交付:把 `capture.ts` 的 16k 采集帧上行到 host 实时通道(I3 的\n * `RealtimeHost`),下行经 SSE 收到 `RealtimeProviderEvent`,驱动字幕与回合。\n * 这第三档引擎与 browser(Web Speech)/ segmented(本地 VAD + 整段转写)的\n * 区别:**回合边界由服务端 VAD 给**(provider 发 speech-stopped/final),\n * 本地不再用文字静默判定——换来的是逐字延迟更低的流式体验,代价是必须有\n * 一条 host 实时通道(I3 已交付,I3 阶段用假 provider,I5 换真云端)。\n * 传输层是注入的(`CloudTransport`):单测直接喂假事件与假采集,不碰网络。\n * 真实的浏览器实现见 `realtime-cloud-transport.ts`。\n * 回合语义:provider 发 `final` 即「这一句说完了」,直接 onTurn;`partial`\n * 驱动字幕;`error` 判死。采集帧在**静音守卫**(趋零不上行)之后量化成\n * int16 LE 字节逐帧上行。\n */\nimport { startPcmCapture, type PcmCapture, type PcmCaptureOptions } from './capture.ts'\nimport { peakAbs, quantiseInt16 } from './pcm.ts'\nimport type { RealtimeEvents, RealtimeSession } from './realtime.ts'\nimport { meaningfulTurn } from './turn-guard.ts'\n\n/** 云端通道收到的上游事件(镜像 host 的 RealtimeProviderEvent 形状)。 */\nexport type CloudProviderEvent =\n | { type: 'partial'; text: string }\n | { type: 'final'; text: string }\n | { type: 'speech-started' }\n | { type: 'speech-stopped' }\n | { type: 'error'; code: string }\n\n/** 云端通道的传输层(浏览器 fetch 实现见 realtime-cloud-transport.ts)。 */\nexport interface CloudTransport {\n /** 建会话,返回 host 铸造的 sid。 */\n createSession(): Promise<string>\n /** 上行一段 int16 LE 16k 单声道 PCM 字节。 */\n upload(sid: string, pcm: Uint8Array): Promise<void>\n /** 打开 SSE 下行;返回取消订阅的 disposer。 */\n openEvents(sid: string, onEvent: (ev: CloudProviderEvent) => void): () => void\n /** 关会话(幂等)。 */\n closeSession(sid: string): Promise<void>\n}\n\n/** 可注入依赖(单测借此跑真状态机,不碰 DOM/网络)。 */\nexport interface CloudRealtimeDeps {\n capture(options: PcmCaptureOptions): Promise<PcmCapture>\n transport: CloudTransport\n}\n\n/** Float32 帧 → int16 LE 字节(16k 单声道;归一化只在需要时做一次)。 */\nexport function floatToInt16Le(pcm: Float32Array, gain = 1): Uint8Array {\n const out = new Uint8Array(pcm.length * 2)\n const view = new DataView(out.buffer)\n for (let i = 0; i < pcm.length; i++) {\n view.setInt16(i * 2, quantiseInt16(pcm[i] ?? 0, gain), true)\n }\n return out\n}\n\n/** 连续失败判死阈值(上游 error 达此数即结束会话)。 */\nconst CLOUD_FAIL_LIMIT = 3\n\n/** 上行泵队列上限:镜像 host 侧 pending 缓冲 cap(64 帧 ≈ 2.6s @40ms)。\n * 网络上行慢于采集时队列有界,满了丢最旧帧——实时音频无重放价值,保新不保旧。 */\nconst UPLOAD_PUMP_CAP = 64\n\n/** 上行串行泵:采集帧在回调里来,网络上行是异步的——必须排队逐帧发,不能并发堆叠。 */\nfunction createUploadPump(upload: (pcm: Uint8Array) => Promise<void>): {\n push(pcm: Uint8Array): void\n /** 清空队列(pause/stop 时用,丢弃未发的帧)。 */\n clear(): void\n} {\n let queue: Uint8Array[] = []\n let inFlight = false\n const pump = (): void => {\n if (inFlight) return\n const next = queue.shift()\n if (next === undefined) return\n inFlight = true\n // upload 失败(HTTP 非 2xx / AbortSignal.timeout 超时)静默丢这帧、继续下一帧——\n // 实时上行没有重试价值(重试的旧帧已过时)。catch 必须消费 rejection,否则\n // fetch 超时路径会留 unhandled rejection。\n void upload(next).catch(() => {}).finally(() => { inFlight = false; pump() })\n }\n return {\n push(pcm) {\n // 有界:上行慢于采集(网络慢/挂起)时不能无界堆叠;cap 与 host 侧一致,\n // 满了丢最旧帧(镜像 host 的 SSE 降级策略:保新不保旧)。\n if (queue.length >= UPLOAD_PUMP_CAP) queue.shift()\n queue.push(pcm)\n pump()\n },\n clear() {\n queue = []\n },\n }\n}\n\n/** 云端实时引擎:采集帧 → int16 上行 → SSE 事件驱动字幕/回合。 @param deps - 采集 + 传输层(测试注入;浏览器用默认实现)。 */\nexport function createCloudRealtime(\n tuning: { frameMs: number },\n events: RealtimeEvents,\n deps: CloudRealtimeDeps,\n): RealtimeSession {\n let active = false\n let paused = true\n let capture: PcmCapture | null = null\n let disposeEvents: (() => void) | null = null\n let sid = ''\n let failures = 0\n let pump = createUploadPump((pcm) => deps.transport.upload(sid, pcm))\n /** 会话代际:stop/start 换代会作废在途的上行与事件。 */\n let generation = 0\n\n const teardown = (): void => {\n pump.clear()\n disposeEvents?.()\n disposeEvents = null\n capture?.stop()\n capture = null\n }\n\n // failNow 全程同步:单线程内无人能中途改代际(generation 只在 start/stop 入口变更),\n // `gen === generation` 恒真。真正需要代际守卫的是异步回调节点(start 的\n // createSession.then 已各自带 `gen === generation` 守卫),此处直接判死送达。\n const failNow = (code: string): void => {\n active = false\n paused = true\n teardown()\n if (sid !== '') void deps.transport.closeSession(sid).catch(() => {})\n events.onFail(code)\n }\n\n const onProviderEvent = (ev: CloudProviderEvent): void => {\n if (!active) return\n if (ev.type === 'error') {\n // 错误不受 pause 门控:断流常发生在播报期(半双工暂停中),吞掉它\n // 引擎就聋死到下一次 resume 也没有任何提示。\n failures += 1\n // 事件流断了 = 这一局的回合再也到不了(final 全靠 SSE 下行),立即判死;\n // 其余 provider 错误仍按连败阈值处理。\n if (ev.code === 'events-unavailable' || failures >= CLOUD_FAIL_LIMIT) {\n failNow(ev.code || 'provider-unreachable')\n }\n return\n }\n if (paused) return\n if (ev.type === 'partial') {\n events.onPartial(ev.text)\n } else if (ev.type === 'final') {\n failures = 0\n const text = ev.text.trim()\n if (text !== '' && meaningfulTurn(text)) events.onTurn(text)\n }\n // speech-started / speech-stopped:服务端 VAD 边界,本地不消费(final 已含文本)。\n }\n\n const openCapture = (): void => {\n deps.capture({\n frameMs: tuning.frameMs,\n onFrame: (pcm) => {\n if (!active || paused) return\n // 静音守卫:趋零帧上行只会换来上游幻觉字(与整段模式同一判据)。\n if (peakAbs(pcm) < 0.005) return\n pump.push(floatToInt16Le(pcm))\n },\n onFail: (code) => { if (active) failNow(code) },\n }).then((next) => {\n if (!active || paused) { next.stop(); return }\n capture = next\n }, () => {\n if (active) failNow('capture-failed')\n })\n }\n\n return {\n start(): void {\n if (active) return\n active = true\n paused = false\n const gen = ++generation\n pump = createUploadPump((pcm) => deps.transport.upload(sid, pcm))\n void deps.transport.createSession().then((next) => {\n if (!active || gen !== generation) { void deps.transport.closeSession(next).catch(() => {}); return }\n sid = next\n disposeEvents = deps.transport.openEvents(next, onProviderEvent)\n openCapture()\n }, () => {\n if (active && gen === generation) failNow('provider-unreachable')\n })\n },\n pause(): void {\n if (!active || paused) return\n paused = true\n pump.clear()\n capture?.setMuted(true)\n },\n resume(): void {\n if (!active || !paused) return\n paused = false\n capture?.setMuted(false)\n if (capture === null) openCapture()\n },\n stop(): void {\n if (!active) return\n active = false\n paused = true\n generation += 1\n const closeSid = sid\n teardown()\n if (closeSid !== '') void deps.transport.closeSession(closeSid).catch(() => {})\n },\n get listening(): boolean { return active && !paused },\n }\n}\n\n/** 默认采集(真实麦克风)。 */\nexport function defaultCloudCapture(options: PcmCaptureOptions): Promise<PcmCapture> {\n return startPcmCapture(options)\n}\n","/** dsh-asr-voice — 云端实时引擎的浏览器传输层(真实 fetch/SSE 实现)。\n * 对应 host 半区 `RealtimeHost` 的 4 条 exact 路由:\n * POST /api/asr-voice/realtime/session → { ok, sid }\n * POST /api/asr-voice/realtime/audio → 上行 int16 PCM(?sid=…)\n * GET /api/asr-voice/realtime/events → SSE 下行(?sid=…)\n * POST /api/asr-voice/realtime/close → 关会话(?sid=…)\n * 信任围栏由 host 侧 `isTrusted` 负责(本机回环同源天然通过),client 侧不\n * 携带任何密钥。SSE 用 fetch + ReadableStream 逐行解析 `data: …`(不用\n * EventSource:后者无法带 AbortController 精确关闭,且对错误恢复的控制更弱)。\n */\nimport type { CloudProviderEvent, CloudTransport } from './realtime-cloud.ts'\n\nconst BASE = '/api/asr-voice/realtime'\n\n/** 会话建连/上行超时(毫秒):host 本机回环,正常远低于此。 */\nconst ROUTE_TIMEOUT_MS = 15_000\n\n/** SSE 单帧缓冲上限(字符):host 被异常对端喂进无换行巨型帧时,不能无限累积。\n * 正常事件(含大段字幕)远小于此,只兜底断帧/恶意对端。 */\nconst MAX_SSE_BUF = 64 * 1024\n\n/** 一次简单 POST,返回 JSON;非 ok 抛错。 */\nasync function postJson(path: string, search = ''): Promise<{ ok?: boolean; sid?: string; reason?: string }> {\n const res = await fetch(`${BASE}${path}${search}`, {\n method: 'POST',\n signal: AbortSignal.timeout(ROUTE_TIMEOUT_MS),\n })\n const data = (await res.json().catch(() => ({}))) as { ok?: boolean; sid?: string; reason?: string }\n if (!res.ok || data.ok !== true) throw new Error(data.reason || `realtime route ${path} failed`)\n return data\n}\n\n/** 上行一段 PCM(int16 LE)。 */\nasync function uploadAudio(sid: string, pcm: Uint8Array): Promise<void> {\n // Uint8Array<ArrayBufferLike> → fresh Uint8Array<ArrayBuffer> so it satisfies\n // BodyInit under the DOM/undici typings (Node/TS6 Buffer is ArrayBufferLike).\n const bytes = new Uint8Array(pcm.byteLength)\n bytes.set(pcm)\n const res = await fetch(`${BASE}/audio?sid=${encodeURIComponent(sid)}`, {\n method: 'POST',\n headers: { 'content-type': 'application/octet-stream' },\n body: bytes,\n signal: AbortSignal.timeout(ROUTE_TIMEOUT_MS),\n })\n if (!res.ok) {\n const data = (await res.json().catch(() => ({}))) as { reason?: string }\n throw new Error(data.reason || `audio upload failed (HTTP ${res.status})`)\n }\n}\n\n/** 打开 SSE 下行:返回取消订阅的 disposer(幂等)。 */\nfunction openEvents(sid: string, onEvent: (ev: CloudProviderEvent) => void): () => void {\n const controller = new AbortController()\n let disposed = false\n void (async () => {\n try {\n const res = await fetch(`${BASE}/events?sid=${encodeURIComponent(sid)}`, {\n headers: { accept: 'text/event-stream' },\n signal: controller.signal,\n })\n if (!res.ok || res.body === null) {\n if (!disposed) onEvent({ type: 'error', code: 'events-unavailable' })\n return\n }\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buf = ''\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buf += decoder.decode(value, { stream: true })\n let nl\n while ((nl = buf.indexOf('\\n\\n')) >= 0) {\n const block = buf.slice(0, nl)\n buf = buf.slice(nl + 2)\n for (const line of block.split('\\n')) {\n if (line.startsWith('data: ')) {\n try {\n onEvent(JSON.parse(line.slice(6)) as CloudProviderEvent)\n } catch { /* 非 JSON 事件(心跳注释等)忽略 */ }\n }\n }\n }\n // 背压防线:host 被异常对端喂进无换行的巨型帧(或断帧)时,buf 不能\n // 无限增长拖垮浏览器标签页。超过上限按断流处理(与 done 分支同等报错)。\n if (buf.length > MAX_SSE_BUF) {\n if (!disposed) onEvent({ type: 'error', code: 'events-unavailable' })\n reader.cancel().catch(() => {})\n return\n }\n }\n // 服务端干净关流(host 重启/代理超时/provider 结束会话):与异常路径同等\n // 对待——不报错引擎就聋死(字幕冻结、麦克风开着、永不 onFail)。\n if (!disposed) onEvent({ type: 'error', code: 'events-unavailable' })\n } catch {\n if (!disposed) onEvent({ type: 'error', code: 'events-unavailable' })\n }\n })()\n return () => {\n disposed = true\n controller.abort()\n }\n}\n\n/** 浏览器实时通道传输层。 */\nexport function createBrowserCloudTransport(): CloudTransport {\n return {\n async createSession() {\n const data = await postJson('/session')\n if (typeof data.sid !== 'string' || data.sid === '') throw new Error('realtime session returned no sid')\n return data.sid\n },\n upload: uploadAudio,\n openEvents,\n async closeSession(sid) {\n await postJson('/close', `?sid=${encodeURIComponent(sid)}`)\n },\n }\n}\n","/** dsh-asr-voice — 实时转写引擎(连续会话 + 本地回合判定)。\n * 与 recorder.ts 的整段模式是两种活法:整段模式靠一次点击划回合,实时模式没有那次\n * 点击,边界只能自己判。浏览器 Web Speech 不给任何 VAD/回合信号,所以判定落在\n * **文字稳定性**上:连续 settleMs 没有新结果、再多等 tailMs 接住最后一个词的迟到\n * final,就把这句交出去。云端实时引擎会直接给 speech_stopped,届时这条\n * 本地兜底只在它沉默时起作用。\n * 两个引擎共用同一份回合判定(`createSettleGate`):换引擎不该换回合边界。\n * - browser:连续 Web Speech 会话,零 key、零 host 改动。\n * - segmented:本地能量 VAD 切段 + 已有整段转写通道,逐句出字,同样不需要新协议。\n * 引擎选择不在此处:调用方拿到的就是 RealtimeSession(`createRealtime` 负责分派)。\n */\nimport { isWebSpeechSupported, startLevelSimulation, transcribeViaHost, type SpeechRecognitionLike } from './recorder.ts'\nimport { isRestartEcho } from './turn-guard.ts'\nimport { startPcmCapture, type PcmCapture, type PcmCaptureOptions } from './capture.ts'\nimport { PCM_SAMPLE_RATE, encodeWav16MonoPcm, isSilentPeak, normaliseGain, peakAbs, rmsOfFloat } from './pcm.ts'\nimport { createEnergyVad, VAD_WINDOW_MS, type EnergyVad, type VadTuning } from './vad.ts'\nimport { createRmsFloorEstimator, DEFAULT_RMS_FLOOR_TUNING, createBargeInGate, DEFAULT_BARGE_IN_TUNING } from './rms-floor.ts'\nimport { meaningfulTurn } from './turn-guard.ts'\nimport { createCloudRealtime, defaultCloudCapture } from './realtime-cloud.ts'\nimport { createBrowserCloudTransport } from './realtime-cloud-transport.ts'\n\n/** 引擎回调(与 VoiceRecorder 的回调契约同构,语义换成连续会话)。 */\nexport interface RealtimeEvents {\n /** 当前这句的实时全文(已确认部分 + 候选部分),驱动字幕行。 */\n onPartial(text: string): void\n /** 一个回合说完:文本交出,调用方据此提交给 agent。 */\n onTurn(text: string): void\n /** 电平 0~1(浏览器引擎为装饰性模拟,不参与判定)。 */\n onLevel(level: number): void\n /** 会话级失败(mic 被拒 / 无 Web Speech / 识别服务不可达),带错误码。 */\n onFail(code: string): void\n /** 背压丢段:字幕从这里起不再完整(segmented 引擎独有)。 */\n onGap?(): void\n /** 语音插话(D19):播放回复期间检测到真正的人声打断(segmented 引擎独有)。 */\n onBargeIn?(): void\n}\n\n/** 回合判定参数。 */\nexport interface TurnTuning {\n /** 文字静默多久算说完(毫秒)。 */\n settleMs: number\n /** 之后再宽限这么久才交出(毫秒)。 */\n tailMs: number\n}\n\n/** 一次连续转写会话。 */\nexport interface RealtimeSession {\n /** 开始监听(幂等)。 */\n start(): void\n/** 半双工门控:不收音。播报期间必须走这条——浏览器 AEC 吃不吃得掉我们自己的 TTS 尚未实测,先不赌;同时它也是「提交后不再进新词」的闩。 */\n pause(): void\n /** 交还麦克风,从干净的一句开始。 */\n resume(): void\n /** 结束会话(不再有任何回调)。 */\n stop(): void\n /** 是否正在收音。 */\n readonly listening: boolean\n/** 语音插话(barge-in,D19):播放回复期间恢复采集并武装回声门控。只有带本地 能量门控的引擎(segmented)实现;browser/cloud 引擎保持半双工(pause 静音)。 */\n armBargeIn?(): void\n /** 解除回声门控(播放自然排空/被打断时调用)。 */\n disarmBargeIn?(): void\n}\n\n/** 清掉一个可能为空的计时器(返回 null 便于链回变量),browser/segmented 引擎共用。 */\nfunction clearTimer(timer: ReturnType<typeof setTimeout> | null): null {\n if (timer !== null) clearTimeout(timer)\n return null\n}\n\n/** Web Speech 在 onend 后重新拉起前的最小间隔:紧接着 start() 会撞 InvalidStateError。 */\nconst RESTART_DELAY_MS = 120\n\n/** 取一个 webkitSpeechRecognition 构造器;不支持返回 null。 */\nfunction recognitionCtor(): (new () => SpeechRecognitionLike) | null {\n if (!isWebSpeechSupported()) return null\n return (window as unknown as { webkitSpeechRecognition: new () => SpeechRecognitionLike }).webkitSpeechRecognition\n}\n\n/** 空白归一:多段拼接与重启动带来的重复空格不应让字幕跳动。 */\nfunction joinText(...parts: string[]): string {\n return parts.filter((p) => p !== '').join(' ').replace(/\\s+/g, ' ').trim()\n}\n\n/** 回合判定:连续 `settleMs` 没有新结果、再宽限 `tailMs` 接住最后一个词的迟到结果,\n * 才交出这一句。两个引擎共用它,所以换引擎不会换回合边界。\n * `arm(hasText)` 每次调用都重新计时——静默才是「说完了」,噪声式抖动不会提前收尾;\n * `hasText === false` 时不排 timer(空句不该提交)。timer 到点先问 `live()`:\n * 会话已停/已暂停就什么也不做,交出动作由 `commit` 承担。\n */\nfunction createSettleGate(\n tuning: TurnTuning,\n live: () => boolean,\n commit: () => void,\n): { arm(hasText: boolean): void; cancel(): void } {\n let settleTimer: ReturnType<typeof setTimeout> | null = null\n let tailTimer: ReturnType<typeof setTimeout> | null = null\n return {\n arm(hasText: boolean): void {\n settleTimer = clearTimer(settleTimer)\n tailTimer = clearTimer(tailTimer)\n if (!hasText) return\n settleTimer = setTimeout(() => {\n settleTimer = null\n if (!live()) return\n if (tuning.tailMs <= 0) { commit(); return }\n tailTimer = setTimeout(() => { tailTimer = null; commit() }, tuning.tailMs)\n }, tuning.settleMs)\n },\n cancel(): void {\n settleTimer = clearTimer(settleTimer)\n tailTimer = clearTimer(tailTimer)\n },\n }\n}\n\n/** 浏览器引擎:连续的 Web Speech 会话 + 本地回合判定。 */\nexport function createBrowserRealtime(\n language: string,\n tuning: TurnTuning,\n events: RealtimeEvents,\n): RealtimeSession {\n const Ctor = recognitionCtor()\n if (Ctor === null) {\n queueMicrotask(() => { events.onFail('no-speech-support') })\n return { start: () => {}, pause: () => {}, resume: () => {}, stop: () => {}, listening: false }\n }\n const lang = language === 'auto' ? '' : language\n\n let active = false\n let paused = true\n let failed = false\n let segment = ''\n let interim = ''\n let recognition: SpeechRecognitionLike | null = null\n let restartTimer: ReturnType<typeof setTimeout> | null = null\n let stopLevel: (() => void) | null = null\n /** 上一次交出去的文本与其交出时刻:识别器重启后可能在很短窗口内把同一句再报一遍。 */\n let lastTurn = ''\n let lastTurnAt = 0\n\n const latest = (): string => joinText(segment, interim)\n\n /** 交出当前这句,并为下一句清空累加器。 */\n const commit = (): void => {\n const text = latest()\n segment = ''\n interim = ''\n if (text === '' || !active || paused) return\n if (!meaningfulTurn(text)) return // 噪音幻觉(嗯…)不上屏不提交\n lastTurn = text\n lastTurnAt = Date.now()\n events.onTurn(text)\n }\n\n const gate = createSettleGate(tuning, () => active && !paused, commit)\n\n /** 收到新结果就重新计时:静默才是「说完了」,噪声式抖动不会提前收尾。 */\n const armSettle = (): void => { gate.arm(latest() !== '') }\n\n const clearTimers = (): void => {\n gate.cancel()\n restartTimer = clearTimer(restartTimer)\n }\n\n const emitPartial = (): void => { events.onPartial(latest()) }\n\n /** 装一个新的识别器并启动(每段一个实例:重启后旧实例的内部错误态会残留)。 */\n const openRecognition = (): void => {\n if (recognition !== null) {\n try { recognition.abort() } catch { /* noop */ }\n }\n const rec = new Ctor()\n if (lang !== '') rec.lang = lang\n rec.continuous = true\n rec.interimResults = true\n rec.maxAlternatives = 1\n\n rec.onresult = (event) => {\n let finalChunk = ''\n let interimChunk = ''\n for (let i = event.resultIndex; i < event.results.length; i++) {\n const result = event.results.item(i)\n const transcript = result.item(0)?.transcript ?? ''\n if (result.isFinal) finalChunk += transcript\n else interimChunk += transcript\n }\n if (!active || paused) return\n const chunk = finalChunk.trim()\n const fresh = interimChunk.trim()\n // 这条事件什么也没说(只有空白):不动字幕、不冲掉在手候选、不重排静默计时。\n if (chunk === '' && fresh === '') return\n // 重启后重复上报同一句:窗口内丢弃(isRestartEcho 带时间盒——窗口外同句\n // 是用户真的又说了一遍,不能吞)。\n if (chunk !== '' && segment === '' && isRestartEcho(lastTurn, lastTurnAt, chunk, Date.now())) {\n interim = ''\n armSettle()\n return\n }\n if (chunk !== '') segment = joinText(segment, chunk)\n interim = fresh\n emitPartial()\n armSettle()\n }\n\n rec.onerror = (event) => {\n const code = event.error || 'unknown'\n if (code === 'no-speech' || code === 'aborted') return\n if (code === 'not-allowed' || code === 'service-not-allowed' || code === 'audio-capture') {\n failed = true\n active = false\n clearTimers()\n events.onFail(code === 'audio-capture' ? 'no-mic' : 'mic-denied')\n return\n }\n if (code === 'network') {\n failed = true\n active = false\n clearTimers()\n events.onFail('network')\n }\n // 其余错误码(rms 之类)不判死:会话可能还在,交给 onend 的重启逻辑续上。\n }\n\n rec.onend = () => {\n if (recognition === rec) recognition = null\n if (!active || paused || failed) return\n // Chrome 会在一段静音后自行结束 continuous 会话:悄悄续上,用户不该察觉。\n restartTimer = clearTimer(restartTimer)\n restartTimer = setTimeout(() => {\n restartTimer = null\n if (active && !paused && !failed) openRecognition()\n }, RESTART_DELAY_MS)\n }\n\n recognition = rec\n try {\n rec.start()\n } catch {\n // already started:交给 onend 续。\n }\n }\n\n const beginListening = (): void => {\n paused = false\n stopLevel ??= startLevelSimulation((level) => { events.onLevel(level) })\n openRecognition()\n }\n\n return {\n start(): void {\n if (active) return\n active = true\n failed = false\n beginListening()\n },\n pause(): void {\n if (!active || paused) return\n paused = true\n clearTimers()\n segment = ''\n interim = ''\n const rec = recognition\n recognition = null\n try { rec?.abort() } catch { /* noop */ }\n stopLevel?.()\n stopLevel = null\n },\n resume(): void {\n if (!active || !paused) return\n lastTurn = ''\n beginListening()\n },\n stop(): void {\n active = false\n paused = true\n clearTimers()\n segment = ''\n interim = ''\n const rec = recognition\n recognition = null\n try { rec?.abort() } catch { /* noop */ }\n stopLevel?.()\n stopLevel = null\n },\n get listening(): boolean { return active && !paused },\n }\n}\n\n/** 按句转写引擎参数:回合判定 + 声学切段 + 采集与背压。 */\nexport interface SegmentedTuning extends TurnTuning {\n /** 采集帧长(毫秒)。 */\n frameMs: number\n /** 待转写队列上限(不含在途那一段):超出丢最旧。 */\n maxPending: number\n /** 声学切段参数。 */\n vad: VadTuning\n}\n\n/** 可注入依赖。`capture` 永不 reject(失败以 onFail 送达),`transcribe` 以抛错表示 这段没转出来。单测借此跑真状态机,不碰 DOM。 */\nexport interface SegmentedDeps {\n capture(options: PcmCaptureOptions): Promise<PcmCapture>\n transcribe(pcm: Float32Array, language: string, signal: AbortSignal): Promise<string>\n}\n\n/** 连败判死阈值:上游持续失败时不该把用户的会话挂在半空(整段模式是同一取向)。 */\nconst CONSECUTIVE_FAIL_LIMIT = 3\n\n/** 段 → WAV → host 整段转写代理(复用已验证的免费通道,不新增协议)。 */\nasync function transcribeWavSegment(pcm: Float32Array, language: string, signal: AbortSignal): Promise<string> {\n const bytes = encodeWav16MonoPcm(pcm, PCM_SAMPLE_RATE, normaliseGain(peakAbs(pcm)))\n return transcribeViaHost(new Blob([bytes], { type: 'audio/wav' }), language, signal)\n}\n\nconst DEFAULT_SEGMENTED_DEPS: SegmentedDeps = {\n capture: startPcmCapture,\n transcribe: transcribeWavSegment,\n}\n\n/** 按句转写引擎:本地能量 VAD 切段 + 已有整段转写通道。\n * 出字节奏由「声学段边界 + 上游往返」决定,不是逐字流式:每句在说完 `silenceMs`\n * 后约一个往返才上屏。它换来的是零新协议、零新 key,并且用真实麦克风电平驱动电平表\n * (浏览器引擎只能模拟)。\n */\nexport function createSegmentedRealtime(\n language: string,\n tuning: SegmentedTuning,\n events: RealtimeEvents,\n deps: SegmentedDeps = DEFAULT_SEGMENTED_DEPS,\n): RealtimeSession {\n let active = false\n let paused = true\n /** 本期已确认文字(各段转写结果按序拼接)。 */\n let text = ''\n let vad: EnergyVad | null = null\n let capture: PcmCapture | null = null\n /** rmsAuto 的噪声底估计器:与 VAD 同生命周期,静音期持续学习。\n * 窗口按 **VAD 分析窗**(20ms)折算而非采集帧长:observe 是 VAD 每分析窗投喂一次,\n * 用采集帧长(默认 40ms)折算会把真实观测窗缩到标称值的一半。 */\n const floor = tuning.vad.rmsAuto === true\n ? createRmsFloorEstimator({ ...DEFAULT_RMS_FLOOR_TUNING, frameMs: VAD_WINDOW_MS })\n : null\n /** barge-in 回声门控:播放回复期间武装,只有它触发才打断(D19,默认关)。 */\n const bargeGate = createBargeInGate({ ...DEFAULT_BARGE_IN_TUNING, frameMs: tuning.frameMs })\n let inFlight = false\n let failures = 0\n const queue: Float32Array[] = []\n /** 在途请求按代际登记:pause/resume/stop 递增代际并 abort,旧代结果一律作废。 */\n const inflight = new Map<number, AbortController>()\n/** 代际。用它而不是逐个标志位:一段语音的转写请求可能在说话人已经开始下一句之后 才回来,不作废就会把上一句的字幕倒灌进新一句。 */\n let generation = 0\n\n const commit = (): void => {\n const out = text\n text = ''\n if (out === '' || !active || paused) return\n if (!meaningfulTurn(out)) return // 底噪段幻觉(嗯…)不上屏不提交\n events.onTurn(out)\n }\n\n/** 还在出声时不交出回合:转写有往返,先落地的半句字幕不该把一句话说成两半。 静音边沿(onSpeech(false))会重新计时,所以这里挡下来的一定还有下一次机会。 */\n const gate = createSettleGate(tuning, () => active && !paused && !(vad?.inSpeech ?? false), commit)\n\n const abortAll = (): void => {\n for (const controller of inflight.values()) controller.abort()\n inflight.clear()\n }\n\n const tearDown = (): void => {\n gate.cancel()\n bargeGate.disarm()\n abortAll()\n queue.length = 0\n inFlight = false\n text = ''\n vad?.reset()\n }\n\n const failNow = (code: string): void => {\n active = false\n paused = true\n tearDown()\n capture?.stop()\n capture = null\n events.onFail(code)\n }\n\n const pump = (): void => {\n if (!active || paused || inFlight) return\n const pcm = queue.shift()\n if (pcm === undefined) return\n const gen = generation\n const controller = new AbortController()\n inflight.set(gen, controller)\n inFlight = true\n deps.transcribe(pcm, language, controller.signal).then(\n (result) => {\n if (!inflight.delete(gen)) return\n inFlight = false\n failures = 0\n if (!active || paused) { pump(); return }\n const chunk = result.trim()\n if (chunk !== '') {\n text = joinText(text, chunk)\n events.onPartial(text)\n }\n gate.arm(text !== '')\n pump()\n },\n () => {\n if (!inflight.delete(gen)) return\n inFlight = false\n if (!active || paused) { pump(); return }\n failures += 1\n if (failures >= CONSECUTIVE_FAIL_LIMIT) { failNow('provider-unreachable'); return }\n pump()\n },\n )\n }\n\n const enqueue = (pcm: Float32Array): void => {\n queue.push(pcm)\n // 转写慢过说话:丢最旧。宁可少一句,也不能让字幕越拖越长。\n while (queue.length > tuning.maxPending) {\n queue.shift()\n events.onGap?.()\n }\n pump()\n }\n\n const ensureVad = (): EnergyVad => {\n vad ??= createEnergyVad(PCM_SAMPLE_RATE, tuning.vad, {\n onSegment: (pcm) => {\n if (!active || paused) return\n // barge-in 期(播报回复中):回声可能被 VAD 切成段,直接丢弃不上行——\n // 打断检测走能量门控,这里留白只会浪费一次上游配额。\n if (bargeGate.armed) return\n // 静音守卫:VAD 也可能被噪声底顶开一段,趋零的段发上去只会换来幻觉字。\n if (isSilentPeak(peakAbs(pcm))) return\n enqueue(pcm)\n },\n onSpeech: (inSpeech) => { if (!inSpeech) gate.arm(text !== '') },\n }, floor)\n return vad\n }\n\n const onFrame = (pcm: Float32Array): void => {\n if (!active || paused) return\n const rms = rmsOfFloat(pcm)\n events.onLevel(rms)\n if (bargeGate.armed && bargeGate.feed(rms, vad?.inSpeech ?? false)) {\n events.onBargeIn?.()\n }\n ensureVad().feed(pcm)\n }\n\n const openCapture = (): void => {\n deps.capture({\n frameMs: tuning.frameMs,\n onFrame,\n onFail: (code) => { if (active) failNow(code) },\n }).then((next) => {\n // 授权弹窗挂起期间可能已经 stop():立刻关掉,别让麦克风自己开着。\n if (!active || paused) { next.stop(); return }\n capture = next\n }, () => {\n if (active) failNow('capture-failed')\n })\n }\n\n return {\n start(): void {\n if (active) return\n active = true\n paused = false\n generation += 1\n // 每个会话重新估一遍噪声底:估计器与引擎实例同生命周期,而 estimate 一旦被\n // 开场语音带偏(学习期把语音当底噪 → 阈值抬到语音之上 → 永远不判有声),不重置\n // 就是整个页面会话都失效。会话边界是唯一「从零开始」的干净时机——pause/resume\n // 不能重置,否则每次播报回来都要盲听一个观测窗。\n floor?.reset()\n ensureVad()\n openCapture()\n },\n pause(): void {\n if (!active || paused) return\n paused = true\n generation += 1\n // 播报期间轨道直接静音:比「收帧但丢弃」少一份回声,也少一份电量。\n capture?.setMuted(true)\n tearDown()\n },\n resume(): void {\n if (!active || !paused) return\n paused = false\n generation += 1\n ensureVad().reset()\n if (capture === null) { openCapture(); return }\n capture.setMuted(false)\n },\n stop(): void {\n active = false\n paused = true\n generation += 1\n bargeGate.disarm()\n tearDown()\n capture?.stop()\n capture = null\n },\n get listening(): boolean { return active && !paused },\n armBargeIn(): void {\n if (!active) return\n bargeGate.arm()\n // 从 thinking 的静音态回到收音(与 resume 同款逻辑:重启采集 + 重置 VAD,\n // 播报回声不留痕迹;在途请求随代际作废——此时本就没有在途请求)。\n if (paused) {\n paused = false\n generation += 1\n ensureVad().reset()\n if (capture === null) openCapture()\n else capture.setMuted(false)\n }\n },\n disarmBargeIn(): void {\n bargeGate.disarm()\n },\n }\n}\n\n/** 实时引擎标识(对应 settings 的 realtime.engine)。 */\nexport type RealtimeEngine = 'browser' | 'segmented' | 'cloud'\n\n/** 按配置装配实时引擎(browser/segmented 回合判定同源;cloud 由服务端 VAD 给回合)。 */\nexport function createRealtime(\n engine: RealtimeEngine,\n language: string,\n tuning: SegmentedTuning,\n events: RealtimeEvents,\n): RealtimeSession {\n if (engine === 'segmented') return createSegmentedRealtime(language, tuning, events)\n if (engine === 'cloud') {\n return createCloudRealtime({ frameMs: tuning.frameMs }, events, {\n capture: defaultCloudCapture,\n transport: createBrowserCloudTransport(),\n })\n }\n return createBrowserRealtime(language, tuning, events)\n}\n","/** dsh-asr-voice — 语音播报出口(SpeakSink)与「流式回复 → 可朗读句子」的分句泵。\n * 一期只有浏览器 `speechSynthesis` 一种实现:它是三浏览器交集内唯一零配置、零密钥、\n * 零依赖的播放通路,且与麦克风走不同的音频路由(回声消除能否吃掉它决定半双工还是\n * 全双工,见 README 的实时对话一节)。接缝留在这里,云 TTS 落地时新增一个实现,\n * 调用方(语音对话按钮)不需要知道播报是谁做的。\n * 两处不显眼但会决定体验的坑:\n * 1. `utterance.onend` 不可信——Chrome 对长句常不回调,队列会永久卡住、麦克风再也不\n * 交还。故每句都挂看门狗(realtime.speech.utteranceWatchdogMs)。\n * 2. agent 回复是**逐块累积**的字符串,不是天生分好句的。泵负责「只朗读已经说完的\n * 句子」,并且在新回合/新 step 使文本流从头开始时,把旧流的尾巴先吐干净。\n */\n\n/** 一次朗读的最小生命周期回调。 */\nexport interface SpeakSink {\n /** 追加一句到队列(空闲时立即开播)。 */\n enqueue(text: string): void\n /** 队列里还有话要说,或正在说。半双工门控据此决定麦克风给不给。 */\n readonly active: boolean\n /** 打断:停止当前发声并清空队列,**不**触发 onDrain。 */\n cancel(): void\n /** 队列排空时回调(每次排空都回调,调用方自己判断是不是真的说完了)。 */\n onDrain: (() => void) | null\n /** 释放:断开音色监听、清空队列、停止发声。 */\n dispose(): void\n /** 在用户手势里调用一次,为 Safari 建立发声权限。 */\n prime(): void\n}\n\n/** 浏览器是否具备语音合成能力。 */\nexport function isSpeechSynthesisSupported(): boolean {\n return typeof window !== 'undefined' && 'speechSynthesis' in window && 'SpeechSynthesisUtterance' in window\n}\n\n/** 未闭合句子的硬切上限(字符)。与 utteranceWatchdogMs 是一对:块必须能在看门狗时限 内念完,否则会被看门狗误判成「播完」而截断。正常句长远小于此,只有全程无标点的 退化回复才会撞上。 */\nexport const MAX_UTTERANCE_CHARS = 200\n\n/** 中日韩句读:出现即断句,不需要后接空白。 */\nconst CJK_STOP = '。!?…;'\n/** 拉丁句末符:只在词尾(后接空白或到结尾)才断句。 */\nconst LATIN_STOP = '!?;:'\n// 已知代价:`Mr. Smith` / `e.g.` 这类「点后跟空格」的缩写会被判成句末,多切出一个短句。\n// 认了——加缩写表会引入语言分支,而这里最坏只是多一句几百毫秒的片段,不会漏念。\n/** 跟着句末符一起归入上一句的收尾引号/括号。 */\nconst CLOSERS = '”’))』」】'\n\n/** text 中 from 之后**第一个**句子边界的结束下标(含紧随的句末符、收尾符与空白); 没有边界返回 -1。正向扫描:一次 feed 里含多句时也要切成多段,才能及早可打断、 且每段都落在看门狗盖得住的长度内。 */\nfunction nextBreak(text: string, from = 0): number {\n for (let i = from; i < text.length; i++) {\n const c = text[i] ?? ''\n if (c === '\\n') return i + 1\n const isCjk = CJK_STOP.includes(c)\n if (!isCjk && !LATIN_STOP.includes(c) && c !== '.') continue\n // 小数点不断句:回复里的 `3.5`、版本号远比「句子以数字结尾」常见,\n // 真到句尾的那一个由 finish() 或硬切上限兜住。\n if (c === '.' && /\\d/.test(text[i - 1] ?? ' ')) continue\n let j = i + 1\n for (let nj = j; nj < text.length; nj++) {\n const t = text[nj] ?? ''\n if (!CJK_STOP.includes(t) && !LATIN_STOP.includes(t) && !CLOSERS.includes(t)) break\n j = nj + 1\n }\n // 拉丁句末符只有站在词尾才算结束:`U.S.` 里点后面紧跟字母时不算。\n if (!isCjk && j < text.length && !/\\s/.test(text[j] ?? '')) continue\n while (j < text.length && (text[j] === ' ' || text[j] === '\\t')) j++\n return j\n }\n return -1\n}\n\n/** 创建一个分句泵:喂**累积**文本,吐出「已经说完的句子」。 @param firstSentenceMinChars - 首句最少字数:一句太短就继续攒,避免以「好的。」这种 碎片起音(听众会觉得机器人结巴)。仅作用于每个流的第一个切段。 */\nexport function createSentencePump(firstSentenceMinChars: number): {\n /** 喂入当前流的累积全文;返回新增的完整句子。 */\n feed(cumulative: string): string[]\n /** 流结束:吐出缓冲里剩下的尾巴(不再受首句字数约束)。 */\n finish(): string[]\n} {\n let buffer = ''\n let seen = ''\n let started = false\n /** 从 buffer 里尽量切出可朗读句子(按句一段)。final=true 时连尾巴一起吐。 */\n const cut = (final: boolean): string[] => {\n const out: string[] = []\n let pos = 0\n for (;;) {\n const boundary = nextBreak(buffer, pos)\n if (boundary > 0 && boundary <= MAX_UTTERANCE_CHARS) {\n const head = buffer.slice(0, boundary).trim()\n if (head === '') { pos = boundary; continue }\n if (!started && !final && head.length < firstSentenceMinChars) {\n // 首句太短就往后并一句再念(碎片起音听着像机器人结巴)。没有下一句可并\n // 且流已收尾时,才照念这个短开头。\n pos = boundary\n continue\n }\n out.push(head)\n buffer = buffer.slice(boundary)\n pos = 0\n started = true\n continue\n }\n if (buffer.length >= MAX_UTTERANCE_CHARS) {\n // 只切固定长度:一次喂进 500 字无标点长文时,整段吐出会让看门狗盖不住。\n const head = buffer.slice(0, MAX_UTTERANCE_CHARS).trim()\n buffer = buffer.slice(MAX_UTTERANCE_CHARS)\n pos = 0\n if (head === '') continue // 整刀落在空白里:没有可念内容,不产出空块\n out.push(head)\n started = true\n continue\n }\n break\n }\n if (final && buffer.trim() !== '') {\n out.push(buffer.trim())\n buffer = ''\n }\n return out\n }\n return {\n feed(cumulative: string): string[] {\n if (cumulative === seen) return []\n if (!cumulative.startsWith(seen)) {\n // 回合或 step 换了:blocks 从头部重来。旧流没念完的尾巴必须先吐,否则丢半句。\n const tail = cut(true)\n seen = cumulative\n buffer = cumulative\n started = false\n return [...tail, ...cut(false)]\n }\n buffer += cumulative.slice(seen.length)\n seen = cumulative\n return cut(false)\n },\n finish(): string[] {\n return cut(true)\n },\n }\n}\n\n/** 播报参数(来自 config.realtime 快照)。 */\nexport interface SpeakTuning {\n /** 单句看门狗(毫秒)。 */\n utteranceWatchdogMs: number\n /** 识别语言:auto 时跟随 navigator.language,否则用它挑音色。 */\n language: string\n}\n\n/** 排队播放骨架:两条 SpeakSink(浏览器合成/云端 TTS)共用的状态机。\n * 单一来源化的语义(历史上一处回归过:drain 处理器首轮后被摘掉,第二回合起\n * 麦克风永远要不回来):\n * - 一次一句:上一句 settle 前不起播下一句;\n * - pending-drain:只在「起播过的一批」排空时触发一次 onDrain,空转不重复;\n * - cancel:清队列、止住当前句、active 立即为 false,且**吞掉本轮 drain**\n * (打断后何时还麦由调用方决定);迟到 settle 一律作废,不抢跑新状态。\n * 播放细节由实现注入:`play` 一句(Promise 在这句结束——自然/被打断/超时——时\n * settle),`interrupt` 止住正在发声的这一句。\n */\ninterface QueueRunnerDeps {\n play(text: string): Promise<void>\n interrupt(): void\n}\n\nfunction createQueueRunner(deps: QueueRunnerDeps): {\n enqueue(text: string): void\n cancel(): void\n dispose(): void\n /** 是否空闲(既没在播也没排队)。 */\n active(): boolean\n set onDrain(fn: (() => void) | null)\n get onDrain(): (() => void) | null\n} {\n const queue: string[] = []\n let token = 0\n let pending = false\n let drain: (() => void) | null = null\n let disposed = false\n\n const next = (): void => {\n if (disposed || token !== 0) return\n const text = queue.shift()\n if (text === undefined) {\n if (pending) { pending = false; drain?.() }\n return\n }\n pending = true\n const my = ++token\n // play 的 rejection 必须消费(否则 unhandled rejection),且不能打断队列:\n // catch 放在 finally 之前,保证任何结果都走收尾(作废迟到 settle + 续播下一句)。\n void deps.play(text).catch(() => {}).finally(() => {\n // 迟到的旧代 settle(cancel/dispose 后):不得动新状态(新句可能已在播)。\n if (disposed || my !== token) return\n token = 0\n next()\n })\n }\n\n return {\n enqueue(text: string): void {\n if (disposed || text.trim() === '') return\n queue.push(text)\n next()\n },\n cancel(): void {\n queue.length = 0\n pending = false\n token = 0\n deps.interrupt()\n },\n dispose(): void {\n disposed = true\n queue.length = 0\n pending = false\n token = 0\n deps.interrupt()\n },\n active: () => token !== 0 || queue.length > 0,\n set onDrain(fn: (() => void) | null) { drain = fn },\n get onDrain(): (() => void) | null { return drain },\n }\n}\n\n/** 按语言挑音色:没有完全匹配时退到同主语言的任何音色,再退到浏览器默认。 */\nfunction pickVoice(voices: SpeechSynthesisVoice[], lang: string): SpeechSynthesisVoice | null {\n if (voices.length === 0 || lang === '') return null\n const wanted = lang.toLowerCase()\n const base = wanted.split('-')[0]\n return voices.find((v) => v.lang.toLowerCase() === wanted)\n ?? voices.find((v) => v.lang.toLowerCase().split('-')[0] === base)\n ?? null\n}\n\n/** 浏览器语音合成实现。排队/一次一句/drain 语义在 QueueRunner;这里只提供 「合成并播一句」(onend/onerror/看门狗三者谁先到都算这句结束)与「止住当前句」。 */\nexport function createSpeechSynthesisSink(tuning: SpeakTuning): SpeakSink {\n const synth = typeof window === 'undefined' ? undefined : window.speechSynthesis\n let voices: SpeechSynthesisVoice[] = []\n let watchdog: ReturnType<typeof setTimeout> | null = null\n /** 正在播的这句(interrupt/dispose 时 synth.cancel,看门狗一并清)。 */\n let current: SpeechSynthesisUtterance | null = null\n let disposed = false\n\n const lang = tuning.language === 'auto'\n ? (typeof navigator === 'undefined' ? '' : navigator.language)\n : tuning.language\n\n const clearWatchdog = (): void => {\n if (watchdog !== null) clearTimeout(watchdog)\n watchdog = null\n }\n\n const runner = createQueueRunner({\n play(text: string): Promise<void> {\n return new Promise<void>((resolve) => {\n if (synth === undefined) { resolve(); return }\n const utter = new SpeechSynthesisUtterance(text)\n if (lang !== '') utter.lang = lang\n const voice = pickVoice(voices, lang)\n if (voice !== null) utter.voice = voice\n current = utter\n let settled = false\n const finish = (): void => {\n if (settled) return\n settled = true\n if (current === utter) current = null\n clearWatchdog()\n resolve()\n }\n utter.onend = finish\n utter.onerror = finish\n // onend 不可信:Chrome 长句、部分音色会静默不回。超时按播完处理,\n // 否则麦克风永远还不回来(半双工门控挂在 onDrain 上)。\n watchdog = setTimeout(() => {\n try { synth.cancel() } catch { /* noop */ }\n finish()\n }, tuning.utteranceWatchdogMs)\n try {\n synth.speak(utter)\n } catch {\n finish() // 同步失败也结算:队列继续,不因单句卡死\n }\n })\n },\n interrupt(): void {\n clearWatchdog()\n try { synth?.cancel() } catch { /* noop */ }\n },\n })\n\n // Chrome 的音色表要等 voiceschanged 才填得满,构造时常为空。\n const onVoices = (): void => { voices = synth?.getVoices() ?? [] }\n onVoices()\n synth?.addEventListener?.('voiceschanged', onVoices)\n\n return {\n enqueue: (text) => runner.enqueue(text),\n get active() { return runner.active() },\n cancel: () => runner.cancel(),\n set onDrain(fn) { runner.onDrain = fn },\n get onDrain() { return runner.onDrain },\n dispose(): void {\n disposed = true\n runner.dispose()\n synth?.removeEventListener?.('voiceschanged', onVoices)\n },\n prime(): void {\n if (disposed || synth === undefined) return\n onVoices()\n // Safari 只在用户激活上下文里允许首次发声:在点击回调内说一个空白片段把\n // 音频会话建起来,真正的内容稍后经队列播出就不会被静默拦掉。\n try {\n const blank = new SpeechSynthesisUtterance(' ')\n blank.volume = 0\n synth.speak(blank)\n } catch { /* noop */ }\n },\n }\n}\n\n/** 云端 TTS 是否需要 Web Audio(浏览器能力检查)。 */\nexport function isCloudTtsSupported(): boolean {\n const w = typeof window === 'undefined' ? undefined : (window as unknown as { AudioContext?: unknown; webkitAudioContext?: unknown })\n return Boolean(w?.AudioContext ?? w?.webkitAudioContext)\n}\n\n/** 云端 TTS 实现(I6):文本 → host 私有路由 → 阿里云百炼 qwen3-tts-flash-realtime\n * → base64 PCM(16k int16 LE)→ `AudioBufferSourceNode → ctx.destination` 播放。\n * 与浏览器 speechSynthesis 同接口(SpeakSink),调用方(语音对话按钮)不感知实现。\n * 排队/drain/打断语义在 QueueRunner;这里只提供「合成并播一句」与「止住当前句」。\n * `onended` 是 Web Audio 的精确事件(不像 speechSynthesis 的 onend 会漏回调),\n * 另有播放时长兜底;合成请求带超时,且打断(cancel/dispose)按句子代际作废\n * in-flight 请求——fetch 窗口内的句子不得在打断后再起播。云端不可达或合成失败时\n * 跳过该句继续排队(不阻塞对话),失败静默(错误面由录音侧暴露)。\n */\nexport function createCloudTtsSink(tuning: { language: string; voice?: string }): SpeakSink {\n let ctx: AudioContext | null = null\n let current: AudioBufferSourceNode | null = null\n let disposed = false\n /** 句子代际:interrupt 递增,in-flight 合成链在起播前按代作废。 */\n let utteranceGen = 0\n /** 合成请求超时:host 挂起时不能让一句永久占用播放槽(半双工门挂在 onDrain 上)。 */\n const TTS_TIMEOUT_MS = 30_000\n\n const getCtx = (): AudioContext | null => {\n if (ctx === null) {\n const w = window as unknown as { AudioContext?: new () => AudioContext; webkitAudioContext?: new () => AudioContext }\n const Ctor = w.AudioContext ?? w.webkitAudioContext\n if (Ctor === undefined) return null\n ctx = new Ctor()\n }\n return ctx\n }\n\n const runner = createQueueRunner({\n play(text: string): Promise<void> {\n return (async () => {\n const my = ++utteranceGen\n const ac = getCtx()\n if (ac === null || disposed) return\n let data: { ok?: boolean; audio?: string; sampleRate?: number; reason?: string }\n try {\n const res = await fetch('/api/asr-voice/tts', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ text, voice: tuning.voice }),\n signal: AbortSignal.timeout(TTS_TIMEOUT_MS),\n })\n data = await res.json().catch(() => ({})) as typeof data\n } catch {\n return // 网络失败/超时:跳过这句,不阻塞队列\n }\n if (disposed || my !== utteranceGen || data.ok !== true || data.audio === undefined) return\n // base64 → 16k int16 LE → Float32(除以 32768 归一化到 -1~1)。\n const bin = atob(data.audio)\n const pcm = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) pcm[i] = bin.charCodeAt(i)\n const frames = pcm.byteLength >> 1\n const f32 = new Float32Array(frames)\n const view = new DataView(pcm.buffer)\n for (let i = 0; i < frames; i++) f32[i] = view.getInt16(i * 2, true) / 32768\n if (disposed || my !== utteranceGen || frames === 0) return\n const buffer = ac.createBuffer(1, frames, data.sampleRate ?? 16000)\n buffer.copyToChannel(f32, 0)\n const source = ac.createBufferSource()\n source.buffer = buffer\n source.connect(ac.destination)\n current = source\n await new Promise<void>((resolve) => {\n let playTimer: ReturnType<typeof setTimeout> | null = null\n const finishPlay = (): void => {\n if (playTimer !== null) { clearTimeout(playTimer); playTimer = null }\n resolve()\n }\n source.onended = () => finishPlay()\n source.start()\n // 保险:start 后超时(如 ctx 被挂起)不能让队列永久卡住。长句按 5s/1000 帧估算。\n // 自然播完/打断都会经 onended 清掉这个 timer——不留空转的兜底(它会在音频\n // 结束后仍对已释放的 source 调 stop,dispose 后还会拖住节点引用)。\n playTimer = setTimeout(() => {\n try { source.stop() } catch { /* already ended */ }\n finishPlay()\n }, Math.max(2_000, Math.ceil(frames / 16000) * 1000 + 1_500))\n })\n if (current === source) current = null\n })()\n },\n interrupt(): void {\n utteranceGen += 1\n if (current !== null) { try { current.stop() } catch { /* already ended */ } }\n current = null\n },\n })\n\n return {\n enqueue: (text) => runner.enqueue(text),\n get active() { return runner.active() },\n cancel: () => runner.cancel(),\n set onDrain(fn) { runner.onDrain = fn },\n get onDrain() { return runner.onDrain },\n dispose(): void {\n disposed = true\n runner.dispose()\n if (ctx !== null) { void ctx.close().catch(() => {}); ctx = null }\n },\n prime(): void {\n if (disposed) return\n const ac = getCtx()\n if (ac !== null && ac.state === 'suspended') void ac.resume()\n },\n }\n}\n","/** dsh-asr-voice — 语音对话链路(不再是独立按钮,而是并入麦克风按钮的一段能力)。\n * 闭环:开始 → 边说边上字幕 → 停顿即把这句 setDraft+submit 发起 agent 回合\n * → 读 session.partial 的流式回复,分句交给 SpeakSink 朗读\n * → 念完自动把麦克风还回来,进入下一句。\n * 三条不可让的规矩:\n * 1. **半双工**:一切实弹/播报期间引擎都是 pause() 的。浏览器 AEC 能不能吃掉\n * 我们自己的 TTS 尚未实测,不赌;实测通过前不做语音插话。\n * 2. **一次一个在途回合**:turnRef 非空就是闩,任何路径都不允许第二个提交插进去。\n * 3. **麦克风不无人值守**:realtime.maxSessionMs 到点自动结束并交还设备。\n * 实时路径不做提示词优化:对话要的是即时,不是清洗过的转写。\n *\n * 形态说明:本模块导出的是 **hook + 状态条**,不是按钮。界面上只有一个按钮\n * (voice-button.tsx),「点击转写 / 长按对话」由那个按钮统一裁决;这里只负责\n * 对话自己的状态机与它那一条状态提示。全局快捷键仍经 voiceChatController 驱动。\n */\nimport * as react from 'react'\n// Type-only: pulls the ui-conversation SlotMap merge (input seats + standard kit).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { cloudConfigured, config, realtimeTuning, subscribeConfig, type RealtimeTuning } from './config.ts'\nimport { appendDraftText } from './draft.ts'\nimport { createRealtime, type RealtimeSession } from './realtime.ts'\nimport { isPcmCaptureSupported } from './capture.ts'\nimport { createSentencePump, createSpeechSynthesisSink, createCloudTtsSink, isSpeechSynthesisSupported, isCloudTtsSupported, type SpeakSink } from './speech-out.ts'\nimport { isWebSpeechSupported } from './recorder.ts'\nimport { SpectrumBars, Spinner } from './icons.tsx'\nimport { systemDict } from './locales.ts'\nimport type { LocaleT } from './locales.ts'\n\n/** 输入动作最小面(官方 standard kit 的 inputActions)。 */\ninterface InputActionsLike {\n setDraft(text: string): void\n submit(): void\n}\n\n/** assistant 内容块的最小面(只认 text,reasoning/tool-call 不念)。 */\nexport interface ReplyBlock {\n kind?: string\n text?: string\n}\n\n/** 对话链路需要的 owner share + 标准 kit 最小面 + 注入项。\n * 可选属性显式带上 `| undefined`:调用方(slot 注入)本来就可能传 undefined,\n * 而本仓开了 exactOptionalPropertyTypes,不写就只能靠条件展开绕,噪音更大。 */\nexport interface VoiceChatProps {\n sessionId?: string | undefined\n session?: { running?: boolean; partial?: { blocks?: readonly ReplyBlock[] } | null } | undefined\n input?: { draft?: string } | undefined\n inputActions?: InputActionsLike | undefined\n /** 取消当前回合:由 host 半区经 slot inject 注入(组件拿不到 ctx)。 */\n cancelTurn?: ((sessionId: string) => void) | undefined\n /** 开始对话前的额外许可:录音链路正占着麦克风时返回 false。 */\n canStart?: (() => boolean) | undefined\n t: LocaleT\n}\n\n/** 会话状态机:idle = 麦克风未开;listening = 收音中;thinking/speaking = 门控关麦。 */\nexport type ChatPhase = 'idle' | 'listening' | 'thinking' | 'speaking'\n\n/** 字幕行只显示尾部这么多字符(提示条是单行的,整段回复会把它撑破)。 */\nconst CAPTION_TAIL_CHARS = 80\n\n/** 全局对话控制器:快捷键只驱动「最后挂载」的实例(当前可见会话)。 */\nexport const voiceChatController = {\n toggle: (): void => { currentChat?.toggle() },\n isActive: (): boolean => currentChat?.isActive() ?? false,\n mount(instance: { toggle(): void; isActive(): boolean }): () => void {\n currentChat = instance\n return () => { if (currentChat === instance) currentChat = undefined }\n },\n}\nlet currentChat: { toggle(): void; isActive(): boolean } | undefined\n\n/** realtime.enabled 的响应式读取:设置里一拨开关,按钮的长按能力立刻跟着变。 */\nconst subscribeEnabled = (fn: () => void): (() => void) => subscribeConfig(fn)\nconst readEnabled = (): boolean => config.realtime.enabled\n\nexport function useRealtimeEnabled(): boolean {\n return react.useSyncExternalStore(subscribeEnabled, readEnabled)\n}\n\n/** 只保留尾部字符,供单行字幕显示长回复。 */\nfunction tailText(text: string): string {\n const flat = text.replace(/\\s+/g, ' ').trim()\n return flat.length > CAPTION_TAIL_CHARS ? `…${flat.slice(-CAPTION_TAIL_CHARS)}` : flat\n}\n\n/** 从流式 blocks 里取可见正文(与官方 assistantText 同义,不依赖其未导出路径)。 */\nfunction replyTextOf(blocks: readonly ReplyBlock[] | undefined): string {\n if (blocks === undefined) return ''\n return blocks.reduce((acc, b) => (b.kind === 'text' ? acc + (b.text ?? '') : acc), '')\n}\n\n/** 对话按钮需要的那几样东西。status 由本模块渲染——状态条的定位依赖按钮的\n * `.dshav-mic-wrap`,但内容是对话自己的语义,拆两处会立刻走味。 */\nexport interface VoiceChatHandle {\n /** 当前阶段。 */\n phase: ChatPhase\n /** 是否在对话中(phase !== 'idle')。 */\n active: boolean\n /** 对话能力是否可用(realtime.enabled)。关闭时长按退化成点击。 */\n enabled: boolean\n /** 按钮的悬停/无障碍标题。 */\n title: string\n /** 点按钮该做的事:进/出对话或打断。 */\n toggle: () => void\n /** 对话的状态提示条(错误 / 提示 / 字幕)。放进按钮的同一个 wrap 里。 */\n status: react.ReactNode\n}\n\n/** 对话链路。 @param props - slot 注入的 owner share + 标准 kit + 翻译函数。 */\nexport function useVoiceChat(props: VoiceChatProps): VoiceChatHandle {\n const { inputActions, t } = props\n const [phase, setPhaseState] = react.useState<ChatPhase>('idle')\n const [live, setLive] = react.useState('')\n const [error, setError] = react.useState<string | null>(null)\n const [notice, setNotice] = react.useState<string | null>(null)\n /** 对话能力总开关(realtime.enabled)。设置里一拨,按钮的长按能力立刻跟着变。 */\n const enabled = useRealtimeEnabled()\n\n const phaseRef = react.useRef<ChatPhase>('idle')\n const setPhase = (next: ChatPhase): void => { phaseRef.current = next; setPhaseState(next) }\n const engineRef = react.useRef<RealtimeSession | null>(null)\n const sinkRef = react.useRef<SpeakSink | null>(null)\n const tuningRef = react.useRef<RealtimeTuning | null>(null)\n /** 浏览器识别(Web Speech)在本机会话内已被网络/服务判定不可用 → 整个会话降级到\n * segmented(按句云端转写)再说话。整段录音的 auto 模式早有同款降级,这里补齐\n * 实时语音对话的路:Chrome 走 Google 服务器被网络屏蔽时 network 错误一上来就报,\n * 不许用户每次手动切引擎。 */\n const fallbackEngineRef = react.useRef<'segmented' | null>(null)\n /** 跨 begin() 传递的提示(降级提示要先于 begin 决定、又不能被 begin 的 notice 覆盖)。 */\n const fallbackNoticeRef = react.useRef<string | null>(null)\n /** 在途回合闩:非空表示这句话已提交、回复还没念完。 */\n const turnRef = react.useRef<{\n armed: boolean\n lastText: string\n pump: ReturnType<typeof createSentencePump>\n } | null>(null)\n const noReplyRef = react.useRef<ReturnType<typeof setTimeout> | null>(null)\n const capRef = react.useRef<ReturnType<typeof setTimeout> | null>(null)\n const spectrumRef = react.useRef<HTMLSpanElement | null>(null)\n const levelRef = react.useRef(-1)\n const mountedRef = react.useRef(true)\n // 回调活在被创建的那一帧里:props/最新闭包一律经 ref 转发,免得拿到旧 inputActions。\n const actionsRef = react.useRef<InputActionsLike | undefined>(inputActions)\n const cancelRef = react.useRef<VoiceChatProps['cancelTurn']>(props.cancelTurn)\n const canStartRef = react.useRef<VoiceChatProps['canStart']>(props.canStart)\n const runningRef = react.useRef(props.session?.running ?? false)\n const draftRef = react.useRef(props.input?.draft ?? '')\n actionsRef.current = inputActions\n cancelRef.current = props.cancelTurn\n canStartRef.current = props.canStart\n runningRef.current = props.session?.running ?? false\n const replyText = react.useMemo(() => replyTextOf(props.session?.partial?.blocks), [props.session?.partial?.blocks])\n const running = props.session?.running ?? false\n\n const clearNoReply = (): void => {\n if (noReplyRef.current !== null) clearTimeout(noReplyRef.current)\n noReplyRef.current = null\n }\n\n /** 结束本次对话:拆引擎、拆播报、清闩,并把提示留下。 */\n const endSession = (note?: string, err?: string): void => {\n clearNoReply()\n if (capRef.current !== null) { clearTimeout(capRef.current); capRef.current = null }\n turnRef.current = null\n engineRef.current?.stop()\n engineRef.current = null\n sinkRef.current?.dispose()\n sinkRef.current = null\n tuningRef.current = null\n setLive('')\n setPhase('idle')\n setError(err ?? null)\n setNotice(note ?? null)\n }\n\n // 开关被拨到关:正在进行的对话立刻收摊。以前靠「注销第二个按钮」顺带卸载组件\n // 完成这件事(卸载 effect 里 stop 引擎),入口合并成常驻按钮后那个副作用没有了,\n // 必须显式补上——否则对话会带着开着的麦克风一直跑下去。\n const teardownRef = react.useRef<() => void>(() => {})\n teardownRef.current = (): void => { if (phaseRef.current !== 'idle') endSession() }\n react.useEffect(() => { if (!enabled) teardownRef.current() }, [enabled])\n\n /** 交还麦克风,开始听下一句(幂等:只有 thinking/speaking 才需要还)。 */\n const resumeListening = (): void => {\n if (!mountedRef.current || phaseRef.current === 'idle' || phaseRef.current === 'listening') return\n if (turnRef.current !== null) return\n setLive('')\n // barge-in 播报结束:解除回声门控,回到普通聆听。\n engineRef.current?.disarmBargeIn?.()\n engineRef.current?.resume()\n setPhase('listening')\n }\n\n /** 播一句(tts=off 时没有 sink,字幕照常走,回合结束直接还麦)。 */\n const speakSentence = (sentence: string): void => {\n const sink = sinkRef.current\n if (sink === null) return\n sink.enqueue(sentence)\n if (phaseRef.current === 'thinking') {\n setPhase('speaking')\n // 全双工(barge-in,默认关):开始播报时武装回声门控并恢复收音——\n // 门控把 TTS 回声当背景,只有真正的人声持续超出才打断(D19)。\n if (tuningRef.current?.bargeIn === true) engineRef.current?.armBargeIn?.()\n }\n }\n\n /** 提交一句 → 发起回合。 */\n const commitTurn = (text: string): void => {\n const tuning = tuningRef.current\n const actions = actionsRef.current\n if (turnRef.current !== null || phaseRef.current !== 'listening') return\n if (text === '' || tuning === null || actions === undefined) return\n let merged = text\n if (config.behavior.textMode === 'append') {\n // 分隔决策在共享纯函数 appendDraftText(src/client/draft.ts,与录音按钮同源)。\n merged = appendDraftText(draftRef.current, text)\n }\n actions.setDraft(merged)\n actions.submit()\n engineRef.current?.pause()\n turnRef.current = { armed: false, lastText: '', pump: createSentencePump(tuning.firstSentenceMinChars) }\n // submit 没起作用(草稿被吞、会话不可写)时不能把人卡在「思考中」:\n // 一个断句窗口内没看到回合启动,就当作没发出去,把麦克风还回去。\n clearNoReply()\n noReplyRef.current = setTimeout(() => {\n noReplyRef.current = null\n const turn = turnRef.current\n if (turn === null || turn.armed) return\n turnRef.current = null\n setNotice(t('chatNoReply'))\n resumeListening()\n }, tuning.settleMs)\n setPhase('thinking')\n }\n\n /** 打断:止住播报、取消在途回合,立刻回到聆听。 */\n const interrupt = (): void => {\n engineRef.current?.disarmBargeIn?.()\n sinkRef.current?.cancel()\n clearNoReply()\n turnRef.current = null\n if (runningRef.current && props.sessionId !== undefined) cancelRef.current?.(props.sessionId)\n setLive('')\n engineRef.current?.resume()\n setPhase('listening')\n }\n\n /** 当前实际生效的引擎(含 Web Speech 被网络屏蔽后的降级结果)。 */\n const activeEngine = (): string => fallbackEngineRef.current ?? tuningRef.current?.engine ?? 'browser'\n\n /** 失败码 → 用户可读文案。**按引擎分派**:云端引擎下把「云端通道断开」说成\n * 「浏览器不支持 Web Speech,请改用云端 ASR」,是指引完全反向的误导——用户本来\n * 就在用云端。连接级失败(握手超时 / 对端关闭 / 上游转写失败 / 事件流断)一律\n * 归到云端通道文案,不再落进 Web Speech 兜底。 */\n const failMessage = (code: string): string => {\n if (code === 'no-mic' || code === 'mic-denied' || code === 'silent-device' || code === 'no-audio-context') {\n return t('errNoMic')\n }\n if (code === 'network') return t('errWebSpeechNetwork')\n if (code === 'no-worklet' || code === 'capture-failed') return t('errSegmentedUnsupported')\n if (code === 'provider-closed' || code === 'provider-timeout' || code === 'transcription-failed' || code === 'socket-closed') {\n return t('errRealtimeProvider')\n }\n if (code === 'provider-unreachable' || code === 'events-unavailable') {\n return activeEngine() === 'cloud' ? t('errRealtimeProvider') : t('errSegmentedUnreachable')\n }\n return t('errNoSpeechSupport')\n }\n\n const failByCode = (code: string): void => {\n // 自动降级:browser 引擎 network 失败且云端 ASR 已配置 → 切 segmented 立即重开\n // (浏览器识别已被网络屏蔽是持久事实,整个会话都不要再碰 Web Speech)。\n if (code === 'network' && (fallbackEngineRef.current === null && tuningRef.current?.engine === 'browser') && cloudConfigured()) {\n fallbackEngineRef.current = 'segmented'\n endSession(undefined, undefined)\n // 提示经 ref 交给 begin():begin 自己也要写 notice(TTS 不可用),在这里直接\n // setNotice 会被它随后覆盖,用户永远看不到「已切到按句转写」。\n fallbackNoticeRef.current = t('chatWebSpeechFallback')\n begin()\n return\n }\n endSession(undefined, failMessage(code))\n }\n\n /** 开始一次对话。必须在点击回调里调用(Safari 的发声权限只认用户激活上下文)。 */\n const begin = (): void => {\n const tuning = realtimeTuning()\n // 降级后的引擎\n const engine = fallbackEngineRef.current ?? tuning.engine\n // 前置检查按引擎分:segmented 不用 Web Speech,但每句都要过一次已配置的云端转写;\n // cloud 走 host 实时通道(I3 假 provider / I5 真云端),也不需要 Web Speech。\n if (engine === 'segmented') {\n if (!isPcmCaptureSupported()) { setError(t('errSegmentedUnsupported')); setNotice(null); return }\n if (!cloudConfigured()) { setError(t('errSegmentedNeedsCloud')); setNotice(null); return }\n } else if (engine === 'cloud') {\n if (!isPcmCaptureSupported()) { setError(t('errSegmentedUnsupported')); setNotice(null); return }\n } else if (!isWebSpeechSupported()) {\n setError(t('errNoSpeechSupport')); setNotice(null); return\n }\n const ttsReady = tuning.tts === 'cloud' ? isCloudTtsSupported() : isSpeechSynthesisSupported()\n tuningRef.current = tuning\n setError(null)\n // 降级提示优先于 TTS 提示:它解释了「引擎为什么换了」,更该被看到(见 failByCode)。\n const pendingNotice = fallbackNoticeRef.current\n fallbackNoticeRef.current = null\n setNotice(pendingNotice ?? (tuning.tts !== 'off' && !ttsReady ? t('chatNoTts') : null))\n setLive('')\n levelRef.current = -1\n if (tuning.tts !== 'off' && ttsReady) {\n const sink = tuning.tts === 'cloud'\n ? createCloudTtsSink({ language: tuning.language, voice: tuning.ttsVoice })\n : createSpeechSynthesisSink({\n utteranceWatchdogMs: tuning.utteranceWatchdogMs,\n language: tuning.language,\n })\n sink.onDrain = () => { if (turnRef.current === null) resumeListening() }\n sinkRef.current = sink\n sink.prime()\n }\n engineRef.current = createRealtime(engine, tuning.language, tuning.segmented, {\n onPartial: (text) => { if (phaseRef.current === 'listening') setLive(text) },\n onTurn: (text) => { commitTurn(text) },\n onLevel: (level) => {\n const el = spectrumRef.current\n if (el && Math.abs(level - levelRef.current) >= 0.01) {\n levelRef.current = level\n el.style.setProperty('--level', level.toFixed(2))\n }\n },\n onFail: (code) => { failByCode(code) },\n onGap: () => { setNotice(t('chatGap')) },\n onBargeIn: () => {\n // 听到真正的人声打断(回声音量不足触发不了门控):立刻止读、取消回合、\n // 回到聆听。键盘/关门等瞬态被持续时长门槛滤掉,不会误断。\n setNotice(null)\n interrupt()\n },\n })\n // 到点自己收:麦克风不能因为人去倒杯水就一直开着。\n capRef.current = setTimeout(() => { endSession(t('chatEndedLimit')) }, tuning.maxSessionMs)\n setPhase('listening')\n engineRef.current.start()\n }\n\n const toggle = (): void => {\n if (phaseRef.current === 'idle') {\n // 麦克风被录音链路占用时不开对话:两边都抓同一个输入设备,必然互相打架。\n if (canStartRef.current !== undefined && !canStartRef.current()) return\n // 新会话重新按用户配置起:上次会话的 network 降级只该在**那次会话内**粘住\n // (同一次对话里别再碰 Web Speech),不该跨会话——用户可能换了网络,也可能\n // 手动把引擎改回了 browser,那就要按新配置走。\n fallbackEngineRef.current = null\n begin()\n } else if (phaseRef.current === 'listening') endSession()\n else interrupt()\n }\n\n // 快捷键实例:冻结一次,经 ref 转发最新闭包(同麦克风按钮的处理)。\n const handlersRef = react.useRef<{ toggle(): void; isActive(): boolean }>({ toggle: () => {}, isActive: () => false })\n handlersRef.current = { toggle, isActive: () => phaseRef.current !== 'idle' }\n const instance = react.useMemo(() => ({\n toggle: () => { handlersRef.current.toggle() },\n isActive: () => handlersRef.current.isActive(),\n }), [])\n react.useEffect(() => voiceChatController.mount(instance), [instance])\n\n // 草稿镜像:append 模式要拿用户当时已敲的文字,不能用本帧快照。\n react.useEffect(() => { draftRef.current = props.input?.draft ?? '' }, [props.input?.draft])\n // 提示自动消散(与麦克风按钮同节奏)。\n react.useEffect(() => {\n if (error === null && notice === null) return\n const timer = setTimeout(() => { setError(null); setNotice(null) }, 6000)\n return () => clearTimeout(timer)\n }, [error, notice])\n\n // 回复跟读:owner share 每次 flush 都会带新快照进来,这里只做「喂泵 + 判收摊」。\n react.useEffect(() => {\n const turn = turnRef.current\n if (turn === null) return\n if (replyText !== turn.lastText) {\n turn.lastText = replyText\n for (const sentence of turn.pump.feed(replyText)) speakSentence(sentence)\n }\n if (running && !turn.armed) { clearNoReply(); turn.armed = true }\n if (turn.armed && !running) {\n for (const sentence of turn.pump.finish()) speakSentence(sentence)\n turnRef.current = null\n const sink = sinkRef.current\n if (sink === null || !sink.active) resumeListening()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [replyText, running])\n\n // 卸载:拆会话、拆播报,不留活着的麦克风或排队里的句子。\n react.useEffect(() => {\n mountedRef.current = true\n return () => {\n mountedRef.current = false\n clearNoReply()\n if (capRef.current !== null) clearTimeout(capRef.current)\n engineRef.current?.stop()\n sinkRef.current?.dispose()\n }\n }, [])\n\n const busy = phase !== 'idle'\n // 悬停提示按系统语言(与 DSH 界面语言解耦),其余文案仍随界面语言。\n const sys = systemDict()\n const title = phase === 'idle' ? sys.chatTitle\n : phase === 'listening' ? sys.chatListeningTitle\n : phase === 'thinking' ? sys.chatThinkingTitle\n : sys.chatSpeakingTitle\n const shown = phase === 'listening' ? tailText(live) : tailText(replyText)\n const hintText = shown !== ''\n ? shown\n : phase === 'listening' ? t('chatListeningTitle')\n : phase === 'thinking' ? t('chatThinkingHint')\n : t('chatSpeakingHint')\n // 状态条按「错误 > 提示 > 字幕」的优先级只显示一条(与录音链路同款取舍)。\n // 不 memo:里面挂着 spectrumRef 与 interrupt 的最新闭包,缓存反而要小心陈旧。\n const status = ((): react.ReactNode => {\n if (error !== null) {\n return (\n <span className=\"dshav-hotkey-hint\" data-kind=\"err\" role=\"status\">\n <span className=\"dshav-dot\" style={{ background: 'var(--dshav-danger)' }} />\n <span className=\"dshav-hint-text\">{error}</span>\n <button type=\"button\" className=\"dshav-hint-dismiss\" aria-label={t('dismiss')} onClick={() => { setError(null) }}>×</button>\n </span>\n )\n }\n if (notice !== null) {\n return (\n <span className=\"dshav-hotkey-hint\" data-kind=\"notice\" role=\"status\">\n <span className=\"dshav-dot\" />\n <span className=\"dshav-hint-text\">{notice}</span>\n <button type=\"button\" className=\"dshav-hint-dismiss\" aria-label={t('dismiss')} onClick={() => { setNotice(null) }}>×</button>\n </span>\n )\n }\n if (!busy) return null\n return (\n <span className=\"dshav-hotkey-hint\" data-kind=\"caption\" data-state={phase} role=\"status\" aria-live=\"polite\">\n {phase === 'listening' ? <span className=\"dshav-dot\" /> : <Spinner />}\n <span className=\"dshav-hint-text\">{hintText}</span>\n {phase === 'listening' && (\n <span className=\"dshav-spectrum\" ref={spectrumRef} aria-hidden=\"true\">\n <SpectrumBars />\n </span>\n )}\n {phase !== 'listening' && (\n <button type=\"button\" className=\"dshav-hint-dismiss\" aria-label={t('chatInterrupt')} title={t('chatInterrupt')} onClick={interrupt}>×</button>\n )}\n </span>\n )\n })()\n\n return {\n phase,\n active: busy,\n enabled,\n title,\n toggle,\n status,\n }\n}\n","/** dsh-asr-voice — client 语音按钮(conversation.input.right 工具行)。\n * **一个按钮,两个动作**:点 = 录音转写,长按 = 语音对话。\n * 曾经是并排两个按钮(麦克风 + 气泡),交互面冗余、还占输入栏宽度;合并后\n * 用「点/长按」这对手势区分,两条链路各自仍是完整闭环:\n * - 点:录音(浏览器 Web Speech 实时 / 云端 MediaRecorder)→ 停止 → 转写文本\n * → 提示词优化(heuristic 即时 / llm 预览卡)→ 填入草稿(inputActions.setDraft),\n * 可选自动发送(inputActions.submit)。\n * - 长按:进入实时语音对话(voice-chat.tsx),再点结束或打断。\n * 两条链路共用同一个输入设备,所以互斥守卫成对出现:录音侧在 begin() 与长按许可里\n * 读 chatActiveRef,对话侧经 canStart 读麦克风状态。\n * 动效(microanimations 原则:反馈/定向/愉悦,克制):\n * - 录音:多层呼吸光环(back.out 缓动、错开延迟、非机械)+ 实时频谱条\n * (cloud 真实 RMS / browser 模拟能量,CSS 变量驱动)\n * - 状态提示条:滑入 + 呼吸点(recording)/ 转圈(transcribing/optimizing)\n * - 按钮:hover 微缩放、active 按压缩放;长按可用时右上角一颗静态小点\n * 独立契约:本组件只依赖官方 slot 标准 kit(inputActions / session / input),\n * 不依赖任何第三方插件;样式 data 标签与命名空间唯一。\n */\nimport * as react from 'react'\n// Type-only: pulls the ui-conversation SlotMap merge (input seats + standard kit).\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { cloudConfigured, config, recordBehavior } from './config.ts'\nimport { appendDraftText } from './draft.ts'\nimport { resolveEngine, shouldFallbackToCloud } from './engine.ts'\nimport { heuristicOptimize, llmOptimize } from './optimize.ts'\nimport { createVoiceRecorder, isWebSpeechSupported, type VoiceRecorder } from './recorder.ts'\nimport { fromTo } from './animate.ts'\nimport { ChatIcon, MicIcon, RecDot, SpectrumBars, Spinner } from './icons.tsx'\nimport { DEFAULT_LONG_PRESS_MS, createLongPressGate } from './long-press.ts'\nimport { routeButtonPress, type MicPhase } from './button-route.ts'\nimport { useVoiceChat, type ReplyBlock } from './voice-chat.tsx'\nimport { systemDict } from './locales.ts'\nimport type { LocaleT } from './locales.ts'\n\n/** 输入动作最小面(来自官方 standard kit 的 inputActions prop)。 */\ninterface InputActionsLike {\n setDraft(text: string): void\n submit(): void\n}\n\n/** 组件收到的 owner share + standard kit 最小面(结构类型,参照官方契约)。\n * 一个按钮同时驱动两条链路:录音转写(本文件)与实时语音对话(voice-chat.tsx),\n * 所以 owner share 要同时满足两边——`phase`/`blank` 归录音用,\n * `running`/`partial` 归对话用(跟读流式回复)。 */\nexport interface VoiceButtonProps {\n sessionId?: string\n input?: { draft?: string; phase?: string }\n session?: {\n blank?: boolean\n composerPhase?: string\n running?: boolean\n partial?: { blocks?: readonly ReplyBlock[] } | null\n }\n inputActions?: InputActionsLike\n /** 取消当前回合:由 host 半区经 slot inject 注入(组件拿不到 ctx)。 */\n cancelTurn?: (sessionId: string) => void\n /** 本地化翻译函数(由 slot inject 注入)。 */\n t: LocaleT\n}\n\n/** 组件内部状态机(与 button-route.ts 的 MicPhase 同源,单点定义防漂移)。 */\ntype VoiceState = MicPhase\n\n/** 全局录音控制器:只驱动「最后挂载」的实例(当前可见会话)。 */\nexport const voiceController = {\n toggle: (): void => { current?.toggle() },\n isRecording: (): boolean => current?.isRecording() ?? false,\n /** 是否处于 busy(录音 / 识别 / 优化)——快捷键据此区分\"打断\"与\"开始\"。 */\n isBusy: (): boolean => current?.isBusy() ?? false,\n mount(instance: { toggle(): void; isRecording(): boolean; isBusy(): boolean }): () => void {\n current = instance\n return () => { if (current === instance) current = undefined }\n },\n}\nlet current: { toggle(): void; isRecording(): boolean; isBusy(): boolean } | undefined\n\n/** 录音按钮 + 状态提示条 + 预览卡。 @param props - slot 注入的 owner share + 标准 kit + 翻译函数。 */\nexport function VoiceButton(props: VoiceButtonProps): react.ReactElement {\n const { inputActions, t } = props\n // 只有拿不到标准 kit 的 inputActions(无可用会话/惰性输入栏)才禁用。\n // 注意:不按 session.blank 禁用——新版 DSH 里空白会话(hero 新对话)的输入栏\n // 完全可用(可打字/发送首条消息),语音输入应同样可用;实测 blank 时点麦克风\n // 无任何响应(disabled 吞掉点击),玩家以为是坏按钮。\n const disabled = !inputActions\n const [state, setState] = react.useState<VoiceState>('idle')\n const [error, setError] = react.useState<string | null>(null)\n const [notice, setNotice] = react.useState<string | null>(null)\n const [interim, setInterim] = react.useState<string>('')\n const [preview, setPreview] = react.useState<{ original: string; optimized: string } | null>(null)\n // 快速路径标志:optimizing 状态下草稿已填入(区别于预览卡路径的等待)。\n const [optimizingDraft, setOptimizingDraft] = react.useState(false)\n\n const wrapRef = react.useRef<HTMLSpanElement | null>(null)\n const hintRef = react.useRef<HTMLSpanElement | null>(null)\n const spectrumRef = react.useRef<HTMLSpanElement | null>(null)\n // 频谱电平上次写入值:rAF 每帧都会回调,只在变化超过阈值时写 CSS 变量,\n // 避免无谓的样式传播(波动是连续的,0.01 步进视觉无差)。\n const levelRef = react.useRef(-1)\n const recorderRef = react.useRef<VoiceRecorder | null>(null)\n const optimizeControllerRef = react.useRef<AbortController | null>(null)\n const stateRef = react.useRef<VoiceState>('idle')\n stateRef.current = state\n // 对话是否正在进行。两条链路共用同一个输入设备,谁先占了另一个就不能再抢:\n // 录音侧在 begin() 与长按许可里都读它(对话侧的对称守卫见 voice-chat.tsx 的 canStart)。\n const chatActiveRef = react.useRef(false)\n // 长按要用的两样东西在下面 useVoiceChat 之后才有值,先留 ref 转发:\n // 长按回调活在事件里,不能用挂载那一帧的闭包。\n const chatEnabledRef = react.useRef(false)\n const chatToggleRef = react.useRef<() => void>(() => {})\n // 长按手势:点 = 转写,长按 = 对话。判定是纯逻辑(long-press.ts),这里只管接线。\n const longPressRef = react.useRef<ReturnType<typeof createLongPressGate> | null>(null)\n if (longPressRef.current === null) {\n longPressRef.current = createLongPressGate({\n thresholdMs: DEFAULT_LONG_PRESS_MS,\n // 麦克风正忙(录音/识别/优化)时不认长按:此时按住只该是「点」的拖长,\n // 用户想的是停止录音,不该顺手把对话也开起来。\n isLongPressAllowed: () => chatEnabledRef.current && stateRef.current === 'idle',\n onLongPress: () => { chatToggleRef.current() },\n })\n }\n // 打断标志:cancel() 置位后,迟到的 onDone/onFail/优化结果一律丢弃。\n const cancelledRef = react.useRef(false)\n // 会话代际:begin() 递增,异步回调捕获自己的代际;cancel→立即 begin 时\n // 旧 promise 的 finally 用代际差异识别并丢弃,避免改写新会话状态。\n const generationRef = react.useRef(0)\n // 草稿最新值(props 异步回写)与本次填入的值——后台优化替换时防覆盖用户编辑。\n const draftRef = react.useRef<string>('')\n const insertedRef = react.useRef<string | null>(null)\n react.useEffect(() => {\n const next = props.input?.draft ?? ''\n // 草稿离开插件的掌控(用户编辑/发送清空)→ 后台优化立即作废:\n // 优化走 ctx.llm.stream(与 agent 回合同一条 LLM 通道),发送后仍挂着会让\n // 新回合的 LLM 请求排队(发送后卡顿的直接来源),且结果对已发送草稿毫无价值。\n if (insertedRef.current !== null && next !== insertedRef.current && optimizeControllerRef.current !== null) {\n optimizeControllerRef.current.abort()\n optimizeControllerRef.current = null\n }\n draftRef.current = next\n }, [props.input?.draft])\n // 错误/提示自动消散(6s),避免状态条永久悬挂;也可点 × 手动关闭。\n react.useEffect(() => {\n if (error === null && notice === null) return\n const timer = setTimeout(() => {\n setError(null)\n setNotice(null)\n }, 6000)\n return () => clearTimeout(timer)\n }, [error, notice])\n const dismissHint = (): void => {\n setError(null)\n setNotice(null)\n }\n\n // 挂载/卸载:注册到全局控制器(快捷键驱动当前实例)。\n // instance 是 useMemo([]) 冻结产物,若直接捕获首帧 begin/finish/cancel,\n // 其闭包里的 inputActions/props.input 也是首帧快照——会话就绪前挂载时,\n // 快捷键路径 finalize 会拿不到 inputActions 静默丢字。经 ref 每渲染转发最新闭包。\n const handlersRef = react.useRef<{ begin(): Promise<void>; finish(): void; cancel(): void }>({\n begin: async () => {}, finish: () => {}, cancel: () => {},\n })\n const instance = react.useMemo(() => ({\n toggle: () => {\n if (stateRef.current === 'idle') { void handlersRef.current.begin() } else if (stateRef.current === 'recording') { void handlersRef.current.finish() } else { handlersRef.current.cancel() }\n },\n isRecording: () => stateRef.current === 'recording',\n isBusy: () => stateRef.current !== 'idle',\n }), [])\n react.useEffect(() => voiceController.mount(instance), [instance])\n\n const setPhase = (next: VoiceState): void => {\n // 离开 recording 就停光环:静音/超时自动停止(onDone 直达)、录音中出错\n // (onFail)与 no-speech 都不经 finish/cancel,漏停会让 repeat:Infinity 的\n // 波纹 rAF 循环空转到卸载,idle 按钮上残留红色脉冲环。\n if (stateRef.current === 'recording' && next !== 'recording') stopWave()\n stateRef.current = next\n setState(next)\n }\n\n const showError = (code: string, detail?: string): void => {\n // 静音守卫:录音整段静音(麦克风未采到声)→ 明确提示 + 输入设备名,误打误撞的 ASR 报错。\n if (code === 'transcribe' && detail?.startsWith('no-sound')) {\n const label = detail.slice('no-sound'.length).replace(/^:/, '')\n const shown = label.length > 80 ? `${label.slice(0, 80)}…` : label\n setError(`${t('errNoSound')}${shown ? `(${shown})` : ''}`)\n setNotice(null)\n setPhase('idle')\n return\n }\n // 错误码 → 文案用 switch 而非嵌套三元:这条链已到 8 个分支,深层三元改一处极易\n // 错位到相邻分支(本仓刚修过「云端引擎却提示不支持 Web Speech」那类反向误导)。\n const msg = ((): string => {\n switch (code) {\n case 'mic-denied':\n case 'no-mic':\n return t('errNoMic')\n case 'recorder-start-failed':\n case 'recorder-unsupported':\n return t('errRecorderStart')\n case 'no-speech-support':\n return t('errNoSpeechSupport')\n case 'network':\n return t('errWebSpeechNetwork')\n case 'cloud-not-configured':\n return t('errCloudNotConfigured')\n case 'optimize':\n return `${t('errOptimize')}${detail ? `: ${detail}` : ''}`\n default:\n return `${t('errTranscribe')}${detail ? `: ${detail}` : ''}`\n }\n })()\n setError(msg)\n setNotice(null)\n setPhase('idle')\n }\n\n /** 启动指定引擎的录音(云端自动兜底:auto 模式下浏览器失败 → 云端重试一次)。 */\n const startWithEngine = (engine: 'browser' | 'cloud'): void => {\n let recorder: VoiceRecorder\n // 本会话代际:recorder 级 cancelled 标志只防 abort,防不了「停止→立刻重开」\n // 的会话替换——旧 recorder 的迟到回调(onDone/onFail/onState/onInterim)必须\n // 用代际差异丢弃,否则会 null 掉新会话的 recorderRef 或污染新草稿。\n const myGen = generationRef.current\n const stale = (): boolean => cancelledRef.current || generationRef.current !== myGen\n try {\n recorder = createVoiceRecorder(engine, config.language, (code) => {\n if (stale()) return\n if (code === 'no-speech') {\n // 无语音:当作正常结束,给一个轻提示(非错误)。\n setPhase('idle')\n setError(null)\n setNotice(t('noSpeechDetected'))\n return\n }\n // auto 兜底:浏览器不可用(网络/权限被浏览器 Web Speech 拒)且云端已配置 → 重试云端。\n const recoverable = code === 'network' || code === 'not-allowed' || code === 'service-not-allowed' || code === 'no-speech-support'\n if (shouldFallbackToCloud(engine, config.asr.provider, cloudConfigured(), recoverable)) {\n setNotice(t('fallbackToCloud'))\n startWithEngine('cloud')\n return\n }\n showError(code)\n }, recordBehavior())\n } catch {\n if (shouldFallbackToCloud(engine, config.asr.provider, cloudConfigured())) {\n setNotice(t('fallbackToCloud'))\n startWithEngine('cloud')\n return\n }\n showError('no-speech-support')\n return\n }\n recorderRef.current = recorder\n levelRef.current = -1\n recorder.onInterim = (text) => { if (!stale()) setInterim(text) }\n recorder.onState = (s) => { if (s === 'transcribing' && !stale()) setPhase('transcribing') }\n recorder.onLevel = (rms) => {\n // 频谱条:CSS 变量驱动柱高(避免每帧 React 渲染)。只在与上次写入\n // 差 ≥0.01 时更新(rAF 每帧回调,连续波动微小变化不触发样式传播)。\n const was = levelRef.current\n if (spectrumRef.current && Math.abs(rms - was) >= 0.01) {\n levelRef.current = rms\n spectrumRef.current.style.setProperty('--level', rms.toFixed(2))\n }\n }\n // 结果统一经 onDone/onFail 送达(手动 / 静音自动停止 / 超时自动停止三条路径收敛),\n // 由 handleTranscribed 消费文本、showTranscribeError 消费错误。\n recorder.onDone = (text) => { if (!stale()) handleTranscribed(text) }\n recorder.onFail = (error) => { if (!stale()) showTranscribeError(error) }\n setPhase('recording')\n startWave()\n recorder.start()\n }\n\n const begin = async (): Promise<void> => {\n // 对话正占着麦克风:录音键(尤其快捷键那条路)不许抢设备。\n // 按钮点击本来就会被路由到对话分支,这里是给快捷键兜底。\n if (chatActiveRef.current) return\n setError(null)\n setNotice(null)\n setInterim('')\n setOptimizingDraft(false)\n // 上一段 preview 未确认就开新录音:旧预览确认会以旧文本覆盖新会话草稿,先清掉。\n setPreview(null)\n cancelledRef.current = false\n // 新会话代际:让旧异步回调(优化/迟到结果)用自己的代际差异识别并丢弃。\n generationRef.current += 1\n optimizeControllerRef.current = new AbortController()\n // 引擎决策是纯函数(src/client/engine.ts):入参配置三态 + 两个可用性标志,出参决定。\n const engine = resolveEngine(config.asr.provider, isWebSpeechSupported(), cloudConfigured())\n if (engine === 'cloud' && !cloudConfigured()) {\n showError('cloud-not-configured')\n return\n }\n if (engine === 'browser' && !isWebSpeechSupported()) {\n showError('no-speech-support')\n return\n }\n startWithEngine(engine)\n }\n\n /** 转写错误统一入口(onFail):取消后到达的错误一律丢弃。 */\n const showTranscribeError = (error: unknown): void => {\n if (cancelledRef.current) return\n recorderRef.current = null\n showError('transcribe', String(error instanceof Error ? error.message : error))\n }\n\n/** 转写完成统一入口(onDone):默认快速路径(preview=false)——ASR 文本返回后 立即把清洗版填入草稿,LLM 优化在后台跑;preview / autoSend 走等优化路径。 */\n const handleTranscribed = (text: string): void => {\n if (cancelledRef.current) return\n const myGen = generationRef.current\n recorderRef.current = null\n const cleaned = text.trim()\n if (cleaned === '') { setPhase('idle'); return }\n\n const mode = config.optimize.mode\n if (mode === 'llm') {\n // 文本本来就干净(无语气词/标点问题)→ 跳过 LLM 优化,直接走填入/发送流程。\n const clean = heuristicOptimize(cleaned)\n // autoSend 保持原流程(说完即发,用优化后文本);preview = 预览卡确认。\n if (config.behavior.autoSend || config.optimize.preview) {\n if (clean === cleaned) {\n finalize(cleaned)\n return\n }\n setPhase('optimizing')\n void llmOptimize(cleaned, {\n provider: config.optimize.llm.provider,\n model: config.optimize.llm.model,\n }, optimizeControllerRef.current?.signal)\n .then((optimized) => {\n if (cancelledRef.current || generationRef.current !== myGen) return\n if (config.optimize.preview) {\n setPreview({ original: cleaned, optimized })\n setPhase('idle')\n } else {\n finalize(optimized)\n }\n })\n .catch((error: unknown) => {\n if (cancelledRef.current || generationRef.current !== myGen) return\n showError('optimize', String(error instanceof Error ? error.message : error))\n })\n return\n }\n // 快速路径(默认):立即填入清洗版,优化后台替换。\n const fast = clean || cleaned\n insertedRef.current = finalize(fast)\n setOptimizingDraft(true)\n setPhase('optimizing')\n void runBackgroundOptimize(cleaned)\n return\n }\n finalize(heuristicOptimize(cleaned))\n }\n\n/** 快速路径(preview=false 默认):ASR 文本返回后立即把清洗版填入草稿, LLM 优化在后台跑,完成后仅在用户未编辑草稿时替换。 */\n const runBackgroundOptimize = async (raw: string): Promise<void> => {\n const myGen = generationRef.current\n try {\n const target = {\n provider: config.optimize.llm.provider,\n model: config.optimize.llm.model,\n }\n const optimized = await llmOptimize(raw, target, optimizeControllerRef.current?.signal)\n // 防覆盖:仅当草稿仍是我们填入的文本时才替换(用户已编辑则保留编辑)。\n if (cancelledRef.current || generationRef.current !== myGen) return\n if (draftRef.current === insertedRef.current && inputActions !== undefined) {\n inputActions.setDraft(optimized)\n }\n } catch {\n // 后台优化失败不打断用户:草稿已可用,仅轻提示(6s 后自动消散,可点 × 关闭)。\n // 取消→立刻重开时 cancelledRef 已重置,须连同会话代际一起判定,防旧会话假失败提示串台。\n // 发送/编辑触发的主动放弃(abort 已把 optimizeControllerRef 置 null)同样不算失败。\n if (cancelledRef.current || generationRef.current !== myGen) return\n if (optimizeControllerRef.current === null) return\n setNotice(t('optimizeFailedKeep'))\n } finally {\n // 打断(cancel)后旧 promise 的 finally 不得改写新会话状态:\n // cancel → 立即 begin 时 cancelledRef 已重置,若直接 setPhase('idle')\n // 会把新录音打回 idle。用会话代际标志(toast 由 begin 递增)区分。\n if (cancelledRef.current || generationRef.current !== myGen) return\n insertedRef.current = null\n setOptimizingDraft(false)\n setPhase('idle')\n }\n }\n\n /** 手动停止:结束录音并进入识别(结果经 onDone 送达)。 */\n const finish = (): void => {\n const recorder = recorderRef.current\n if (!recorder) { setPhase('idle'); return }\n stopWave()\n setNotice(null)\n setPhase('transcribing')\n void recorder.stop().catch(() => { /* 错误经 onFail 送达 */ })\n }\n\n /** 打断:中止录音 / 转写 / 优化,丢弃一切迟到结果,回到 idle。 */\n const cancel = (): void => {\n if (cancelledRef.current && stateRef.current === 'idle') return\n cancelledRef.current = true\n const recorder = recorderRef.current\n recorderRef.current = null\n optimizeControllerRef.current?.abort()\n optimizeControllerRef.current = null\n if (recorder) recorder.abort()\n stopWave()\n setInterim('')\n insertedRef.current = null\n setOptimizingDraft(false)\n setPreview(null)\n setNotice(null)\n setError(null)\n setPhase('idle')\n }\n\n /** 把最终文本填入草稿(完整替换 / 末尾追加)+ 可选剪贴板。\n * 返回实际写入草稿的字符串(append 模式下 = existing + sep + text)。 */\n const finalize = (text: string): string => {\n if (text === '') { setPhase('idle'); return text }\n if (inputActions) {\n if (config.behavior.textMode === 'append') {\n // draftRef 是草稿的权威镜像(effect 同步 props 异步回写);props.input?.draft\n // 是本渲染帧快照,录音期间的编辑会读不到,append 会覆盖丢字。\n // 分隔决策在共享纯函数 appendDraftText(src/client/draft.ts,与语音对话按钮同源)。\n text = appendDraftText(draftRef.current, text)\n }\n inputActions.setDraft(text)\n if (config.behavior.autoSend) inputActions.submit()\n }\n if (config.behavior.copyToClipboard) {\n try {\n void navigator.clipboard?.writeText(text).catch(() => { /* 剪贴板不可用:静默 */ })\n } catch { /* noop */ }\n }\n setPhase('idle')\n return text\n }\n // 最新闭包转发的挂载点:所有 handler 定义完毕后更新,供冻结的 instance 调用。\n handlersRef.current = { begin, finish, cancel }\n\n const onConfirm = (): void => {\n if (preview) finalize(preview.optimized)\n setPreview(null)\n }\n\n // ── 呼吸光环(back.out 缓动 + 错开延迟 + 变化幅度,非机械同步) ──\n // 保存无限循环补间的 handle,stop 时 kill 掉——否则 repeat:Infinity 的 rAF 循环\n // 永不停(stopWave 只改内联样式,下一帧又被动画覆盖回去,红色波纹残留)。\n const waveHandlesRef = react.useRef<Array<{ kill(): void }>>([])\n const startWave = (): void => {\n const wrap = wrapRef.current\n if (!wrap) return\n stopWave()\n // 系统 reduced-motion:GSAP 呼吸环是内联样式驱动,CSS media query 管不到,\n // 命中时跳过无限循环动画,只画静态第一帧(半透明外圈)。\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n wrap.querySelectorAll<HTMLElement>('.dshav-wave-ring').forEach((ring) => {\n ring.style.opacity = '0.5'\n ring.style.transform = 'scale(0.72)'\n })\n return\n }\n wrap.querySelectorAll<HTMLElement>('.dshav-wave-ring').forEach((ring, i) => {\n ring.style.opacity = '0.5'\n const spread = 1.9 + (i % 2) * 0.35\n const duration = 1.35 + (i % 2) * 0.2\n const handle = fromTo(ring, { scale: 0.72, opacity: 0.5 }, {\n scale: spread,\n opacity: 0,\n duration,\n delay: i * 0.24,\n ease: 'back.out',\n repeat: Infinity,\n })\n waveHandlesRef.current.push(handle)\n })\n }\n const stopWave = (): void => {\n for (const handle of waveHandlesRef.current) {\n try { handle.kill() } catch { /* noop */ }\n }\n waveHandlesRef.current = []\n const wrap = wrapRef.current\n if (!wrap) return\n wrap.querySelectorAll<HTMLElement>('.dshav-wave-ring').forEach((ring) => {\n ring.style.opacity = '0'\n ring.style.transform = ''\n })\n }\n\n // 卸载清理:停止录音 + 停止波纹 + 中止在途优化(防对已卸载会话 setDraft)。\n react.useEffect(() => () => {\n stopWave()\n recorderRef.current?.abort()\n optimizeControllerRef.current?.abort()\n }, [])\n\n // ── 语音对话(同一按钮的另一半能力) ────────────────────────────────\n // 对话的状态机、引擎、播报都在 voice-chat.tsx;这里只取它需要的那几样:\n // 图标/标题/点击动作,以及它自己的状态提示条(放进同一个 wrap 才定位得准)。\n const chat = useVoiceChat({\n sessionId: props.sessionId,\n session: props.session,\n input: props.input,\n inputActions,\n cancelTurn: props.cancelTurn,\n // 录音链路占着麦克风时不给开对话:两边抓的是同一个输入设备。\n canStart: () => stateRef.current === 'idle',\n t,\n })\n chatActiveRef.current = chat.active\n chatEnabledRef.current = chat.enabled\n chatToggleRef.current = chat.toggle\n\n /** 点按钮:对话中归对话,否则归录音。仲裁本身是纯函数(button-route.ts)。 */\n const onButtonClick = (): void => {\n const action = routeButtonPress({\n // 长按刚触发过:浏览器照样会补一个 click,读一次就复位。\n longPress: longPressRef.current?.shouldSuppressClick() === true,\n chatActive: chat.active,\n micPhase: state,\n })\n if (action === 'none') return\n if (action === 'chat') { chat.toggle(); return }\n if (action === 'begin') { void begin(); return }\n if (action === 'finish') { void finish(); return }\n cancel()\n }\n // 长按用指针捕获:手指/鼠标移出按钮后 pointerup 仍回到按钮,不会漏掉 release。\n const onPointerDown = (e: react.PointerEvent<HTMLButtonElement>): void => {\n // 多指同时按下时只认主指针(第二个指针不该重启计时)。\n if (!e.isPrimary) return\n try { e.currentTarget.setPointerCapture(e.pointerId) } catch { /* 指针已失效:退化成普通点击 */ }\n longPressRef.current?.press()\n }\n const onPointerUp = (): void => { longPressRef.current?.release() }\n // pointercancel(浏览器接管手势)与 lostpointercapture(捕获意外释放)都要收摊,\n // 否则 pressed 卡在 true,下一次 press 会被自己忽略掉。\n const onPointerAbort = (): void => { longPressRef.current?.cancel() }\n\n const busy = state !== 'idle'\n // 悬停提示按系统语言(与 DSH 界面语言解耦),其余文案仍随界面语言。\n const sys = systemDict()\n // 对话进行中时按钮的语义整个归对话;空闲时提示「长按可对话」,否则用户没处知道。\n const title = chat.active\n ? chat.title\n : busy\n ? state === 'recording' ? sys.recordingTitle : state === 'transcribing' ? sys.transcribingTitle : sys.optimizingTitle\n : chat.enabled ? sys.micTitleHoldChat : sys.micTitle\n // 两条链路共用一条提示位(同一个 wrap,绝对定位会重叠)。优先级:\n // 对话进行中的字幕 > 录音的错误/提示 > 录音进行中的状态 > 对话留下的提示。\n // 录音在途的状态排在对话「留下的」提示之前——前者是正在发生的事。\n const chatStatus = chat.active || (!busy && error === null && notice === null) ? chat.status : null\n\n return (\n <react.Fragment>\n <span className=\"dshav-mic-wrap\" ref={wrapRef}>\n <button\n type=\"button\"\n className=\"dshav-mic-button\"\n data-state={chat.active ? chat.phase : state}\n data-mode={chat.active ? 'chat' : 'input'}\n data-chat={chat.enabled ? 'ready' : 'none'}\n aria-label={title}\n aria-pressed={chat.active || state === 'recording'}\n disabled={disabled}\n onClick={onButtonClick}\n onPointerDown={onPointerDown}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerAbort}\n onLostPointerCapture={onPointerAbort}\n >\n {chat.active\n ? (chat.phase === 'listening' ? <RecDot /> : <ChatIcon />)\n : (state === 'recording' ? <RecDot /> : <MicIcon />)}\n {/* 悬停气泡:不依赖浏览器原生 title(自动化浏览器会禁用),文案随系统语言 */}\n <span className=\"dshav-tooltip\" role=\"tooltip\">{title}</span>\n <span className=\"dshav-wave\" aria-hidden=\"true\">\n <span className=\"dshav-wave-ring\" data-ring=\"1\" />\n <span className=\"dshav-wave-ring\" data-ring=\"2\" />\n <span className=\"dshav-wave-ring\" data-ring=\"3\" />\n </span>\n </button>\n {chatStatus !== null ? chatStatus : (\n <react.Fragment>\n {error !== null && (\n <span className=\"dshav-hotkey-hint\" data-kind=\"err\" role=\"status\">\n <span className=\"dshav-dot\" style={{ background: 'var(--dshav-danger)' }} />\n <span className=\"dshav-hint-text\">{error}</span>\n <button type=\"button\" className=\"dshav-hint-dismiss\" aria-label={sys.dismiss} onClick={dismissHint}>×</button>\n </span>\n )}\n {notice !== null && (\n <span className=\"dshav-hotkey-hint\" data-kind=\"notice\" role=\"status\">\n <span className=\"dshav-dot\" />\n <span className=\"dshav-hint-text\">{notice}</span>\n <button type=\"button\" className=\"dshav-hint-dismiss\" aria-label={sys.dismiss} onClick={dismissHint}>×</button>\n </span>\n )}\n {busy && (\n <span className=\"dshav-hotkey-hint\" data-state={state} ref={hintRef} role=\"status\">\n {state === 'recording' ? <span className=\"dshav-dot\" /> : <Spinner />}\n {state === 'recording' && interim !== '' ? <span className=\"dshav-hint-text\">{interim}</span> : null}\n {state === 'optimizing' && optimizingDraft ? <span className=\"dshav-hint-text\">{t('optimizingHint')}</span> : null}\n {state === 'transcribing' ? <span className=\"dshav-hint-text\">{t('transcribingHint')}</span> : null}\n {state === 'recording' && (\n <span className=\"dshav-spectrum\" ref={spectrumRef} aria-hidden=\"true\">\n <SpectrumBars />\n </span>\n )}\n {state !== 'recording' && (\n <button type=\"button\" className=\"dshav-hint-dismiss\" aria-label={t('cancelBusy')} title={t('cancelBusy')} onClick={cancel}>×</button>\n )}\n </span>\n )}\n </react.Fragment>\n )}\n </span>\n {preview !== null && (\n <div className=\"dshav-preview\" role=\"dialog\" aria-label={t('previewTitle')}>\n <div className=\"dshav-preview-title\">\n <MicIcon />\n <span>{t('previewTitle')}</span>\n </div>\n <div className=\"dshav-preview-body\">\n <div className=\"dshav-preview-block\" data-role=\"original\">\n <span className=\"dshav-preview-label\">{t('previewOriginal')}</span>\n <p className=\"dshav-preview-text\" data-role=\"original\">{preview.original}</p>\n </div>\n <div className=\"dshav-preview-block\" data-role=\"optimized\">\n <span className=\"dshav-preview-label\">{t('previewOptimized')}</span>\n <p className=\"dshav-preview-text\" data-role=\"optimized\">{preview.optimized}</p>\n </div>\n </div>\n <div className=\"dshav-preview-actions\">\n <button type=\"button\" className=\"dshav-button dshav-button-outline dshav-button-sm\" onClick={() => setPreview(null)}>{t('previewCancel')}</button>\n <button type=\"button\" className=\"dshav-button dshav-button-primary dshav-button-sm\" onClick={onConfirm}>{t('previewConfirm')}</button>\n </div>\n </div>\n )}\n </react.Fragment>\n )\n}\n","/** dsh-asr-voice — client 半区入口(单 fiber)。\n * 组合:设置卡片(settings.plugin.item)、录音按钮(conversation.input.right)、\n * 快捷键(可选按住说话)。配置由 host settings 服务持有(config.ts)。\n * 独立性契约:入口 id / locale namespace / CSS data 标签 / 路由全部唯一,\n * 只依赖官方 @deepseek-ai/* 服务,不 import 任何第三方插件。\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\n// Type-only: pulls the ui-renderer Context merge (ctx.slots), moved here in dsh-settings alpha.2.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: pulls the settings slot merges (settings.general.item / settings.plugins.tab).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: pulls the ui-settings-plugins SlotMap merge (the settings.plugin.item card seat).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport * as jsxRuntime from 'react/jsx-runtime'\nimport { zh, en } from './locales.ts'\nimport { CSS } from './styles.ts'\nimport {\n adaptLegacyCredentials, bindConfigScope, bindCredentialsApi, config, subscribeConfig,\n type CredentialsApiLike, type LegacyCredentialsApiLike,\n} from './config.ts'\nimport { VoiceSettingsCard } from './settings-card.tsx'\nimport { VoiceButton, voiceController } from './voice-button.tsx'\nimport { voiceChatController } from './voice-chat.tsx'\nimport { matchHotkey, parseHotkey, type HotkeySpec } from './hotkey.ts'\n\nexport { zh, en }\n\nconst NS = 'asr-voice'\n\n/** 硬依赖:设置卡/配置读写必须的顶层服务(与兄弟插件同款:settingsScope 必须硬依赖,\n * 否则卡片包在 scoped inject 里会因某服务不可注入而永不注册——这正是此前设置卡消失的根因)。 */\nexport const inject = [\n 'slots',\n 'locale',\n 'settingsScope',\n]\n\n/** 快捷键处理(按住说话 / 点击切换 / 实时对话进出),随 fiber 生命周期注册。 */\nfunction applyHotkey(): () => void {\n let held = false\n // -------- 快捷键规格缓存:keydown 是高频路径,只有 hotkey 字符串变化才重解析 --------\n let cachedHotkey = ''\n let cachedSpec: HotkeySpec | null = null\n const hotkeySpec = (): HotkeySpec | null => {\n const hk = config.behavior.hotkey\n if (hk !== cachedHotkey) {\n cachedHotkey = hk\n cachedSpec = parseHotkey(hk)\n }\n return cachedSpec\n }\n let cachedChatHotkey = ''\n let cachedChatSpec: HotkeySpec | null = null\n /** 对话快捷键(realtime.hotkey):关掉总开关即失效,两个键撞在一起时对话优先。 */\n const chatHotkeySpec = (): HotkeySpec | null => {\n if (!config.realtime.enabled) return null\n const hk = config.realtime.hotkey\n if (hk !== cachedChatHotkey) {\n cachedChatHotkey = hk\n cachedChatSpec = parseHotkey(hk)\n }\n return cachedChatSpec\n }\n const onKeyDown = (e: KeyboardEvent): void => {\n // 按住不放的自动重复:chat 热键的 toggle 会按重复速率在开/关间抖动。\n // holdToTalk 不受影响——held 只由首次 keydown 置位、keyup 复位,重复事件本就该忽略。\n if (e.repeat) return\n const chatSpec = chatHotkeySpec()\n if (chatSpec !== null && matchHotkey(e, chatSpec)) {\n e.preventDefault()\n e.stopPropagation()\n // 录音链路正占着麦克风(录音/识别/优化):对话抢不到设备,忽略。\n // 守卫放在 preventDefault 之后——这个组合键已经被我们认领了,\n // 无论开不开对话都不该再漏给页面。\n if (!voiceController.isBusy()) voiceChatController.toggle()\n return\n }\n const spec = hotkeySpec()\n if (spec === null) return\n if (!matchHotkey(e, spec)) return\n e.preventDefault()\n e.stopPropagation()\n // 对话进行中录音键不生效:两条链路共用同一个输入设备,谁在跑谁独占。\n // (按钮那一路的对称守卫在 voice-button.tsx 的 begin()。)\n if (voiceChatController.isActive()) return\n if (config.behavior.holdToTalk) {\n // busy(识别/优化中):按一次 = 打断,不进入 held(避免松键误触发新录音)。\n if (voiceController.isBusy()) {\n voiceController.toggle()\n return\n }\n if (!held && !voiceController.isRecording()) {\n held = true\n voiceController.toggle()\n }\n } else if (!voiceController.isRecording()) {\n voiceController.toggle()\n }\n }\n const onKeyUp = (e: KeyboardEvent): void => {\n if (!held) return\n const spec = hotkeySpec()\n if (spec === null || !matchHotkey(e, spec)) return\n held = false\n if (config.behavior.holdToTalk) voiceController.toggle()\n }\n const off = subscribeConfig(() => {\n // 快捷键变更即时生效:无额外动作,监听器每次按键实时解析 config\n })\n window.addEventListener('keydown', onKeyDown, true)\n window.addEventListener('keyup', onKeyUp, true)\n return () => {\n off()\n window.removeEventListener('keydown', onKeyDown, true)\n window.removeEventListener('keyup', onKeyUp, true)\n }\n}\n\nexport function apply(ctx: ClientContext): void {\n // 词典注册。\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'asr-voice: dictionaries')\n // 样式注入(单 <style data-plugin=\"@bittersmilezzz/dsh-asr-voice\"> 标签)。\n ctx.effect(() => {\n const tag = document.createElement('style')\n tag.dataset.plugin = '@bittersmilezzz/dsh-asr-voice'\n tag.dataset.pluginCss = '@bittersmilezzz/dsh-asr-voice/client'\n tag.textContent = CSS\n document.head.appendChild(tag)\n return () => tag.remove()\n }, 'asr-voice: styles')\n\n const t = ctx.locale.bind(NS)\n\n // 语音输入 + 语音对话:同一个座位只放一个按钮(点 = 转写,长按 = 对话)。\n // 对话能力仍由 realtime.enabled 控制,但入口不再随之增删——开关一拨,\n // 按钮内部经 config 订阅立刻跟着变(voice-chat.tsx 的 useRealtimeEnabled),\n // 所以原来那套「注册/注销第二个 entry」的动态逻辑连同它的副作用一起没了。\n ctx.slots.inject('conversation.input.right', () => ctx.slots.register({\n name: 'conversation.input.right',\n id: 'dsh-asr-voice-button',\n order: 10,\n locale: NS,\n inject: (sessionId: string) => ({ sessionId, t, cancelTurn }),\n }, (props) => jsxRuntime.jsx(VoiceButton, props)))\n\n /** 打断当前回合:InputActions 只有 5 个成员、不含 cancel,取消只能走会话作用域的 conversation 服务。 */\n let cancelTurn: (sessionId: string) => void = () => {}\n ctx.inject(['sessions'], (raw) => {\n const sessions = (raw as { sessions?: {\n scope(id: string): { get(name: string): unknown } | undefined\n } }).sessions\n cancelTurn = (sessionId: string): void => {\n try {\n const scoped = sessions?.scope(sessionId)\n const conversation = scoped?.get('conversation') as { cancel?(): Promise<void> } | undefined\n void conversation?.cancel?.()?.catch?.(() => { /* 取消失败会体现在快照里 */ })\n } catch { /* 无会话作用域:打断退化成「止住播报 + 继续听」 */ }\n }\n })\n\n // 语音对话按钮:已并入上面的麦克风按钮(点 = 转写,长按 = 对话),\n // 这里不再注册第二个 entry,也不再有随开关增删的注册副作用。\n\n // 快捷键(默认 Ctrl+Shift+Space;可选按住说话)。\n ctx.effect(applyHotkey, 'asr-voice: hotkey')\n\n // 设置卡片(settings.plugin.item, key: asr-voice)+ 配置绑定。\n // settingsScope 已是顶层硬依赖,卡片直接在 apply 顶层注册(与兄弟插件同款)——\n // 绝不能把卡片包进 ctx.inject([...]):任一服务不可注入则回调永不执行、卡片永不出现\n // (此前把凭据服务的 connection 放进 scoped inject,设置卡就消失过)。\n ctx.effect(() => bindConfigScope(ctx.settingsScope), 'asr-voice: settings scope sync')\n ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({\n name: 'settings.plugin.item',\n key: 'asr-voice',\n locale: NS,\n }, () => jsxRuntime.jsx(VoiceSettingsCard, { t })))\n\n // 凭据绑定(可选服务,单独 scoped inject):alpha.3 起凭据域在 `remote.credentials`,\n // 旧运行时经 `connection.api.credentials` 回退,由适配器归一化。这个回调只负责绑定\n // 凭据,任一服务缺席就永远不执行——但不影响上面卡片的注册。\n ctx.inject(['settingsScope', 'connection', 'remote', 'remote.credentials'], (raw) => {\n const c = raw as ClientContext & {\n connection?: { api?: { credentials?: LegacyCredentialsApiLike } }\n remote?: { credentials?: CredentialsApiLike }\n }\n bindCredentialsApi(c.remote?.credentials ?? adaptLegacyCredentials(c.connection?.api?.credentials))\n })\n}\n\nexport const name = 'dsh-asr-voice'\n"],"mappings":"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"}