@adminforth/agent 1.40.4 → 1.41.0

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,093 bytes received 860 bytes 3,321,906.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
@@ -134,6 +134,20 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
134
134
  hasAudioAdapter: Boolean(this.options.audioAdapter),
135
135
  }
136
136
  });
137
+ if (!this.adminforth.config.customization.customHeadItems) {
138
+ this.adminforth.config.customization.customHeadItems = [];
139
+ }
140
+ this.adminforth.config.customization.customHeadItems.push({
141
+ tagName: 'script',
142
+ attributes: {
143
+ src: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/ort.wasm.min.js'
144
+ }
145
+ }, {
146
+ tagName: 'script',
147
+ attributes: {
148
+ src: 'https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/bundle.min.js'
149
+ },
150
+ });
137
151
  if (!this.options.sessionResource) {
138
152
  throw new Error("sessionResource is required for AdminForthAgentPlugin");
139
153
  }
package/index.ts CHANGED
@@ -176,6 +176,23 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
176
176
  hasAudioAdapter: Boolean(this.options.audioAdapter),
177
177
  }
178
178
  });
179
+ if (!this.adminforth.config.customization.customHeadItems) {
180
+ this.adminforth.config.customization.customHeadItems = [];
181
+ }
182
+ this.adminforth.config.customization.customHeadItems.push(
183
+ {
184
+ tagName: 'script',
185
+ attributes: {
186
+ src: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/ort.wasm.min.js'
187
+ }
188
+ },
189
+ {
190
+ tagName: 'script',
191
+ attributes: {
192
+ src: 'https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/bundle.min.js'
193
+ },
194
+ }
195
+ );
179
196
  if (!this.options.sessionResource) {
180
197
  throw new Error("sessionResource is required for AdminForthAgentPlugin");
181
198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.40.4",
3
+ "version": "1.41.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",