xiaoyuan-assistant 0.5.51 → 0.5.54

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.
package/CHANGELOG.md CHANGED
@@ -424,3 +424,21 @@ ls -lh /mnt/data/xiaoyuan-assistant-siliconflow-v0.5.29.zip
424
424
 
425
425
  - 修复 NPM + Vite 场景下内置“收到指令”TTS 使用 `new URL(..., import.meta.url)` 被预构建到 `node_modules/.vite/assets` 后产生 404 的问题。
426
426
  - 两个内置 voice 的提示音改为随 JS 包内嵌 Base64,运行时直接生成 Blob,不再请求静态 MP3 URL。
427
+
428
+ ## v0.5.52
429
+ - 移除小园面板右上角的停止播报/静音图标。
430
+ - 优化语音输入:手动录音前先暂停唤醒识别并等待浏览器释放麦克风,避免双 Recognition 抢占。
431
+ - 增加麦克风权限、设备占用、audio-capture、aborted、启动超时等兼容处理与最多 2 次轻量重试。
432
+ - 语音识别异常不再强制触发 TTS,避免识别与播报互相干扰。
433
+
434
+
435
+ ## v0.5.53
436
+ - 新增“你好小园”唤醒后的本地“我在您请讲”语音,按配置 voice 使用对应内置 MP3。
437
+ - 唤醒后进入连续语音收音,连续 3 秒无新输入才结束收音并进入指令处理。
438
+ - 手动打开小园仍保持输入框模式,不自动抢占麦克风。
439
+
440
+ ## v0.5.54
441
+ - 修复手动打开小园后语音识别偶发 aborted:识别实例改为串行释放,启动前等待旧 Recognition 真正结束。
442
+ - 唤醒监听 pause 改为可等待的异步暂停,避免持续唤醒与手动录音/唤醒后的连续监听抢占麦克风。
443
+ - 首次麦克风授权不再每次识别重复 getUserMedia + stop,减少 Chrome/Edge 音频设备切换导致的 aborted。
444
+ - 内置“我在您请讲”改用本次提供的 zh-CN-XiaoxiaoNeural2.mp3 与 zh-CN-XiaoyiNeural2.mp3,并继续按 voice 选择。
package/README.md CHANGED
@@ -240,3 +240,9 @@ app.use(Xiaoyuan, {
240
240
 
241
241
  - 修复 NPM + Vite 场景下内置“收到指令”TTS 使用 `new URL(..., import.meta.url)` 被预构建到 `node_modules/.vite/assets` 后产生 404 的问题。
242
242
  - 两个内置 voice 的提示音改为随 JS 包内嵌 Base64,运行时直接生成 Blob,不再请求静态 MP3 URL。
243
+
244
+
245
+ ## v0.5.53 语音唤醒
246
+ - `wakeWord` 默认:`你好小园`。
247
+ - 唤醒成功后播放本地 `我在您请讲`,按 `voice` 匹配。
248
+ - 唤醒后的后续语音连续收音,3 秒静默后进入指令处理。
@@ -1246,3 +1246,22 @@ AI 分析开始提示音与数据获取/模型分析并行;多个数据源采
1246
1246
 
1247
1247
  - 修复 NPM + Vite 场景下内置“收到指令”TTS 使用 `new URL(..., import.meta.url)` 被预构建到 `node_modules/.vite/assets` 后产生 404 的问题。
1248
1248
  - 两个内置 voice 的提示音改为随 JS 包内嵌 Base64,运行时直接生成 Blob,不再请求静态 MP3 URL。
1249
+
1250
+ ## v0.5.52 变更
1251
+ - 移除面板右上角停止播报/静音图标,避免用户误触。
1252
+ - 语音输入采用“先暂停唤醒监听 -> 短暂释放麦克风 -> 单次 Recognition”的顺序,降低语音输入偶发失效。
1253
+ - 麦克风权限、设备缺失、设备占用、浏览器启动失败和网络异常增加明确错误处理与轻量重试。
1254
+ - 语音识别错误不自动播报错误 TTS,减少 SpeechRecognition 与 SpeechSynthesis 之间的资源竞争。
1255
+
1256
+
1257
+ ## v0.5.53 唤醒与连续语音流程
1258
+ - 手动打开:只展开输入框,不自动监听。
1259
+ - 说“你好小园”:命中后暂停唤醒监听,立即播放对应 voice 的本地“我在您请讲”MP3。
1260
+ - 播放开始后进入连续收音;3 秒没有新的语音输入,结束本轮收音。
1261
+ - 收音结束后调用“收到指令,请您稍等”本地/缓存 TTS,再解析并按现有步骤流程执行。
1262
+
1263
+ ### v0.5.54 语音输入稳定性
1264
+ - 手动打开小园后点击麦克风:先等待唤醒 Recognition 完整释放,再启动一次性识别。
1265
+ - “你好小园”唤醒:播放本地对应 voice 的“我在您请讲”,随后再进入后续 3 秒静默监听。
1266
+ - 识别启动前只做一次麦克风权限确认,避免反复 getUserMedia 导致浏览器出现 aborted。
1267
+ - 新增可等待 pause/resume,保证全局唤醒监听与用户录音不会并发抢占同一个 Recognition。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xiaoyuan-assistant",
3
- "version": "0.5.51",
3
+ "version": "0.5.54",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "module": "./src/index.js",
@@ -30,9 +30,6 @@
30
30
  <button type="button" class="xy-icon-btn" title="收起" @click="collapse" aria-label="收起">
31
31
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/></svg>
32
32
  </button>
33
- <button type="button" class="xy-icon-btn" title="停止播报" @click="stopSpeech" aria-label="停止播报">
34
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4l16 16M9.5 6.2A7 7 0 0 1 19 12v1.2M4.8 9.1A7 7 0 0 0 5 13"/></svg>
35
- </button>
36
33
  </div>
37
34
  </header>
38
35
 
@@ -120,7 +117,7 @@ const runtime =
120
117
  // 如果宿主项目因为 HMR / 重复依赖导致当前模块暂时拿不到注入值,
121
118
  // 组件保持可挂载,并在 mounted 后再次尝试解析,避免整个页面崩溃。
122
119
  const safeRuntime = runtime || { manager: null, speech: null, options: {} }
123
- const XIAOYUAN_SDK_VERSION = '0.5.51'
120
+ const XIAOYUAN_SDK_VERSION = '0.5.54'
124
121
  const { manager, speech, options } = safeRuntime
125
122
 
126
123
  const wakeWord = options.wakeWord || '你好小园'
@@ -220,19 +217,60 @@ function collapse() {
220
217
  }
221
218
 
222
219
  async function activate(reason = 'manual') {
223
- // 用户点击/唤醒时先完成本地前置 TTS 的解锁与预解码,避免收到识别结果后播放被浏览器拦截。
224
220
  await speech.unlockTTS?.()
225
221
  expanded.value = true
226
- showSubtitle(reason === 'wake' ? '小园已唤醒,请说出您的指令。' : '小园已唤醒,请输入或说出您的指令。')
227
- if (reason === 'wake') {
228
- wakeController?.pause?.()
229
- wakeListening.value = false
230
- // 被唤醒后不再立即播报欢迎语,避免 TTS 与麦克风识别互相干扰。
231
- // 被唤醒后自动进入一次性聆听,用户无需再次点击麦克风。
232
- window.setTimeout(() => {
233
- if (!expanded.value || listening.value || processing.value) return
234
- void listenForCommand({ auto: true })
235
- }, 180)
222
+ showSubtitle(reason === 'wake' ? '我在您请讲' : '小园已唤醒,请输入或说出您的指令。')
223
+
224
+ if (reason !== 'wake') return
225
+
226
+ wakeListening.value = false
227
+ try { await wakeController?.pause?.() } catch (_) {}
228
+
229
+ // 唤醒后的固定应答必须先本地播放,但不等待播放完成;声音开始后即可进入后续收音。
230
+ if (options.enableTTS) {
231
+ try {
232
+ const started = await speech.ttsProvider?.speakWakeResponseAsync?.()
233
+ if (started !== false) {
234
+ await new Promise((resolve) => window.setTimeout(resolve, 120))
235
+ }
236
+ } catch (_) {}
237
+ }
238
+
239
+ if (!expanded.value || processing.value || listening.value) return
240
+ void listenAfterWake()
241
+ }
242
+
243
+ async function listenAfterWake() {
244
+ if (!speech?.listenAfterWake) return
245
+ listening.value = true
246
+ interimText.value = ''
247
+ showSubtitle('我在您请讲…', 0)
248
+ try {
249
+ const spoken = await speech.listenAfterWake({
250
+ silenceMs: 3000,
251
+ retries: 2,
252
+ onInterim: (value) => { interimText.value = value || '' }
253
+ })
254
+ interimText.value = ''
255
+ if (spoken) {
256
+ // 3 秒无后续输入后,才进入“收到指令”前置提示与业务解析流程。
257
+ await handleCommand(spoken, 'voice-wake')
258
+ } else {
259
+ showSubtitle('没有听到您的指令,请再说一次。', 5000)
260
+ }
261
+ } catch (error) {
262
+ interimText.value = ''
263
+ showSubtitle(String(error?.message || '语音识别失败'), 6000)
264
+ } finally {
265
+ listening.value = false
266
+ if (!processing.value && options.enableWakeWord) {
267
+ window.setTimeout(() => {
268
+ if (!processing.value && !listening.value) {
269
+ wakeController?.resume?.()
270
+ wakeListening.value = true
271
+ }
272
+ }, 120)
273
+ }
236
274
  }
237
275
  }
238
276
 
@@ -286,12 +324,21 @@ function interruptForNewCommand() {
286
324
 
287
325
  async function toggleListening() {
288
326
  if (!manager || !speech) return
289
- if (listening.value) return
290
- await speech.unlockTTS?.()
291
- await listenForCommand()
327
+ if (listening.value || processing.value) return
328
+ try {
329
+ await speech.unlockTTS?.()
330
+ // 先彻底暂停唤醒识别,给浏览器一个很短的释放麦克风窗口,避免两个 Recognition 抢占。
331
+ wakeController?.pause?.()
332
+ wakeListening.value = false
333
+ await new Promise((resolve) => window.setTimeout(resolve, 120))
334
+ await listenForCommand({ manual: true })
335
+ } catch (error) {
336
+ const message = String(error?.message || error || '语音识别启动失败')
337
+ showSubtitle(message, 6000)
338
+ }
292
339
  }
293
340
 
294
- async function listenForCommand({ auto = false } = {}) {
341
+ async function listenForCommand({ auto = false, manual = false } = {}) {
295
342
  // 在开始语音识别时就完成本地前置 TTS 的 AudioContext 解锁/预解码,
296
343
  // 这样识别结束后收到结果时无需依赖新的用户手势。
297
344
  await speech.unlockTTS?.()
@@ -302,8 +349,10 @@ async function listenForCommand({ auto = false } = {}) {
302
349
  }
303
350
 
304
351
  // 手动或唤醒后的单次聆听期间,暂停持续唤醒监听,避免两个 Recognition 实例抢麦克风。
305
- wakeController?.pause?.()
306
352
  wakeListening.value = false
353
+ try { await wakeController?.pause?.() } catch (_) {}
354
+ // 等待旧 Recognition 真正 onend,再创建新的 Recognition,减少 Chrome/Edge 的 aborted。
355
+ await new Promise((resolve) => window.setTimeout(resolve, manual ? 220 : 180))
307
356
 
308
357
  listening.value = true
309
358
  interimText.value = ''
@@ -321,9 +370,8 @@ async function listenForCommand({ auto = false } = {}) {
321
370
  } catch (error) {
322
371
  interimText.value = ''
323
372
  const message = String(error?.message || error || '语音识别失败')
324
- addMessage('assistant', message)
325
373
  showSubtitle(message, 6000)
326
- queueSpeak(message)
374
+ // 语音识别错误不再强制播报,避免把浏览器 TTS 与识别重新启动互相干扰。
327
375
  } finally {
328
376
  listening.value = false
329
377
  // 当前指令处理结束后恢复全局唤醒监听。
package/src/index.js CHANGED
@@ -15,7 +15,7 @@ export function useXiaoyuan() {
15
15
  return null
16
16
  }
17
17
 
18
- export const XIAOYUAN_VERSION = '0.5.51'
18
+ export const XIAOYUAN_VERSION = '0.5.52'
19
19
 
20
20
  const Xiaoyuan = {
21
21
  install(app, options = {}) {
@@ -0,0 +1,13 @@
1
+ // Built-in wake response: “我在您请讲” audio, keyed by configured voice.
2
+ // Exact MP3s supplied by the project owner; embedded to avoid Vite/NPM asset URL rewriting.
3
+ export const BUILTIN_WAKE_RESPONSE_AUDIO = Object.freeze({
4
+ 'zh-CN-XiaoxiaoNeural': "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RNQJGqJHjQaEpzf/6fKfKfHypbZp/3bf/VPG5Ut5KtE7ZbfT//NkxPciJDYAAEhGxMbLPn+vlPiTbNP3w5kjTEiMJG9AhRVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxPEhBBHUAFDNOVVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//NkxHwAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
5
+ 'zh-CN-XiaoyiNeural': "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qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqpMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NkxHwAAANIAAAAAKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
6
+ })
7
+
8
+ export function base64ToWakeBlob(base64, mimeType = 'audio/mpeg') {
9
+ const binary = atob(base64)
10
+ const bytes = new Uint8Array(binary.length)
11
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i)
12
+ return new Blob([bytes], { type: mimeType })
13
+ }
@@ -1,4 +1,5 @@
1
1
  import { getReceivedCommandAudio, saveReceivedCommandAudio } from './localReceivedTTS.js'
2
+ import { BUILTIN_WAKE_RESPONSE_AUDIO, base64ToWakeBlob } from './builtinWakeTTS.js'
2
3
 
3
4
  const RECEIVED_COMMAND_TEXT = '收到指令,请您稍等。'
4
5
 
@@ -641,6 +642,34 @@ export class HewoyiTTSProvider {
641
642
  }
642
643
  }
643
644
 
645
+ getBuiltinWakeResponseBlob() {
646
+ const base64 = BUILTIN_WAKE_RESPONSE_AUDIO[String(this.voice || '').trim()]
647
+ if (!base64) return null
648
+ try {
649
+ return base64ToWakeBlob(base64, 'audio/mpeg')
650
+ } catch (error) {
651
+ this.error('内置“我在您请讲”语音解析失败:', error?.message || error)
652
+ return null
653
+ }
654
+ }
655
+
656
+ async speakWakeResponseAsync() {
657
+ // 唤醒词响应只走本地音频,不请求网络。
658
+ const blob = this.getBuiltinWakeResponseBlob()
659
+ if (!blob) return false
660
+ try {
661
+ this.stopCurrentSpeechOnly()
662
+ const played = await this.playWithAudioElement(blob, { waitForEnd: false, minStartHoldMs: 80 })
663
+ if (played) {
664
+ this.log('本地“我在您请讲”语音已开始播放,voice:', this.voice)
665
+ return true
666
+ }
667
+ } catch (error) {
668
+ this.error('本地“我在您请讲”播放失败:', error?.message || error)
669
+ }
670
+ return false
671
+ }
672
+
644
673
  async speakReceivedCommandAsync() {
645
674
  const value = RECEIVED_COMMAND_TEXT
646
675
 
@@ -18,6 +18,18 @@ export class SpeechService {
18
18
  return await this.ttsProvider?.unlock?.()
19
19
  }
20
20
 
21
+ async waitForRecognitionRelease(timeoutMs = 1200) {
22
+ const current = this.recognition
23
+ if (!current) return true
24
+ try { current.__xiaoyuanWaitingRelease = true } catch (_) {}
25
+ try { current.stop() } catch (_) {}
26
+ const started = Date.now()
27
+ while (this.recognition === current && Date.now() - started < timeoutMs) {
28
+ await new Promise((resolve) => window.setTimeout(resolve, 40))
29
+ }
30
+ return this.recognition !== current
31
+ }
32
+
21
33
  supported() {
22
34
  return typeof window !== 'undefined' && !!(
23
35
  window.SpeechRecognition ||
@@ -26,13 +38,33 @@ export class SpeechService {
26
38
  }
27
39
 
28
40
  async requestMicrophonePermission() {
29
- if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {
41
+ if (typeof navigator === 'undefined') throw new Error('当前环境无法访问麦克风')
42
+ // Web Speech 自己持有识别麦克风;不要在每次 start 前额外 getUserMedia -> stop,
43
+ // 否则 Chrome/Edge 容易在两个音频会话切换瞬间返回 aborted。
44
+ if (this._micPermissionGranted) return true
45
+ if (!navigator.mediaDevices?.getUserMedia) return true
46
+
47
+ try {
48
+ const stream = await navigator.mediaDevices.getUserMedia({
49
+ audio: true
50
+ })
51
+ this._micPermissionGranted = true
52
+ // 只用于首次授权,立即释放独立权限流;后续识别不再反复申请。
53
+ for (const track of stream.getTracks()) track.stop()
30
54
  return true
55
+ } catch (error) {
56
+ const name = error?.name || 'unknown'
57
+ if (name === 'NotAllowedError' || name === 'SecurityError') {
58
+ throw new Error('麦克风权限被拒绝,请在浏览器地址栏允许当前页面使用麦克风')
59
+ }
60
+ if (name === 'NotFoundError' || name === 'DevicesNotFoundError') {
61
+ throw new Error('没有检测到麦克风设备,请检查麦克风是否连接')
62
+ }
63
+ if (name === 'NotReadableError' || name === 'TrackStartError') {
64
+ throw new Error('麦克风正在被其他程序占用,请关闭其他录音/视频程序后重试')
65
+ }
66
+ throw error
31
67
  }
32
-
33
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
34
- for (const track of stream.getTracks()) track.stop()
35
- return true
36
68
  }
37
69
 
38
70
  createRecognition({ continuous = false } = {}) {
@@ -169,30 +201,60 @@ export class SpeechService {
169
201
  }
170
202
  }
171
203
 
172
- listenOnce() {
204
+ async listenOnce({ retries = 2 } = {}) {
205
+ if (!this.supported()) {
206
+ throw new Error('当前浏览器不支持语音识别,请使用 Chrome / Edge')
207
+ }
208
+
209
+ let lastError = null
210
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
211
+ try {
212
+ return await this._listenOnceInternal()
213
+ } catch (error) {
214
+ lastError = error
215
+ const msg = String(error?.message || '')
216
+ const retryable = /启动失败|audio-capture|麦克风|网络异常|network|aborted/i.test(msg)
217
+ if (!retryable || attempt >= retries) throw error
218
+ await new Promise((resolve) => window.setTimeout(resolve, 220 + attempt * 280))
219
+ }
220
+ }
221
+ throw lastError || new Error('语音识别失败')
222
+ }
223
+
224
+ _listenOnceInternal() {
173
225
  return new Promise(async (resolve, reject) => {
174
- if (!this.supported()) {
175
- reject(new Error('当前浏览器不支持语音识别'))
226
+ try {
227
+ await this.waitForRecognitionRelease()
228
+ await this.requestMicrophonePermission()
229
+ } catch (error) {
230
+ reject(error instanceof Error ? error : new Error(String(error)))
176
231
  return
177
232
  }
178
233
 
234
+ let recognition
179
235
  try {
180
- await this.requestMicrophonePermission()
236
+ recognition = this.createRecognition({ continuous: false })
181
237
  } catch (error) {
182
- const reason = error?.name || error?.message || error
183
- reject(new Error(`麦克风权限不可用:${reason}`))
238
+ reject(error)
184
239
  return
185
240
  }
186
241
 
187
- const recognition = this.createRecognition({ continuous: false })
188
242
  let finalText = ''
189
243
  let settled = false
244
+ let startTimer = null
245
+ let stopTimer = null
246
+
247
+ const cleanup = () => {
248
+ if (startTimer) window.clearTimeout(startTimer)
249
+ if (stopTimer) window.clearTimeout(stopTimer)
250
+ this.listening = false
251
+ this.recognition = null
252
+ }
190
253
 
191
254
  const finish = (error = null) => {
192
255
  if (settled) return
193
256
  settled = true
194
- this.listening = false
195
- this.recognition = null
257
+ cleanup()
196
258
  this.onEnd()
197
259
  if (error) reject(error)
198
260
  else resolve(finalText.trim())
@@ -200,6 +262,7 @@ export class SpeechService {
200
262
 
201
263
  recognition.onstart = () => {
202
264
  this.listening = true
265
+ if (startTimer) window.clearTimeout(startTimer)
203
266
  }
204
267
 
205
268
  recognition.onresult = (event) => {
@@ -215,17 +278,16 @@ export class SpeechService {
215
278
  }
216
279
  this.onInterim(interim)
217
280
 
218
- // 已得到 final 文本就尽快结束本轮,减少“说完以后还要等很久”的感觉。
219
281
  if (finalText.trim()) {
220
- window.setTimeout(() => {
282
+ if (stopTimer) window.clearTimeout(stopTimer)
283
+ stopTimer = window.setTimeout(() => {
221
284
  try { recognition.stop() } catch (_) {}
222
- }, 80)
285
+ }, 60)
223
286
  }
224
287
  }
225
288
 
226
289
  recognition.onerror = (event) => {
227
290
  const code = event?.error || 'unknown'
228
- // no-speech 更像“没有听到”,不是系统故障。
229
291
  if (code === 'no-speech') {
230
292
  finish(new Error('没有听到您的声音,请再说一次。'))
231
293
  return
@@ -235,28 +297,166 @@ export class SpeechService {
235
297
  return
236
298
  }
237
299
  if (code === 'audio-capture') {
238
- finish(new Error('没有检测到可用麦克风,请检查麦克风设备。'))
300
+ finish(new Error('没有检测到可用麦克风,或麦克风正在被其他程序占用。'))
239
301
  return
240
302
  }
241
303
  if (code === 'network') {
242
304
  finish(new Error('语音识别网络异常,请检查网络后再试。'))
243
305
  return
244
306
  }
307
+ if (code === 'aborted') {
308
+ // aborted 通常来自外部 stop/pause,不把它当成系统崩溃。
309
+ if (finalText.trim()) finish()
310
+ else finish(new Error('语音识别被中断,请再试一次。'))
311
+ return
312
+ }
313
+ finish(new Error(`语音识别失败:${code}`))
314
+ }
315
+
316
+ recognition.onend = () => {
317
+ if (finalText.trim()) finish()
318
+ else if (!settled) finish(new Error('语音识别结束但没有识别到内容,请再试一次。'))
319
+ }
320
+
321
+ try {
322
+ recognition.start()
323
+ this.recognition = recognition
324
+ this.listening = true
325
+ // 防止某些 Chrome 版本 start() 成功但迟迟没有 onstart,释放状态并让上层重试。
326
+ startTimer = window.setTimeout(() => {
327
+ if (!settled && !this.listening) {
328
+ try { recognition.stop() } catch (_) {}
329
+ finish(new Error('语音识别启动失败,请再试一次。'))
330
+ }
331
+ }, 1800)
332
+ } catch (error) {
333
+ finish(error instanceof Error ? error : new Error('语音识别启动失败'))
334
+ }
335
+ })
336
+ }
337
+
338
+ listenAfterWake({ silenceMs = 3000, retries = 1, onInterim = this.onInterim } = {}) {
339
+ if (!this.supported()) return Promise.reject(new Error('当前浏览器不支持语音识别,请使用 Chrome / Edge'))
340
+
341
+ const run = () => new Promise(async (resolve, reject) => {
342
+ try {
343
+ await this.requestMicrophonePermission()
344
+ } catch (error) {
345
+ reject(error instanceof Error ? error : new Error(String(error)))
346
+ return
347
+ }
348
+
349
+ let recognition
350
+ try {
351
+ recognition = this.createRecognition({ continuous: true })
352
+ } catch (error) {
353
+ reject(error)
354
+ return
355
+ }
356
+
357
+ let finalText = ''
358
+ let settled = false
359
+ let silenceTimer = null
360
+ let startTimer = null
361
+
362
+ const cleanup = () => {
363
+ if (silenceTimer) window.clearTimeout(silenceTimer)
364
+ if (startTimer) window.clearTimeout(startTimer)
365
+ this.listening = false
366
+ this.recognition = null
367
+ }
368
+
369
+ const finish = (error = null) => {
370
+ if (settled) return
371
+ settled = true
372
+ cleanup()
373
+ try { recognition.stop() } catch (_) {}
374
+ this.onEnd()
375
+ if (error) reject(error)
376
+ else resolve(finalText.trim())
377
+ }
378
+
379
+ const armSilenceTimer = () => {
380
+ if (silenceTimer) window.clearTimeout(silenceTimer)
381
+ silenceTimer = window.setTimeout(() => finish(), Math.max(1200, Number(silenceMs) || 3000))
382
+ }
383
+
384
+ recognition.onstart = () => {
385
+ this.listening = true
386
+ if (startTimer) window.clearTimeout(startTimer)
387
+ armSilenceTimer()
388
+ }
389
+
390
+ recognition.onresult = (event) => {
391
+ let interim = ''
392
+ let gotSpeech = false
393
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
394
+ const part = event.results[i][0]?.transcript || ''
395
+ if (part) gotSpeech = true
396
+ if (event.results[i].isFinal) {
397
+ finalText += part
398
+ this.onFinal(part)
399
+ } else {
400
+ interim += part
401
+ }
402
+ }
403
+ onInterim?.(interim)
404
+ this.onInterim(interim)
405
+ if (gotSpeech) armSilenceTimer()
406
+ }
407
+
408
+ recognition.onerror = (event) => {
409
+ const code = event?.error || 'unknown'
410
+ if (code === 'no-speech') {
411
+ // 3 秒无输入视为用户说完,不当成崩溃。
412
+ finish()
413
+ return
414
+ }
415
+ if (code === 'aborted' && finalText.trim()) {
416
+ finish()
417
+ return
418
+ }
419
+ if (code === 'not-allowed' || code === 'service-not-allowed') {
420
+ finish(new Error('麦克风权限被拒绝,请允许当前页面使用麦克风后再试。'))
421
+ return
422
+ }
423
+ if (code === 'audio-capture') {
424
+ finish(new Error('没有检测到可用麦克风,或麦克风正在被其他程序占用。'))
425
+ return
426
+ }
427
+ if (code === 'network') {
428
+ finish(new Error('语音识别网络异常,请稍后再试。'))
429
+ return
430
+ }
245
431
  finish(new Error(`语音识别失败:${code}`))
246
432
  }
247
433
 
248
434
  recognition.onend = () => {
249
- finish()
435
+ if (settled) return
436
+ if (finalText.trim()) finish()
437
+ else finish(new Error('语音识别结束但没有识别到内容,请再说一次。'))
250
438
  }
251
439
 
252
440
  try {
253
441
  recognition.start()
254
442
  this.recognition = recognition
255
443
  this.listening = true
444
+ startTimer = window.setTimeout(() => {
445
+ if (!settled && !this.listening) finish(new Error('语音识别启动失败,请再试一次。'))
446
+ }, 1800)
256
447
  } catch (error) {
257
448
  finish(error instanceof Error ? error : new Error('语音识别启动失败'))
258
449
  }
259
450
  })
451
+
452
+ return run().catch(async (error) => {
453
+ const retryable = /启动失败|audio-capture|网络异常|network|aborted/i.test(String(error?.message || ''))
454
+ if (retryable && retries > 0) {
455
+ await new Promise((resolve) => window.setTimeout(resolve, 220))
456
+ return this.listenAfterWake({ silenceMs, retries: retries - 1, onInterim })
457
+ }
458
+ throw error
459
+ })
260
460
  }
261
461
 
262
462
  startWakeWordListener({ wakeWord = '你好小园', onWake, onError }) {
@@ -311,14 +511,22 @@ export class SpeechService {
311
511
  try { recognition.stop() } catch (_) {}
312
512
  buffer = ''
313
513
  },
314
- pause() {
514
+ async pause() {
315
515
  paused = true
316
516
  try { recognition.stop() } catch (_) {}
517
+ const started = Date.now()
518
+ while (!stopped && this.recognition === recognition && Date.now() - started < 1200) {
519
+ await new Promise((resolve) => window.setTimeout(resolve, 40))
520
+ }
521
+ return this.recognition !== recognition
317
522
  },
318
523
  resume() {
319
524
  if (stopped) return
320
525
  paused = false
321
- try { recognition.start() } catch (_) {}
526
+ window.setTimeout(() => {
527
+ if (stopped || paused) return
528
+ try { recognition.start() } catch (_) { restart() }
529
+ }, 180)
322
530
  },
323
531
  recognition
324
532
  }