@adminforth/agent 1.40.4 → 1.41.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.
package/build.log CHANGED
@@ -58,5 +58,5 @@ custom/speech_recognition_frontend/voiceActivityDetection.ts
58
58
  custom/speech_recognition_frontend/types/
59
59
  custom/speech_recognition_frontend/types/voice-activity-detection.d.ts
60
60
 
61
- sent 1,657,857 bytes received 860 bytes 3,317,434.00 bytes/sec
62
- total size is 1,653,941 speedup is 1.00
61
+ sent 1,660,143 bytes received 856 bytes 3,321,998.00 bytes/sec
62
+ total size is 1,656,231 speedup is 1.00
@@ -67,7 +67,7 @@ export const useAgentAudio = defineStore('agentAudio', () => {
67
67
  async function sendAudioToServerAndHandleResponse(blob: Blob) {
68
68
  currentAbortController = new AbortController();
69
69
  const formData = new FormData();
70
- formData.append('file', blob, 'user_prompt.webm');
70
+ formData.append('file', blob, 'user_prompt.wav');
71
71
  formData.append('sessionId', agentStore.activeSessionId);
72
72
  formData.append('mode', agentStore.activeModeName ?? '');
73
73
  formData.append('timeZone', Intl.DateTimeFormat().resolvedOptions().timeZone);
@@ -14,6 +14,7 @@
14
14
  "@incremark/core": "^1.0.2",
15
15
  "@incremark/theme": "^1.0.2",
16
16
  "@incremark/vue": "^1.0.2",
17
+ "@ricky0123/vad-web": "^0.0.30",
17
18
  "@shikijs/langs": "^4.0.2",
18
19
  "@shikijs/themes": "^4.0.2",
19
20
  "@vueuse/core": "^14.2.1",
@@ -17,6 +17,9 @@ importers:
17
17
  '@incremark/vue':
18
18
  specifier: ^1.0.2
19
19
  version: 1.0.2(katex@0.16.45)(vue@3.5.32)
20
+ '@ricky0123/vad-web':
21
+ specifier: ^0.0.30
22
+ version: 0.0.30
20
23
  '@shikijs/langs':
21
24
  specifier: ^4.0.2
22
25
  version: 4.0.2
@@ -139,6 +142,39 @@ packages:
139
142
  resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
140
143
  engines: {node: '>=8.0.0'}
141
144
 
145
+ '@protobufjs/aspromise@1.1.2':
146
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
147
+
148
+ '@protobufjs/base64@1.1.2':
149
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
150
+
151
+ '@protobufjs/codegen@2.0.5':
152
+ resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==}
153
+
154
+ '@protobufjs/eventemitter@1.1.0':
155
+ resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
156
+
157
+ '@protobufjs/fetch@1.1.0':
158
+ resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
159
+
160
+ '@protobufjs/float@1.0.2':
161
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
162
+
163
+ '@protobufjs/inquire@1.1.1':
164
+ resolution: {integrity: sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==}
165
+
166
+ '@protobufjs/path@1.1.2':
167
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
168
+
169
+ '@protobufjs/pool@1.1.0':
170
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
171
+
172
+ '@protobufjs/utf8@1.1.1':
173
+ resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==}
174
+
175
+ '@ricky0123/vad-web@0.0.30':
176
+ resolution: {integrity: sha512-cJyYrh4YeeUBJcbR9Bic/bFDyB9qBkAepvpuWM3vLxnAi7bC3VHzf51UeNdT+OtY4D7MLAgV8iJMc4z41ZnaWg==}
177
+
142
178
  '@shikijs/core@3.23.0':
143
179
  resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==}
144
180
 
@@ -218,6 +254,9 @@ packages:
218
254
  '@types/ms@2.1.0':
219
255
  resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
220
256
 
257
+ '@types/node@25.6.0':
258
+ resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
259
+
221
260
  '@types/trusted-types@2.0.7':
222
261
  resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
223
262
 
@@ -472,6 +511,9 @@ packages:
472
511
  fast-json-patch@3.1.1:
473
512
  resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==}
474
513
 
514
+ flatbuffers@25.9.23:
515
+ resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==}
516
+
475
517
  get-caller-file@2.0.5:
476
518
  resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
477
519
  engines: {node: 6.* || 8.* || >= 10.*}
@@ -480,6 +522,9 @@ packages:
480
522
  resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
481
523
  engines: {node: '>=18'}
482
524
 
525
+ guid-typescript@1.0.9:
526
+ resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==}
527
+
483
528
  hast-util-to-html@9.0.5:
484
529
  resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
485
530
 
@@ -525,6 +570,9 @@ packages:
525
570
  lodash-es@4.18.1:
526
571
  resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
527
572
 
573
+ long@5.3.2:
574
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
575
+
528
576
  longest-streak@3.1.0:
529
577
  resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
530
578
 
@@ -690,12 +738,21 @@ packages:
690
738
  oniguruma-to-es@4.3.5:
691
739
  resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==}
692
740
 
741
+ onnxruntime-common@1.25.1:
742
+ resolution: {integrity: sha512-kKvYQFdos4LWJqhZ+nmKu3NT8NXzw8I5x9fNUKe1rNKcPfNKnYXUtW7JBpcKFsvLtrJashRgVYSbFap4cHxvNg==}
743
+
744
+ onnxruntime-web@1.25.1:
745
+ resolution: {integrity: sha512-mgs61sJ9m3hLa5jGRr9Pen3kkG00vlxmrcRL6FufYpSWBZKaklo0sotQCq2fLjgDVLnW57jrDcLqzYJNKeZskQ==}
746
+
693
747
  parse-entities@4.0.2:
694
748
  resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
695
749
 
696
750
  picocolors@1.1.1:
697
751
  resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
698
752
 
753
+ platform@1.3.6:
754
+ resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
755
+
699
756
  postcss@8.5.10:
700
757
  resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
701
758
  engines: {node: ^10 || ^12 || >=14}
@@ -703,6 +760,10 @@ packages:
703
760
  property-information@7.1.0:
704
761
  resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
705
762
 
763
+ protobufjs@7.5.6:
764
+ resolution: {integrity: sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==}
765
+ engines: {node: '>=12.0.0'}
766
+
706
767
  regex-recursion@6.0.2:
707
768
  resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
708
769
 
@@ -775,6 +836,9 @@ packages:
775
836
  tslib@2.8.1:
776
837
  resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
777
838
 
839
+ undici-types@7.19.2:
840
+ resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
841
+
778
842
  unist-util-is@6.0.1:
779
843
  resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
780
844
 
@@ -1063,6 +1127,33 @@ snapshots:
1063
1127
 
1064
1128
  '@opentelemetry/api@1.9.0': {}
1065
1129
 
1130
+ '@protobufjs/aspromise@1.1.2': {}
1131
+
1132
+ '@protobufjs/base64@1.1.2': {}
1133
+
1134
+ '@protobufjs/codegen@2.0.5': {}
1135
+
1136
+ '@protobufjs/eventemitter@1.1.0': {}
1137
+
1138
+ '@protobufjs/fetch@1.1.0':
1139
+ dependencies:
1140
+ '@protobufjs/aspromise': 1.1.2
1141
+ '@protobufjs/inquire': 1.1.1
1142
+
1143
+ '@protobufjs/float@1.0.2': {}
1144
+
1145
+ '@protobufjs/inquire@1.1.1': {}
1146
+
1147
+ '@protobufjs/path@1.1.2': {}
1148
+
1149
+ '@protobufjs/pool@1.1.0': {}
1150
+
1151
+ '@protobufjs/utf8@1.1.1': {}
1152
+
1153
+ '@ricky0123/vad-web@0.0.30':
1154
+ dependencies:
1155
+ onnxruntime-web: 1.25.1
1156
+
1066
1157
  '@shikijs/core@3.23.0':
1067
1158
  dependencies:
1068
1159
  '@shikijs/types': 3.23.0
@@ -1162,6 +1253,10 @@ snapshots:
1162
1253
 
1163
1254
  '@types/ms@2.1.0': {}
1164
1255
 
1256
+ '@types/node@25.6.0':
1257
+ dependencies:
1258
+ undici-types: 7.19.2
1259
+
1165
1260
  '@types/trusted-types@2.0.7':
1166
1261
  optional: true
1167
1262
 
@@ -1404,10 +1499,14 @@ snapshots:
1404
1499
 
1405
1500
  fast-json-patch@3.1.1: {}
1406
1501
 
1502
+ flatbuffers@25.9.23: {}
1503
+
1407
1504
  get-caller-file@2.0.5: {}
1408
1505
 
1409
1506
  get-east-asian-width@1.5.0: {}
1410
1507
 
1508
+ guid-typescript@1.0.9: {}
1509
+
1411
1510
  hast-util-to-html@9.0.5:
1412
1511
  dependencies:
1413
1512
  '@types/hast': 3.0.4
@@ -1457,6 +1556,8 @@ snapshots:
1457
1556
 
1458
1557
  lodash-es@4.18.1: {}
1459
1558
 
1559
+ long@5.3.2: {}
1560
+
1460
1561
  longest-streak@3.1.0: {}
1461
1562
 
1462
1563
  magic-string@0.30.21:
@@ -1832,6 +1933,17 @@ snapshots:
1832
1933
  regex: 6.1.0
1833
1934
  regex-recursion: 6.0.2
1834
1935
 
1936
+ onnxruntime-common@1.25.1: {}
1937
+
1938
+ onnxruntime-web@1.25.1:
1939
+ dependencies:
1940
+ flatbuffers: 25.9.23
1941
+ guid-typescript: 1.0.9
1942
+ long: 5.3.2
1943
+ onnxruntime-common: 1.25.1
1944
+ platform: 1.3.6
1945
+ protobufjs: 7.5.6
1946
+
1835
1947
  parse-entities@4.0.2:
1836
1948
  dependencies:
1837
1949
  '@types/unist': 2.0.11
@@ -1844,6 +1956,8 @@ snapshots:
1844
1956
 
1845
1957
  picocolors@1.1.1: {}
1846
1958
 
1959
+ platform@1.3.6: {}
1960
+
1847
1961
  postcss@8.5.10:
1848
1962
  dependencies:
1849
1963
  nanoid: 3.3.11
@@ -1852,6 +1966,21 @@ snapshots:
1852
1966
 
1853
1967
  property-information@7.1.0: {}
1854
1968
 
1969
+ protobufjs@7.5.6:
1970
+ dependencies:
1971
+ '@protobufjs/aspromise': 1.1.2
1972
+ '@protobufjs/base64': 1.1.2
1973
+ '@protobufjs/codegen': 2.0.5
1974
+ '@protobufjs/eventemitter': 1.1.0
1975
+ '@protobufjs/fetch': 1.1.0
1976
+ '@protobufjs/float': 1.0.2
1977
+ '@protobufjs/inquire': 1.1.1
1978
+ '@protobufjs/path': 1.1.2
1979
+ '@protobufjs/pool': 1.1.0
1980
+ '@protobufjs/utf8': 1.1.1
1981
+ '@types/node': 25.6.0
1982
+ long: 5.3.2
1983
+
1855
1984
  regex-recursion@6.0.2:
1856
1985
  dependencies:
1857
1986
  regex-utilities: 2.3.0
@@ -1925,6 +2054,8 @@ snapshots:
1925
2054
 
1926
2055
  tslib@2.8.1: {}
1927
2056
 
2057
+ undici-types@7.19.2: {}
2058
+
1928
2059
  unist-util-is@6.0.1:
1929
2060
  dependencies:
1930
2061
  '@types/unist': 3.0.3
@@ -28,7 +28,7 @@
28
28
  <script setup lang="ts">
29
29
  import { computed, onMounted, onBeforeUnmount, ref, watch } from 'vue';
30
30
  import debounce from 'lodash.debounce';
31
- import { requestMicAndStartVAD, stopUserMedia, getRecorder, CALIBRATION_DURATION } from './voiceActivityDetection';
31
+ import { requestMicAndStartVAD, stopUserMedia, getRecord } from './voiceActivityDetection';
32
32
  import { Spinner } from '@/afcl'
33
33
  import { storeToRefs } from 'pinia';
34
34
  import { useAgentStore } from '../composables/useAgentStore';
@@ -41,7 +41,7 @@ const { sendAudioToServerAndHandleResponse } = agentAudio;
41
41
  const { stopGenerationAndAudio } = agentAudio;
42
42
  const { stopCurrentAudioPlayback } = agentAudio;
43
43
  const { agentAudioMode } = storeToRefs(agentAudio);
44
- const microphoneButtonMode = ref<'off' | 'calibrating' | 'listen' | 'transcribing' | 'generating'>('off');
44
+ const microphoneButtonMode = ref<'off' | 'listen' | 'transcribing' | 'generating'>('off');
45
45
  const showAudioWavesAnimation = ref(false);
46
46
  const audioAmplitude = ref(0);
47
47
  const hideAnimationDebounced = debounce(() => {
@@ -93,14 +93,9 @@ function toggleChatMode() {
93
93
  }
94
94
 
95
95
  async function onStartRecording() {
96
- microphoneButtonMode.value = 'calibrating';
97
96
  await requestMicAndStartVAD(saidSomething, stopRecording, onAnySound);
98
- setTimeout(() => {
99
- if (isAudioChatMode.value) {
100
97
  microphoneButtonMode.value = 'listen';
101
98
  agentAudio.playBeep(1000);
102
- }
103
- }, CALIBRATION_DURATION);
104
99
  }
105
100
 
106
101
  function onStopRecording() {
@@ -145,8 +140,9 @@ function onAnySound(amplitude: number) {
145
140
 
146
141
  async function sendRecordForTranscription() {
147
142
  showAudioWavesAnimation.value = false;
148
- const recordBlob = await getRecorder();
143
+ const recordBlob = await getRecord();
149
144
  if (recordBlob) {
145
+ console.log('Audio recorded, sending to server for transcription. Audio Blob size:', recordBlob.size, recordBlob.type);
150
146
  onStopRecording();
151
147
  await sendAudioToServerAndHandleResponse(recordBlob);
152
148
  if (agentStore.isAudioChatMode) {
@@ -1,151 +1,62 @@
1
- import vad from 'voice-activity-detection';
2
-
3
- let currentStream: MediaStream | null = null;
4
- let vadInstance: any = null;
5
- let audioContext: AudioContext | null = null;
6
- let mediaRecorder: MediaRecorder | null = null;
7
- let recordedChunks: BlobPart[] = [];
8
- let wasVoiceStarted = false;
9
-
10
- export const CALIBRATION_DURATION = 1000; // in ms
1
+ import { MicVAD, utils } from "@ricky0123/vad-web"
2
+
3
+ let VADInstance: MicVAD | null = null;
4
+ let recordedAudioChunks: Float32Array[] = [];
5
+ let onVoiceStopCallback: () => void = () => {};
6
+ let onVoiceStartCallback: () => void = () => {};
7
+ let onUpdateCallback: (amplitude: number) => void = () => {};
8
+
9
+ async function createVADInstance(){
10
+ VADInstance = await MicVAD.new({
11
+ onFrameProcessed: ({ isSpeech }) => {
12
+ onUpdateCallback(isSpeech);
13
+ },
14
+ onSpeechEnd: (audio) => {
15
+ recordedAudioChunks.push(audio);
16
+ onVoiceStopCallback();
17
+ },
18
+ onSpeechStart: () => {
19
+ onVoiceStartCallback();
20
+ },
21
+ onnxWASMBasePath: "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/",
22
+ baseAssetPath: "https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/",
23
+ })
24
+ }
11
25
 
12
26
  export async function requestMicAndStartVAD(
13
- onVoiceStopCallback: () => void,
14
- onVoiceStartCallback: () => void,
15
- onUpdateCallback: (amplitude: number) => void
27
+ onVoiceStop: () => void,
28
+ onVoiceStart: () => void,
29
+ onUpdate: (amplitude: number) => void
16
30
  ) {
17
- return new Promise<void>((resolve, reject) => {
18
- try {
19
- audioContext = new AudioContext();
20
-
21
- navigator.mediaDevices
22
- .getUserMedia({ audio: true })
23
- .then((stream) => {
24
- currentStream = stream;
25
- startRecording(stream);
26
- resolve();
27
- startUserMedia(audioContext as AudioContext, stream, onVoiceStartCallback, onVoiceStopCallback, onUpdateCallback);
28
- })
29
- .catch((error) => {
30
- handleMicConnectError();
31
- reject(error);
32
- });
33
- } catch (e) {
34
- handleUserMediaError();
35
- reject(e);
36
- }
37
- });
38
- }
39
-
40
- function handleUserMediaError() {
41
- console.error('Mic input is not supported by the browser.');
42
- }
43
-
44
- function handleMicConnectError() {
45
- console.error('Could not connect microphone. Possible rejected by the user or is blocked by the browser.');
46
- }
47
-
48
- export async function stopUserMedia() {
49
- wasVoiceStarted = false;
50
- if (vadInstance && vadInstance.destroy) {
51
- vadInstance.destroy();
52
- vadInstance = null;
53
- }
54
-
55
- if (currentStream) {
56
- currentStream.getTracks().forEach(track => track.stop());
57
- currentStream = null;
58
- }
59
-
60
- if (audioContext) {
61
- audioContext.close();
62
- audioContext = null;
63
- }
31
+ onVoiceStopCallback = onVoiceStop;
32
+ onVoiceStartCallback = onVoiceStart;
33
+ onUpdateCallback = onUpdate;
64
34
 
65
- if (mediaRecorder) {
66
- mediaRecorder.stop();
67
- mediaRecorder = null;
35
+ if (!VADInstance) {
36
+ await createVADInstance();
68
37
  }
69
-
38
+ VADInstance?.start();
70
39
  }
71
40
 
72
- function startRecording(stream: MediaStream) {
73
- recordedChunks = [];
74
- mediaRecorder = new MediaRecorder(stream);
75
- mediaRecorder.ondataavailable = (event: BlobEvent) => {
76
- if (event.data.size > 0) {
77
- recordedChunks.push(event.data);
78
- }
79
- };
80
- mediaRecorder.start();
41
+ export async function stopUserMedia() {
42
+ await VADInstance?.pause();
81
43
  }
82
44
 
83
- export async function getRecorder(): Promise<Blob | null> {
84
- if (!mediaRecorder) {
85
- return Promise.resolve(null);
45
+ export async function getRecord() {
46
+ const totalSamples = recordedAudioChunks.reduce((sum, chunk) => sum + chunk.length, 0);
47
+ if (totalSamples === 0) {
48
+ return null;
86
49
  }
87
50
 
88
- const recorder = mediaRecorder;
89
- mediaRecorder = null;
90
-
91
- const finalizeBlob = () => {
92
- const blob = new Blob(recordedChunks, { type: recorder.mimeType || 'audio/webm' });
93
- recordedChunks = [];
94
- return blob;
95
- };
96
-
97
- if (recorder.state === 'inactive') {
98
- return Promise.resolve(finalizeBlob());
51
+ const mergedAudio = new Float32Array(totalSamples);
52
+ let offset = 0;
53
+ for (const chunk of recordedAudioChunks) {
54
+ mergedAudio.set(chunk, offset);
55
+ offset += chunk.length;
99
56
  }
100
57
 
101
- return new Promise<Blob>((resolve, reject) => {
102
- recorder.onstop = () => {
103
- resolve(finalizeBlob());
104
- };
105
- recorder.onerror = () => {
106
- recordedChunks = [];
107
- reject(new Error('Failed to finalize audio recording.'));
108
- };
109
- recorder.stop();
110
- });
111
- }
112
-
113
- function startUserMedia(
114
- audioContext: AudioContext,
115
- stream: MediaStream,
116
- onVoiceStartCallback: () => void,
117
- onVoiceStopCallback: () => void,
118
- onUpdateCallback: (amplitude: number) => void
119
- ) {
120
- const options = {
121
- fftSize: 1024,
122
- bufferLen: 1024,
123
- smoothingTimeConstant: 0.2,
124
- minCaptureFreq: 85, // in Hz
125
- maxCaptureFreq: 255, // in Hz
126
- noiseCaptureDuration: CALIBRATION_DURATION, // in ms
127
- minNoiseLevel: 0.5, // from 0 to 1
128
- maxNoiseLevel: 0.7, // from 0 to 1
129
- avgNoiseMultiplier: 1.2,
130
- onVoiceStart() {
131
- wasVoiceStarted = true;
132
- if (!mediaRecorder || mediaRecorder.state === 'inactive') {
133
- startRecording(currentStream as MediaStream);
134
- }
135
- console.log('👹👹👹voice start👹👹👹');
136
- onVoiceStartCallback();
137
- },
138
- onVoiceStop() {
139
- if (!wasVoiceStarted) {
140
- return;
141
- }
142
- console.log('👿👿👿voice stop👿👿👿');
143
- onVoiceStopCallback();
144
- }, //Doesn't work properly, so we will handle it with onUpdate callback
145
- onUpdate(val: number) {
146
- onUpdateCallback(val);
147
- }
148
- };
149
-
150
- vadInstance = vad(audioContext, stream, options);
58
+ const wavBuffer = utils.encodeWAV(mergedAudio, 1, 16000, 1, 16);
59
+ const recordToReturn = new Blob([wavBuffer], { type: 'audio/wav' });
60
+ recordedAudioChunks = [];
61
+ return recordToReturn;
151
62
  }
@@ -67,7 +67,7 @@ export const useAgentAudio = defineStore('agentAudio', () => {
67
67
  async function sendAudioToServerAndHandleResponse(blob: Blob) {
68
68
  currentAbortController = new AbortController();
69
69
  const formData = new FormData();
70
- formData.append('file', blob, 'user_prompt.webm');
70
+ formData.append('file', blob, 'user_prompt.wav');
71
71
  formData.append('sessionId', agentStore.activeSessionId);
72
72
  formData.append('mode', agentStore.activeModeName ?? '');
73
73
  formData.append('timeZone', Intl.DateTimeFormat().resolvedOptions().timeZone);
@@ -14,6 +14,7 @@
14
14
  "@incremark/core": "^1.0.2",
15
15
  "@incremark/theme": "^1.0.2",
16
16
  "@incremark/vue": "^1.0.2",
17
+ "@ricky0123/vad-web": "^0.0.30",
17
18
  "@shikijs/langs": "^4.0.2",
18
19
  "@shikijs/themes": "^4.0.2",
19
20
  "@vueuse/core": "^14.2.1",
@@ -17,6 +17,9 @@ importers:
17
17
  '@incremark/vue':
18
18
  specifier: ^1.0.2
19
19
  version: 1.0.2(katex@0.16.45)(vue@3.5.32)
20
+ '@ricky0123/vad-web':
21
+ specifier: ^0.0.30
22
+ version: 0.0.30
20
23
  '@shikijs/langs':
21
24
  specifier: ^4.0.2
22
25
  version: 4.0.2
@@ -139,6 +142,39 @@ packages:
139
142
  resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
140
143
  engines: {node: '>=8.0.0'}
141
144
 
145
+ '@protobufjs/aspromise@1.1.2':
146
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
147
+
148
+ '@protobufjs/base64@1.1.2':
149
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
150
+
151
+ '@protobufjs/codegen@2.0.5':
152
+ resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==}
153
+
154
+ '@protobufjs/eventemitter@1.1.0':
155
+ resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
156
+
157
+ '@protobufjs/fetch@1.1.0':
158
+ resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
159
+
160
+ '@protobufjs/float@1.0.2':
161
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
162
+
163
+ '@protobufjs/inquire@1.1.1':
164
+ resolution: {integrity: sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==}
165
+
166
+ '@protobufjs/path@1.1.2':
167
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
168
+
169
+ '@protobufjs/pool@1.1.0':
170
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
171
+
172
+ '@protobufjs/utf8@1.1.1':
173
+ resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==}
174
+
175
+ '@ricky0123/vad-web@0.0.30':
176
+ resolution: {integrity: sha512-cJyYrh4YeeUBJcbR9Bic/bFDyB9qBkAepvpuWM3vLxnAi7bC3VHzf51UeNdT+OtY4D7MLAgV8iJMc4z41ZnaWg==}
177
+
142
178
  '@shikijs/core@3.23.0':
143
179
  resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==}
144
180
 
@@ -218,6 +254,9 @@ packages:
218
254
  '@types/ms@2.1.0':
219
255
  resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
220
256
 
257
+ '@types/node@25.6.0':
258
+ resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
259
+
221
260
  '@types/trusted-types@2.0.7':
222
261
  resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
223
262
 
@@ -472,6 +511,9 @@ packages:
472
511
  fast-json-patch@3.1.1:
473
512
  resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==}
474
513
 
514
+ flatbuffers@25.9.23:
515
+ resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==}
516
+
475
517
  get-caller-file@2.0.5:
476
518
  resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
477
519
  engines: {node: 6.* || 8.* || >= 10.*}
@@ -480,6 +522,9 @@ packages:
480
522
  resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
481
523
  engines: {node: '>=18'}
482
524
 
525
+ guid-typescript@1.0.9:
526
+ resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==}
527
+
483
528
  hast-util-to-html@9.0.5:
484
529
  resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
485
530
 
@@ -525,6 +570,9 @@ packages:
525
570
  lodash-es@4.18.1:
526
571
  resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
527
572
 
573
+ long@5.3.2:
574
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
575
+
528
576
  longest-streak@3.1.0:
529
577
  resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
530
578
 
@@ -690,12 +738,21 @@ packages:
690
738
  oniguruma-to-es@4.3.5:
691
739
  resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==}
692
740
 
741
+ onnxruntime-common@1.25.1:
742
+ resolution: {integrity: sha512-kKvYQFdos4LWJqhZ+nmKu3NT8NXzw8I5x9fNUKe1rNKcPfNKnYXUtW7JBpcKFsvLtrJashRgVYSbFap4cHxvNg==}
743
+
744
+ onnxruntime-web@1.25.1:
745
+ resolution: {integrity: sha512-mgs61sJ9m3hLa5jGRr9Pen3kkG00vlxmrcRL6FufYpSWBZKaklo0sotQCq2fLjgDVLnW57jrDcLqzYJNKeZskQ==}
746
+
693
747
  parse-entities@4.0.2:
694
748
  resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
695
749
 
696
750
  picocolors@1.1.1:
697
751
  resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
698
752
 
753
+ platform@1.3.6:
754
+ resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
755
+
699
756
  postcss@8.5.10:
700
757
  resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
701
758
  engines: {node: ^10 || ^12 || >=14}
@@ -703,6 +760,10 @@ packages:
703
760
  property-information@7.1.0:
704
761
  resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
705
762
 
763
+ protobufjs@7.5.6:
764
+ resolution: {integrity: sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==}
765
+ engines: {node: '>=12.0.0'}
766
+
706
767
  regex-recursion@6.0.2:
707
768
  resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
708
769
 
@@ -775,6 +836,9 @@ packages:
775
836
  tslib@2.8.1:
776
837
  resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
777
838
 
839
+ undici-types@7.19.2:
840
+ resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
841
+
778
842
  unist-util-is@6.0.1:
779
843
  resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
780
844
 
@@ -1063,6 +1127,33 @@ snapshots:
1063
1127
 
1064
1128
  '@opentelemetry/api@1.9.0': {}
1065
1129
 
1130
+ '@protobufjs/aspromise@1.1.2': {}
1131
+
1132
+ '@protobufjs/base64@1.1.2': {}
1133
+
1134
+ '@protobufjs/codegen@2.0.5': {}
1135
+
1136
+ '@protobufjs/eventemitter@1.1.0': {}
1137
+
1138
+ '@protobufjs/fetch@1.1.0':
1139
+ dependencies:
1140
+ '@protobufjs/aspromise': 1.1.2
1141
+ '@protobufjs/inquire': 1.1.1
1142
+
1143
+ '@protobufjs/float@1.0.2': {}
1144
+
1145
+ '@protobufjs/inquire@1.1.1': {}
1146
+
1147
+ '@protobufjs/path@1.1.2': {}
1148
+
1149
+ '@protobufjs/pool@1.1.0': {}
1150
+
1151
+ '@protobufjs/utf8@1.1.1': {}
1152
+
1153
+ '@ricky0123/vad-web@0.0.30':
1154
+ dependencies:
1155
+ onnxruntime-web: 1.25.1
1156
+
1066
1157
  '@shikijs/core@3.23.0':
1067
1158
  dependencies:
1068
1159
  '@shikijs/types': 3.23.0
@@ -1162,6 +1253,10 @@ snapshots:
1162
1253
 
1163
1254
  '@types/ms@2.1.0': {}
1164
1255
 
1256
+ '@types/node@25.6.0':
1257
+ dependencies:
1258
+ undici-types: 7.19.2
1259
+
1165
1260
  '@types/trusted-types@2.0.7':
1166
1261
  optional: true
1167
1262
 
@@ -1404,10 +1499,14 @@ snapshots:
1404
1499
 
1405
1500
  fast-json-patch@3.1.1: {}
1406
1501
 
1502
+ flatbuffers@25.9.23: {}
1503
+
1407
1504
  get-caller-file@2.0.5: {}
1408
1505
 
1409
1506
  get-east-asian-width@1.5.0: {}
1410
1507
 
1508
+ guid-typescript@1.0.9: {}
1509
+
1411
1510
  hast-util-to-html@9.0.5:
1412
1511
  dependencies:
1413
1512
  '@types/hast': 3.0.4
@@ -1457,6 +1556,8 @@ snapshots:
1457
1556
 
1458
1557
  lodash-es@4.18.1: {}
1459
1558
 
1559
+ long@5.3.2: {}
1560
+
1460
1561
  longest-streak@3.1.0: {}
1461
1562
 
1462
1563
  magic-string@0.30.21:
@@ -1832,6 +1933,17 @@ snapshots:
1832
1933
  regex: 6.1.0
1833
1934
  regex-recursion: 6.0.2
1834
1935
 
1936
+ onnxruntime-common@1.25.1: {}
1937
+
1938
+ onnxruntime-web@1.25.1:
1939
+ dependencies:
1940
+ flatbuffers: 25.9.23
1941
+ guid-typescript: 1.0.9
1942
+ long: 5.3.2
1943
+ onnxruntime-common: 1.25.1
1944
+ platform: 1.3.6
1945
+ protobufjs: 7.5.6
1946
+
1835
1947
  parse-entities@4.0.2:
1836
1948
  dependencies:
1837
1949
  '@types/unist': 2.0.11
@@ -1844,6 +1956,8 @@ snapshots:
1844
1956
 
1845
1957
  picocolors@1.1.1: {}
1846
1958
 
1959
+ platform@1.3.6: {}
1960
+
1847
1961
  postcss@8.5.10:
1848
1962
  dependencies:
1849
1963
  nanoid: 3.3.11
@@ -1852,6 +1966,21 @@ snapshots:
1852
1966
 
1853
1967
  property-information@7.1.0: {}
1854
1968
 
1969
+ protobufjs@7.5.6:
1970
+ dependencies:
1971
+ '@protobufjs/aspromise': 1.1.2
1972
+ '@protobufjs/base64': 1.1.2
1973
+ '@protobufjs/codegen': 2.0.5
1974
+ '@protobufjs/eventemitter': 1.1.0
1975
+ '@protobufjs/fetch': 1.1.0
1976
+ '@protobufjs/float': 1.0.2
1977
+ '@protobufjs/inquire': 1.1.1
1978
+ '@protobufjs/path': 1.1.2
1979
+ '@protobufjs/pool': 1.1.0
1980
+ '@protobufjs/utf8': 1.1.1
1981
+ '@types/node': 25.6.0
1982
+ long: 5.3.2
1983
+
1855
1984
  regex-recursion@6.0.2:
1856
1985
  dependencies:
1857
1986
  regex-utilities: 2.3.0
@@ -1925,6 +2054,8 @@ snapshots:
1925
2054
 
1926
2055
  tslib@2.8.1: {}
1927
2056
 
2057
+ undici-types@7.19.2: {}
2058
+
1928
2059
  unist-util-is@6.0.1:
1929
2060
  dependencies:
1930
2061
  '@types/unist': 3.0.3
@@ -28,7 +28,7 @@
28
28
  <script setup lang="ts">
29
29
  import { computed, onMounted, onBeforeUnmount, ref, watch } from 'vue';
30
30
  import debounce from 'lodash.debounce';
31
- import { requestMicAndStartVAD, stopUserMedia, getRecorder, CALIBRATION_DURATION } from './voiceActivityDetection';
31
+ import { requestMicAndStartVAD, stopUserMedia, getRecord } from './voiceActivityDetection';
32
32
  import { Spinner } from '@/afcl'
33
33
  import { storeToRefs } from 'pinia';
34
34
  import { useAgentStore } from '../composables/useAgentStore';
@@ -41,7 +41,7 @@ const { sendAudioToServerAndHandleResponse } = agentAudio;
41
41
  const { stopGenerationAndAudio } = agentAudio;
42
42
  const { stopCurrentAudioPlayback } = agentAudio;
43
43
  const { agentAudioMode } = storeToRefs(agentAudio);
44
- const microphoneButtonMode = ref<'off' | 'calibrating' | 'listen' | 'transcribing' | 'generating'>('off');
44
+ const microphoneButtonMode = ref<'off' | 'listen' | 'transcribing' | 'generating'>('off');
45
45
  const showAudioWavesAnimation = ref(false);
46
46
  const audioAmplitude = ref(0);
47
47
  const hideAnimationDebounced = debounce(() => {
@@ -93,14 +93,9 @@ function toggleChatMode() {
93
93
  }
94
94
 
95
95
  async function onStartRecording() {
96
- microphoneButtonMode.value = 'calibrating';
97
96
  await requestMicAndStartVAD(saidSomething, stopRecording, onAnySound);
98
- setTimeout(() => {
99
- if (isAudioChatMode.value) {
100
97
  microphoneButtonMode.value = 'listen';
101
98
  agentAudio.playBeep(1000);
102
- }
103
- }, CALIBRATION_DURATION);
104
99
  }
105
100
 
106
101
  function onStopRecording() {
@@ -145,8 +140,9 @@ function onAnySound(amplitude: number) {
145
140
 
146
141
  async function sendRecordForTranscription() {
147
142
  showAudioWavesAnimation.value = false;
148
- const recordBlob = await getRecorder();
143
+ const recordBlob = await getRecord();
149
144
  if (recordBlob) {
145
+ console.log('Audio recorded, sending to server for transcription. Audio Blob size:', recordBlob.size, recordBlob.type);
150
146
  onStopRecording();
151
147
  await sendAudioToServerAndHandleResponse(recordBlob);
152
148
  if (agentStore.isAudioChatMode) {
@@ -1,151 +1,62 @@
1
- import vad from 'voice-activity-detection';
2
-
3
- let currentStream: MediaStream | null = null;
4
- let vadInstance: any = null;
5
- let audioContext: AudioContext | null = null;
6
- let mediaRecorder: MediaRecorder | null = null;
7
- let recordedChunks: BlobPart[] = [];
8
- let wasVoiceStarted = false;
9
-
10
- export const CALIBRATION_DURATION = 1000; // in ms
1
+ import { MicVAD, utils } from "@ricky0123/vad-web"
2
+
3
+ let VADInstance: MicVAD | null = null;
4
+ let recordedAudioChunks: Float32Array[] = [];
5
+ let onVoiceStopCallback: () => void = () => {};
6
+ let onVoiceStartCallback: () => void = () => {};
7
+ let onUpdateCallback: (amplitude: number) => void = () => {};
8
+
9
+ async function createVADInstance(){
10
+ VADInstance = await MicVAD.new({
11
+ onFrameProcessed: ({ isSpeech }) => {
12
+ onUpdateCallback(isSpeech);
13
+ },
14
+ onSpeechEnd: (audio) => {
15
+ recordedAudioChunks.push(audio);
16
+ onVoiceStopCallback();
17
+ },
18
+ onSpeechStart: () => {
19
+ onVoiceStartCallback();
20
+ },
21
+ onnxWASMBasePath: "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/",
22
+ baseAssetPath: "https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/",
23
+ })
24
+ }
11
25
 
12
26
  export async function requestMicAndStartVAD(
13
- onVoiceStopCallback: () => void,
14
- onVoiceStartCallback: () => void,
15
- onUpdateCallback: (amplitude: number) => void
27
+ onVoiceStop: () => void,
28
+ onVoiceStart: () => void,
29
+ onUpdate: (amplitude: number) => void
16
30
  ) {
17
- return new Promise<void>((resolve, reject) => {
18
- try {
19
- audioContext = new AudioContext();
20
-
21
- navigator.mediaDevices
22
- .getUserMedia({ audio: true })
23
- .then((stream) => {
24
- currentStream = stream;
25
- startRecording(stream);
26
- resolve();
27
- startUserMedia(audioContext as AudioContext, stream, onVoiceStartCallback, onVoiceStopCallback, onUpdateCallback);
28
- })
29
- .catch((error) => {
30
- handleMicConnectError();
31
- reject(error);
32
- });
33
- } catch (e) {
34
- handleUserMediaError();
35
- reject(e);
36
- }
37
- });
38
- }
39
-
40
- function handleUserMediaError() {
41
- console.error('Mic input is not supported by the browser.');
42
- }
43
-
44
- function handleMicConnectError() {
45
- console.error('Could not connect microphone. Possible rejected by the user or is blocked by the browser.');
46
- }
47
-
48
- export async function stopUserMedia() {
49
- wasVoiceStarted = false;
50
- if (vadInstance && vadInstance.destroy) {
51
- vadInstance.destroy();
52
- vadInstance = null;
53
- }
54
-
55
- if (currentStream) {
56
- currentStream.getTracks().forEach(track => track.stop());
57
- currentStream = null;
58
- }
59
-
60
- if (audioContext) {
61
- audioContext.close();
62
- audioContext = null;
63
- }
31
+ onVoiceStopCallback = onVoiceStop;
32
+ onVoiceStartCallback = onVoiceStart;
33
+ onUpdateCallback = onUpdate;
64
34
 
65
- if (mediaRecorder) {
66
- mediaRecorder.stop();
67
- mediaRecorder = null;
35
+ if (!VADInstance) {
36
+ await createVADInstance();
68
37
  }
69
-
38
+ VADInstance?.start();
70
39
  }
71
40
 
72
- function startRecording(stream: MediaStream) {
73
- recordedChunks = [];
74
- mediaRecorder = new MediaRecorder(stream);
75
- mediaRecorder.ondataavailable = (event: BlobEvent) => {
76
- if (event.data.size > 0) {
77
- recordedChunks.push(event.data);
78
- }
79
- };
80
- mediaRecorder.start();
41
+ export async function stopUserMedia() {
42
+ await VADInstance?.pause();
81
43
  }
82
44
 
83
- export async function getRecorder(): Promise<Blob | null> {
84
- if (!mediaRecorder) {
85
- return Promise.resolve(null);
45
+ export async function getRecord() {
46
+ const totalSamples = recordedAudioChunks.reduce((sum, chunk) => sum + chunk.length, 0);
47
+ if (totalSamples === 0) {
48
+ return null;
86
49
  }
87
50
 
88
- const recorder = mediaRecorder;
89
- mediaRecorder = null;
90
-
91
- const finalizeBlob = () => {
92
- const blob = new Blob(recordedChunks, { type: recorder.mimeType || 'audio/webm' });
93
- recordedChunks = [];
94
- return blob;
95
- };
96
-
97
- if (recorder.state === 'inactive') {
98
- return Promise.resolve(finalizeBlob());
51
+ const mergedAudio = new Float32Array(totalSamples);
52
+ let offset = 0;
53
+ for (const chunk of recordedAudioChunks) {
54
+ mergedAudio.set(chunk, offset);
55
+ offset += chunk.length;
99
56
  }
100
57
 
101
- return new Promise<Blob>((resolve, reject) => {
102
- recorder.onstop = () => {
103
- resolve(finalizeBlob());
104
- };
105
- recorder.onerror = () => {
106
- recordedChunks = [];
107
- reject(new Error('Failed to finalize audio recording.'));
108
- };
109
- recorder.stop();
110
- });
111
- }
112
-
113
- function startUserMedia(
114
- audioContext: AudioContext,
115
- stream: MediaStream,
116
- onVoiceStartCallback: () => void,
117
- onVoiceStopCallback: () => void,
118
- onUpdateCallback: (amplitude: number) => void
119
- ) {
120
- const options = {
121
- fftSize: 1024,
122
- bufferLen: 1024,
123
- smoothingTimeConstant: 0.2,
124
- minCaptureFreq: 85, // in Hz
125
- maxCaptureFreq: 255, // in Hz
126
- noiseCaptureDuration: CALIBRATION_DURATION, // in ms
127
- minNoiseLevel: 0.5, // from 0 to 1
128
- maxNoiseLevel: 0.7, // from 0 to 1
129
- avgNoiseMultiplier: 1.2,
130
- onVoiceStart() {
131
- wasVoiceStarted = true;
132
- if (!mediaRecorder || mediaRecorder.state === 'inactive') {
133
- startRecording(currentStream as MediaStream);
134
- }
135
- console.log('👹👹👹voice start👹👹👹');
136
- onVoiceStartCallback();
137
- },
138
- onVoiceStop() {
139
- if (!wasVoiceStarted) {
140
- return;
141
- }
142
- console.log('👿👿👿voice stop👿👿👿');
143
- onVoiceStopCallback();
144
- }, //Doesn't work properly, so we will handle it with onUpdate callback
145
- onUpdate(val: number) {
146
- onUpdateCallback(val);
147
- }
148
- };
149
-
150
- vadInstance = vad(audioContext, stream, options);
58
+ const wavBuffer = utils.encodeWAV(mergedAudio, 1, 16000, 1, 16);
59
+ const recordToReturn = new Blob([wavBuffer], { type: 'audio/wav' });
60
+ recordedAudioChunks = [];
61
+ return recordToReturn;
151
62
  }
package/dist/index.js CHANGED
@@ -47,6 +47,15 @@ const sessionIdBodySchema = z.object({
47
47
  const createSessionBodySchema = z.object({
48
48
  triggerMessage: z.string().optional(),
49
49
  }).strict();
50
+ function isAbortError(error) {
51
+ return (error instanceof DOMException && error.name === "AbortError") || (typeof error === "object" &&
52
+ error !== null &&
53
+ "name" in error &&
54
+ (error.name === "AbortError" || error.name === "APIUserAbortError"));
55
+ }
56
+ function getErrorMessage(error) {
57
+ return error instanceof Error ? error.message : String(error);
58
+ }
50
59
  export default class AdminForthAgentPlugin extends AdminForthPlugin {
51
60
  parseBody(schema, body, response) {
52
61
  const parsed = schema.safeParse(body !== null && body !== void 0 ? body : {});
@@ -134,6 +143,20 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
134
143
  hasAudioAdapter: Boolean(this.options.audioAdapter),
135
144
  }
136
145
  });
146
+ if (!this.adminforth.config.customization.customHeadItems) {
147
+ this.adminforth.config.customization.customHeadItems = [];
148
+ }
149
+ this.adminforth.config.customization.customHeadItems.push({
150
+ tagName: 'script',
151
+ attributes: {
152
+ src: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/ort.wasm.min.js'
153
+ }
154
+ }, {
155
+ tagName: 'script',
156
+ attributes: {
157
+ src: 'https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/bundle.min.js'
158
+ },
159
+ });
137
160
  if (!this.options.sessionResource) {
138
161
  throw new Error("sessionResource is required for AdminForthAgentPlugin");
139
162
  }
@@ -151,7 +174,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
151
174
  runAgentTurn(input) {
152
175
  return __awaiter(this, void 0, void 0, function* () {
153
176
  var _a, e_1, _b, _c;
154
- var _d, _e, _f, _g;
177
+ var _d, _e, _f, _g, _h;
155
178
  let fullResponse = "";
156
179
  const maxTokens = (_d = this.options.maxTokens) !== null && _d !== void 0 ? _d : 1000;
157
180
  const selectedMode = (_e = this.options.modes.find((mode) => mode.name === input.modeName)) !== null && _e !== void 0 ? _e : this.options.modes[0];
@@ -172,7 +195,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
172
195
  const modelMiddleware = primaryModelSpec.middleware;
173
196
  const userLanguage = yield detectUserLanguage(selectedMode.completionAdapter, input.prompt, input.previousUserMessages)
174
197
  .catch((error) => {
175
- logger.warn(`Failed to detect user language: ${error.message}`);
198
+ var _a;
199
+ if (((_a = input.abortSignal) === null || _a === void 0 ? void 0 : _a.aborted) || isAbortError(error)) {
200
+ throw error;
201
+ }
202
+ logger.warn(`Failed to detect user language: ${getErrorMessage(error)}`);
176
203
  return null;
177
204
  });
178
205
  const systemPrompt = buildAgentTurnSystemPrompt({
@@ -209,10 +236,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
209
236
  sequenceDebugSink: input.sequenceDebugCollector,
210
237
  });
211
238
  try {
212
- for (var _h = true, _j = __asyncValues(stream), _k; _k = yield _j.next(), _a = _k.done, !_a; _h = true) {
213
- _c = _k.value;
214
- _h = false;
239
+ for (var _j = true, _k = __asyncValues(stream), _l; _l = yield _k.next(), _a = _l.done, !_a; _j = true) {
240
+ _c = _l.value;
241
+ _j = false;
215
242
  const rawChunk = _c;
243
+ if ((_f = input.abortSignal) === null || _f === void 0 ? void 0 : _f.aborted) {
244
+ throw new DOMException("This operation was aborted", "AbortError");
245
+ }
216
246
  const [token, metadata] = rawChunk;
217
247
  const nodeName = typeof (metadata === null || metadata === void 0 ? void 0 : metadata.langgraph_node) === "string"
218
248
  ? metadata.langgraph_node
@@ -234,18 +264,18 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
234
264
  .map((b) => { var _a; return String((_a = b.text) !== null && _a !== void 0 ? _a : ""); })
235
265
  .join("");
236
266
  if (reasoningDelta) {
237
- (_f = input.emitReasoningDelta) === null || _f === void 0 ? void 0 : _f.call(input, reasoningDelta);
267
+ (_g = input.emitReasoningDelta) === null || _g === void 0 ? void 0 : _g.call(input, reasoningDelta);
238
268
  }
239
269
  if (textDelta) {
240
270
  fullResponse += textDelta;
241
- (_g = input.emitTextDelta) === null || _g === void 0 ? void 0 : _g.call(input, textDelta);
271
+ (_h = input.emitTextDelta) === null || _h === void 0 ? void 0 : _h.call(input, textDelta);
242
272
  }
243
273
  }
244
274
  }
245
275
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
246
276
  finally {
247
277
  try {
248
- if (!_h && !_a && (_b = _j.return)) yield _b.call(_j);
278
+ if (!_j && !_a && (_b = _k.return)) yield _b.call(_k);
249
279
  }
250
280
  finally { if (e_1) throw e_1.error; }
251
281
  }
@@ -285,14 +315,14 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
285
315
  fullResponse = agentResponse.text;
286
316
  }
287
317
  catch (error) {
288
- if ((_a = input.abortSignal) === null || _a === void 0 ? void 0 : _a.aborted) {
318
+ if (((_a = input.abortSignal) === null || _a === void 0 ? void 0 : _a.aborted) || isAbortError(error)) {
289
319
  aborted = true;
290
320
  logger.info(input.abortLogMessage);
291
321
  }
292
322
  else {
293
323
  failed = true;
294
- logger.error(`${input.failureLogMessage}:\n${error.message}`);
295
- fullResponse = error.message;
324
+ fullResponse = getErrorMessage(error);
325
+ logger.error(`${input.failureLogMessage}:\n${fullResponse}`);
296
326
  (_b = input.emitErrorResponse) === null || _b === void 0 ? void 0 : _b.call(input, fullResponse);
297
327
  }
298
328
  }
@@ -392,12 +422,12 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
392
422
  });
393
423
  }
394
424
  catch (error) {
395
- if (abortSignal.aborted) {
425
+ if (abortSignal.aborted || isAbortError(error)) {
396
426
  logger.info("Agent speech transcription aborted by the client");
397
427
  stream.end();
398
428
  return null;
399
429
  }
400
- logger.error(`Agent speech transcription failed:\n${error.message}`);
430
+ logger.error(`Agent speech transcription failed:\n${getErrorMessage(error)}`);
401
431
  stream.error("Speech transcription failed. Check server logs for details.");
402
432
  stream.end();
403
433
  return null;
@@ -481,7 +511,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
481
511
  return null;
482
512
  }
483
513
  catch (error) {
484
- if (abortSignal.aborted) {
514
+ if (abortSignal.aborted || isAbortError(error)) {
485
515
  logger.info("Agent speech audio streaming aborted by the client");
486
516
  }
487
517
  else {
package/index.ts CHANGED
@@ -75,6 +75,20 @@ const createSessionBodySchema = z.object({
75
75
  triggerMessage: z.string().optional(),
76
76
  }).strict();
77
77
 
78
+ function isAbortError(error: unknown): boolean {
79
+ return (
80
+ error instanceof DOMException && error.name === "AbortError"
81
+ ) || (
82
+ typeof error === "object" &&
83
+ error !== null &&
84
+ "name" in error &&
85
+ (error.name === "AbortError" || error.name === "APIUserAbortError")
86
+ );
87
+ }
88
+
89
+ function getErrorMessage(error: unknown): string {
90
+ return error instanceof Error ? error.message : String(error);
91
+ }
78
92
 
79
93
  export default class AdminForthAgentPlugin extends AdminForthPlugin {
80
94
  options: PluginOptions;
@@ -176,6 +190,23 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
176
190
  hasAudioAdapter: Boolean(this.options.audioAdapter),
177
191
  }
178
192
  });
193
+ if (!this.adminforth.config.customization.customHeadItems) {
194
+ this.adminforth.config.customization.customHeadItems = [];
195
+ }
196
+ this.adminforth.config.customization.customHeadItems.push(
197
+ {
198
+ tagName: 'script',
199
+ attributes: {
200
+ src: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/ort.wasm.min.js'
201
+ }
202
+ },
203
+ {
204
+ tagName: 'script',
205
+ attributes: {
206
+ src: 'https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/bundle.min.js'
207
+ },
208
+ }
209
+ );
179
210
  if (!this.options.sessionResource) {
180
211
  throw new Error("sessionResource is required for AdminForthAgentPlugin");
181
212
  }
@@ -216,7 +247,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
216
247
 
217
248
  const userLanguage = await detectUserLanguage(selectedMode.completionAdapter, input.prompt, input.previousUserMessages)
218
249
  .catch((error) => {
219
- logger.warn(`Failed to detect user language: ${error.message}`);
250
+ if (input.abortSignal?.aborted || isAbortError(error)) {
251
+ throw error;
252
+ }
253
+
254
+ logger.warn(`Failed to detect user language: ${getErrorMessage(error)}`);
220
255
  return null;
221
256
  });
222
257
  const systemPrompt = buildAgentTurnSystemPrompt({
@@ -257,6 +292,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
257
292
  });
258
293
 
259
294
  for await (const rawChunk of stream as AsyncIterable<[any, any]>) {
295
+ if (input.abortSignal?.aborted) {
296
+ throw new DOMException("This operation was aborted", "AbortError");
297
+ }
298
+
260
299
  const [token, metadata] = rawChunk;
261
300
 
262
301
  const nodeName =
@@ -327,13 +366,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
327
366
  });
328
367
  fullResponse = agentResponse.text;
329
368
  } catch (error) {
330
- if (input.abortSignal?.aborted) {
369
+ if (input.abortSignal?.aborted || isAbortError(error)) {
331
370
  aborted = true;
332
371
  logger.info(input.abortLogMessage);
333
372
  } else {
334
373
  failed = true;
335
- logger.error(`${input.failureLogMessage}:\n${error.message}`);
336
- fullResponse = error.message;
374
+ fullResponse = getErrorMessage(error);
375
+ logger.error(`${input.failureLogMessage}:\n${fullResponse}`);
337
376
  input.emitErrorResponse?.(fullResponse);
338
377
  }
339
378
  }
@@ -437,13 +476,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
437
476
  abortSignal,
438
477
  });
439
478
  } catch (error) {
440
- if (abortSignal.aborted) {
479
+ if (abortSignal.aborted || isAbortError(error)) {
441
480
  logger.info("Agent speech transcription aborted by the client");
442
481
  stream.end();
443
482
  return null;
444
483
  }
445
484
 
446
- logger.error(`Agent speech transcription failed:\n${error.message}`);
485
+ logger.error(`Agent speech transcription failed:\n${getErrorMessage(error)}`);
447
486
  stream.error("Speech transcription failed. Check server logs for details.");
448
487
  stream.end();
449
488
  return null;
@@ -545,7 +584,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
545
584
  stream.end();
546
585
  return null;
547
586
  } catch (error) {
548
- if (abortSignal.aborted) {
587
+ if (abortSignal.aborted || isAbortError(error)) {
549
588
  logger.info("Agent speech audio streaming aborted by the client");
550
589
  } else {
551
590
  logger.error(`Agent speech audio streaming failed:\n${error}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.40.4",
3
+ "version": "1.41.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",