chenbhao commited on
Commit
d12b0d9
·
1 Parent(s): bc9c65c

feat: friend text tts ok but websearch tts not ok

Browse files
bun.lock CHANGED
@@ -77,6 +77,7 @@
77
  "lodash-es": "^4.17.23",
78
  "lru-cache": "^11.2.7",
79
  "marked": "^17.0.5",
 
80
  "p-map": "^7.0.4",
81
  "picomatch": "^4.0.4",
82
  "plist": "^3.1.0",
@@ -875,6 +876,8 @@
875
 
876
  "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
877
 
 
 
878
  "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
879
 
880
  "node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="],
@@ -1381,6 +1384,10 @@
1381
 
1382
  "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
1383
 
 
 
 
 
1384
  "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1385
 
1386
  "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
@@ -1475,6 +1482,14 @@
1475
 
1476
  "ink/wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
1477
 
 
 
 
 
 
 
 
 
1478
  "qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
1479
 
1480
  "qrcode/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -1525,6 +1540,16 @@
1525
 
1526
  "gtoken/gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1527
 
 
 
 
 
 
 
 
 
 
 
1528
  "qrcode/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1529
 
1530
  "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
@@ -1545,6 +1570,12 @@
1545
 
1546
  "@grpc/proto-loader/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1547
 
 
 
 
 
 
 
1548
  "qrcode/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1549
 
1550
  "qrcode/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
 
77
  "lodash-es": "^4.17.23",
78
  "lru-cache": "^11.2.7",
79
  "marked": "^17.0.5",
80
+ "node-edge-tts": "^1.2.10",
81
  "p-map": "^7.0.4",
82
  "picomatch": "^4.0.4",
83
  "plist": "^3.1.0",
 
876
 
877
  "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
878
 
879
+ "node-edge-tts": ["node-edge-tts@1.2.10", "", { "dependencies": { "https-proxy-agent": "^7.0.1", "ws": "^8.13.0", "yargs": "^17.7.2" }, "bin": { "node-edge-tts": "bin.js" } }, "sha512-bV2i4XU54D45+US0Zm1HcJRkifuB3W438dWyuJEHLQdKxnuqlI1kim2MOvR6Q3XUQZvfF9PoDyR1Rt7aeXhPdQ=="],
880
+
881
  "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
882
 
883
  "node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="],
 
1384
 
1385
  "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
1386
 
1387
+ "node-edge-tts/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
1388
+
1389
+ "node-edge-tts/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
1390
+
1391
  "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1392
 
1393
  "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
 
1482
 
1483
  "ink/wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
1484
 
1485
+ "node-edge-tts/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1486
+
1487
+ "node-edge-tts/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
1488
+
1489
+ "node-edge-tts/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
1490
+
1491
+ "node-edge-tts/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
1492
+
1493
  "qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
1494
 
1495
  "qrcode/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
 
1540
 
1541
  "gtoken/gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1542
 
1543
+ "node-edge-tts/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1544
+
1545
+ "node-edge-tts/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
1546
+
1547
+ "node-edge-tts/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
1548
+
1549
+ "node-edge-tts/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
1550
+
1551
+ "node-edge-tts/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1552
+
1553
  "qrcode/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1554
 
1555
  "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
 
1570
 
1571
  "@grpc/proto-loader/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1572
 
1573
+ "node-edge-tts/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1574
+
1575
+ "node-edge-tts/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
1576
+
1577
+ "node-edge-tts/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1578
+
1579
  "qrcode/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1580
 
1581
  "qrcode/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
package.json CHANGED
@@ -95,6 +95,7 @@
95
  "lodash-es": "^4.17.23",
96
  "lru-cache": "^11.2.7",
97
  "marked": "^17.0.5",
 
98
  "p-map": "^7.0.4",
99
  "picomatch": "^4.0.4",
100
  "plist": "^3.1.0",
 
95
  "lodash-es": "^4.17.23",
96
  "lru-cache": "^11.2.7",
97
  "marked": "^17.0.5",
98
+ "node-edge-tts": "^1.2.10",
99
  "p-map": "^7.0.4",
100
  "picomatch": "^4.0.4",
101
  "plist": "^3.1.0",
src/components/friend/frontend/components/ChatInput.tsx CHANGED
@@ -196,12 +196,20 @@ export function ChatInput({ visible = true, onActiveChange, uiAlign = 'right', o
196
  }, [handleVoiceCallResult, sttProvider, serverStt, language])
197
 
198
  // --- Voice Call: end ---
199
- const endVoiceCall = useCallback(() => {
 
200
  voiceCallActiveRef.current = false
201
  setVoiceCallActive(false)
202
- serverStt.stopStreaming()
203
  setRecording(false)
204
 
 
 
 
 
 
 
 
 
205
  if (silenceTimerRef.current) { clearTimeout(silenceTimerRef.current); silenceTimerRef.current = null }
206
  cancelInterrupt()
207
  mutedUntilRef.current = 0
@@ -209,7 +217,7 @@ export function ChatInput({ visible = true, onActiveChange, uiAlign = 'right', o
209
  setText('')
210
  setOpen(false)
211
  onActiveChange?.(false)
212
- }, [onActiveChange, cancelInterrupt, serverStt])
213
 
214
  endVoiceCallRef.current = endVoiceCall
215
 
 
196
  }, [handleVoiceCallResult, sttProvider, serverStt, language])
197
 
198
  // --- Voice Call: end ---
199
+ const endVoiceCall = useCallback(async () => {
200
+ if (!voiceCallActiveRef.current) return // guard against double-invocation
201
  voiceCallActiveRef.current = false
202
  setVoiceCallActive(false)
 
203
  setRecording(false)
204
 
205
+ // Stop capture and get the final transcript
206
+ const text = await serverStt.stopStreaming()
207
+
208
+ // Send any remaining transcript as a message (no-op if empty)
209
+ if (text.trim()) {
210
+ voiceCallSend(text)
211
+ }
212
+
213
  if (silenceTimerRef.current) { clearTimeout(silenceTimerRef.current); silenceTimerRef.current = null }
214
  cancelInterrupt()
215
  mutedUntilRef.current = 0
 
217
  setText('')
218
  setOpen(false)
219
  onActiveChange?.(false)
220
+ }, [onActiveChange, cancelInterrupt, serverStt, voiceCallSend])
221
 
222
  endVoiceCallRef.current = endVoiceCall
223
 
src/components/friend/frontend/hooks/useServerStt.ts CHANGED
@@ -95,8 +95,11 @@ export function useServerStt() {
95
  [],
96
  )
97
 
98
- /** Stop streaming STT. */
99
- const stopStreaming = useCallback(() => {
 
 
 
100
  // Stop polling
101
  const pollId = (window as any).__friendSttPollId
102
  if (pollId) {
@@ -104,12 +107,12 @@ export function useServerStt() {
104
  delete (window as any).__friendSttPollId
105
  }
106
 
107
- // Stop capture
108
- fetch(`${FRIEND_API_BASE}/voice/stop`, { method: 'POST' })
109
- .catch(() => {})
110
- .finally(() => {
111
- setConnected(false)
112
- })
113
  }, [])
114
 
115
  return {
 
95
  [],
96
  )
97
 
98
+ /**
99
+ * Stop streaming STT.
100
+ * Returns the final transcript text from the server.
101
+ */
102
+ const stopStreaming = useCallback(async (): Promise<string> => {
103
  // Stop polling
104
  const pollId = (window as any).__friendSttPollId
105
  if (pollId) {
 
107
  delete (window as any).__friendSttPollId
108
  }
109
 
110
+ // Stop capture and get final transcript
111
+ const res = await fetch(`${FRIEND_API_BASE}/voice/stop`, { method: 'POST' })
112
+ setConnected(false)
113
+ if (!res.ok) return ''
114
+ const data = await res.json()
115
+ return data.text || ''
116
  }, [])
117
 
118
  return {
src/friend/FriendService.ts CHANGED
@@ -369,9 +369,10 @@ class FriendService {
369
  // Generate TTS for the full response if enabled
370
  if (prefs.ttsEnabled) {
371
  try {
372
- const audioUrl = await this.generateTts(text);
373
- if (audioUrl) {
374
- broadcastToVrm({ audioUrl, sendFirstTts: true });
 
375
  }
376
  } catch (err) {
377
  console.warn('[FriendService] TTS generation failed:', err);
@@ -460,7 +461,7 @@ class FriendService {
460
  try {
461
  const args = tool === 'parecord'
462
  ? ['--raw', '--rate=16000', '--format=s16le', '--channels=1', '--latency-msec=20']
463
- : ['-r', '16000', '-f', 'S16_LE', '-c', '1', '-t', 'raw', '-q'];
464
  const proc = spawn(tool, args, { stdio: ['pipe', 'pipe', 'pipe'] });
465
  if (proc.pid !== undefined) {
466
  captureProc = proc;
 
369
  // Generate TTS for the full response if enabled
370
  if (prefs.ttsEnabled) {
371
  try {
372
+ const audioId = await this.generateTts(text);
373
+ if (audioId) {
374
+ const fullUrl = `http://127.0.0.1:3456/plugins/friend/audio/${audioId}`;
375
+ broadcastToVrm({ audioUrl: fullUrl, sendFirstTts: true });
376
  }
377
  } catch (err) {
378
  console.warn('[FriendService] TTS generation failed:', err);
 
461
  try {
462
  const args = tool === 'parecord'
463
  ? ['--raw', '--rate=16000', '--format=s16le', '--channels=1', '--latency-msec=20']
464
+ : ['-D', 'default', '-r', '16000', '-f', 'S16_LE', '-c', '1', '-t', 'raw', '-q'];
465
  const proc = spawn(tool, args, { stdio: ['pipe', 'pipe', 'pipe'] });
466
  if (proc.pid !== undefined) {
467
  captureProc = proc;