@adminforth/agent 1.43.3 → 1.43.4
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 +2 -2
- package/custom/composables/useAgentAudio.ts +20 -129
- package/custom/speech_recognition_frontend/MicrophoneButon.vue +0 -1
- package/dist/custom/composables/useAgentAudio.ts +20 -129
- package/dist/custom/speech_recognition_frontend/MicrophoneButon.vue +0 -1
- package/package.json +1 -1
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,
|
|
62
|
-
total size is 1,
|
|
61
|
+
sent 1,661,185 bytes received 860 bytes 3,324,090.00 bytes/sec
|
|
62
|
+
total size is 1,657,269 speedup is 1.00
|
|
@@ -14,118 +14,26 @@ type StreamingAudioState = {
|
|
|
14
14
|
isDone: boolean;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
let audioUnlockSourceUrl: string | null = null;
|
|
18
|
-
let audioUnlockInFlight: Promise<void> | null = null;
|
|
19
|
-
let isAudioPlaybackUnlocked = false;
|
|
20
17
|
let standByAudio: HTMLAudioElement | null = null;
|
|
21
18
|
let isStandByAudioPlaying = false;
|
|
22
|
-
|
|
23
|
-
function writeAsciiString(view: DataView, offset: number, value: string) {
|
|
24
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
25
|
-
view.setUint8(offset + index, value.charCodeAt(index));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function createSilentWavBlob(durationMs = 50) {
|
|
30
|
-
const sampleRate = 8000;
|
|
31
|
-
const bitsPerSample = 16;
|
|
32
|
-
const channelCount = 1;
|
|
33
|
-
const bytesPerSample = bitsPerSample / 8;
|
|
34
|
-
const sampleCount = Math.max(1, Math.round((sampleRate * durationMs) / 1000));
|
|
35
|
-
const pcmDataSize = sampleCount * channelCount * bytesPerSample;
|
|
36
|
-
const buffer = new ArrayBuffer(44 + pcmDataSize);
|
|
37
|
-
const view = new DataView(buffer);
|
|
38
|
-
|
|
39
|
-
writeAsciiString(view, 0, 'RIFF');
|
|
40
|
-
view.setUint32(4, 36 + pcmDataSize, true);
|
|
41
|
-
writeAsciiString(view, 8, 'WAVE');
|
|
42
|
-
writeAsciiString(view, 12, 'fmt ');
|
|
43
|
-
view.setUint32(16, 16, true);
|
|
44
|
-
view.setUint16(20, 1, true);
|
|
45
|
-
view.setUint16(22, channelCount, true);
|
|
46
|
-
view.setUint32(24, sampleRate, true);
|
|
47
|
-
view.setUint32(28, sampleRate * channelCount * bytesPerSample, true);
|
|
48
|
-
view.setUint16(32, channelCount * bytesPerSample, true);
|
|
49
|
-
view.setUint16(34, bitsPerSample, true);
|
|
50
|
-
writeAsciiString(view, 36, 'data');
|
|
51
|
-
view.setUint32(40, pcmDataSize, true);
|
|
52
|
-
|
|
53
|
-
return new Blob([buffer], { type: 'audio/wav' });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function getAudioUnlockSourceUrl() {
|
|
57
|
-
if (!audioUnlockSourceUrl) {
|
|
58
|
-
audioUnlockSourceUrl = URL.createObjectURL(createSilentWavBlob());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return audioUnlockSourceUrl;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function unlockAudioPlayback() {
|
|
65
|
-
if (isAudioPlaybackUnlocked) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (audioUnlockInFlight) {
|
|
70
|
-
await audioUnlockInFlight;
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
audioUnlockInFlight = (async () => {
|
|
75
|
-
const unlockAudio = new Audio(getAudioUnlockSourceUrl());
|
|
76
|
-
unlockAudio.muted = true;
|
|
77
|
-
unlockAudio.preload = 'auto';
|
|
78
|
-
unlockAudio.setAttribute('playsinline', '');
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
await unlockAudio.play();
|
|
82
|
-
unlockAudio.pause();
|
|
83
|
-
unlockAudio.currentTime = 0;
|
|
84
|
-
isAudioPlaybackUnlocked = true;
|
|
85
|
-
} catch (error) {
|
|
86
|
-
console.error('Failed to unlock audio playback:', error);
|
|
87
|
-
} finally {
|
|
88
|
-
unlockAudio.removeAttribute('src');
|
|
89
|
-
unlockAudio.load();
|
|
90
|
-
audioUnlockInFlight = null;
|
|
91
|
-
}
|
|
92
|
-
})();
|
|
93
|
-
|
|
94
|
-
await audioUnlockInFlight;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
19
|
async function playStandByAudio() {
|
|
98
20
|
if (!standByAudio) {
|
|
99
21
|
standByAudio = new Audio(`/plugins/AdminForthAgentPlugin/agentAudio/agent-processing.mp3`);
|
|
100
|
-
standByAudio.preload = 'auto';
|
|
101
|
-
standByAudio.setAttribute('playsinline', '');
|
|
102
22
|
standByAudio.addEventListener('ended', () => {
|
|
103
|
-
if (standByAudio
|
|
23
|
+
if (!standByAudio.paused) {
|
|
104
24
|
restartStandByAudio();
|
|
105
25
|
}
|
|
106
26
|
});
|
|
107
27
|
}
|
|
108
|
-
|
|
109
|
-
if (!standByAudio) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
28
|
standByAudio.currentTime = 0;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
await standByAudio.play();
|
|
117
|
-
isStandByAudioPlaying = true;
|
|
118
|
-
} catch (error) {
|
|
119
|
-
isStandByAudioPlaying = false;
|
|
120
|
-
console.error('Failed to play standby audio:', error);
|
|
121
|
-
}
|
|
29
|
+
await standByAudio.play();
|
|
30
|
+
isStandByAudioPlaying = true;
|
|
122
31
|
}
|
|
123
32
|
|
|
124
33
|
function stopStandByAudio() {
|
|
125
34
|
if (!standByAudio) {
|
|
126
35
|
return;
|
|
127
36
|
}
|
|
128
|
-
|
|
129
37
|
standByAudio.pause();
|
|
130
38
|
standByAudio.currentTime = 0;
|
|
131
39
|
isStandByAudioPlaying = false;
|
|
@@ -135,15 +43,15 @@ function restartStandByAudio() {
|
|
|
135
43
|
if (standByAudio) {
|
|
136
44
|
standByAudio.currentTime = 0;
|
|
137
45
|
}
|
|
138
|
-
|
|
139
|
-
void playStandByAudio();
|
|
46
|
+
playStandByAudio();
|
|
140
47
|
}
|
|
141
48
|
|
|
49
|
+
|
|
142
50
|
export const useAgentAudio = defineStore('agentAudio', () => {
|
|
143
51
|
const agentStore = useAgentStore();
|
|
144
|
-
const agentAudioMode = ref<'transcribing' | 'streaming' | 'fetchingAudio' | 'playingAgentResponse' | 'readyToRespond'>('readyToRespond');
|
|
52
|
+
const agentAudioMode = ref<'transcribing' | 'streaming' | 'fetchingAudio' | 'playingAgentResponse' | 'readyToRespond' >('readyToRespond');
|
|
145
53
|
const isStreamingResponse = ref(false);
|
|
146
|
-
|
|
54
|
+
|
|
147
55
|
let currentAbortController: AbortController | null = null;
|
|
148
56
|
let isPlaying = false;
|
|
149
57
|
let currentAudio: HTMLAudioElement | null = null;
|
|
@@ -173,7 +81,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
173
81
|
formData.append('timeZone', Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
174
82
|
formData.append('currentPage', JSON.stringify(getCurrentPageContext()));
|
|
175
83
|
const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}/adminapi/v1/agent/speech-response`;
|
|
176
|
-
|
|
177
84
|
try {
|
|
178
85
|
agentAudioMode.value = 'transcribing';
|
|
179
86
|
const res = await fetch(fullPath, {
|
|
@@ -184,23 +91,22 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
184
91
|
},
|
|
185
92
|
signal: currentAbortController!.signal,
|
|
186
93
|
});
|
|
187
|
-
|
|
188
94
|
isStreamingResponse.value = true;
|
|
189
|
-
|
|
190
95
|
if (res.ok) {
|
|
191
96
|
agentAudioMode.value = 'streaming';
|
|
192
97
|
await readSpeechResponseStream(res);
|
|
193
98
|
} else {
|
|
194
99
|
console.error('Failed to transcribe audio:', res.statusText);
|
|
195
|
-
adminforth.alert({
|
|
100
|
+
adminforth.alert({message: 'Failed to transcribe audio', variant: 'danger'});
|
|
196
101
|
}
|
|
197
102
|
} catch (error) {
|
|
198
|
-
if (
|
|
103
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
104
|
+
//
|
|
105
|
+
} else {
|
|
199
106
|
console.error('Error sending audio to server:', error);
|
|
200
107
|
}
|
|
201
108
|
} finally {
|
|
202
109
|
isStreamingResponse.value = false;
|
|
203
|
-
|
|
204
110
|
if (!wasAudioResponseReceived) {
|
|
205
111
|
setAudioModeReadyToRespond();
|
|
206
112
|
}
|
|
@@ -209,9 +115,8 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
209
115
|
|
|
210
116
|
async function readSpeechResponseStream(res: Response) {
|
|
211
117
|
const reader = res.body?.getReader();
|
|
212
|
-
|
|
213
118
|
if (!reader) {
|
|
214
|
-
adminforth.alert({
|
|
119
|
+
adminforth.alert({message: 'Speech response stream is not available', variant: 'danger'});
|
|
215
120
|
return;
|
|
216
121
|
}
|
|
217
122
|
|
|
@@ -219,7 +124,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
219
124
|
let buffer = '';
|
|
220
125
|
|
|
221
126
|
agentStore.setCurrentChatStatus('streaming');
|
|
222
|
-
|
|
223
127
|
try {
|
|
224
128
|
while (true) {
|
|
225
129
|
const { value, done } = await reader.read();
|
|
@@ -254,7 +158,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
254
158
|
if (currentAbortController?.signal.aborted) {
|
|
255
159
|
return;
|
|
256
160
|
}
|
|
257
|
-
|
|
258
161
|
const data = eventBlock
|
|
259
162
|
.split('\n')
|
|
260
163
|
.filter((line) => line.startsWith('data:'))
|
|
@@ -268,6 +171,7 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
268
171
|
const event = JSON.parse(data) as SpeechStreamEvent;
|
|
269
172
|
|
|
270
173
|
if (event.type === 'error') {
|
|
174
|
+
|
|
271
175
|
return;
|
|
272
176
|
}
|
|
273
177
|
|
|
@@ -305,9 +209,8 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
305
209
|
|
|
306
210
|
if (event.type === 'data-tool-call') {
|
|
307
211
|
if (!isStandByAudioPlaying) {
|
|
308
|
-
|
|
212
|
+
playStandByAudio();
|
|
309
213
|
}
|
|
310
|
-
|
|
311
214
|
agentStore.addDataToolCallMessage(event.data);
|
|
312
215
|
}
|
|
313
216
|
}
|
|
@@ -324,16 +227,10 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
324
227
|
currentAudio.currentTime = 0;
|
|
325
228
|
return;
|
|
326
229
|
}
|
|
327
|
-
|
|
328
230
|
agentAudioMode.value = 'playingAgentResponse';
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
await currentAudio.play();
|
|
332
|
-
} catch (error) {
|
|
333
|
-
isPlaying = false;
|
|
334
|
-
setAudioModeReadyToRespond();
|
|
231
|
+
await void currentAudio.play().catch((error) => {
|
|
335
232
|
console.error('Failed to play audio:', error);
|
|
336
|
-
}
|
|
233
|
+
});
|
|
337
234
|
}
|
|
338
235
|
|
|
339
236
|
function initializeAudioStream(mimeType: string) {
|
|
@@ -347,8 +244,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
347
244
|
const mediaSource = new MediaSource();
|
|
348
245
|
currentAudioObjectUrl = URL.createObjectURL(mediaSource);
|
|
349
246
|
currentAudio = new Audio(currentAudioObjectUrl);
|
|
350
|
-
currentAudio.preload = 'auto';
|
|
351
|
-
currentAudio.setAttribute('playsinline', '');
|
|
352
247
|
currentAudio.addEventListener('ended', handleAudioEnded, { once: true });
|
|
353
248
|
currentStreamingAudio = {
|
|
354
249
|
mimeType,
|
|
@@ -404,7 +299,7 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
404
299
|
|
|
405
300
|
if (!currentStreamingAudio.hasStartedPlayback) {
|
|
406
301
|
currentStreamingAudio.hasStartedPlayback = true;
|
|
407
|
-
|
|
302
|
+
setIsPlaying(true);
|
|
408
303
|
}
|
|
409
304
|
|
|
410
305
|
return;
|
|
@@ -461,7 +356,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
461
356
|
bufferedAudioMimeType = 'audio/mpeg';
|
|
462
357
|
detachStreamingAudio();
|
|
463
358
|
destroyCurrentAudioElement();
|
|
464
|
-
|
|
465
359
|
if (!dontResetMode) {
|
|
466
360
|
setAudioModeReadyToRespond();
|
|
467
361
|
}
|
|
@@ -475,10 +369,8 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
475
369
|
function playAudioChunks(chunks: ArrayBuffer[], mimeType: string) {
|
|
476
370
|
currentAudioObjectUrl = URL.createObjectURL(new Blob(chunks, { type: mimeType }));
|
|
477
371
|
currentAudio = new Audio(currentAudioObjectUrl);
|
|
478
|
-
currentAudio.preload = 'auto';
|
|
479
|
-
currentAudio.setAttribute('playsinline', '');
|
|
480
372
|
currentAudio.addEventListener('ended', handleAudioEnded, { once: true });
|
|
481
|
-
|
|
373
|
+
setIsPlaying(true);
|
|
482
374
|
}
|
|
483
375
|
|
|
484
376
|
function base64ToArrayBuffer(base64: string) {
|
|
@@ -515,9 +407,8 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
515
407
|
sendAudioToServerAndHandleResponse,
|
|
516
408
|
stopGenerationAndAudio,
|
|
517
409
|
stopCurrentAudioPlayback,
|
|
518
|
-
unlockAudioPlayback,
|
|
519
410
|
playBeep,
|
|
520
|
-
agentAudioMode
|
|
521
|
-
playStandByAudio,
|
|
411
|
+
agentAudioMode
|
|
522
412
|
};
|
|
413
|
+
|
|
523
414
|
});
|
|
@@ -111,7 +111,6 @@ function toggleChatMode() {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
async function onStartRecording() {
|
|
114
|
-
await agentAudio.unlockAudioPlayback();
|
|
115
114
|
await requestMicAndStartVAD(saidSomething, stopRecording, onAnySound);
|
|
116
115
|
microphoneButtonMode.value = 'listen';
|
|
117
116
|
agentAudio.playBeep(1000);
|
|
@@ -14,118 +14,26 @@ type StreamingAudioState = {
|
|
|
14
14
|
isDone: boolean;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
let audioUnlockSourceUrl: string | null = null;
|
|
18
|
-
let audioUnlockInFlight: Promise<void> | null = null;
|
|
19
|
-
let isAudioPlaybackUnlocked = false;
|
|
20
17
|
let standByAudio: HTMLAudioElement | null = null;
|
|
21
18
|
let isStandByAudioPlaying = false;
|
|
22
|
-
|
|
23
|
-
function writeAsciiString(view: DataView, offset: number, value: string) {
|
|
24
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
25
|
-
view.setUint8(offset + index, value.charCodeAt(index));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function createSilentWavBlob(durationMs = 50) {
|
|
30
|
-
const sampleRate = 8000;
|
|
31
|
-
const bitsPerSample = 16;
|
|
32
|
-
const channelCount = 1;
|
|
33
|
-
const bytesPerSample = bitsPerSample / 8;
|
|
34
|
-
const sampleCount = Math.max(1, Math.round((sampleRate * durationMs) / 1000));
|
|
35
|
-
const pcmDataSize = sampleCount * channelCount * bytesPerSample;
|
|
36
|
-
const buffer = new ArrayBuffer(44 + pcmDataSize);
|
|
37
|
-
const view = new DataView(buffer);
|
|
38
|
-
|
|
39
|
-
writeAsciiString(view, 0, 'RIFF');
|
|
40
|
-
view.setUint32(4, 36 + pcmDataSize, true);
|
|
41
|
-
writeAsciiString(view, 8, 'WAVE');
|
|
42
|
-
writeAsciiString(view, 12, 'fmt ');
|
|
43
|
-
view.setUint32(16, 16, true);
|
|
44
|
-
view.setUint16(20, 1, true);
|
|
45
|
-
view.setUint16(22, channelCount, true);
|
|
46
|
-
view.setUint32(24, sampleRate, true);
|
|
47
|
-
view.setUint32(28, sampleRate * channelCount * bytesPerSample, true);
|
|
48
|
-
view.setUint16(32, channelCount * bytesPerSample, true);
|
|
49
|
-
view.setUint16(34, bitsPerSample, true);
|
|
50
|
-
writeAsciiString(view, 36, 'data');
|
|
51
|
-
view.setUint32(40, pcmDataSize, true);
|
|
52
|
-
|
|
53
|
-
return new Blob([buffer], { type: 'audio/wav' });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function getAudioUnlockSourceUrl() {
|
|
57
|
-
if (!audioUnlockSourceUrl) {
|
|
58
|
-
audioUnlockSourceUrl = URL.createObjectURL(createSilentWavBlob());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return audioUnlockSourceUrl;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function unlockAudioPlayback() {
|
|
65
|
-
if (isAudioPlaybackUnlocked) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (audioUnlockInFlight) {
|
|
70
|
-
await audioUnlockInFlight;
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
audioUnlockInFlight = (async () => {
|
|
75
|
-
const unlockAudio = new Audio(getAudioUnlockSourceUrl());
|
|
76
|
-
unlockAudio.muted = true;
|
|
77
|
-
unlockAudio.preload = 'auto';
|
|
78
|
-
unlockAudio.setAttribute('playsinline', '');
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
await unlockAudio.play();
|
|
82
|
-
unlockAudio.pause();
|
|
83
|
-
unlockAudio.currentTime = 0;
|
|
84
|
-
isAudioPlaybackUnlocked = true;
|
|
85
|
-
} catch (error) {
|
|
86
|
-
console.error('Failed to unlock audio playback:', error);
|
|
87
|
-
} finally {
|
|
88
|
-
unlockAudio.removeAttribute('src');
|
|
89
|
-
unlockAudio.load();
|
|
90
|
-
audioUnlockInFlight = null;
|
|
91
|
-
}
|
|
92
|
-
})();
|
|
93
|
-
|
|
94
|
-
await audioUnlockInFlight;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
19
|
async function playStandByAudio() {
|
|
98
20
|
if (!standByAudio) {
|
|
99
21
|
standByAudio = new Audio(`/plugins/AdminForthAgentPlugin/agentAudio/agent-processing.mp3`);
|
|
100
|
-
standByAudio.preload = 'auto';
|
|
101
|
-
standByAudio.setAttribute('playsinline', '');
|
|
102
22
|
standByAudio.addEventListener('ended', () => {
|
|
103
|
-
if (standByAudio
|
|
23
|
+
if (!standByAudio.paused) {
|
|
104
24
|
restartStandByAudio();
|
|
105
25
|
}
|
|
106
26
|
});
|
|
107
27
|
}
|
|
108
|
-
|
|
109
|
-
if (!standByAudio) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
28
|
standByAudio.currentTime = 0;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
await standByAudio.play();
|
|
117
|
-
isStandByAudioPlaying = true;
|
|
118
|
-
} catch (error) {
|
|
119
|
-
isStandByAudioPlaying = false;
|
|
120
|
-
console.error('Failed to play standby audio:', error);
|
|
121
|
-
}
|
|
29
|
+
await standByAudio.play();
|
|
30
|
+
isStandByAudioPlaying = true;
|
|
122
31
|
}
|
|
123
32
|
|
|
124
33
|
function stopStandByAudio() {
|
|
125
34
|
if (!standByAudio) {
|
|
126
35
|
return;
|
|
127
36
|
}
|
|
128
|
-
|
|
129
37
|
standByAudio.pause();
|
|
130
38
|
standByAudio.currentTime = 0;
|
|
131
39
|
isStandByAudioPlaying = false;
|
|
@@ -135,15 +43,15 @@ function restartStandByAudio() {
|
|
|
135
43
|
if (standByAudio) {
|
|
136
44
|
standByAudio.currentTime = 0;
|
|
137
45
|
}
|
|
138
|
-
|
|
139
|
-
void playStandByAudio();
|
|
46
|
+
playStandByAudio();
|
|
140
47
|
}
|
|
141
48
|
|
|
49
|
+
|
|
142
50
|
export const useAgentAudio = defineStore('agentAudio', () => {
|
|
143
51
|
const agentStore = useAgentStore();
|
|
144
|
-
const agentAudioMode = ref<'transcribing' | 'streaming' | 'fetchingAudio' | 'playingAgentResponse' | 'readyToRespond'>('readyToRespond');
|
|
52
|
+
const agentAudioMode = ref<'transcribing' | 'streaming' | 'fetchingAudio' | 'playingAgentResponse' | 'readyToRespond' >('readyToRespond');
|
|
145
53
|
const isStreamingResponse = ref(false);
|
|
146
|
-
|
|
54
|
+
|
|
147
55
|
let currentAbortController: AbortController | null = null;
|
|
148
56
|
let isPlaying = false;
|
|
149
57
|
let currentAudio: HTMLAudioElement | null = null;
|
|
@@ -173,7 +81,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
173
81
|
formData.append('timeZone', Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
174
82
|
formData.append('currentPage', JSON.stringify(getCurrentPageContext()));
|
|
175
83
|
const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}/adminapi/v1/agent/speech-response`;
|
|
176
|
-
|
|
177
84
|
try {
|
|
178
85
|
agentAudioMode.value = 'transcribing';
|
|
179
86
|
const res = await fetch(fullPath, {
|
|
@@ -184,23 +91,22 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
184
91
|
},
|
|
185
92
|
signal: currentAbortController!.signal,
|
|
186
93
|
});
|
|
187
|
-
|
|
188
94
|
isStreamingResponse.value = true;
|
|
189
|
-
|
|
190
95
|
if (res.ok) {
|
|
191
96
|
agentAudioMode.value = 'streaming';
|
|
192
97
|
await readSpeechResponseStream(res);
|
|
193
98
|
} else {
|
|
194
99
|
console.error('Failed to transcribe audio:', res.statusText);
|
|
195
|
-
adminforth.alert({
|
|
100
|
+
adminforth.alert({message: 'Failed to transcribe audio', variant: 'danger'});
|
|
196
101
|
}
|
|
197
102
|
} catch (error) {
|
|
198
|
-
if (
|
|
103
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
104
|
+
//
|
|
105
|
+
} else {
|
|
199
106
|
console.error('Error sending audio to server:', error);
|
|
200
107
|
}
|
|
201
108
|
} finally {
|
|
202
109
|
isStreamingResponse.value = false;
|
|
203
|
-
|
|
204
110
|
if (!wasAudioResponseReceived) {
|
|
205
111
|
setAudioModeReadyToRespond();
|
|
206
112
|
}
|
|
@@ -209,9 +115,8 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
209
115
|
|
|
210
116
|
async function readSpeechResponseStream(res: Response) {
|
|
211
117
|
const reader = res.body?.getReader();
|
|
212
|
-
|
|
213
118
|
if (!reader) {
|
|
214
|
-
adminforth.alert({
|
|
119
|
+
adminforth.alert({message: 'Speech response stream is not available', variant: 'danger'});
|
|
215
120
|
return;
|
|
216
121
|
}
|
|
217
122
|
|
|
@@ -219,7 +124,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
219
124
|
let buffer = '';
|
|
220
125
|
|
|
221
126
|
agentStore.setCurrentChatStatus('streaming');
|
|
222
|
-
|
|
223
127
|
try {
|
|
224
128
|
while (true) {
|
|
225
129
|
const { value, done } = await reader.read();
|
|
@@ -254,7 +158,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
254
158
|
if (currentAbortController?.signal.aborted) {
|
|
255
159
|
return;
|
|
256
160
|
}
|
|
257
|
-
|
|
258
161
|
const data = eventBlock
|
|
259
162
|
.split('\n')
|
|
260
163
|
.filter((line) => line.startsWith('data:'))
|
|
@@ -268,6 +171,7 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
268
171
|
const event = JSON.parse(data) as SpeechStreamEvent;
|
|
269
172
|
|
|
270
173
|
if (event.type === 'error') {
|
|
174
|
+
|
|
271
175
|
return;
|
|
272
176
|
}
|
|
273
177
|
|
|
@@ -305,9 +209,8 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
305
209
|
|
|
306
210
|
if (event.type === 'data-tool-call') {
|
|
307
211
|
if (!isStandByAudioPlaying) {
|
|
308
|
-
|
|
212
|
+
playStandByAudio();
|
|
309
213
|
}
|
|
310
|
-
|
|
311
214
|
agentStore.addDataToolCallMessage(event.data);
|
|
312
215
|
}
|
|
313
216
|
}
|
|
@@ -324,16 +227,10 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
324
227
|
currentAudio.currentTime = 0;
|
|
325
228
|
return;
|
|
326
229
|
}
|
|
327
|
-
|
|
328
230
|
agentAudioMode.value = 'playingAgentResponse';
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
await currentAudio.play();
|
|
332
|
-
} catch (error) {
|
|
333
|
-
isPlaying = false;
|
|
334
|
-
setAudioModeReadyToRespond();
|
|
231
|
+
await void currentAudio.play().catch((error) => {
|
|
335
232
|
console.error('Failed to play audio:', error);
|
|
336
|
-
}
|
|
233
|
+
});
|
|
337
234
|
}
|
|
338
235
|
|
|
339
236
|
function initializeAudioStream(mimeType: string) {
|
|
@@ -347,8 +244,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
347
244
|
const mediaSource = new MediaSource();
|
|
348
245
|
currentAudioObjectUrl = URL.createObjectURL(mediaSource);
|
|
349
246
|
currentAudio = new Audio(currentAudioObjectUrl);
|
|
350
|
-
currentAudio.preload = 'auto';
|
|
351
|
-
currentAudio.setAttribute('playsinline', '');
|
|
352
247
|
currentAudio.addEventListener('ended', handleAudioEnded, { once: true });
|
|
353
248
|
currentStreamingAudio = {
|
|
354
249
|
mimeType,
|
|
@@ -404,7 +299,7 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
404
299
|
|
|
405
300
|
if (!currentStreamingAudio.hasStartedPlayback) {
|
|
406
301
|
currentStreamingAudio.hasStartedPlayback = true;
|
|
407
|
-
|
|
302
|
+
setIsPlaying(true);
|
|
408
303
|
}
|
|
409
304
|
|
|
410
305
|
return;
|
|
@@ -461,7 +356,6 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
461
356
|
bufferedAudioMimeType = 'audio/mpeg';
|
|
462
357
|
detachStreamingAudio();
|
|
463
358
|
destroyCurrentAudioElement();
|
|
464
|
-
|
|
465
359
|
if (!dontResetMode) {
|
|
466
360
|
setAudioModeReadyToRespond();
|
|
467
361
|
}
|
|
@@ -475,10 +369,8 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
475
369
|
function playAudioChunks(chunks: ArrayBuffer[], mimeType: string) {
|
|
476
370
|
currentAudioObjectUrl = URL.createObjectURL(new Blob(chunks, { type: mimeType }));
|
|
477
371
|
currentAudio = new Audio(currentAudioObjectUrl);
|
|
478
|
-
currentAudio.preload = 'auto';
|
|
479
|
-
currentAudio.setAttribute('playsinline', '');
|
|
480
372
|
currentAudio.addEventListener('ended', handleAudioEnded, { once: true });
|
|
481
|
-
|
|
373
|
+
setIsPlaying(true);
|
|
482
374
|
}
|
|
483
375
|
|
|
484
376
|
function base64ToArrayBuffer(base64: string) {
|
|
@@ -515,9 +407,8 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
515
407
|
sendAudioToServerAndHandleResponse,
|
|
516
408
|
stopGenerationAndAudio,
|
|
517
409
|
stopCurrentAudioPlayback,
|
|
518
|
-
unlockAudioPlayback,
|
|
519
410
|
playBeep,
|
|
520
|
-
agentAudioMode
|
|
521
|
-
playStandByAudio,
|
|
411
|
+
agentAudioMode
|
|
522
412
|
};
|
|
413
|
+
|
|
523
414
|
});
|
|
@@ -111,7 +111,6 @@ function toggleChatMode() {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
async function onStartRecording() {
|
|
114
|
-
await agentAudio.unlockAudioPlayback();
|
|
115
114
|
await requestMicAndStartVAD(saidSomething, stopRecording, onAnySound);
|
|
116
115
|
microphoneButtonMode.value = 'listen';
|
|
117
116
|
agentAudio.playBeep(1000);
|