@memori.ai/memori-react 7.19.2 → 7.21.1

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 (111) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
  3. package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
  4. package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
  5. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +19 -77
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +95 -70
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +747 -0
  13. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
  14. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
  15. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +60 -2
  16. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  17. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
  18. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
  19. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  20. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
  21. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js +68 -70
  22. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  23. package/dist/components/Avatar/AvatarView/index.d.ts +1 -1
  24. package/dist/components/Avatar/AvatarView/index.js +2 -2
  25. package/dist/components/Avatar/AvatarView/index.js.map +1 -1
  26. package/dist/components/ChatBubble/ChatBubble.css +9 -0
  27. package/dist/components/ChatBubble/ChatBubble.js +7 -1
  28. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  29. package/dist/components/MemoriWidget/MemoriWidget.js +130 -62
  30. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  31. package/dist/components/UploadButton/UploadButton.js +2 -2
  32. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  33. package/dist/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
  34. package/dist/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
  35. package/dist/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
  36. package/dist/context/visemeContext.js +0 -39
  37. package/dist/context/visemeContext.js.map +1 -1
  38. package/dist/locales/de.json +1 -0
  39. package/dist/locales/en.json +1 -0
  40. package/dist/locales/es.json +1 -0
  41. package/dist/locales/fr.json +1 -0
  42. package/dist/locales/it.json +1 -0
  43. package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
  44. package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
  45. package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
  46. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
  47. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +20 -78
  48. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  49. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
  50. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +99 -74
  51. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  52. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
  53. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +743 -0
  54. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
  55. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
  56. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +61 -3
  57. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  58. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
  59. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
  60. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  61. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
  62. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js +67 -69
  63. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  64. package/esm/components/Avatar/AvatarView/index.d.ts +1 -1
  65. package/esm/components/Avatar/AvatarView/index.js +2 -2
  66. package/esm/components/Avatar/AvatarView/index.js.map +1 -1
  67. package/esm/components/ChatBubble/ChatBubble.css +9 -0
  68. package/esm/components/ChatBubble/ChatBubble.js +7 -1
  69. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  70. package/esm/components/MemoriWidget/MemoriWidget.js +130 -62
  71. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  72. package/esm/components/UploadButton/UploadButton.js +2 -2
  73. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  74. package/esm/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
  75. package/esm/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
  76. package/esm/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
  77. package/esm/context/visemeContext.js +0 -39
  78. package/esm/context/visemeContext.js.map +1 -1
  79. package/esm/locales/de.json +1 -0
  80. package/esm/locales/en.json +1 -0
  81. package/esm/locales/es.json +1 -0
  82. package/esm/locales/fr.json +1 -0
  83. package/esm/locales/it.json +1 -0
  84. package/package.json +2 -2
  85. package/src/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.tsx +15 -8
  86. package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +64 -219
  87. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +221 -124
  88. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.ts +1250 -0
  89. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +164 -8
  90. package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +19 -17
  91. package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +80 -79
  92. package/src/components/Avatar/AvatarView/index.tsx +1 -7
  93. package/src/components/ChatBubble/ChatBubble.css +9 -0
  94. package/src/components/ChatBubble/ChatBubble.tsx +14 -2
  95. package/src/components/MemoriWidget/MemoriWidget.tsx +168 -76
  96. package/src/components/UploadButton/UploadButton.tsx +4 -4
  97. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +1 -1
  98. package/src/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
  99. package/src/components/WhyThisAnswer/WhyThisAnswer.stories.tsx +44 -3
  100. package/src/components/WhyThisAnswer/WhyThisAnswer.test.tsx +128 -8
  101. package/src/components/WhyThisAnswer/WhyThisAnswer.tsx +28 -3
  102. package/src/components/WhyThisAnswer/__snapshots__/WhyThisAnswer.test.tsx.snap +15 -1
  103. package/src/components/layouts/layouts.stories.tsx +0 -8
  104. package/src/context/visemeContext.tsx +40 -41
  105. package/src/index.stories.tsx +63 -65
  106. package/src/locales/de.json +1 -0
  107. package/src/locales/en.json +1 -0
  108. package/src/locales/es.json +1 -0
  109. package/src/locales/fr.json +1 -0
  110. package/src/locales/it.json +1 -0
  111. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.ts +0 -308
@@ -2202,26 +2202,20 @@ const MemoriWidget = ({
2202
2202
  cleanup();
2203
2203
  };
2204
2204
 
2205
- const cleanup = (): void => {
2206
- setIsPlayingAudio(false);
2207
- stopProcessing();
2208
- resetVisemeQueue();
2209
- memoriSpeaking = false;
2205
+ const cleanup = () => {
2206
+ if (recognizer) {
2207
+ recognizer.stopContinuousRecognitionAsync();
2208
+ recognizer.close();
2209
+ recognizer = null;
2210
+ }
2210
2211
 
2211
- try {
2212
- if (speechSynthesizer) {
2213
- const currentSynthesizer = speechSynthesizer;
2214
- speechSynthesizer = null; // Clear reference first
2215
- console.debug('Closing speech synthesizer');
2216
- currentSynthesizer.close();
2217
- }
2218
- } catch (error) {
2219
- console.debug('Error during synthesizer cleanup:', error);
2220
- // Even if close fails, ensure synthesizer is nullified
2212
+ if (speechSynthesizer) {
2213
+ speechSynthesizer.close();
2221
2214
  speechSynthesizer = null;
2222
2215
  }
2223
2216
 
2224
- emitEndSpeakEvent();
2217
+ setListening(false);
2218
+ clearListeningTimeout();
2225
2219
  };
2226
2220
 
2227
2221
  // Modify stopAudio to include speech state reset
@@ -2282,34 +2276,41 @@ const MemoriWidget = ({
2282
2276
 
2283
2277
  const resetTranscript = () => {
2284
2278
  setTranscript('');
2285
- // setIsProcessingSTT(false);
2286
2279
  };
2287
-
2280
+ // Modify setListeningTimeout to be more robust
2288
2281
  const setListeningTimeout = () => {
2289
- clearListeningTimeout();
2290
- const timeout = setTimeout(
2291
- handleTranscriptProcessing,
2292
- continuousSpeechTimeout * 1000 + 300
2293
- );
2282
+ clearListeningTimeout(); // Clear any existing timeout
2283
+
2284
+ console.debug('Setting speech processing timeout');
2285
+ const timeout = setTimeout(() => {
2286
+ console.debug('Speech timeout triggered, processing transcript');
2287
+ handleTranscriptProcessing();
2288
+ }, continuousSpeechTimeout * 1000 + 300);
2289
+
2294
2290
  setTranscriptTimeout(timeout as unknown as NodeJS.Timeout);
2295
2291
  };
2296
2292
 
2297
2293
  const clearListeningTimeout = () => {
2298
2294
  if (transcriptTimeout) {
2295
+ console.debug('Clearing transcript timeout');
2299
2296
  clearTimeout(transcriptTimeout);
2300
2297
  setTranscriptTimeout(null);
2301
2298
  }
2302
2299
  };
2303
2300
 
2301
+ // Add safety check in resetListeningTimeout
2304
2302
  const resetListeningTimeout = () => {
2305
2303
  clearListeningTimeout();
2306
- if (continuousSpeech) {
2304
+ if (continuousSpeech && !isProcessingSTT) {
2305
+ console.debug('Setting new listening timeout');
2307
2306
  setListeningTimeout();
2308
2307
  }
2309
2308
  };
2310
- // Modified useEffect to handle transcript changes
2309
+
2310
+ // Make sure only one path can trigger message sending
2311
2311
  useEffect(() => {
2312
- if (!isSpeaking) {
2312
+ if (!isSpeaking && transcript && transcript.length > 0) {
2313
+ console.debug('Transcript updated while not speaking, resetting timeout');
2313
2314
  resetListeningTimeout();
2314
2315
  resetInteractionTimeout();
2315
2316
  }
@@ -2325,87 +2326,126 @@ const MemoriWidget = ({
2325
2326
  /**
2326
2327
  * Listening methods
2327
2328
  */
2328
- /**
2329
- * Starts speech recognition using Azure Cognitive Services
2330
- * Sets up recognizer and begins continuous recognition
2331
- */
2329
+ let microphoneStream: MediaStream | null = null;
2330
+ // Modify startListening to ensure full cleanup before starting
2332
2331
  const startListening = async (): Promise<void> => {
2332
+ console.debug('Starting speech recognition...');
2333
+
2333
2334
  if (!AZURE_COGNITIVE_SERVICES_TTS_KEY) {
2335
+ console.error('No TTS key available');
2334
2336
  throw new Error('No TTS key available');
2335
2337
  }
2336
2338
 
2337
2339
  if (!sessionId) {
2340
+ console.error('No session ID available');
2338
2341
  throw new Error('No session ID available');
2339
2342
  }
2340
2343
 
2341
- // Ensure complete cleanup before starting, if it's already listening, stop it
2342
- cleanup();
2344
+ // First, ensure any existing recognizer is fully closed
2345
+ if (recognizer) {
2346
+ console.debug('Cleaning up existing recognizer...');
2347
+ try {
2348
+ // Stop the recognizer properly
2349
+ await new Promise<void>((resolve, _) => {
2350
+ recognizer?.stopContinuousRecognitionAsync(resolve, error => {
2351
+ console.error('Error stopping recognition:', error);
2352
+ resolve(); // Resolve anyway to continue cleanup
2353
+ });
2354
+ });
2355
+
2356
+ console.debug('Closing existing recognizer...');
2357
+ recognizer.close();
2358
+ recognizer = null;
2359
+ } catch (error) {
2360
+ console.error('Error during recognizer cleanup:', error);
2361
+ // Continue with initialization anyway
2362
+ }
2363
+ }
2364
+
2365
+ // Clear any existing state
2366
+ console.debug('Resetting transcript and STT state...');
2343
2367
  resetTranscript();
2368
+ setIsProcessingSTT(false);
2369
+
2370
+ // Add a small delay to ensure Azure services have time to release resources
2371
+ console.debug('Adding delay for Azure services cleanup...');
2372
+ await new Promise(resolve => setTimeout(resolve, 500));
2344
2373
 
2345
2374
  try {
2346
- // Add delay to ensure previous instance is fully cleaned up
2347
- // await new Promise(resolve => setTimeout(resolve, 300));
2375
+ console.debug('Requesting microphone access...');
2376
+ // Release previous microphone stream if it exists
2377
+ if (microphoneStream) {
2378
+ microphoneStream.getTracks().forEach(track => track.stop());
2379
+ microphoneStream = null;
2380
+ }
2348
2381
 
2349
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
2382
+ const stream = await navigator.mediaDevices.getUserMedia({
2383
+ audio: true,
2384
+ });
2350
2385
  setHasUserActivatedListening(true);
2351
2386
 
2352
2387
  // Recreate speech config each time
2388
+ console.debug('Setting up speech config...');
2353
2389
  speechConfig = setupSpeechConfig(AZURE_COGNITIVE_SERVICES_TTS_KEY);
2354
2390
 
2391
+ console.debug('Creating audio config and recognizer...');
2355
2392
  const audioConfig = speechSdk.AudioConfig.fromDefaultMicrophoneInput();
2356
2393
  recognizer = new speechSdk.SpeechRecognizer(speechConfig, audioConfig);
2357
2394
 
2358
2395
  // Set up recognizer event handlers
2396
+ console.debug('Setting up recognizer handlers...');
2359
2397
  setupRecognizerHandlers(recognizer);
2360
2398
 
2361
- // Start recognition
2362
- setListening(true);
2363
- recognizer.startContinuousRecognitionAsync();
2364
-
2365
- recognizer.canceled = (_s, e) => {
2366
- if (e.reason === speechSdk.CancellationReason.Error) {
2367
- console.debug(`"CANCELED: ErrorCode=${e.errorCode}`);
2368
- console.debug(`"CANCELED: ErrorDetails=${e.errorDetails}`);
2369
- console.debug(
2370
- 'CANCELED: Did you set the speech resource key and region values?'
2371
- );
2372
- stopListening();
2373
- cleanup();
2374
- }
2375
-
2376
- stopListening();
2377
- };
2399
+ // Start recognition - use promises for better error handling
2400
+ console.debug('Starting continuous recognition...');
2401
+ await new Promise<void>((resolve, reject) => {
2402
+ recognizer?.startContinuousRecognitionAsync(resolve, error => {
2403
+ console.error('Failed to start recognition:', error);
2404
+ reject(error);
2405
+ });
2406
+ });
2378
2407
 
2379
- recognizer.sessionStopped = (_s, _e) => {
2380
- stopListening();
2381
- resetTranscript();
2382
- };
2408
+ console.debug('Speech recognition started successfully');
2409
+ setListening(true);
2383
2410
  } catch (error) {
2384
2411
  console.error('Error in startListening:', error);
2385
- stopListening();
2412
+ // Ensure cleanup happens even on error
2413
+ if (recognizer) {
2414
+ console.debug('Cleaning up recognizer after error...');
2415
+ recognizer.close();
2416
+ recognizer = null;
2417
+ }
2418
+ setListening(false);
2386
2419
  throw error;
2387
2420
  }
2388
2421
  };
2389
2422
 
2390
2423
  const setupSpeechConfig = (AZURE_COGNITIVE_SERVICES_TTS_KEY: string) => {
2424
+ console.debug('Creating speech config...');
2391
2425
  speechConfig = speechSdk.SpeechConfig.fromSubscription(
2392
2426
  AZURE_COGNITIVE_SERVICES_TTS_KEY,
2393
2427
  'westeurope'
2394
2428
  );
2429
+ console.debug('Setting speech recognition language:', userLang);
2395
2430
  speechConfig.speechRecognitionLanguage = getCultureCodeByLanguage(userLang);
2396
2431
  speechConfig.speechSynthesisLanguage = getCultureCodeByLanguage(userLang);
2397
2432
  speechConfig.speechSynthesisVoiceName = getTTSVoice(userLang); // https://docs.microsoft.com/it-it/azure/cognitive-services/speech-service/language-support#text-to-speech
2398
2433
  return speechConfig;
2399
2434
  };
2400
2435
 
2436
+ const [isProcessingSTT, setIsProcessingSTT] = useState(false);
2437
+
2401
2438
  const setupRecognizerHandlers = (recognizer: speechSdk.SpeechRecognizer) => {
2402
2439
  if (recognizer) {
2440
+ console.debug('Setting up recognizer event handlers...');
2403
2441
  recognizer.recognized = (_, event) => {
2404
2442
  // Process the recognized speech result
2443
+ console.debug('Recognition event received');
2405
2444
  handleRecognizedSpeech(event.result.text);
2406
2445
  };
2407
2446
 
2408
2447
  // Configure speech recognition properties directly on the recognizer
2448
+ console.debug('Configuring recognizer properties...');
2409
2449
  recognizer.properties.setProperty(
2410
2450
  'SpeechServiceResponse_JsonResult',
2411
2451
  'true'
@@ -2423,32 +2463,66 @@ const MemoriWidget = ({
2423
2463
  }
2424
2464
  };
2425
2465
 
2426
- const handleRecognizedSpeech = (text: string) => {
2427
- console.debug('Handling recognized speech:', text);
2466
+ // Add a mutex-like flag to prevent duplicate processing
2467
+ let isProcessingSpeech = false;
2428
2468
 
2429
- if (!text || text.trim().length === 0) {
2430
- console.debug('No valid text received from speech recognition');
2469
+ // Create a single, centralized function to process and send messages
2470
+ const processSpeechAndSendMessage = (text: string) => {
2471
+ // Skip if already processing or no text
2472
+ if (isProcessingSpeech || !text || text.trim().length === 0) {
2473
+ console.debug(
2474
+ 'Skipping speech processing: already processing or empty text'
2475
+ );
2431
2476
  return;
2432
2477
  }
2433
2478
 
2479
+ try {
2480
+ // Set processing flag immediately
2481
+ isProcessingSpeech = true;
2482
+
2483
+ // Process the text
2484
+ const message = stripDuplicates(text);
2485
+ console.debug('Processing speech message:', message);
2486
+
2487
+ if (message.length > 0) {
2488
+ // Update UI states
2489
+ setIsProcessingSTT(true);
2490
+ setUserMessage('');
2491
+
2492
+ // Send the message
2493
+ console.debug('Sending message:', message);
2494
+ sendMessage(message);
2495
+
2496
+ // Reset states
2497
+ resetTranscript();
2498
+ clearListening();
2499
+ }
2500
+ } finally {
2501
+ // Reset processing flag after a short delay to prevent race conditions
2502
+ setTimeout(() => {
2503
+ isProcessingSpeech = false;
2504
+ }, 1000);
2505
+ }
2506
+ };
2507
+
2508
+ // Update handleRecognizedSpeech to use the centralized function
2509
+ const handleRecognizedSpeech = (text: string) => {
2510
+ console.debug('Speech recognized:', text);
2434
2511
  setTranscript(text);
2435
2512
  setIsSpeaking(false);
2436
2513
 
2437
- const message = stripDuplicates(text);
2438
- console.debug('Stripped message:', message);
2439
- if (message.length > 0) {
2440
- setUserMessage(message);
2514
+ // Don't process here - wait for timeout or explicit processing
2515
+ if (!continuousSpeech) {
2516
+ // For manual mode, process immediately
2517
+ processSpeechAndSendMessage(text);
2441
2518
  }
2519
+ // For continuous mode, rely on the timeout
2442
2520
  };
2443
2521
 
2444
- // Helper function to handle transcript processing
2522
+ // Update handleTranscriptProcessing to use the centralized function
2445
2523
  const handleTranscriptProcessing = () => {
2446
- const message = stripDuplicates(transcript);
2447
- if (message.length > 0 && listening) {
2448
- sendMessage(message);
2449
- resetTranscript();
2450
- setUserMessage('');
2451
- clearListening();
2524
+ if (transcript && transcript.length > 0 && listening) {
2525
+ processSpeechAndSendMessage(transcript);
2452
2526
  } else if (listening) {
2453
2527
  resetInteractionTimeout();
2454
2528
  }
@@ -2458,14 +2532,32 @@ const MemoriWidget = ({
2458
2532
  * Stops the speech recognition process
2459
2533
  * Closes recognizer and cleans up resources
2460
2534
  */
2461
- const stopListening = () => {
2535
+ // Similarly, modify stopListening to use promises
2536
+ // Enhance stopListening to fully release resources
2537
+ const stopListening = async () => {
2462
2538
  console.debug('Stopping speech recognition');
2539
+
2540
+ // Stop the recognizer
2463
2541
  if (recognizer) {
2464
- // Stop continuous recognition and close the recognizer
2465
- recognizer.stopContinuousRecognitionAsync();
2466
- recognizer.close();
2542
+ try {
2543
+ recognizer.stopContinuousRecognitionAsync();
2544
+ recognizer.close();
2545
+ } catch (error) {
2546
+ console.error('Error stopping recognizer:', error);
2547
+ }
2467
2548
  recognizer = null;
2468
2549
  }
2550
+
2551
+ // Release the microphone stream
2552
+ if (microphoneStream) {
2553
+ try {
2554
+ microphoneStream.getTracks().forEach(track => track.stop());
2555
+ } catch (error) {
2556
+ console.error('Error stopping microphone stream:', error);
2557
+ }
2558
+ microphoneStream = null;
2559
+ }
2560
+
2469
2561
  setListening(false);
2470
2562
  };
2471
2563
 
@@ -39,7 +39,7 @@ const FileUploadButton = ({
39
39
  }) => {
40
40
  // State for loading indicator
41
41
  const [isLoading, setIsLoading] = useState(false);
42
- // State for tracking upload errors
42
+ // State for tracking upload errors
43
43
  const [errors, setErrors] = useState<UploadError[]>([]);
44
44
  // Reference to hidden file input
45
45
  const fileInputRef = useRef<HTMLInputElement>(null);
@@ -122,7 +122,7 @@ const FileUploadButton = ({
122
122
  const validateFile = (file: File): boolean => {
123
123
  const fileExt = `.${file.name.split('.').pop()?.toLowerCase()}`;
124
124
  const ALLOWED_FILE_TYPES = ['.pdf', '.txt'];
125
-
125
+
126
126
  if (!ALLOWED_FILE_TYPES.includes(fileExt)) {
127
127
  addError({
128
128
  message: `File type "${fileExt}" is not supported. Please use: ${ALLOWED_FILE_TYPES.join(
@@ -161,7 +161,7 @@ const FileUploadButton = ({
161
161
 
162
162
  if (fileExt === 'pdf') {
163
163
  text = await extractTextFromPDF(file);
164
- } else if (fileExt === 'txt') {
164
+ } else if (fileExt === 'txt' || fileExt === 'json') {
165
165
  text = await file.text();
166
166
  }
167
167
 
@@ -241,7 +241,7 @@ const FileUploadButton = ({
241
241
  <input
242
242
  ref={fileInputRef}
243
243
  type="file"
244
- accept=".pdf,.txt"
244
+ accept=".pdf,.txt,.json"
245
245
  className="memori--upload-file-input"
246
246
  onChange={handleFileSelect}
247
247
  multiple
@@ -6,7 +6,7 @@ exports[`renders UploadButton unchanged 1`] = `
6
6
  class="relative file-upload-wrapper"
7
7
  >
8
8
  <input
9
- accept=".pdf,.txt"
9
+ accept=".pdf,.txt,.json"
10
10
  class="memori--upload-file-input"
11
11
  multiple=""
12
12
  type="file"
@@ -126,3 +126,46 @@ div.memori--whythisanswer-skeleton-text {
126
126
  margin-bottom: 0.5rem;
127
127
  background: #ccc;
128
128
  }
129
+
130
+ .memori--whythisanswer-title-text {
131
+ width: 100%;
132
+ }
133
+
134
+ .memori--whythisanswer-title-text-top {
135
+ display: flex;
136
+ align-items: space-between;
137
+ gap: 0.5rem;
138
+ }
139
+
140
+ .memori--whythisanswer-title-text-top p {
141
+ margin: 0;
142
+ font-size: 0.75rem;
143
+ }
144
+
145
+ .memori--whythisanswer-contextvars {
146
+ display: flex;
147
+ flex-wrap: wrap;
148
+ margin-top: 0.5rem;
149
+ }
150
+
151
+ .memori--whythisanswer-contextvars-card {
152
+ max-width: max-content;
153
+ padding: 0.5rem;
154
+ box-shadow: 0 0 0.25rem var(--memori-primary);
155
+ font-size: 0.75rem;
156
+ }
157
+
158
+ .memori--whythisanswer-contextvars-card + .memori--whythisanswer-contextvars-card {
159
+ margin-left: 0.5rem;
160
+ }
161
+
162
+ .memori--whythisanswer-contextvars-card > div > div {
163
+ padding: 0 !important;
164
+ }
165
+
166
+ .memori--whythisanswer-title-text-top-container {
167
+ display: flex;
168
+ justify-content: space-between;
169
+ margin-bottom: 8px;
170
+ gap: 0.5rem;
171
+ }
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
2
  import { Meta, Story } from '@storybook/react';
3
- import { sessionID, memoryQuestion } from '../../mocks/data';
3
+ import { sessionID } from '../../mocks/data';
4
4
  import I18nWrapper from '../../I18nWrapper';
5
5
  import WhyThisAnswer, { Props } from './WhyThisAnswer';
6
+ import { SearchMatches } from '@memori.ai/memori-api-client/dist/types';
6
7
 
7
8
  import './WhyThisAnswer.css';
8
- import { SearchMatches } from '@memori.ai/memori-api-client/dist/types';
9
9
 
10
10
  const meta: Meta = {
11
11
  title: 'Why This Answer',
@@ -69,6 +69,7 @@ WithData.args = {
69
69
  confidenceLevel: 'HIGH',
70
70
  memory: {
71
71
  memoryID: '1',
72
+ memoryType: 'Question',
72
73
  title: 'This is the title of the content',
73
74
  titleVariants: [
74
75
  "This is a variant of the content's title",
@@ -89,6 +90,7 @@ WithData.args = {
89
90
  confidenceLevel: 'LOW',
90
91
  memory: {
91
92
  memoryID: '2',
93
+ memoryType: 'Question',
92
94
  title: 'Content with a long answer',
93
95
  titleVariants: undefined,
94
96
  answers: [
@@ -105,6 +107,7 @@ WithData.args = {
105
107
  memoryID: '3',
106
108
  title: 'Content with sources',
107
109
  titleVariants: undefined,
110
+ memoryType: 'Question',
108
111
  answers: [
109
112
  {
110
113
  text: 'This is a test answer',
@@ -130,9 +133,9 @@ WithData.args = {
130
133
  confidence: 0.7,
131
134
  confidenceLevel: 'MEDIUM',
132
135
  memory: {
133
- ...memoryQuestion,
134
136
  memoryID: '4',
135
137
  title: 'Content with links',
138
+ memoryType: 'Question',
136
139
  titleVariants: undefined,
137
140
  answers: [
138
141
  {
@@ -155,5 +158,43 @@ WithData.args = {
155
158
  ],
156
159
  },
157
160
  } as SearchMatches,
161
+ {
162
+ confidence: 0.7,
163
+ confidenceLevel: 'MEDIUM',
164
+ memory: {
165
+ memoryID: '5',
166
+ memoryType: 'Question',
167
+ title: 'Content with receiver',
168
+ titleVariants: undefined,
169
+ receiverName: 'receiver',
170
+ receiverTag: '🧑‍💻',
171
+ answers: [
172
+ {
173
+ text: 'This is a an answer',
174
+ },
175
+ ],
176
+ media: [],
177
+ },
178
+ } as SearchMatches,
179
+ {
180
+ confidence: 0.5,
181
+ confidenceLevel: 'MEDIUM',
182
+ memory: {
183
+ memoryID: '5',
184
+ memoryType: 'Question',
185
+ title: 'Content with context',
186
+ titleVariants: undefined,
187
+ contextVars: {
188
+ KEY1: 'VALUE1',
189
+ KEY2: 'VALUE2',
190
+ },
191
+ answers: [
192
+ {
193
+ text: 'This is a an answer',
194
+ },
195
+ ],
196
+ media: [],
197
+ },
198
+ } as SearchMatches,
158
199
  ],
159
200
  };