@memori.ai/memori-react 2.9.2 → 2.10.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 (32) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/components/ChatBubble/ChatBubble.js +31 -31
  3. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  4. package/dist/components/MemoriWidget/MemoriWidget.d.ts +16 -1
  5. package/dist/components/MemoriWidget/MemoriWidget.js +242 -226
  6. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  7. package/dist/components/StartPanel/StartPanel.js +6 -1
  8. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  9. package/dist/helpers/utils.d.ts +1 -0
  10. package/dist/helpers/utils.js +5 -1
  11. package/dist/helpers/utils.js.map +1 -1
  12. package/dist/helpers/utils.test.js +12 -0
  13. package/dist/helpers/utils.test.js.map +1 -1
  14. package/esm/components/ChatBubble/ChatBubble.js +31 -31
  15. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  16. package/esm/components/MemoriWidget/MemoriWidget.d.ts +16 -1
  17. package/esm/components/MemoriWidget/MemoriWidget.js +243 -227
  18. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  19. package/esm/components/StartPanel/StartPanel.js +6 -1
  20. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  21. package/esm/helpers/utils.d.ts +1 -0
  22. package/esm/helpers/utils.js +3 -0
  23. package/esm/helpers/utils.js.map +1 -1
  24. package/esm/helpers/utils.test.js +13 -1
  25. package/esm/helpers/utils.test.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/components/ChatBubble/ChatBubble.tsx +1 -1
  28. package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +0 -3
  29. package/src/components/MemoriWidget/MemoriWidget.tsx +353 -292
  30. package/src/components/StartPanel/StartPanel.tsx +8 -3
  31. package/src/helpers/utils.test.ts +15 -1
  32. package/src/helpers/utils.ts +4 -0
@@ -13,6 +13,7 @@ import {
13
13
  GamificationLevel,
14
14
  Tenant,
15
15
  Asset,
16
+ MemoriSession,
16
17
  } from '@memori.ai/memori-api-client/src/types';
17
18
  import {
18
19
  SpeakerAudioDestination,
@@ -59,7 +60,11 @@ import ChatLayout from '../layouts/Chat';
59
60
  // Helpers / Utils
60
61
  import { getTranslation } from '../../helpers/translations';
61
62
  import { setLocalConfig, getLocalConfig } from '../../helpers/configuration';
62
- import { hasTouchscreen, stripDuplicates } from '../../helpers/utils';
63
+ import {
64
+ hasTouchscreen,
65
+ stripDuplicates,
66
+ stripEmojis,
67
+ } from '../../helpers/utils';
63
68
  import { anonTag } from '../../helpers/constants';
64
69
  import { getErrori18nKey } from '../../helpers/error';
65
70
  import { getGamificationLevel } from '../../helpers/statistics';
@@ -89,55 +94,55 @@ const getMemoriState = (integrationId?: string): object | null => {
89
94
  return dialogState;
90
95
  };
91
96
 
92
- function setNativeValue(element: Element, value: string) {
93
- const valueSetter = Object?.getOwnPropertyDescriptor?.(element, 'value')?.set;
94
- const prototype = Object.getPrototypeOf(element);
95
- const prototypeValueSetter = Object?.getOwnPropertyDescriptor?.(
96
- prototype,
97
- 'value'
98
- )?.set;
99
-
100
- if (
101
- prototypeValueSetter &&
102
- valueSetter &&
103
- valueSetter !== prototypeValueSetter
104
- ) {
105
- prototypeValueSetter.call(element, value);
106
- } else if (valueSetter) {
107
- valueSetter.call(element, value);
108
- }
109
- }
110
-
111
- const typeMessage = (message: string) => {
112
- let textarea =
113
- document.querySelector('fieldset#chat-fieldset textarea') ||
114
- document
115
- .querySelector('memori-client')
116
- ?.shadowRoot?.querySelector('fieldset#chat-fieldset textarea');
117
- if (!textarea) return;
118
-
119
- setNativeValue(textarea, message);
120
- textarea.dispatchEvent(new Event('input', { bubbles: true }));
97
+ type MemoriTextEnteredEvent = CustomEvent<{
98
+ text: string;
99
+ waitForPrevious?: boolean;
100
+ hidden?: boolean;
101
+ }>;
102
+
103
+ const typeMessage = (
104
+ message: string,
105
+ waitForPrevious = true,
106
+ hidden = false
107
+ ) => {
108
+ const e: MemoriTextEnteredEvent = new CustomEvent('MemoriTextEntered', {
109
+ detail: {
110
+ text: message,
111
+ waitForPrevious,
112
+ hidden,
113
+ },
114
+ });
121
115
 
122
- setTimeout(() => {
123
- let sendButton =
124
- document.querySelector('button.memori-chat-inputs--send') ||
125
- document
126
- .querySelector('memori-client')
127
- ?.shadowRoot?.querySelector('button.memori-chat-inputs--send');
128
- if (!sendButton) return;
129
- (sendButton as HTMLButtonElement).click();
130
- }, 100);
116
+ document.dispatchEvent(e);
131
117
  };
118
+ const typeMessageHidden = (message: string, waitForPrevious = true) =>
119
+ typeMessage(message, waitForPrevious, true);
132
120
 
121
+ interface CustomEventMap {
122
+ MemoriTextEntered: MemoriTextEnteredEvent;
123
+ }
133
124
  declare global {
125
+ interface Document {
126
+ addEventListener<K extends keyof CustomEventMap>(
127
+ type: K,
128
+ listener: (this: Document, ev: CustomEventMap[K]) => void
129
+ ): void;
130
+ removeEventListener<K extends keyof CustomEventMap>(
131
+ type: K,
132
+ listener: (this: Document, ev: CustomEventMap[K]) => void
133
+ ): void;
134
+ dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
135
+ }
136
+
134
137
  interface Window {
135
138
  getMemoriState: typeof getMemoriState;
136
139
  typeMessage: typeof typeMessage;
140
+ typeMessageHidden: typeof typeMessageHidden;
137
141
  }
138
142
  }
139
143
  window.getMemoriState = getMemoriState;
140
144
  window.typeMessage = typeMessage;
145
+ window.typeMessageHidden = typeMessageHidden;
141
146
 
142
147
  // Global variables
143
148
  let recognizer: SpeechRecognizer | null;
@@ -148,6 +153,7 @@ let audioContext: IAudioContext;
148
153
 
149
154
  let memoriPassword: string | undefined;
150
155
  let speakerMuted: boolean = false;
156
+ let memoriSpeaking: boolean = false;
151
157
 
152
158
  export interface LayoutProps {
153
159
  Header?: typeof Header;
@@ -305,6 +311,7 @@ const MemoriWidget = ({
305
311
  const [hideEmissions, setHideEmissions] = useState(false);
306
312
  useEffect(() => {
307
313
  setIsPlayingAudio(!!speechSynthesizer);
314
+ memoriSpeaking = !!speechSynthesizer;
308
315
  // eslint-disable-next-line react-hooks/exhaustive-deps
309
316
  }, [speechSynthesizer]);
310
317
 
@@ -376,258 +383,6 @@ const MemoriWidget = ({
376
383
  applyPosition(venue);
377
384
  };
378
385
 
379
- /**
380
- * History e gestione invio messaggi
381
- */
382
- const [userMessage, setUserMessage] = useState<string>('');
383
- const onChangeUserMessage = (value: string) => {
384
- if (!value || value === '\n' || value.trim() === '') {
385
- setUserMessage('');
386
- resetInteractionTimeout();
387
- return;
388
- }
389
- setUserMessage(value);
390
- clearInteractionTimeout();
391
- };
392
- const [listening, setListening] = useState(false);
393
- const [history, setHistory] = useState<Message[]>([]);
394
- const pushMessage = (message: Message) => {
395
- setHistory(history => [...history, { ...message }]);
396
- };
397
- const sendMessage = async (
398
- text: string,
399
- media?: Medium[],
400
- newSessionId?: string,
401
- translate: boolean = true,
402
- translatedText?: string
403
- ) => {
404
- if (!sessionId || !text?.length) return;
405
-
406
- pushMessage({
407
- text: text,
408
- translatedText,
409
- fromUser: true,
410
- media: media ?? [],
411
- initial: !!newSessionId,
412
- });
413
-
414
- setMemoriTyping(true);
415
-
416
- const language = memori.culture?.split('-')?.[0] ?? i18n.language ?? 'IT';
417
- let msg = text;
418
-
419
- if (
420
- translate &&
421
- !instruct &&
422
- isMultilanguageEnabled &&
423
- userLang.toUpperCase() !== language.toUpperCase()
424
- ) {
425
- const translation = await getTranslation(
426
- text,
427
- language,
428
- userLang,
429
- baseUrl
430
- );
431
- msg = translation.text;
432
- }
433
-
434
- const { currentState, ...response } = await postTextEnteredEvent({
435
- sessionId: newSessionId ?? sessionId,
436
- text: msg,
437
- });
438
- if (response.resultCode === 0 && currentState) {
439
- const emission = currentState.emission ?? currentDialogState?.emission;
440
- if (currentState.state === 'X4' && memori.giverTag) {
441
- const { currentState, ...resp } = await postTagChangedEvent(
442
- sessionId,
443
- memori.giverTag
444
- );
445
-
446
- if (resp.resultCode === 0) {
447
- setCurrentDialogState(currentState);
448
-
449
- if (currentState.emission) {
450
- pushMessage({
451
- text: currentState.emission,
452
- media: currentState.media,
453
- fromUser: false,
454
- });
455
- speak(currentState.emission);
456
- }
457
- } else {
458
- console.error(response, resp);
459
- message.error(t(getErrori18nKey(resp.resultCode)));
460
- }
461
- } else if (currentState.state === 'X2d' && memori.giverTag) {
462
- const { currentState, ...resp } = await postTextEnteredEvent({
463
- sessionId: newSessionId ?? sessionId,
464
- text: Math.random().toString().substring(2, 8),
465
- });
466
-
467
- if (resp.resultCode === 0) {
468
- const { currentState, ...resp } = await postTagChangedEvent(
469
- sessionId,
470
- memori.giverTag
471
- );
472
-
473
- if (resp.resultCode === 0) {
474
- setCurrentDialogState(currentState);
475
-
476
- if (currentState.emission) {
477
- pushMessage({
478
- text: currentState.emission,
479
- media: currentState.media,
480
- fromUser: false,
481
- });
482
- speak(currentState.emission);
483
- }
484
- } else {
485
- console.error(response, resp);
486
- message.error(t(getErrori18nKey(resp.resultCode)));
487
- }
488
- } else {
489
- console.error(response, resp);
490
- message.error(t(getErrori18nKey(resp.resultCode)));
491
- }
492
- } else if (
493
- userLang.toLowerCase() !== language.toLowerCase() &&
494
- emission &&
495
- !instruct &&
496
- isMultilanguageEnabled
497
- ) {
498
- translateDialogState(currentState, userLang).then(ts => {
499
- if (ts.emission) {
500
- speak(ts.emission);
501
- }
502
- });
503
- } else {
504
- setCurrentDialogState({
505
- ...currentState,
506
- emission,
507
- });
508
-
509
- if (emission) {
510
- pushMessage({
511
- text: emission,
512
- media: currentState.media,
513
- fromUser: false,
514
- generatedByAI: !!currentState.completion,
515
- });
516
- speak(emission);
517
- }
518
- }
519
- } else if (response.resultCode === 404) {
520
- // remove last sent message, will set it as initial
521
- setHistory(h => [...h.slice(0, h.length - 1)]);
522
-
523
- // post session timeout -> Z0/A0 -> restart session and re-send msg
524
- reopenSession(
525
- false,
526
- memoriPwd || memori.secretToken,
527
- memoriTokens,
528
- instruct && memori.giverTag ? memori.giverTag : undefined,
529
- instruct && memori.giverPIN ? memori.giverPIN : undefined,
530
- initialContextVars,
531
- initialQuestion
532
- ).then(state => {
533
- console.info('session timeout');
534
- if (state?.sessionID) {
535
- setTimeout(() => {
536
- sendMessage(text, media, state?.sessionID);
537
- }, 500);
538
- }
539
- });
540
- }
541
-
542
- setMemoriTyping(false);
543
- };
544
-
545
- /**
546
- * Traduzioni istantanee
547
- */
548
- const translateDialogState = async (state: DialogState, userLang: string) => {
549
- const language = memori.culture?.split('-')?.[0] ?? i18n.language ?? 'IT';
550
- const emission = state.emission ?? currentDialogState?.emission;
551
-
552
- let translatedState = { ...state };
553
- let translatedMsg = null;
554
-
555
- if (
556
- !emission ||
557
- instruct ||
558
- language.toUpperCase() === userLang.toUpperCase() ||
559
- !isMultilanguageEnabled
560
- ) {
561
- translatedState = { ...state, emission };
562
- if (emission) {
563
- translatedMsg = {
564
- text: emission,
565
- media: state.media,
566
- fromUser: false,
567
- };
568
- }
569
- } else {
570
- const t = await getTranslation(emission, userLang, language, baseUrl);
571
- if (state.hints && state.hints.length > 0) {
572
- const translatedHints = await Promise.all(
573
- (state.hints ?? []).map(async hint => {
574
- const tHint = await getTranslation(
575
- hint,
576
- userLang,
577
- language,
578
- baseUrl
579
- );
580
- return {
581
- text: tHint?.text ?? hint,
582
- originalText: hint,
583
- } as TranslatedHint;
584
- })
585
- );
586
- translatedState = {
587
- ...state,
588
- emission: t.text,
589
- translatedHints,
590
- };
591
- } else {
592
- translatedState = {
593
- ...state,
594
- emission: t.text,
595
- hints:
596
- state.hints ??
597
- (state.state === 'G1' ? currentDialogState?.hints : []),
598
- };
599
- }
600
-
601
- if (t.text.length > 0)
602
- translatedMsg = {
603
- text: t.text,
604
- media: state.media,
605
- fromUser: false,
606
- generatedByAI: !!state.completion,
607
- };
608
- }
609
-
610
- setCurrentDialogState(translatedState);
611
- if (translatedMsg) {
612
- pushMessage(translatedMsg);
613
- }
614
-
615
- return translatedState;
616
- };
617
-
618
- /**
619
- * Age verification
620
- */
621
- const minAge = memori.ageRestriction
622
- ? memori.ageRestriction
623
- : memori.nsfw
624
- ? 18
625
- : memori.enableCompletions
626
- ? 14
627
- : 0;
628
- const [birthDate, setBirthDate] = useState<string | undefined>();
629
- const [showAgeVerification, setShowAgeVerification] = useState(false);
630
-
631
386
  /**
632
387
  * Sessione
633
388
  */
@@ -940,6 +695,264 @@ const MemoriWidget = ({
940
695
  // eslint-disable-next-line react-hooks/exhaustive-deps
941
696
  }, []);
942
697
 
698
+ /**
699
+ * History e gestione invio messaggi
700
+ */
701
+ const [userMessage, setUserMessage] = useState<string>('');
702
+ const onChangeUserMessage = (value: string) => {
703
+ if (!value || value === '\n' || value.trim() === '') {
704
+ setUserMessage('');
705
+ resetInteractionTimeout();
706
+ return;
707
+ }
708
+ setUserMessage(value);
709
+ clearInteractionTimeout();
710
+ };
711
+ const [listening, setListening] = useState(false);
712
+ const [history, setHistory] = useState<Message[]>([]);
713
+ const pushMessage = (message: Message) => {
714
+ setHistory(history => [...history, { ...message }]);
715
+ };
716
+ const sendMessage = useCallback(
717
+ async (
718
+ text: string,
719
+ media?: Medium[],
720
+ newSessionId?: string,
721
+ translate: boolean = true,
722
+ translatedText?: string,
723
+ hidden: boolean = false
724
+ ) => {
725
+ const sessionID = newSessionId || sessionId;
726
+ if (!sessionID || !text?.length) return;
727
+
728
+ if (!hidden)
729
+ pushMessage({
730
+ text: text,
731
+ translatedText,
732
+ fromUser: true,
733
+ media: media ?? [],
734
+ initial: !!newSessionId,
735
+ });
736
+
737
+ setMemoriTyping(true);
738
+
739
+ const language = memori.culture?.split('-')?.[0] ?? i18n.language ?? 'IT';
740
+ let msg = text;
741
+
742
+ if (
743
+ translate &&
744
+ !instruct &&
745
+ isMultilanguageEnabled &&
746
+ userLang.toUpperCase() !== language.toUpperCase()
747
+ ) {
748
+ const translation = await getTranslation(
749
+ text,
750
+ language,
751
+ userLang,
752
+ baseUrl
753
+ );
754
+ msg = translation.text;
755
+ }
756
+
757
+ const { currentState, ...response } = await postTextEnteredEvent({
758
+ sessionId: sessionID,
759
+ text: msg,
760
+ });
761
+ if (response.resultCode === 0 && currentState) {
762
+ const emission = currentState.emission ?? currentDialogState?.emission;
763
+ if (currentState.state === 'X4' && memori.giverTag) {
764
+ const { currentState, ...resp } = await postTagChangedEvent(
765
+ sessionID,
766
+ memori.giverTag
767
+ );
768
+
769
+ if (resp.resultCode === 0) {
770
+ setCurrentDialogState(currentState);
771
+
772
+ if (currentState.emission) {
773
+ pushMessage({
774
+ text: currentState.emission,
775
+ media: currentState.media,
776
+ fromUser: false,
777
+ });
778
+ speak(currentState.emission);
779
+ }
780
+ } else {
781
+ console.error(response, resp);
782
+ message.error(t(getErrori18nKey(resp.resultCode)));
783
+ }
784
+ } else if (currentState.state === 'X2d' && memori.giverTag) {
785
+ const { currentState, ...resp } = await postTextEnteredEvent({
786
+ sessionId: sessionID,
787
+ text: Math.random().toString().substring(2, 8),
788
+ });
789
+
790
+ if (resp.resultCode === 0) {
791
+ const { currentState, ...resp } = await postTagChangedEvent(
792
+ sessionID,
793
+ memori.giverTag
794
+ );
795
+
796
+ if (resp.resultCode === 0) {
797
+ setCurrentDialogState(currentState);
798
+
799
+ if (currentState.emission) {
800
+ pushMessage({
801
+ text: currentState.emission,
802
+ media: currentState.media,
803
+ fromUser: false,
804
+ });
805
+ speak(currentState.emission);
806
+ }
807
+ } else {
808
+ console.error(response, resp);
809
+ message.error(t(getErrori18nKey(resp.resultCode)));
810
+ }
811
+ } else {
812
+ console.error(response, resp);
813
+ message.error(t(getErrori18nKey(resp.resultCode)));
814
+ }
815
+ } else if (
816
+ userLang.toLowerCase() !== language.toLowerCase() &&
817
+ emission &&
818
+ !instruct &&
819
+ isMultilanguageEnabled
820
+ ) {
821
+ translateDialogState(currentState, userLang).then(ts => {
822
+ if (ts.emission) {
823
+ speak(ts.emission);
824
+ }
825
+ });
826
+ } else {
827
+ setCurrentDialogState({
828
+ ...currentState,
829
+ emission,
830
+ });
831
+
832
+ if (emission) {
833
+ pushMessage({
834
+ text: emission,
835
+ media: currentState.media,
836
+ fromUser: false,
837
+ generatedByAI: !!currentState.completion,
838
+ });
839
+ speak(emission);
840
+ }
841
+ }
842
+ } else if (response.resultCode === 404) {
843
+ // remove last sent message, will set it as initial
844
+ setHistory(h => [...h.slice(0, h.length - 1)]);
845
+
846
+ // post session timeout -> Z0/A0 -> restart session and re-send msg
847
+ reopenSession(
848
+ false,
849
+ memoriPwd || memori.secretToken,
850
+ memoriTokens,
851
+ instruct && memori.giverTag ? memori.giverTag : undefined,
852
+ instruct && memori.giverPIN ? memori.giverPIN : undefined,
853
+ initialContextVars,
854
+ initialQuestion
855
+ ).then(state => {
856
+ console.info('session timeout');
857
+ if (state?.sessionID) {
858
+ setTimeout(() => {
859
+ sendMessage(text, media, state?.sessionID);
860
+ }, 500);
861
+ }
862
+ });
863
+ }
864
+
865
+ setMemoriTyping(false);
866
+ },
867
+ [sessionId]
868
+ );
869
+
870
+ /**
871
+ * Traduzioni istantanee
872
+ */
873
+ const translateDialogState = async (state: DialogState, userLang: string) => {
874
+ const language = memori.culture?.split('-')?.[0] ?? i18n.language ?? 'IT';
875
+ const emission = state.emission ?? currentDialogState?.emission;
876
+
877
+ let translatedState = { ...state };
878
+ let translatedMsg = null;
879
+
880
+ if (
881
+ !emission ||
882
+ instruct ||
883
+ language.toUpperCase() === userLang.toUpperCase() ||
884
+ !isMultilanguageEnabled
885
+ ) {
886
+ translatedState = { ...state, emission };
887
+ if (emission) {
888
+ translatedMsg = {
889
+ text: emission,
890
+ media: state.media,
891
+ fromUser: false,
892
+ };
893
+ }
894
+ } else {
895
+ const t = await getTranslation(emission, userLang, language, baseUrl);
896
+ if (state.hints && state.hints.length > 0) {
897
+ const translatedHints = await Promise.all(
898
+ (state.hints ?? []).map(async hint => {
899
+ const tHint = await getTranslation(
900
+ hint,
901
+ userLang,
902
+ language,
903
+ baseUrl
904
+ );
905
+ return {
906
+ text: tHint?.text ?? hint,
907
+ originalText: hint,
908
+ } as TranslatedHint;
909
+ })
910
+ );
911
+ translatedState = {
912
+ ...state,
913
+ emission: t.text,
914
+ translatedHints,
915
+ };
916
+ } else {
917
+ translatedState = {
918
+ ...state,
919
+ emission: t.text,
920
+ hints:
921
+ state.hints ??
922
+ (state.state === 'G1' ? currentDialogState?.hints : []),
923
+ };
924
+ }
925
+
926
+ if (t.text.length > 0)
927
+ translatedMsg = {
928
+ text: t.text,
929
+ media: state.media,
930
+ fromUser: false,
931
+ generatedByAI: !!state.completion,
932
+ };
933
+ }
934
+
935
+ setCurrentDialogState(translatedState);
936
+ if (translatedMsg) {
937
+ pushMessage(translatedMsg);
938
+ }
939
+
940
+ return translatedState;
941
+ };
942
+
943
+ /**
944
+ * Age verification
945
+ */
946
+ const minAge = memori.ageRestriction
947
+ ? memori.ageRestriction
948
+ : memori.nsfw
949
+ ? 18
950
+ : memori.enableCompletions
951
+ ? 14
952
+ : 0;
953
+ const [birthDate, setBirthDate] = useState<string | undefined>();
954
+ const [showAgeVerification, setShowAgeVerification] = useState(false);
955
+
943
956
  /**
944
957
  * Timeout conversazione
945
958
  */
@@ -1302,6 +1315,8 @@ const MemoriWidget = ({
1302
1315
  if (preview) return;
1303
1316
 
1304
1317
  if (muteSpeaker || speakerMuted) {
1318
+ memoriSpeaking = false;
1319
+
1305
1320
  // trigger start continuous listening if set, see MemoriChat
1306
1321
  if (continuousSpeech) {
1307
1322
  setListeningTimeout();
@@ -1337,6 +1352,7 @@ const MemoriWidget = ({
1337
1352
 
1338
1353
  if (isPlayingAudio) {
1339
1354
  try {
1355
+ memoriSpeaking = false;
1340
1356
  if (speechSynthesizer) {
1341
1357
  speechSynthesizer.close();
1342
1358
  speechSynthesizer = null;
@@ -1389,9 +1405,11 @@ const MemoriWidget = ({
1389
1405
  const source = audioContext.createBufferSource();
1390
1406
  source.addEventListener('ended', () => {
1391
1407
  setIsPlayingAudio(false);
1408
+ memoriSpeaking = false;
1392
1409
  });
1393
1410
  audioDestination.onAudioEnd = () => {
1394
1411
  setIsPlayingAudio(false);
1412
+ memoriSpeaking = false;
1395
1413
  source.disconnect();
1396
1414
 
1397
1415
  // trigger start continuous listening if set
@@ -1403,12 +1421,13 @@ const MemoriWidget = ({
1403
1421
  `<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(
1404
1422
  userLang
1405
1423
  )}"><voice name="${getTTSVoice(userLang)}"><s>${replaceTextWithPhonemes(
1406
- escapeHTML(text),
1424
+ escapeHTML(stripEmojis(text)),
1407
1425
  userLang.toLowerCase()
1408
1426
  )}</s></voice></speak>`,
1409
1427
  result => {
1410
1428
  if (result) {
1411
1429
  setIsPlayingAudio(true);
1430
+ memoriSpeaking = true;
1412
1431
 
1413
1432
  try {
1414
1433
  // if (audioContext.destination.context.state === 'running') {
@@ -1430,6 +1449,7 @@ const MemoriWidget = ({
1430
1449
  ) {
1431
1450
  source.disconnect();
1432
1451
  setIsPlayingAudio(false);
1452
+ memoriSpeaking = false;
1433
1453
  } else if ((audioContext.state as string) === 'interrupted') {
1434
1454
  audioContext.resume();
1435
1455
  }
@@ -1445,6 +1465,7 @@ const MemoriWidget = ({
1445
1465
  console.error('speak error: ', e);
1446
1466
  window.speechSynthesis.speak(new SpeechSynthesisUtterance(text));
1447
1467
  setIsPlayingAudio(false);
1468
+ memoriSpeaking = false;
1448
1469
 
1449
1470
  if (speechSynthesizer) {
1450
1471
  speechSynthesizer.close();
@@ -1454,12 +1475,14 @@ const MemoriWidget = ({
1454
1475
  } else {
1455
1476
  audioContext.resume();
1456
1477
  setIsPlayingAudio(false);
1478
+ memoriSpeaking = false;
1457
1479
  }
1458
1480
  },
1459
1481
  error => {
1460
1482
  console.error('speak:', error);
1461
1483
  window.speechSynthesis.speak(new SpeechSynthesisUtterance(text));
1462
1484
  setIsPlayingAudio(false);
1485
+ memoriSpeaking = false;
1463
1486
  }
1464
1487
  );
1465
1488
 
@@ -1467,6 +1490,7 @@ const MemoriWidget = ({
1467
1490
  };
1468
1491
  const stopAudio = () => {
1469
1492
  setIsPlayingAudio(false);
1493
+ memoriSpeaking = false;
1470
1494
  try {
1471
1495
  if (speechSynthesizer) {
1472
1496
  speechSynthesizer.close();
@@ -1925,6 +1949,43 @@ const MemoriWidget = ({
1925
1949
  sendMessage(text, undefined, undefined, false, translatedText);
1926
1950
  };
1927
1951
 
1952
+ // listen to events from browser
1953
+ // to use in integrations or snippets
1954
+ const memoriTextEnteredHandler = useCallback(
1955
+ (e: MemoriTextEnteredEvent) => {
1956
+ const { text, waitForPrevious, hidden } = e.detail;
1957
+
1958
+ const sessionID =
1959
+ sessionId || (window.getMemoriState() as MemoriSession)?.sessionID;
1960
+
1961
+ if (text) {
1962
+ // wait to finish reading previous emission
1963
+ if (
1964
+ waitForPrevious &&
1965
+ !speakerMuted &&
1966
+ (memoriSpeaking || memoriTyping)
1967
+ ) {
1968
+ setTimeout(() => {
1969
+ memoriTextEnteredHandler(e);
1970
+ }, 1000);
1971
+ } else {
1972
+ sendMessage(text, undefined, sessionID, undefined, undefined, hidden);
1973
+ }
1974
+ }
1975
+ },
1976
+ [sessionId, isPlayingAudio, memoriTyping]
1977
+ );
1978
+ useEffect(() => {
1979
+ document.addEventListener('MemoriTextEntered', memoriTextEnteredHandler);
1980
+
1981
+ return () => {
1982
+ document.removeEventListener(
1983
+ 'MemoriTextEntered',
1984
+ memoriTextEnteredHandler
1985
+ );
1986
+ };
1987
+ }, []);
1988
+
1928
1989
  const onClickStart = useCallback(
1929
1990
  async (session?: { dialogState: DialogState; sessionID: string }) => {
1930
1991
  const sessionID = session?.sessionID || sessionId;