@memori.ai/memori-react 7.4.5 → 7.5.0

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 (174) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/dist/I18nWrapper.js +6 -5
  4. package/dist/I18nWrapper.js.map +1 -1
  5. package/dist/components/Avatar/Avatar.d.ts +1 -0
  6. package/dist/components/Avatar/Avatar.js +64 -60
  7. package/dist/components/Avatar/Avatar.js.map +1 -1
  8. package/dist/components/Avatar/AvatarView/components/controls.d.ts +23 -0
  9. package/dist/components/Avatar/AvatarView/components/controls.js +55 -0
  10. package/dist/components/Avatar/AvatarView/components/controls.js.map +1 -0
  11. package/dist/components/Avatar/AvatarView/components/fullbodyAvatar.d.ts +18 -0
  12. package/dist/components/Avatar/AvatarView/components/fullbodyAvatar.js +60 -0
  13. package/dist/components/Avatar/AvatarView/components/fullbodyAvatar.js.map +1 -0
  14. package/dist/components/Avatar/AvatarView/components/halfbodyAvatar.d.ts +9 -0
  15. package/dist/components/Avatar/AvatarView/components/halfbodyAvatar.js +39 -0
  16. package/dist/components/Avatar/AvatarView/components/halfbodyAvatar.js.map +1 -0
  17. package/dist/components/Avatar/AvatarView/components/loader.d.ts +5 -0
  18. package/dist/components/Avatar/AvatarView/components/loader.js +12 -0
  19. package/dist/components/Avatar/AvatarView/components/loader.js.map +1 -0
  20. package/dist/components/Avatar/AvatarView/index.d.ts +18 -0
  21. package/dist/components/Avatar/AvatarView/index.js +109 -0
  22. package/dist/components/Avatar/AvatarView/index.js.map +1 -0
  23. package/dist/components/Avatar/AvatarView/utils/useEyeBlink.d.ts +2 -0
  24. package/dist/components/Avatar/AvatarView/utils/useEyeBlink.js +47 -0
  25. package/dist/components/Avatar/AvatarView/utils/useEyeBlink.js.map +1 -0
  26. package/dist/components/Avatar/AvatarView/utils/useHeadMovement.d.ts +2 -0
  27. package/dist/components/Avatar/AvatarView/utils/useHeadMovement.js +53 -0
  28. package/dist/components/Avatar/AvatarView/utils/useHeadMovement.js.map +1 -0
  29. package/dist/components/Avatar/AvatarView/utils/useLoadingMorphAnim.d.ts +2 -0
  30. package/dist/components/Avatar/AvatarView/utils/useLoadingMorphAnim.js +34 -0
  31. package/dist/components/Avatar/AvatarView/utils/useLoadingMorphAnim.js.map +1 -0
  32. package/dist/components/Avatar/AvatarView/utils/useMouthSpeaking.d.ts +2 -0
  33. package/dist/components/Avatar/AvatarView/utils/useMouthSpeaking.js +68 -0
  34. package/dist/components/Avatar/AvatarView/utils/useMouthSpeaking.js.map +1 -0
  35. package/dist/components/Avatar/AvatarView/utils/useSmile.d.ts +2 -0
  36. package/dist/components/Avatar/AvatarView/utils/useSmile.js +28 -0
  37. package/dist/components/Avatar/AvatarView/utils/useSmile.js.map +1 -0
  38. package/dist/components/Avatar/AvatarView/utils/utils.d.ts +13 -0
  39. package/dist/components/Avatar/AvatarView/utils/utils.js +42 -0
  40. package/dist/components/Avatar/AvatarView/utils/utils.js.map +1 -0
  41. package/dist/components/ChatBubble/ChatBubble.js +28 -18
  42. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  43. package/dist/components/MemoriWidget/MemoriWidget.js +5 -0
  44. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  45. package/dist/components/StartPanel/StartPanel.js +3 -2
  46. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  47. package/dist/components/icons/Code.js +1 -1
  48. package/dist/components/icons/Code.js.map +1 -1
  49. package/dist/components/icons/Copy.js +1 -1
  50. package/dist/components/icons/Copy.js.map +1 -1
  51. package/dist/helpers/constants.d.ts +1 -0
  52. package/dist/helpers/constants.js +2 -1
  53. package/dist/helpers/constants.js.map +1 -1
  54. package/dist/helpers/error.js +5 -0
  55. package/dist/helpers/error.js.map +1 -1
  56. package/dist/i18n.js +13 -1
  57. package/dist/i18n.js.map +1 -1
  58. package/dist/index.d.ts +1 -1
  59. package/dist/index.js +14 -2
  60. package/dist/index.js.map +1 -1
  61. package/dist/locales/de.json +426 -0
  62. package/dist/locales/en.json +5 -0
  63. package/dist/locales/es.json +426 -0
  64. package/dist/locales/fr.json +426 -0
  65. package/dist/locales/it.json +5 -0
  66. package/esm/I18nWrapper.js +6 -5
  67. package/esm/I18nWrapper.js.map +1 -1
  68. package/esm/components/Avatar/Avatar.d.ts +1 -0
  69. package/esm/components/Avatar/Avatar.js +66 -62
  70. package/esm/components/Avatar/Avatar.js.map +1 -1
  71. package/esm/components/Avatar/AvatarView/components/controls.d.ts +23 -0
  72. package/esm/components/Avatar/AvatarView/components/controls.js +52 -0
  73. package/esm/components/Avatar/AvatarView/components/controls.js.map +1 -0
  74. package/esm/components/Avatar/AvatarView/components/fullbodyAvatar.d.ts +18 -0
  75. package/esm/components/Avatar/AvatarView/components/fullbodyAvatar.js +56 -0
  76. package/esm/components/Avatar/AvatarView/components/fullbodyAvatar.js.map +1 -0
  77. package/esm/components/Avatar/AvatarView/components/halfbodyAvatar.d.ts +9 -0
  78. package/esm/components/Avatar/AvatarView/components/halfbodyAvatar.js +35 -0
  79. package/esm/components/Avatar/AvatarView/components/halfbodyAvatar.js.map +1 -0
  80. package/esm/components/Avatar/AvatarView/components/loader.d.ts +5 -0
  81. package/esm/components/Avatar/AvatarView/components/loader.js +9 -0
  82. package/esm/components/Avatar/AvatarView/components/loader.js.map +1 -0
  83. package/esm/components/Avatar/AvatarView/index.d.ts +18 -0
  84. package/esm/components/Avatar/AvatarView/index.js +105 -0
  85. package/esm/components/Avatar/AvatarView/index.js.map +1 -0
  86. package/esm/components/Avatar/AvatarView/utils/useEyeBlink.d.ts +2 -0
  87. package/esm/components/Avatar/AvatarView/utils/useEyeBlink.js +44 -0
  88. package/esm/components/Avatar/AvatarView/utils/useEyeBlink.js.map +1 -0
  89. package/esm/components/Avatar/AvatarView/utils/useHeadMovement.d.ts +2 -0
  90. package/esm/components/Avatar/AvatarView/utils/useHeadMovement.js +50 -0
  91. package/esm/components/Avatar/AvatarView/utils/useHeadMovement.js.map +1 -0
  92. package/esm/components/Avatar/AvatarView/utils/useLoadingMorphAnim.d.ts +2 -0
  93. package/esm/components/Avatar/AvatarView/utils/useLoadingMorphAnim.js +31 -0
  94. package/esm/components/Avatar/AvatarView/utils/useLoadingMorphAnim.js.map +1 -0
  95. package/esm/components/Avatar/AvatarView/utils/useMouthSpeaking.d.ts +2 -0
  96. package/esm/components/Avatar/AvatarView/utils/useMouthSpeaking.js +65 -0
  97. package/esm/components/Avatar/AvatarView/utils/useMouthSpeaking.js.map +1 -0
  98. package/esm/components/Avatar/AvatarView/utils/useSmile.d.ts +2 -0
  99. package/esm/components/Avatar/AvatarView/utils/useSmile.js +25 -0
  100. package/esm/components/Avatar/AvatarView/utils/useSmile.js.map +1 -0
  101. package/esm/components/Avatar/AvatarView/utils/utils.d.ts +13 -0
  102. package/esm/components/Avatar/AvatarView/utils/utils.js +33 -0
  103. package/esm/components/Avatar/AvatarView/utils/utils.js.map +1 -0
  104. package/esm/components/ChatBubble/ChatBubble.js +28 -18
  105. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  106. package/esm/components/MemoriWidget/MemoriWidget.js +6 -1
  107. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  108. package/esm/components/StartPanel/StartPanel.js +3 -2
  109. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  110. package/esm/components/icons/Code.js +1 -1
  111. package/esm/components/icons/Code.js.map +1 -1
  112. package/esm/components/icons/Copy.js +1 -1
  113. package/esm/components/icons/Copy.js.map +1 -1
  114. package/esm/helpers/constants.d.ts +1 -0
  115. package/esm/helpers/constants.js +1 -0
  116. package/esm/helpers/constants.js.map +1 -1
  117. package/esm/helpers/error.js +5 -0
  118. package/esm/helpers/error.js.map +1 -1
  119. package/esm/i18n.js +13 -1
  120. package/esm/i18n.js.map +1 -1
  121. package/esm/index.d.ts +1 -1
  122. package/esm/index.js +15 -3
  123. package/esm/index.js.map +1 -1
  124. package/esm/locales/de.json +426 -0
  125. package/esm/locales/en.json +5 -0
  126. package/esm/locales/es.json +426 -0
  127. package/esm/locales/fr.json +426 -0
  128. package/esm/locales/it.json +5 -0
  129. package/package.json +2 -1
  130. package/src/I18nWrapper.tsx +6 -8
  131. package/src/components/Avatar/Avatar.tsx +149 -144
  132. package/src/components/{AvatarView → Avatar/AvatarView}/AvatarView.stories.tsx +9 -2
  133. package/src/components/Avatar/AvatarView/components/controls.tsx +94 -0
  134. package/src/components/Avatar/AvatarView/components/fullbodyAvatar.tsx +120 -0
  135. package/src/components/Avatar/AvatarView/components/halfbodyAvatar.tsx +69 -0
  136. package/src/components/{AvatarView → Avatar/AvatarView}/components/loader.tsx +1 -1
  137. package/src/components/Avatar/AvatarView/index.tsx +251 -0
  138. package/src/components/Avatar/AvatarView/utils/useEyeBlink.ts +59 -0
  139. package/src/components/Avatar/AvatarView/utils/useMouthSpeaking.ts +87 -0
  140. package/src/components/Avatar/AvatarView/utils/useSmile.ts +39 -0
  141. package/src/components/Avatar/__snapshots__/Avatar.test.tsx.snap +1 -1
  142. package/src/components/ChatBubble/ChatBubble.stories.tsx +85 -3
  143. package/src/components/ChatBubble/ChatBubble.tsx +44 -34
  144. package/src/components/MemoriWidget/MemoriWidget.tsx +11 -1
  145. package/src/components/StartPanel/StartPanel.tsx +3 -2
  146. package/src/components/icons/Code.tsx +3 -3
  147. package/src/components/icons/Copy.tsx +3 -3
  148. package/src/components/layouts/Chat.test.tsx +12 -9
  149. package/src/components/layouts/FullPage.test.tsx +12 -9
  150. package/src/components/layouts/Totem.test.tsx +12 -9
  151. package/src/components/layouts/WebsiteAssistant.test.tsx +12 -9
  152. package/src/components/layouts/__snapshots__/Chat.test.tsx.snap +13 -8
  153. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +14 -9
  154. package/src/components/layouts/__snapshots__/Totem.test.tsx.snap +14 -9
  155. package/src/components/layouts/__snapshots__/WebsiteAssistant.test.tsx.snap +6 -1
  156. package/src/helpers/constants.ts +2 -0
  157. package/src/helpers/error.ts +5 -0
  158. package/src/i18n.ts +13 -1
  159. package/src/index.stories.tsx +29 -0
  160. package/src/index.tsx +17 -5
  161. package/src/locales/de.json +454 -0
  162. package/src/locales/en.json +5 -0
  163. package/src/locales/es.json +426 -0
  164. package/src/locales/fr.json +426 -0
  165. package/src/locales/it.json +5 -0
  166. package/src/components/AvatarView/components/avatar.tsx +0 -57
  167. package/src/components/AvatarView/components/fullbodyAvatar.tsx +0 -99
  168. package/src/components/AvatarView/index.tsx +0 -101
  169. package/src/components/AvatarView/utils/useEyeBlink.ts +0 -48
  170. package/src/components/AvatarView/utils/useMouthSpeaking.ts +0 -70
  171. package/src/components/AvatarView/utils/useSmile.ts +0 -31
  172. /package/src/components/{AvatarView → Avatar/AvatarView}/utils/useHeadMovement.ts +0 -0
  173. /package/src/components/{AvatarView → Avatar/AvatarView}/utils/useLoadingMorphAnim.ts +0 -0
  174. /package/src/components/{AvatarView → Avatar/AvatarView}/utils/utils.ts +0 -0
@@ -1,4 +1,4 @@
1
- import React, { memo, useEffect, useMemo, useState } from 'react';
1
+ import React, { memo, useEffect, useState } from 'react';
2
2
  import {
3
3
  Integration,
4
4
  Memori,
@@ -6,7 +6,6 @@ import {
6
6
  } from '@memori.ai/memori-api-client/dist/types';
7
7
  import ErrorBoundary from '../ErrorBoundary/ErrorBoundary';
8
8
  import Tooltip from '../ui/Tooltip';
9
- import AvatarView, { Props as AvatarViewProps } from '../AvatarView';
10
9
  import { getResourceUrl } from '../../helpers/media';
11
10
  import Blob from '../Blob/Blob';
12
11
  import ModelViewer from '../CustomGLBModelViewer/ModelViewer';
@@ -16,6 +15,7 @@ import Eye from '../icons/Eye';
16
15
  import EyeInvisible from '../icons/EyeInvisible';
17
16
  import Edit from '../icons/Edit';
18
17
  import cx from 'classnames';
18
+ import ContainerAvatarView from './AvatarView';
19
19
 
20
20
  export interface Props {
21
21
  memori: Memori;
@@ -30,6 +30,7 @@ export interface Props {
30
30
  loading?: boolean;
31
31
  baseUrl?: string;
32
32
  apiUrl?: string;
33
+ animation?: string;
33
34
  }
34
35
 
35
36
  const Avatar: React.FC<Props> = ({
@@ -45,6 +46,7 @@ const Avatar: React.FC<Props> = ({
45
46
  loading = false,
46
47
  baseUrl,
47
48
  apiUrl,
49
+ animation,
48
50
  }) => {
49
51
  const { t } = useTranslation();
50
52
  const [isClient, setIsClient] = useState(false);
@@ -53,26 +55,33 @@ const Avatar: React.FC<Props> = ({
53
55
  setIsClient(true);
54
56
  }, []);
55
57
 
56
- const animation = useMemo(
57
- () =>
58
- isPlayingAudio
59
- ? (['Talk 1', 'Talk 2', 'Talk 3'][
60
- Math.round(Math.random() * 2)
61
- ] as AvatarViewProps['animation'])
62
- : loading
63
- ? 'Loading'
64
- : (['Idle', 'Idle 1', 'Idle 2', 'Idle 3'][
65
- Math.round(Math.random() * 3)
66
- ] as AvatarViewProps['animation']),
67
- [isPlayingAudio, loading]
68
- );
58
+ // Get the avatar URL, if the avatar is a user avatar, the avatar URL is the user avatar URL, if the avatar is a default avatar, the avatar URL is the default avatar URL
59
+ const getAvatarUrl = () => {
60
+ if (
61
+ integrationConfig?.avatar === 'userAvatar' &&
62
+ memori.avatarURL &&
63
+ memori.avatarURL.length > 0
64
+ ) {
65
+ return getResourceUrl({
66
+ type: 'avatar',
67
+ tenantID: tenant?.id,
68
+ resourceURI: memori.avatarURL,
69
+ baseURL: baseUrl,
70
+ apiURL: apiUrl,
71
+ });
72
+ }
73
+ return undefined;
74
+ };
69
75
 
70
- return (
71
- <>
72
- {(integrationConfig?.avatar === 'readyplayerme' ||
76
+ // Render the avatar, if the avatar is a user avatar, the avatar is rendered, if the avatar is a default avatar, the avatar is rendered
77
+ const renderAvatar = () => {
78
+ if (
79
+ (integrationConfig?.avatar === 'readyplayerme' ||
73
80
  integrationConfig?.avatar === 'readyplayerme-full' ||
74
81
  integrationConfig?.avatar === 'customglb') &&
75
- integrationConfig?.avatarURL ? (
82
+ integrationConfig?.avatarURL
83
+ ) {
84
+ return (
76
85
  <>
77
86
  <div
78
87
  className={cx(
@@ -83,133 +92,129 @@ const Avatar: React.FC<Props> = ({
83
92
  }
84
93
  )}
85
94
  >
86
- {isClient &&
87
- (integrationConfig.avatar === 'readyplayerme' ||
88
- integrationConfig.avatar === 'readyplayerme-full') && (
89
- <ErrorBoundary
90
- fallback={
91
- <div className="memori--blob-container">
92
- {isClient && (
93
- <Blob
94
- speaking={isPlayingAudio}
95
- avatar={
96
- integrationConfig?.avatar === 'userAvatar' &&
97
- memori.avatarURL &&
98
- memori.avatarURL.length > 0
99
- ? getResourceUrl({
100
- type: 'avatar',
101
- tenantID: tenant?.id,
102
- resourceURI: memori.avatarURL,
103
- baseURL: baseUrl,
104
- apiURL: apiUrl,
105
- })
106
- : undefined
107
- }
108
- />
109
- )}
110
- </div>
111
- }
112
- >
113
- <AvatarView
114
- url={integrationConfig.avatarURL}
115
- sex={memori.voiceType === 'FEMALE' ? 'FEMALE' : 'MALE'}
116
- fallbackImg={getResourceUrl({
117
- type: 'avatar',
118
- tenantID: tenant?.id,
119
- resourceURI: memori.avatarURL,
120
- baseURL: baseUrl,
121
- apiURL: apiUrl,
122
- })}
123
- headMovement
124
- eyeBlink
125
- halfBody={integrationConfig.avatar === 'readyplayerme'}
126
- animation={animation}
127
- speaking={isPlayingAudio}
128
- style={
129
- integrationConfig.avatar === 'readyplayerme'
130
- ? {
131
- width: '300px',
132
- height: '300px',
133
- backgroundColor: 'none',
134
- borderRadius: '100%',
135
- boxShadow: 'none',
136
- }
137
- : {
138
- width: '600px',
139
- height: '600px',
140
- backgroundColor: 'none',
141
- }
142
- }
143
- />
144
- </ErrorBoundary>
145
- )}
146
- {isClient && integrationConfig.avatar === 'customglb' && (
147
- <ModelViewer
148
- poster={getResourceUrl({
149
- type: 'avatar',
150
- tenantID: tenant?.id,
151
- resourceURI: memori.avatarURL,
152
- baseURL: baseUrl,
153
- apiURL: apiUrl,
154
- })}
155
- src={integrationConfig.avatarURL}
156
- alt=""
157
- />
158
- )}
159
- </div>
160
- <div className="memori--avatar-toggle">
161
- <Button
162
- ghost
163
- onClick={() => setAvatar3dVisible(!avatar3dVisible)}
164
- icon={avatar3dVisible ? <EyeInvisible /> : <Eye />}
165
- >
166
- <span className="memori--avatar-toggle-text">
167
- {avatar3dVisible ? t('hide') : t('show')}
168
- </span>
169
- </Button>
95
+ {renderAvatarContent()}
170
96
  </div>
97
+ {renderAvatarToggle()}
171
98
  </>
172
- ) : (
173
- <div className="memori--blob-container">
174
- {isClient && (
175
- <Blob
176
- avatar={
177
- integrationConfig?.avatar === 'userAvatar' &&
178
- memori.avatarURL &&
179
- memori.avatarURL.length > 0
180
- ? getResourceUrl({
181
- type: 'avatar',
182
- tenantID: tenant?.id,
183
- resourceURI: memori.avatarURL,
184
- baseURL: baseUrl,
185
- apiURL: apiUrl,
186
- })
187
- : undefined
188
- }
189
- />
190
- )}
191
- </div>
192
- )}
193
- {instruct && !hasUserActivatedSpeak && memori.isGiver && tenant?.id && (
194
- <div className="memori--avatar-link-to-integrations">
195
- <a
196
- className="memori-button memori-button--circle memori-button--outlined"
197
- href={`https://${tenant.id}/${
198
- memori.culture === 'it-IT' ? 'it' : 'en'
199
- }/${memori.ownerUserName}/${memori.name}/integrations${
200
- integration?.integrationID
201
- ? `?integration=${integration.integrationID}&openAvatarModal=true`
202
- : ''
203
- }`}
204
- >
205
- <Tooltip content={t('widgetgoToIntegrationsToCustomizeAvatar')}>
206
- <span className="memori-button--icon">
207
- <Edit />
208
- </span>
209
- </Tooltip>
210
- </a>
211
- </div>
212
- )}
99
+ );
100
+ }
101
+ return (
102
+ <div className="memori--blob-container">
103
+ {isClient && <Blob speaking={isPlayingAudio} avatar={getAvatarUrl()} />}
104
+ </div>
105
+ );
106
+ };
107
+
108
+ const renderAvatarContent = () => {
109
+ if (!isClient) return null;
110
+
111
+ if (
112
+ integrationConfig?.avatar === 'readyplayerme' ||
113
+ integrationConfig?.avatar === 'readyplayerme-full'
114
+ ) {
115
+ return (
116
+ <ErrorBoundary
117
+ fallback={
118
+ <div className="memori--blob-container">
119
+ {isClient && (
120
+ <Blob speaking={isPlayingAudio} avatar={getAvatarUrl()} />
121
+ )}
122
+ </div>
123
+ }
124
+ >
125
+ <ContainerAvatarView
126
+ url={integrationConfig.avatarURL}
127
+ sex={memori.voiceType === 'FEMALE' ? 'FEMALE' : 'MALE'}
128
+ fallbackImg={getAvatarUrl()}
129
+ headMovement
130
+ eyeBlink
131
+ animation={animation}
132
+ halfBody={integrationConfig.avatar === 'readyplayerme'}
133
+ speaking={isPlayingAudio}
134
+ loading={loading}
135
+ style={getAvatarStyle()}
136
+ />
137
+ </ErrorBoundary>
138
+ );
139
+ }
140
+
141
+ if (integrationConfig?.avatar === 'customglb') {
142
+ return (
143
+ <ModelViewer
144
+ poster={getAvatarUrl() || ''}
145
+ src={integrationConfig.avatarURL}
146
+ alt=""
147
+ />
148
+ );
149
+ }
150
+
151
+ return null;
152
+ };
153
+
154
+ // Render the avatar toggle
155
+ const renderAvatarToggle = () => (
156
+ <div className="memori--avatar-toggle">
157
+ <Button
158
+ ghost
159
+ onClick={() => setAvatar3dVisible(!avatar3dVisible)}
160
+ icon={avatar3dVisible ? <EyeInvisible /> : <Eye />}
161
+ >
162
+ <span className="memori--avatar-toggle-text">
163
+ {avatar3dVisible ? t('hide') : t('show')}
164
+ </span>
165
+ </Button>
166
+ </div>
167
+ );
168
+
169
+ const getAvatarStyle = () => {
170
+ if (integrationConfig?.avatar === 'readyplayerme') {
171
+ return {
172
+ width: '300px',
173
+ height: '300px',
174
+ backgroundColor: 'none',
175
+ borderRadius: '100%',
176
+ boxShadow: 'none',
177
+ };
178
+ }
179
+ return {
180
+ width: '600px',
181
+ height: '600px',
182
+ backgroundColor: 'none',
183
+ };
184
+ };
185
+
186
+ const renderIntegrationsLink = () => {
187
+ if (!(instruct && !hasUserActivatedSpeak && memori.isGiver && tenant?.id))
188
+ return null;
189
+
190
+ const href = `https://${tenant.id}/${
191
+ memori.culture === 'it-IT' ? 'it' : 'en'
192
+ }/${memori.ownerUserName}/${memori.name}/integrations${
193
+ integration?.integrationID
194
+ ? `?integration=${integration.integrationID}&openAvatarModal=true`
195
+ : ''
196
+ }`;
197
+
198
+ return (
199
+ <div className="memori--avatar-link-to-integrations">
200
+ <a
201
+ className="memori-button memori-button--circle memori-button--outlined"
202
+ href={href}
203
+ >
204
+ <Tooltip content={t('widgetgoToIntegrationsToCustomizeAvatar')}>
205
+ <span className="memori-button--icon">
206
+ <Edit />
207
+ </span>
208
+ </Tooltip>
209
+ </a>
210
+ </div>
211
+ );
212
+ };
213
+
214
+ return (
215
+ <>
216
+ {renderAvatar()}
217
+ {renderIntegrationsLink()}
213
218
  </>
214
219
  );
215
220
  };
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Meta, Story } from '@storybook/react';
3
- import I18nWrapper from '../../I18nWrapper';
3
+ import I18nWrapper from '../../../I18nWrapper';
4
4
  import AvatarView, { Props } from './index';
5
5
 
6
6
  const meta: Meta = {
@@ -37,6 +37,11 @@ const meta: Meta = {
37
37
  type: 'boolean',
38
38
  },
39
39
  },
40
+ showControls: {
41
+ control: {
42
+ type: 'boolean',
43
+ },
44
+ },
40
45
  animation: {
41
46
  control: {
42
47
  type: 'select',
@@ -55,7 +60,9 @@ const meta: Meta = {
55
60
  },
56
61
  },
57
62
  parameters: {
58
- controls: { expanded: true },
63
+ controls: { expanded: false,
64
+ },
65
+
59
66
  },
60
67
  };
61
68
 
@@ -0,0 +1,94 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import GUI from 'lil-gui';
3
+
4
+ interface BaseAction {
5
+ weight: number;
6
+ action?: string;
7
+ }
8
+
9
+ interface AdditiveAction {
10
+ weight: number;
11
+ action?: string;
12
+ }
13
+
14
+ export interface AnimationControlPanelProps {
15
+ baseActions: Record<string, BaseAction>;
16
+ additiveActions: Record<string, AdditiveAction>;
17
+ onBaseActionChange: (action: string) => void;
18
+ onAdditiveActionChange?: (action: string, weight: number) => void;
19
+ currentBaseAction: {
20
+ action: string;
21
+ weight: number;
22
+ };
23
+ modifyTimeScale: (value: number) => void;
24
+ timeScale: number;
25
+ }
26
+
27
+ const AnimationControlPanel: React.FC<AnimationControlPanelProps> = ({
28
+ onBaseActionChange,
29
+ onAdditiveActionChange,
30
+ baseActions,
31
+ additiveActions,
32
+ modifyTimeScale,
33
+ timeScale,
34
+ }) => {
35
+ const guiRef = useRef<GUI | null>(null);
36
+ const panelSettingsRef = useRef<Record<string, any>>({
37
+ 'modify time scale': timeScale,
38
+ });
39
+ const crossFadeControlsRef = useRef<any[]>([]);
40
+
41
+ useEffect(() => {
42
+
43
+ const gui = new GUI({ width: 310 });
44
+ guiRef.current = gui;
45
+
46
+ const folder1 = gui.addFolder('Base Actions');
47
+ const folder2 = gui.addFolder('Additive Action Weights');
48
+ const folder3 = gui.addFolder('General Speed');
49
+
50
+ const baseNames = ['None', ...Object.keys(baseActions)];
51
+
52
+ baseNames.forEach(name => {
53
+ const settings = baseActions[name];
54
+ panelSettingsRef.current[name] = () => {
55
+ onBaseActionChange(name);
56
+ };
57
+
58
+ const control = folder1.add(panelSettingsRef.current, name);
59
+ crossFadeControlsRef.current.push(control);
60
+ });
61
+
62
+ Object.entries(additiveActions).forEach(([name, settings]) => {
63
+ panelSettingsRef.current[name] = settings.weight;
64
+ folder2
65
+ .add(panelSettingsRef.current, name, 0.0, 1.0, 0.01)
66
+ .listen()
67
+ .onChange((weight: number) => {
68
+ settings.weight = weight;
69
+ onAdditiveActionChange?.(name, weight);
70
+ });
71
+ });
72
+
73
+ folder3
74
+ .add(panelSettingsRef.current, 'modify time scale', 0.0, 1.5, 0.01)
75
+ .onChange((value: number) => {
76
+ modifyTimeScale(value);
77
+ });
78
+
79
+ folder1.open();
80
+ folder2.open();
81
+ folder3.open();
82
+
83
+ return () => {
84
+ gui.destroy();
85
+ };
86
+ }, [
87
+ onBaseActionChange,
88
+ onAdditiveActionChange /*onTimeScaleChange, setWeight, prepareCrossFade, modifyTimeScale */,
89
+ ]);
90
+
91
+ return null; // This component doesn't render anything itself
92
+ };
93
+
94
+ export default AnimationControlPanel;
@@ -0,0 +1,120 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import {
3
+ Vector3,
4
+ Euler,
5
+ AnimationMixer,
6
+ AnimationAction,
7
+ AnimationClip,
8
+ } from 'three';
9
+ import { useAnimations, useGLTF } from '@react-three/drei';
10
+ import { useGraph, dispose, useFrame } from '@react-three/fiber';
11
+ import { correctMaterials, isSkinnedMesh } from '../utils/utils';
12
+ import useEyeBlink from '../utils/useEyeBlink';
13
+ import useMouthSpeaking from '../utils/useMouthSpeaking';
14
+ import useHeadMovement from '../utils/useHeadMovement';
15
+ import useSmile from '../utils/useSmile';
16
+
17
+ export interface FullbodyAvatarProps {
18
+ url: string;
19
+ sex: 'MALE' | 'FEMALE';
20
+ onLoaded?: () => void;
21
+ currentBaseAction: {
22
+ action: string;
23
+ weight: number;
24
+ };
25
+ additiveActions: {
26
+ [key: string]: {
27
+ weight: number;
28
+ };
29
+ };
30
+ timeScale: number;
31
+ loading?: boolean;
32
+ speaking?: boolean;
33
+ }
34
+
35
+ const AVATAR_POSITION = new Vector3(0, -1, 0);
36
+ const AVATAR_ROTATION = new Euler(0.175, 0, 0);
37
+ const ANIMATION_URLS = {
38
+ MALE: 'https://assets.memori.ai/api/v2/asset/5de7456f-0cd8-4e29-95a7-0cd0045a5325.glb',
39
+ FEMALE:
40
+ 'https://assets.memori.ai/api/v2/asset/84487a2b-377c-4565-800a-51459d580ec8.glb',
41
+ };
42
+
43
+ export default function FullbodyAvatar({
44
+ url,
45
+ sex,
46
+ onLoaded,
47
+ currentBaseAction,
48
+ additiveActions,
49
+ timeScale
50
+ }: FullbodyAvatarProps) {
51
+ const { scene } = useGLTF(url);
52
+ const { animations } = useGLTF(ANIMATION_URLS[sex]);
53
+ const { nodes, materials } = useGraph(scene);
54
+ const { actions } = useAnimations(animations, scene);
55
+ const [mixer] = useState(() => new AnimationMixer(scene));
56
+
57
+ useEffect(() => {
58
+ correctMaterials(materials);
59
+ onLoaded?.();
60
+
61
+ return () => {
62
+ Object.values(materials).forEach(dispose);
63
+ Object.values(nodes).filter(isSkinnedMesh).forEach(dispose);
64
+ };
65
+ }, [materials, nodes, url, onLoaded]);
66
+
67
+ useEffect(() => {
68
+ if (!actions || !currentBaseAction.action) return;
69
+
70
+ const newAction = actions[currentBaseAction.action];
71
+
72
+ if (!newAction) {
73
+ console.warn(
74
+ `Animation "${currentBaseAction.action}" not found in actions.`
75
+ );
76
+ return;
77
+ }
78
+
79
+ const fadeOutDuration = 0.8;
80
+ const fadeInDuration = 0.8;
81
+
82
+ newAction.timeScale = timeScale;
83
+ newAction.reset().fadeIn(fadeInDuration).play();
84
+
85
+ return () => {
86
+ newAction.fadeOut(fadeOutDuration);
87
+ };
88
+ }, [currentBaseAction, timeScale]);
89
+
90
+
91
+
92
+
93
+ // useEffect(() => {
94
+ // if (speaking && actions['Talk 1'] && actions['Talk 2']) {
95
+ // const talk1 = actions['Talk 1'].getClip();
96
+ // const talk2 = actions['Talk 2'].getClip();
97
+ // const talk = new AnimationClip(
98
+ // 'Talk',
99
+ // talk1.duration + talk2.duration,
100
+ // );
101
+ // mixer.clipAction(talk, scene).play();
102
+ // }
103
+ // }, [speaking]);
104
+
105
+ // Additive actions
106
+ useEyeBlink(additiveActions.blink.weight > 0, nodes);
107
+ useMouthSpeaking(additiveActions.speak.weight > 0, nodes);
108
+ useHeadMovement(additiveActions.headMovement.weight > 0, nodes);
109
+ useSmile(additiveActions.smile.weight > 0, nodes);
110
+
111
+ useFrame((_, delta) => {
112
+ mixer.update(delta * 0.001);
113
+ });
114
+
115
+ return (
116
+ <group position={AVATAR_POSITION} rotation={AVATAR_ROTATION}>
117
+ <primitive object={scene} />
118
+ </group>
119
+ );
120
+ }
@@ -0,0 +1,69 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import { Object3D, Vector3 } from 'three';
3
+ import { useGLTF } from '@react-three/drei';
4
+ import useEyeBlink from '../utils/useEyeBlink';
5
+ import useHeadMovement from '../utils/useHeadMovement';
6
+ import useMouthSpeaking from '../utils/useMouthSpeaking';
7
+ import { dispose, useGraph } from '@react-three/fiber';
8
+ import { correctMaterials, hideHands, isSkinnedMesh } from '../utils/utils';
9
+
10
+ interface HalfBodyAvatarProps {
11
+ url: string;
12
+ eyeBlink?: boolean;
13
+ headMovement?: boolean;
14
+ speaking?: boolean;
15
+ onLoaded?: () => void;
16
+ }
17
+
18
+ const AVATAR_POSITION = new Vector3(0, -0.6, 0);
19
+
20
+ export default function HalfBodyAvatar({
21
+ url,
22
+ eyeBlink,
23
+ headMovement,
24
+ speaking,
25
+ onLoaded,
26
+ }: HalfBodyAvatarProps) {
27
+ const { scene } = useGLTF(url);
28
+ const { nodes, materials } = useGraph(scene);
29
+
30
+ useEyeBlink(eyeBlink, nodes);
31
+ useHeadMovement(headMovement, nodes);
32
+ useMouthSpeaking(!!speaking, nodes);
33
+
34
+ useEffect(() => {
35
+ const setupAvatar = () => {
36
+ hideHands(nodes);
37
+ correctMaterials(materials);
38
+ onLoaded?.();
39
+ };
40
+
41
+ setupAvatar();
42
+
43
+ return () => {
44
+ const disposeObjects = () => {
45
+ Object.values(materials).forEach(dispose);
46
+ Object.values(nodes).filter(isSkinnedMesh).forEach(dispose);
47
+ };
48
+
49
+ disposeObjects();
50
+ };
51
+ }, [materials, nodes, url, onLoaded]);
52
+
53
+ const skinnedMeshes = useMemo(
54
+ () => Object.values(nodes).filter(isSkinnedMesh),
55
+ [nodes]
56
+ );
57
+
58
+ return (
59
+ <group position={AVATAR_POSITION}>
60
+ {nodes.Hips && <primitive key="armature" object={nodes.Hips} />}
61
+ {skinnedMeshes.map(
62
+ (node: Object3D) =>
63
+ node && (
64
+ <primitive key={node.name} object={node} receiveShadow castShadow />
65
+ )
66
+ )}
67
+ </group>
68
+ );
69
+ }
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Html, useProgress } from '@react-three/drei';
3
- import Spin from '../../ui/Spin';
3
+ import Spin from '../../../ui/Spin';
4
4
 
5
5
  interface Props {
6
6
  fallbackImg?: string;