@memori.ai/memori-react 8.7.8 → 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.
Files changed (85) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/components/ChatBubble/ChatBubble.js +5 -1
  3. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  4. package/dist/components/Header/Header.js +28 -14
  5. package/dist/components/Header/Header.js.map +1 -1
  6. package/dist/components/MemoriWidget/MemoriWidget.js +49 -15
  7. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  8. package/dist/components/MicrophoneButton/MicrophoneButton.css +36 -3
  9. package/dist/components/MicrophoneButton/MicrophoneButton.js +25 -6
  10. package/dist/components/MicrophoneButton/MicrophoneButton.js.map +1 -1
  11. package/dist/components/SettingsDrawer/SettingsDrawer.d.ts +1 -1
  12. package/dist/components/SettingsDrawer/SettingsDrawer.js +2 -4
  13. package/dist/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  14. package/dist/components/StartPanel/StartPanel.css +2 -0
  15. package/dist/components/StartPanel/StartPanel.js +2 -1
  16. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  17. package/dist/components/layouts/chat.css +0 -20
  18. package/dist/context/visemeContext.d.ts +1 -1
  19. package/dist/context/visemeContext.js +1 -1
  20. package/dist/helpers/message.js +11 -0
  21. package/dist/helpers/message.js.map +1 -1
  22. package/dist/helpers/stt/useSTT.d.ts +0 -6
  23. package/dist/helpers/stt/useSTT.js +7 -137
  24. package/dist/helpers/stt/useSTT.js.map +1 -1
  25. package/dist/helpers/tts/useTTS.d.ts +1 -3
  26. package/dist/helpers/tts/useTTS.js +60 -18
  27. package/dist/helpers/tts/useTTS.js.map +1 -1
  28. package/esm/components/ChatBubble/ChatBubble.js +5 -1
  29. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  30. package/esm/components/Header/Header.js +28 -14
  31. package/esm/components/Header/Header.js.map +1 -1
  32. package/esm/components/MemoriWidget/MemoriWidget.js +49 -15
  33. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  34. package/esm/components/MicrophoneButton/MicrophoneButton.css +36 -3
  35. package/esm/components/MicrophoneButton/MicrophoneButton.js +25 -6
  36. package/esm/components/MicrophoneButton/MicrophoneButton.js.map +1 -1
  37. package/esm/components/SettingsDrawer/SettingsDrawer.d.ts +1 -1
  38. package/esm/components/SettingsDrawer/SettingsDrawer.js +2 -4
  39. package/esm/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  40. package/esm/components/StartPanel/StartPanel.css +2 -0
  41. package/esm/components/StartPanel/StartPanel.js +2 -1
  42. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  43. package/esm/components/layouts/chat.css +0 -20
  44. package/esm/context/visemeContext.d.ts +1 -1
  45. package/esm/context/visemeContext.js +1 -1
  46. package/esm/helpers/message.js +11 -0
  47. package/esm/helpers/message.js.map +1 -1
  48. package/esm/helpers/stt/useSTT.d.ts +0 -6
  49. package/esm/helpers/stt/useSTT.js +7 -137
  50. package/esm/helpers/stt/useSTT.js.map +1 -1
  51. package/esm/helpers/tts/useTTS.d.ts +1 -3
  52. package/esm/helpers/tts/useTTS.js +60 -18
  53. package/esm/helpers/tts/useTTS.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/components/ChatBubble/ChatBubble.stories.tsx +25 -0
  56. package/src/components/ChatBubble/ChatBubble.test.tsx +17 -0
  57. package/src/components/ChatBubble/ChatBubble.tsx +8 -1
  58. package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +142 -5
  59. package/src/components/ChatInputs/__snapshots__/ChatInputs.test.tsx.snap +100 -90
  60. package/src/components/Header/Header.tsx +43 -30
  61. package/src/components/MemoriWidget/MemoriWidget.tsx +66 -26
  62. package/src/components/MicrophoneButton/MicrophoneButton.css +36 -3
  63. package/src/components/MicrophoneButton/MicrophoneButton.tsx +44 -18
  64. package/src/components/SettingsDrawer/SettingsDrawer.tsx +4 -11
  65. package/src/components/StartPanel/StartPanel.css +2 -0
  66. package/src/components/StartPanel/StartPanel.tsx +21 -17
  67. package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +99 -44
  68. package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +9 -4
  69. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +18 -8
  70. package/src/components/layouts/__snapshots__/HiddenChat.test.tsx.snap +9 -4
  71. package/src/components/layouts/__snapshots__/Totem.test.tsx.snap +9 -4
  72. package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +9 -4
  73. package/src/components/layouts/chat.css +0 -20
  74. package/src/context/visemeContext.tsx +1 -1
  75. package/src/helpers/message.test.ts +14 -0
  76. package/src/helpers/message.ts +32 -1
  77. package/src/helpers/stt/useSTT.ts +43 -253
  78. package/src/helpers/tts/useTTS.ts +273 -202
  79. package/src/index.stories.tsx +30 -14
  80. package/dist/components/AccountForm/AccountForm.d.ts +0 -11
  81. package/dist/components/AccountForm/AccountForm.js +0 -121
  82. package/dist/components/AccountForm/AccountForm.js.map +0 -1
  83. package/esm/components/AccountForm/AccountForm.d.ts +0 -11
  84. package/esm/components/AccountForm/AccountForm.js +0 -118
  85. 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((text: string, maxLength: number = 800): string[] => {
224
- if (text.length <= maxLength) {
225
- return [text];
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
- const audioBlob = await response.blob();
273
- const audioUrl = URL.createObjectURL(audioBlob);
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
- if (!isSpeakingRef.current || !isMountedRef.current) {
276
- URL.revokeObjectURL(audioUrl);
277
- return;
278
- }
245
+ if (currentChunk.trim().length > 0) {
246
+ chunks.push(currentChunk.trim());
247
+ }
279
248
 
280
- // Crea un nuovo Audio element per riprodurre il chunk corrente
281
- const audio = new Audio(audioUrl);
282
-
283
- // Track the current chunk audio element
284
- currentChunkAudioRef.current = audio;
285
-
286
- return new Promise<void>((resolve, reject) => {
287
- // Quando l'audio è pronto per essere riprodotto
288
- audio.oncanplaythrough = async () => {
289
- try {
290
- // Controlla se il componente è ancora montato e se non è stato interrotto
291
- if (!isSpeakingRef.current || !isMountedRef.current) {
292
- URL.revokeObjectURL(audioUrl);
293
- resolve();
294
- return;
295
- }
296
- // Riproduce l'audio
297
- await audio.play();
298
- } catch (e) {
299
- URL.revokeObjectURL(audioUrl);
300
- reject(e);
301
- }
302
- };
303
-
304
- // Quando l'audio termina di riprodurre
305
- audio.onended = () => {
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
- // Clear the current chunk audio reference
308
- if (currentChunkAudioRef.current === audio) {
309
- currentChunkAudioRef.current = null;
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
- // Risolve la Promise quando l'audio termina di riprodurre
312
- resolve();
313
- };
306
+ }
314
307
 
315
- audio.onerror = () => {
308
+ // Clean up if speaking was cancelled
309
+ if (!isSpeakingRef.current || !isMountedRef.current) {
316
310
  URL.revokeObjectURL(audioUrl);
317
- // Clear the current chunk audio reference
318
- if (currentChunkAudioRef.current === audio) {
319
- currentChunkAudioRef.current = null;
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
- // Rifiuta la Promise se l'audio fallisce
322
- reject(new Error('Audio playback failed'));
323
- };
324
-
325
- audio.load();
326
- });
327
- }, [config, options]);
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
- * Sintetizza il testo in audio e lo riproduce
331
- */
332
- const speak = useCallback(
333
- async (text: string): Promise<void> => {
334
- if (!isMountedRef.current) {
335
- return;
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
- // Early exit conditions before setting speaking flag
339
- if (!text || !text.trim() || options.preview) {
340
- emitEndSpeakEvent();
341
- return;
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
- // If speaker is muted, completely disable TTS functionality
345
- if (speakerMuted) {
346
- // Still set hasUserActivatedSpeak to true when audio is disabled
347
- // so the chat can start properly
348
- if (!hasUserActivatedSpeak) {
349
- setHasUserActivatedSpeak(true);
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
- // Stop any existing playback first (before checking/setting speaking flag)
356
- if (isPlaying) {
357
- stop();
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
- // Now check if we're already processing a request
361
- if (isSpeakingRef.current) {
362
- return;
363
- }
411
+ // Early exit conditions before setting speaking flag
412
+ if (!text || !text.trim() || options.preview) {
413
+ emitEndSpeakEvent();
414
+ return;
415
+ }
364
416
 
365
- // Set the flag after all the early exits and cleanup
366
- isSpeakingRef.current = true;
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
- if (!hasUserActivatedSpeak) {
369
- setHasUserActivatedSpeak(true);
370
- }
428
+ // Stop any existing playback first (before checking/setting speaking flag)
429
+ if (isPlaying) {
430
+ stop();
431
+ }
371
432
 
372
- try {
373
- setIsPlaying(true);
374
- setError(null);
433
+ // Now check if we're already processing a request
434
+ if (isSpeakingRef.current) {
435
+ return;
436
+ }
375
437
 
376
- // CHUNKING LOGIC: Dividi il testo in chunk se necessario
377
- const chunks = createChunks(text, 800);
438
+ // Set the flag after all the early exits and cleanup
439
+ isSpeakingRef.current = true;
378
440
 
379
- // Riproduci tutti i chunk in sequenza
380
- // Il loop itera su ogni chunk di testo che deve essere riprodotto
381
- for (let i = 0; i < chunks.length; i++) {
382
- // Controlla se il componente è ancora montato e se non è stato interrotto
383
- if (!isSpeakingRef.current || !isMountedRef.current) {
384
- break; // Interrompe il loop se il componente viene smontato
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
- // Attende che il chunk corrente venga riprodotto prima di passare al successivo
388
- await speakChunk(chunks[i]);
389
-
390
- // Se ci sono altri chunk da riprodurre, aggiunge una piccola pausa
391
- if (i < chunks.length - 1 && isSpeakingRef.current && isMountedRef.current) {
392
- // Crea una Promise che si risolve dopo 300ms
393
- // Questo crea una pausa tra un chunk e l'altro
394
- // setTimeout viene wrappato in una Promise per poter usare await
395
- await new Promise(resolve => setTimeout(resolve, 300));
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
- setIsPlaying(false);
400
- isSpeakingRef.current = false;
401
- emitEndSpeakEvent();
402
-
403
- // Dispatch custom event to notify MemoriWidget that audio has ended
404
- const e = new CustomEvent('MemoriAudioEnded');
405
- document.dispatchEvent(e);
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
- cleanup();
417
- const errorMsg = err instanceof Error ? err : new Error(String(err));
418
- setError(errorMsg);
419
-
420
- emitEndSpeakEvent();
421
-
422
- // Dispatch custom event to notify MemoriWidget that audio has ended
423
- const e = new CustomEvent('MemoriAudioEnded');
424
- document.dispatchEvent(e);
425
- }
426
- },
427
- [
428
- config,
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
  }
@@ -39,6 +39,21 @@ Anonymous.args = {
39
39
  showChatHistory: true,
40
40
  };
41
41
 
42
+ export const Vaccaboia = Template.bind({});
43
+ Vaccaboia.args = {
44
+ ownerUserName: 'francesco.timo',
45
+ memoriName: 'AI-ssistantSafe',
46
+ tenantID: 'tecne.aclambda.online',
47
+ engineURL: 'https://engine.memori.ai',
48
+ apiURL: 'https://backend.memori.ai',
49
+ baseURL: 'https://www.aisuru.com',
50
+ uiLang: 'IT',
51
+ spokenLang: 'IT',
52
+ enableAudio: true,
53
+ autoStart: false,
54
+ showChatHistory: true,
55
+ };
56
+
42
57
  export const WithInitialContextAndQuestion = Template.bind({});
43
58
  WithInitialContextAndQuestion.args = {
44
59
  ownerUserName: 'nzambello',
@@ -397,18 +412,19 @@ WithCustomUserAvatar.args = {
397
412
 
398
413
  export const Test = Template.bind({});
399
414
  Test.args = {
400
- memoriName: 'Test Artifact',
401
- ownerUserName: 'Andrea-Patini',
402
- memoriID: '8cec936c-d440-4781-91df-e4ddf044255e',
403
- ownerUserID: '91dbc9ba-b684-4fbe-9828-b5980af6cda9',
404
- tenantID: 'aisuru-staging.aclambda.online',
405
- engineURL: 'https://engine-staging.memori.ai/memori/v2',
406
- apiURL: 'https://backend-staging.memori.ai/api/v2',
407
- baseURL: 'https://aisuru-staging.aclambda.online',
408
- layout: 'FULLPAGE',
409
- uiLang: 'EN',
410
- spokenLang: 'EN',
411
- showLogin: true,
412
- enableAudio: false,
413
- integrationID: '2b37c25d-8cf9-456f-b9ee-2c27dc4d54fc',
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",
414
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;