@memori.ai/memori-react 8.41.0 → 8.41.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/Avatar/Avatar.js +5 -10
  3. package/dist/components/Avatar/Avatar.js.map +1 -1
  4. package/dist/components/MemoriWidget/MemoriWidget.js +123 -26
  5. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  6. package/dist/components/layouts/website-assistant.css +2 -1
  7. package/dist/helpers/nats/isSessionExpiredError.d.ts +3 -0
  8. package/dist/helpers/nats/isSessionExpiredError.js +37 -0
  9. package/dist/helpers/nats/isSessionExpiredError.js.map +1 -0
  10. package/dist/locales/de.json +1 -0
  11. package/dist/locales/en.json +1 -0
  12. package/dist/locales/es.json +1 -0
  13. package/dist/locales/fr.json +1 -0
  14. package/dist/locales/it.json +1 -0
  15. package/dist/types/integration.d.ts +3 -0
  16. package/dist/types/integration.js +16 -0
  17. package/dist/types/integration.js.map +1 -0
  18. package/dist/version.d.ts +1 -1
  19. package/dist/version.js +1 -1
  20. package/esm/components/Avatar/Avatar.js +5 -10
  21. package/esm/components/Avatar/Avatar.js.map +1 -1
  22. package/esm/components/MemoriWidget/MemoriWidget.js +123 -26
  23. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  24. package/esm/components/layouts/website-assistant.css +2 -1
  25. package/esm/helpers/nats/isSessionExpiredError.d.ts +3 -0
  26. package/esm/helpers/nats/isSessionExpiredError.js +32 -0
  27. package/esm/helpers/nats/isSessionExpiredError.js.map +1 -0
  28. package/esm/locales/de.json +1 -0
  29. package/esm/locales/en.json +1 -0
  30. package/esm/locales/es.json +1 -0
  31. package/esm/locales/fr.json +1 -0
  32. package/esm/locales/it.json +1 -0
  33. package/esm/types/integration.d.ts +3 -0
  34. package/esm/types/integration.js +11 -0
  35. package/esm/types/integration.js.map +1 -0
  36. package/esm/version.d.ts +1 -1
  37. package/esm/version.js +1 -1
  38. package/package.json +1 -1
  39. package/src/components/Avatar/Avatar.stories.tsx +45 -28
  40. package/src/components/Avatar/Avatar.test.tsx +27 -0
  41. package/src/components/Avatar/Avatar.tsx +8 -12
  42. package/src/components/Avatar/__snapshots__/Avatar.test.tsx.snap +63 -0
  43. package/src/components/MemoriWidget/MemoriWidget.tsx +195 -46
  44. package/src/components/layouts/website-assistant.css +2 -1
  45. package/src/helpers/nats/isSessionExpiredError.test.ts +38 -0
  46. package/src/helpers/nats/isSessionExpiredError.ts +51 -0
  47. package/src/index.stories.tsx +16 -0
  48. package/src/locales/de.json +1 -0
  49. package/src/locales/en.json +1 -0
  50. package/src/locales/es.json +1 -0
  51. package/src/locales/fr.json +1 -0
  52. package/src/locales/it.json +1 -0
  53. package/src/types/integration.test.ts +32 -0
  54. package/src/types/integration.ts +31 -0
  55. package/src/version.ts +1 -1
@@ -91,6 +91,10 @@ import {
91
91
  NatsDialogResponseEvent,
92
92
  NatsErrorEvent,
93
93
  } from '../../helpers/nats/useNatsSession';
94
+ import {
95
+ isSessionExpiredNatsError,
96
+ isSessionExpiredNatsResponse,
97
+ } from '../../helpers/nats/isSessionExpiredError';
94
98
 
95
99
  // Widget utilities and helpers
96
100
  const getMemoriState = (integrationId?: string): object | null => {
@@ -646,11 +650,21 @@ const MemoriWidget = ({
646
650
  const [memoriTyping, setMemoriTyping] = useState<boolean>(false);
647
651
  const [typingText, setTypingText] = useState<string>();
648
652
 
649
- type PendingEnterText = {
650
- msg?: string;
653
+ type EnterTextRetryParams = {
654
+ text?: string;
655
+ media?: Medium[];
656
+ translate?: boolean;
657
+ translatedText?: string;
658
+ hidden?: boolean;
651
659
  typingText?: string;
652
660
  useLoaderTextAsMsg?: boolean;
653
661
  hasBatchQueued?: boolean;
662
+ expiredSessionID?: string;
663
+ continueFromChatLogID?: string;
664
+ };
665
+
666
+ type PendingEnterText = EnterTextRetryParams & {
667
+ msg?: string;
654
668
  waitForResponse?: {
655
669
  resolve: (event: NatsDialogResponseEvent) => void;
656
670
  reject: (error: Error) => void;
@@ -893,6 +907,7 @@ const MemoriWidget = ({
893
907
  * @param typingText Optional custom typing indicator text
894
908
  * @param useLoaderTextAsMsg Whether to use the loader text as the message (default false)
895
909
  * @param hasBatchQueued Whether there are more messages queued to be sent (default false)
910
+ * @param skipHistoryPush Skip adding the user message to history (e.g. session-expired retry)
896
911
  */
897
912
  const sendMessage = async (
898
913
  text: string,
@@ -903,7 +918,8 @@ const MemoriWidget = ({
903
918
  hidden: boolean = false,
904
919
  typingText?: string,
905
920
  useLoaderTextAsMsg = false,
906
- hasBatchQueued = false
921
+ hasBatchQueued = false,
922
+ skipHistoryPush = false
907
923
  ) => {
908
924
  // Get the session ID from params or global state
909
925
  const sessionID =
@@ -970,7 +986,7 @@ const MemoriWidget = ({
970
986
  }
971
987
 
972
988
  // Add user message to chat history if not hidden
973
- if (!hidden)
989
+ if (!hidden && !skipHistoryPush)
974
990
  pushMessage({
975
991
  text: text,
976
992
  translatedText,
@@ -998,6 +1014,11 @@ const MemoriWidget = ({
998
1014
  if (response.resultCode === 0 && correlationID) {
999
1015
  registerPendingEnterText(correlationID, {
1000
1016
  msg,
1017
+ text,
1018
+ media,
1019
+ translate,
1020
+ translatedText,
1021
+ hidden,
1001
1022
  typingText,
1002
1023
  useLoaderTextAsMsg,
1003
1024
  hasBatchQueued,
@@ -1007,34 +1028,17 @@ const MemoriWidget = ({
1007
1028
  } else if (response.resultCode === 0) {
1008
1029
  logWidgetError('enter-text missing correlationID', response);
1009
1030
  } else if (response.resultCode === 404) {
1010
- // Handle expired session
1011
- // remove last sent message, will set it as initial
1012
- setHistory(h => [...h.slice(0, h.length - 1)]);
1013
-
1014
- reopenSession(
1015
- true,
1016
- memoriPwd || memori.secretToken,
1017
- memoriTokens,
1018
- undefined,
1019
- undefined,
1020
- {
1021
- LANG: userLang,
1022
- PATHNAME: window.location.pathname,
1023
- ROUTE: window.location.pathname?.split('/')?.pop() || '',
1024
- ...(initialContextVars || {}),
1025
- },
1026
- initialQuestion,
1027
- undefined,
1028
- undefined,
1029
- undefined,
1030
- undefined,
1031
- true // isSessionExpired
1032
- ).then(state => {
1033
- if (state?.sessionID) {
1034
- setTimeout(() => {
1035
- sendMessage(text, media, state?.sessionID);
1036
- }, 500);
1037
- }
1031
+ retryAfterExpiredSessionRef.current({
1032
+ text,
1033
+ media,
1034
+ translate,
1035
+ translatedText,
1036
+ hidden,
1037
+ typingText,
1038
+ useLoaderTextAsMsg,
1039
+ hasBatchQueued,
1040
+ expiredSessionID: sessionID,
1041
+ continueFromChatLogID: chatLogID,
1038
1042
  });
1039
1043
  } else if (response.resultCode === 500 && response.resultMessage) {
1040
1044
  setHistory(h => [
@@ -1487,7 +1491,8 @@ const MemoriWidget = ({
1487
1491
  additionalInfoProp?: { [key: string]: string | undefined },
1488
1492
  continueFromChatLogID?: string,
1489
1493
  continueFromSessionID?: string,
1490
- isSessionExpired?: boolean
1494
+ isSessionExpired?: boolean,
1495
+ suppressHistoryUpdate?: boolean
1491
1496
  ) => {
1492
1497
  // Set loading state while reopening session
1493
1498
  setLoading(true);
@@ -1577,12 +1582,28 @@ const MemoriWidget = ({
1577
1582
  if (updateDialogState) {
1578
1583
  setCurrentDialogState(currentState);
1579
1584
 
1580
- if (currentState.emission) {
1585
+ const sessionExpiredStatus =
1586
+ isSessionExpired && history.length > 1
1587
+ ? t('sessionExpiredReopening')
1588
+ : null;
1589
+
1590
+ if (sessionExpiredStatus && suppressHistoryUpdate) {
1591
+ pushMessage({
1592
+ text: '',
1593
+ emitter: 'system',
1594
+ fromUser: false,
1595
+ initial: sessionExpiredStatus as any,
1596
+ contextVars: {},
1597
+ date: new Date().toISOString(),
1598
+ });
1599
+ }
1600
+
1601
+ if (currentState.emission && !suppressHistoryUpdate) {
1581
1602
  // Determine initial status message based on context
1582
1603
  // Show status message only if session expired and there's existing history
1583
1604
  const initialStatus =
1584
- isSessionExpired && history.length > 1
1585
- ? 'Session Expired, reopening session'
1605
+ sessionExpiredStatus
1606
+ ? sessionExpiredStatus
1586
1607
  : history.length <= 1
1587
1608
  ? true
1588
1609
  : undefined;
@@ -1661,6 +1682,94 @@ const MemoriWidget = ({
1661
1682
  return null;
1662
1683
  };
1663
1684
 
1685
+ const retryAfterExpiredSessionRef = useRef<
1686
+ (params: EnterTextRetryParams) => ReturnType<typeof reopenSession>
1687
+ >(() => Promise.resolve(null));
1688
+
1689
+ retryAfterExpiredSessionRef.current = (params: EnterTextRetryParams) => {
1690
+ const {
1691
+ text,
1692
+ media,
1693
+ translate = true,
1694
+ translatedText,
1695
+ hidden = false,
1696
+ typingText,
1697
+ useLoaderTextAsMsg = false,
1698
+ hasBatchQueued = false,
1699
+ expiredSessionID,
1700
+ continueFromChatLogID: continueFromChatLogIDParam,
1701
+ } = params;
1702
+
1703
+ const continueFromSessionID = expiredSessionID ?? sessionId;
1704
+ const continueFromChatLogID = continueFromChatLogIDParam ?? chatLogID;
1705
+
1706
+ const reopenAfterExpiry = () =>
1707
+ reopenSession(
1708
+ true,
1709
+ memoriPwd || memori.secretToken,
1710
+ memoriTokens,
1711
+ undefined,
1712
+ undefined,
1713
+ {
1714
+ LANG: userLang,
1715
+ PATHNAME: window.location.pathname,
1716
+ ROUTE: window.location.pathname?.split('/')?.pop() || '',
1717
+ ...(initialContextVars || {}),
1718
+ },
1719
+ initialQuestion,
1720
+ undefined,
1721
+ undefined,
1722
+ continueFromChatLogID,
1723
+ continueFromSessionID,
1724
+ true,
1725
+ true
1726
+ );
1727
+
1728
+ const scheduleRetry = (newSessionID: string) => {
1729
+ setTimeout(() => {
1730
+ sendMessage(
1731
+ text!,
1732
+ media,
1733
+ newSessionID,
1734
+ translate,
1735
+ translatedText,
1736
+ hidden,
1737
+ typingText,
1738
+ useLoaderTextAsMsg,
1739
+ hasBatchQueued,
1740
+ true
1741
+ );
1742
+ }, 500);
1743
+ };
1744
+
1745
+ const handleReopenFailure = (state: Awaited<ReturnType<typeof reopenSession>>) => {
1746
+ setMemoriTyping(false);
1747
+ setTypingText(undefined);
1748
+ // `null` = explicit failure; `undefined` = auth/age modal opened (message kept in history)
1749
+ if (state === null && text && !hidden) {
1750
+ toast.error(t('errors.SESSION_EXPIRED'));
1751
+ }
1752
+ };
1753
+
1754
+ if (!text) {
1755
+ return reopenAfterExpiry().then(state => {
1756
+ if (!state?.sessionID) {
1757
+ handleReopenFailure(state);
1758
+ }
1759
+ return state;
1760
+ });
1761
+ }
1762
+
1763
+ return reopenAfterExpiry().then(state => {
1764
+ if (state?.sessionID) {
1765
+ scheduleRetry(state.sessionID);
1766
+ } else {
1767
+ handleReopenFailure(state);
1768
+ }
1769
+ return state;
1770
+ });
1771
+ };
1772
+
1664
1773
  const changeTag = async (
1665
1774
  memoriId: string,
1666
1775
  sessionId: string,
@@ -1990,6 +2099,24 @@ const MemoriWidget = ({
1990
2099
  const currentState = event.currentState;
1991
2100
 
1992
2101
  if (event.resultCode !== 0 || !currentState) {
2102
+ if (isSessionExpiredNatsResponse(event) && pending.text) {
2103
+ setMemoriTyping(false);
2104
+ setTypingText(undefined);
2105
+ retryAfterExpiredSessionRef.current({
2106
+ text: pending.text,
2107
+ media: pending.media,
2108
+ translate: pending.translate,
2109
+ translatedText: pending.translatedText,
2110
+ hidden: pending.hidden,
2111
+ typingText: pending.typingText,
2112
+ useLoaderTextAsMsg: pending.useLoaderTextAsMsg,
2113
+ hasBatchQueued: pending.hasBatchQueued,
2114
+ expiredSessionID: sessionId,
2115
+ continueFromChatLogID: chatLogID,
2116
+ });
2117
+ return;
2118
+ }
2119
+
1993
2120
  if (event.resultCode === 500 && event.resultMessage) {
1994
2121
  setHistory(h => [
1995
2122
  ...h,
@@ -2083,6 +2210,38 @@ const MemoriWidget = ({
2083
2210
  const deliverEnterTextNatsError = useCallback(
2084
2211
  (event: NatsErrorEvent) => {
2085
2212
  const correlationID = event.correlationID;
2213
+ let pending: PendingEnterText | undefined;
2214
+
2215
+ if (correlationID) {
2216
+ pending = pendingEnterTextRef.current.get(correlationID);
2217
+ if (pending) {
2218
+ clearEnterTextPending(correlationID, pending);
2219
+ pending.waitForResponse?.reject(
2220
+ new Error(
2221
+ event.errorMessage ?? String(event.errorCode ?? 'NATS error')
2222
+ )
2223
+ );
2224
+ }
2225
+ }
2226
+
2227
+ if (isSessionExpiredNatsError(event) && pending?.text) {
2228
+ setMemoriTyping(false);
2229
+ setTypingText(undefined);
2230
+ retryAfterExpiredSessionRef.current({
2231
+ text: pending.text,
2232
+ media: pending.media,
2233
+ translate: pending.translate,
2234
+ translatedText: pending.translatedText,
2235
+ hidden: pending.hidden,
2236
+ typingText: pending.typingText,
2237
+ useLoaderTextAsMsg: pending.useLoaderTextAsMsg,
2238
+ hasBatchQueued: pending.hasBatchQueued,
2239
+ expiredSessionID: sessionId,
2240
+ continueFromChatLogID: chatLogID,
2241
+ });
2242
+ return;
2243
+ }
2244
+
2086
2245
  const errorText = event.errorMessage
2087
2246
  ? `Error: ${event.errorMessage}`
2088
2247
  : event.errorCode
@@ -2098,16 +2257,6 @@ const MemoriWidget = ({
2098
2257
  date: new Date().toISOString(),
2099
2258
  });
2100
2259
 
2101
- if (correlationID) {
2102
- const pending = pendingEnterTextRef.current.get(correlationID);
2103
- if (pending) {
2104
- clearEnterTextPending(correlationID, pending);
2105
- pending.waitForResponse?.reject(
2106
- new Error(event.errorMessage ?? String(event.errorCode ?? 'NATS error'))
2107
- );
2108
- }
2109
- }
2110
-
2111
2260
  setMemoriTyping(false);
2112
2261
  setTypingText(undefined);
2113
2262
  },
@@ -397,7 +397,8 @@
397
397
  transform: none !important;
398
398
  }
399
399
 
400
- .memori-layout-website_assistant.memori--avatar-readyplayerme-full .memori--avatar-wrapper > div {
400
+ .memori-layout-website_assistant.memori--avatar-readyplayerme-full .memori--avatar-wrapper > div,
401
+ .memori-layout-website_assistant.memori--avatar-avatar-configurator .memori--avatar-wrapper > div {
401
402
  transform: scale(1.7) translate(0px, 10vh);
402
403
  }
403
404
 
@@ -0,0 +1,38 @@
1
+ import {
2
+ isSessionExpiredNatsError,
3
+ isSessionExpiredNatsResponse,
4
+ } from './isSessionExpiredError';
5
+
6
+ describe('isSessionExpiredNatsError', () => {
7
+ it('detects session not found from EnterTextAsync NATS error', () => {
8
+ expect(
9
+ isSessionExpiredNatsError({
10
+ eventType: 'error',
11
+ errorCode: 'EnterTextAsync Error',
12
+ errorMessage:
13
+ 'Session with ID "c681a10b-37c1-4783-af3b-ecb592b93036" not found',
14
+ correlationID: 'f85a6882-92e1-4e18-9fb7-29d3bc06fd7b',
15
+ })
16
+ ).toBe(true);
17
+ });
18
+
19
+ it('detects SESSION_EXPIRED result code on dialog response', () => {
20
+ expect(
21
+ isSessionExpiredNatsResponse({
22
+ eventType: 'dialog_text_entered_response',
23
+ resultCode: -103,
24
+ correlationID: 'abc',
25
+ })
26
+ ).toBe(true);
27
+ });
28
+
29
+ it('ignores unrelated NATS errors', () => {
30
+ expect(
31
+ isSessionExpiredNatsError({
32
+ eventType: 'error',
33
+ errorCode: 'SomeOtherError',
34
+ errorMessage: 'Something went wrong',
35
+ })
36
+ ).toBe(false);
37
+ });
38
+ });
@@ -0,0 +1,51 @@
1
+ import type {
2
+ NatsDialogResponseEvent,
3
+ NatsErrorEvent,
4
+ } from './useNatsSession';
5
+
6
+ /** Backend result codes for missing / expired sessions. */
7
+ const SESSION_NOT_FOUND = -101;
8
+ const SESSION_EXPIRED = -103;
9
+
10
+ const SESSION_NOT_FOUND_MESSAGE =
11
+ /session\s+with\s+id\s+["']?[^"']+["']?\s+not\s+found/i;
12
+
13
+ function isExpiredResultCode(resultCode?: number): boolean {
14
+ return (
15
+ resultCode === 404 ||
16
+ resultCode === SESSION_EXPIRED ||
17
+ resultCode === SESSION_NOT_FOUND
18
+ );
19
+ }
20
+
21
+ function isExpiredErrorMessage(message?: string): boolean {
22
+ if (!message) return false;
23
+ return SESSION_NOT_FOUND_MESSAGE.test(message);
24
+ }
25
+
26
+ /** True when a NATS `error` event indicates the active session is gone or expired. */
27
+ export function isSessionExpiredNatsError(event: NatsErrorEvent): boolean {
28
+ if (isExpiredResultCode(event.errorCode as number | undefined)) {
29
+ return true;
30
+ }
31
+
32
+ if (typeof event.errorCode === 'string') {
33
+ const code = event.errorCode.toUpperCase();
34
+ if (code.includes('SESSION_EXPIRED') || code.includes('SESSION_NOT_FOUND')) {
35
+ return true;
36
+ }
37
+ }
38
+
39
+ return isExpiredErrorMessage(event.errorMessage);
40
+ }
41
+
42
+ /** True when a NATS dialog response reports a missing or expired session. */
43
+ export function isSessionExpiredNatsResponse(
44
+ event: NatsDialogResponseEvent
45
+ ): boolean {
46
+ if (isExpiredResultCode(event.resultCode)) {
47
+ return true;
48
+ }
49
+
50
+ return isExpiredErrorMessage(event.resultMessage);
51
+ }
@@ -202,3 +202,19 @@ WithLocalNats.args = {
202
202
  spokenLang: 'IT',
203
203
  integrationID: 'ee1c3d98-7819-4506-ba28-818e79ba86cb',
204
204
  };
205
+
206
+ export const WithFunctionalities = Template.bind({});
207
+ WithFunctionalities.args = {
208
+ memoriName: 'test324',
209
+ ownerUserName: 'andrea.patini',
210
+ memoriID: 'd661a9ca-e907-4396-a986-5095ccd582d6',
211
+ ownerUserID: '69fcc557-9cb6-4e5e-b8ab-140cff975492',
212
+ tenantID: 'localhost:3000',
213
+ engineURL: 'http://localhost:7778/memori/v2',
214
+ apiURL: 'http://localhost:7778/api/v2',
215
+ baseURL: 'http://localhost:3000',
216
+ layout: 'FULLPAGE',
217
+ uiLang: 'IT',
218
+ spokenLang: 'IT',
219
+ integrationID: 'ee1c3d98-7819-4506-ba28-818e79ba86cb',
220
+ };
@@ -75,6 +75,7 @@
75
75
  "errorFetchingSession": "Fehler beim Laden der Sitzung",
76
76
  "errorGettingReferralURL": "Fehler beim Laden des Referrals",
77
77
  "errorReopeningSession": "Fehler beim erneuten Öffnen der Sitzung",
78
+ "sessionExpiredReopening": "Sitzung abgelaufen, Sitzung wird neu geöffnet",
78
79
  "ageVerification": "Altersüberprüfung",
79
80
  "ageVerificationText": "Um mit diesem Zwilling interagieren zu können, müssen Sie mindestens sein {{minAge}} Jahre alt.",
80
81
  "nsfw": "NSFW: Dieser Agent enthält Inhalte für Erwachsene",
@@ -77,6 +77,7 @@
77
77
  "errorFetchingSession": "Error during session loading",
78
78
  "errorGettingReferralURL": "Error during referral loading",
79
79
  "errorReopeningSession": "Error during session reopening",
80
+ "sessionExpiredReopening": "Session expired, reopening session",
80
81
  "ageVerification": "Age verification",
81
82
  "ageVerificationText": "To interact with this agent, you must be at least {{minAge}} years old.",
82
83
  "nsfw": "NSFW: This agent contains adult contents",
@@ -75,6 +75,7 @@
75
75
  "errorFetchingSession": "Error durante el cargamento de la sesión",
76
76
  "errorGettingReferralURL": "Error durante el cargamento del référent",
77
77
  "errorReopeningSession": "Error durante el re-abrir la sesión",
78
+ "sessionExpiredReopening": "Sesión expirada, reabriendo sesión",
78
79
  "ageVerification": "Verificación de edad",
79
80
  "ageVerificationText": "Para interactuar con este Gemelo, debes tener al menos {{minAge}} años.",
80
81
  "nsfw": "NSFW: Este gemelo contiene contenido para adultos",
@@ -74,6 +74,7 @@
74
74
  "errorFetchingSession": "Erreur lors du chargement de la session",
75
75
  "errorGettingReferralURL": "Erreur lors du chargement du référent",
76
76
  "errorReopeningSession": "Erreur lors de la re-ouverture de la session",
77
+ "sessionExpiredReopening": "Session expirée, réouverture en cours",
77
78
  "ageVerification": "Vérification de l'âge",
78
79
  "ageVerificationText": "Pour interagir avec ce Agent, vous devez être au minimum {{minAge}} ans.",
79
80
  "nsfw": "NSFW : Ce jumeau contient du contenu pour adultes",
@@ -78,6 +78,7 @@
78
78
  "errorFetchingSession": "Errore durante il caricamento della sessione",
79
79
  "errorGettingReferralURL": "Errore durante il caricamento del riferimento",
80
80
  "errorReopeningSession": "Errore durante il riapertura della sessione",
81
+ "sessionExpiredReopening": "Sessione scaduta, riapertura in corso",
81
82
  "ageVerification": "Verifica dell'età",
82
83
  "ageVerificationText": "Per interagire con questo agente, devi aver almeno {{minAge}} anni.",
83
84
  "nsfw": "NSFW: Questo agente contiene contenuti per adulti",
@@ -0,0 +1,32 @@
1
+ import {
2
+ is3dAvatarWithUrl,
3
+ usesRpmAvatarView,
4
+ } from './integration';
5
+
6
+ describe('integration avatar helpers', () => {
7
+ const avatarURL = 'https://example.com/avatar.glb';
8
+
9
+ describe('is3dAvatarWithUrl', () => {
10
+ it('returns true for avatar-configurator with avatarURL', () => {
11
+ expect(is3dAvatarWithUrl('avatar-configurator', avatarURL)).toBe(true);
12
+ });
13
+
14
+ it('returns false for avatar-configurator without avatarURL', () => {
15
+ expect(is3dAvatarWithUrl('avatar-configurator', undefined)).toBe(false);
16
+ });
17
+ });
18
+
19
+ describe('usesRpmAvatarView', () => {
20
+ it('returns true for avatar-configurator', () => {
21
+ expect(usesRpmAvatarView('avatar-configurator')).toBe(true);
22
+ });
23
+
24
+ it('returns true for readyplayerme-full', () => {
25
+ expect(usesRpmAvatarView('readyplayerme-full')).toBe(true);
26
+ });
27
+
28
+ it('returns false for customglb', () => {
29
+ expect(usesRpmAvatarView('customglb')).toBe(false);
30
+ });
31
+ });
32
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Avatar mode stored in integration.customData.avatar.
3
+ * `avatar-configurator` is used in admin/editor UI; at runtime it renders like `readyplayerme-full`.
4
+ */
5
+ export type AvatarMode =
6
+ | 'readyplayerme'
7
+ | 'readyplayerme-full'
8
+ | 'customglb'
9
+ | 'customrpm'
10
+ | 'userAvatar'
11
+ | 'avatar-configurator';
12
+
13
+ export const is3dAvatarWithUrl = (
14
+ avatar: AvatarMode | string | undefined,
15
+ avatarURL: string | undefined
16
+ ): boolean =>
17
+ !!avatarURL &&
18
+ (avatar === 'readyplayerme' ||
19
+ avatar === 'readyplayerme-full' ||
20
+ avatar === 'customglb' ||
21
+ avatar === 'customrpm' ||
22
+ avatar === 'avatar-configurator');
23
+
24
+ /** RPM-based 3D avatar view (ContainerAvatarView), including avatar-configurator exports. */
25
+ export const usesRpmAvatarView = (
26
+ avatar: AvatarMode | string | undefined
27
+ ): boolean =>
28
+ avatar === 'readyplayerme' ||
29
+ avatar === 'readyplayerme-full' ||
30
+ avatar === 'customrpm' ||
31
+ avatar === 'avatar-configurator';
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
- export const version = '8.41.0';
2
+ export const version = '8.41.2';