@clikvn/agent-widget-embedded 0.0.11-dev → 0.0.13-dev
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/dist/components/Chat/MultimodalInput.d.ts.map +1 -1
- package/dist/index.html +52 -12
- package/dist/web.js +1 -1
- package/package.json +23 -19
- package/rollup.config.js +1 -0
- package/.eslintrc +0 -34
- package/.prettierrc +0 -8
- package/src/assets/common.css +0 -148
- package/src/assets/tailwindcss.css +0 -3
- package/src/commons/constants/index.ts +0 -1
- package/src/commons/constants/variables.ts +0 -25
- package/src/components/Agent/index.tsx +0 -14
- package/src/components/Chat/AudioPlayer.tsx +0 -44
- package/src/components/Chat/Chat.tsx +0 -91
- package/src/components/Chat/Icons.tsx +0 -1796
- package/src/components/Chat/Markdown.tsx +0 -335
- package/src/components/Chat/Message.tsx +0 -217
- package/src/components/Chat/MultimodalInput.tsx +0 -505
- package/src/components/Chat/Overview.tsx +0 -46
- package/src/components/Chat/PreviewAttachment.tsx +0 -46
- package/src/components/Chat/SuggestedActions.tsx +0 -99
- package/src/components/Chat/ui/Button.tsx +0 -55
- package/src/components/Chat/ui/Textarea.tsx +0 -23
- package/src/constants.ts +0 -1
- package/src/env.d.ts +0 -10
- package/src/features/AgentWidget/index.tsx +0 -63
- package/src/global.d.ts +0 -1
- package/src/hooks/useAudioRecording.ts +0 -50
- package/src/hooks/useChat.ts +0 -262
- package/src/hooks/useChatData.tsx +0 -68
- package/src/hooks/useConfiguration.tsx +0 -63
- package/src/hooks/useScrollToBottom.ts +0 -31
- package/src/index.ts +0 -1
- package/src/models/FlowiseClient.ts +0 -103
- package/src/models.ts +0 -1
- package/src/register.tsx +0 -85
- package/src/services/apis.ts +0 -12
- package/src/services/bot.service.ts +0 -15
- package/src/services/chat.service.ts +0 -199
- package/src/types/bot.type.ts +0 -10
- package/src/types/chat.type.ts +0 -11
- package/src/types/common.type.ts +0 -24
- package/src/types/flowise.type.ts +0 -108
- package/src/types/user.type.ts +0 -15
- package/src/types.ts +0 -0
- package/src/utils/audioRecording.ts +0 -371
- package/src/utils/commonUtils.ts +0 -47
- package/src/utils/functionUtils.ts +0 -17
- package/src/utils/requestUtils.ts +0 -113
- package/src/utils/streamUtils.ts +0 -18
- package/src/web.ts +0 -6
- package/src/window.ts +0 -43
- package/tsconfig.json +0 -24
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
import DeviceDetector from 'device-detector-js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @fileoverview This file contains the API to handle audio recording.
|
|
5
|
-
* Originally from 'https://ralzohairi.medium.com/audio-recording-in-javascript-96eed45b75ee'
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// audio-recording.js ---------------
|
|
9
|
-
let elapsedTime = '00:00';
|
|
10
|
-
|
|
11
|
-
/** Stores the actual start time when an audio recording begins to take place to ensure elapsed time start time is accurate*/
|
|
12
|
-
let audioRecordStartTime: Date;
|
|
13
|
-
|
|
14
|
-
/** Stores the maximum recording time in hours to stop recording once maximum recording hour has been reached */
|
|
15
|
-
const maximumRecordingTimeInHours = 1;
|
|
16
|
-
|
|
17
|
-
/** Stores the reference of the setInterval function that controls the timer in audio recording*/
|
|
18
|
-
let elapsedTimeTimer: ReturnType<typeof setInterval>;
|
|
19
|
-
|
|
20
|
-
export function getElaspedTime() {
|
|
21
|
-
return elapsedTime;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Starts the audio recording*/
|
|
25
|
-
export function startAudioRecording(
|
|
26
|
-
onRecordingStart: (value: boolean) => void,
|
|
27
|
-
onUnsupportedBrowser: (value: boolean) => void,
|
|
28
|
-
setElapsedTime: (value: string) => void
|
|
29
|
-
) {
|
|
30
|
-
//start recording using the audio recording API
|
|
31
|
-
audioRecorder
|
|
32
|
-
.start()
|
|
33
|
-
.then(() => {
|
|
34
|
-
//on success show the controls to stop and cancel the recording
|
|
35
|
-
if (onRecordingStart) {
|
|
36
|
-
onRecordingStart(true);
|
|
37
|
-
}
|
|
38
|
-
//store the recording start time to display the elapsed time according to it
|
|
39
|
-
audioRecordStartTime = new Date();
|
|
40
|
-
|
|
41
|
-
//Handle the displaying of the elapsed recording time
|
|
42
|
-
handleElapsedRecordingTime(setElapsedTime);
|
|
43
|
-
})
|
|
44
|
-
.catch((error) => {
|
|
45
|
-
//on error
|
|
46
|
-
//No Browser Support Error
|
|
47
|
-
if (
|
|
48
|
-
error.message.includes(
|
|
49
|
-
'mediaDevices API or getUserMedia method is not supported in this browser.'
|
|
50
|
-
)
|
|
51
|
-
) {
|
|
52
|
-
if (onUnsupportedBrowser) {
|
|
53
|
-
onUnsupportedBrowser(true);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
console.log(error);
|
|
58
|
-
|
|
59
|
-
//Error handling structure
|
|
60
|
-
switch (error.name) {
|
|
61
|
-
case 'AbortError': //error from navigator.mediaDevices.getUserMedia
|
|
62
|
-
// eslint-disable-next-line no-console
|
|
63
|
-
console.log('An AbortError has occurred.');
|
|
64
|
-
break;
|
|
65
|
-
case 'NotAllowedError': //error from navigator.mediaDevices.getUserMedia
|
|
66
|
-
// eslint-disable-next-line no-console
|
|
67
|
-
console.log(
|
|
68
|
-
'A NotAllowedError has occurred. User might have denied permission.'
|
|
69
|
-
);
|
|
70
|
-
break;
|
|
71
|
-
case 'NotFoundError': //error from navigator.mediaDevices.getUserMedia
|
|
72
|
-
// eslint-disable-next-line no-console
|
|
73
|
-
console.log('A NotFoundError has occurred.');
|
|
74
|
-
break;
|
|
75
|
-
case 'NotReadableError': //error from navigator.mediaDevices.getUserMedia
|
|
76
|
-
// eslint-disable-next-line no-console
|
|
77
|
-
console.log('A NotReadableError has occurred.');
|
|
78
|
-
break;
|
|
79
|
-
case 'SecurityError': //error from navigator.mediaDevices.getUserMedia or from the MediaRecorder.start
|
|
80
|
-
// eslint-disable-next-line no-console
|
|
81
|
-
console.log('A SecurityError has occurred.');
|
|
82
|
-
break;
|
|
83
|
-
case 'TypeError': //error from navigator.mediaDevices.getUserMedia
|
|
84
|
-
// eslint-disable-next-line no-console
|
|
85
|
-
console.log('A TypeError has occurred.');
|
|
86
|
-
break;
|
|
87
|
-
case 'InvalidStateError': //error from the MediaRecorder.start
|
|
88
|
-
// eslint-disable-next-line no-console
|
|
89
|
-
console.log('An InvalidStateError has occurred.');
|
|
90
|
-
break;
|
|
91
|
-
case 'UnknownError': //error from the MediaRecorder.start
|
|
92
|
-
// eslint-disable-next-line no-console
|
|
93
|
-
console.log('An UnknownError has occurred.');
|
|
94
|
-
break;
|
|
95
|
-
default:
|
|
96
|
-
// eslint-disable-next-line no-console
|
|
97
|
-
console.log('An error occurred with the error name ' + error.name);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
/** Stop the currently started audio recording & sends it
|
|
102
|
-
*/
|
|
103
|
-
export function stopAudioRecording(
|
|
104
|
-
addRecordingToPreviews: null | ((blob: Blob) => void)
|
|
105
|
-
) {
|
|
106
|
-
//stop the recording using the audio recording API
|
|
107
|
-
audioRecorder
|
|
108
|
-
.stop()
|
|
109
|
-
.then((audioBlob) => {
|
|
110
|
-
//stop interval that handles both time elapsed and the red dot
|
|
111
|
-
clearInterval(elapsedTimeTimer);
|
|
112
|
-
if (addRecordingToPreviews) {
|
|
113
|
-
addRecordingToPreviews(audioBlob as Blob);
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
.catch((error) => {
|
|
117
|
-
//Error handling structure
|
|
118
|
-
switch (error.name) {
|
|
119
|
-
case 'InvalidStateError': //error from the MediaRecorder.stop
|
|
120
|
-
// eslint-disable-next-line no-console
|
|
121
|
-
console.log('An InvalidStateError has occurred.');
|
|
122
|
-
break;
|
|
123
|
-
default:
|
|
124
|
-
// eslint-disable-next-line no-console
|
|
125
|
-
console.log('An error occurred with the error name ' + error.name);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Cancel the currently started audio recording */
|
|
131
|
-
export function cancelAudioRecording() {
|
|
132
|
-
//cancel the recording using the audio recording API
|
|
133
|
-
audioRecorder.cancel();
|
|
134
|
-
|
|
135
|
-
//stop interval that handles both time elapsed and the red dot
|
|
136
|
-
clearInterval(elapsedTimeTimer);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** Computes the elapsed recording time since the moment the function is called in the format h:m:s*/
|
|
140
|
-
function handleElapsedRecordingTime(setElapsedTime: (value: string) => void) {
|
|
141
|
-
//display initial time when recording begins
|
|
142
|
-
elapsedTime = '00:00';
|
|
143
|
-
// set elapsed time so it can be displayed in the component
|
|
144
|
-
setElapsedTime(elapsedTime);
|
|
145
|
-
|
|
146
|
-
//create an interval that compute & displays elapsed time, as well as, animate red dot - every second
|
|
147
|
-
elapsedTimeTimer = setInterval(() => {
|
|
148
|
-
//compute the elapsed time every second
|
|
149
|
-
elapsedTime = computeElapsedTime(audioRecordStartTime); //pass the actual record start time
|
|
150
|
-
// set elapsed time so it can be displayed in the component
|
|
151
|
-
setElapsedTime(elapsedTime);
|
|
152
|
-
//display the elapsed time
|
|
153
|
-
displayElapsedTimeDuringAudioRecording();
|
|
154
|
-
}, 1000); //every second
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** Display elapsed time during audio recording
|
|
158
|
-
*/
|
|
159
|
-
function displayElapsedTimeDuringAudioRecording() {
|
|
160
|
-
// Stop the recording when the max number of hours is reached
|
|
161
|
-
if (elapsedTimeReachedMaximumNumberOfHours()) {
|
|
162
|
-
stopAudioRecording(null);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* @returns {Boolean} whether the elapsed time reached the maximum number of hours or not
|
|
168
|
-
*/
|
|
169
|
-
function elapsedTimeReachedMaximumNumberOfHours() {
|
|
170
|
-
//Split the elapsed time by the symbol that separates the hours, minutes and seconds :
|
|
171
|
-
const elapsedTimeSplit = elapsedTime.split(':');
|
|
172
|
-
|
|
173
|
-
//Turn the maximum recording time in hours to a string and pad it with zero if less than 10
|
|
174
|
-
const maximumRecordingTimeInHoursAsString =
|
|
175
|
-
maximumRecordingTimeInHours < 10
|
|
176
|
-
? '0' + maximumRecordingTimeInHours
|
|
177
|
-
: maximumRecordingTimeInHours.toString();
|
|
178
|
-
|
|
179
|
-
//if the elapsed time reach hours and also reach the maximum recording time in hours return true
|
|
180
|
-
return (
|
|
181
|
-
elapsedTimeSplit.length === 3 &&
|
|
182
|
-
elapsedTimeSplit[0] === maximumRecordingTimeInHoursAsString
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function padLeft(num: number, size: number) {
|
|
187
|
-
return `${num}`.padStart(size, '0');
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** Computes the elapsedTime since the moment the function is called in the format mm:ss or hh:mm:ss
|
|
191
|
-
* @param {String} startTime - start time to compute the elapsed time since
|
|
192
|
-
* @returns {String} elapsed time in mm:ss format or hh:mm:ss format, if elapsed hours are 0.
|
|
193
|
-
*/
|
|
194
|
-
function computeElapsedTime(startTime: Date) {
|
|
195
|
-
//record end time
|
|
196
|
-
const endTime = new Date();
|
|
197
|
-
|
|
198
|
-
//time difference in ms
|
|
199
|
-
let timeDiff = endTime.getTime() - startTime.getTime();
|
|
200
|
-
|
|
201
|
-
//convert time difference from ms to seconds
|
|
202
|
-
timeDiff = timeDiff / 1000;
|
|
203
|
-
|
|
204
|
-
//extract integer seconds that don't form a minute using %
|
|
205
|
-
const seconds = Math.floor(timeDiff % 60); //ignoring incomplete seconds (floor)
|
|
206
|
-
|
|
207
|
-
//convert time difference from seconds to minutes using %
|
|
208
|
-
timeDiff = Math.floor(timeDiff / 60);
|
|
209
|
-
|
|
210
|
-
//extract integer minutes that don't form an hour using %
|
|
211
|
-
const minutes = timeDiff % 60; //no need to floor possible incomplete minutes, because they've been handled as seconds
|
|
212
|
-
|
|
213
|
-
//convert time difference from minutes to hours
|
|
214
|
-
timeDiff = Math.floor(timeDiff / 60);
|
|
215
|
-
|
|
216
|
-
//extract integer hours that don't form a day using %
|
|
217
|
-
const hours = timeDiff % 24; //no need to floor possible incomplete hours, because they've been handled as seconds
|
|
218
|
-
|
|
219
|
-
//convert time difference from hours to days
|
|
220
|
-
timeDiff = Math.floor(timeDiff / 24);
|
|
221
|
-
|
|
222
|
-
// the rest of timeDiff is number of days
|
|
223
|
-
const days = timeDiff; //add days to hours
|
|
224
|
-
|
|
225
|
-
const totalHours = hours + days * 24;
|
|
226
|
-
|
|
227
|
-
if (totalHours === 0) {
|
|
228
|
-
return padLeft(minutes, 2) + ':' + padLeft(seconds, 2);
|
|
229
|
-
} else {
|
|
230
|
-
return (
|
|
231
|
-
padLeft(totalHours, 2) +
|
|
232
|
-
':' +
|
|
233
|
-
padLeft(minutes, 2) +
|
|
234
|
-
':' +
|
|
235
|
-
padLeft(seconds, 2)
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
//API to handle audio recording
|
|
241
|
-
|
|
242
|
-
type AudioRecorder = {
|
|
243
|
-
audioBlobs: Blob[];
|
|
244
|
-
mediaRecorder: MediaRecorder | null;
|
|
245
|
-
streamBeingCaptured: MediaStream | null;
|
|
246
|
-
start: () => Promise<void>;
|
|
247
|
-
stop: () => Promise<unknown>;
|
|
248
|
-
cancel: () => void;
|
|
249
|
-
stopStream: () => void;
|
|
250
|
-
resetRecordingProperties: () => void;
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
export const audioRecorder: AudioRecorder = {
|
|
254
|
-
/** Stores the recorded audio as Blob objects of audio data as the recording continues*/
|
|
255
|
-
audioBlobs: [] /*of type Blob[]*/,
|
|
256
|
-
/** Stores the reference of the MediaRecorder instance that handles the MediaStream when recording starts*/
|
|
257
|
-
mediaRecorder: null /*of type MediaRecorder*/,
|
|
258
|
-
/** Stores the reference to the stream currently capturing the audio*/
|
|
259
|
-
streamBeingCaptured: null /*of type MediaStream*/,
|
|
260
|
-
/** Start recording the audio
|
|
261
|
-
* @returns {Promise} - returns a promise that resolves if audio recording successfully started
|
|
262
|
-
*/
|
|
263
|
-
start: function () {
|
|
264
|
-
//Feature Detection
|
|
265
|
-
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
|
|
266
|
-
//Feature is not supported in browser
|
|
267
|
-
//return a custom error
|
|
268
|
-
return Promise.reject(
|
|
269
|
-
new Error(
|
|
270
|
-
'mediaDevices API or getUserMedia method is not supported in this browser.'
|
|
271
|
-
)
|
|
272
|
-
);
|
|
273
|
-
} else {
|
|
274
|
-
// Feature is supported in browser
|
|
275
|
-
const deviceDetector = new DeviceDetector();
|
|
276
|
-
const userAgent = navigator.userAgent;
|
|
277
|
-
const device = deviceDetector.parse(userAgent);
|
|
278
|
-
const isSafari = device.client?.name === 'Mobile Safari';
|
|
279
|
-
|
|
280
|
-
//create an audio stream
|
|
281
|
-
return (
|
|
282
|
-
navigator.mediaDevices
|
|
283
|
-
.getUserMedia({ audio: true } /*of type MediaStreamConstraints*/)
|
|
284
|
-
//returns a promise that resolves to the audio stream
|
|
285
|
-
.then((stream) /*of type MediaStream*/ => {
|
|
286
|
-
//save the reference of the stream to be able to stop it when necessary
|
|
287
|
-
audioRecorder.streamBeingCaptured = stream;
|
|
288
|
-
|
|
289
|
-
//create a media recorder instance by passing that stream into the MediaRecorder constructor
|
|
290
|
-
audioRecorder.mediaRecorder = new MediaRecorder(stream);
|
|
291
|
-
/*the MediaRecorder interface of the MediaStream Recording API provides functionality to easily record media*/
|
|
292
|
-
|
|
293
|
-
//clear previously saved audio Blobs, if any
|
|
294
|
-
audioRecorder.audioBlobs = [];
|
|
295
|
-
|
|
296
|
-
//add a dataavailable event listener in order to store the audio data Blobs when recording
|
|
297
|
-
audioRecorder.mediaRecorder.addEventListener(
|
|
298
|
-
'dataavailable',
|
|
299
|
-
(event) => {
|
|
300
|
-
//store audio Blob object
|
|
301
|
-
audioRecorder.audioBlobs.push(event.data);
|
|
302
|
-
}
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
//start the recording by calling the start method on the media recorder
|
|
306
|
-
if (isSafari) {
|
|
307
|
-
// https://community.openai.com/t/whisper-problem-with-audio-mp4-blobs-from-safari/322252
|
|
308
|
-
// https://community.openai.com/t/whisper-api-cannot-read-files-correctly/93420/46
|
|
309
|
-
audioRecorder.mediaRecorder.start(1000);
|
|
310
|
-
} else {
|
|
311
|
-
audioRecorder.mediaRecorder.start();
|
|
312
|
-
}
|
|
313
|
-
})
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
/* errors are not handled in the API because if its handled and the promise is chained, the .then after the catch will be executed*/
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
/** Stop the started audio recording
|
|
320
|
-
* @returns {Promise} - returns a promise that resolves to the audio as a blob file
|
|
321
|
-
*/
|
|
322
|
-
stop: function () {
|
|
323
|
-
//return a promise that would return the blob or URL of the recording
|
|
324
|
-
return new Promise((resolve) => {
|
|
325
|
-
//save audio type to pass to set the Blob type
|
|
326
|
-
const mimeType = audioRecorder.mediaRecorder?.mimeType;
|
|
327
|
-
|
|
328
|
-
//listen to the stop event in order to create & return a single Blob object
|
|
329
|
-
audioRecorder.mediaRecorder?.addEventListener('stop', () => {
|
|
330
|
-
//create a single blob object, as we might have gathered a few Blob objects that needs to be joined as one
|
|
331
|
-
const audioBlob = new Blob(audioRecorder.audioBlobs, {
|
|
332
|
-
type: mimeType,
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
//resolve promise with the single audio blob representing the recorded audio
|
|
336
|
-
resolve(audioBlob);
|
|
337
|
-
});
|
|
338
|
-
audioRecorder.cancel();
|
|
339
|
-
});
|
|
340
|
-
},
|
|
341
|
-
/** Cancel audio recording*/
|
|
342
|
-
cancel: function () {
|
|
343
|
-
//stop the recording feature
|
|
344
|
-
audioRecorder.mediaRecorder?.stop();
|
|
345
|
-
|
|
346
|
-
//stop all the tracks on the active stream in order to stop the stream
|
|
347
|
-
audioRecorder.stopStream();
|
|
348
|
-
|
|
349
|
-
//reset API properties for next recording
|
|
350
|
-
audioRecorder.resetRecordingProperties();
|
|
351
|
-
},
|
|
352
|
-
/** Stop all the tracks on the active stream in order to stop the stream and remove
|
|
353
|
-
* the red flashing dot showing in the tab
|
|
354
|
-
*/
|
|
355
|
-
stopStream: function () {
|
|
356
|
-
//stopping the capturing request by stopping all the tracks on the active stream
|
|
357
|
-
audioRecorder.streamBeingCaptured
|
|
358
|
-
?.getTracks() //get all tracks from the stream
|
|
359
|
-
.forEach((track) /*of type MediaStreamTrack*/ => track.stop()); //stop each one
|
|
360
|
-
},
|
|
361
|
-
/** Reset all the recording properties including the media recorder and stream being captured*/
|
|
362
|
-
resetRecordingProperties: function () {
|
|
363
|
-
audioRecorder.mediaRecorder = null;
|
|
364
|
-
audioRecorder.streamBeingCaptured = null;
|
|
365
|
-
|
|
366
|
-
/*No need to remove event listeners attached to mediaRecorder as
|
|
367
|
-
If a DOM element which is removed is reference-free (no references pointing to it), the element itself is picked
|
|
368
|
-
up by the garbage collector as well as any event handlers/listeners associated with it.
|
|
369
|
-
getEventListeners(audioRecorder.mediaRecorder) will return an empty array of events.*/
|
|
370
|
-
},
|
|
371
|
-
};
|
package/src/utils/commonUtils.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { type ClassValue, clsx } from 'clsx';
|
|
2
|
-
import { twMerge } from 'tailwind-merge';
|
|
3
|
-
|
|
4
|
-
export const cn = (...inputs: ClassValue[]) => {
|
|
5
|
-
return twMerge(clsx(inputs));
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export function getLocalStorage(key: string) {
|
|
9
|
-
if (typeof window !== 'undefined') {
|
|
10
|
-
return JSON.parse(localStorage.getItem(key) || '[]');
|
|
11
|
-
}
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function generateUUID(): string {
|
|
16
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
17
|
-
const r = (Math.random() * 16) | 0;
|
|
18
|
-
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
19
|
-
return v.toString(16);
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const generateExtendedFileName = (originalFileName: string) => {
|
|
24
|
-
// Extract the name and extension from the original file name
|
|
25
|
-
const dotIndex = originalFileName.lastIndexOf('.');
|
|
26
|
-
const baseName =
|
|
27
|
-
dotIndex === -1
|
|
28
|
-
? originalFileName
|
|
29
|
-
: originalFileName.substring(0, dotIndex);
|
|
30
|
-
const extension = dotIndex === -1 ? '' : originalFileName.substring(dotIndex);
|
|
31
|
-
|
|
32
|
-
// Get the current date and time
|
|
33
|
-
const now = new Date();
|
|
34
|
-
const year = now.getFullYear();
|
|
35
|
-
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-11
|
|
36
|
-
const day = String(now.getDate()).padStart(2, '0');
|
|
37
|
-
const hours = String(now.getHours()).padStart(2, '0');
|
|
38
|
-
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
39
|
-
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
40
|
-
|
|
41
|
-
// Create the new unique file name
|
|
42
|
-
return `${baseName}_${year}${month}${day}_${hours}${minutes}${seconds}${extension}`;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const sleep = async (duration: number): Promise<void> => {
|
|
46
|
-
return new Promise((resolve) => setTimeout(resolve, duration));
|
|
47
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export const nonNull = (obj: any): boolean => {
|
|
2
|
-
return obj !== null && obj !== undefined;
|
|
3
|
-
};
|
|
4
|
-
|
|
5
|
-
export const toQuery = (params: any, delimiter = '&') => {
|
|
6
|
-
const keys = Object.keys(params);
|
|
7
|
-
|
|
8
|
-
return keys.reduce((str, key, index) => {
|
|
9
|
-
let query = `${str}${key}=${params[key]}`;
|
|
10
|
-
|
|
11
|
-
if (index < keys.length - 1) {
|
|
12
|
-
query += delimiter;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return query;
|
|
16
|
-
}, '');
|
|
17
|
-
};
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { BE_API, LANGUAGE_HEADER } from '../commons/constants';
|
|
2
|
-
import { processTextStream } from './streamUtils';
|
|
3
|
-
|
|
4
|
-
type RequestPropsTypes = {
|
|
5
|
-
host?: string;
|
|
6
|
-
url?: string;
|
|
7
|
-
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
8
|
-
streamProtocol?: 'data' | 'text';
|
|
9
|
-
onStreamUpdate?: (chunks: { content: string }[]) => void;
|
|
10
|
-
onStreamFinish?: (result: { content: string }) => void;
|
|
11
|
-
[x: string]: any;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const defaultHeaders = { 'Content-Type': 'application/json' };
|
|
15
|
-
|
|
16
|
-
export const request = async (props: RequestPropsTypes): Promise<any> => {
|
|
17
|
-
const {
|
|
18
|
-
host = BE_API,
|
|
19
|
-
url = '',
|
|
20
|
-
method = 'GET',
|
|
21
|
-
headers = { ...defaultHeaders, ...props.headers },
|
|
22
|
-
params,
|
|
23
|
-
streamProtocol,
|
|
24
|
-
onStreamUpdate,
|
|
25
|
-
onStreamFinish,
|
|
26
|
-
...options
|
|
27
|
-
} = props;
|
|
28
|
-
let apiLanguage: string = LANGUAGE_HEADER.en;
|
|
29
|
-
if (headers?.language) {
|
|
30
|
-
apiLanguage = LANGUAGE_HEADER[headers.language];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const customHeader = { ...headers, language: apiLanguage };
|
|
34
|
-
const requestInit = {
|
|
35
|
-
method,
|
|
36
|
-
headers: customHeader,
|
|
37
|
-
...options,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
let queryString = '';
|
|
41
|
-
let apiUrl = '';
|
|
42
|
-
|
|
43
|
-
// Generate query string if params exist
|
|
44
|
-
if (!!params && !!Object.keys(params).length) {
|
|
45
|
-
queryString = Object.keys(params)
|
|
46
|
-
.filter((key) => !!params[key])
|
|
47
|
-
.map((key) => {
|
|
48
|
-
const val: string[] | string = params[key];
|
|
49
|
-
if (Array.isArray(val)) {
|
|
50
|
-
return val
|
|
51
|
-
.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`)
|
|
52
|
-
.join('&');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return `${encodeURIComponent(key)}=${encodeURIComponent(val)}`;
|
|
56
|
-
})
|
|
57
|
-
.join('&');
|
|
58
|
-
|
|
59
|
-
apiUrl = `${url}?${queryString}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const response = await fetch(`${host}${apiUrl || url}`, {
|
|
64
|
-
...requestInit,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
console.error(
|
|
69
|
-
new Error(`API request failed with status ${response.status}`)
|
|
70
|
-
);
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!response.body) {
|
|
75
|
-
console.error(new Error('The response body is empty.'));
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!streamProtocol) {
|
|
80
|
-
return response.json();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
switch (streamProtocol) {
|
|
84
|
-
case 'text': {
|
|
85
|
-
const resultMessage = {
|
|
86
|
-
content: '',
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
await processTextStream({
|
|
90
|
-
stream: response.body as any,
|
|
91
|
-
onTextPart: (chunk) => {
|
|
92
|
-
resultMessage.content += chunk;
|
|
93
|
-
|
|
94
|
-
// note: creating a new message object is required for Solid.js streaming
|
|
95
|
-
onStreamUpdate?.([{ ...resultMessage }]);
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// in text mode, we don't have usage information or finish reason:
|
|
100
|
-
onStreamFinish?.(resultMessage);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
default: {
|
|
105
|
-
console.error(new Error(`Unknown stream protocol: ${streamProtocol}`));
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
} catch (error) {
|
|
110
|
-
console.error(error);
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
};
|
package/src/utils/streamUtils.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export const processTextStream = async ({
|
|
2
|
-
stream,
|
|
3
|
-
onTextPart,
|
|
4
|
-
}: {
|
|
5
|
-
stream: ReadableStream<Uint8Array>;
|
|
6
|
-
onTextPart: (chunk: string) => Promise<void> | void;
|
|
7
|
-
}): Promise<void> => {
|
|
8
|
-
const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
|
|
9
|
-
|
|
10
|
-
// eslint-disable-next-line no-constant-condition
|
|
11
|
-
while (true) {
|
|
12
|
-
const { done, value } = await reader.read();
|
|
13
|
-
if (done) {
|
|
14
|
-
break;
|
|
15
|
-
}
|
|
16
|
-
await onTextPart(value);
|
|
17
|
-
}
|
|
18
|
-
};
|
package/src/web.ts
DELETED
package/src/window.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { agentWidgetElementName } from './constants';
|
|
2
|
-
import { AgentWidgetType, registerWebComponents } from './register';
|
|
3
|
-
|
|
4
|
-
let elementUsed: Element | undefined;
|
|
5
|
-
|
|
6
|
-
export const initWidget = (props: AgentWidgetType & { id?: string }) => {
|
|
7
|
-
destroy();
|
|
8
|
-
const element: any = props.id
|
|
9
|
-
? document.getElementById(props.id)
|
|
10
|
-
: document.querySelector(agentWidgetElementName);
|
|
11
|
-
if (!element) throw new Error(`${agentWidgetElementName} element not found.`);
|
|
12
|
-
if (customElements.get(agentWidgetElementName)) {
|
|
13
|
-
element.updateAttributes(props);
|
|
14
|
-
} else {
|
|
15
|
-
Object.assign(element, props);
|
|
16
|
-
registerWebComponents();
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const destroy = () => {
|
|
21
|
-
elementUsed?.remove();
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type AgentWidget = {
|
|
25
|
-
initWidget: typeof initWidget;
|
|
26
|
-
destroy: typeof destroy;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
declare const window:
|
|
30
|
-
| {
|
|
31
|
-
AgentWidget: AgentWidget | undefined;
|
|
32
|
-
}
|
|
33
|
-
| undefined;
|
|
34
|
-
|
|
35
|
-
export const parseAgentVoice = () => ({
|
|
36
|
-
initWidget,
|
|
37
|
-
destroy,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
export const injectAgentVoiceInWindow = (agent: AgentWidget) => {
|
|
41
|
-
if (typeof window === 'undefined') return;
|
|
42
|
-
window.AgentWidget = { ...agent };
|
|
43
|
-
};
|
package/tsconfig.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "./base.json",
|
|
3
|
-
"include": ["src/**/*"],
|
|
4
|
-
"exclude": ["dist", "node_modules"],
|
|
5
|
-
"compilerOptions": {
|
|
6
|
-
"baseUrl": "./src",
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"target": "es2015",
|
|
9
|
-
"lib": ["dom", "dom.iterable", "esnext"],
|
|
10
|
-
"allowJs": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"esModuleInterop": true,
|
|
13
|
-
"allowSyntheticDefaultImports": true,
|
|
14
|
-
"strict": true,
|
|
15
|
-
"forceConsistentCasingInFileNames": true,
|
|
16
|
-
"noFallthroughCasesInSwitch": true,
|
|
17
|
-
"module": "esnext",
|
|
18
|
-
"moduleResolution": "node",
|
|
19
|
-
"resolveJsonModule": true,
|
|
20
|
-
"isolatedModules": true,
|
|
21
|
-
"noEmit": true,
|
|
22
|
-
"jsx": "react-jsx"
|
|
23
|
-
}
|
|
24
|
-
}
|