@memori.ai/memori-react 7.34.2 → 8.0.0-rc.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 (54) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/components/Chat/Chat.d.ts +1 -0
  3. package/dist/components/Chat/Chat.js +2 -2
  4. package/dist/components/Chat/Chat.js.map +1 -1
  5. package/dist/components/ChatInputs/ChatInputs.d.ts +1 -0
  6. package/dist/components/ChatInputs/ChatInputs.js +3 -3
  7. package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
  8. package/dist/components/MemoriWidget/MemoriWidget.d.ts +3 -3
  9. package/dist/components/MemoriWidget/MemoriWidget.js +138 -425
  10. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  11. package/dist/context/visemeContext.js +39 -30
  12. package/dist/context/visemeContext.js.map +1 -1
  13. package/dist/helpers/sanitizer.d.ts +6 -0
  14. package/dist/helpers/sanitizer.js +41 -0
  15. package/dist/helpers/sanitizer.js.map +1 -0
  16. package/dist/helpers/tts/ttsVoiceUtility.d.ts +158 -0
  17. package/dist/helpers/tts/ttsVoiceUtility.js +192 -0
  18. package/dist/helpers/tts/ttsVoiceUtility.js.map +1 -0
  19. package/dist/helpers/tts/useTTS.d.ts +26 -0
  20. package/dist/helpers/tts/useTTS.js +274 -0
  21. package/dist/helpers/tts/useTTS.js.map +1 -0
  22. package/dist/index.js +12 -7
  23. package/dist/index.js.map +1 -1
  24. package/esm/components/Chat/Chat.d.ts +1 -0
  25. package/esm/components/Chat/Chat.js +2 -2
  26. package/esm/components/Chat/Chat.js.map +1 -1
  27. package/esm/components/ChatInputs/ChatInputs.d.ts +1 -0
  28. package/esm/components/ChatInputs/ChatInputs.js +3 -3
  29. package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
  30. package/esm/components/MemoriWidget/MemoriWidget.d.ts +3 -3
  31. package/esm/components/MemoriWidget/MemoriWidget.js +139 -426
  32. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  33. package/esm/context/visemeContext.js +39 -30
  34. package/esm/context/visemeContext.js.map +1 -1
  35. package/esm/helpers/sanitizer.d.ts +6 -0
  36. package/esm/helpers/sanitizer.js +32 -0
  37. package/esm/helpers/sanitizer.js.map +1 -0
  38. package/esm/helpers/tts/ttsVoiceUtility.d.ts +158 -0
  39. package/esm/helpers/tts/ttsVoiceUtility.js +182 -0
  40. package/esm/helpers/tts/ttsVoiceUtility.js.map +1 -0
  41. package/esm/helpers/tts/useTTS.d.ts +26 -0
  42. package/esm/helpers/tts/useTTS.js +270 -0
  43. package/esm/helpers/tts/useTTS.js.map +1 -0
  44. package/esm/index.js +12 -7
  45. package/esm/index.js.map +1 -1
  46. package/package.json +2 -2
  47. package/src/components/Chat/Chat.tsx +3 -0
  48. package/src/components/ChatInputs/ChatInputs.tsx +4 -2
  49. package/src/components/MemoriWidget/MemoriWidget.tsx +246 -637
  50. package/src/context/visemeContext.tsx +77 -55
  51. package/src/helpers/sanitizer.ts +71 -0
  52. package/src/helpers/tts/ttsVoiceUtility.ts +275 -0
  53. package/src/helpers/tts/useTTS.ts +431 -0
  54. package/src/index.tsx +14 -10
@@ -30,6 +30,7 @@ import React, {
30
30
  useCallback,
31
31
  CSSProperties,
32
32
  useRef,
33
+ useMemo,
33
34
  } from 'react';
34
35
  import { useTranslation } from 'react-i18next';
35
36
  import memoriApiClient from '@memori.ai/memori-api-client';
@@ -73,14 +74,10 @@ import {
73
74
  import {
74
75
  hasTouchscreen,
75
76
  stripDuplicates,
76
- stripEmojis,
77
- escapeHTML,
78
- stripMarkdown,
79
- stripOutputTags,
80
- stripHTML,
81
77
  installMathJax,
82
78
  stripReasoningTags,
83
79
  } from '../../helpers/utils';
80
+ import { getTTSVoice } from '../../helpers/tts/ttsVoiceUtility';
84
81
  import {
85
82
  allowedMediaTypes,
86
83
  anonTag,
@@ -89,6 +86,9 @@ import {
89
86
  import { getErrori18nKey } from '../../helpers/error';
90
87
  import { getCredits } from '../../helpers/credits';
91
88
  import { useViseme } from '../../context/visemeContext';
89
+ import { sanitizeText } from '../../helpers/sanitizer';
90
+ import { TTSConfig, useTTS } from '../../helpers/tts/useTTS';
91
+ import Alert from '../ui/Alert';
92
92
  import ChatHistoryDrawer from '../ChatHistoryDrawer/ChatHistory';
93
93
 
94
94
  // Widget utilities and helpers
@@ -330,13 +330,10 @@ window.typeBatchMessages = typeBatchMessages;
330
330
  // Global variables
331
331
  let recognizer: SpeechRecognizer | null;
332
332
  let speechConfig: SpeechConfig;
333
- let speechSynthesizer: SpeechSynthesizer | null;
334
333
  let audioDestination: SpeakerAudioDestination;
335
334
  let audioContext: IAudioContext;
336
335
 
337
336
  let memoriPassword: string | undefined;
338
- let speakerMuted: boolean = false;
339
- let memoriSpeaking: boolean = false;
340
337
  let userToken: string | undefined;
341
338
 
342
339
  export interface LayoutProps {
@@ -407,8 +404,7 @@ export interface Props {
407
404
  tag: string;
408
405
  pin: string;
409
406
  };
410
- authToken?: string;
411
- AZURE_COGNITIVE_SERVICES_TTS_KEY?: string;
407
+ ttsProvider?: 'azure' | 'openai';
412
408
  enableAudio?: boolean;
413
409
  defaultSpeakerActive?: boolean;
414
410
  disableTextEnteredEvents?: boolean;
@@ -421,6 +417,7 @@ export interface Props {
421
417
  autoStart?: boolean;
422
418
  applyVarsToRoot?: boolean;
423
419
  showFunctionCache?: boolean;
420
+ authToken?: string;
424
421
  }
425
422
 
426
423
  const MemoriWidget = ({
@@ -457,12 +454,12 @@ const MemoriWidget = ({
457
454
  engineURL = 'https://engine-staging.memori.ai',
458
455
  initialContextVars,
459
456
  initialQuestion,
457
+ ttsProvider,
460
458
  ogImage,
461
459
  sessionID: initialSessionID,
462
460
  tenant,
463
461
  personification,
464
462
  authToken,
465
- AZURE_COGNITIVE_SERVICES_TTS_KEY,
466
463
  enableAudio,
467
464
  defaultSpeakerActive = true,
468
465
  disableTextEnteredEvents = false,
@@ -588,7 +585,6 @@ const MemoriWidget = ({
588
585
  const defaultEnableAudio =
589
586
  enableAudio ?? integrationConfig?.enableAudio ?? false;
590
587
 
591
- const [hasUserActivatedSpeak, setHasUserActivatedSpeak] = useState(false);
592
588
  const [hasUserActivatedListening, setHasUserActivatedListening] =
593
589
  useState(false);
594
590
  const [showPositionDrawer, setShowPositionDrawer] = useState(false);
@@ -596,12 +592,8 @@ const MemoriWidget = ({
596
592
  const [showChatHistoryDrawer, setShowChatHistoryDrawer] = useState(false);
597
593
  const [showKnownFactsDrawer, setShowKnownFactsDrawer] = useState(false);
598
594
  const [showExpertsDrawer, setShowExpertsDrawer] = useState(false);
599
- const [muteSpeaker, setMuteSpeaker] = useState(
600
- !defaultEnableAudio || !defaultSpeakerActive || autoStart
601
- );
602
595
  const [continuousSpeech, setContinuousSpeech] = useState(false);
603
596
  const [continuousSpeechTimeout, setContinuousSpeechTimeout] = useState(2);
604
- const [isPlayingAudio, setIsPlayingAudio] = useState(false);
605
597
  const [controlsPosition, setControlsPosition] = useState<'center' | 'bottom'>(
606
598
  'center'
607
599
  );
@@ -612,18 +604,12 @@ const MemoriWidget = ({
612
604
  );
613
605
  const [hideEmissions, setHideEmissions] = useState(false);
614
606
 
615
- const {
616
- startProcessing,
617
- setAudioContext,
618
- addViseme,
619
- stopProcessing,
620
- resetVisemeQueue,
621
- } = useViseme();
607
+ const speechSynthesizerRef = useRef<SpeechSynthesizer | null>(null);
608
+ const [memoriSpeaking, setMemoriSpeaking] = useState(false);
622
609
 
623
610
  useEffect(() => {
624
- memoriSpeaking = !!speechSynthesizer;
625
- // eslint-disable-next-line react-hooks/exhaustive-deps
626
- }, [speechSynthesizer]);
611
+ setMemoriSpeaking(!!speechSynthesizerRef.current);
612
+ }, [speechSynthesizerRef.current]);
627
613
 
628
614
  useEffect(() => {
629
615
  let defaultControlsPosition: 'center' | 'bottom' = 'bottom';
@@ -648,17 +634,7 @@ const MemoriWidget = ({
648
634
  defaultControlsPosition = 'bottom';
649
635
  }
650
636
 
651
- const muteSpeaker =
652
- autoStart ||
653
- getLocalConfig(
654
- 'muteSpeaker',
655
- !defaultEnableAudio || !defaultSpeakerActive || autoStart
656
- );
657
-
658
- setMuteSpeaker(muteSpeaker);
659
- speakerMuted = muteSpeaker;
660
-
661
- setContinuousSpeech(muteSpeaker ? false : microphoneMode === 'CONTINUOUS');
637
+ setContinuousSpeech(speakerMuted ? false : microphoneMode === 'CONTINUOUS');
662
638
  setContinuousSpeechTimeout(getLocalConfig('continuousSpeechTimeout', 2));
663
639
  setControlsPosition(
664
640
  getLocalConfig('controlsPosition', defaultControlsPosition)
@@ -877,7 +853,7 @@ const MemoriWidget = ({
877
853
  translateDialogState(currentState, userLang, msg).then(ts => {
878
854
  let text = ts.translatedEmission || ts.emission;
879
855
  if (text) {
880
- speak(text);
856
+ handleSpeak(text);
881
857
  }
882
858
  });
883
859
  } else {
@@ -903,7 +879,7 @@ const MemoriWidget = ({
903
879
  tag: currentState.currentTag,
904
880
  memoryTags: currentState.memoryTags,
905
881
  });
906
- speak(emission);
882
+ handleSpeak(emission);
907
883
  }
908
884
  }
909
885
  } else if (response.resultCode === 404) {
@@ -947,14 +923,8 @@ const MemoriWidget = ({
947
923
  };
948
924
 
949
925
  /**
950
- * Traduzioni istantanee
951
- */
952
- /**
953
- * Translates the dialog state and message into the user's language if needed
954
- * @param state The current dialog state to translate
955
- * @param userLang The target language to translate to
956
- * @param msg Optional message that was answered
957
- * @returns The translated dialog state
926
+ * An enhanced version of translateDialogState that integrates smooth speaking
927
+ * This preserves all your existing logic while improving speech reliability
958
928
  */
959
929
  const translateDialogState = async (
960
930
  state: DialogState,
@@ -1794,6 +1764,80 @@ const MemoriWidget = ({
1794
1764
  timeoutRef.current = undefined;
1795
1765
  }
1796
1766
  };
1767
+ useEffect(() => {
1768
+ return () => {
1769
+ setHasUserActivatedSpeak(false);
1770
+ setClickedStart(false);
1771
+ clearInteractionTimeout();
1772
+ timeoutRef.current = undefined;
1773
+ };
1774
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1775
+ }, []);
1776
+
1777
+ /**
1778
+ * Speech recognition event handlers
1779
+ */
1780
+ const [requestedListening, setRequestedListening] = useState(false);
1781
+ const onEndSpeakStartListen = useCallback(
1782
+ (_e?: Event) => {
1783
+ if (isPlayingAudio && speechSynthesizerRef.current) {
1784
+ speechSynthesizerRef.current.close();
1785
+ speechSynthesizerRef.current = null;
1786
+ }
1787
+ if (
1788
+ continuousSpeech &&
1789
+ (hasUserActivatedListening || !requestedListening)
1790
+ ) {
1791
+ setRequestedListening(true);
1792
+ startListening();
1793
+ }
1794
+ },
1795
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1796
+ [continuousSpeech, hasUserActivatedListening]
1797
+ );
1798
+
1799
+ // Define audio playback options
1800
+ const audioPlaybackOptions = useMemo(
1801
+ () => ({
1802
+ apiUrl: apiURL,
1803
+ continuousSpeech: continuousSpeech,
1804
+ onEndSpeakStartListen: onEndSpeakStartListen,
1805
+ preview: preview,
1806
+ }),
1807
+ [apiURL, continuousSpeech, preview, onEndSpeakStartListen]
1808
+ );
1809
+
1810
+ console.log('tenantID', tenantID);
1811
+
1812
+ // Define TTS configuration
1813
+ const ttsConfig = useMemo(
1814
+ () => ({
1815
+ provider: ttsProvider,
1816
+ voice: getTTSVoice(
1817
+ userLang || memori.culture?.split('-')?.[0] || 'EN',
1818
+ ttsProvider,
1819
+ memori.voiceType as 'MALE' | 'FEMALE' | 'NEUTRAL'
1820
+ ),
1821
+ tenant: tenantID,
1822
+ region: 'westeurope',
1823
+ voiceType: memori.voiceType
1824
+ }),
1825
+ [ttsProvider, userLang, memori.culture, memori.voiceType]
1826
+ );
1827
+
1828
+ // Initialize TTS hook
1829
+ const {
1830
+ speak: ttsSpeak,
1831
+ stop: ttsStop,
1832
+ isPlaying: isPlayingAudio,
1833
+ speakerMuted,
1834
+ toggleMute,
1835
+ hasUserActivatedSpeak,
1836
+ setHasUserActivatedSpeak,
1837
+ error,
1838
+ setError,
1839
+ } = useTTS(ttsConfig as TTSConfig, audioPlaybackOptions, autoStart, defaultEnableAudio, defaultSpeakerActive);
1840
+
1797
1841
  const resetInteractionTimeout = () => {
1798
1842
  clearInteractionTimeout();
1799
1843
  if (!isPlayingAudio && !userMessage.length && !memoriTyping && !listening)
@@ -1829,7 +1873,7 @@ const MemoriWidget = ({
1829
1873
  ).then(ts => {
1830
1874
  let text = ts.translatedEmission || ts.emission;
1831
1875
  if (text) {
1832
- speak(text);
1876
+ handleSpeak(text);
1833
1877
  }
1834
1878
  });
1835
1879
  } else if (emission && emission.length > 0) {
@@ -1848,7 +1892,7 @@ const MemoriWidget = ({
1848
1892
  tag: currentState.currentTag,
1849
1893
  memoryTags: currentState.memoryTags,
1850
1894
  });
1851
- speak(emission);
1895
+ handleSpeak(emission);
1852
1896
  setCurrentDialogState({
1853
1897
  ...currentState,
1854
1898
  hints:
@@ -1905,155 +1949,6 @@ const MemoriWidget = ({
1905
1949
  memoriTyping,
1906
1950
  hasUserActivatedSpeak,
1907
1951
  ]);
1908
- useEffect(() => {
1909
- return () => {
1910
- setHasUserActivatedSpeak(false);
1911
- setClickedStart(false);
1912
- clearInteractionTimeout();
1913
- timeoutRef.current = undefined;
1914
- };
1915
- // eslint-disable-next-line react-hooks/exhaustive-deps
1916
- }, []);
1917
-
1918
- const initializeTTS = () => {
1919
- if (!AZURE_COGNITIVE_SERVICES_TTS_KEY) return;
1920
-
1921
- speechConfig = speechSdk.SpeechConfig.fromSubscription(
1922
- AZURE_COGNITIVE_SERVICES_TTS_KEY,
1923
- 'westeurope'
1924
- );
1925
-
1926
- speechConfig.speechSynthesisLanguage = getCultureCodeByLanguage(userLang);
1927
- speechConfig.speechSynthesisVoiceName = getTTSVoice(userLang); // https://docs.microsoft.com/it-it/azure/cognitive-services/speech-service/language-support#text-to-speech
1928
- speechConfig.speechRecognitionLanguage = getCultureCodeByLanguage(userLang);
1929
- speechConfig.setProperty('speechSynthesis.outputFormat', 'viseme');
1930
-
1931
- if (hasTouchscreen())
1932
- speechConfig.speechSynthesisOutputFormat =
1933
- speechSdk.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3;
1934
-
1935
- audioContext = new AudioContext();
1936
- let buffer = audioContext.createBuffer(1, 10000, 22050);
1937
- let source = audioContext.createBufferSource();
1938
- source.buffer = buffer;
1939
- source.connect(audioContext.destination);
1940
-
1941
- audioDestination = new speechSdk.SpeakerAudioDestination();
1942
- let audioConfig = speechSdk.AudioConfig.fromSpeakerOutput(audioDestination);
1943
- speechSynthesizer = new speechSdk.SpeechSynthesizer(
1944
- speechConfig,
1945
- audioConfig
1946
- );
1947
- };
1948
-
1949
- const getTTSVoice = useCallback(
1950
- (lang?: string): string => {
1951
- let voice = '';
1952
- let voiceLang = (
1953
- lang ??
1954
- memori.culture?.split('-')?.[0] ??
1955
- i18n.language ??
1956
- 'IT'
1957
- ).toUpperCase();
1958
-
1959
- let voiceType = memori.voiceType;
1960
- if (memori.enableBoardOfExperts && currentDialogState?.emitter) {
1961
- let expert = experts?.find(e => e.name === currentDialogState?.emitter);
1962
-
1963
- // TODO: once got info from backend, select voice from expert
1964
- // if (expert?.voiceType) {
1965
- // voiceType = expert.voiceType;
1966
- // }
1967
- }
1968
-
1969
- // https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts
1970
- switch (voiceLang) {
1971
- case 'IT':
1972
- voice = `${
1973
- voiceType === 'MALE' ? 'it-IT-DiegoNeural' : 'it-IT-ElsaNeural'
1974
- }`;
1975
- break;
1976
- case 'DE':
1977
- voice = `${
1978
- voiceType === 'MALE' ? 'de-DE-ConradNeural' : 'de-DE-KatjaNeural'
1979
- }`;
1980
- break;
1981
- case 'EN':
1982
- voice = `${
1983
- voiceType === 'MALE' ? 'en-GB-RyanNeural' : 'en-GB-SoniaNeural'
1984
- }`;
1985
- break;
1986
- case 'ES':
1987
- voice = `${
1988
- voiceType === 'MALE' ? 'es-ES-AlvaroNeural' : 'es-ES-ElviraNeural'
1989
- }`;
1990
- break;
1991
- case 'FR':
1992
- voice = `${
1993
- voiceType === 'MALE' ? 'fr-FR-HenriNeural' : 'fr-FR-DeniseNeural'
1994
- }`;
1995
- break;
1996
- case 'PT':
1997
- voice = `${
1998
- voiceType === 'MALE' ? 'pt-PT-DuarteNeural' : 'pt-PT-RaquelNeural'
1999
- }`;
2000
- break;
2001
- case 'UK':
2002
- voice = `${
2003
- voiceType === 'MALE' ? 'uk-UA-OstapNeural' : 'uk-UA-PolinaNeural'
2004
- }`;
2005
- break;
2006
- case 'RU':
2007
- voice = `${
2008
- voiceType === 'MALE' ? 'ru-RU-DmitryNeural' : 'ru-RU-SvetlanaNeural'
2009
- }`;
2010
- break;
2011
- case 'PL':
2012
- voice = `${
2013
- voiceType === 'MALE' ? 'pl-PL-MarekNeural' : 'pl-PL-AgnieszkaNeural'
2014
- }`;
2015
- break;
2016
- case 'FI':
2017
- voice = `${
2018
- voiceType === 'MALE' ? 'fi-FI-HarriNeural' : 'fi-FI-SelmaNeural'
2019
- }`;
2020
- break;
2021
- case 'EL':
2022
- voice = `${
2023
- voiceType === 'MALE' ? 'el-GR-NestorasNeural' : 'el-GR-AthinaNeural'
2024
- }`;
2025
- break;
2026
- case 'AR':
2027
- voice = `${
2028
- voiceType === 'MALE' ? 'ar-SA-HamedNeural' : 'ar-SA-ZariyahNeural'
2029
- }`;
2030
- break;
2031
- case 'ZH':
2032
- voice = `${
2033
- voiceType === 'MALE' ? 'zh-CN-YunxiNeural' : 'zh-CN-XiaoxiaoNeural'
2034
- }`;
2035
- break;
2036
- case 'JA':
2037
- voice = `${
2038
- voiceType === 'MALE' ? 'ja-JP-KeitaNeural' : 'ja-JP-NanamiNeural'
2039
- }`;
2040
- break;
2041
- default:
2042
- voice = `${
2043
- voiceType === 'MALE' ? 'it-IT-DiegoNeural' : 'it-IT-IsabellaNeural'
2044
- }`;
2045
- break;
2046
- }
2047
- return voice;
2048
- },
2049
- [
2050
- memori.voiceType,
2051
- memori.enableBoardOfExperts,
2052
- currentDialogState?.emitter,
2053
- i18n.language,
2054
- memori.culture,
2055
- ]
2056
- );
2057
1952
 
2058
1953
  const getCultureCodeByLanguage = (lang?: string): string => {
2059
1954
  let voice = '';
@@ -2113,278 +2008,95 @@ const MemoriWidget = ({
2113
2008
  return voice;
2114
2009
  };
2115
2010
 
2116
- const [phonemesMap, setPhonemesMap] = useState<{
2117
- [ns: 'common' | string]: {
2118
- [word: string]: {
2119
- caseSensitive: boolean;
2120
- default: string;
2121
- it?: string;
2122
- en?: string;
2123
- fr?: string;
2124
- };
2125
- };
2126
- }>();
2127
- const fetchLexiconJSON = async () => {
2128
- try {
2129
- const lexiconReq = await fetch(
2130
- `${baseUrl || 'https://aisuru.com'}/api/lexiconmap`
2131
- );
2132
- const lexicon = await lexiconReq.json();
2133
- return lexicon;
2134
- } catch (err) {
2135
- console.error(err);
2136
- }
2137
- };
2138
- useEffect(() => {
2139
- fetchLexiconJSON().then(lexicon => {
2140
- setPhonemesMap(lexicon);
2141
- });
2142
- // eslint-disable-next-line react-hooks/exhaustive-deps
2143
- }, []);
2144
-
2145
- const replaceTextWithPhonemes = (text: string, lang: string) => {
2146
- if (!phonemesMap) return text;
2147
-
2148
- const phonemes = {
2149
- ...(phonemesMap.common ?? {}),
2150
- ...(tenant?.name && phonemesMap[tenant.name]
2151
- ? phonemesMap[tenant.name]
2152
- : {}),
2153
- };
2154
- const phonemesPairs = Object.keys(phonemes).map(word => {
2155
- const phoneme =
2156
- phonemes[word][lang.toLowerCase() as 'it' | 'en' | 'fr'] ??
2157
- phonemes[word].default;
2158
- return { word, phoneme, caseSensitive: phonemes[word].caseSensitive };
2159
- });
2160
- const ssmlText = phonemesPairs.reduce(
2161
- (acc, { word, phoneme, caseSensitive }) => {
2162
- return acc.replace(
2163
- new RegExp(`\\b${word}\\b`, caseSensitive ? 'g' : 'gi'),
2164
- `<phoneme alphabet="ipa" ph="${phoneme}">${word}</phoneme>`
2165
- );
2166
- },
2167
- text
2168
- );
2169
-
2170
- return ssmlText;
2171
-
2172
- // E.g.:
2173
- // return text.replace(
2174
- // /martius/gi,
2175
- // `<phoneme alphabet="ipa" ph="ˈmaːrːtzius">Martius</phoneme>`,
2176
- // )
2177
- // .replace(
2178
- // /rawmaterial/gi,
2179
- // `<phoneme alphabet="ipa" ph="ˈpippo">RawMaterial</phoneme>`,
2180
- // )
2181
- // .replace(/qfe/gi, `<sub alias="Quota Filo Erba">QFE</sub>`)
2182
- };
2183
-
2184
- const emitEndSpeakEvent = () => {
2185
- const e = new CustomEvent('MemoriEndSpeak');
2186
- document.dispatchEvent(e);
2187
- };
2188
-
2189
- const speak = (text: string): void => {
2190
- console.debug('speak called with text:', text);
2191
-
2192
- if (!AZURE_COGNITIVE_SERVICES_TTS_KEY || preview) {
2193
- console.debug('No TTS key or preview mode, emitting end speak event');
2194
- emitEndSpeakEvent();
2195
- return;
2196
- }
2197
-
2198
- console.debug('Stopping listening before speaking');
2199
- stopListening();
2200
-
2201
- if (preview) {
2202
- console.debug('Preview mode, returning early');
2203
- return;
2204
- }
2205
-
2206
- if (speakerMuted) {
2207
- console.debug('Speaker muted, skipping speech synthesis');
2208
- memoriSpeaking = false;
2209
- setMemoriTyping(false);
2210
-
2211
- emitEndSpeakEvent();
2011
+ /**
2012
+ * Enhanced handleSpeak that integrates with the improved useTTS hook
2013
+ * Uses promise-based approach for better reliability
2014
+ */
2015
+ const handleSpeak = async (text: string) => {
2016
+ if (!text || preview || speakerMuted) {
2017
+ const e = new CustomEvent('MemoriEndSpeak');
2018
+ document.dispatchEvent(e);
2212
2019
 
2213
- // trigger start continuous listening if set, see MemoriChat
2214
- if (continuousSpeech) {
2215
- console.debug('Setting listening timeout for continuous speech');
2216
- setListeningTimeout();
2020
+ if (continuousSpeech) {
2021
+ setListeningTimeout();
2022
+ }
2023
+ return Promise.resolve();
2217
2024
  }
2218
- return;
2219
- }
2220
-
2221
- if (audioDestination) {
2222
- console.debug('Pausing existing audio destination');
2223
- audioDestination.pause();
2224
- }
2225
-
2226
- let isSafari =
2227
- window.navigator.userAgent.includes('Safari') &&
2228
- !window.navigator.userAgent.includes('Chrome');
2229
- let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
2230
2025
 
2231
- console.debug('Browser detection - Safari:', isSafari, 'iOS:', isIOS);
2026
+ if (typeof stopListening === 'function') {
2027
+ stopListening();
2028
+ }
2232
2029
 
2233
- if ((audioContext.state as string) === 'interrupted') {
2234
- console.debug('Audio context interrupted, attempting resume');
2235
- audioContext.resume().then(() => speak(text));
2236
- return;
2237
- }
2238
- if (audioContext.state === 'closed') {
2239
- console.debug('Audio context closed, creating new context');
2240
- audioContext = new AudioContext();
2241
- let buffer = audioContext.createBuffer(1, 10000, 22050);
2242
- let source = audioContext.createBufferSource();
2243
- source.buffer = buffer;
2244
- source.connect(audioContext.destination);
2245
- } else if (audioContext.state === 'suspended') {
2246
- console.debug(
2247
- 'Audio context suspended, stopping audio and creating new context'
2248
- );
2249
- stopAudio();
2030
+ setMemoriTyping(true);
2250
2031
 
2251
- audioContext = new AudioContext();
2252
- let buffer = audioContext.createBuffer(1, 10000, 22050);
2253
- let source = audioContext.createBufferSource();
2254
- source.buffer = buffer;
2255
- source.connect(audioContext.destination);
2256
- }
2032
+ const processedText = sanitizeText(text);
2257
2033
 
2258
- if (!speechSynthesizer) {
2259
- initializeTTS();
2034
+ return ttsSpeak(processedText)
2035
+ .then(() => {
2036
+ setMemoriTyping(false);
2037
+ })
2038
+ .catch(error => {
2039
+ setMemoriTyping(false);
2040
+ throw error;
2041
+ });
2260
2042
  }
2043
+ /**
2044
+ * Integrated solution for translating dialog state and speaking
2045
+ * This uses promise chaining for reliable sequencing without timeouts
2046
+ */
2047
+ const translateAndSpeak = useCallback(
2048
+ async (
2049
+ dialogState: DialogState,
2050
+ language: string,
2051
+ msg?: string,
2052
+ skipEmission: boolean = false
2053
+ ) => {
2054
+ try {
2055
+ // First ensure we have a valid dialog state
2056
+ if (!dialogState) {
2057
+ console.warn('translateAndSpeak called with empty dialog state');
2058
+ return null;
2059
+ }
2261
2060
 
2262
- const source = audioContext.createBufferSource();
2263
- source.addEventListener('ended', () => {
2264
- console.debug('Audio source ended');
2265
- setIsPlayingAudio(false);
2266
- memoriSpeaking = false;
2267
- });
2268
- audioDestination.onAudioEnd = () => {
2269
- console.debug('Audio destination ended');
2270
- setIsPlayingAudio(false);
2271
- memoriSpeaking = false;
2272
- source.disconnect();
2273
-
2274
- emitEndSpeakEvent();
2275
-
2276
- // trigger start continuous listening if set
2277
- onEndSpeakStartListen();
2278
- };
2279
-
2280
- // Clear any existing visemes before starting new speech
2281
- console.debug('Resetting viseme queue');
2282
- resetVisemeQueue();
2283
-
2284
- // Set up the viseme event handler
2285
- if (speechSynthesizer) {
2286
- speechSynthesizer.visemeReceived = function (_, e) {
2287
- console.debug(
2288
- 'Viseme received:',
2289
- e.visemeId,
2290
- 'at offset:',
2291
- e.audioOffset
2061
+ // Then translate the dialog state
2062
+ const translatedState = await translateDialogState(
2063
+ dialogState,
2064
+ language,
2065
+ msg,
2066
+ skipEmission
2292
2067
  );
2293
- addViseme(e.visemeId, e.audioOffset);
2294
- };
2295
- }
2296
2068
 
2297
- // Set up viseme handling
2298
- const textToSpeak = escapeHTML(
2299
- stripMarkdown(
2300
- stripEmojis(stripHTML(stripReasoningTags(stripOutputTags(text))))
2301
- )
2302
- );
2303
- console.debug('Processed text to speak:', textToSpeak);
2069
+ // If we're not skipping emission and there's something to speak, speak it
2070
+ const textToSpeak =
2071
+ translatedState.translatedEmission || translatedState.emission;
2304
2072
 
2305
- setTimeout(() => {
2306
- if (speechSynthesizer) {
2307
- console.debug('Starting speech synthesis');
2308
- speechSynthesizer.speakSsmlAsync(
2309
- `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" xml:lang="${getCultureCodeByLanguage(
2310
- userLang
2311
- )}"><voice name="${getTTSVoice(
2312
- userLang
2313
- )}"><s>${replaceTextWithPhonemes(
2314
- textToSpeak,
2315
- userLang.toLowerCase()
2316
- )}</s></voice></speak>`,
2317
- result => {
2318
- if (result) {
2319
- console.debug('Speech synthesis successful');
2320
- setIsPlayingAudio(true);
2321
- memoriSpeaking = true;
2322
-
2323
- // Process the viseme data
2324
- startProcessing(audioContext);
2073
+ if (textToSpeak && !skipEmission) {
2074
+ // Update activation state before speaking for better browser interaction
2075
+ if (!hasUserActivatedSpeak) {
2076
+ setHasUserActivatedSpeak(true);
2077
+ }
2325
2078
 
2326
- try {
2327
- // Decode the audio data
2328
- console.debug('Decoding audio data');
2329
- audioContext.decodeAudioData(
2330
- result.audioData,
2331
- function (buffer) {
2332
- console.debug('Audio data decoded successfully');
2333
- source.buffer = buffer;
2334
- source.connect(audioContext.destination);
2335
-
2336
- if (history.length < 1 || (isSafari && isIOS)) {
2337
- console.debug('Starting audio playback');
2338
- source.start(0);
2339
- }
2340
- }
2341
- );
2079
+ // Note: now using the Promise-based speak function to ensure proper sequencing
2080
+ await handleSpeak(textToSpeak);
2081
+ }
2342
2082
 
2343
- // Handle the audio context state changes
2344
- audioContext.onstatechange = () => {
2345
- console.debug(
2346
- 'Audio context state changed to:',
2347
- audioContext.state
2348
- );
2349
- if (
2350
- audioContext.state === 'suspended' ||
2351
- audioContext.state === 'closed'
2352
- ) {
2353
- source.disconnect();
2354
- setIsPlayingAudio(false);
2355
- stopProcessing();
2356
- resetVisemeQueue();
2357
- memoriSpeaking = false;
2358
- } else if ((audioContext.state as string) === 'interrupted') {
2359
- audioContext.resume();
2360
- }
2361
- };
2362
-
2363
- audioContext.resume();
2364
-
2365
- if (speechSynthesizer) {
2366
- console.debug('Closing speech synthesizer');
2367
- speechSynthesizer.close();
2368
- speechSynthesizer = null;
2369
- }
2370
- } catch (error) {
2371
- console.error('Error processing audio data:', error);
2372
- handleFallback(text);
2373
- }
2374
- } else {
2375
- console.debug('No result from speech synthesis, using fallback');
2376
- handleFallback(text);
2377
- }
2378
- },
2379
- error => {
2380
- console.error('Speak error:', error);
2381
- handleFallback(text);
2382
- }
2383
- );
2083
+ return translatedState;
2084
+ } catch (error) {
2085
+ console.error('Error in translateAndSpeak:', error);
2086
+ // Still update activation state even if there's an error
2087
+ if (!hasUserActivatedSpeak) {
2088
+ setHasUserActivatedSpeak(true);
2089
+ }
2090
+ return dialogState;
2384
2091
  }
2385
- }, 100);
2386
- setMemoriTyping(false);
2387
- };
2092
+ },
2093
+ [
2094
+ translateDialogState,
2095
+ handleSpeak,
2096
+ hasUserActivatedSpeak,
2097
+ setHasUserActivatedSpeak,
2098
+ ]
2099
+ );
2388
2100
 
2389
2101
  // Helper function for fallback behavior
2390
2102
  const handleFallback = (text: string) => {
@@ -2399,43 +2111,22 @@ const MemoriWidget = ({
2399
2111
  recognizer = null;
2400
2112
  }
2401
2113
 
2402
- if (speechSynthesizer) {
2403
- speechSynthesizer.close();
2404
- speechSynthesizer = null;
2114
+ if (speechSynthesizerRef.current) {
2115
+ speechSynthesizerRef.current.close();
2116
+ speechSynthesizerRef.current = null;
2405
2117
  }
2406
2118
 
2407
2119
  setListening(false);
2408
2120
  clearListeningTimeout();
2409
2121
  };
2410
2122
 
2411
- // Modify stopAudio to include speech state reset
2412
- const stopAudio = async (): Promise<void> => {
2413
- setIsPlayingAudio(false);
2414
- memoriSpeaking = false;
2415
-
2416
- try {
2417
- if (speechSynthesizer) {
2418
- const currentSynthesizer = speechSynthesizer;
2419
- speechSynthesizer = null;
2420
- try {
2421
- currentSynthesizer.close();
2422
- } catch (e) {
2423
- console.debug('Error closing speech synthesizer:', e);
2424
- }
2425
- }
2426
-
2427
- if (audioContext?.state !== 'closed') {
2428
- audioContext.close();
2429
- }
2123
+ /**
2124
+ * Funzione stopAudio che sostituisce quella originale
2125
+ */
2126
+ const stopAudio = useCallback(async () => {
2127
+ ttsStop();
2128
+ }, [ttsStop]);
2430
2129
 
2431
- if (audioDestination) {
2432
- audioDestination.pause();
2433
- audioDestination.close();
2434
- }
2435
- } catch (e) {
2436
- console.debug('stopAudio error: ', e);
2437
- }
2438
- };
2439
2130
  const focusChatInput = () => {
2440
2131
  let textarea = document.querySelector(
2441
2132
  '#chat-fieldset textarea'
@@ -2521,10 +2212,10 @@ const MemoriWidget = ({
2521
2212
  const startListening = async (): Promise<void> => {
2522
2213
  console.debug('Starting speech recognition...');
2523
2214
 
2524
- if (!AZURE_COGNITIVE_SERVICES_TTS_KEY) {
2525
- console.error('No TTS key available');
2526
- throw new Error('No TTS key available');
2527
- }
2215
+ // if (!AZURE_COGNITIVE_SERVICES_TTS_KEY) {
2216
+ // console.error('No TTS key available');
2217
+ // throw new Error('No TTS key available');
2218
+ // }
2528
2219
 
2529
2220
  if (!sessionId) {
2530
2221
  console.error('No session ID available');
@@ -2576,7 +2267,7 @@ const MemoriWidget = ({
2576
2267
 
2577
2268
  // Recreate speech config each time
2578
2269
  console.debug('Setting up speech config...');
2579
- speechConfig = setupSpeechConfig(AZURE_COGNITIVE_SERVICES_TTS_KEY);
2270
+ // speechConfig = setupSpeechConfig(AZURE_COGNITIVE_SERVICES_TTS_KEY);
2580
2271
 
2581
2272
  console.debug('Creating audio config and recognizer...');
2582
2273
  const audioConfig = speechSdk.AudioConfig.fromDefaultMicrophoneInput();
@@ -2799,28 +2490,6 @@ const MemoriWidget = ({
2799
2490
  // eslint-disable-next-line react-hooks/exhaustive-deps
2800
2491
  }, [currentDialogState?.state]);
2801
2492
 
2802
- /**
2803
- * Speech recognition event handlers
2804
- */
2805
- const [requestedListening, setRequestedListening] = useState(false);
2806
- const onEndSpeakStartListen = useCallback(
2807
- (_e?: Event) => {
2808
- if (isPlayingAudio && speechSynthesizer) {
2809
- speechSynthesizer.close();
2810
- speechSynthesizer = null;
2811
- }
2812
- if (
2813
- continuousSpeech &&
2814
- (hasUserActivatedListening || !requestedListening)
2815
- ) {
2816
- setRequestedListening(true);
2817
- startListening();
2818
- }
2819
- },
2820
- // eslint-disable-next-line react-hooks/exhaustive-deps
2821
- [continuousSpeech, hasUserActivatedListening]
2822
- );
2823
-
2824
2493
  useEffect(() => {
2825
2494
  // if memori is speaking, don't start listening
2826
2495
  if (
@@ -3076,10 +2745,9 @@ const MemoriWidget = ({
3076
2745
  window.navigator.userAgent.includes('Safari') &&
3077
2746
  !window.navigator.userAgent.includes('Chrome');
3078
2747
  if (memoriAudioElement && isSafari) {
3079
- // console.log('[CLICK_START] Enabling audio for Safari');
3080
2748
  memoriAudioElement.muted = false;
3081
2749
  memoriAudioElement.play().catch((e: any) => {
3082
- console.warn('error playing intro audio', e);
2750
+ console.warn('[CLICK_START] Error playing intro audio:', e);
3083
2751
  });
3084
2752
  }
3085
2753
 
@@ -3092,21 +2760,18 @@ const MemoriWidget = ({
3092
2760
  if (!birth && autoStart && initialSessionID)
3093
2761
  birth = '1970-01-01T10:24:03.845Z';
3094
2762
 
3095
- // console.log('[CLICK_START] Using birth date:', birth);
3096
2763
  const localPosition = getLocalConfig<Venue | undefined>(
3097
2764
  'position',
3098
2765
  undefined
3099
2766
  );
3100
2767
  // Only check for position requirement if memori.needsPosition is true
3101
2768
  if (autoStart && !localPosition && memori.needsPosition) {
3102
- console.log('position required', localPosition);
3103
2769
  setShowPositionDrawer(true);
3104
2770
  return;
3105
2771
  }
3106
2772
 
3107
2773
  // Handle age verification
3108
2774
  if (!sessionID && !!minAge && !birth) {
3109
- // console.log('[CLICK_START] Age verification required');
3110
2775
  setShowAgeVerification(true);
3111
2776
  setClickedStart(false);
3112
2777
  }
@@ -3119,14 +2784,12 @@ const MemoriWidget = ({
3119
2784
  !memoriTokens) ||
3120
2785
  (!sessionID && gotErrorInOpening)
3121
2786
  ) {
3122
- // console.log('[CLICK_START] Authentication required');
3123
2787
  setAuthModalState('password');
3124
2788
  setClickedStart(false);
3125
2789
  return;
3126
2790
  }
3127
2791
  // Create new session if needed
3128
2792
  else if (!sessionID || initialSessionExpired) {
3129
- // console.log('[CLICK_START] Creating new session');
3130
2793
  setClickedStart(false);
3131
2794
  setGotErrorInOpening(false);
3132
2795
  const session = await fetchSession({
@@ -3158,21 +2821,14 @@ const MemoriWidget = ({
3158
2821
  });
3159
2822
 
3160
2823
  if (session?.dialogState) {
3161
- // console.log('[CLICK_START] Got new session with dialog state');
3162
2824
  // reset history
3163
2825
  if (!chatLog) {
3164
2826
  setHistory([]);
3165
2827
 
3166
- translateDialogState(session.dialogState, userLang)
3167
- .then(ts => {
3168
- let text = ts.translatedEmission || ts.emission;
3169
- if (text) {
3170
- speak(text);
3171
- }
3172
- })
3173
- .finally(() => {
3174
- setHasUserActivatedSpeak(true);
3175
- });
2828
+ // Use translateAndSpeak which already handles the speaking
2829
+ await translateAndSpeak(session.dialogState, userLang);
2830
+ // No need for additional handleSpeak call since translateAndSpeak already handles it
2831
+ setHasUserActivatedSpeak(true);
3176
2832
  } else {
3177
2833
  const messages = chatLog.lines.map(
3178
2834
  (l, i) =>
@@ -3198,7 +2854,6 @@ const MemoriWidget = ({
3198
2854
  isMultilanguageEnabled
3199
2855
  ) {
3200
2856
  try {
3201
- console.debug('[CLICK_START] Translating messages');
3202
2857
  translatedMessages = await Promise.all(
3203
2858
  messages.map(async m => ({
3204
2859
  ...m,
@@ -3208,9 +2863,8 @@ const MemoriWidget = ({
3208
2863
  ).text,
3209
2864
  }))
3210
2865
  );
3211
- // console.log('[CLICK_START] Translated messages:', translatedMessages);
3212
2866
  } catch (e) {
3213
- // console.log('[CLICK_START] Error translating messages:', e);
2867
+ console.error('[CLICK_START] Error translating messages:', e);
3214
2868
  }
3215
2869
  }
3216
2870
 
@@ -3226,7 +2880,6 @@ const MemoriWidget = ({
3226
2880
  });
3227
2881
  }
3228
2882
  } else if (session?.resultCode === 0) {
3229
- // console.log('[CLICK_START] Retrying with session:', session);
3230
2883
  await onClickStart((session as any) || undefined);
3231
2884
  } else {
3232
2885
  setLoading(false);
@@ -3236,11 +2889,10 @@ const MemoriWidget = ({
3236
2889
  }
3237
2890
  // Handle initial session
3238
2891
  else if (initialSessionID) {
3239
- console.debug('[CLICK_START] Handling initial session');
3240
2892
  // check if session is valid and not expired
3241
2893
  const { currentState, ...response } = await getSession(sessionID);
2894
+
3242
2895
  if (response.resultCode !== 0 || !currentState) {
3243
- console.debug('[CLICK_START] Session expired, opening new session');
3244
2896
  setGotErrorInOpening(true);
3245
2897
  setSessionId(undefined);
3246
2898
  setClickedStart(false);
@@ -3252,10 +2904,12 @@ const MemoriWidget = ({
3252
2904
  setHistory([]);
3253
2905
 
3254
2906
  // date and place events
3255
- if (position && memori.needsPosition)
2907
+ if (position && memori.needsPosition) {
3256
2908
  applyPosition(position, sessionID);
3257
- if (memori.needsDateTime)
2909
+ }
2910
+ if (memori.needsDateTime) {
3258
2911
  sendDateChangedEvent({ sessionID: sessionID, state: currentState });
2912
+ }
3259
2913
 
3260
2914
  // Handle personification tag changes
3261
2915
  if (
@@ -3263,11 +2917,6 @@ const MemoriWidget = ({
3263
2917
  currentState.currentTag !== personification.tag
3264
2918
  ) {
3265
2919
  try {
3266
- console.debug(
3267
- '[CLICK_START] Changing tag for personification',
3268
- personification,
3269
- currentState
3270
- );
3271
2920
  // reset tag
3272
2921
  await changeTag(memori.engineMemoriID!, sessionID, '-');
3273
2922
  // change tag to receiver
@@ -3279,22 +2928,11 @@ const MemoriWidget = ({
3279
2928
  );
3280
2929
 
3281
2930
  if (session && session.resultCode === 0) {
3282
- translateDialogState(session.currentState, userLang)
3283
- .then(ts => {
3284
- let text = ts.translatedEmission || ts.emission;
3285
- if (text) {
3286
- speak(text);
3287
- }
3288
- })
3289
- .finally(() => {
3290
- setHasUserActivatedSpeak(true);
3291
- });
2931
+ await translateAndSpeak(session.currentState, userLang);
3292
2932
  } else {
3293
- console.error('[CLICK_START] Session error:', session);
3294
2933
  throw new Error('No session');
3295
2934
  }
3296
2935
  } catch (e) {
3297
- console.error('[CLICK_START] Error changing tag:', e);
3298
2936
  reopenSession(
3299
2937
  true,
3300
2938
  memori?.secretToken,
@@ -3323,7 +2961,6 @@ const MemoriWidget = ({
3323
2961
  currentState?.currentTag !== '-'
3324
2962
  ) {
3325
2963
  try {
3326
- console.debug('[CLICK_START] Changing to anonymous tag');
3327
2964
  // reset tag
3328
2965
  await changeTag(memori.engineMemoriID!, sessionID, '-');
3329
2966
  // change tag to anonymous
@@ -3334,22 +2971,11 @@ const MemoriWidget = ({
3334
2971
  );
3335
2972
 
3336
2973
  if (session && session.resultCode === 0) {
3337
- translateDialogState(session.currentState, userLang)
3338
- .then(ts => {
3339
- let text = ts.translatedEmission || ts.emission;
3340
- if (text) {
3341
- speak(text);
3342
- }
3343
- })
3344
- .finally(() => {
3345
- setHasUserActivatedSpeak(true);
3346
- });
2974
+ await translateAndSpeak(session.currentState, userLang);
3347
2975
  } else {
3348
- console.error('[CLICK_START] Session error:', session);
3349
2976
  throw new Error('No session');
3350
2977
  }
3351
2978
  } catch (e) {
3352
- console.error('[CLICK_START] Error changing tag:', e);
3353
2979
  reopenSession(
3354
2980
  true,
3355
2981
  memori?.secretToken,
@@ -3373,8 +2999,7 @@ const MemoriWidget = ({
3373
2999
  // No tag changes needed
3374
3000
  else {
3375
3001
  try {
3376
- console.debug('[CLICK_START] Getting chat history');
3377
- const { chatLogs, ...resp } = await getSessionChatLogs(
3002
+ const { chatLogs } = await getSessionChatLogs(
3378
3003
  sessionID,
3379
3004
  sessionID
3380
3005
  );
@@ -3403,7 +3028,6 @@ const MemoriWidget = ({
3403
3028
  isMultilanguageEnabled
3404
3029
  ) {
3405
3030
  try {
3406
- console.debug('[CLICK_START] Translating messages');
3407
3031
  translatedMessages = await Promise.all(
3408
3032
  messages.map(async m => ({
3409
3033
  ...m,
@@ -3413,35 +3037,22 @@ const MemoriWidget = ({
3413
3037
  ).text,
3414
3038
  }))
3415
3039
  );
3416
- // console.log('[CLICK_START] Translated messages:', translatedMessages);
3417
3040
  } catch (e) {
3418
- // console.log('[CLICK_START] Error translating messages:', e);
3041
+ console.error('[CLICK_START] Error translating messages:', e);
3419
3042
  }
3420
3043
  }
3421
3044
 
3422
3045
  setHistory(translatedMessages);
3423
- console.debug(
3424
- '[CLICK_START] props currentState:',
3425
- currentState,
3426
- 'userLang:',
3427
- userLang,
3428
- 'translatedMessages:',
3429
- translatedMessages,
3430
- 'history:',
3431
- history
3432
- );
3433
3046
  } catch (e) {
3434
- console.log('[CLICK_START] Error retrieving chat logs:', e);
3047
+ console.error('[CLICK_START] Error retrieving chat logs:', e);
3435
3048
  }
3436
3049
 
3437
3050
  if (
3438
3051
  (!!translatedMessages?.length && translatedMessages.length > 1) ||
3439
3052
  !initialQuestion
3440
3053
  ) {
3441
- console.log('[CLICK_START] Using existing chat history');
3442
-
3443
3054
  // we have a history, don't push message
3444
- translateDialogState(
3055
+ await translateAndSpeak(
3445
3056
  currentState,
3446
3057
  userLang,
3447
3058
  undefined,
@@ -3449,20 +3060,7 @@ const MemoriWidget = ({
3449
3060
  // otherwise, don't push message
3450
3061
  !!translatedMessages?.length
3451
3062
  )
3452
- .then(ts => {
3453
- let text = ts.translatedEmission || ts.emission;
3454
- if (text) {
3455
- speak(text);
3456
- }
3457
- })
3458
- .finally(() => {
3459
- setHasUserActivatedSpeak(true);
3460
- });
3461
3063
  } else {
3462
- console.log(
3463
- '[CLICK_START] Using existing chat history with message from initial question'
3464
- );
3465
-
3466
3064
  // remove default initial message
3467
3065
  translatedMessages = [];
3468
3066
  setHistory([]);
@@ -3475,48 +3073,30 @@ const MemoriWidget = ({
3475
3073
  text: initialQuestion,
3476
3074
  });
3477
3075
 
3478
- translateDialogState(
3076
+ await translateAndSpeak(
3479
3077
  response.currentState ?? currentState,
3480
3078
  userLang,
3481
3079
  undefined,
3482
3080
  false
3483
3081
  )
3484
- .then(ts => {
3485
- let text = ts.translatedEmission || ts.emission;
3486
- if (text) {
3487
- speak(text);
3488
- }
3489
- })
3490
- .finally(() => {
3491
- setMemoriTyping(false);
3492
- setHasUserActivatedSpeak(true);
3493
- });
3494
3082
  }
3495
3083
  }
3496
3084
 
3497
3085
  // date and place events
3498
- if (position && memori.needsPosition)
3086
+ if (position && memori.needsPosition) {
3499
3087
  applyPosition(position, sessionID);
3500
- if (memori.needsDateTime)
3088
+ }
3089
+ if (memori.needsDateTime) {
3501
3090
  sendDateChangedEvent({ sessionID: sessionID, state: currentState });
3091
+ }
3502
3092
  }
3503
3093
  // Default case - just translate and activate
3504
3094
  else {
3505
- console.debug('[CLICK_START] Using existing session');
3506
3095
  // reset history
3507
3096
  setHistory([]);
3508
3097
 
3509
3098
  // everything is fine, just translate dialog state and activate chat
3510
- translateDialogState(dialogState!, userLang)
3511
- .then(ts => {
3512
- let text = ts.translatedEmission || ts.emission;
3513
- if (text) {
3514
- speak(text);
3515
- }
3516
- })
3517
- .finally(() => {
3518
- setHasUserActivatedSpeak(true);
3519
- });
3099
+ await translateAndSpeak(dialogState!, userLang);
3520
3100
  }
3521
3101
  },
3522
3102
  [memoriPwd, memori, memoriTokens, birthDate, sessionId, userLang, position]
@@ -3524,10 +3104,6 @@ const MemoriWidget = ({
3524
3104
 
3525
3105
  useEffect(() => {
3526
3106
  if (!clickedStart && autoStart) {
3527
- // Initialize TTS before starting if AZURE_COGNITIVE_SERVICES_TTS_KEY exists
3528
- if (AZURE_COGNITIVE_SERVICES_TTS_KEY && !speechSynthesizer) {
3529
- initializeTTS();
3530
- }
3531
3107
  onClickStart();
3532
3108
  }
3533
3109
  }, [clickedStart, autoStart]);
@@ -3675,11 +3251,10 @@ const MemoriWidget = ({
3675
3251
  setShowKnownFactsDrawer,
3676
3252
  setShowExpertsDrawer,
3677
3253
  enableAudio: enableAudio ?? integrationConfig?.enableAudio ?? true,
3678
- showSpeaker: !!AZURE_COGNITIVE_SERVICES_TTS_KEY,
3679
- speakerMuted: muteSpeaker || speakerMuted,
3254
+ showSpeaker: !!ttsProvider,
3255
+ speakerMuted: speakerMuted,
3680
3256
  setSpeakerMuted: mute => {
3681
- speakerMuted = !!mute;
3682
- setMuteSpeaker(mute);
3257
+ toggleMute(mute);
3683
3258
  let microphoneMode = getLocalConfig<string>(
3684
3259
  'microphoneMode',
3685
3260
  'HOLD_TO_TALK'
@@ -3724,7 +3299,7 @@ const MemoriWidget = ({
3724
3299
  avatar3dVisible,
3725
3300
  setAvatar3dVisible,
3726
3301
  hasUserActivatedSpeak,
3727
- isPlayingAudio: isPlayingAudio && !muteSpeaker,
3302
+ isPlayingAudio: isPlayingAudio && !speakerMuted,
3728
3303
  loading: !!memoriTyping,
3729
3304
  baseUrl,
3730
3305
  apiUrl: client.constants.BACKEND_URL,
@@ -3749,7 +3324,6 @@ const MemoriWidget = ({
3749
3324
  clickedStart: clickedStart,
3750
3325
  isMultilanguageEnabled: isMultilanguageEnabled,
3751
3326
  onClickStart: onClickStart,
3752
- initializeTTS: initializeTTS,
3753
3327
  isUserLoggedIn: !!loginToken && !!user?.userID,
3754
3328
  hasInitialSession: !!initialSessionID,
3755
3329
  notEnoughCredits: needsCredits && !hasEnoughCredits,
@@ -3762,6 +3336,7 @@ const MemoriWidget = ({
3762
3336
  memori,
3763
3337
  sessionID: sessionId || '',
3764
3338
  tenant,
3339
+ provider: ttsProvider as 'azure' | 'openai',
3765
3340
  translateTo:
3766
3341
  isMultilanguageEnabled &&
3767
3342
  userLang.toUpperCase() !==
@@ -3801,7 +3376,7 @@ const MemoriWidget = ({
3801
3376
  attachmentsMenuOpen,
3802
3377
  setAttachmentsMenuOpen,
3803
3378
  showInputs,
3804
- showMicrophone: !!AZURE_COGNITIVE_SERVICES_TTS_KEY,
3379
+ showMicrophone: !!ttsProvider,
3805
3380
  showFunctionCache,
3806
3381
  userMessage,
3807
3382
  onChangeUserMessage,
@@ -3880,7 +3455,7 @@ const MemoriWidget = ({
3880
3455
  'memori--preview': preview,
3881
3456
  'memori--embed': embed,
3882
3457
  'memori--with-integration': integration,
3883
- 'memori--with-speechkey': !!AZURE_COGNITIVE_SERVICES_TTS_KEY,
3458
+ 'memori--with-speechkey': !!ttsProvider,
3884
3459
  'memori--active': hasUserActivatedSpeak,
3885
3460
  'memori--hide-emissions': hideEmissions,
3886
3461
  'memori--has-active-session': !!sessionId,
@@ -4036,6 +3611,26 @@ const MemoriWidget = ({
4036
3611
  isAvatar3d={!!integrationConfig?.avatarURL}
4037
3612
  additionalSettings={additionalSettings}
4038
3613
  speakerMuted={speakerMuted}
3614
+
3615
+ />
3616
+ )}
3617
+
3618
+ {showChatHistoryDrawer && (
3619
+ <ChatHistoryDrawer
3620
+ open={!!showChatHistoryDrawer}
3621
+ onClose={() => setShowChatHistoryDrawer(false)}
3622
+ resumeSession={chatLog => {
3623
+ setChatLogID(chatLog.chatLogID);
3624
+ onClickStart(undefined, false, chatLog);
3625
+ setShowChatHistoryDrawer(false);
3626
+ }}
3627
+ apiClient={client}
3628
+ sessionId={sessionId || ''}
3629
+ memori={memori}
3630
+ baseUrl={baseUrl}
3631
+ history={history}
3632
+ apiUrl={client.constants.BACKEND_URL}
3633
+ loginToken={loginToken}
4039
3634
  />
4040
3635
  )}
4041
3636
 
@@ -4123,6 +3718,20 @@ const MemoriWidget = ({
4123
3718
  }}
4124
3719
  />
4125
3720
  )}
3721
+
3722
+ {error && (
3723
+ <Alert
3724
+ open={!!error}
3725
+ onClose={() => {
3726
+ setError(null);
3727
+ //opens up the allow media of the browser
3728
+ window.open('chrome://settings/content/autoplay', '_blank');
3729
+ }}
3730
+ title="Error"
3731
+ description={error.message}
3732
+ type="error"
3733
+ />
3734
+ )}
4126
3735
  </div>
4127
3736
  );
4128
3737
  };