@memori.ai/memori-react 2.10.0 → 2.10.2

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.
@@ -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,13 +60,16 @@ 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';
66
71
  import AgeVerificationModal from '../AgeVerificationModal/AgeVerificationModal';
67
72
  import SettingsDrawer from '../SettingsDrawer/SettingsDrawer';
68
- import { MemoriSession } from '@memori.ai/memori-api-client/dist/types';
69
73
 
70
74
  // Widget utilities and helpers
71
75
  const getMemoriState = (integrationId?: string): object | null => {
@@ -92,20 +96,27 @@ const getMemoriState = (integrationId?: string): object | null => {
92
96
 
93
97
  type MemoriTextEnteredEvent = CustomEvent<{
94
98
  text: string;
99
+ waitForPrevious?: boolean;
95
100
  hidden?: boolean;
96
101
  }>;
97
102
 
98
- const typeMessage = (message: string, hidden = false) => {
103
+ const typeMessage = (
104
+ message: string,
105
+ waitForPrevious = true,
106
+ hidden = false
107
+ ) => {
99
108
  const e: MemoriTextEnteredEvent = new CustomEvent('MemoriTextEntered', {
100
109
  detail: {
101
110
  text: message,
111
+ waitForPrevious,
102
112
  hidden,
103
113
  },
104
114
  });
105
115
 
106
116
  document.dispatchEvent(e);
107
117
  };
108
- const typeMessageHidden = (message: string) => typeMessage(message, true);
118
+ const typeMessageHidden = (message: string, waitForPrevious = true) =>
119
+ typeMessage(message, waitForPrevious, true);
109
120
 
110
121
  interface CustomEventMap {
111
122
  MemoriTextEntered: MemoriTextEnteredEvent;
@@ -259,10 +270,12 @@ const MemoriWidget = ({
259
270
  const [clickedStart, setClickedStart] = useState(false);
260
271
  const [gotErrorInOpening, setGotErrorInOpening] = useState(false);
261
272
 
262
- const language = memoriConfigs
263
- ?.find(c => c.memoriConfigID === memori.memoriConfigurationID)
264
- ?.culture?.split('-')?.[0]
265
- ?.toUpperCase();
273
+ const language =
274
+ memori.culture?.split('-')?.[0]?.toUpperCase()! ||
275
+ memoriConfigs
276
+ ?.find(c => c.memoriConfigID === memori.memoriConfigurationID)
277
+ ?.culture?.split('-')?.[0]
278
+ ?.toUpperCase()!;
266
279
  const integrationConfig = integration?.customData
267
280
  ? JSON.parse(integration.customData)
268
281
  : null;
@@ -273,7 +286,6 @@ const MemoriWidget = ({
273
286
  const [userLang, setUserLang] = useState(
274
287
  memoriLang ??
275
288
  integrationConfig?.lang ??
276
- memori?.culture?.split('-')?.[0] ??
277
289
  language ??
278
290
  integrationConfig?.uiLang ??
279
291
  i18n.language ??
@@ -372,6 +384,264 @@ const MemoriWidget = ({
372
384
  applyPosition(venue);
373
385
  };
374
386
 
387
+ /**
388
+ * History e gestione invio messaggi
389
+ */
390
+ const [userMessage, setUserMessage] = useState<string>('');
391
+ const onChangeUserMessage = (value: string) => {
392
+ if (!value || value === '\n' || value.trim() === '') {
393
+ setUserMessage('');
394
+ resetInteractionTimeout();
395
+ return;
396
+ }
397
+ setUserMessage(value);
398
+ clearInteractionTimeout();
399
+ };
400
+ const [listening, setListening] = useState(false);
401
+ const [history, setHistory] = useState<Message[]>([]);
402
+ const pushMessage = (message: Message) => {
403
+ setHistory(history => [...history, { ...message }]);
404
+ };
405
+ const sendMessage = async (
406
+ text: string,
407
+ media?: Medium[],
408
+ newSessionId?: string,
409
+ translate: boolean = true,
410
+ translatedText?: string,
411
+ hidden: boolean = false
412
+ ) => {
413
+ const sessionID =
414
+ newSessionId ||
415
+ sessionId ||
416
+ (window.getMemoriState() as MemoriSession)?.sessionID;
417
+ if (!sessionID || !text?.length) return;
418
+
419
+ if (!hidden)
420
+ pushMessage({
421
+ text: text,
422
+ translatedText,
423
+ fromUser: true,
424
+ media: media ?? [],
425
+ initial: sessionId
426
+ ? !!newSessionId && newSessionId !== sessionId
427
+ : !!newSessionId,
428
+ });
429
+
430
+ setMemoriTyping(true);
431
+
432
+ let msg = text;
433
+
434
+ if (
435
+ translate &&
436
+ !instruct &&
437
+ isMultilanguageEnabled &&
438
+ userLang.toUpperCase() !== language.toUpperCase()
439
+ ) {
440
+ const translation = await getTranslation(
441
+ text,
442
+ language,
443
+ userLang,
444
+ baseUrl
445
+ );
446
+ msg = translation.text;
447
+ }
448
+
449
+ const { currentState, ...response } = await postTextEnteredEvent({
450
+ sessionId: sessionID,
451
+ text: msg,
452
+ });
453
+ if (response.resultCode === 0 && currentState) {
454
+ const emission = currentState.emission ?? currentDialogState?.emission;
455
+ if (currentState.state === 'X4' && memori.giverTag) {
456
+ const { currentState, ...resp } = await postTagChangedEvent(
457
+ sessionID,
458
+ memori.giverTag
459
+ );
460
+
461
+ if (resp.resultCode === 0) {
462
+ setCurrentDialogState(currentState);
463
+
464
+ if (currentState.emission) {
465
+ pushMessage({
466
+ text: currentState.emission,
467
+ media: currentState.media,
468
+ fromUser: false,
469
+ });
470
+ speak(currentState.emission);
471
+ }
472
+ } else {
473
+ console.error(response, resp);
474
+ message.error(t(getErrori18nKey(resp.resultCode)));
475
+ }
476
+ } else if (currentState.state === 'X2d' && memori.giverTag) {
477
+ const { currentState, ...resp } = await postTextEnteredEvent({
478
+ sessionId: sessionID,
479
+ text: Math.random().toString().substring(2, 8),
480
+ });
481
+
482
+ if (resp.resultCode === 0) {
483
+ const { currentState, ...resp } = await postTagChangedEvent(
484
+ sessionID,
485
+ memori.giverTag
486
+ );
487
+
488
+ if (resp.resultCode === 0) {
489
+ setCurrentDialogState(currentState);
490
+
491
+ if (currentState.emission) {
492
+ pushMessage({
493
+ text: currentState.emission,
494
+ media: currentState.media,
495
+ fromUser: false,
496
+ });
497
+ speak(currentState.emission);
498
+ }
499
+ } else {
500
+ console.error(response, resp);
501
+ message.error(t(getErrori18nKey(resp.resultCode)));
502
+ }
503
+ } else {
504
+ console.error(response, resp);
505
+ message.error(t(getErrori18nKey(resp.resultCode)));
506
+ }
507
+ } else if (
508
+ userLang.toLowerCase() !== language.toLowerCase() &&
509
+ emission &&
510
+ !instruct &&
511
+ isMultilanguageEnabled
512
+ ) {
513
+ translateDialogState(currentState, userLang).then(ts => {
514
+ if (ts.emission) {
515
+ speak(ts.emission);
516
+ }
517
+ });
518
+ } else {
519
+ setCurrentDialogState({
520
+ ...currentState,
521
+ emission,
522
+ });
523
+
524
+ if (emission) {
525
+ pushMessage({
526
+ text: emission,
527
+ media: currentState.media,
528
+ fromUser: false,
529
+ generatedByAI: !!currentState.completion,
530
+ });
531
+ speak(emission);
532
+ }
533
+ }
534
+ } else if (response.resultCode === 404) {
535
+ // remove last sent message, will set it as initial
536
+ setHistory(h => [...h.slice(0, h.length - 1)]);
537
+
538
+ // post session timeout -> Z0/A0 -> restart session and re-send msg
539
+ reopenSession(
540
+ false,
541
+ memoriPwd || memori.secretToken,
542
+ memoriTokens,
543
+ instruct && memori.giverTag ? memori.giverTag : undefined,
544
+ instruct && memori.giverPIN ? memori.giverPIN : undefined,
545
+ initialContextVars,
546
+ initialQuestion
547
+ ).then(state => {
548
+ console.info('session timeout');
549
+ if (state?.sessionID) {
550
+ setTimeout(() => {
551
+ sendMessage(text, media, state?.sessionID);
552
+ }, 500);
553
+ }
554
+ });
555
+ }
556
+
557
+ setMemoriTyping(false);
558
+ };
559
+
560
+ /**
561
+ * Traduzioni istantanee
562
+ */
563
+ const translateDialogState = async (state: DialogState, userLang: string) => {
564
+ const emission = state.emission ?? currentDialogState?.emission;
565
+
566
+ let translatedState = { ...state };
567
+ let translatedMsg = null;
568
+
569
+ if (
570
+ !emission ||
571
+ instruct ||
572
+ language.toUpperCase() === userLang.toUpperCase() ||
573
+ !isMultilanguageEnabled
574
+ ) {
575
+ translatedState = { ...state, emission };
576
+ if (emission) {
577
+ translatedMsg = {
578
+ text: emission,
579
+ media: state.media,
580
+ fromUser: false,
581
+ };
582
+ }
583
+ } else {
584
+ const t = await getTranslation(emission, userLang, language, baseUrl);
585
+ if (state.hints && state.hints.length > 0) {
586
+ const translatedHints = await Promise.all(
587
+ (state.hints ?? []).map(async hint => {
588
+ const tHint = await getTranslation(
589
+ hint,
590
+ userLang,
591
+ language,
592
+ baseUrl
593
+ );
594
+ return {
595
+ text: tHint?.text ?? hint,
596
+ originalText: hint,
597
+ } as TranslatedHint;
598
+ })
599
+ );
600
+ translatedState = {
601
+ ...state,
602
+ emission: t.text,
603
+ translatedHints,
604
+ };
605
+ } else {
606
+ translatedState = {
607
+ ...state,
608
+ emission: t.text,
609
+ hints:
610
+ state.hints ??
611
+ (state.state === 'G1' ? currentDialogState?.hints : []),
612
+ };
613
+ }
614
+
615
+ if (t.text.length > 0)
616
+ translatedMsg = {
617
+ text: t.text,
618
+ media: state.media,
619
+ fromUser: false,
620
+ generatedByAI: !!state.completion,
621
+ };
622
+ }
623
+
624
+ setCurrentDialogState(translatedState);
625
+ if (translatedMsg) {
626
+ pushMessage(translatedMsg);
627
+ }
628
+
629
+ return translatedState;
630
+ };
631
+
632
+ /**
633
+ * Age verification
634
+ */
635
+ const minAge = memori.ageRestriction
636
+ ? memori.ageRestriction
637
+ : memori.nsfw
638
+ ? 18
639
+ : memori.enableCompletions
640
+ ? 14
641
+ : 0;
642
+ const [birthDate, setBirthDate] = useState<string | undefined>();
643
+ const [showAgeVerification, setShowAgeVerification] = useState(false);
644
+
375
645
  /**
376
646
  * Sessione
377
647
  */
@@ -684,264 +954,6 @@ const MemoriWidget = ({
684
954
  // eslint-disable-next-line react-hooks/exhaustive-deps
685
955
  }, []);
686
956
 
687
- /**
688
- * History e gestione invio messaggi
689
- */
690
- const [userMessage, setUserMessage] = useState<string>('');
691
- const onChangeUserMessage = (value: string) => {
692
- if (!value || value === '\n' || value.trim() === '') {
693
- setUserMessage('');
694
- resetInteractionTimeout();
695
- return;
696
- }
697
- setUserMessage(value);
698
- clearInteractionTimeout();
699
- };
700
- const [listening, setListening] = useState(false);
701
- const [history, setHistory] = useState<Message[]>([]);
702
- const pushMessage = (message: Message) => {
703
- setHistory(history => [...history, { ...message }]);
704
- };
705
- const sendMessage = useCallback(
706
- async (
707
- text: string,
708
- media?: Medium[],
709
- newSessionId?: string,
710
- translate: boolean = true,
711
- translatedText?: string,
712
- hidden: boolean = false
713
- ) => {
714
- const sessionID = newSessionId || sessionId;
715
- if (!sessionID || !text?.length) return;
716
-
717
- if (!hidden)
718
- pushMessage({
719
- text: text,
720
- translatedText,
721
- fromUser: true,
722
- media: media ?? [],
723
- initial: !!newSessionId,
724
- });
725
-
726
- setMemoriTyping(true);
727
-
728
- const language = memori.culture?.split('-')?.[0] ?? i18n.language ?? 'IT';
729
- let msg = text;
730
-
731
- if (
732
- translate &&
733
- !instruct &&
734
- isMultilanguageEnabled &&
735
- userLang.toUpperCase() !== language.toUpperCase()
736
- ) {
737
- const translation = await getTranslation(
738
- text,
739
- language,
740
- userLang,
741
- baseUrl
742
- );
743
- msg = translation.text;
744
- }
745
-
746
- const { currentState, ...response } = await postTextEnteredEvent({
747
- sessionId: sessionID,
748
- text: msg,
749
- });
750
- if (response.resultCode === 0 && currentState) {
751
- const emission = currentState.emission ?? currentDialogState?.emission;
752
- if (currentState.state === 'X4' && memori.giverTag) {
753
- const { currentState, ...resp } = await postTagChangedEvent(
754
- sessionID,
755
- memori.giverTag
756
- );
757
-
758
- if (resp.resultCode === 0) {
759
- setCurrentDialogState(currentState);
760
-
761
- if (currentState.emission) {
762
- pushMessage({
763
- text: currentState.emission,
764
- media: currentState.media,
765
- fromUser: false,
766
- });
767
- speak(currentState.emission);
768
- }
769
- } else {
770
- console.error(response, resp);
771
- message.error(t(getErrori18nKey(resp.resultCode)));
772
- }
773
- } else if (currentState.state === 'X2d' && memori.giverTag) {
774
- const { currentState, ...resp } = await postTextEnteredEvent({
775
- sessionId: sessionID,
776
- text: Math.random().toString().substring(2, 8),
777
- });
778
-
779
- if (resp.resultCode === 0) {
780
- const { currentState, ...resp } = await postTagChangedEvent(
781
- sessionID,
782
- memori.giverTag
783
- );
784
-
785
- if (resp.resultCode === 0) {
786
- setCurrentDialogState(currentState);
787
-
788
- if (currentState.emission) {
789
- pushMessage({
790
- text: currentState.emission,
791
- media: currentState.media,
792
- fromUser: false,
793
- });
794
- speak(currentState.emission);
795
- }
796
- } else {
797
- console.error(response, resp);
798
- message.error(t(getErrori18nKey(resp.resultCode)));
799
- }
800
- } else {
801
- console.error(response, resp);
802
- message.error(t(getErrori18nKey(resp.resultCode)));
803
- }
804
- } else if (
805
- userLang.toLowerCase() !== language.toLowerCase() &&
806
- emission &&
807
- !instruct &&
808
- isMultilanguageEnabled
809
- ) {
810
- translateDialogState(currentState, userLang).then(ts => {
811
- if (ts.emission) {
812
- speak(ts.emission);
813
- }
814
- });
815
- } else {
816
- setCurrentDialogState({
817
- ...currentState,
818
- emission,
819
- });
820
-
821
- if (emission) {
822
- pushMessage({
823
- text: emission,
824
- media: currentState.media,
825
- fromUser: false,
826
- generatedByAI: !!currentState.completion,
827
- });
828
- speak(emission);
829
- }
830
- }
831
- } else if (response.resultCode === 404) {
832
- // remove last sent message, will set it as initial
833
- setHistory(h => [...h.slice(0, h.length - 1)]);
834
-
835
- // post session timeout -> Z0/A0 -> restart session and re-send msg
836
- reopenSession(
837
- false,
838
- memoriPwd || memori.secretToken,
839
- memoriTokens,
840
- instruct && memori.giverTag ? memori.giverTag : undefined,
841
- instruct && memori.giverPIN ? memori.giverPIN : undefined,
842
- initialContextVars,
843
- initialQuestion
844
- ).then(state => {
845
- console.info('session timeout');
846
- if (state?.sessionID) {
847
- setTimeout(() => {
848
- sendMessage(text, media, state?.sessionID);
849
- }, 500);
850
- }
851
- });
852
- }
853
-
854
- setMemoriTyping(false);
855
- },
856
- [sessionId]
857
- );
858
-
859
- /**
860
- * Traduzioni istantanee
861
- */
862
- const translateDialogState = async (state: DialogState, userLang: string) => {
863
- const language = memori.culture?.split('-')?.[0] ?? i18n.language ?? 'IT';
864
- const emission = state.emission ?? currentDialogState?.emission;
865
-
866
- let translatedState = { ...state };
867
- let translatedMsg = null;
868
-
869
- if (
870
- !emission ||
871
- instruct ||
872
- language.toUpperCase() === userLang.toUpperCase() ||
873
- !isMultilanguageEnabled
874
- ) {
875
- translatedState = { ...state, emission };
876
- if (emission) {
877
- translatedMsg = {
878
- text: emission,
879
- media: state.media,
880
- fromUser: false,
881
- };
882
- }
883
- } else {
884
- const t = await getTranslation(emission, userLang, language, baseUrl);
885
- if (state.hints && state.hints.length > 0) {
886
- const translatedHints = await Promise.all(
887
- (state.hints ?? []).map(async hint => {
888
- const tHint = await getTranslation(
889
- hint,
890
- userLang,
891
- language,
892
- baseUrl
893
- );
894
- return {
895
- text: tHint?.text ?? hint,
896
- originalText: hint,
897
- } as TranslatedHint;
898
- })
899
- );
900
- translatedState = {
901
- ...state,
902
- emission: t.text,
903
- translatedHints,
904
- };
905
- } else {
906
- translatedState = {
907
- ...state,
908
- emission: t.text,
909
- hints:
910
- state.hints ??
911
- (state.state === 'G1' ? currentDialogState?.hints : []),
912
- };
913
- }
914
-
915
- if (t.text.length > 0)
916
- translatedMsg = {
917
- text: t.text,
918
- media: state.media,
919
- fromUser: false,
920
- generatedByAI: !!state.completion,
921
- };
922
- }
923
-
924
- setCurrentDialogState(translatedState);
925
- if (translatedMsg) {
926
- pushMessage(translatedMsg);
927
- }
928
-
929
- return translatedState;
930
- };
931
-
932
- /**
933
- * Age verification
934
- */
935
- const minAge = memori.ageRestriction
936
- ? memori.ageRestriction
937
- : memori.nsfw
938
- ? 18
939
- : memori.enableCompletions
940
- ? 14
941
- : 0;
942
- const [birthDate, setBirthDate] = useState<string | undefined>();
943
- const [showAgeVerification, setShowAgeVerification] = useState(false);
944
-
945
957
  /**
946
958
  * Timeout conversazione
947
959
  */
@@ -1410,7 +1422,7 @@ const MemoriWidget = ({
1410
1422
  `<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(
1411
1423
  userLang
1412
1424
  )}"><voice name="${getTTSVoice(userLang)}"><s>${replaceTextWithPhonemes(
1413
- escapeHTML(text),
1425
+ escapeHTML(stripEmojis(text)),
1414
1426
  userLang.toLowerCase()
1415
1427
  )}</s></voice></speak>`,
1416
1428
  result => {
@@ -1942,23 +1954,29 @@ const MemoriWidget = ({
1942
1954
  // to use in integrations or snippets
1943
1955
  const memoriTextEnteredHandler = useCallback(
1944
1956
  (e: MemoriTextEnteredEvent) => {
1945
- const { text, hidden } = e.detail;
1957
+ const { text, waitForPrevious, hidden } = e.detail;
1946
1958
 
1947
1959
  const sessionID =
1948
1960
  sessionId || (window.getMemoriState() as MemoriSession)?.sessionID;
1949
1961
 
1950
1962
  if (text) {
1951
1963
  // wait to finish reading previous emission
1952
- if (!speakerMuted && (memoriSpeaking || memoriTyping)) {
1964
+ if (
1965
+ waitForPrevious &&
1966
+ !speakerMuted &&
1967
+ (memoriSpeaking || memoriTyping)
1968
+ ) {
1953
1969
  setTimeout(() => {
1954
1970
  memoriTextEnteredHandler(e);
1955
1971
  }, 1000);
1956
1972
  } else {
1957
- sendMessage(text, undefined, sessionID, undefined, undefined, hidden);
1973
+ stopListening();
1974
+ stopAudio();
1975
+ sendMessage(text, undefined, undefined, undefined, undefined, hidden);
1958
1976
  }
1959
1977
  }
1960
1978
  },
1961
- [sessionId, isPlayingAudio, memoriTyping]
1979
+ [sessionId, isPlayingAudio, memoriTyping, userLang]
1962
1980
  );
1963
1981
  useEffect(() => {
1964
1982
  document.addEventListener('MemoriTextEntered', memoriTextEnteredHandler);
@@ -1969,7 +1987,7 @@ const MemoriWidget = ({
1969
1987
  memoriTextEnteredHandler
1970
1988
  );
1971
1989
  };
1972
- }, []);
1990
+ }, [sessionId, userLang]);
1973
1991
 
1974
1992
  const onClickStart = useCallback(
1975
1993
  async (session?: { dialogState: DialogState; sessionID: string }) => {
@@ -1,4 +1,4 @@
1
- import { difference } from './utils';
1
+ import { difference, stripEmojis } from './utils';
2
2
 
3
3
  describe('Utils/difference', () => {
4
4
  it('should return the difference between two objects with numeric values', () => {
@@ -29,3 +29,17 @@ describe('Utils/difference', () => {
29
29
  expect(result).toEqual({ a: ['alpha', 'beta', 'gamma'] });
30
30
  });
31
31
  });
32
+
33
+ describe('utils/stripEmojis', () => {
34
+ it('should strip emojis from a string', () => {
35
+ const text = 'Hello 👋ðŸŧ';
36
+ const result = stripEmojis(text);
37
+ expect(result).toEqual('Hello');
38
+ });
39
+
40
+ it('should strip emojis from a string with multiple emojis', () => {
41
+ const text = '😊 Hello 😉ðŸĪŠâ™Ĩïļ';
42
+ const result = stripEmojis(text);
43
+ expect(result).toEqual('Hello');
44
+ });
45
+ });
@@ -106,6 +106,10 @@ export const stripDuplicates = (text: string) => {
106
106
  return text;
107
107
  };
108
108
 
109
+ export const stripEmojis = (text: string) => {
110
+ return text.replaceAll(/[^\p{L}\p{N}\p{P}\p{Z}^$\n]/gu, '').trim();
111
+ };
112
+
109
113
  export const getFieldFromCustomData = (
110
114
  fieldName: string,
111
115
  data: string | undefined