@memori.ai/memori-react 7.19.1 → 7.21.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 (145) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/components/Avatar/Avatar.js +3 -3
  3. package/dist/components/Avatar/Avatar.js.map +1 -1
  4. package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
  5. package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +19 -77
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +95 -70
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  13. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
  14. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +747 -0
  15. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
  16. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
  17. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +60 -2
  18. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  19. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
  20. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
  21. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  22. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
  23. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js +68 -70
  24. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  25. package/dist/components/Avatar/AvatarView/index.d.ts +1 -1
  26. package/dist/components/Avatar/AvatarView/index.js +2 -2
  27. package/dist/components/Avatar/AvatarView/index.js.map +1 -1
  28. package/dist/components/Chat/Chat.js +2 -2
  29. package/dist/components/Chat/Chat.js.map +1 -1
  30. package/dist/components/ChatBubble/ChatBubble.js +12 -9
  31. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  32. package/dist/components/ExpertsDrawer/ExpertsDrawer.js +1 -1
  33. package/dist/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
  34. package/dist/components/LoginDrawer/LoginDrawer.js +6 -6
  35. package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -1
  36. package/dist/components/MemoriWidget/MemoriWidget.js +143 -64
  37. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  38. package/dist/components/SignupForm/SignupForm.js +4 -4
  39. package/dist/components/SignupForm/SignupForm.js.map +1 -1
  40. package/dist/components/StartPanel/StartPanel.js +5 -5
  41. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  42. package/dist/components/UploadButton/UploadButton.js +2 -2
  43. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  44. package/dist/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
  45. package/dist/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
  46. package/dist/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
  47. package/dist/context/visemeContext.js +0 -39
  48. package/dist/context/visemeContext.js.map +1 -1
  49. package/dist/index.js +4 -3
  50. package/dist/index.js.map +1 -1
  51. package/dist/locales/de.json +1 -0
  52. package/dist/locales/en.json +1 -0
  53. package/dist/locales/es.json +1 -0
  54. package/dist/locales/fr.json +1 -0
  55. package/dist/locales/it.json +1 -0
  56. package/esm/components/Avatar/Avatar.js +3 -3
  57. package/esm/components/Avatar/Avatar.js.map +1 -1
  58. package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
  59. package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
  60. package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
  61. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
  62. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +20 -78
  63. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  64. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
  65. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +99 -74
  66. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  67. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
  68. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +743 -0
  69. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
  70. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
  71. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +61 -3
  72. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  73. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
  74. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
  75. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  76. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
  77. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js +67 -69
  78. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  79. package/esm/components/Avatar/AvatarView/index.d.ts +1 -1
  80. package/esm/components/Avatar/AvatarView/index.js +2 -2
  81. package/esm/components/Avatar/AvatarView/index.js.map +1 -1
  82. package/esm/components/Chat/Chat.js +2 -2
  83. package/esm/components/Chat/Chat.js.map +1 -1
  84. package/esm/components/ChatBubble/ChatBubble.js +12 -9
  85. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  86. package/esm/components/ExpertsDrawer/ExpertsDrawer.js +1 -1
  87. package/esm/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
  88. package/esm/components/LoginDrawer/LoginDrawer.js +6 -6
  89. package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -1
  90. package/esm/components/MemoriWidget/MemoriWidget.js +143 -64
  91. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  92. package/esm/components/SignupForm/SignupForm.js +4 -4
  93. package/esm/components/SignupForm/SignupForm.js.map +1 -1
  94. package/esm/components/StartPanel/StartPanel.js +5 -5
  95. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  96. package/esm/components/UploadButton/UploadButton.js +2 -2
  97. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  98. package/esm/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
  99. package/esm/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
  100. package/esm/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
  101. package/esm/context/visemeContext.js +0 -39
  102. package/esm/context/visemeContext.js.map +1 -1
  103. package/esm/index.js +4 -3
  104. package/esm/index.js.map +1 -1
  105. package/esm/locales/de.json +1 -0
  106. package/esm/locales/en.json +1 -0
  107. package/esm/locales/es.json +1 -0
  108. package/esm/locales/fr.json +1 -0
  109. package/esm/locales/it.json +1 -0
  110. package/package.json +2 -2
  111. package/src/components/Avatar/Avatar.tsx +3 -3
  112. package/src/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.tsx +15 -8
  113. package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +64 -219
  114. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +221 -124
  115. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.ts +1250 -0
  116. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +164 -8
  117. package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +19 -17
  118. package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +80 -79
  119. package/src/components/Avatar/AvatarView/index.tsx +1 -7
  120. package/src/components/Chat/Chat.tsx +2 -2
  121. package/src/components/ChatBubble/ChatBubble.tsx +37 -26
  122. package/src/components/ExpertsDrawer/ExpertsDrawer.tsx +1 -1
  123. package/src/components/LoginDrawer/LoginDrawer.tsx +6 -6
  124. package/src/components/MemoriWidget/MemoriWidget.tsx +184 -78
  125. package/src/components/SignupForm/SignupForm.tsx +5 -5
  126. package/src/components/StartPanel/StartPanel.tsx +5 -5
  127. package/src/components/UploadButton/UploadButton.tsx +4 -4
  128. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +1 -1
  129. package/src/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
  130. package/src/components/WhyThisAnswer/WhyThisAnswer.stories.tsx +44 -3
  131. package/src/components/WhyThisAnswer/WhyThisAnswer.test.tsx +128 -8
  132. package/src/components/WhyThisAnswer/WhyThisAnswer.tsx +28 -3
  133. package/src/components/WhyThisAnswer/__snapshots__/WhyThisAnswer.test.tsx.snap +15 -1
  134. package/src/components/layouts/layouts.stories.tsx +0 -8
  135. package/src/context/visemeContext.tsx +40 -41
  136. package/src/index.stories.tsx +63 -65
  137. package/src/index.tsx +5 -3
  138. package/src/locales/de.json +1 -0
  139. package/src/locales/en.json +1 -0
  140. package/src/locales/es.json +1 -0
  141. package/src/locales/fr.json +1 -0
  142. package/src/locales/it.json +1 -0
  143. package/src/mocks/data.ts +3 -9
  144. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.ts +0 -308
  145. package/src/helpers/tenant.ts +0 -47
@@ -55,7 +55,7 @@ const LoginDrawer = ({
55
55
  __TEST__changePwd
56
56
  ? {
57
57
  flowID: 'flowID',
58
- tenant: tenant.id,
58
+ tenant: tenant.name,
59
59
  eMail: 'email',
60
60
  userName: 'username',
61
61
  password: 'password',
@@ -85,12 +85,12 @@ const LoginDrawer = ({
85
85
  const isEmail = mailRegEx.test(userNameOrEmail);
86
86
  const user: User = isEmail
87
87
  ? {
88
- tenant: tenant?.id,
88
+ tenant: tenant?.name,
89
89
  eMail: userNameOrEmail,
90
90
  password: password,
91
91
  }
92
92
  : {
93
- tenant: tenant?.id,
93
+ tenant: tenant?.name,
94
94
  userName: userNameOrEmail,
95
95
  password: password,
96
96
  };
@@ -100,7 +100,7 @@ const LoginDrawer = ({
100
100
  .then(data => {
101
101
  if (data.resultCode === -14) {
102
102
  setUserMustChangePwd({
103
- tenant: tenant.id,
103
+ tenant: tenant.name,
104
104
  eMail: isEmail ? userNameOrEmail : undefined,
105
105
  userName: isEmail ? undefined : userNameOrEmail,
106
106
  password: password,
@@ -200,7 +200,7 @@ const LoginDrawer = ({
200
200
 
201
201
  const form = e.currentTarget as HTMLFormElement;
202
202
 
203
- const tenantID = form.tenant.value ?? tenant.id;
203
+ const tenantID = form.tenant.value ?? tenant.name;
204
204
  const flowID = form.flowID.value ?? userMustChangePwd?.flowID;
205
205
  const eMail = form.eMail.value ?? userMustChangePwd?.eMail;
206
206
  const userName = form.userName.value ?? userMustChangePwd?.userName;
@@ -389,7 +389,7 @@ const LoginDrawer = ({
389
389
  <input
390
390
  type="hidden"
391
391
  name="tenant"
392
- value={userMustChangePwd.tenant ?? tenant?.id}
392
+ value={userMustChangePwd.tenant ?? tenant?.name}
393
393
  />
394
394
  <input
395
395
  type="hidden"
@@ -1959,7 +1959,9 @@ const MemoriWidget = ({
1959
1959
 
1960
1960
  const phonemes = {
1961
1961
  ...(phonemesMap.common ?? {}),
1962
- ...(tenant?.id && phonemesMap[tenant.id] ? phonemesMap[tenant.id] : {}),
1962
+ ...(tenant?.name && phonemesMap[tenant.name]
1963
+ ? phonemesMap[tenant.name]
1964
+ : {}),
1963
1965
  };
1964
1966
  const phonemesPairs = Object.keys(phonemes).map(word => {
1965
1967
  const phoneme =
@@ -2200,26 +2202,20 @@ const MemoriWidget = ({
2200
2202
  cleanup();
2201
2203
  };
2202
2204
 
2203
- const cleanup = (): void => {
2204
- setIsPlayingAudio(false);
2205
- stopProcessing();
2206
- resetVisemeQueue();
2207
- memoriSpeaking = false;
2205
+ const cleanup = () => {
2206
+ if (recognizer) {
2207
+ recognizer.stopContinuousRecognitionAsync();
2208
+ recognizer.close();
2209
+ recognizer = null;
2210
+ }
2208
2211
 
2209
- try {
2210
- if (speechSynthesizer) {
2211
- const currentSynthesizer = speechSynthesizer;
2212
- speechSynthesizer = null; // Clear reference first
2213
- console.debug('Closing speech synthesizer');
2214
- currentSynthesizer.close();
2215
- }
2216
- } catch (error) {
2217
- console.debug('Error during synthesizer cleanup:', error);
2218
- // Even if close fails, ensure synthesizer is nullified
2212
+ if (speechSynthesizer) {
2213
+ speechSynthesizer.close();
2219
2214
  speechSynthesizer = null;
2220
2215
  }
2221
2216
 
2222
- emitEndSpeakEvent();
2217
+ setListening(false);
2218
+ clearListeningTimeout();
2223
2219
  };
2224
2220
 
2225
2221
  // Modify stopAudio to include speech state reset
@@ -2280,34 +2276,41 @@ const MemoriWidget = ({
2280
2276
 
2281
2277
  const resetTranscript = () => {
2282
2278
  setTranscript('');
2283
- // setIsProcessingSTT(false);
2284
2279
  };
2285
-
2280
+ // Modify setListeningTimeout to be more robust
2286
2281
  const setListeningTimeout = () => {
2287
- clearListeningTimeout();
2288
- const timeout = setTimeout(
2289
- handleTranscriptProcessing,
2290
- continuousSpeechTimeout * 1000 + 300
2291
- );
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
+
2292
2290
  setTranscriptTimeout(timeout as unknown as NodeJS.Timeout);
2293
2291
  };
2294
2292
 
2295
2293
  const clearListeningTimeout = () => {
2296
2294
  if (transcriptTimeout) {
2295
+ console.debug('Clearing transcript timeout');
2297
2296
  clearTimeout(transcriptTimeout);
2298
2297
  setTranscriptTimeout(null);
2299
2298
  }
2300
2299
  };
2301
2300
 
2301
+ // Add safety check in resetListeningTimeout
2302
2302
  const resetListeningTimeout = () => {
2303
2303
  clearListeningTimeout();
2304
- if (continuousSpeech) {
2304
+ if (continuousSpeech && !isProcessingSTT) {
2305
+ console.debug('Setting new listening timeout');
2305
2306
  setListeningTimeout();
2306
2307
  }
2307
2308
  };
2308
- // Modified useEffect to handle transcript changes
2309
+
2310
+ // Make sure only one path can trigger message sending
2309
2311
  useEffect(() => {
2310
- if (!isSpeaking) {
2312
+ if (!isSpeaking && transcript && transcript.length > 0) {
2313
+ console.debug('Transcript updated while not speaking, resetting timeout');
2311
2314
  resetListeningTimeout();
2312
2315
  resetInteractionTimeout();
2313
2316
  }
@@ -2323,87 +2326,126 @@ const MemoriWidget = ({
2323
2326
  /**
2324
2327
  * Listening methods
2325
2328
  */
2326
- /**
2327
- * Starts speech recognition using Azure Cognitive Services
2328
- * Sets up recognizer and begins continuous recognition
2329
- */
2329
+ let microphoneStream: MediaStream | null = null;
2330
+ // Modify startListening to ensure full cleanup before starting
2330
2331
  const startListening = async (): Promise<void> => {
2332
+ console.debug('Starting speech recognition...');
2333
+
2331
2334
  if (!AZURE_COGNITIVE_SERVICES_TTS_KEY) {
2335
+ console.error('No TTS key available');
2332
2336
  throw new Error('No TTS key available');
2333
2337
  }
2334
2338
 
2335
2339
  if (!sessionId) {
2340
+ console.error('No session ID available');
2336
2341
  throw new Error('No session ID available');
2337
2342
  }
2338
2343
 
2339
- // Ensure complete cleanup before starting, if it's already listening, stop it
2340
- 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...');
2341
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));
2342
2373
 
2343
2374
  try {
2344
- // Add delay to ensure previous instance is fully cleaned up
2345
- // 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
+ }
2346
2381
 
2347
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
2382
+ const stream = await navigator.mediaDevices.getUserMedia({
2383
+ audio: true,
2384
+ });
2348
2385
  setHasUserActivatedListening(true);
2349
2386
 
2350
2387
  // Recreate speech config each time
2388
+ console.debug('Setting up speech config...');
2351
2389
  speechConfig = setupSpeechConfig(AZURE_COGNITIVE_SERVICES_TTS_KEY);
2352
2390
 
2391
+ console.debug('Creating audio config and recognizer...');
2353
2392
  const audioConfig = speechSdk.AudioConfig.fromDefaultMicrophoneInput();
2354
2393
  recognizer = new speechSdk.SpeechRecognizer(speechConfig, audioConfig);
2355
2394
 
2356
2395
  // Set up recognizer event handlers
2396
+ console.debug('Setting up recognizer handlers...');
2357
2397
  setupRecognizerHandlers(recognizer);
2358
2398
 
2359
- // Start recognition
2360
- setListening(true);
2361
- recognizer.startContinuousRecognitionAsync();
2362
-
2363
- recognizer.canceled = (_s, e) => {
2364
- if (e.reason === speechSdk.CancellationReason.Error) {
2365
- console.debug(`"CANCELED: ErrorCode=${e.errorCode}`);
2366
- console.debug(`"CANCELED: ErrorDetails=${e.errorDetails}`);
2367
- console.debug(
2368
- 'CANCELED: Did you set the speech resource key and region values?'
2369
- );
2370
- stopListening();
2371
- cleanup();
2372
- }
2373
-
2374
- stopListening();
2375
- };
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
+ });
2376
2407
 
2377
- recognizer.sessionStopped = (_s, _e) => {
2378
- stopListening();
2379
- resetTranscript();
2380
- };
2408
+ console.debug('Speech recognition started successfully');
2409
+ setListening(true);
2381
2410
  } catch (error) {
2382
2411
  console.error('Error in startListening:', error);
2383
- 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);
2384
2419
  throw error;
2385
2420
  }
2386
2421
  };
2387
2422
 
2388
2423
  const setupSpeechConfig = (AZURE_COGNITIVE_SERVICES_TTS_KEY: string) => {
2424
+ console.debug('Creating speech config...');
2389
2425
  speechConfig = speechSdk.SpeechConfig.fromSubscription(
2390
2426
  AZURE_COGNITIVE_SERVICES_TTS_KEY,
2391
2427
  'westeurope'
2392
2428
  );
2429
+ console.debug('Setting speech recognition language:', userLang);
2393
2430
  speechConfig.speechRecognitionLanguage = getCultureCodeByLanguage(userLang);
2394
2431
  speechConfig.speechSynthesisLanguage = getCultureCodeByLanguage(userLang);
2395
2432
  speechConfig.speechSynthesisVoiceName = getTTSVoice(userLang); // https://docs.microsoft.com/it-it/azure/cognitive-services/speech-service/language-support#text-to-speech
2396
2433
  return speechConfig;
2397
2434
  };
2398
2435
 
2436
+ const [isProcessingSTT, setIsProcessingSTT] = useState(false);
2437
+
2399
2438
  const setupRecognizerHandlers = (recognizer: speechSdk.SpeechRecognizer) => {
2400
2439
  if (recognizer) {
2440
+ console.debug('Setting up recognizer event handlers...');
2401
2441
  recognizer.recognized = (_, event) => {
2402
2442
  // Process the recognized speech result
2443
+ console.debug('Recognition event received');
2403
2444
  handleRecognizedSpeech(event.result.text);
2404
2445
  };
2405
2446
 
2406
2447
  // Configure speech recognition properties directly on the recognizer
2448
+ console.debug('Configuring recognizer properties...');
2407
2449
  recognizer.properties.setProperty(
2408
2450
  'SpeechServiceResponse_JsonResult',
2409
2451
  'true'
@@ -2421,32 +2463,66 @@ const MemoriWidget = ({
2421
2463
  }
2422
2464
  };
2423
2465
 
2424
- const handleRecognizedSpeech = (text: string) => {
2425
- console.debug('Handling recognized speech:', text);
2466
+ // Add a mutex-like flag to prevent duplicate processing
2467
+ let isProcessingSpeech = false;
2426
2468
 
2427
- if (!text || text.trim().length === 0) {
2428
- 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
+ );
2429
2476
  return;
2430
2477
  }
2431
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);
2432
2511
  setTranscript(text);
2433
2512
  setIsSpeaking(false);
2434
2513
 
2435
- const message = stripDuplicates(text);
2436
- console.debug('Stripped message:', message);
2437
- if (message.length > 0) {
2438
- 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);
2439
2518
  }
2519
+ // For continuous mode, rely on the timeout
2440
2520
  };
2441
2521
 
2442
- // Helper function to handle transcript processing
2522
+ // Update handleTranscriptProcessing to use the centralized function
2443
2523
  const handleTranscriptProcessing = () => {
2444
- const message = stripDuplicates(transcript);
2445
- if (message.length > 0 && listening) {
2446
- sendMessage(message);
2447
- resetTranscript();
2448
- setUserMessage('');
2449
- clearListening();
2524
+ if (transcript && transcript.length > 0 && listening) {
2525
+ processSpeechAndSendMessage(transcript);
2450
2526
  } else if (listening) {
2451
2527
  resetInteractionTimeout();
2452
2528
  }
@@ -2456,14 +2532,32 @@ const MemoriWidget = ({
2456
2532
  * Stops the speech recognition process
2457
2533
  * Closes recognizer and cleans up resources
2458
2534
  */
2459
- const stopListening = () => {
2535
+ // Similarly, modify stopListening to use promises
2536
+ // Enhance stopListening to fully release resources
2537
+ const stopListening = async () => {
2460
2538
  console.debug('Stopping speech recognition');
2539
+
2540
+ // Stop the recognizer
2461
2541
  if (recognizer) {
2462
- // Stop continuous recognition and close the recognizer
2463
- recognizer.stopContinuousRecognitionAsync();
2464
- recognizer.close();
2542
+ try {
2543
+ recognizer.stopContinuousRecognitionAsync();
2544
+ recognizer.close();
2545
+ } catch (error) {
2546
+ console.error('Error stopping recognizer:', error);
2547
+ }
2465
2548
  recognizer = null;
2466
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
+
2467
2561
  setListening(false);
2468
2562
  };
2469
2563
 
@@ -2804,6 +2898,15 @@ const MemoriWidget = ({
2804
2898
  birth = '1970-01-01T10:24:03.845Z';
2805
2899
 
2806
2900
  // console.log('[CLICK_START] Using birth date:', birth);
2901
+ const localPosition = getLocalConfig<Venue | undefined>(
2902
+ 'position',
2903
+ undefined
2904
+ );
2905
+ if (autoStart && !localPosition && memori.needsPosition) {
2906
+ console.log('position required', localPosition);
2907
+ setShowPositionDrawer(true);
2908
+ return;
2909
+ }
2807
2910
 
2808
2911
  // Handle age verification
2809
2912
  if (!sessionID && !!minAge && !birth) {
@@ -3681,6 +3784,9 @@ const MemoriWidget = ({
3681
3784
  onClose={position => {
3682
3785
  if (position) applyPosition(position);
3683
3786
  setShowPositionDrawer(false);
3787
+ if (autoStart) {
3788
+ onClickStart();
3789
+ }
3684
3790
  }}
3685
3791
  />
3686
3792
  )}
@@ -3706,7 +3812,7 @@ const MemoriWidget = ({
3706
3812
  />
3707
3813
  )}
3708
3814
 
3709
- {showLoginDrawer && tenant?.id && (
3815
+ {showLoginDrawer && tenant?.name && (
3710
3816
  <LoginDrawer
3711
3817
  tenant={tenant}
3712
3818
  apiClient={client}
@@ -80,7 +80,7 @@ const SignupForm = ({
80
80
  e.preventDefault();
81
81
  const form = e.currentTarget as HTMLFormElement;
82
82
 
83
- const tenantID = form.tenant.value ?? tenant.id;
83
+ const tenantID = form.tenant.value ?? tenant.name;
84
84
 
85
85
  const eMail = (form.eMail.value ?? email).toLowerCase();
86
86
  const userName = form.userName.value ?? username;
@@ -151,7 +151,7 @@ const SignupForm = ({
151
151
 
152
152
  try {
153
153
  const resp = await resendVerificationCode({
154
- tenant: tenant?.id,
154
+ tenant: tenant?.name,
155
155
  userName: username,
156
156
  });
157
157
 
@@ -174,7 +174,7 @@ const SignupForm = ({
174
174
  e.preventDefault();
175
175
  const form = e.currentTarget as HTMLFormElement;
176
176
 
177
- const tenantID = form.tenant.value ?? tenant.id;
177
+ const tenantID = form.tenant.value ?? tenant.name;
178
178
  const userName = form.userName.value ?? username;
179
179
  const pwd = form.password.value ?? password;
180
180
 
@@ -212,7 +212,7 @@ const SignupForm = ({
212
212
  <form className="memori--login-drawer--form" onSubmit={validateOtp}>
213
213
  <input type="hidden" name="userName" value={username} />
214
214
  <input type="hidden" name="password" value={password} />
215
- <input type="hidden" name="tenant" value={tenant.id} />
215
+ <input type="hidden" name="tenant" value={tenant.name} />
216
216
 
217
217
  <label htmlFor="#verificationCode">
218
218
  {t('login.otpCode')}
@@ -244,7 +244,7 @@ const SignupForm = ({
244
244
  ) : (
245
245
  <>
246
246
  <form className="memori--login-drawer--form" onSubmit={signup}>
247
- <input type="hidden" name="tenant" value={tenant.id} />
247
+ <input type="hidden" name="tenant" value={tenant.name} />
248
248
  <input type="hidden" name="referral" value={referral} />
249
249
 
250
250
  <label htmlFor="#eMail">
@@ -106,13 +106,13 @@ const StartPanel: React.FC<Props> = ({
106
106
  style={{
107
107
  backgroundImage: `url("${getResourceUrl({
108
108
  type: 'cover',
109
- tenantID: tenant?.id,
109
+ tenantID: tenant?.name,
110
110
  resourceURI: memori.coverURL,
111
111
  baseURL: baseUrl,
112
112
  apiURL: apiUrl,
113
113
  })}"), url("${getResourceUrl({
114
114
  type: 'cover',
115
- tenantID: tenant?.id,
115
+ tenantID: tenant?.name,
116
116
  baseURL: baseUrl,
117
117
  apiURL: apiUrl,
118
118
  })}")`,
@@ -150,7 +150,7 @@ const StartPanel: React.FC<Props> = ({
150
150
  memori.avatarURL ??
151
151
  getResourceUrl({
152
152
  type: 'avatar',
153
- tenantID: tenant?.id,
153
+ tenantID: tenant?.name,
154
154
  resourceURI: memori.avatarURL,
155
155
  baseURL: baseUrl,
156
156
  apiURL: apiUrl,
@@ -163,14 +163,14 @@ const StartPanel: React.FC<Props> = ({
163
163
  memori.avatarURL && memori.avatarURL.length > 0
164
164
  ? getResourceUrl({
165
165
  type: 'avatar',
166
- tenantID: tenant?.id,
166
+ tenantID: tenant?.name,
167
167
  resourceURI: memori.avatarURL,
168
168
  baseURL: baseUrl,
169
169
  apiURL: apiUrl,
170
170
  })
171
171
  : getResourceUrl({
172
172
  type: 'avatar',
173
- tenantID: tenant?.id,
173
+ tenantID: tenant?.name,
174
174
  baseURL: baseUrl,
175
175
  apiURL: apiUrl,
176
176
  })
@@ -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
+ }