@memori.ai/memori-react 7.5.1 → 7.6.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 (156) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +10 -2
  3. package/dist/components/Avatar/Avatar.d.ts +2 -0
  4. package/dist/components/Avatar/Avatar.js +11 -6
  5. package/dist/components/Avatar/Avatar.js.map +1 -1
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +20 -0
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +107 -0
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -0
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controls.d.ts +26 -0
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controls.js +59 -0
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controls.js.map +1 -0
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.d.ts +30 -0
  13. package/dist/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js +148 -0
  14. package/dist/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js.map +1 -0
  15. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +15 -0
  16. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +77 -0
  17. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -0
  18. package/dist/components/Avatar/AvatarView/AvatarComponent/components/loader.d.ts +5 -0
  19. package/dist/components/Avatar/AvatarView/AvatarComponent/components/loader.js +12 -0
  20. package/dist/components/Avatar/AvatarView/AvatarComponent/components/loader.js.map +1 -0
  21. package/dist/components/Avatar/AvatarView/components/fullbodyAvatar.d.ts +2 -1
  22. package/dist/components/Avatar/AvatarView/components/fullbodyAvatar.js +3 -2
  23. package/dist/components/Avatar/AvatarView/components/fullbodyAvatar.js.map +1 -1
  24. package/dist/components/Avatar/AvatarView/index.d.ts +6 -1
  25. package/dist/components/Avatar/AvatarView/index.js +14 -84
  26. package/dist/components/Avatar/AvatarView/index.js.map +1 -1
  27. package/dist/components/Avatar/AvatarView/utils/useEyeBlink.d.ts +16 -2
  28. package/dist/components/Avatar/AvatarView/utils/useEyeBlink.js +62 -38
  29. package/dist/components/Avatar/AvatarView/utils/useEyeBlink.js.map +1 -1
  30. package/dist/components/Avatar/AvatarView/utils/useMouthAnimation.d.ts +16 -0
  31. package/dist/components/Avatar/AvatarView/utils/useMouthAnimation.js +59 -0
  32. package/dist/components/Avatar/AvatarView/utils/useMouthAnimation.js.map +1 -0
  33. package/dist/components/Avatar/AvatarView/utils/useSmile.js +1 -1
  34. package/dist/components/Avatar/AvatarView/utils/useSmile.js.map +1 -1
  35. package/dist/components/ChatBubble/ChatBubble.js +2 -3
  36. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  37. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.d.ts +1 -1
  38. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js +24 -3
  39. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  40. package/dist/components/MemoriWidget/MemoriWidget.d.ts +1 -1
  41. package/dist/components/MemoriWidget/MemoriWidget.js +25 -3
  42. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  43. package/dist/components/StartPanel/StartPanel.js +1 -1
  44. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  45. package/dist/components/layouts/HiddenChat.d.ts +4 -0
  46. package/dist/components/layouts/HiddenChat.js +51 -0
  47. package/dist/components/layouts/HiddenChat.js.map +1 -0
  48. package/dist/components/layouts/ZoomedFullBody.d.ts +4 -0
  49. package/dist/components/layouts/ZoomedFullBody.js +8 -0
  50. package/dist/components/layouts/ZoomedFullBody.js.map +1 -0
  51. package/dist/components/layouts/hidden-chat.css +184 -0
  52. package/dist/context/visemeContext.d.ts +27 -0
  53. package/dist/context/visemeContext.js +221 -0
  54. package/dist/context/visemeContext.js.map +1 -0
  55. package/dist/helpers/utils.d.ts +7 -0
  56. package/dist/helpers/utils.js +51 -1
  57. package/dist/helpers/utils.js.map +1 -1
  58. package/dist/index.js +20 -16
  59. package/dist/index.js.map +1 -1
  60. package/dist/styles.css +1 -0
  61. package/esm/components/Avatar/Avatar.d.ts +2 -0
  62. package/esm/components/Avatar/Avatar.js +11 -6
  63. package/esm/components/Avatar/Avatar.js.map +1 -1
  64. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +20 -0
  65. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +102 -0
  66. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -0
  67. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controls.d.ts +26 -0
  68. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controls.js +56 -0
  69. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controls.js.map +1 -0
  70. package/esm/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.d.ts +30 -0
  71. package/esm/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js +145 -0
  72. package/esm/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js.map +1 -0
  73. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +15 -0
  74. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +73 -0
  75. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -0
  76. package/esm/components/Avatar/AvatarView/AvatarComponent/components/loader.d.ts +5 -0
  77. package/esm/components/Avatar/AvatarView/AvatarComponent/components/loader.js +9 -0
  78. package/esm/components/Avatar/AvatarView/AvatarComponent/components/loader.js.map +1 -0
  79. package/esm/components/Avatar/AvatarView/components/fullbodyAvatar.d.ts +2 -1
  80. package/esm/components/Avatar/AvatarView/components/fullbodyAvatar.js +3 -2
  81. package/esm/components/Avatar/AvatarView/components/fullbodyAvatar.js.map +1 -1
  82. package/esm/components/Avatar/AvatarView/index.d.ts +6 -1
  83. package/esm/components/Avatar/AvatarView/index.js +15 -85
  84. package/esm/components/Avatar/AvatarView/index.js.map +1 -1
  85. package/esm/components/Avatar/AvatarView/utils/useEyeBlink.d.ts +16 -2
  86. package/esm/components/Avatar/AvatarView/utils/useEyeBlink.js +61 -38
  87. package/esm/components/Avatar/AvatarView/utils/useEyeBlink.js.map +1 -1
  88. package/esm/components/Avatar/AvatarView/utils/useMouthAnimation.d.ts +16 -0
  89. package/esm/components/Avatar/AvatarView/utils/useMouthAnimation.js +55 -0
  90. package/esm/components/Avatar/AvatarView/utils/useMouthAnimation.js.map +1 -0
  91. package/esm/components/Avatar/AvatarView/utils/useSmile.js +1 -1
  92. package/esm/components/Avatar/AvatarView/utils/useSmile.js.map +1 -1
  93. package/esm/components/ChatBubble/ChatBubble.js +2 -3
  94. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  95. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.d.ts +1 -1
  96. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js +24 -3
  97. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  98. package/esm/components/MemoriWidget/MemoriWidget.d.ts +1 -1
  99. package/esm/components/MemoriWidget/MemoriWidget.js +26 -4
  100. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  101. package/esm/components/StartPanel/StartPanel.js +1 -1
  102. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  103. package/esm/components/layouts/HiddenChat.d.ts +4 -0
  104. package/esm/components/layouts/HiddenChat.js +48 -0
  105. package/esm/components/layouts/HiddenChat.js.map +1 -0
  106. package/esm/components/layouts/ZoomedFullBody.d.ts +4 -0
  107. package/esm/components/layouts/ZoomedFullBody.js +5 -0
  108. package/esm/components/layouts/ZoomedFullBody.js.map +1 -0
  109. package/esm/components/layouts/hidden-chat.css +184 -0
  110. package/esm/context/visemeContext.d.ts +27 -0
  111. package/esm/context/visemeContext.js +216 -0
  112. package/esm/context/visemeContext.js.map +1 -0
  113. package/esm/helpers/utils.d.ts +7 -0
  114. package/esm/helpers/utils.js +45 -0
  115. package/esm/helpers/utils.js.map +1 -1
  116. package/esm/index.js +20 -16
  117. package/esm/index.js.map +1 -1
  118. package/esm/styles.css +1 -0
  119. package/package.json +2 -2
  120. package/src/components/Avatar/Avatar.test.tsx +28 -20
  121. package/src/components/Avatar/Avatar.tsx +19 -5
  122. package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +222 -0
  123. package/src/components/Avatar/AvatarView/{components → AvatarComponent/components}/controls.tsx +16 -10
  124. package/src/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.tsx +234 -0
  125. package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +123 -0
  126. package/src/components/Avatar/AvatarView/{components → AvatarComponent/components}/loader.tsx +1 -1
  127. package/src/components/Avatar/AvatarView/AvatarView.stories.tsx +47 -8
  128. package/src/components/Avatar/AvatarView/index.tsx +35 -167
  129. package/src/components/Avatar/AvatarView/utils/useEyeBlink.ts +89 -48
  130. package/src/components/Avatar/AvatarView/utils/useMouthAnimation.ts +93 -0
  131. package/src/components/Avatar/AvatarView/utils/useSmile.ts +1 -1
  132. package/src/components/ChatBubble/ChatBubble.tsx +3 -4
  133. package/src/components/CompletionProviderStatus/CompletionProviderStatus.tsx +33 -3
  134. package/src/components/CompletionProviderStatus/__snapshots__/CompletionProviderStatus.test.tsx.snap +18 -0
  135. package/src/components/MemoriWidget/MemoriWidget.tsx +60 -5
  136. package/src/components/StartPanel/StartPanel.tsx +1 -1
  137. package/src/components/layouts/Chat.test.tsx +7 -5
  138. package/src/components/layouts/FullPage.test.tsx +11 -8
  139. package/src/components/layouts/HiddenChat.test.tsx +37 -0
  140. package/src/components/layouts/HiddenChat.tsx +108 -0
  141. package/src/components/layouts/Totem.test.tsx +6 -4
  142. package/src/components/layouts/WebsiteAssistant.test.tsx +7 -5
  143. package/src/components/layouts/ZoomedFullBody.test.tsx +37 -0
  144. package/src/components/layouts/ZoomedFullBody.tsx +55 -0
  145. package/src/components/layouts/__snapshots__/HiddenChat.test.tsx.snap +210 -0
  146. package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +444 -0
  147. package/src/components/layouts/hidden-chat.css +184 -0
  148. package/src/components/layouts/layouts.stories.tsx +135 -19
  149. package/src/context/visemeContext.tsx +328 -0
  150. package/src/helpers/utils.ts +73 -0
  151. package/src/index.stories.tsx +40 -17
  152. package/src/index.tsx +82 -78
  153. package/src/styles.css +1 -0
  154. package/src/components/Avatar/AvatarView/components/fullbodyAvatar.tsx +0 -120
  155. package/src/components/Avatar/AvatarView/components/halfbodyAvatar.tsx +0 -69
  156. package/src/components/Avatar/AvatarView/utils/useMouthSpeaking.ts +0 -87
@@ -0,0 +1,234 @@
1
+ import React, { useEffect, useRef, useState, useCallback } from 'react';
2
+ import {
3
+ Vector3,
4
+ Euler,
5
+ AnimationMixer,
6
+ SkinnedMesh,
7
+ Object3D,
8
+ AnimationAction,
9
+ } from 'three';
10
+ import { useAnimations, useGLTF } from '@react-three/drei';
11
+ import { useGraph, dispose, useFrame } from '@react-three/fiber';
12
+ import { correctMaterials, isSkinnedMesh } from '../../../../../helpers/utils';
13
+ import { useAvatarBlink } from '../../utils/useEyeBlink';
14
+ import { useViseme } from '../../../../../context/visemeContext';
15
+
16
+ const lerp = (start: number, end: number, alpha: number): number => {
17
+ return start * (1 - alpha) + end * alpha;
18
+ };
19
+
20
+ interface FullbodyAvatarProps {
21
+ url: string;
22
+ sex: 'MALE' | 'FEMALE';
23
+ onLoaded?: () => void;
24
+ currentBaseAction: {
25
+ action: string;
26
+ weight: number;
27
+ };
28
+ timeScale: number;
29
+ loading?: boolean;
30
+ speaking?: boolean;
31
+ isZoomed?: boolean;
32
+ setMorphTargetInfluences: (influences: { [key: string]: number }) => void;
33
+ setMorphTargetDictionary: (dictionary: { [key: string]: number }) => void;
34
+ morphTargetInfluences: { [key: string]: number };
35
+ morphTargetDictionary: { [key: string]: number };
36
+ setMeshRef: any;
37
+ eyeBlink?: boolean;
38
+ clearVisemes: () => void;
39
+ }
40
+
41
+ const AVATAR_POSITION = new Vector3(0, -1, 0);
42
+ const AVATAR_ROTATION = new Euler(0.175, 0, 0);
43
+ const AVATAR_POSITION_ZOOMED = new Vector3(0, -1.45, 0);
44
+
45
+ const ANIMATION_URLS = {
46
+ MALE: 'https://assets.memori.ai/api/v2/asset/1c350a21-97d8-4add-82cc-9dc10767a26b.glb',
47
+ FEMALE:
48
+ 'https://assets.memori.ai/api/v2/asset/c2b07166-de10-4c66-918b-7b7cd380cca7.glb',
49
+ };
50
+ const ANIMATION_DURATION = 3000; // Duration in milliseconds for non-idle animations
51
+
52
+ export default function FullbodyAvatar({
53
+ url,
54
+ sex,
55
+ onLoaded,
56
+ currentBaseAction,
57
+ timeScale,
58
+ isZoomed,
59
+ setMorphTargetInfluences,
60
+ setMorphTargetDictionary,
61
+ morphTargetInfluences,
62
+ eyeBlink,
63
+ setMeshRef,
64
+ clearVisemes,
65
+ }: FullbodyAvatarProps) {
66
+ const { scene } = useGLTF(url);
67
+ const { animations } = useGLTF(ANIMATION_URLS[sex]);
68
+ const { nodes, materials } = useGraph(scene);
69
+ const { actions } = useAnimations(animations, scene);
70
+ const [mixer] = useState(() => new AnimationMixer(scene));
71
+
72
+ const avatarMeshRef = useRef<SkinnedMesh>();
73
+ const currentActionRef = useRef<AnimationAction | null>(null);
74
+ const isTransitioningRef = useRef(false);
75
+
76
+ // Blink animation
77
+ useAvatarBlink({
78
+ enabled: eyeBlink || false,
79
+ setMorphTargetInfluences,
80
+ config: {
81
+ minInterval: 1500,
82
+ maxInterval: 4000,
83
+ blinkDuration: 120,
84
+ },
85
+ });
86
+
87
+ // Idle animation when emotion animation is finished
88
+ const transitionToIdle = useCallback(() => {
89
+ if (!actions || isTransitioningRef.current) return;
90
+
91
+ isTransitioningRef.current = true;
92
+
93
+ const finishCurrentAnimation = () => {
94
+ if (currentActionRef.current && !currentActionRef.current.paused) {
95
+ const remainingTime = (currentActionRef.current.getClip().duration - currentActionRef.current.time) * 1000;
96
+ setTimeout(() => {
97
+ startIdleAnimation();
98
+ }, remainingTime);
99
+ } else {
100
+ startIdleAnimation();
101
+ }
102
+ };
103
+
104
+ const startIdleAnimation = () => {
105
+ const idleAnimations = Object.keys(actions).filter(key =>
106
+ key.startsWith('Idle')
107
+ );
108
+ const randomIdle =
109
+ idleAnimations[Math.floor(Math.random() * idleAnimations.length)];
110
+
111
+ const idleAction = actions[randomIdle];
112
+ const fadeOutDuration = 0.5;
113
+ const fadeInDuration = 0.5;
114
+
115
+ if (currentActionRef.current) {
116
+ currentActionRef.current.fadeOut(fadeOutDuration);
117
+ }
118
+
119
+ idleAction?.reset().fadeIn(fadeInDuration).play();
120
+ currentActionRef.current = idleAction;
121
+
122
+ setTimeout(() => {
123
+ isTransitioningRef.current = false;
124
+ }, (fadeOutDuration + fadeInDuration) * 1000);
125
+ };
126
+
127
+ if (currentActionRef.current && !currentActionRef.current.getClip().name.startsWith('Idle')) {
128
+ finishCurrentAnimation();
129
+ } else {
130
+ startIdleAnimation();
131
+ }
132
+ }, [actions]);
133
+
134
+ // Base animation
135
+ useEffect(() => {
136
+ if (!actions || !currentBaseAction.action || isTransitioningRef.current)
137
+ return;
138
+
139
+ const newAction = actions[currentBaseAction.action];
140
+ if (!newAction) {
141
+ console.warn(
142
+ `Animation "${currentBaseAction.action}" not found in actions.`
143
+ );
144
+ return;
145
+ }
146
+
147
+ const fadeOutDuration = 0.8;
148
+ const fadeInDuration = 0.8;
149
+
150
+ if (!currentBaseAction.action.startsWith('Idle')) {
151
+ setTimeout(() => {
152
+ transitionToIdle();
153
+ }, ANIMATION_DURATION);
154
+ }
155
+
156
+ if (currentActionRef.current) {
157
+ currentActionRef.current.fadeOut(fadeOutDuration);
158
+ }
159
+
160
+ newAction.timeScale = timeScale;
161
+ newAction.reset().fadeIn(fadeInDuration).play();
162
+ currentActionRef.current = newAction;
163
+ }, [currentBaseAction, timeScale, actions, transitionToIdle]);
164
+
165
+ // Set up the mesh reference and morph target influences
166
+ useEffect(() => {
167
+ correctMaterials(materials);
168
+
169
+ scene.traverse((object: Object3D) => {
170
+ if (
171
+ object instanceof SkinnedMesh &&
172
+ (object.name === 'GBNL__Head' || object.name === 'Wolf3D_Avatar')
173
+ ) {
174
+ avatarMeshRef.current = object;
175
+ setMeshRef(object);
176
+
177
+ if (object.morphTargetDictionary && object.morphTargetInfluences) {
178
+ setMorphTargetDictionary(object.morphTargetDictionary);
179
+
180
+ const initialInfluences = Object.keys(
181
+ object.morphTargetDictionary
182
+ ).reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
183
+ setMorphTargetInfluences(initialInfluences);
184
+ }
185
+ }
186
+ });
187
+
188
+ onLoaded?.();
189
+
190
+ return () => {
191
+ Object.values(materials).forEach(dispose);
192
+ Object.values(nodes).filter(isSkinnedMesh).forEach(dispose);
193
+ clearVisemes();
194
+ };
195
+ }, [
196
+ materials,
197
+ nodes,
198
+ url,
199
+ onLoaded,
200
+ setMorphTargetDictionary,
201
+ setMorphTargetInfluences,
202
+ setMeshRef,
203
+ clearVisemes,
204
+ ]);
205
+
206
+ // Update morph target influences
207
+ useFrame((_, delta) => {
208
+ if (avatarMeshRef.current && avatarMeshRef.current.morphTargetDictionary) {
209
+ updateMorphTargetInfluences();
210
+ }
211
+ mixer.update(delta * 0.001);
212
+
213
+ function updateMorphTargetInfluences() {
214
+ Object.entries(morphTargetInfluences).forEach(([key, value]) => {
215
+ const index = avatarMeshRef.current!.morphTargetDictionary![key];
216
+ if (typeof index === 'number' &&
217
+ avatarMeshRef.current!.morphTargetInfluences) {
218
+ const currentValue = avatarMeshRef.current!.morphTargetInfluences[index];
219
+ const smoothValue = lerp(currentValue, value, 0.1);
220
+ avatarMeshRef.current!.morphTargetInfluences[index] = smoothValue;
221
+ }
222
+ });
223
+ }
224
+ });
225
+
226
+ return (
227
+ <group
228
+ position={isZoomed ? AVATAR_POSITION_ZOOMED : AVATAR_POSITION}
229
+ rotation={AVATAR_ROTATION}
230
+ >
231
+ <primitive object={scene} />
232
+ </group>
233
+ );
234
+ }
@@ -0,0 +1,123 @@
1
+ import React, { useEffect, useMemo, useRef } from 'react';
2
+ import { Object3D, SkinnedMesh, Vector3 } from 'three';
3
+ import { useGLTF } from '@react-three/drei';
4
+ import { correctMaterials, isSkinnedMesh } from '../../../../../helpers/utils';
5
+ import { useGraph, dispose, useFrame } from '@react-three/fiber';
6
+ import { useAvatarBlink } from '../../utils/useEyeBlink';
7
+ import useHeadMovement from '../../utils/useHeadMovement';
8
+ import { hideHands } from '../../utils/utils';
9
+
10
+
11
+ interface HalfBodyAvatarProps {
12
+ url: string;
13
+ setMorphTargetInfluences: (morphTargetInfluences: any) => void;
14
+ headMovement?: boolean;
15
+ speaking?: boolean;
16
+ onLoaded?: () => void;
17
+ setMeshRef: (mesh: Object3D) => void;
18
+ clearVisemes: () => void;
19
+ setMorphTargetDictionary: (morphTargetDictionary: any) => void;
20
+ eyeBlink?: boolean;
21
+ morphTargetInfluences: any;
22
+ }
23
+
24
+ const AVATAR_POSITION = new Vector3(0, -0.6, 0);
25
+ const lerp = (start: number, end: number, alpha: number): number => {
26
+ return start * (1 - alpha) + end * alpha;
27
+ };
28
+ export default function HalfBodyAvatar({
29
+ url,
30
+ setMorphTargetInfluences,
31
+ setMorphTargetDictionary,
32
+ headMovement,
33
+ eyeBlink,
34
+ setMeshRef,
35
+ onLoaded,
36
+ clearVisemes,
37
+ morphTargetInfluences,
38
+ }: HalfBodyAvatarProps) {
39
+ const { scene } = useGLTF(url);
40
+ const { nodes, materials } = useGraph(scene);
41
+ const avatarMeshRef = useRef<SkinnedMesh | null>(null);
42
+
43
+ useAvatarBlink({
44
+ enabled: eyeBlink || false,
45
+ setMorphTargetInfluences,
46
+ config: {
47
+ minInterval: 1500,
48
+ maxInterval: 4000,
49
+ blinkDuration: 120
50
+ }
51
+ });
52
+ useHeadMovement(headMovement, nodes);
53
+
54
+ useEffect(() => {
55
+ const setupAvatar = () => {
56
+ hideHands(nodes);
57
+ correctMaterials(materials);
58
+ // Set mesh reference for the first SkinnedMesh found
59
+ const firstSkinnedMesh = Object.values(nodes).find(isSkinnedMesh) as SkinnedMesh;
60
+ if (firstSkinnedMesh) {
61
+ setMeshRef(firstSkinnedMesh);
62
+ avatarMeshRef.current = firstSkinnedMesh;
63
+ if (firstSkinnedMesh.morphTargetDictionary && firstSkinnedMesh.morphTargetInfluences) {
64
+ setMorphTargetDictionary(firstSkinnedMesh.morphTargetDictionary);
65
+ const initialInfluences = Object.keys(
66
+ firstSkinnedMesh.morphTargetDictionary
67
+ ).reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
68
+ setMorphTargetInfluences(initialInfluences);
69
+ }
70
+ }
71
+ onLoaded?.();
72
+ };
73
+
74
+ setupAvatar();
75
+
76
+ return () => {
77
+ const disposeObjects = () => {
78
+ Object.values(materials).forEach(dispose);
79
+ Object.values(nodes).filter(isSkinnedMesh).forEach(dispose);
80
+ clearVisemes();
81
+ };
82
+
83
+ disposeObjects();
84
+ };
85
+ }, [materials, nodes, url, onLoaded, clearVisemes]);
86
+
87
+ const skinnedMeshes = useMemo(
88
+ () => Object.values(nodes).filter(isSkinnedMesh),
89
+ [nodes]
90
+ );
91
+
92
+ // Update morph target influences
93
+ useFrame((_) => {
94
+ if (avatarMeshRef.current && avatarMeshRef.current.morphTargetDictionary) {
95
+ updateMorphTargetInfluences();
96
+ }
97
+
98
+ function updateMorphTargetInfluences() {
99
+ Object.entries(morphTargetInfluences).forEach(([key, value]) => {
100
+ const index = avatarMeshRef.current!.morphTargetDictionary![key];
101
+ if (typeof index === 'number' &&
102
+ avatarMeshRef.current!.morphTargetInfluences) {
103
+ const currentValue = avatarMeshRef.current!.morphTargetInfluences[index];
104
+ const smoothValue = lerp(currentValue, value as number, 0.1);
105
+ avatarMeshRef.current!.morphTargetInfluences[index] = smoothValue;
106
+ }
107
+ });
108
+ }
109
+ });
110
+
111
+
112
+ return (
113
+ <group position={AVATAR_POSITION}>
114
+ {nodes.Hips && <primitive key="armature" object={nodes.Hips} />}
115
+ {skinnedMeshes.map(
116
+ (node: Object3D) =>
117
+ node && (
118
+ <primitive key={node.name} object={node} receiveShadow castShadow />
119
+ )
120
+ )}
121
+ </group>
122
+ );
123
+ }
@@ -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;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { Meta, Story } from '@storybook/react';
3
3
  import I18nWrapper from '../../../I18nWrapper';
4
4
  import AvatarView, { Props } from './index';
5
+ import { VisemeProvider } from '../../../context/visemeContext';
5
6
 
6
7
  const meta: Meta = {
7
8
  title: 'RPM 3D Avatar',
@@ -60,9 +61,7 @@ const meta: Meta = {
60
61
  },
61
62
  },
62
63
  parameters: {
63
- controls: { expanded: false,
64
- },
65
-
64
+ controls: { expanded: false },
66
65
  },
67
66
  };
68
67
 
@@ -76,11 +75,13 @@ const Template: Story<Props> = args => {
76
75
 
77
76
  return hydrated ? (
78
77
  <I18nWrapper>
79
- <AvatarView
80
- {...args}
81
- url={args.url + `#${new Date(Date.now()).toISOString()}`}
82
- key={Date.now()}
83
- />
78
+ <VisemeProvider>
79
+ <AvatarView
80
+ {...args}
81
+ url={args.url + `#${new Date(Date.now()).toISOString()}`}
82
+ key={Date.now()}
83
+ />
84
+ </VisemeProvider>
84
85
  </I18nWrapper>
85
86
  ) : (
86
87
  <></>
@@ -95,6 +96,8 @@ Default.args = {
95
96
  headMovement: false,
96
97
  rotateAvatar: false,
97
98
  speaking: false,
99
+ clearVisemes: () => {},
100
+ setMeshRef: () => {},
98
101
  url: 'https://assets.memori.ai/api/v2/asset/b791f77c-1a94-4272-829e-eca82fcc62b7.glb',
99
102
  fallbackImg:
100
103
  'https://assets.memori.ai/api/v2/asset/d8035229-08cf-42a7-a532-ab051df2603d.png',
@@ -102,6 +105,8 @@ Default.args = {
102
105
 
103
106
  export const EyeBlink = Template.bind({});
104
107
  EyeBlink.args = {
108
+ clearVisemes: () => {},
109
+ setMeshRef: () => {},
105
110
  eyeBlink: true,
106
111
  headMovement: false,
107
112
  rotateAvatar: false,
@@ -113,6 +118,8 @@ EyeBlink.args = {
113
118
 
114
119
  export const HeadMovement = Template.bind({});
115
120
  HeadMovement.args = {
121
+ clearVisemes: () => {},
122
+ setMeshRef: () => {},
116
123
  eyeBlink: false,
117
124
  headMovement: true,
118
125
  rotateAvatar: false,
@@ -124,6 +131,8 @@ HeadMovement.args = {
124
131
 
125
132
  export const RotateAvatar = Template.bind({});
126
133
  RotateAvatar.args = {
134
+ clearVisemes: () => {},
135
+ setMeshRef: () => {},
127
136
  eyeBlink: false,
128
137
  headMovement: false,
129
138
  rotateAvatar: true,
@@ -135,6 +144,8 @@ RotateAvatar.args = {
135
144
 
136
145
  export const Speaking = Template.bind({});
137
146
  Speaking.args = {
147
+ clearVisemes: () => {},
148
+ setMeshRef: () => {},
138
149
  eyeBlink: false,
139
150
  headMovement: false,
140
151
  rotateAvatar: false,
@@ -151,12 +162,30 @@ Fullbody.args = {
151
162
  headMovement: true,
152
163
  rotateAvatar: true,
153
164
  speaking: false,
165
+ clearVisemes: () => {},
166
+ setMeshRef: () => {},
154
167
  url: 'https://models.readyplayer.me/63b55751f17e295642bf07a2.glb',
155
168
  fallbackImg:
156
169
  'https://assets.memori.ai/api/v2/asset/3049582f-db5f-452c-913d-e4340d4afd0a.png',
157
170
  halfBody: false,
158
171
  };
159
172
 
173
+ export const FullbodyZoomed = Template.bind({});
174
+ FullbodyZoomed.args = {
175
+ sex: 'FEMALE',
176
+ eyeBlink: true,
177
+ headMovement: true,
178
+ rotateAvatar: true,
179
+ speaking: false,
180
+ isZoomed: true,
181
+ clearVisemes: () => {},
182
+ setMeshRef: () => {},
183
+ url: 'https://assets.memori.ai/api/v2/asset/3f5ef41c-6c4c-449c-888d-cf9c89782528.glb',
184
+ fallbackImg:
185
+ 'https://assets.memori.ai/api/v2/asset/3049582f-db5f-452c-913d-e4340d4afd0a.png',
186
+ halfBody: false,
187
+ };
188
+
160
189
  export const FullbodyAnimatedIdle = Template.bind({});
161
190
  FullbodyAnimatedIdle.args = {
162
191
  sex: 'MALE',
@@ -164,6 +193,8 @@ FullbodyAnimatedIdle.args = {
164
193
  headMovement: true,
165
194
  rotateAvatar: true,
166
195
  speaking: false,
196
+ clearVisemes: () => {},
197
+ setMeshRef: () => {},
167
198
  url: 'https://models.readyplayer.me/63b55751f17e295642bf07a2.glb',
168
199
  fallbackImg:
169
200
  'https://assets.memori.ai/api/v2/asset/3049582f-db5f-452c-913d-e4340d4afd0a.png',
@@ -178,6 +209,8 @@ FullbodyAnimatedLoading.args = {
178
209
  headMovement: true,
179
210
  rotateAvatar: true,
180
211
  speaking: false,
212
+ clearVisemes: () => {},
213
+ setMeshRef: () => {},
181
214
  url: 'https://models.readyplayer.me/63b55751f17e295642bf07a2.glb',
182
215
  fallbackImg:
183
216
  'https://assets.memori.ai/api/v2/asset/3049582f-db5f-452c-913d-e4340d4afd0a.png',
@@ -192,6 +225,8 @@ FullbodyAnimatedSpeaking.args = {
192
225
  headMovement: true,
193
226
  rotateAvatar: true,
194
227
  speaking: true,
228
+ clearVisemes: () => {},
229
+ setMeshRef: () => {},
195
230
  url: 'https://models.readyplayer.me/63b55751f17e295642bf07a2.glb',
196
231
  fallbackImg:
197
232
  'https://assets.memori.ai/api/v2/asset/3049582f-db5f-452c-913d-e4340d4afd0a.png',
@@ -206,6 +241,8 @@ FullbodyFemale.args = {
206
241
  headMovement: true,
207
242
  rotateAvatar: true,
208
243
  speaking: false,
244
+ clearVisemes: () => {},
245
+ setMeshRef: () => {},
209
246
  url: 'https://models.readyplayer.me/650d50c2663b19e0d2831b2b.glb',
210
247
  fallbackImg:
211
248
  'https://assets.memori.ai/api/v2/asset/3049582f-db5f-452c-913d-e4340d4afd0a.png',
@@ -219,6 +256,8 @@ FullbodyAnimatedFemale.args = {
219
256
  headMovement: true,
220
257
  rotateAvatar: true,
221
258
  speaking: true,
259
+ clearVisemes: () => {},
260
+ setMeshRef: () => {},
222
261
  url: 'https://models.readyplayer.me/650d50c2663b19e0d2831b2b.glb',
223
262
  fallbackImg:
224
263
  'https://assets.memori.ai/api/v2/asset/3049582f-db5f-452c-913d-e4340d4afd0a.png',