@memori.ai/memori-react 8.7.9 → 8.8.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/CHANGELOG.md +22 -0
- package/dist/components/ChatBubble/ChatBubble.js +5 -1
- package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
- package/dist/components/Header/Header.js +28 -14
- package/dist/components/Header/Header.js.map +1 -1
- package/dist/components/MemoriWidget/MemoriWidget.js +2 -9
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/components/MicrophoneButton/MicrophoneButton.css +36 -3
- package/dist/components/MicrophoneButton/MicrophoneButton.js +25 -6
- package/dist/components/MicrophoneButton/MicrophoneButton.js.map +1 -1
- package/dist/components/SettingsDrawer/SettingsDrawer.d.ts +1 -1
- package/dist/components/SettingsDrawer/SettingsDrawer.js +2 -4
- package/dist/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
- package/dist/context/visemeContext.d.ts +1 -1
- package/dist/context/visemeContext.js +1 -1
- package/dist/helpers/stt/useSTT.d.ts +0 -6
- package/dist/helpers/stt/useSTT.js +7 -137
- package/dist/helpers/stt/useSTT.js.map +1 -1
- package/dist/helpers/tts/useTTS.d.ts +1 -3
- package/dist/helpers/tts/useTTS.js +60 -18
- package/dist/helpers/tts/useTTS.js.map +1 -1
- package/esm/components/ChatBubble/ChatBubble.js +5 -1
- package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
- package/esm/components/Header/Header.js +28 -14
- package/esm/components/Header/Header.js.map +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.js +2 -9
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/components/MicrophoneButton/MicrophoneButton.css +36 -3
- package/esm/components/MicrophoneButton/MicrophoneButton.js +25 -6
- package/esm/components/MicrophoneButton/MicrophoneButton.js.map +1 -1
- package/esm/components/SettingsDrawer/SettingsDrawer.d.ts +1 -1
- package/esm/components/SettingsDrawer/SettingsDrawer.js +2 -4
- package/esm/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
- package/esm/context/visemeContext.d.ts +1 -1
- package/esm/context/visemeContext.js +1 -1
- package/esm/helpers/stt/useSTT.d.ts +0 -6
- package/esm/helpers/stt/useSTT.js +7 -137
- package/esm/helpers/stt/useSTT.js.map +1 -1
- package/esm/helpers/tts/useTTS.d.ts +1 -3
- package/esm/helpers/tts/useTTS.js +60 -18
- package/esm/helpers/tts/useTTS.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ChatBubble/ChatBubble.tsx +8 -1
- package/src/components/ChatInputs/__snapshots__/ChatInputs.test.tsx.snap +100 -90
- package/src/components/Header/Header.tsx +43 -30
- package/src/components/MemoriWidget/MemoriWidget.tsx +3 -19
- package/src/components/MicrophoneButton/MicrophoneButton.css +36 -3
- package/src/components/MicrophoneButton/MicrophoneButton.tsx +44 -18
- package/src/components/SettingsDrawer/SettingsDrawer.tsx +4 -11
- package/src/context/visemeContext.tsx +1 -1
- package/src/helpers/stt/useSTT.ts +43 -253
- package/src/helpers/tts/useTTS.ts +273 -202
- package/src/index.stories.tsx +15 -14
- package/dist/components/AccountForm/AccountForm.d.ts +0 -11
- package/dist/components/AccountForm/AccountForm.js +0 -121
- package/dist/components/AccountForm/AccountForm.js.map +0 -1
- package/esm/components/AccountForm/AccountForm.d.ts +0 -11
- package/esm/components/AccountForm/AccountForm.js +0 -118
- package/esm/components/AccountForm/AccountForm.js.map +0 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
3
3
|
import { sanitizeText } from '../sanitizer';
|
|
4
4
|
import { getLocalConfig } from '../configuration';
|
|
5
|
-
import Alert from '../../components/ui/Alert';
|
|
6
5
|
import { useViseme } from '../../context/visemeContext';
|
|
7
6
|
import { IAudioContext } from 'standardized-audio-context';
|
|
7
|
+
import { isAndroid } from '../utils';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Configurazione per il TTS
|
|
@@ -15,7 +15,7 @@ export interface TTSConfig {
|
|
|
15
15
|
model?: string;
|
|
16
16
|
region?: string; // richiesto per Azure
|
|
17
17
|
tenant?: string; // Tenant identifier for multi-tenant applications
|
|
18
|
-
layout?: 'DEFAULT' | 'ZOOMED_FULL_BODY' | 'FULLPAGE';
|
|
18
|
+
layout?: 'DEFAULT' | 'ZOOMED_FULL_BODY' | 'FULLPAGE' | 'TOTEM';
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
type VisemeData = {
|
|
@@ -63,7 +63,6 @@ export function useTTS(
|
|
|
63
63
|
const { addViseme, resetVisemeQueue, startProcessing, stopProcessing } =
|
|
64
64
|
useViseme();
|
|
65
65
|
const [hasUserActivatedSpeak, setHasUserActivatedSpeak] = useState(false);
|
|
66
|
-
const [error, setError] = useState<Error | null>(null);
|
|
67
66
|
|
|
68
67
|
// Riferimenti
|
|
69
68
|
const audioRef = useRef<HTMLAudioElement | null>(null);
|
|
@@ -83,7 +82,7 @@ export function useTTS(
|
|
|
83
82
|
visemeLoadedRef.current = false;
|
|
84
83
|
|
|
85
84
|
if (visemeData && visemeData.length > 0) {
|
|
86
|
-
visemeData.forEach(viseme => {
|
|
85
|
+
visemeData.forEach((viseme) => {
|
|
87
86
|
addViseme(viseme.visemeId, viseme.audioOffset);
|
|
88
87
|
});
|
|
89
88
|
visemeLoadedRef.current = true;
|
|
@@ -219,224 +218,298 @@ export function useTTS(
|
|
|
219
218
|
}
|
|
220
219
|
}, [options.continuousSpeech, options.onEndSpeakStartListen]);
|
|
221
220
|
|
|
222
|
-
// Helper per creare i chunk
|
|
223
|
-
const createChunks = useCallback(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
|
229
|
-
const chunks: string[] = [];
|
|
230
|
-
let currentChunk = '';
|
|
231
|
-
|
|
232
|
-
for (const sentence of sentences) {
|
|
233
|
-
const sentenceWithPunct = sentence.trim() + '.';
|
|
234
|
-
if ((currentChunk + sentenceWithPunct).length > maxLength && currentChunk.length > 0) {
|
|
235
|
-
chunks.push(currentChunk.trim());
|
|
236
|
-
currentChunk = sentenceWithPunct;
|
|
237
|
-
} else {
|
|
238
|
-
currentChunk += sentenceWithPunct + ' ';
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (currentChunk.trim().length > 0) {
|
|
243
|
-
chunks.push(currentChunk.trim());
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return chunks;
|
|
247
|
-
}, []);
|
|
248
|
-
|
|
249
|
-
// Helper per riprodurre un singolo chunk
|
|
250
|
-
const speakChunk = useCallback(async (chunkText: string): Promise<void> => {
|
|
251
|
-
const response = await fetch(options.apiUrl || '/api/tts', {
|
|
252
|
-
method: 'POST',
|
|
253
|
-
headers: {
|
|
254
|
-
'Content-Type': 'application/json',
|
|
255
|
-
},
|
|
256
|
-
body: JSON.stringify({
|
|
257
|
-
text: chunkText,
|
|
258
|
-
tenant: config.tenant || 'www.aisuru.com',
|
|
259
|
-
voice: config.voice,
|
|
260
|
-
model: config.model || 'tts-1',
|
|
261
|
-
region: config.region,
|
|
262
|
-
provider: config.provider,
|
|
263
|
-
includeVisemes: config.layout === 'ZOOMED_FULL_BODY' || config.layout === 'FULLPAGE' || config.layout === 'DEFAULT',
|
|
264
|
-
}),
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
if (!response.ok) {
|
|
268
|
-
const errorData = await response.json().catch(() => ({}));
|
|
269
|
-
throw new Error(errorData.error || `API error: ${response.status}`);
|
|
270
|
-
}
|
|
221
|
+
// Helper per creare i chunk
|
|
222
|
+
const createChunks = useCallback(
|
|
223
|
+
(text: string, maxLength: number = 800): string[] => {
|
|
224
|
+
if (text.length <= maxLength) {
|
|
225
|
+
return [text];
|
|
226
|
+
}
|
|
271
227
|
|
|
272
|
-
|
|
273
|
-
|
|
228
|
+
const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
|
229
|
+
const chunks: string[] = [];
|
|
230
|
+
let currentChunk = '';
|
|
231
|
+
|
|
232
|
+
for (const sentence of sentences) {
|
|
233
|
+
const sentenceWithPunct = sentence.trim() + '.';
|
|
234
|
+
if (
|
|
235
|
+
(currentChunk + sentenceWithPunct).length > maxLength &&
|
|
236
|
+
currentChunk.length > 0
|
|
237
|
+
) {
|
|
238
|
+
chunks.push(currentChunk.trim());
|
|
239
|
+
currentChunk = sentenceWithPunct;
|
|
240
|
+
} else {
|
|
241
|
+
currentChunk += sentenceWithPunct + ' ';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
274
244
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
245
|
+
if (currentChunk.trim().length > 0) {
|
|
246
|
+
chunks.push(currentChunk.trim());
|
|
247
|
+
}
|
|
279
248
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
249
|
+
return chunks;
|
|
250
|
+
},
|
|
251
|
+
[]
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Helper per riprodurre un singolo chunk
|
|
255
|
+
// Helper function to handle text-to-speech for a single chunk of text
|
|
256
|
+
const speakChunk = useCallback(
|
|
257
|
+
async (chunkText: string): Promise<void> => {
|
|
258
|
+
// Make API request to TTS endpoint
|
|
259
|
+
const response = await fetch(options.apiUrl || '/api/tts', {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
headers: {
|
|
262
|
+
'Content-Type': 'application/json',
|
|
263
|
+
},
|
|
264
|
+
body: JSON.stringify({
|
|
265
|
+
text: chunkText,
|
|
266
|
+
tenant: config.tenant || 'www.aisuru.com',
|
|
267
|
+
voice: config.voice,
|
|
268
|
+
model: config.model || 'tts-1',
|
|
269
|
+
region: config.region,
|
|
270
|
+
provider: config.provider,
|
|
271
|
+
// Include viseme data for certain layout types that need lip sync
|
|
272
|
+
includeVisemes:
|
|
273
|
+
config.layout === 'ZOOMED_FULL_BODY' ||
|
|
274
|
+
config.layout === 'FULLPAGE' ||
|
|
275
|
+
config.layout === 'DEFAULT' ||
|
|
276
|
+
config.layout === 'TOTEM',
|
|
277
|
+
}),
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Handle API errors
|
|
281
|
+
if (!response.ok) {
|
|
282
|
+
const errorData = await response.json().catch(() => ({}));
|
|
283
|
+
throw new Error(errorData.error || `API error: ${response.status}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Get audio blob from response and create URL
|
|
287
|
+
const audioBlob = await response.blob();
|
|
288
|
+
const audioUrl = URL.createObjectURL(audioBlob);
|
|
289
|
+
|
|
290
|
+
// Clean up if speaking was cancelled
|
|
291
|
+
if (!isSpeakingRef.current || !isMountedRef.current) {
|
|
306
292
|
URL.revokeObjectURL(audioUrl);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Parse viseme data from response headers if available
|
|
297
|
+
let hasVisemeData = false;
|
|
298
|
+
const visemeDataHeader = response.headers.get('X-Viseme-Data');
|
|
299
|
+
if (visemeDataHeader) {
|
|
300
|
+
try {
|
|
301
|
+
const visemeData: VisemeData[] = JSON.parse(visemeDataHeader);
|
|
302
|
+
hasVisemeData = loadVisemeData(visemeData);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
// Silently handle error
|
|
310
305
|
}
|
|
311
|
-
|
|
312
|
-
resolve();
|
|
313
|
-
};
|
|
306
|
+
}
|
|
314
307
|
|
|
315
|
-
|
|
308
|
+
// Clean up if speaking was cancelled
|
|
309
|
+
if (!isSpeakingRef.current || !isMountedRef.current) {
|
|
316
310
|
URL.revokeObjectURL(audioUrl);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Create audio element with Android optimizations
|
|
315
|
+
const audio = new Audio();
|
|
316
|
+
audio.preload = 'auto'; // Force preloading for Android compatibility
|
|
317
|
+
audio.src = audioUrl;
|
|
318
|
+
audioRef.current = audio;
|
|
319
|
+
currentChunkAudioRef.current = audio;
|
|
320
|
+
|
|
321
|
+
// Create audio wrapper for viseme processing if needed
|
|
322
|
+
if (hasVisemeData) {
|
|
323
|
+
audioWrapperRef.current = createAudioWrapper();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Return promise that resolves when audio finishes playing
|
|
327
|
+
return new Promise<void>((resolve, reject) => {
|
|
328
|
+
if (!audioRef.current) {
|
|
329
|
+
reject(new Error('Audio element not found'));
|
|
330
|
+
return;
|
|
320
331
|
}
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
332
|
+
// Use Android-optimized event listener
|
|
333
|
+
const isAndroidDevice = isAndroid();
|
|
334
|
+
const audioEvent = isAndroidDevice ? 'canplay' : 'canplaythrough';
|
|
335
|
+
|
|
336
|
+
const handleCanPlay = async () => {
|
|
337
|
+
try {
|
|
338
|
+
// Check if playback was cancelled
|
|
339
|
+
if (!isSpeakingRef.current || !isMountedRef.current) {
|
|
340
|
+
URL.revokeObjectURL(audioUrl);
|
|
341
|
+
resolve();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Play audio first, then start viseme processing
|
|
346
|
+
try {
|
|
347
|
+
await audioRef.current?.play();
|
|
348
|
+
|
|
349
|
+
// Start viseme processing AFTER audio starts playing
|
|
350
|
+
if (hasVisemeData && audioWrapperRef.current) {
|
|
351
|
+
startProcessing(
|
|
352
|
+
audioWrapperRef.current as unknown as IAudioContext
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
} catch (playError) {
|
|
356
|
+
// Retry once for Android compatibility
|
|
357
|
+
await new Promise(r => setTimeout(r, 100));
|
|
358
|
+
await audioRef.current?.play();
|
|
359
|
+
|
|
360
|
+
if (hasVisemeData && audioWrapperRef.current) {
|
|
361
|
+
startProcessing(
|
|
362
|
+
audioWrapperRef.current as unknown as IAudioContext
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
} catch (e) {
|
|
367
|
+
// Clean up on error
|
|
368
|
+
URL.revokeObjectURL(audioUrl);
|
|
369
|
+
reject(e);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
audioRef.current.addEventListener(audioEvent, handleCanPlay, { once: true });
|
|
328
374
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
375
|
+
// When audio finishes playing
|
|
376
|
+
audioRef.current.onended = () => {
|
|
377
|
+
// Clean up resources
|
|
378
|
+
URL.revokeObjectURL(audioUrl);
|
|
379
|
+
if (currentChunkAudioRef.current === audio) {
|
|
380
|
+
currentChunkAudioRef.current = null;
|
|
381
|
+
}
|
|
382
|
+
resolve();
|
|
383
|
+
};
|
|
337
384
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
385
|
+
// Handle audio errors
|
|
386
|
+
audioRef.current.onerror = () => {
|
|
387
|
+
// Clean up resources
|
|
388
|
+
URL.revokeObjectURL(audioUrl);
|
|
389
|
+
if (currentChunkAudioRef.current === audio) {
|
|
390
|
+
currentChunkAudioRef.current = null;
|
|
391
|
+
}
|
|
392
|
+
reject(new Error('Audio playback failed'));
|
|
393
|
+
};
|
|
343
394
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
emitEndSpeakEvent();
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
395
|
+
// Start loading the audio
|
|
396
|
+
audioRef.current?.load();
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
[config, options, loadVisemeData, createAudioWrapper, startProcessing, isSpeakingRef, isMountedRef]
|
|
400
|
+
);
|
|
354
401
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
402
|
+
/**
|
|
403
|
+
* Sintetizza il testo in audio e lo riproduce
|
|
404
|
+
*/
|
|
405
|
+
const speak = useCallback(
|
|
406
|
+
async (text: string): Promise<void> => {
|
|
407
|
+
if (!isMountedRef.current) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
359
410
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
411
|
+
// Early exit conditions before setting speaking flag
|
|
412
|
+
if (!text || !text.trim() || options.preview) {
|
|
413
|
+
emitEndSpeakEvent();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
364
416
|
|
|
365
|
-
|
|
366
|
-
|
|
417
|
+
// If speaker is muted, completely disable TTS functionality
|
|
418
|
+
if (speakerMuted) {
|
|
419
|
+
// Still set hasUserActivatedSpeak to true when audio is disabled
|
|
420
|
+
// so the chat can start properly
|
|
421
|
+
if (!hasUserActivatedSpeak) {
|
|
422
|
+
setHasUserActivatedSpeak(true);
|
|
423
|
+
}
|
|
424
|
+
emitEndSpeakEvent();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
367
427
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
428
|
+
// Stop any existing playback first (before checking/setting speaking flag)
|
|
429
|
+
if (isPlaying) {
|
|
430
|
+
stop();
|
|
431
|
+
}
|
|
371
432
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
433
|
+
// Now check if we're already processing a request
|
|
434
|
+
if (isSpeakingRef.current) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
375
437
|
|
|
376
|
-
//
|
|
377
|
-
|
|
438
|
+
// Set the flag after all the early exits and cleanup
|
|
439
|
+
isSpeakingRef.current = true;
|
|
378
440
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
441
|
+
if (!hasUserActivatedSpeak) {
|
|
442
|
+
setHasUserActivatedSpeak(true);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
setIsPlaying(true);
|
|
447
|
+
|
|
448
|
+
// CHUNKING LOGIC: Dividi il testo in chunk se necessario
|
|
449
|
+
const chunks = createChunks(text, 500);
|
|
450
|
+
|
|
451
|
+
// Riproduci tutti i chunk in sequenza
|
|
452
|
+
// Il loop itera su ogni chunk di testo che deve essere riprodotto
|
|
453
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
454
|
+
// Controlla se il componente è ancora montato e se non è stato interrotto
|
|
455
|
+
if (!isSpeakingRef.current || !isMountedRef.current) {
|
|
456
|
+
break; // Interrompe il loop se il componente viene smontato
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Attende che il chunk corrente venga riprodotto prima di passare al successivo
|
|
460
|
+
await speakChunk(chunks[i]);
|
|
461
|
+
|
|
462
|
+
// Se ci sono altri chunk da riprodurre, aggiunge una piccola pausa
|
|
463
|
+
if (
|
|
464
|
+
i < chunks.length - 1 &&
|
|
465
|
+
isSpeakingRef.current &&
|
|
466
|
+
isMountedRef.current
|
|
467
|
+
) {
|
|
468
|
+
// Crea una Promise che si risolve dopo 300ms
|
|
469
|
+
// Questo crea una pausa tra un chunk e l'altro
|
|
470
|
+
// setTimeout viene wrappato in una Promise per poter usare await
|
|
471
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
472
|
+
}
|
|
385
473
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
474
|
+
|
|
475
|
+
setIsPlaying(false);
|
|
476
|
+
isSpeakingRef.current = false;
|
|
477
|
+
emitEndSpeakEvent();
|
|
478
|
+
|
|
479
|
+
// Dispatch custom event to notify MemoriWidget that audio has ended
|
|
480
|
+
const e = new CustomEvent('MemoriAudioEnded');
|
|
481
|
+
document.dispatchEvent(e);
|
|
482
|
+
} catch (err) {
|
|
483
|
+
setIsPlaying(false);
|
|
484
|
+
isSpeakingRef.current = false;
|
|
485
|
+
|
|
486
|
+
if (timeoutRef.current) {
|
|
487
|
+
clearTimeout(timeoutRef.current);
|
|
488
|
+
timeoutRef.current = null;
|
|
396
489
|
}
|
|
397
|
-
}
|
|
398
490
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
} catch (err) {
|
|
408
|
-
setIsPlaying(false);
|
|
409
|
-
isSpeakingRef.current = false;
|
|
410
|
-
|
|
411
|
-
if (timeoutRef.current) {
|
|
412
|
-
clearTimeout(timeoutRef.current);
|
|
413
|
-
timeoutRef.current = null;
|
|
491
|
+
cleanup();
|
|
492
|
+
|
|
493
|
+
emitEndSpeakEvent();
|
|
494
|
+
|
|
495
|
+
// Dispatch custom event to notify MemoriWidget that audio has ended
|
|
496
|
+
const e = new CustomEvent('MemoriAudioEnded');
|
|
497
|
+
document.dispatchEvent(e);
|
|
414
498
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
speakerMuted,
|
|
430
|
-
options.preview,
|
|
431
|
-
hasUserActivatedSpeak,
|
|
432
|
-
stop,
|
|
433
|
-
cleanup,
|
|
434
|
-
createChunks,
|
|
435
|
-
speakChunk,
|
|
436
|
-
emitEndSpeakEvent,
|
|
437
|
-
isPlaying,
|
|
438
|
-
]
|
|
439
|
-
);
|
|
499
|
+
},
|
|
500
|
+
[
|
|
501
|
+
config,
|
|
502
|
+
speakerMuted,
|
|
503
|
+
options.preview,
|
|
504
|
+
hasUserActivatedSpeak,
|
|
505
|
+
stop,
|
|
506
|
+
cleanup,
|
|
507
|
+
createChunks,
|
|
508
|
+
speakChunk,
|
|
509
|
+
emitEndSpeakEvent,
|
|
510
|
+
isPlaying,
|
|
511
|
+
]
|
|
512
|
+
);
|
|
440
513
|
/**
|
|
441
514
|
* Imposta lo stato del muto
|
|
442
515
|
*/
|
|
@@ -494,7 +567,5 @@ const speak = useCallback(
|
|
|
494
567
|
toggleMute,
|
|
495
568
|
hasUserActivatedSpeak,
|
|
496
569
|
setHasUserActivatedSpeak,
|
|
497
|
-
error,
|
|
498
|
-
setError,
|
|
499
570
|
};
|
|
500
571
|
}
|
package/src/index.stories.tsx
CHANGED
|
@@ -412,18 +412,19 @@ WithCustomUserAvatar.args = {
|
|
|
412
412
|
|
|
413
413
|
export const Test = Template.bind({});
|
|
414
414
|
Test.args = {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
415
|
+
memoriName: "test23",
|
|
416
|
+
ownerUserName: "Andrea-Patini",
|
|
417
|
+
memoriID: "bb672b4a-712a-4298-b440-61bfe5d67489",
|
|
418
|
+
ownerUserID: "91dbc9ba-b684-4fbe-9828-b5980af6cda9",
|
|
419
|
+
tenantID: "aisuru-staging.aclambda.online",
|
|
420
|
+
apiURL: "https://backend-staging.memori.ai/api/v2",
|
|
421
|
+
engineURL: "https://engine-staging.memori.ai/memori/v2",
|
|
422
|
+
baseURL: "https://aisuru-staging.aclambda.online",
|
|
423
|
+
uiLang: "it",
|
|
424
|
+
spokenLang: "IT",
|
|
425
|
+
layout: "FULLPAGE",
|
|
426
|
+
multilingual: "false",
|
|
427
|
+
showSettings: "true",
|
|
428
|
+
showShare: "true",
|
|
429
|
+
integrationID: "59c6f519-d02a-46e1-9bfb-ef8cf1203ebf",
|
|
429
430
|
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
|
-
import { User } from '@memori.ai/memori-api-client/dist/types';
|
|
3
|
-
import memoriApiClient from '@memori.ai/memori-api-client';
|
|
4
|
-
export interface Props {
|
|
5
|
-
user: User;
|
|
6
|
-
loginToken: string;
|
|
7
|
-
apiClient: ReturnType<typeof memoriApiClient>;
|
|
8
|
-
onUserUpdate: (user: User) => void;
|
|
9
|
-
}
|
|
10
|
-
declare const AccountForm: ({ user, loginToken, apiClient, onUserUpdate }: Props) => JSX.Element;
|
|
11
|
-
export default AccountForm;
|