@memori.ai/memori-react 7.19.1 → 7.21.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 (145) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/components/Avatar/Avatar.js +3 -3
  3. package/dist/components/Avatar/Avatar.js.map +1 -1
  4. package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
  5. package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +19 -77
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +95 -70
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  13. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
  14. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +747 -0
  15. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
  16. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
  17. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +60 -2
  18. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  19. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
  20. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
  21. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  22. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
  23. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js +68 -70
  24. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  25. package/dist/components/Avatar/AvatarView/index.d.ts +1 -1
  26. package/dist/components/Avatar/AvatarView/index.js +2 -2
  27. package/dist/components/Avatar/AvatarView/index.js.map +1 -1
  28. package/dist/components/Chat/Chat.js +2 -2
  29. package/dist/components/Chat/Chat.js.map +1 -1
  30. package/dist/components/ChatBubble/ChatBubble.js +12 -9
  31. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  32. package/dist/components/ExpertsDrawer/ExpertsDrawer.js +1 -1
  33. package/dist/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
  34. package/dist/components/LoginDrawer/LoginDrawer.js +6 -6
  35. package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -1
  36. package/dist/components/MemoriWidget/MemoriWidget.js +143 -64
  37. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  38. package/dist/components/SignupForm/SignupForm.js +4 -4
  39. package/dist/components/SignupForm/SignupForm.js.map +1 -1
  40. package/dist/components/StartPanel/StartPanel.js +5 -5
  41. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  42. package/dist/components/UploadButton/UploadButton.js +2 -2
  43. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  44. package/dist/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
  45. package/dist/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
  46. package/dist/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
  47. package/dist/context/visemeContext.js +0 -39
  48. package/dist/context/visemeContext.js.map +1 -1
  49. package/dist/index.js +4 -3
  50. package/dist/index.js.map +1 -1
  51. package/dist/locales/de.json +1 -0
  52. package/dist/locales/en.json +1 -0
  53. package/dist/locales/es.json +1 -0
  54. package/dist/locales/fr.json +1 -0
  55. package/dist/locales/it.json +1 -0
  56. package/esm/components/Avatar/Avatar.js +3 -3
  57. package/esm/components/Avatar/Avatar.js.map +1 -1
  58. package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
  59. package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
  60. package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
  61. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
  62. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +20 -78
  63. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  64. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
  65. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +99 -74
  66. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  67. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
  68. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +743 -0
  69. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
  70. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
  71. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +61 -3
  72. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  73. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
  74. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
  75. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  76. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
  77. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js +67 -69
  78. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  79. package/esm/components/Avatar/AvatarView/index.d.ts +1 -1
  80. package/esm/components/Avatar/AvatarView/index.js +2 -2
  81. package/esm/components/Avatar/AvatarView/index.js.map +1 -1
  82. package/esm/components/Chat/Chat.js +2 -2
  83. package/esm/components/Chat/Chat.js.map +1 -1
  84. package/esm/components/ChatBubble/ChatBubble.js +12 -9
  85. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  86. package/esm/components/ExpertsDrawer/ExpertsDrawer.js +1 -1
  87. package/esm/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
  88. package/esm/components/LoginDrawer/LoginDrawer.js +6 -6
  89. package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -1
  90. package/esm/components/MemoriWidget/MemoriWidget.js +143 -64
  91. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  92. package/esm/components/SignupForm/SignupForm.js +4 -4
  93. package/esm/components/SignupForm/SignupForm.js.map +1 -1
  94. package/esm/components/StartPanel/StartPanel.js +5 -5
  95. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  96. package/esm/components/UploadButton/UploadButton.js +2 -2
  97. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  98. package/esm/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
  99. package/esm/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
  100. package/esm/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
  101. package/esm/context/visemeContext.js +0 -39
  102. package/esm/context/visemeContext.js.map +1 -1
  103. package/esm/index.js +4 -3
  104. package/esm/index.js.map +1 -1
  105. package/esm/locales/de.json +1 -0
  106. package/esm/locales/en.json +1 -0
  107. package/esm/locales/es.json +1 -0
  108. package/esm/locales/fr.json +1 -0
  109. package/esm/locales/it.json +1 -0
  110. package/package.json +2 -2
  111. package/src/components/Avatar/Avatar.tsx +3 -3
  112. package/src/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.tsx +15 -8
  113. package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +64 -219
  114. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +221 -124
  115. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.ts +1250 -0
  116. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +164 -8
  117. package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +19 -17
  118. package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +80 -79
  119. package/src/components/Avatar/AvatarView/index.tsx +1 -7
  120. package/src/components/Chat/Chat.tsx +2 -2
  121. package/src/components/ChatBubble/ChatBubble.tsx +37 -26
  122. package/src/components/ExpertsDrawer/ExpertsDrawer.tsx +1 -1
  123. package/src/components/LoginDrawer/LoginDrawer.tsx +6 -6
  124. package/src/components/MemoriWidget/MemoriWidget.tsx +184 -78
  125. package/src/components/SignupForm/SignupForm.tsx +5 -5
  126. package/src/components/StartPanel/StartPanel.tsx +5 -5
  127. package/src/components/UploadButton/UploadButton.tsx +4 -4
  128. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +1 -1
  129. package/src/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
  130. package/src/components/WhyThisAnswer/WhyThisAnswer.stories.tsx +44 -3
  131. package/src/components/WhyThisAnswer/WhyThisAnswer.test.tsx +128 -8
  132. package/src/components/WhyThisAnswer/WhyThisAnswer.tsx +28 -3
  133. package/src/components/WhyThisAnswer/__snapshots__/WhyThisAnswer.test.tsx.snap +15 -1
  134. package/src/components/layouts/layouts.stories.tsx +0 -8
  135. package/src/context/visemeContext.tsx +40 -41
  136. package/src/index.stories.tsx +63 -65
  137. package/src/index.tsx +5 -3
  138. package/src/locales/de.json +1 -0
  139. package/src/locales/en.json +1 -0
  140. package/src/locales/es.json +1 -0
  141. package/src/locales/fr.json +1 -0
  142. package/src/locales/it.json +1 -0
  143. package/src/mocks/data.ts +3 -9
  144. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.ts +0 -308
  145. package/src/helpers/tenant.ts +0 -47
@@ -1,56 +1,95 @@
1
- import { useEffect, useRef, useMemo } from 'react';
2
- import {
3
- AnimationMixer,
4
- SkinnedMesh,
5
- Object3D,
6
- AnimationAction,
7
- Vector3
8
- } from 'three';
9
- import { useAnimations, useGLTF } from '@react-three/drei';
1
+ import { useEffect, useRef, useMemo, useCallback, useState } from 'react';
2
+ import { AnimationAction, Vector3, Scene, SkinnedMesh, Object3D } from 'three';
3
+ import { useGLTF, useAnimations } from '@react-three/drei';
10
4
  import { useFrame } from '@react-three/fiber';
11
- import { AnimationState, FullbodyAvatarProps } from './types';
12
- import { AnimationController } from '../controllers/AnimationController';
13
5
  import { MorphTargetController } from '../controllers/MorphTargetController';
14
6
  import { AvatarPositionController } from '../controllers/AvatarPositionController';
7
+ import { AvatarAnimator } from '../controllers/AvatarAnimator';
15
8
  import {
9
+ ANIMATION_URLS,
16
10
  AVATAR_POSITION,
17
11
  AVATAR_ROTATION,
18
- AVATAR_POSITION_ZOOMED,
19
- ANIMATION_URLS,
20
- DEFAULT_CONFIG,
21
12
  SCALE_LERP_FACTOR,
22
13
  } from '../../constants';
23
14
  import DynamicShadow from '../../Shadow/DynamicShadow';
24
15
 
16
+ // Enhanced props interface for FullbodyAvatar
17
+ export interface FullbodyAvatarProps {
18
+ url: string;
19
+ sex: 'MALE' | 'FEMALE';
20
+ eyeBlink: boolean;
21
+ updateCurrentViseme: (
22
+ currentTime: number
23
+ ) => { name: string; weight: number } | null;
24
+ avatarHeight?: number;
25
+ avatarDepth?: number;
26
+ onCameraZChange?: (value: number) => void;
27
+ chatEmission: any;
28
+ loading: boolean;
29
+ // New prop to expose animator to parent component
30
+ setAnimatorRef?: (animator: AvatarAnimator | null) => void;
31
+ }
32
+
25
33
  export function FullbodyAvatar({
26
34
  url,
27
35
  sex,
28
- setIsRpm,
29
- currentBaseAction,
30
- timeScale,
31
36
  eyeBlink,
32
37
  updateCurrentViseme,
33
- setMorphTargetDictionary,
34
- setMorphTargetInfluences,
35
- emotionMorphTargets,
36
38
  avatarHeight = 50,
37
39
  avatarDepth = 0,
38
40
  onCameraZChange,
41
+ chatEmission,
42
+ loading,
43
+ setAnimatorRef,
39
44
  }: FullbodyAvatarProps) {
40
- const { scene } = useGLTF(url);
41
- const { animations: baseAnimations } = useGLTF(url);
42
- const { animations: additionalAnimations } = useGLTF(ANIMATION_URLS[sex]);
43
- const mergedAnimations = useMemo(() => [...baseAnimations, ...additionalAnimations], [baseAnimations, additionalAnimations]);
45
+ // Load the avatar model and its animations
46
+ const { scene, animations: baseAnimations } = useGLTF(url);
47
+
48
+ // Load additional animations based on sex (fallback animations)
49
+ const { animations: additionalAnimations } = useGLTF(ANIMATION_URLS[sex]);
50
+
51
+ // Check if avatar needs additional animations
52
+ const needsAdditionalAnimations = useMemo(() => {
53
+ let found = false;
54
+ scene.traverse((object: Object3D) => {
55
+ if (
56
+ object instanceof SkinnedMesh &&
57
+ (object.name === 'GBNL__Head' ||
58
+ object.name === 'Wolf3D_Avatar' ||
59
+ object.name === 'Wolf3D_Avatar006_1')
60
+ ) {
61
+ found = true;
62
+ }
63
+ });
64
+ return found;
65
+ }, [scene]);
66
+
67
+ // Merge base and additional animations only if needed
68
+ const mergedAnimations = useMemo(
69
+ () => needsAdditionalAnimations ? [...baseAnimations, ...additionalAnimations] : baseAnimations,
70
+ [baseAnimations, additionalAnimations, needsAdditionalAnimations]
71
+ );
72
+
73
+ // Create animation actions from the merged animations
44
74
  const { actions } = useAnimations(mergedAnimations, scene);
45
75
 
46
- const animationControllerRef = useRef<AnimationController>();
47
- const morphTargetControllerRef = useRef<MorphTargetController>();
48
- const positionControllerRef = useRef<AvatarPositionController>();
76
+ // System controllers - use refs to maintain instance across renders
77
+ const morphTargetControllerRef = useRef<MorphTargetController | null>(null);
78
+ const positionControllerRef = useRef<AvatarPositionController | null>(null);
79
+ const animatorRef = useRef<AvatarAnimator | null>(null);
80
+
81
+ // Reference to track initialization status
82
+ const isInitializedRef = useRef<boolean>(false);
83
+
84
+ // For position tracking and updates
49
85
  const lastPositionRef = useRef<Vector3>(AVATAR_POSITION.clone());
50
86
  const positionUpdateThrottleRef = useRef<number>(0);
51
87
  const POSITION_UPDATE_INTERVAL = 1; // ~1000fps for more frequent updates
52
- const POSITION_THRESHOLD = 0.000001; // Much smaller threshold for more sensitive detection
53
-
88
+
89
+
90
+ const [isRpm, setIsRpm] = useState(false);
91
+
92
+ // For eye blinking
54
93
  const blinkStateRef = useRef({
55
94
  isBlinking: false,
56
95
  lastBlinkTime: 0,
@@ -58,157 +97,215 @@ export function FullbodyAvatar({
58
97
  blinkStartTime: 0,
59
98
  });
60
99
 
61
- // Initialize controllers
100
+ // Find head mesh and initialize morph target controller - only run when scene changes
62
101
  useEffect(() => {
63
- if (!positionControllerRef.current) {
64
- positionControllerRef.current = new AvatarPositionController(AVATAR_POSITION);
65
- }
102
+ if (!scene) return;
66
103
 
67
- if (!actions || !scene) return;
104
+ // Find head mesh
105
+ let headMesh: SkinnedMesh | null = null;
106
+ scene.traverse((object: Object3D) => {
107
+ if (
108
+ object instanceof SkinnedMesh &&
109
+ (object.name === 'GBNL__Head' ||
110
+ object.name === 'Wolf3D_Avatar' ||
111
+ object.name === 'Wolf3D_Avatar006_1')
112
+ ) {
68
113
 
69
- const mixer = new AnimationMixer(scene);
70
- animationControllerRef.current = new AnimationController(
71
- mixer,
72
- actions as Record<string, AnimationAction>,
73
- { ...DEFAULT_CONFIG }
74
- );
114
+ if(object.name === 'Wolf3D_Avatar' ||
115
+ object.name === 'Wolf3D_Avatar006_1') {
116
+ setIsRpm(true);
117
+ console.log('RPM avatar detected');
118
+ }
119
+ headMesh = object;
120
+ }
121
+ });
75
122
 
123
+ // Initialize morph target controller if head mesh found
76
124
  if (headMesh) {
77
- morphTargetControllerRef.current = new MorphTargetController(headMesh);
78
-
79
- if (headMesh.morphTargetDictionary && headMesh.morphTargetInfluences) {
80
- setMorphTargetDictionary(headMesh.morphTargetDictionary);
81
- const initialInfluences = Object.keys(headMesh.morphTargetDictionary)
82
- .reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
83
- setMorphTargetInfluences(initialInfluences);
125
+ // Only create a new controller if one doesn't exist
126
+ if (!morphTargetControllerRef.current) {
127
+ morphTargetControllerRef.current = new MorphTargetController(headMesh);
84
128
  }
85
129
  }
130
+ }, [scene]); // Only re-run if scene changes
86
131
 
87
- // Initialize position tracking with high precision
88
- const initialPosition = AVATAR_POSITION.clone();
89
- initialPosition.setX(Number(initialPosition.x.toFixed(6)));
90
- initialPosition.setY(Number(initialPosition.y.toFixed(6)));
91
- initialPosition.setZ(Number(initialPosition.z.toFixed(6)));
92
- }, [actions, scene]);
132
+ // Initialize position controller once
133
+ useEffect(() => {
134
+ if (!positionControllerRef.current) {
135
+ positionControllerRef.current = new AvatarPositionController(
136
+ AVATAR_POSITION
137
+ );
138
+ }
139
+
140
+ // Cleanup function
141
+ return () => {
142
+ // No need to dispose the position controller here
143
+ };
144
+ }, []); // Empty dependency array means this runs once
93
145
 
146
+ // Handle avatar height changes
94
147
  useEffect(() => {
95
148
  if (positionControllerRef.current) {
96
149
  positionControllerRef.current.updateHeight(avatarHeight, false);
97
150
  }
98
151
  }, [avatarHeight]);
99
152
 
153
+ // Handle avatar depth changes
100
154
  useEffect(() => {
101
155
  if (positionControllerRef.current && onCameraZChange) {
102
- const newCameraZ = positionControllerRef.current.updateDepth(avatarDepth, false);
156
+ const newCameraZ = positionControllerRef.current.updateDepth(
157
+ avatarDepth,
158
+ false
159
+ );
103
160
  onCameraZChange(newCameraZ);
104
161
  }
105
162
  }, [avatarDepth, onCameraZChange]);
106
163
 
107
- // Find head mesh
108
- const headMesh = useMemo(() => {
109
- let foundMesh: SkinnedMesh | undefined;
110
- scene?.traverse((object: Object3D) => {
111
- if (
112
- object instanceof SkinnedMesh &&
113
- (object.name === 'GBNL__Head' || object.name === 'Wolf3D_Avatar' || object.name === 'Wolf3D_Avatar006_1')
114
- ) {
115
- if(object.name === 'GBNL__Head') {
116
- setIsRpm(false);
117
- } else {
118
- setIsRpm(true);
164
+ // Initialize animator only once and cleanup properly
165
+ useEffect(() => {
166
+ // Only initialize if not already initialized and dependencies are available
167
+ if (!scene || !actions || isInitializedRef.current) {
168
+ return;
169
+ }
170
+
171
+ console.log('Initializing animator');
172
+
173
+ // Create the animator only once
174
+ if (!animatorRef.current) {
175
+ animatorRef.current = new AvatarAnimator();
176
+ }
177
+
178
+ const animator = animatorRef.current;
179
+
180
+ const initWithPreloadedAnimations = async () => {
181
+ try {
182
+ // Prevent multiple initializations
183
+ if (animator.isInitialized()) {
184
+ console.log('Animator already initialized, skipping initialization');
185
+ return;
119
186
  }
120
- foundMesh = object;
187
+
188
+ // Initialize animator with avatar animations first, then fallback animations
189
+ await animator.initialize(
190
+ scene as unknown as Scene,
191
+ actions as Record<string, AnimationAction>,
192
+ mergedAnimations,
193
+ isRpm ? 'RPM' : 'CUSTOM_GLB'
194
+ );
195
+
196
+ console.log(
197
+ 'AvatarAnimator initialized with',
198
+ Object.keys(actions).length,
199
+ 'animations'
200
+ );
201
+
202
+ // Expose animator to parent component if callback provided
203
+ if (setAnimatorRef) {
204
+ setAnimatorRef(animator);
205
+ }
206
+
207
+ // Mark as initialized
208
+ isInitializedRef.current = true;
209
+
210
+ // Set initial time scale
211
+ animator.setTimeScale(0.8);
212
+ } catch (error) {
213
+ console.error('Error initializing AvatarAnimator:', error);
121
214
  }
122
- });
123
- return foundMesh;
124
- }, [scene]);
215
+ };
125
216
 
126
- // Handle animation state changes
127
- useEffect(() => {
128
- if (!animationControllerRef.current) return;
217
+ initWithPreloadedAnimations();
129
218
 
130
- if (currentBaseAction.action.startsWith('Loading')) {
131
- animationControllerRef.current.updateIsChatAlreadyStarted(true);
132
- animationControllerRef.current.transitionTo(
133
- AnimationState.LOADING,
134
- currentBaseAction.action
135
- );
136
- } else if (currentBaseAction.action.includes('->')) {
137
- animationControllerRef.current.playSequence(currentBaseAction.action);
138
- } else if (currentBaseAction.action.startsWith('Idle')) {
139
- animationControllerRef.current.transitionTo(AnimationState.IDLE);
140
- } else {
141
- animationControllerRef.current.transitionTo(
142
- AnimationState.EMOTION,
143
- currentBaseAction.action
144
- );
145
- }
146
- }, [currentBaseAction]);
219
+ // Cleanup on unmount
220
+ return () => {
221
+ // Only clean up if we created it in this component
222
+ if (animatorRef.current && isInitializedRef.current) {
223
+ console.log('Cleaning up animator');
147
224
 
148
- // Update timeScale when it changes
149
- useEffect(() => {
150
- animationControllerRef.current?.setTimeScale(timeScale);
151
- }, [timeScale]);
225
+ // Dispose mixer if needed
226
+ if ('mixer' in animatorRef.current && animatorRef.current['mixer']) {
227
+ (animatorRef.current['mixer'] as any).stopAllAction();
228
+ }
152
229
 
153
- // Animation and scaling update loop with high precision position tracking
154
- useFrame((state, delta) => {
230
+ // Clear reference in parent component
231
+ if (setAnimatorRef) {
232
+ setAnimatorRef(null);
233
+ }
234
+
235
+ // Reset references
236
+ isInitializedRef.current = false;
237
+ // Don't null out animatorRef here to prevent flickering during re-renders
238
+ }
239
+ };
240
+ }, [scene, actions, mergedAnimations, sex, setAnimatorRef]);
241
+
242
+ // Memoize the frame callback to prevent creating a new function every render
243
+ const frameCallback = useCallback((state: any, delta: number) => {
155
244
  const currentTime = state.clock.elapsedTime * 1000;
156
245
 
157
- // Update animations
158
- animationControllerRef.current?.update(delta);
246
+ // Update animation system
247
+ if (animatorRef.current && isInitializedRef.current) {
248
+ animatorRef.current.update(delta);
249
+ }
159
250
 
160
- // Update morph targets
251
+ // Update morph targets for facial expressions and visemes
161
252
  if (morphTargetControllerRef.current) {
162
253
  const currentViseme = updateCurrentViseme(currentTime / 1000);
254
+
163
255
  morphTargetControllerRef.current.updateMorphTargets(
164
256
  currentTime,
165
- emotionMorphTargets,
257
+ chatEmission,
258
+ loading,
166
259
  currentViseme,
167
- eyeBlink || false,
260
+ eyeBlink,
168
261
  blinkStateRef.current
169
262
  );
170
263
  }
171
264
 
172
- // Update scale and check position changes with high precision
265
+ // Update position and scale
173
266
  if (scene && positionControllerRef.current) {
174
- const newScale = positionControllerRef.current.updateScale(SCALE_LERP_FACTOR);
267
+ // Update scale
268
+ const newScale =
269
+ positionControllerRef.current.updateScale(SCALE_LERP_FACTOR);
175
270
  scene.scale.copy(newScale);
176
271
 
177
272
  // High frequency position update check
178
- if (currentTime - positionUpdateThrottleRef.current >= POSITION_UPDATE_INTERVAL) {
273
+ if (
274
+ currentTime - positionUpdateThrottleRef.current >=
275
+ POSITION_UPDATE_INTERVAL
276
+ ) {
179
277
  const currentPosition = positionControllerRef.current.getPosition();
180
-
278
+
181
279
  // Round to 6 decimal places for high precision
182
280
  currentPosition.setX(Number(currentPosition.x.toFixed(6)));
183
281
  currentPosition.setY(Number(currentPosition.y.toFixed(6)));
184
282
  currentPosition.setZ(Number(currentPosition.z.toFixed(6)));
185
-
186
- const positionDelta = currentPosition.distanceTo(lastPositionRef.current);
187
-
283
+
188
284
  lastPositionRef.current.copy(currentPosition);
189
-
190
285
  positionUpdateThrottleRef.current = currentTime;
191
286
  }
192
287
  }
193
- });
288
+ }, [scene, updateCurrentViseme, chatEmission, loading, eyeBlink]);
289
+
290
+ // Use the memoized callback in useFrame
291
+ useFrame(frameCallback);
194
292
 
195
- // Get current position from controller with fallback
293
+ // Get current position from controller with fallback - memoize to prevent recreation
196
294
  const position = useMemo(() => {
197
- return positionControllerRef.current?.getPosition() || AVATAR_POSITION.clone();
198
- }, [positionControllerRef.current]);
295
+ return (
296
+ positionControllerRef.current?.getPosition() || AVATAR_POSITION.clone()
297
+ );
298
+ }, []);
199
299
 
200
300
  return (
201
301
  <>
202
- <DynamicShadow
203
- currentBaseAction={currentBaseAction}
204
- avatarPosition={position}
205
- />
206
- <group
207
- position={position}
208
- rotation={AVATAR_ROTATION}
209
- >
210
- <primitive object={scene} />
211
- </group>
302
+ <DynamicShadow animator={animatorRef.current} avatarPosition={position} />
303
+ <group position={position} rotation={AVATAR_ROTATION}>
304
+ <primitive object={scene} />
305
+ </group>
212
306
  </>
213
307
  );
214
- }
308
+ }
309
+
310
+ // Avoid using React.memo here as it's exported as a named function
311
+ // The parent component should handle memoization if needed