@memori.ai/memori-react 7.13.4 → 7.14.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 (56) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/components/Avatar/Avatar.css +2 -2
  3. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +15 -7
  4. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  5. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +7 -4
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.d.ts +38 -0
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.js +181 -0
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.js.map +1 -0
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarPositionController.d.ts +19 -0
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarPositionController.js +60 -0
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarPositionController.js.map +1 -0
  13. package/dist/components/Avatar/AvatarView/AvatarComponent/components/{FullbodyAvatar/MorhTargetController.js → controllers/MorphTargetController.js} +6 -4
  14. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -0
  15. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +3 -3
  16. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  17. package/esm/components/Avatar/Avatar.css +2 -2
  18. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +15 -7
  19. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
  20. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +6 -3
  21. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  22. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.d.ts +38 -0
  23. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.js +177 -0
  24. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.js.map +1 -0
  25. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarPositionController.d.ts +19 -0
  26. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarPositionController.js +56 -0
  27. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarPositionController.js.map +1 -0
  28. package/esm/components/Avatar/AvatarView/AvatarComponent/components/{FullbodyAvatar/MorhTargetController.js → controllers/MorphTargetController.js} +6 -4
  29. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -0
  30. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +2 -2
  31. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/components/Avatar/Avatar.css +2 -2
  34. package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +115 -67
  35. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +5 -3
  36. package/src/components/Avatar/AvatarView/AvatarComponent/components/{FullbodyAvatar → controllers}/AnimationController.ts +52 -1
  37. package/src/components/Avatar/AvatarView/AvatarComponent/components/{PositionController.ts → controllers/AvatarPositionController.ts} +1 -1
  38. package/src/components/Avatar/AvatarView/AvatarComponent/components/{MorphTargetController.ts → controllers/MorphTargetController.ts} +1 -2
  39. package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +2 -2
  40. package/src/index.stories.tsx +16 -0
  41. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/MorhTargetController.js.map +0 -1
  42. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/constants.d.ts +0 -17
  43. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/constants.js +0 -25
  44. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/constants.js.map +0 -1
  45. package/dist/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.d.ts +0 -26
  46. package/dist/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js +0 -166
  47. package/dist/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js.map +0 -1
  48. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/MorhTargetController.js.map +0 -1
  49. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/constants.d.ts +0 -17
  50. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/constants.js +0 -22
  51. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/constants.js.map +0 -1
  52. package/esm/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.d.ts +0 -26
  53. package/esm/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js +0 -163
  54. package/esm/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js.map +0 -1
  55. /package/dist/components/Avatar/AvatarView/AvatarComponent/components/{FullbodyAvatar/MorhTargetController.d.ts → controllers/MorphTargetController.d.ts} +0 -0
  56. /package/esm/components/Avatar/AvatarView/AvatarComponent/components/{FullbodyAvatar/MorhTargetController.d.ts → controllers/MorphTargetController.d.ts} +0 -0
@@ -25,7 +25,9 @@ interface Props {
25
25
  avatarDepth?: number;
26
26
  stopProcessing: () => void;
27
27
  resetVisemeQueue: () => void;
28
- updateCurrentViseme: (currentTime: number) => { name: string; weight: number } | null;
28
+ updateCurrentViseme: (
29
+ currentTime: number
30
+ ) => { name: string; weight: number } | null;
29
31
  setCameraZ: (value: number) => void;
30
32
  }
31
33
 
@@ -55,9 +57,15 @@ export const AvatarView: React.FC<Props & { halfBody: boolean }> = ({
55
57
  action: animation || 'Idle1',
56
58
  weight: 1,
57
59
  });
58
- const [morphTargetInfluences, setMorphTargetInfluences] = useState<Record<string, number>>({});
59
- const [morphTargetDictionary, setMorphTargetDictionary] = useState<Record<string, number>>({});
60
- const [emotionMorphTargets, setEmotionMorphTargets] = useState<Record<string, number>>({});
60
+ const [morphTargetInfluences, setMorphTargetInfluences] = useState<
61
+ Record<string, number>
62
+ >({});
63
+ const [morphTargetDictionary, setMorphTargetDictionary] = useState<
64
+ Record<string, number>
65
+ >({});
66
+ const [emotionMorphTargets, setEmotionMorphTargets] = useState<
67
+ Record<string, number>
68
+ >({});
61
69
  const [isRPM, setIsRPM] = useState(false);
62
70
  const [timeScale, setTimeScale] = useState(0.8);
63
71
 
@@ -71,73 +79,92 @@ export const AvatarView: React.FC<Props & { halfBody: boolean }> = ({
71
79
  };
72
80
 
73
81
  // Helper function to get default emotion state (all set to 0)
74
- const getDefaultEmotions = () =>
75
- Object.keys(emotionMap).reduce((acc, key) => ({...acc, [key]: 0}), {});
82
+ const getDefaultEmotions = () =>
83
+ Object.keys(emotionMap).reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
76
84
 
77
85
  // Handlers for different blend shape types
78
- const handleRPMBlendShape = useCallback((outputContent: string) =>
79
- MAPPING_BLEND_SHAPE_TO_EMOTION_RPM[outputContent as keyof typeof MAPPING_BLEND_SHAPE_TO_EMOTION_RPM],
80
- []);
86
+ const handleRPMBlendShape = useCallback(
87
+ (outputContent: string) =>
88
+ MAPPING_BLEND_SHAPE_TO_EMOTION_RPM[
89
+ outputContent as keyof typeof MAPPING_BLEND_SHAPE_TO_EMOTION_RPM
90
+ ],
91
+ []
92
+ );
81
93
 
82
- const handleCustomGLBBlendShape = useCallback((outputContent: string) =>
83
- MAPPING_BLEND_SHAPE_TO_EMOTION_CUSTOM_GLB[outputContent as keyof typeof MAPPING_BLEND_SHAPE_TO_EMOTION_CUSTOM_GLB],
84
- []);
94
+ const handleCustomGLBBlendShape = useCallback(
95
+ (outputContent: string) =>
96
+ MAPPING_BLEND_SHAPE_TO_EMOTION_CUSTOM_GLB[
97
+ outputContent as keyof typeof MAPPING_BLEND_SHAPE_TO_EMOTION_CUSTOM_GLB
98
+ ],
99
+ []
100
+ );
85
101
 
86
102
  // Handler for setting emotion morph target influences, used for RPM and GLB blend shapes
87
- const setEmotionMorphTargetInfluences = useCallback((action: string, outputContent: string) => {
88
- if (action.startsWith('Loading')) return;
103
+ const setEmotionMorphTargetInfluences = useCallback(
104
+ (action: string, outputContent: string) => {
105
+ if (action.startsWith('Loading')) return;
89
106
 
90
- const defaultEmotions = getDefaultEmotions();
107
+ const defaultEmotions = getDefaultEmotions();
91
108
 
92
- // If output content is default, set default emotions
93
- if (outputContent === 'default') {
94
- setEmotionMorphTargets(defaultEmotions);
95
- return;
96
- }
109
+ // If output content is default, set default emotions
110
+ if (outputContent === 'default') {
111
+ setEmotionMorphTargets(defaultEmotions);
112
+ return;
113
+ }
97
114
 
98
- // If RPM, convert emotion to blend shape
99
- /*from the chat output, we get the emotion and we convert it to the blend shapes
100
- * we map the emotion to the blend shape, example:
101
- * Anger -> {browDownLeft: 1, browDownRight: 0}
102
- * Joy -> {browUpLeft: 1, browUpRight: 0}
103
- * Surprise -> {browUpLeft: 1, browUpRight: 0}
104
- * Sadness -> {browDownLeft: 1, browDownRight: 0}
105
- * Fear -> {browDownLeft: 1, browDownRight: 0}
106
- */
107
- if (isRPM) {
108
- const emotion = handleRPMBlendShape(outputContent);
109
- setEmotionMorphTargets((_) => ({...defaultEmotions, ...emotion}));
110
- } else {
111
- // If GLB, convert italian emotions to english ones
112
- const emotion = handleCustomGLBBlendShape(outputContent);
113
- const emotionValues = emotion === 'default' ? defaultEmotions : emotionMap[emotion];
114
- setEmotionMorphTargets((_) => ({...defaultEmotions, ...emotionValues}));
115
- }
116
- }, [isRPM, handleRPMBlendShape, handleCustomGLBBlendShape]);
115
+ // If RPM, convert emotion to blend shape
116
+ /*from the chat output, we get the emotion and we convert it to the blend shapes
117
+ * we map the emotion to the blend shape, example:
118
+ * Anger -> {browDownLeft: 1, browDownRight: 0}
119
+ * Joy -> {browUpLeft: 1, browUpRight: 0}
120
+ * Surprise -> {browUpLeft: 1, browUpRight: 0}
121
+ * Sadness -> {browDownLeft: 1, browDownRight: 0}
122
+ * Fear -> {browDownLeft: 1, browDownRight: 0}
123
+ */
124
+ if (isRPM) {
125
+ const emotion = handleRPMBlendShape(outputContent);
126
+ setEmotionMorphTargets(_ => ({ ...defaultEmotions, ...emotion }));
127
+ } else {
128
+ // If GLB, convert italian emotions to english ones
129
+ const emotion = handleCustomGLBBlendShape(outputContent);
130
+ const emotionValues =
131
+ emotion === 'default' ? defaultEmotions : emotionMap[emotion];
132
+ setEmotionMorphTargets(_ => ({ ...defaultEmotions, ...emotionValues }));
133
+ }
134
+ },
135
+ [isRPM, handleRPMBlendShape, handleCustomGLBBlendShape]
136
+ );
117
137
 
118
138
  // Callback handlers for various avatar state changes
119
- const onBaseActionChange = useCallback((action: string, outputContent: string) => {
139
+ const onBaseActionChange = useCallback(
140
+ (action: string, outputContent: string) => {
141
+ // Set emotion morph target influences
142
+ setEmotionMorphTargetInfluences(action, outputContent);
120
143
 
121
- // Set emotion morph target influences
122
- setEmotionMorphTargetInfluences(action, outputContent);
123
-
124
- // Set current base action
125
- setCurrentBaseAction({action, weight: 1});
126
- }, [setEmotionMorphTargetInfluences]);
144
+ // Set current base action
145
+ setCurrentBaseAction({ action, weight: 1 });
146
+ },
147
+ [setEmotionMorphTargetInfluences]
148
+ );
127
149
 
128
- const onMorphTargetInfluencesChange = useCallback((influences: Record<string, number>) => {
129
- // Set morph target influences
130
- setMorphTargetInfluences(prev => ({...prev, ...influences}));
131
- }, []);
150
+ const onMorphTargetInfluencesChange = useCallback(
151
+ (influences: Record<string, number>) => {
152
+ // Set morph target influences
153
+ setMorphTargetInfluences(prev => ({ ...prev, ...influences }));
154
+ },
155
+ []
156
+ );
132
157
 
133
- const onMorphTargetDictionaryChange = useCallback((dictionary: Record<string, number>) => {
134
- // Set morph target dictionary
135
- setMorphTargetDictionary(dictionary);
136
- }, []);
158
+ const onMorphTargetDictionaryChange = useCallback(
159
+ (dictionary: Record<string, number>) => {
160
+ // Set morph target dictionary
161
+ setMorphTargetDictionary(dictionary);
162
+ },
163
+ []
164
+ );
137
165
 
138
166
  // Effect to handle animation changes based on loading state and chat emissions
139
167
  useEffect(() => {
140
-
141
168
  // If loading, set a random loading animation
142
169
  if (loading) {
143
170
  const randomNumber = Math.floor(Math.random() * 3) + 1;
@@ -145,16 +172,40 @@ export const AvatarView: React.FC<Props & { halfBody: boolean }> = ({
145
172
  return;
146
173
  }
147
174
 
148
- // If there's chat emission, set the corresponding emotion animation
149
- const hasOutputTag = chatEmission?.includes('<output class="memori-emotion">');
150
- const outputContent = hasOutputTag
151
- ? chatEmission?.split('<output class="memori-emotion">')[1]?.split('</output>')[0]?.trim()
175
+ // Check if chat emission contains animation control
176
+ const hasOutputTagEmotion = chatEmission?.includes(
177
+ '<output class="memori-emotion">'
178
+ );
179
+ const outputContentEmotion = hasOutputTagEmotion
180
+ ? chatEmission
181
+ ?.split('<output class="memori-emotion">')[1]
182
+ ?.split('</output>')[0]
183
+ ?.trim()
184
+ : null;
185
+
186
+ // Check if chat emission contains animation sequence
187
+ const hasOutputTagSequence = chatEmission?.includes(
188
+ '<output class="animation-sequence">'
189
+ );
190
+ const outputContentSequence = hasOutputTagSequence
191
+ ? chatEmission
192
+ ?.split('<output class="animation-sequence">')[1]
193
+ ?.split('</output>')[0]
194
+ ?.trim()
152
195
  : null;
153
196
 
154
- // If there's an emotion, set the corresponding animation
155
- if (outputContent) {
197
+ if (outputContentSequence && outputContentSequence.includes('->')) {
198
+ // It's a sequence
199
+ onBaseActionChange(outputContentSequence, outputContentSequence);
200
+ } else if (outputContentEmotion) {
201
+
202
+ console.log('[AvatarView] outputContentEmotion:', outputContentEmotion);
203
+ // It's an emotion
156
204
  const randomNumber = Math.floor(Math.random() * 3) + 1;
157
- onBaseActionChange(`${outputContent}${randomNumber}`, outputContent);
205
+ onBaseActionChange(
206
+ `${outputContentEmotion}${randomNumber}`,
207
+ outputContentEmotion
208
+ );
158
209
  } else {
159
210
  const randomNumber = Math.floor(Math.random() * 5) + 1;
160
211
  onBaseActionChange(`Idle${randomNumber === 3 ? 4 : randomNumber}`, '');
@@ -187,12 +238,9 @@ export const AvatarView: React.FC<Props & { halfBody: boolean }> = ({
187
238
  modifyTimeScale={setTimeScale}
188
239
  />
189
240
  )}
190
-
241
+
191
242
  {halfBody ? (
192
- <HalfBodyAvatar
193
- {...commonAvatarProps}
194
- headMovement={headMovement}
195
- />
243
+ <HalfBodyAvatar {...commonAvatarProps} headMovement={headMovement} />
196
244
  ) : (
197
245
  <FullbodyAvatar
198
246
  {...commonAvatarProps}
@@ -8,9 +8,9 @@ import {
8
8
  import { useAnimations, useGLTF } from '@react-three/drei';
9
9
  import { useFrame } from '@react-three/fiber';
10
10
  import { AnimationState, FullbodyAvatarProps } from './types';
11
- import { AnimationController } from './AnimationController';
12
- import { MorphTargetController } from '../MorphTargetController';
13
- import { AvatarPositionController } from '../PositionController';
11
+ import { AnimationController } from '../controllers/AnimationController';
12
+ import { MorphTargetController } from '../controllers/MorphTargetController';
13
+ import { AvatarPositionController } from '../controllers/AvatarPositionController';
14
14
  import {
15
15
  AVATAR_POSITION,
16
16
  AVATAR_ROTATION,
@@ -152,6 +152,8 @@ export function FullbodyAvatar({
152
152
  AnimationState.LOADING,
153
153
  currentBaseAction.action
154
154
  );
155
+ } else if (currentBaseAction.action.includes('->')) {
156
+ animationControllerRef.current.playSequence(currentBaseAction.action);
155
157
  } else if (currentBaseAction.action.startsWith('Idle')) {
156
158
  animationControllerRef.current.transitionTo(AnimationState.IDLE);
157
159
  } else {
@@ -1,4 +1,4 @@
1
- import { AnimationState, AnimationConfig } from './types';
1
+ import { AnimationState, AnimationConfig } from '../FullbodyAvatar/types';
2
2
  import { AnimationAction, AnimationMixer, LoopOnce } from 'three';
3
3
  import { DEFAULT_CONFIG, MAX_IDLE_LOOPS_DEFAULT } from '../../constants';
4
4
 
@@ -28,6 +28,10 @@ export class AnimationController {
28
28
  private lastAnimationTime: number = 0;
29
29
  // Flag to check if chat has already started
30
30
  private isChatAlreadyStarted: boolean = false;
31
+ // Sequence of animations
32
+ private sequence: string[] | null = null;
33
+ // Index of current animation in sequence
34
+ private sequenceIndex: number = 0;
31
35
 
32
36
  constructor(
33
37
  mixer: AnimationMixer,
@@ -108,6 +112,33 @@ export class AnimationController {
108
112
  return idleAction;
109
113
  }
110
114
 
115
+ /**
116
+ * Plays a sequence of animations
117
+ */
118
+ playSequence(sequenceString: string) {
119
+ const animations = sequenceString.split('->').map(anim => anim.trim());
120
+ console.log('[AnimationController] playSequence:', animations);
121
+
122
+ this.sequence = animations;
123
+ this.sequenceIndex = 0;
124
+
125
+ const firstAnim = this.parseAnimationName(animations[0]);
126
+ this.transitionTo(AnimationState.EMOTION, firstAnim);
127
+ }
128
+
129
+ // Animation Name Parsing
130
+ private parseAnimationName(animation: string): string {
131
+ const randomMatch = animation.match(/(\w+)\[(\d+),(\d+)\]/);
132
+ if (randomMatch) {
133
+ const [_, base, min, max] = randomMatch;
134
+ const random =
135
+ Math.floor(Math.random() * (Number(max) - Number(min) + 1)) +
136
+ Number(min);
137
+ return `${base}${random}`;
138
+ }
139
+ return animation;
140
+ }
141
+
111
142
  /**
112
143
  * Transitions to a new animation state
113
144
  */
@@ -197,6 +228,26 @@ export class AnimationController {
197
228
  // Check for loop completion in idle animations
198
229
  this.checkForLoop();
199
230
 
231
+ // Sequence progression,
232
+ // If sequence is playing, transition to the next animation in the sequence
233
+ // If sequence is finished, transition to idle
234
+ if (
235
+ this.sequence &&
236
+ this.currentState === AnimationState.EMOTION &&
237
+ this.currentAction.time >= this.currentAction.getClip().duration * 0.9
238
+ ) {
239
+ this.sequenceIndex++;
240
+
241
+ if (this.sequenceIndex < this.sequence.length) {
242
+ const nextAnim = this.parseAnimationName(this.sequence[this.sequenceIndex]);
243
+ this.transitionTo(AnimationState.EMOTION, nextAnim);
244
+ } else {
245
+ this.sequence = null;
246
+ this.sequenceIndex = 0;
247
+ this.transitionTo(AnimationState.IDLE);
248
+ }
249
+ }
250
+
200
251
  // Check if emotion/loading animation is finished
201
252
  if (
202
253
  this.currentState !== AnimationState.IDLE &&
@@ -1,5 +1,5 @@
1
1
  import { Vector3, MathUtils } from 'three';
2
- import { AVATAR_POSITION, AVATAR_POSITION_ZOOMED } from '../constants';
2
+ import { AVATAR_POSITION, AVATAR_POSITION_ZOOMED } from '../../constants';
3
3
 
4
4
  export class AvatarPositionController {
5
5
  private currentScale: Vector3;
@@ -1,6 +1,6 @@
1
1
  import { SkinnedMesh } from 'three';
2
2
  import { MathUtils } from 'three';
3
- import { EMOTION_SMOOTHING, VISEME_SMOOTHING, BLINK_CONFIG } from '../constants';
3
+ import { EMOTION_SMOOTHING, VISEME_SMOOTHING, BLINK_CONFIG } from '../../constants';
4
4
 
5
5
  /**
6
6
  * Controller class for handling morph target animations including emotions, visemes and blinking
@@ -62,7 +62,6 @@ export class MorphTargetController {
62
62
  targetEmotionValue * 3, // Amplify emotion by 3x
63
63
  EMOTION_SMOOTHING
64
64
  );
65
- console.log(`[MorphTargetController] Emotion ${key}: current=${currentEmotionValue}, target=${targetEmotionValue}, new=${newEmotionValue}`);
66
65
  this.currentEmotionValues[key] = newEmotionValue;
67
66
  targetValue += newEmotionValue;
68
67
  }
@@ -3,8 +3,8 @@ import { Object3D, SkinnedMesh } from 'three';
3
3
  import { useGLTF } from '@react-three/drei';
4
4
  import { useGraph, useFrame, useThree } from '@react-three/fiber';
5
5
  import { correctMaterials, isSkinnedMesh } from '../../../../../helpers/utils';
6
- import { MorphTargetController } from './MorphTargetController';
7
- import { AvatarPositionController } from './PositionController';
6
+ import { MorphTargetController } from './controllers/MorphTargetController';
7
+ import { AvatarPositionController } from './controllers/AvatarPositionController';
8
8
  import {
9
9
  AVATAR_POSITION,
10
10
  SCALE_LERP_FACTOR,
@@ -166,6 +166,22 @@ GiovannaGLBProva.args = {
166
166
  layout: 'ZOOMED_FULL_BODY',
167
167
  };
168
168
 
169
+ export const MoodChefAssistant = Template.bind({});
170
+ MoodChefAssistant.args = {
171
+ memoriName: 'Mood Chef Assistant',
172
+ ownerUserName: 'andrea.patini3',
173
+ memoriID: 'fb4f9251-e7ec-4002-b0e5-ffa6e75b2fd8',
174
+ ownerUserID: '58770358-a5db-4b49-b3a4-734fc468e745',
175
+ tenantID: 'aisuru-staging.aclambda.online',
176
+ engineURL: 'https://engine-staging-tmp.memori.ai',
177
+ apiURL: 'https://backend-staging.memori.ai',
178
+ baseURL: 'https://aisuru-staging.aclambda.online',
179
+ uiLang: 'EN',
180
+ spokenLang: 'IT',
181
+ layout: 'ZOOMED_FULL_BODY',
182
+ integrationID: '6e289cc1-df99-4879-8a3a-fe10baea7eac',
183
+ };
184
+
169
185
  export const GiovannaProvaWithPreviousSession = Template.bind({});
170
186
  GiovannaProvaWithPreviousSession.args = {
171
187
  memoriName: 'Giovanna Test',
@@ -1 +0,0 @@
1
- {"version":3,"file":"MorhTargetController.js","sourceRoot":"","sources":["../../../../../../../src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/MorhTargetController.ts"],"names":[],"mappings":"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"}
@@ -1,17 +0,0 @@
1
- import { Vector3, Euler } from 'three';
2
- import { AnimationConfig } from './types';
3
- export declare const AVATAR_POSITION: Vector3;
4
- export declare const AVATAR_ROTATION: Euler;
5
- export declare const AVATAR_POSITION_ZOOMED: Vector3;
6
- export declare const ANIMATION_URLS: {
7
- MALE: string;
8
- FEMALE: string;
9
- };
10
- export declare const BLINK_CONFIG: {
11
- minInterval: number;
12
- maxInterval: number;
13
- blinkDuration: number;
14
- };
15
- export declare const DEFAULT_CONFIG: AnimationConfig;
16
- export declare const EMOTION_SMOOTHING = 0.3;
17
- export declare const VISEME_SMOOTHING = 0.5;
@@ -1,25 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VISEME_SMOOTHING = exports.EMOTION_SMOOTHING = exports.DEFAULT_CONFIG = exports.BLINK_CONFIG = exports.ANIMATION_URLS = exports.AVATAR_POSITION_ZOOMED = exports.AVATAR_ROTATION = exports.AVATAR_POSITION = void 0;
4
- const three_1 = require("three");
5
- exports.AVATAR_POSITION = new three_1.Vector3(0, -1, 0);
6
- exports.AVATAR_ROTATION = new three_1.Euler(0.175, 0, 0);
7
- exports.AVATAR_POSITION_ZOOMED = new three_1.Vector3(0, -1.45, 0);
8
- exports.ANIMATION_URLS = {
9
- MALE: 'https://assets.memori.ai/api/v2/asset/2c5e88a4-cf62-408b-9ef0-518b099dfcb2.glb',
10
- FEMALE: 'https://assets.memori.ai/api/v2/asset/2adc934b-24b2-45bd-94ad-ffec58d3cb32.glb',
11
- };
12
- exports.BLINK_CONFIG = {
13
- minInterval: 1000,
14
- maxInterval: 5000,
15
- blinkDuration: 150,
16
- };
17
- exports.DEFAULT_CONFIG = {
18
- fadeInDuration: 0.8,
19
- fadeOutDuration: 0.8,
20
- idleCount: 5,
21
- timeScale: 1.0,
22
- };
23
- exports.EMOTION_SMOOTHING = 0.3;
24
- exports.VISEME_SMOOTHING = 0.5;
25
- //# sourceMappingURL=constants.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../../../../src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/constants.ts"],"names":[],"mappings":";;;AAAA,iCAAuC;AAG1B,QAAA,eAAe,GAAG,IAAI,eAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACxC,QAAA,eAAe,GAAG,IAAI,aAAK,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACzC,QAAA,sBAAsB,GAAG,IAAI,eAAO,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAElD,QAAA,cAAc,GAAG;IAC5B,IAAI,EAAE,gFAAgF;IACtF,MAAM,EACJ,gFAAgF;CACnF,CAAC;AAEW,QAAA,YAAY,GAAG;IAC1B,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,IAAI;IACjB,aAAa,EAAE,GAAG;CACnB,CAAC;AAEW,QAAA,cAAc,GAAoB;IAC7C,cAAc,EAAE,GAAG;IACnB,eAAe,EAAE,GAAG;IACpB,SAAS,EAAE,CAAC;IACZ,SAAS,EAAE,GAAG;CACf,CAAC;AAEW,QAAA,iBAAiB,GAAG,GAAG,CAAC;AACxB,QAAA,gBAAgB,GAAG,GAAG,CAAC"}
@@ -1,26 +0,0 @@
1
- interface FullbodyAvatarProps {
2
- url: string;
3
- sex: 'MALE' | 'FEMALE';
4
- onLoaded?: () => void;
5
- currentBaseAction: {
6
- action: string;
7
- weight: number;
8
- };
9
- timeScale: number;
10
- isZoomed?: boolean;
11
- eyeBlink?: boolean;
12
- stopProcessing: () => void;
13
- resetVisemeQueue: () => void;
14
- updateCurrentViseme: (currentTime: number) => {
15
- name: string;
16
- weight: number;
17
- } | null;
18
- smoothMorphTarget?: boolean;
19
- morphTargetSmoothing?: number;
20
- morphTargetInfluences: Record<string, number>;
21
- setMorphTargetDictionary: (morphTargetDictionary: Record<string, number>) => void;
22
- setMorphTargetInfluences: (morphTargetInfluences: Record<string, number>) => void;
23
- emotionMorphTargets: Record<string, number>;
24
- }
25
- export default function FullbodyAvatar({ url, sex, currentBaseAction, timeScale, isZoomed, eyeBlink, updateCurrentViseme, setMorphTargetDictionary, setMorphTargetInfluences, emotionMorphTargets, }: FullbodyAvatarProps): JSX.Element;
26
- export {};
@@ -1,166 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const jsx_runtime_1 = require("react/jsx-runtime");
4
- const react_1 = require("react");
5
- const three_1 = require("three");
6
- const drei_1 = require("@react-three/drei");
7
- const fiber_1 = require("@react-three/fiber");
8
- const AVATAR_POSITION = new three_1.Vector3(0, -1, 0);
9
- const AVATAR_ROTATION = new three_1.Euler(0.175, 0, 0);
10
- const AVATAR_POSITION_ZOOMED = new three_1.Vector3(0, -1.45, 0);
11
- const ANIMATION_URLS = {
12
- MALE: 'https://assets.memori.ai/api/v2/asset/2c5e88a4-cf62-408b-9ef0-518b099dfcb2.glb',
13
- FEMALE: 'https://assets.memori.ai/api/v2/asset/8d1a5853-f05a-4a34-9f99-6eff64986081.glb',
14
- };
15
- const BLINK_CONFIG = {
16
- minInterval: 1000,
17
- maxInterval: 5000,
18
- blinkDuration: 150,
19
- };
20
- const EMOTION_SMOOTHING = 0.3;
21
- const VISME_SMOOTHING = 0.5;
22
- function FullbodyAvatar({ url, sex, currentBaseAction, timeScale, isZoomed, eyeBlink, updateCurrentViseme, setMorphTargetDictionary, setMorphTargetInfluences, emotionMorphTargets, }) {
23
- const { scene } = (0, drei_1.useGLTF)(url);
24
- const { animations } = (0, drei_1.useGLTF)(ANIMATION_URLS[sex]);
25
- const { actions } = (0, drei_1.useAnimations)(animations, scene);
26
- const mixerRef = (0, react_1.useRef)();
27
- const headMeshRef = (0, react_1.useRef)();
28
- const currentActionRef = (0, react_1.useRef)(null);
29
- const isTransitioningToIdleRef = (0, react_1.useRef)(false);
30
- const lastBlinkTimeRef = (0, react_1.useRef)(0);
31
- const nextBlinkTimeRef = (0, react_1.useRef)(0);
32
- const isBlinkingRef = (0, react_1.useRef)(false);
33
- const blinkStartTimeRef = (0, react_1.useRef)(0);
34
- const currentEmotionRef = (0, react_1.useRef)({});
35
- const previousEmotionKeysRef = (0, react_1.useRef)(new Set());
36
- const headMesh = (0, react_1.useMemo)(() => {
37
- let foundMesh;
38
- scene.traverse((object) => {
39
- if (object instanceof three_1.SkinnedMesh &&
40
- (object.name === 'GBNL__Head' || object.name === 'Wolf3D_Avatar')) {
41
- foundMesh = object;
42
- }
43
- });
44
- return foundMesh;
45
- }, [scene]);
46
- (0, react_1.useEffect)(() => {
47
- if (headMesh) {
48
- headMeshRef.current = headMesh;
49
- if (headMesh.morphTargetDictionary && headMesh.morphTargetInfluences) {
50
- setMorphTargetDictionary(headMesh.morphTargetDictionary);
51
- const initialInfluences = Object.keys(headMesh.morphTargetDictionary).reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
52
- setMorphTargetInfluences(initialInfluences);
53
- }
54
- }
55
- mixerRef.current = new three_1.AnimationMixer(scene);
56
- }, [headMesh, scene, setMorphTargetDictionary, setMorphTargetInfluences]);
57
- const handleAnimationChange = (0, react_1.useCallback)(() => {
58
- if (!actions || !currentBaseAction.action)
59
- return;
60
- const newAction = actions[currentBaseAction.action];
61
- if (!newAction) {
62
- console.warn(`Animation "${currentBaseAction.action}" not found in actions.`);
63
- return;
64
- }
65
- const fadeOutDuration = 0.8;
66
- const fadeInDuration = 0.8;
67
- if (currentActionRef.current) {
68
- currentActionRef.current.fadeOut(fadeOutDuration);
69
- }
70
- newAction.reset().fadeIn(fadeInDuration).play();
71
- currentActionRef.current = newAction;
72
- newAction.timeScale = timeScale;
73
- if (currentBaseAction.action.startsWith('Gioia') ||
74
- currentBaseAction.action.startsWith('Rabbia') ||
75
- currentBaseAction.action.startsWith('Sorpresa') ||
76
- currentBaseAction.action.startsWith('Timore') ||
77
- currentBaseAction.action.startsWith('Tristezza')) {
78
- newAction.setLoop(three_1.LoopOnce, 1);
79
- newAction.clampWhenFinished = true;
80
- isTransitioningToIdleRef.current = true;
81
- }
82
- }, [actions, currentBaseAction, timeScale]);
83
- (0, react_1.useEffect)(() => {
84
- handleAnimationChange();
85
- }, [handleAnimationChange]);
86
- const updateFrame = (0, react_1.useCallback)((currentTime) => {
87
- var _a;
88
- if (!headMeshRef.current ||
89
- !headMeshRef.current.morphTargetDictionary ||
90
- !headMeshRef.current.morphTargetInfluences)
91
- return;
92
- let blinkValue = 0;
93
- if (eyeBlink) {
94
- if (currentTime >= nextBlinkTimeRef.current && !isBlinkingRef.current) {
95
- isBlinkingRef.current = true;
96
- blinkStartTimeRef.current = currentTime;
97
- lastBlinkTimeRef.current = currentTime;
98
- nextBlinkTimeRef.current =
99
- currentTime +
100
- Math.random() *
101
- (BLINK_CONFIG.maxInterval - BLINK_CONFIG.minInterval) +
102
- BLINK_CONFIG.minInterval;
103
- }
104
- if (isBlinkingRef.current) {
105
- const blinkProgress = (currentTime - blinkStartTimeRef.current) /
106
- BLINK_CONFIG.blinkDuration;
107
- if (blinkProgress <= 0.5) {
108
- blinkValue = blinkProgress * 2;
109
- }
110
- else if (blinkProgress <= 1) {
111
- blinkValue = 2 - blinkProgress * 2;
112
- }
113
- else {
114
- isBlinkingRef.current = false;
115
- blinkValue = 0;
116
- }
117
- }
118
- }
119
- const currentViseme = updateCurrentViseme(currentTime / 1000);
120
- const currentEmotionKeys = new Set(Object.keys(emotionMorphTargets));
121
- Object.entries(headMeshRef.current.morphTargetDictionary).forEach(([key, index]) => {
122
- if (typeof index === 'number') {
123
- let targetValue = 0;
124
- if (currentEmotionKeys.has(key)) {
125
- const targetEmotionValue = emotionMorphTargets[key];
126
- const currentEmotionValue = currentEmotionRef.current[key] || 0;
127
- const newEmotionValue = three_1.MathUtils.lerp(currentEmotionValue, targetEmotionValue * 2.5, EMOTION_SMOOTHING);
128
- currentEmotionRef.current[key] = newEmotionValue;
129
- targetValue += newEmotionValue;
130
- }
131
- if (currentViseme && key === currentViseme.name) {
132
- targetValue += currentViseme.weight;
133
- }
134
- if (key === 'eyesClosed' && eyeBlink) {
135
- targetValue += blinkValue;
136
- }
137
- targetValue = three_1.MathUtils.clamp(targetValue, 0, 1);
138
- if (headMeshRef.current &&
139
- headMeshRef.current.morphTargetInfluences) {
140
- headMeshRef.current.morphTargetInfluences[index] = three_1.MathUtils.lerp(headMeshRef.current.morphTargetInfluences[index], targetValue, VISME_SMOOTHING);
141
- }
142
- }
143
- });
144
- previousEmotionKeysRef.current = currentEmotionKeys;
145
- if (isTransitioningToIdleRef.current && currentActionRef.current) {
146
- if (currentActionRef.current.time >=
147
- currentActionRef.current.getClip().duration) {
148
- const idleNumber = Math.floor(Math.random() * 5) + 1;
149
- const idleAction = actions[`Idle${idleNumber === 3 ? 4 : idleNumber}`];
150
- if (idleAction) {
151
- currentActionRef.current.fadeOut(0.5);
152
- idleAction.reset().fadeIn(0.5).play();
153
- currentActionRef.current = idleAction;
154
- isTransitioningToIdleRef.current = false;
155
- }
156
- }
157
- }
158
- (_a = mixerRef.current) === null || _a === void 0 ? void 0 : _a.update(0.01);
159
- }, [actions, emotionMorphTargets, eyeBlink, updateCurrentViseme]);
160
- (0, fiber_1.useFrame)(state => {
161
- updateFrame(state.clock.elapsedTime * 1000);
162
- });
163
- return ((0, jsx_runtime_1.jsx)("group", { position: isZoomed ? AVATAR_POSITION_ZOOMED : AVATAR_POSITION, rotation: AVATAR_ROTATION, children: (0, jsx_runtime_1.jsx)("primitive", { object: scene }) }));
164
- }
165
- exports.default = FullbodyAvatar;
166
- //# sourceMappingURL=fullbodyAvatar.js.map