@adminforth/agent 1.37.0 → 1.39.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.
Files changed (55) hide show
  1. package/agent/languageDetect.ts +0 -8
  2. package/agent/simpleAgent.ts +5 -5
  3. package/agent/systemPrompt.ts +35 -4
  4. package/agent/toolCallEvents.ts +31 -2
  5. package/agent/tools/apiTool.ts +1 -1
  6. package/agentResponseEvents.ts +197 -0
  7. package/apiBasedTools.ts +118 -284
  8. package/build.log +13 -2
  9. package/custom/ChatSurface.vue +31 -21
  10. package/custom/composables/agentStore/constants.ts +8 -1
  11. package/custom/composables/agentStore/useAgentSessions.ts +85 -12
  12. package/custom/composables/useAgentAudio.ts +401 -0
  13. package/custom/composables/useAgentStore.ts +52 -5
  14. package/custom/conversation_area/ConversationArea.vue +1 -1
  15. package/custom/conversation_area/MessageRenderer.vue +12 -1
  16. package/custom/conversation_area/SystemMessageRenderer.vue +28 -0
  17. package/custom/conversation_area/TextRenderer.vue +4 -3
  18. package/custom/conversation_area/ToolRenderer.vue +1 -1
  19. package/custom/package.json +2 -1
  20. package/custom/pnpm-lock.yaml +29 -0
  21. package/custom/public/agentAudio/agent-processing.mp3 +0 -0
  22. package/custom/speech_recognition_frontend/AudioLines.vue +97 -0
  23. package/custom/speech_recognition_frontend/MicrophoneButon.vue +157 -0
  24. package/custom/speech_recognition_frontend/types/voice-activity-detection.d.ts +22 -0
  25. package/custom/speech_recognition_frontend/voiceActivityDetection.ts +151 -0
  26. package/custom/types.ts +52 -2
  27. package/dist/agent/languageDetect.js +0 -6
  28. package/dist/agent/simpleAgent.js +4 -3
  29. package/dist/agent/systemPrompt.js +24 -3
  30. package/dist/agent/toolCallEvents.js +24 -2
  31. package/dist/agent/tools/apiTool.js +1 -1
  32. package/dist/agentResponseEvents.js +141 -0
  33. package/dist/apiBasedTools.js +95 -211
  34. package/dist/custom/ChatSurface.vue +31 -21
  35. package/dist/custom/composables/agentStore/constants.ts +8 -1
  36. package/dist/custom/composables/agentStore/useAgentSessions.ts +85 -12
  37. package/dist/custom/composables/useAgentAudio.ts +401 -0
  38. package/dist/custom/composables/useAgentStore.ts +52 -5
  39. package/dist/custom/conversation_area/ConversationArea.vue +1 -1
  40. package/dist/custom/conversation_area/MessageRenderer.vue +12 -1
  41. package/dist/custom/conversation_area/SystemMessageRenderer.vue +28 -0
  42. package/dist/custom/conversation_area/TextRenderer.vue +4 -3
  43. package/dist/custom/conversation_area/ToolRenderer.vue +1 -1
  44. package/dist/custom/package.json +2 -1
  45. package/dist/custom/pnpm-lock.yaml +29 -0
  46. package/dist/custom/public/agentAudio/agent-processing.mp3 +0 -0
  47. package/dist/custom/speech_recognition_frontend/AudioLines.vue +97 -0
  48. package/dist/custom/speech_recognition_frontend/MicrophoneButon.vue +157 -0
  49. package/dist/custom/speech_recognition_frontend/types/voice-activity-detection.d.ts +22 -0
  50. package/dist/custom/speech_recognition_frontend/voiceActivityDetection.ts +151 -0
  51. package/dist/custom/types.ts +52 -2
  52. package/dist/index.js +290 -400
  53. package/index.ts +318 -492
  54. package/package.json +3 -2
  55. package/types.ts +1 -1
@@ -0,0 +1,401 @@
1
+ import adminforth from "@/adminforth";
2
+ import { useAgentStore } from "./useAgentStore";
3
+ import { defineStore } from 'pinia';
4
+ import type { SpeechStreamEvent } from '../types';
5
+ import { ref } from 'vue';
6
+ import { getCurrentPageContext } from './agentStore/pageContext';
7
+
8
+ type StreamingAudioState = {
9
+ mimeType: string;
10
+ mediaSource: MediaSource;
11
+ sourceBuffer: SourceBuffer | null;
12
+ pendingChunks: ArrayBuffer[];
13
+ hasStartedPlayback: boolean;
14
+ isDone: boolean;
15
+ };
16
+
17
+ let standByAudio: HTMLAudioElement | null = null;
18
+
19
+ function playStandByAudio() {
20
+ if (!standByAudio) {
21
+ standByAudio = new Audio(`/plugins/AdminForthAgentPlugin/agentAudio/agent-processing.mp3`);
22
+ standByAudio.addEventListener('ended', () => {
23
+ if (!standByAudio.paused) {
24
+ restartStandByAudio();
25
+ }
26
+ });
27
+ }
28
+ standByAudio.currentTime = 0;
29
+ standByAudio.play()
30
+ }
31
+
32
+ function stopStandByAudio() {
33
+ if (!standByAudio) {
34
+ return;
35
+ }
36
+ standByAudio.pause();
37
+ standByAudio.currentTime = 0;
38
+ }
39
+
40
+ function restartStandByAudio() {
41
+ if (standByAudio) {
42
+ standByAudio.currentTime = 0;
43
+ }
44
+ playStandByAudio();
45
+ }
46
+
47
+
48
+ export const useAgentAudio = defineStore('agentAudio', () => {
49
+ const agentStore = useAgentStore();
50
+ const agentAudioMode = ref<'transcribing' | 'streaming' | 'fetchingAudio' | 'playingAgentResponse' | null>(null);
51
+ const isStreamingResponse = ref(false);
52
+
53
+ let currentAbortController: AbortController | null = null;
54
+ let isPlaying = false;
55
+ let currentAudio: HTMLAudioElement | null = null;
56
+ let currentAudioObjectUrl: string | null = null;
57
+ let currentStreamingAudio: StreamingAudioState | null = null;
58
+ let bufferedAudioChunks: ArrayBuffer[] = [];
59
+ let bufferedAudioMimeType = 'audio/mpeg';
60
+
61
+ function stopGenerationAndAudio() {
62
+ agentAudioMode.value = null;
63
+ stopCurrentAudioPlayback();
64
+ currentAbortController?.abort();
65
+ }
66
+
67
+ async function sendAudioToServerAndHandleResponse(blob: Blob) {
68
+ currentAbortController = new AbortController();
69
+ const formData = new FormData();
70
+ formData.append('file', blob, 'user_prompt.webm');
71
+ formData.append('sessionId', agentStore.activeSessionId);
72
+ formData.append('mode', agentStore.activeModeName ?? '');
73
+ formData.append('timeZone', Intl.DateTimeFormat().resolvedOptions().timeZone);
74
+ formData.append('currentPage', JSON.stringify(getCurrentPageContext()));
75
+ const fullPath = `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}/adminapi/v1/agent/speech-response`;
76
+ try {
77
+ agentAudioMode.value = 'transcribing';
78
+ const res = await fetch(fullPath, {
79
+ method: 'POST',
80
+ body: formData,
81
+ headers: {
82
+ Accept: 'text/event-stream',
83
+ },
84
+ signal: currentAbortController!.signal,
85
+ });
86
+ isStreamingResponse.value = true;
87
+ if (res.ok) {
88
+ agentAudioMode.value = 'streaming';
89
+ await readSpeechResponseStream(res);
90
+ } else {
91
+ console.error('Failed to transcribe audio:', res.statusText);
92
+ adminforth.alert({message: 'Failed to transcribe audio', variant: 'danger'});
93
+ }
94
+ } catch (error) {
95
+ if (error instanceof DOMException && error.name === 'AbortError') {
96
+ //
97
+ } else {
98
+ console.error('Error sending audio to server:', error);
99
+ }
100
+ } finally {
101
+ isStreamingResponse.value = false;
102
+ agentAudioMode.value = null;
103
+ }
104
+ }
105
+
106
+ async function readSpeechResponseStream(res: Response) {
107
+ const reader = res.body?.getReader();
108
+ if (!reader) {
109
+ adminforth.alert({message: 'Speech response stream is not available', variant: 'danger'});
110
+ return;
111
+ }
112
+
113
+ const decoder = new TextDecoder();
114
+ let buffer = '';
115
+
116
+ agentStore.setCurrentChatStatus('streaming');
117
+ try {
118
+ while (true) {
119
+ const { value, done } = await reader.read();
120
+
121
+ if (done) {
122
+ break;
123
+ }
124
+
125
+ buffer += decoder.decode(value, { stream: true });
126
+ const eventBlocks = buffer.split('\n\n');
127
+ buffer = eventBlocks.pop() ?? '';
128
+
129
+ for (const eventBlock of eventBlocks) {
130
+ await handleSpeechStreamEvent(eventBlock);
131
+ }
132
+ }
133
+
134
+ buffer += decoder.decode();
135
+
136
+ if (buffer.trim()) {
137
+ await handleSpeechStreamEvent(buffer);
138
+ }
139
+
140
+ finishAudioStream();
141
+ } finally {
142
+ reader.releaseLock();
143
+ agentStore.setCurrentChatStatus('ready');
144
+ }
145
+ }
146
+
147
+ async function handleSpeechStreamEvent(eventBlock: string) {
148
+ if (currentAbortController?.signal.aborted) {
149
+ return;
150
+ }
151
+ const data = eventBlock
152
+ .split('\n')
153
+ .filter((line) => line.startsWith('data:'))
154
+ .map((line) => line.slice(5).trimStart())
155
+ .join('\n');
156
+
157
+ if (!data || data === '[DONE]') {
158
+ return;
159
+ }
160
+
161
+ const event = JSON.parse(data) as SpeechStreamEvent;
162
+
163
+ if (event.type === 'error') {
164
+
165
+ return;
166
+ }
167
+
168
+ if (event.type === 'transcript') {
169
+ agentStore.addUserMessage(event.data.text);
170
+ agentStore.updateLastAgentMessage('');
171
+ return;
172
+ }
173
+
174
+ if (event.type === 'speech-response') {
175
+ stopStandByAudio();
176
+ agentStore.setCurrentChatStatus('ready');
177
+ agentStore.addAgentMessage(event.data.response.text);
178
+ agentAudioMode.value = 'playingAgentResponse';
179
+ return;
180
+ }
181
+
182
+ if (event.type === 'audio-start') {
183
+ isStreamingResponse.value = false;
184
+ agentAudioMode.value = 'fetchingAudio';
185
+ initializeAudioStream(event.data.mimeType);
186
+ return;
187
+ }
188
+
189
+ if (event.type === 'audio-delta') {
190
+ appendAudioChunk(event.data.base64);
191
+ return;
192
+ }
193
+
194
+ if (event.type === 'audio-done') {
195
+ finishAudioStream();
196
+ return;
197
+ }
198
+
199
+ if (event.type === 'data-tool-call') {
200
+ playStandByAudio();
201
+ agentStore.addDataToolCallMessage(event.data);
202
+ }
203
+ }
204
+
205
+ function setIsPlaying(value: boolean) {
206
+ isPlaying = value;
207
+
208
+ if (!currentAudio) {
209
+ return;
210
+ }
211
+
212
+ if (!isPlaying) {
213
+ currentAudio.pause();
214
+ currentAudio.currentTime = 0;
215
+ return;
216
+ }
217
+ agentAudioMode.value = 'playingAgentResponse';
218
+ void currentAudio.play().catch((error) => {
219
+ console.error('Failed to play audio:', error);
220
+ });
221
+ }
222
+
223
+ function initializeAudioStream(mimeType: string) {
224
+ stopCurrentAudioPlayback();
225
+ bufferedAudioMimeType = mimeType;
226
+
227
+ if (typeof MediaSource === 'undefined' || !MediaSource.isTypeSupported(mimeType)) {
228
+ return;
229
+ }
230
+
231
+ const mediaSource = new MediaSource();
232
+ currentAudioObjectUrl = URL.createObjectURL(mediaSource);
233
+ currentAudio = new Audio(currentAudioObjectUrl);
234
+ currentAudio.addEventListener('ended', handleAudioEnded, { once: true });
235
+ currentStreamingAudio = {
236
+ mimeType,
237
+ mediaSource,
238
+ sourceBuffer: null,
239
+ pendingChunks: [],
240
+ hasStartedPlayback: false,
241
+ isDone: false,
242
+ };
243
+
244
+ mediaSource.addEventListener('sourceopen', handleMediaSourceOpen, { once: true });
245
+ }
246
+
247
+ function handleMediaSourceOpen() {
248
+ if (!currentStreamingAudio) {
249
+ return;
250
+ }
251
+
252
+ try {
253
+ currentStreamingAudio.sourceBuffer = currentStreamingAudio.mediaSource.addSourceBuffer(currentStreamingAudio.mimeType);
254
+ currentStreamingAudio.sourceBuffer.mode = 'sequence';
255
+ currentStreamingAudio.sourceBuffer.addEventListener('updateend', flushStreamingAudioQueue);
256
+ flushStreamingAudioQueue();
257
+ } catch (error) {
258
+ console.error('Failed to initialize streaming audio playback:', error);
259
+ bufferedAudioChunks.push(...currentStreamingAudio.pendingChunks);
260
+ detachStreamingAudio();
261
+ destroyCurrentAudioElement();
262
+ }
263
+ }
264
+
265
+ function appendAudioChunk(base64: string) {
266
+ const chunk = base64ToArrayBuffer(base64);
267
+
268
+ if (!currentStreamingAudio) {
269
+ bufferedAudioChunks.push(chunk);
270
+ return;
271
+ }
272
+
273
+ currentStreamingAudio.pendingChunks.push(chunk);
274
+ flushStreamingAudioQueue();
275
+ }
276
+
277
+ function flushStreamingAudioQueue() {
278
+ if (!currentStreamingAudio?.sourceBuffer || currentStreamingAudio.sourceBuffer.updating) {
279
+ return;
280
+ }
281
+
282
+ const nextChunk = currentStreamingAudio.pendingChunks.shift();
283
+
284
+ if (nextChunk) {
285
+ currentStreamingAudio.sourceBuffer.appendBuffer(nextChunk);
286
+
287
+ if (!currentStreamingAudio.hasStartedPlayback) {
288
+ currentStreamingAudio.hasStartedPlayback = true;
289
+ setIsPlaying(true);
290
+ }
291
+
292
+ return;
293
+ }
294
+
295
+ if (currentStreamingAudio.isDone && currentStreamingAudio.mediaSource.readyState === 'open') {
296
+ currentStreamingAudio.mediaSource.endOfStream();
297
+ }
298
+ }
299
+
300
+ function finishAudioStream() {
301
+ if (currentStreamingAudio) {
302
+ currentStreamingAudio.isDone = true;
303
+ flushStreamingAudioQueue();
304
+ return;
305
+ }
306
+
307
+ if (!bufferedAudioChunks.length) {
308
+ return;
309
+ }
310
+
311
+ playAudioChunks(bufferedAudioChunks, bufferedAudioMimeType);
312
+ bufferedAudioChunks = [];
313
+ }
314
+
315
+ function detachStreamingAudio() {
316
+ if (currentStreamingAudio?.sourceBuffer) {
317
+ currentStreamingAudio.sourceBuffer.removeEventListener('updateend', flushStreamingAudioQueue);
318
+ }
319
+
320
+ currentStreamingAudio = null;
321
+ }
322
+
323
+ function destroyCurrentAudioElement() {
324
+ if (currentAudio) {
325
+ currentAudio.pause();
326
+ currentAudio.currentTime = 0;
327
+ currentAudio.src = '';
328
+ currentAudio.load();
329
+ currentAudio = null;
330
+ }
331
+
332
+ if (currentAudioObjectUrl) {
333
+ URL.revokeObjectURL(currentAudioObjectUrl);
334
+ currentAudioObjectUrl = null;
335
+ }
336
+
337
+ isPlaying = false;
338
+ }
339
+
340
+ function stopCurrentAudioPlayback(dontResetMode = false) {
341
+ stopStandByAudio();
342
+ bufferedAudioChunks = [];
343
+ bufferedAudioMimeType = 'audio/mpeg';
344
+ detachStreamingAudio();
345
+ destroyCurrentAudioElement();
346
+ if (!dontResetMode) {
347
+ agentAudioMode.value = null;
348
+ }
349
+ }
350
+
351
+ function handleAudioEnded() {
352
+ agentAudioMode.value = null;
353
+ stopCurrentAudioPlayback();
354
+ }
355
+
356
+ function playAudioChunks(chunks: ArrayBuffer[], mimeType: string) {
357
+ currentAudioObjectUrl = URL.createObjectURL(new Blob(chunks, { type: mimeType }));
358
+ currentAudio = new Audio(currentAudioObjectUrl);
359
+ currentAudio.addEventListener('ended', handleAudioEnded, { once: true });
360
+ setIsPlaying(true);
361
+ }
362
+
363
+ function base64ToArrayBuffer(base64: string) {
364
+ const binary = atob(base64);
365
+ const bytes = new Uint8Array(binary.length);
366
+
367
+ for (let i = 0; i < binary.length; i += 1) {
368
+ bytes[i] = binary.charCodeAt(i);
369
+ }
370
+
371
+ return bytes.buffer;
372
+ }
373
+
374
+ function playBeep(freq = 800, duration = 0.05) {
375
+ const ctx = new AudioContext();
376
+ const osc = ctx.createOscillator();
377
+ const gain = ctx.createGain();
378
+
379
+ osc.frequency.value = freq;
380
+ osc.type = 'sine';
381
+
382
+ osc.connect(gain);
383
+ gain.connect(ctx.destination);
384
+
385
+ osc.start();
386
+
387
+ gain.gain.setValueAtTime(1, ctx.currentTime);
388
+ gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
389
+
390
+ osc.stop(ctx.currentTime + duration);
391
+ }
392
+
393
+ return {
394
+ sendAudioToServerAndHandleResponse,
395
+ stopGenerationAndAudio,
396
+ stopCurrentAudioPlayback,
397
+ playBeep,
398
+ agentAudioMode
399
+ };
400
+
401
+ });
@@ -11,6 +11,8 @@ import {
11
11
  DEFAULT_CHAT_WIDTH,
12
12
  MAX_WIDTH,
13
13
  MIN_WIDTH,
14
+ RESERVED_SYSTEM_MESSAGE_CONTENT,
15
+ PRE_SESSION_ID
14
16
  } from './agentStore/constants';
15
17
  import { createAgentChatManager } from './agentStore/useAgentChat';
16
18
  import { createAgentPlaceholderController } from './agentStore/useAgentPlaceholder';
@@ -67,6 +69,36 @@ export const useAgentStore = defineStore('agent', () => {
67
69
  return window.localStorage.getItem(`${coreStore.config.brandName || 'adminforth'}-${key}`);
68
70
  }
69
71
 
72
+ const isAudioChatMode = ref(false);
73
+
74
+ const onBeforeChatCloseCallbacks: Array<() => Promise<void>> = [];
75
+ function registerOnBeforeChatCloseCallback(hook: () => Promise<void>) {
76
+ onBeforeChatCloseCallbacks.push(hook);
77
+ }
78
+
79
+ async function executeOnBeforeChatCloseCallbacks() {
80
+ for(const hook of onBeforeChatCloseCallbacks) {
81
+ try {
82
+ await hook();
83
+ } catch (error) {
84
+ console.error('Error executing onBeforeChatClose callback:', error);
85
+ }
86
+ }
87
+ }
88
+
89
+ function setIsAudioChatMode(isAudioChat: boolean) {
90
+ isAudioChatMode.value = isAudioChat;
91
+ }
92
+
93
+ watch(isAudioChatMode, (newVal: boolean) => {
94
+ if (newVal) {
95
+ addSystemMessage(RESERVED_SYSTEM_MESSAGE_CONTENT.START_AUDIO_CHAT);
96
+ } else {
97
+ addSystemMessage(RESERVED_SYSTEM_MESSAGE_CONTENT.END_AUDIO_CHAT);
98
+ }
99
+ });
100
+
101
+
70
102
  const isResponseInProgress = computed( () => {
71
103
  return currentChat.value?.status === 'streaming';
72
104
  });
@@ -79,6 +111,11 @@ export const useAgentStore = defineStore('agent', () => {
79
111
  deleteSession,
80
112
  addDebugMessage,
81
113
  addSystemMessage,
114
+ addAgentMessage,
115
+ addUserMessage,
116
+ addDataToolCallMessage,
117
+ setCurrentChatStatus,
118
+ updateLastAgentMessage
82
119
  } = createAgentSessionManager({
83
120
  activeSessionId,
84
121
  currentSession,
@@ -144,7 +181,7 @@ export const useAgentStore = defineStore('agent', () => {
144
181
  }
145
182
  }
146
183
  lastSessionId.value = getLocalStorageItem('lastSessionId');
147
- if (lastSessionId.value && lastSessionId.value !== 'pre-session') {
184
+ if (lastSessionId.value && lastSessionId.value !== PRE_SESSION_ID) {
148
185
  setActiveSession(lastSessionId.value);
149
186
  }
150
187
  if (coreStore.isMobile) {
@@ -231,7 +268,8 @@ export const useAgentStore = defineStore('agent', () => {
231
268
  activeModeName.value = modeName;
232
269
  }
233
270
 
234
- function closeChat() {
271
+ async function closeChat() {
272
+ await executeOnBeforeChatCloseCallbacks();
235
273
  if(isFullScreen.value) {
236
274
  document.body.style.overflow = '';
237
275
  }
@@ -273,7 +311,7 @@ export const useAgentStore = defineStore('agent', () => {
273
311
 
274
312
  function abortCurrentChatRequestAndAddSystemMessage() {
275
313
  abortCurrentChatRequest();
276
- addSystemMessage('[Response generation aborted]');
314
+ addSystemMessage(RESERVED_SYSTEM_MESSAGE_CONTENT.AGENT_RESPONSE_ABORTED);
277
315
  }
278
316
 
279
317
  return {
@@ -315,9 +353,18 @@ export const useAgentStore = defineStore('agent', () => {
315
353
  DEFAULT_CHAT_WIDTH,
316
354
  MAX_WIDTH,
317
355
  MIN_WIDTH,
356
+ RESERVED_SYSTEM_MESSAGE_CONTENT,
318
357
  getLocalStorageItem,
319
358
  addDebugMessage,
320
359
  abortCurrentChatRequestAndAddSystemMessage,
321
- addSystemMessage
360
+ addSystemMessage,
361
+ isAudioChatMode,
362
+ setIsAudioChatMode,
363
+ registerOnBeforeChatCloseCallback,
364
+ addAgentMessage,
365
+ addUserMessage,
366
+ addDataToolCallMessage,
367
+ setCurrentChatStatus,
368
+ updateLastAgentMessage
322
369
  }
323
- })
370
+ })
@@ -35,7 +35,7 @@
35
35
 
36
36
  <div
37
37
  v-for="(message, index) in props.messages" :key="index"
38
- class="flex flex-col w-full mt-2"
38
+ class="flex flex-col w-full mt-2 pb-2"
39
39
  :class="message.role === 'user' ? 'self-end' : 'self-start'"
40
40
  ref="messagesRefs"
41
41
  >
@@ -8,11 +8,15 @@
8
8
  :key="part.type"
9
9
  >
10
10
  <TextRenderer
11
- v-if="part.type === 'text'"
11
+ v-if="part.type === 'text' && !checkIfMessageSystemMessage(part.text ?? '')"
12
12
  :message="part.text"
13
13
  :role="props.message.role"
14
14
  :state="part.state ?? (props.message.role === 'user' ? 'done' : undefined)"
15
15
  />
16
+ <SystemMessageRenderer
17
+ v-else
18
+ :message="part.text"
19
+ />
16
20
  </template>
17
21
 
18
22
  </template>
@@ -25,9 +29,16 @@
25
29
  import type { IMessage } from '../types';
26
30
  import { getMessageParts } from '../utils';
27
31
  import ProcessingTimeline from './ProcessingTimeline.vue';
32
+ import SystemMessageRenderer from './SystemMessageRenderer.vue';
33
+ import { RESERVED_SYSTEM_MESSAGE_CONTENT } from '../composables/agentStore/constants';
28
34
 
29
35
  const props = defineProps<{
30
36
  message: IMessage
31
37
  isLastMessageInChat: boolean
32
38
  }>();
39
+
40
+ function checkIfMessageSystemMessage(message: IMessage): boolean {
41
+ const isReserved = Object.values(RESERVED_SYSTEM_MESSAGE_CONTENT).includes(message as RESERVED_SYSTEM_MESSAGE_CONTENT);
42
+ return isReserved;
43
+ }
33
44
  </script>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <p :class="`${TEXT_CLASSES}`" v-if="message === RESERVED_SYSTEM_MESSAGE_CONTENT.START_AUDIO_CHAT">
3
+ <IconPhoneSolid class="inline-block w-4 h-4 mr-1" />
4
+ {{$t('Audio chat started')}}
5
+ </p>
6
+ <p :class="`${TEXT_CLASSES}`" v-else-if="message === RESERVED_SYSTEM_MESSAGE_CONTENT.END_AUDIO_CHAT">
7
+ <IconPhoneHangupSolid class="inline-block w-4 h-4 mr-1" />
8
+ {{$t('Audio chat ended')}}
9
+ </p>
10
+ <p :class="`${TEXT_CLASSES}`" v-else-if="message === RESERVED_SYSTEM_MESSAGE_CONTENT.AGENT_RESPONSE_ABORTED">
11
+ {{$t('Agent response aborted')}}
12
+ </p>
13
+
14
+ </template>
15
+
16
+
17
+
18
+ <script setup lang="ts">
19
+ import { RESERVED_SYSTEM_MESSAGE_CONTENT } from '../composables/agentStore/constants';
20
+ import { IconPhoneSolid, IconPhoneHangupSolid } from '@iconify-prerendered/vue-flowbite';
21
+
22
+ const TEXT_CLASSES = 'text-center italic text-lightListTableHeadingText dark:text-darkListTableHeadingText flex items-center justify-center';
23
+
24
+ const props = defineProps<{
25
+ message: string
26
+ }>();
27
+
28
+ </script>
@@ -16,9 +16,9 @@
16
16
  :components="incremarkComponents"
17
17
  :incremark-options="incremarkOptions"
18
18
  />
19
- <p v-else class="text-red-500 py-2">
19
+ <!-- <p v-else class="text-red-500 py-2">
20
20
  {{ $t('No content to render') }}
21
- </p>
21
+ </p> -->
22
22
  </div>
23
23
  </template>
24
24
 
@@ -28,11 +28,12 @@
28
28
  import { useRouter } from 'vue-router';
29
29
  import { useAgentStore } from '../composables/useAgentStore';
30
30
  import { useCoreStore } from '@/stores/core';
31
+ import type { IMessage } from '../types';
31
32
 
32
33
  const props = defineProps<{
33
34
  message: string | undefined,
34
35
  state: string | undefined,
35
- role: 'user' | 'assistant'
36
+ role: IMessage['role']
36
37
  }>();
37
38
 
38
39
  const emit = defineEmits(['toggle-thoughts']);
@@ -109,7 +109,7 @@
109
109
  const hasToolSections = computed(() => toolSections.value.length > 0);
110
110
 
111
111
  onMounted(() => {
112
- if (toolRendererRef.value) {
112
+ if (toolRendererRef.value && props.data.toolInfo) {
113
113
  toolRendererInitialWidth.value = toolRendererRef.value.offsetWidth;
114
114
  }
115
115
  });
@@ -22,6 +22,7 @@
22
22
  "dompurify": "^3.3.3",
23
23
  "katex": "^0.16.45",
24
24
  "marked": "^18.0.0",
25
- "vega-embed": "^7.1.0"
25
+ "vega-embed": "^7.1.0",
26
+ "voice-activity-detection": "^0.0.5"
26
27
  }
27
28
  }