xiaoyuan-assistant 0.5.51 → 0.5.53

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,15 @@ 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
+ - 手动打开小园仍保持输入框模式,不自动抢占麦克风。
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,16 @@ 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,再解析并按现有步骤流程执行。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xiaoyuan-assistant",
3
- "version": "0.5.51",
3
+ "version": "0.5.53",
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.53'
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
+ wakeController?.pause?.()
227
+ wakeListening.value = false
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?.()
@@ -304,6 +351,8 @@ async function listenForCommand({ auto = false } = {}) {
304
351
  // 手动或唤醒后的单次聆听期间,暂停持续唤醒监听,避免两个 Recognition 实例抢麦克风。
305
352
  wakeController?.pause?.()
306
353
  wakeListening.value = false
354
+ // 等待上一个 recognition.onend 真正释放麦克风,避免 Chrome/Edge 报 audio-capture/not-allowed。
355
+ await new Promise((resolve) => window.setTimeout(resolve, manual ? 120 : 80))
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 the configured TTS voice.
2
+ // Embedded directly into the package to avoid NPM/Vite 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
 
@@ -26,13 +26,38 @@ export class SpeechService {
26
26
  }
27
27
 
28
28
  async requestMicrophonePermission() {
29
- if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {
29
+ if (typeof navigator === 'undefined') {
30
+ throw new Error('当前环境无法访问麦克风')
31
+ }
32
+ if (!navigator.mediaDevices?.getUserMedia) {
33
+ // Web Speech 在部分浏览器可用,即使 MediaDevices API 不可用,也继续让 Recognition 自己处理。
30
34
  return true
31
35
  }
32
36
 
33
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
34
- for (const track of stream.getTracks()) track.stop()
35
- return true
37
+ try {
38
+ const stream = await navigator.mediaDevices.getUserMedia({
39
+ audio: {
40
+ echoCancellation: true,
41
+ noiseSuppression: true,
42
+ autoGainControl: true,
43
+ channelCount: 1
44
+ }
45
+ })
46
+ for (const track of stream.getTracks()) track.stop()
47
+ return true
48
+ } catch (error) {
49
+ const name = error?.name || 'unknown'
50
+ if (name === 'NotAllowedError' || name === 'SecurityError') {
51
+ throw new Error('麦克风权限被拒绝,请在浏览器地址栏允许当前页面使用麦克风')
52
+ }
53
+ if (name === 'NotFoundError' || name === 'DevicesNotFoundError') {
54
+ throw new Error('没有检测到麦克风设备,请检查麦克风是否连接')
55
+ }
56
+ if (name === 'NotReadableError' || name === 'TrackStartError') {
57
+ throw new Error('麦克风正在被其他程序占用,请关闭其他录音/视频程序后重试')
58
+ }
59
+ throw error
60
+ }
36
61
  }
37
62
 
38
63
  createRecognition({ continuous = false } = {}) {
@@ -169,30 +194,59 @@ export class SpeechService {
169
194
  }
170
195
  }
171
196
 
172
- listenOnce() {
197
+ async listenOnce({ retries = 2 } = {}) {
198
+ if (!this.supported()) {
199
+ throw new Error('当前浏览器不支持语音识别,请使用 Chrome / Edge')
200
+ }
201
+
202
+ let lastError = null
203
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
204
+ try {
205
+ return await this._listenOnceInternal()
206
+ } catch (error) {
207
+ lastError = error
208
+ const msg = String(error?.message || '')
209
+ const retryable = /启动失败|audio-capture|麦克风|网络异常|network|aborted/i.test(msg)
210
+ if (!retryable || attempt >= retries) throw error
211
+ await new Promise((resolve) => window.setTimeout(resolve, 220 + attempt * 280))
212
+ }
213
+ }
214
+ throw lastError || new Error('语音识别失败')
215
+ }
216
+
217
+ _listenOnceInternal() {
173
218
  return new Promise(async (resolve, reject) => {
174
- if (!this.supported()) {
175
- reject(new Error('当前浏览器不支持语音识别'))
219
+ try {
220
+ await this.requestMicrophonePermission()
221
+ } catch (error) {
222
+ reject(error instanceof Error ? error : new Error(String(error)))
176
223
  return
177
224
  }
178
225
 
226
+ let recognition
179
227
  try {
180
- await this.requestMicrophonePermission()
228
+ recognition = this.createRecognition({ continuous: false })
181
229
  } catch (error) {
182
- const reason = error?.name || error?.message || error
183
- reject(new Error(`麦克风权限不可用:${reason}`))
230
+ reject(error)
184
231
  return
185
232
  }
186
233
 
187
- const recognition = this.createRecognition({ continuous: false })
188
234
  let finalText = ''
189
235
  let settled = false
236
+ let startTimer = null
237
+ let stopTimer = null
238
+
239
+ const cleanup = () => {
240
+ if (startTimer) window.clearTimeout(startTimer)
241
+ if (stopTimer) window.clearTimeout(stopTimer)
242
+ this.listening = false
243
+ this.recognition = null
244
+ }
190
245
 
191
246
  const finish = (error = null) => {
192
247
  if (settled) return
193
248
  settled = true
194
- this.listening = false
195
- this.recognition = null
249
+ cleanup()
196
250
  this.onEnd()
197
251
  if (error) reject(error)
198
252
  else resolve(finalText.trim())
@@ -200,6 +254,7 @@ export class SpeechService {
200
254
 
201
255
  recognition.onstart = () => {
202
256
  this.listening = true
257
+ if (startTimer) window.clearTimeout(startTimer)
203
258
  }
204
259
 
205
260
  recognition.onresult = (event) => {
@@ -215,17 +270,16 @@ export class SpeechService {
215
270
  }
216
271
  this.onInterim(interim)
217
272
 
218
- // 已得到 final 文本就尽快结束本轮,减少“说完以后还要等很久”的感觉。
219
273
  if (finalText.trim()) {
220
- window.setTimeout(() => {
274
+ if (stopTimer) window.clearTimeout(stopTimer)
275
+ stopTimer = window.setTimeout(() => {
221
276
  try { recognition.stop() } catch (_) {}
222
- }, 80)
277
+ }, 60)
223
278
  }
224
279
  }
225
280
 
226
281
  recognition.onerror = (event) => {
227
282
  const code = event?.error || 'unknown'
228
- // no-speech 更像“没有听到”,不是系统故障。
229
283
  if (code === 'no-speech') {
230
284
  finish(new Error('没有听到您的声音,请再说一次。'))
231
285
  return
@@ -235,30 +289,168 @@ export class SpeechService {
235
289
  return
236
290
  }
237
291
  if (code === 'audio-capture') {
238
- finish(new Error('没有检测到可用麦克风,请检查麦克风设备。'))
292
+ finish(new Error('没有检测到可用麦克风,或麦克风正在被其他程序占用。'))
239
293
  return
240
294
  }
241
295
  if (code === 'network') {
242
296
  finish(new Error('语音识别网络异常,请检查网络后再试。'))
243
297
  return
244
298
  }
299
+ if (code === 'aborted') {
300
+ // aborted 通常来自外部 stop/pause,不把它当成系统崩溃。
301
+ if (finalText.trim()) finish()
302
+ else finish(new Error('语音识别被中断,请再试一次。'))
303
+ return
304
+ }
245
305
  finish(new Error(`语音识别失败:${code}`))
246
306
  }
247
307
 
248
308
  recognition.onend = () => {
249
- finish()
309
+ if (finalText.trim()) finish()
310
+ else if (!settled) finish(new Error('语音识别结束但没有识别到内容,请再试一次。'))
250
311
  }
251
312
 
252
313
  try {
253
314
  recognition.start()
254
315
  this.recognition = recognition
255
316
  this.listening = true
317
+ // 防止某些 Chrome 版本 start() 成功但迟迟没有 onstart,释放状态并让上层重试。
318
+ startTimer = window.setTimeout(() => {
319
+ if (!settled && !this.listening) {
320
+ try { recognition.stop() } catch (_) {}
321
+ finish(new Error('语音识别启动失败,请再试一次。'))
322
+ }
323
+ }, 1800)
256
324
  } catch (error) {
257
325
  finish(error instanceof Error ? error : new Error('语音识别启动失败'))
258
326
  }
259
327
  })
260
328
  }
261
329
 
330
+ listenAfterWake({ silenceMs = 3000, retries = 1, onInterim = this.onInterim } = {}) {
331
+ if (!this.supported()) return Promise.reject(new Error('当前浏览器不支持语音识别,请使用 Chrome / Edge'))
332
+
333
+ const run = () => new Promise(async (resolve, reject) => {
334
+ try {
335
+ await this.requestMicrophonePermission()
336
+ } catch (error) {
337
+ reject(error instanceof Error ? error : new Error(String(error)))
338
+ return
339
+ }
340
+
341
+ let recognition
342
+ try {
343
+ recognition = this.createRecognition({ continuous: true })
344
+ } catch (error) {
345
+ reject(error)
346
+ return
347
+ }
348
+
349
+ let finalText = ''
350
+ let settled = false
351
+ let silenceTimer = null
352
+ let startTimer = null
353
+
354
+ const cleanup = () => {
355
+ if (silenceTimer) window.clearTimeout(silenceTimer)
356
+ if (startTimer) window.clearTimeout(startTimer)
357
+ this.listening = false
358
+ this.recognition = null
359
+ }
360
+
361
+ const finish = (error = null) => {
362
+ if (settled) return
363
+ settled = true
364
+ cleanup()
365
+ try { recognition.stop() } catch (_) {}
366
+ this.onEnd()
367
+ if (error) reject(error)
368
+ else resolve(finalText.trim())
369
+ }
370
+
371
+ const armSilenceTimer = () => {
372
+ if (silenceTimer) window.clearTimeout(silenceTimer)
373
+ silenceTimer = window.setTimeout(() => finish(), Math.max(1200, Number(silenceMs) || 3000))
374
+ }
375
+
376
+ recognition.onstart = () => {
377
+ this.listening = true
378
+ if (startTimer) window.clearTimeout(startTimer)
379
+ armSilenceTimer()
380
+ }
381
+
382
+ recognition.onresult = (event) => {
383
+ let interim = ''
384
+ let gotSpeech = false
385
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
386
+ const part = event.results[i][0]?.transcript || ''
387
+ if (part) gotSpeech = true
388
+ if (event.results[i].isFinal) {
389
+ finalText += part
390
+ this.onFinal(part)
391
+ } else {
392
+ interim += part
393
+ }
394
+ }
395
+ onInterim?.(interim)
396
+ this.onInterim(interim)
397
+ if (gotSpeech) armSilenceTimer()
398
+ }
399
+
400
+ recognition.onerror = (event) => {
401
+ const code = event?.error || 'unknown'
402
+ if (code === 'no-speech') {
403
+ // 3 秒无输入视为用户说完,不当成崩溃。
404
+ finish()
405
+ return
406
+ }
407
+ if (code === 'aborted' && finalText.trim()) {
408
+ finish()
409
+ return
410
+ }
411
+ if (code === 'not-allowed' || code === 'service-not-allowed') {
412
+ finish(new Error('麦克风权限被拒绝,请允许当前页面使用麦克风后再试。'))
413
+ return
414
+ }
415
+ if (code === 'audio-capture') {
416
+ finish(new Error('没有检测到可用麦克风,或麦克风正在被其他程序占用。'))
417
+ return
418
+ }
419
+ if (code === 'network') {
420
+ finish(new Error('语音识别网络异常,请稍后再试。'))
421
+ return
422
+ }
423
+ finish(new Error(`语音识别失败:${code}`))
424
+ }
425
+
426
+ recognition.onend = () => {
427
+ if (settled) return
428
+ if (finalText.trim()) finish()
429
+ else finish(new Error('语音识别结束但没有识别到内容,请再说一次。'))
430
+ }
431
+
432
+ try {
433
+ recognition.start()
434
+ this.recognition = recognition
435
+ this.listening = true
436
+ startTimer = window.setTimeout(() => {
437
+ if (!settled && !this.listening) finish(new Error('语音识别启动失败,请再试一次。'))
438
+ }, 1800)
439
+ } catch (error) {
440
+ finish(error instanceof Error ? error : new Error('语音识别启动失败'))
441
+ }
442
+ })
443
+
444
+ return run().catch(async (error) => {
445
+ const retryable = /启动失败|audio-capture|网络异常|network|aborted/i.test(String(error?.message || ''))
446
+ if (retryable && retries > 0) {
447
+ await new Promise((resolve) => window.setTimeout(resolve, 220))
448
+ return this.listenAfterWake({ silenceMs, retries: retries - 1, onInterim })
449
+ }
450
+ throw error
451
+ })
452
+ }
453
+
262
454
  startWakeWordListener({ wakeWord = '你好小园', onWake, onError }) {
263
455
  if (!this.supported()) return { stop: () => {}, pause: () => {}, resume: () => {} }
264
456