@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.
- package/CHANGELOG.md +35 -0
- package/dist/components/Chat/Chat.d.ts +1 -0
- package/dist/components/Chat/Chat.js +2 -2
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatInputs/ChatInputs.d.ts +1 -0
- package/dist/components/ChatInputs/ChatInputs.js +3 -3
- package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
- package/dist/components/MemoriWidget/MemoriWidget.d.ts +3 -3
- package/dist/components/MemoriWidget/MemoriWidget.js +138 -425
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/context/visemeContext.js +39 -30
- package/dist/context/visemeContext.js.map +1 -1
- package/dist/helpers/sanitizer.d.ts +6 -0
- package/dist/helpers/sanitizer.js +41 -0
- package/dist/helpers/sanitizer.js.map +1 -0
- package/dist/helpers/tts/ttsVoiceUtility.d.ts +158 -0
- package/dist/helpers/tts/ttsVoiceUtility.js +192 -0
- package/dist/helpers/tts/ttsVoiceUtility.js.map +1 -0
- package/dist/helpers/tts/useTTS.d.ts +26 -0
- package/dist/helpers/tts/useTTS.js +274 -0
- package/dist/helpers/tts/useTTS.js.map +1 -0
- package/dist/index.js +12 -7
- package/dist/index.js.map +1 -1
- package/esm/components/Chat/Chat.d.ts +1 -0
- package/esm/components/Chat/Chat.js +2 -2
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatInputs/ChatInputs.d.ts +1 -0
- package/esm/components/ChatInputs/ChatInputs.js +3 -3
- package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.d.ts +3 -3
- package/esm/components/MemoriWidget/MemoriWidget.js +139 -426
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/context/visemeContext.js +39 -30
- package/esm/context/visemeContext.js.map +1 -1
- package/esm/helpers/sanitizer.d.ts +6 -0
- package/esm/helpers/sanitizer.js +32 -0
- package/esm/helpers/sanitizer.js.map +1 -0
- package/esm/helpers/tts/ttsVoiceUtility.d.ts +158 -0
- package/esm/helpers/tts/ttsVoiceUtility.js +182 -0
- package/esm/helpers/tts/ttsVoiceUtility.js.map +1 -0
- package/esm/helpers/tts/useTTS.d.ts +26 -0
- package/esm/helpers/tts/useTTS.js +270 -0
- package/esm/helpers/tts/useTTS.js.map +1 -0
- package/esm/index.js +12 -7
- package/esm/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Chat/Chat.tsx +3 -0
- package/src/components/ChatInputs/ChatInputs.tsx +4 -2
- package/src/components/MemoriWidget/MemoriWidget.tsx +246 -637
- package/src/context/visemeContext.tsx +77 -55
- package/src/helpers/sanitizer.ts +71 -0
- package/src/helpers/tts/ttsVoiceUtility.ts +275 -0
- package/src/helpers/tts/useTTS.ts +431 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
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
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
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
|
-
|
|
2026
|
+
if (typeof stopListening === 'function') {
|
|
2027
|
+
stopListening();
|
|
2028
|
+
}
|
|
2232
2029
|
|
|
2233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2259
|
-
|
|
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
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
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
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
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
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
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
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
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
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
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
|
-
},
|
|
2386
|
-
|
|
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 (
|
|
2403
|
-
|
|
2404
|
-
|
|
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
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
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
|
-
|
|
2526
|
-
|
|
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('
|
|
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
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: !!
|
|
3679
|
-
speakerMuted:
|
|
3254
|
+
showSpeaker: !!ttsProvider,
|
|
3255
|
+
speakerMuted: speakerMuted,
|
|
3680
3256
|
setSpeakerMuted: mute => {
|
|
3681
|
-
|
|
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 && !
|
|
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: !!
|
|
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': !!
|
|
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
|
};
|