@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.
- package/CHANGELOG.md +57 -0
- package/dist/components/Avatar/Avatar.js +3 -3
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
- package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
- package/dist/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +19 -77
- package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +95 -70
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +747 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +60 -2
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
- package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
- package/dist/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
- package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js +68 -70
- package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
- package/dist/components/Avatar/AvatarView/index.d.ts +1 -1
- package/dist/components/Avatar/AvatarView/index.js +2 -2
- package/dist/components/Avatar/AvatarView/index.js.map +1 -1
- package/dist/components/Chat/Chat.js +2 -2
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatBubble/ChatBubble.js +12 -9
- package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
- package/dist/components/ExpertsDrawer/ExpertsDrawer.js +1 -1
- package/dist/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
- package/dist/components/LoginDrawer/LoginDrawer.js +6 -6
- package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -1
- package/dist/components/MemoriWidget/MemoriWidget.js +143 -64
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/components/SignupForm/SignupForm.js +4 -4
- package/dist/components/SignupForm/SignupForm.js.map +1 -1
- package/dist/components/StartPanel/StartPanel.js +5 -5
- package/dist/components/StartPanel/StartPanel.js.map +1 -1
- package/dist/components/UploadButton/UploadButton.js +2 -2
- package/dist/components/UploadButton/UploadButton.js.map +1 -1
- package/dist/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
- package/dist/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
- package/dist/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
- package/dist/context/visemeContext.js +0 -39
- package/dist/context/visemeContext.js.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/locales/de.json +1 -0
- package/dist/locales/en.json +1 -0
- package/dist/locales/es.json +1 -0
- package/dist/locales/fr.json +1 -0
- package/dist/locales/it.json +1 -0
- package/esm/components/Avatar/Avatar.js +3 -3
- package/esm/components/Avatar/Avatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.d.ts +3 -2
- package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js +13 -6
- package/esm/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +14 -18
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +20 -78
- package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.d.ts +17 -2
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +99 -74
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.d.ts +65 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js +743 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.js.map +1 -0
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.d.ts +9 -2
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +61 -3
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +3 -4
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +5 -11
- package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -1
- package/esm/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +13 -52
- package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js +67 -69
- package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
- package/esm/components/Avatar/AvatarView/index.d.ts +1 -1
- package/esm/components/Avatar/AvatarView/index.js +2 -2
- package/esm/components/Avatar/AvatarView/index.js.map +1 -1
- package/esm/components/Chat/Chat.js +2 -2
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatBubble/ChatBubble.js +12 -9
- package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
- package/esm/components/ExpertsDrawer/ExpertsDrawer.js +1 -1
- package/esm/components/ExpertsDrawer/ExpertsDrawer.js.map +1 -1
- package/esm/components/LoginDrawer/LoginDrawer.js +6 -6
- package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.js +143 -64
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/components/SignupForm/SignupForm.js +4 -4
- package/esm/components/SignupForm/SignupForm.js.map +1 -1
- package/esm/components/StartPanel/StartPanel.js +5 -5
- package/esm/components/StartPanel/StartPanel.js.map +1 -1
- package/esm/components/UploadButton/UploadButton.js +2 -2
- package/esm/components/UploadButton/UploadButton.js.map +1 -1
- package/esm/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
- package/esm/components/WhyThisAnswer/WhyThisAnswer.js +2 -1
- package/esm/components/WhyThisAnswer/WhyThisAnswer.js.map +1 -1
- package/esm/context/visemeContext.js +0 -39
- package/esm/context/visemeContext.js.map +1 -1
- package/esm/index.js +4 -3
- package/esm/index.js.map +1 -1
- package/esm/locales/de.json +1 -0
- package/esm/locales/en.json +1 -0
- package/esm/locales/es.json +1 -0
- package/esm/locales/fr.json +1 -0
- package/esm/locales/it.json +1 -0
- package/package.json +2 -2
- package/src/components/Avatar/Avatar.tsx +3 -3
- package/src/components/Avatar/AvatarView/AvatarComponent/Shadow/DynamicShadow.tsx +15 -8
- package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +64 -219
- package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +221 -124
- package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AvatarAnimator.ts +1250 -0
- package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +164 -8
- package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +19 -17
- package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +80 -79
- package/src/components/Avatar/AvatarView/index.tsx +1 -7
- package/src/components/Chat/Chat.tsx +2 -2
- package/src/components/ChatBubble/ChatBubble.tsx +37 -26
- package/src/components/ExpertsDrawer/ExpertsDrawer.tsx +1 -1
- package/src/components/LoginDrawer/LoginDrawer.tsx +6 -6
- package/src/components/MemoriWidget/MemoriWidget.tsx +184 -78
- package/src/components/SignupForm/SignupForm.tsx +5 -5
- package/src/components/StartPanel/StartPanel.tsx +5 -5
- package/src/components/UploadButton/UploadButton.tsx +4 -4
- package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +1 -1
- package/src/components/WhyThisAnswer/WhyThisAnswer.css +43 -0
- package/src/components/WhyThisAnswer/WhyThisAnswer.stories.tsx +44 -3
- package/src/components/WhyThisAnswer/WhyThisAnswer.test.tsx +128 -8
- package/src/components/WhyThisAnswer/WhyThisAnswer.tsx +28 -3
- package/src/components/WhyThisAnswer/__snapshots__/WhyThisAnswer.test.tsx.snap +15 -1
- package/src/components/layouts/layouts.stories.tsx +0 -8
- package/src/context/visemeContext.tsx +40 -41
- package/src/index.stories.tsx +63 -65
- package/src/index.tsx +5 -3
- package/src/locales/de.json +1 -0
- package/src/locales/en.json +1 -0
- package/src/locales/es.json +1 -0
- package/src/locales/fr.json +1 -0
- package/src/locales/it.json +1 -0
- package/src/mocks/data.ts +3 -9
- package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/AnimationController.ts +0 -308
- package/src/helpers/tenant.ts +0 -47
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { SkinnedMesh } from 'three';
|
|
2
2
|
import { MathUtils } from 'three';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
EMOTION_SMOOTHING,
|
|
5
|
+
VISEME_SMOOTHING,
|
|
6
|
+
BLINK_CONFIG,
|
|
7
|
+
MAPPING_EMOTIONS_ITALIAN_TO_ENGLISH,
|
|
8
|
+
MAPPING_BLEND_SHAPE_TO_EMOTION_RPM,
|
|
9
|
+
EmotionMapping,
|
|
10
|
+
BlendShapeMap,
|
|
11
|
+
} from '../../constants';
|
|
4
12
|
|
|
5
13
|
/**
|
|
6
14
|
* Controller class for handling morph target animations including emotions, visemes and blinking
|
|
@@ -9,16 +17,155 @@ export class MorphTargetController {
|
|
|
9
17
|
private headMesh: SkinnedMesh;
|
|
10
18
|
private currentEmotionValues: Record<string, number> = {};
|
|
11
19
|
private previousEmotionKeys: Set<string> = new Set();
|
|
20
|
+
private isRPM: boolean = false;
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
// Default RPM blend shape mappings
|
|
23
|
+
private rpmBlendShapes: BlendShapeMap =
|
|
24
|
+
MAPPING_BLEND_SHAPE_TO_EMOTION_RPM as BlendShapeMap;
|
|
25
|
+
|
|
26
|
+
// Default custom GLB emotion mappings
|
|
27
|
+
private customGlbMapping: EmotionMapping =
|
|
28
|
+
MAPPING_EMOTIONS_ITALIAN_TO_ENGLISH as EmotionMapping;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Constructor for MorphTargetController
|
|
32
|
+
* @param headMesh SkinnedMesh representing the head
|
|
33
|
+
* @param rpmBlendShapes Optional custom RPM blend shapes
|
|
34
|
+
* @param customGlbMapping Optional custom GLB emotion mapping
|
|
35
|
+
*/
|
|
36
|
+
constructor(
|
|
37
|
+
headMesh: SkinnedMesh,
|
|
38
|
+
rpmBlendShapes?: BlendShapeMap,
|
|
39
|
+
customGlbMapping?: EmotionMapping
|
|
40
|
+
) {
|
|
14
41
|
this.headMesh = headMesh;
|
|
42
|
+
|
|
43
|
+
// Detect if this is an RPM avatar based on mesh name
|
|
44
|
+
this.isRPM = headMesh.name !== 'GBNL__Head';
|
|
45
|
+
|
|
46
|
+
// Override default maps if provided
|
|
47
|
+
if (rpmBlendShapes) {
|
|
48
|
+
this.rpmBlendShapes = rpmBlendShapes;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (customGlbMapping) {
|
|
52
|
+
this.customGlbMapping = customGlbMapping;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Process chat emission to extract emotion data
|
|
58
|
+
* @param chatEmission Chat emission text
|
|
59
|
+
* @param isLoading Loading state
|
|
60
|
+
* @returns Object with emotion morph targets
|
|
61
|
+
*/
|
|
62
|
+
processChatEmission(
|
|
63
|
+
chatEmission: any,
|
|
64
|
+
isLoading: boolean
|
|
65
|
+
): Record<string, number> {
|
|
66
|
+
// Default empty emotion targets
|
|
67
|
+
const defaultEmotions = this.getDefaultEmotionMorphTargets();
|
|
68
|
+
|
|
69
|
+
// If loading or no chat emission, return default emotions
|
|
70
|
+
if (isLoading || !chatEmission) {
|
|
71
|
+
return defaultEmotions;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if chat emission contains emotion tag
|
|
75
|
+
const hasOutputTagEmotion = chatEmission?.includes(
|
|
76
|
+
'<output class="memori-emotion">'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (!hasOutputTagEmotion) {
|
|
80
|
+
return defaultEmotions;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Extract emotion name
|
|
84
|
+
const outputContentEmotion = chatEmission
|
|
85
|
+
?.split('<output class="memori-emotion">')[1]
|
|
86
|
+
?.split('</output>')[0]
|
|
87
|
+
?.trim();
|
|
88
|
+
|
|
89
|
+
if (!outputContentEmotion) {
|
|
90
|
+
return defaultEmotions;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Process emotion based on avatar type
|
|
94
|
+
return this.processEmotion(outputContentEmotion);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Process an emotion name into morph target values
|
|
99
|
+
* @param emotionName Emotion name (in Italian or English)
|
|
100
|
+
* @returns Object with morph target values
|
|
101
|
+
*/
|
|
102
|
+
private processEmotion(emotionName: string): Record<string, number> {
|
|
103
|
+
// Get default empty emotion targets
|
|
104
|
+
const defaultEmotions = this.getDefaultEmotionMorphTargets();
|
|
105
|
+
|
|
106
|
+
// First, try to find the emotion regardless of language
|
|
107
|
+
if (this.isRPM) {
|
|
108
|
+
// For RPM avatars, find the matching emotion in the rpmBlendShapes array
|
|
109
|
+
const foundEmotion = this.rpmBlendShapes.find(
|
|
110
|
+
item =>
|
|
111
|
+
item.emotion.italian.toLowerCase() === emotionName.toLowerCase() ||
|
|
112
|
+
item.emotion.english.toLowerCase() === emotionName.toLowerCase()
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (foundEmotion) {
|
|
116
|
+
return { ...defaultEmotions, ...foundEmotion.blendShapes };
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
// For custom GLB, find the matching emotion in the customGlbMapping array
|
|
120
|
+
const foundEmotion = this.customGlbMapping.find(
|
|
121
|
+
item =>
|
|
122
|
+
item.italian.toLowerCase() === emotionName.toLowerCase() ||
|
|
123
|
+
item.english.toLowerCase() === emotionName.toLowerCase()
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (foundEmotion) {
|
|
127
|
+
return { ...defaultEmotions, [foundEmotion.english]: 1 };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log('[MorphTargetController] No emotion found:', emotionName);
|
|
132
|
+
return defaultEmotions;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get default emotion morph targets (all set to 0)
|
|
137
|
+
*/
|
|
138
|
+
private getDefaultEmotionMorphTargets(): Record<string, number> {
|
|
139
|
+
// For RPM, collect all blend shape keys
|
|
140
|
+
if (this.isRPM) {
|
|
141
|
+
const allBlendShapeKeys = new Set<string>();
|
|
142
|
+
|
|
143
|
+
this.rpmBlendShapes.forEach(item => {
|
|
144
|
+
Object.keys(item.blendShapes).forEach(key =>
|
|
145
|
+
allBlendShapeKeys.add(key)
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return Array.from(allBlendShapeKeys).reduce(
|
|
150
|
+
(acc, key) => ({ ...acc, [key]: 0 }),
|
|
151
|
+
{}
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
// For custom GLB, use English emotion names
|
|
155
|
+
else {
|
|
156
|
+
return this.customGlbMapping.reduce(
|
|
157
|
+
(acc, emotion) => ({ ...acc, [emotion.english]: 0 }),
|
|
158
|
+
{}
|
|
159
|
+
);
|
|
160
|
+
}
|
|
15
161
|
}
|
|
16
162
|
/**
|
|
17
163
|
* Updates the morph target influences for emotions, visemes and blinking
|
|
18
164
|
*/
|
|
19
165
|
updateMorphTargets(
|
|
20
166
|
currentTime: number,
|
|
21
|
-
|
|
167
|
+
chatEmission: any,
|
|
168
|
+
isLoading: boolean,
|
|
22
169
|
currentViseme: { name: string; weight: number } | null,
|
|
23
170
|
eyeBlink: boolean,
|
|
24
171
|
blinkState: {
|
|
@@ -33,17 +180,25 @@ export class MorphTargetController {
|
|
|
33
180
|
!this.headMesh.morphTargetDictionary ||
|
|
34
181
|
!this.headMesh.morphTargetInfluences
|
|
35
182
|
) {
|
|
36
|
-
console.error(
|
|
183
|
+
console.error(
|
|
184
|
+
'[MorphTargetController] Missing morphTargetDictionary or morphTargetInfluences'
|
|
185
|
+
);
|
|
37
186
|
return;
|
|
38
187
|
}
|
|
39
188
|
|
|
189
|
+
// Process chat emission to get emotion morph targets
|
|
190
|
+
const emotionMorphTargets = this.processChatEmission(
|
|
191
|
+
chatEmission,
|
|
192
|
+
isLoading
|
|
193
|
+
);
|
|
194
|
+
|
|
40
195
|
// Calculate blink value for this frame
|
|
41
196
|
const blinkValue = this.calculateBlinkValue(
|
|
42
197
|
currentTime,
|
|
43
198
|
blinkState,
|
|
44
199
|
eyeBlink
|
|
45
200
|
);
|
|
46
|
-
|
|
201
|
+
|
|
47
202
|
const currentEmotionKeys = new Set(Object.keys(emotionMorphTargets));
|
|
48
203
|
|
|
49
204
|
// Process each morph target
|
|
@@ -55,6 +210,7 @@ export class MorphTargetController {
|
|
|
55
210
|
|
|
56
211
|
// Handle emotion morphs with smoothing
|
|
57
212
|
if (currentEmotionKeys.has(key)) {
|
|
213
|
+
// console.log('[MorphTargetController] Processing morph target:', key);
|
|
58
214
|
const targetEmotionValue = emotionMorphTargets[key];
|
|
59
215
|
const currentEmotionValue = this.currentEmotionValues[key] || 0;
|
|
60
216
|
const newEmotionValue = MathUtils.lerp(
|
|
@@ -124,15 +280,15 @@ export class MorphTargetController {
|
|
|
124
280
|
if (blinkState.isBlinking) {
|
|
125
281
|
const blinkProgress =
|
|
126
282
|
(currentTime - blinkState.blinkStartTime) / BLINK_CONFIG.blinkDuration;
|
|
127
|
-
|
|
283
|
+
|
|
128
284
|
// First half of blink - closing eyes
|
|
129
285
|
if (blinkProgress <= 0.5) {
|
|
130
286
|
blinkValue = blinkProgress * 2;
|
|
131
|
-
}
|
|
287
|
+
}
|
|
132
288
|
// Second half of blink - opening eyes
|
|
133
289
|
else if (blinkProgress <= 1) {
|
|
134
290
|
blinkValue = 2 - blinkProgress * 2;
|
|
135
|
-
}
|
|
291
|
+
}
|
|
136
292
|
// Blink complete
|
|
137
293
|
else {
|
|
138
294
|
blinkState.isBlinking = false;
|
|
@@ -15,22 +15,19 @@ import useHeadMovement from '../../utils/useHeadMovement';
|
|
|
15
15
|
|
|
16
16
|
interface HalfBodyAvatarProps {
|
|
17
17
|
url: string;
|
|
18
|
-
setMorphTargetInfluences: (morphTargetInfluences: any) => void;
|
|
19
|
-
setMorphTargetDictionary: (morphTargetDictionary: any) => void;
|
|
20
18
|
updateCurrentViseme: (currentTime: number) => any;
|
|
21
19
|
eyeBlink?: boolean;
|
|
22
|
-
heightValue?: number; // 0-100 slider value
|
|
23
20
|
avatarHeight: number;
|
|
24
21
|
avatarDepth: number;
|
|
25
22
|
onLoaded?: () => void;
|
|
26
23
|
onCameraZChange: (value: number) => void;
|
|
27
24
|
headMovement?: boolean;
|
|
25
|
+
chatEmission?: any;
|
|
26
|
+
loading?: boolean;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
export default function HalfBodyAvatar({
|
|
31
30
|
url,
|
|
32
|
-
setMorphTargetInfluences,
|
|
33
|
-
setMorphTargetDictionary,
|
|
34
31
|
updateCurrentViseme,
|
|
35
32
|
eyeBlink = false,
|
|
36
33
|
avatarHeight = 50,
|
|
@@ -38,18 +35,22 @@ export default function HalfBodyAvatar({
|
|
|
38
35
|
headMovement = false,
|
|
39
36
|
onLoaded,
|
|
40
37
|
onCameraZChange,
|
|
38
|
+
chatEmission,
|
|
39
|
+
loading = false,
|
|
41
40
|
}: HalfBodyAvatarProps) {
|
|
42
41
|
const { scene } = useGLTF(url);
|
|
43
42
|
const { nodes, materials } = useGraph(scene);
|
|
44
43
|
const { camera } = useThree();
|
|
45
44
|
|
|
46
|
-
const morphTargetControllerRef = useRef<MorphTargetController>();
|
|
47
|
-
const positionControllerRef = useRef<AvatarPositionController>();
|
|
45
|
+
const morphTargetControllerRef = useRef<MorphTargetController | null>(null);
|
|
46
|
+
const positionControllerRef = useRef<AvatarPositionController | null>(null);
|
|
48
47
|
const targetCameraZRef = useRef(camera.position.z);
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
// Apply head movement if enabled
|
|
51
50
|
useHeadMovement(headMovement, nodes);
|
|
52
51
|
|
|
52
|
+
|
|
53
|
+
// Eye blinking state
|
|
53
54
|
const blinkStateRef = useRef({
|
|
54
55
|
isBlinking: false,
|
|
55
56
|
lastBlinkTime: 0,
|
|
@@ -63,7 +64,7 @@ export default function HalfBodyAvatar({
|
|
|
63
64
|
scene?.traverse((object: Object3D) => {
|
|
64
65
|
if (
|
|
65
66
|
object instanceof SkinnedMesh &&
|
|
66
|
-
(object.name === 'GBNL__Head' || object.name === 'Wolf3D_Avatar')
|
|
67
|
+
(object.name === 'GBNL__Head' || object.name === 'Wolf3D_Avatar' || object.name === 'Wolf3D_Avatar006_1')
|
|
67
68
|
) {
|
|
68
69
|
foundMesh = object;
|
|
69
70
|
}
|
|
@@ -80,21 +81,17 @@ export default function HalfBodyAvatar({
|
|
|
80
81
|
);
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
// Initialize MorphTargetController if head mesh exists
|
|
83
85
|
if (headMesh) {
|
|
84
86
|
morphTargetControllerRef.current = new MorphTargetController(headMesh);
|
|
85
|
-
|
|
86
|
-
if (headMesh.morphTargetDictionary && headMesh.morphTargetInfluences) {
|
|
87
|
-
setMorphTargetDictionary(headMesh.morphTargetDictionary);
|
|
88
|
-
const initialInfluences = Object.keys(headMesh.morphTargetDictionary)
|
|
89
|
-
.reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
|
|
90
|
-
setMorphTargetInfluences(initialInfluences);
|
|
91
|
-
}
|
|
92
87
|
}
|
|
93
88
|
|
|
89
|
+
// Correct materials and perform other initialization tasks
|
|
94
90
|
correctMaterials(materials);
|
|
95
91
|
onLoaded?.();
|
|
96
92
|
hideHands(nodes);
|
|
97
93
|
|
|
94
|
+
// Cleanup on unmount
|
|
98
95
|
return () => {
|
|
99
96
|
Object.values(materials).forEach(material => material.dispose());
|
|
100
97
|
Object.values(nodes)
|
|
@@ -103,12 +100,14 @@ export default function HalfBodyAvatar({
|
|
|
103
100
|
};
|
|
104
101
|
}, [materials, nodes, url, onLoaded, scene, headMesh]);
|
|
105
102
|
|
|
103
|
+
// Handle avatar height changes
|
|
106
104
|
useEffect(() => {
|
|
107
105
|
if (positionControllerRef.current) {
|
|
108
106
|
positionControllerRef.current.updateHeight(avatarHeight, true);
|
|
109
107
|
}
|
|
110
108
|
}, [avatarHeight]);
|
|
111
109
|
|
|
110
|
+
// Handle avatar depth changes
|
|
112
111
|
useEffect(() => {
|
|
113
112
|
if (positionControllerRef.current && onCameraZChange) {
|
|
114
113
|
const newCameraZ = positionControllerRef.current.updateDepth(avatarDepth, true);
|
|
@@ -123,9 +122,12 @@ export default function HalfBodyAvatar({
|
|
|
123
122
|
// Update morph targets
|
|
124
123
|
if (morphTargetControllerRef.current) {
|
|
125
124
|
const currentViseme = updateCurrentViseme(currentTime / 1000);
|
|
125
|
+
|
|
126
|
+
// Use the updated MorphTargetController that handles chat emission directly
|
|
126
127
|
morphTargetControllerRef.current.updateMorphTargets(
|
|
127
128
|
currentTime,
|
|
128
|
-
|
|
129
|
+
chatEmission,
|
|
130
|
+
loading,
|
|
129
131
|
currentViseme,
|
|
130
132
|
eyeBlink,
|
|
131
133
|
blinkStateRef.current,
|
|
@@ -11,94 +11,95 @@ export const SCALE_LERP_FACTOR = 0.1;
|
|
|
11
11
|
|
|
12
12
|
// Maximum number of idle loops before forcing change
|
|
13
13
|
export const MAX_IDLE_LOOPS_DEFAULT = 5;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
action?: string;
|
|
14
|
+
// Type definitions for emotion handling
|
|
15
|
+
interface EmotionMappingItem {
|
|
16
|
+
italian: string;
|
|
17
|
+
english: string;
|
|
19
18
|
}
|
|
19
|
+
export type EmotionMapping = EmotionMappingItem[];
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Idle1: { weight: 1 },
|
|
27
|
-
Idle2: { weight: 0 },
|
|
28
|
-
Idle3: { weight: 0 },
|
|
29
|
-
Idle4: { weight: 0 },
|
|
30
|
-
Idle5: { weight: 0 },
|
|
31
|
-
Rabbia1: { weight: 0 },
|
|
32
|
-
Rabbia2: { weight: 0 },
|
|
33
|
-
Rabbia3: { weight: 0 },
|
|
34
|
-
Sorpresa1: { weight: 0 },
|
|
35
|
-
Sorpresa2: { weight: 0 },
|
|
36
|
-
Sorpresa3: { weight: 0 },
|
|
37
|
-
Timore1: { weight: 0 },
|
|
38
|
-
Timore2: { weight: 0 },
|
|
39
|
-
Timore3: { weight: 0 },
|
|
40
|
-
Tristezza1: { weight: 0 },
|
|
41
|
-
Tristezza2: { weight: 0 },
|
|
42
|
-
Tristezza3: { weight: 0 },
|
|
43
|
-
Loading1: { weight: 0 },
|
|
44
|
-
Loading2: { weight: 0 },
|
|
45
|
-
Loading3: { weight: 0 },
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Mapping of emotions from Italian to English
|
|
49
|
-
export const MAPPING_EMOTIONS_ITALIAN_TO_ENGLISH = {
|
|
50
|
-
Gioia: 'Joy',
|
|
51
|
-
Rabbia: 'Anger',
|
|
52
|
-
Sorpresa: 'Surprise',
|
|
53
|
-
Tristezza: 'Sadness',
|
|
54
|
-
Timore: 'Fear',
|
|
55
|
-
};
|
|
21
|
+
interface BlendShapeMapItem {
|
|
22
|
+
emotion: { italian: string; english: string };
|
|
23
|
+
blendShapes: Record<string, number>;
|
|
24
|
+
}
|
|
25
|
+
export type BlendShapeMap = BlendShapeMapItem[];
|
|
56
26
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'browOuterUpRight': 0.5,
|
|
64
|
-
'mouthSmile': -0.2,
|
|
27
|
+
// Italian to English emotion mapping
|
|
28
|
+
// Italian to English emotion mapping
|
|
29
|
+
export const MAPPING_EMOTIONS_ITALIAN_TO_ENGLISH: EmotionMapping = [
|
|
30
|
+
{
|
|
31
|
+
italian: 'Gioia',
|
|
32
|
+
english: 'Joy',
|
|
65
33
|
},
|
|
66
|
-
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'eyeWideLeft': -0.5,
|
|
70
|
-
'eyeWideRight': -0.5,
|
|
34
|
+
{
|
|
35
|
+
italian: 'Rabbia',
|
|
36
|
+
english: 'Anger',
|
|
71
37
|
},
|
|
72
|
-
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'eyeSquintLeft': 0.5,
|
|
76
|
-
'eyeSquintRight': 0.5,
|
|
77
|
-
'mouthSmile': -0.6,
|
|
38
|
+
{
|
|
39
|
+
italian: 'Sorpresa',
|
|
40
|
+
english: 'Surprise',
|
|
78
41
|
},
|
|
79
|
-
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'browOuterUpRight': 0.5,
|
|
83
|
-
'eyeWideLeft': 0.5,
|
|
84
|
-
'eyeWideRight': 0.5,
|
|
42
|
+
{
|
|
43
|
+
italian: 'Tristezza',
|
|
44
|
+
english: 'Sadness',
|
|
85
45
|
},
|
|
86
|
-
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'browInnerUp': 0.5,
|
|
90
|
-
'mouthSmile': 0.8,
|
|
46
|
+
{
|
|
47
|
+
italian: 'Timore',
|
|
48
|
+
english: 'Fear',
|
|
91
49
|
},
|
|
92
|
-
|
|
50
|
+
];
|
|
93
51
|
|
|
94
|
-
// Mapping of blend shapes to emotions for
|
|
95
|
-
export const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
52
|
+
// Mapping of blend shapes to emotions for RPM avatars
|
|
53
|
+
export const MAPPING_BLEND_SHAPE_TO_EMOTION_RPM: BlendShapeMap = [
|
|
54
|
+
{
|
|
55
|
+
emotion: { italian: 'Rabbia', english: 'Anger' },
|
|
56
|
+
blendShapes: {
|
|
57
|
+
'browDownLeft': 0.5,
|
|
58
|
+
'browDownRight': 0.5,
|
|
59
|
+
'browOuterUpLeft': 0.5,
|
|
60
|
+
'browOuterUpRight': 0.5,
|
|
61
|
+
'mouthSmile': -0.2,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
emotion: { italian: 'Timore', english: 'Fear' },
|
|
66
|
+
blendShapes: {
|
|
67
|
+
'browOuterUpLeft': -0.5,
|
|
68
|
+
'browOuterUpRight': -0.5,
|
|
69
|
+
'eyeWideLeft': -0.5,
|
|
70
|
+
'eyeWideRight': -0.5,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
emotion: { italian: 'Tristezza', english: 'Sadness' },
|
|
75
|
+
blendShapes: {
|
|
76
|
+
'browDownLeft': -0.5,
|
|
77
|
+
'browDownRight': -0.5,
|
|
78
|
+
'eyeSquintLeft': 0.5,
|
|
79
|
+
'eyeSquintRight': 0.5,
|
|
80
|
+
'mouthSmile': -0.6,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
emotion: { italian: 'Sorpresa', english: 'Surprise' },
|
|
85
|
+
blendShapes: {
|
|
86
|
+
'browInnerUp': 0.5,
|
|
87
|
+
'browOuterUpLeft': 0.5,
|
|
88
|
+
'browOuterUpRight': 0.5,
|
|
89
|
+
'eyeWideLeft': 0.5,
|
|
90
|
+
'eyeWideRight': 0.5,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
emotion: { italian: 'Gioia', english: 'Joy' },
|
|
95
|
+
blendShapes: {
|
|
96
|
+
'browDownLeft': 0.5,
|
|
97
|
+
'browDownRight': 0.5,
|
|
98
|
+
'browInnerUp': 0.5,
|
|
99
|
+
'mouthSmile': 0.8,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
];
|
|
102
103
|
|
|
103
104
|
// URL for the male avatar
|
|
104
105
|
export const ANIMATION_URLS = {
|
|
@@ -79,12 +79,10 @@ export default function ContainerAvatarView({
|
|
|
79
79
|
fallbackImg,
|
|
80
80
|
halfBody = true,
|
|
81
81
|
loading,
|
|
82
|
-
animation,
|
|
82
|
+
// animation,
|
|
83
83
|
showControls = false,
|
|
84
84
|
isZoomed,
|
|
85
85
|
chatEmission,
|
|
86
|
-
stopProcessing,
|
|
87
|
-
resetVisemeQueue,
|
|
88
86
|
updateCurrentViseme,
|
|
89
87
|
enablePositionControls,
|
|
90
88
|
setEnablePositionControls,
|
|
@@ -179,16 +177,12 @@ export default function ContainerAvatarView({
|
|
|
179
177
|
sex={sex}
|
|
180
178
|
showControls={showControls}
|
|
181
179
|
loading={loading || false}
|
|
182
|
-
animation={animation}
|
|
183
|
-
isZoomed={isZoomed || false}
|
|
184
180
|
eyeBlink={eyeBlink || false}
|
|
185
181
|
headMovement={headMovement || false}
|
|
186
182
|
speaking={speaking || false}
|
|
187
183
|
halfBody={halfBody}
|
|
188
184
|
chatEmission={chatEmission}
|
|
189
185
|
updateCurrentViseme={updateCurrentViseme}
|
|
190
|
-
stopProcessing={stopProcessing}
|
|
191
|
-
resetVisemeQueue={resetVisemeQueue}
|
|
192
186
|
setCameraZ={setCameraZ}
|
|
193
187
|
avatarHeight={avatarHeight}
|
|
194
188
|
avatarDepth={avatarDepth}
|
|
@@ -193,13 +193,13 @@ const Chat: React.FC<Props> = ({
|
|
|
193
193
|
style={{
|
|
194
194
|
backgroundImage: `url("${getResourceUrl({
|
|
195
195
|
type: 'cover',
|
|
196
|
-
tenantID: tenant?.
|
|
196
|
+
tenantID: tenant?.name,
|
|
197
197
|
resourceURI: memori.coverURL,
|
|
198
198
|
baseURL: baseUrl,
|
|
199
199
|
apiURL: apiUrl,
|
|
200
200
|
})}"), url("${getResourceUrl({
|
|
201
201
|
type: 'cover',
|
|
202
|
-
tenantID: tenant?.
|
|
202
|
+
tenantID: tenant?.name,
|
|
203
203
|
baseURL: baseUrl || 'https://www.aisuru.com',
|
|
204
204
|
apiURL: apiUrl,
|
|
205
205
|
})}")`,
|
|
@@ -97,33 +97,32 @@ const renderMsg = (text: string, useMathFormatting = false): string => {
|
|
|
97
97
|
if (useMathFormatting) {
|
|
98
98
|
// Normalizza tutti i delimitatori LaTeX per equazioni su linea separata
|
|
99
99
|
// Da \\[ ... \\] o \\[ ... ] a $$ ... $$
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
preprocessedText = preprocessedText.replace(
|
|
101
|
+
/\\+\[(.*?)\\*\]/gs,
|
|
102
|
+
(_, content) => {
|
|
103
|
+
return `$$${content}$$`;
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
106
|
|
|
107
107
|
// Gestione dei delimitatori [ ... ] che dovrebbero essere equazioni
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
preprocessedText = preprocessedText.replace(
|
|
109
|
+
/\[([^[\]]+?)\]/g,
|
|
110
|
+
(match, content) => {
|
|
111
111
|
// Verifica se sembra una formula matematica
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
if (
|
|
113
|
+
/[\\+a-z0-9_{}^=\-\+\*\/]+/i.test(content) &&
|
|
114
|
+
!match.startsWith('[http') &&
|
|
115
|
+
!match.includes('](')
|
|
116
|
+
) {
|
|
117
|
+
return `$$${content}$$`;
|
|
118
|
+
}
|
|
117
119
|
return match; // Mantieni invariati i link e altre strutture
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
}
|
|
121
|
+
);
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
// Ora procedi con il parsing markdown
|
|
123
|
-
let parsedText = marked
|
|
124
|
-
.parse(preprocessedText)
|
|
125
|
-
.toString()
|
|
126
|
-
.trim();
|
|
125
|
+
let parsedText = marked.parse(preprocessedText).toString().trim();
|
|
127
126
|
|
|
128
127
|
// Sanitize HTML
|
|
129
128
|
parsedText = DOMPurify.sanitize(parsedText, {
|
|
@@ -291,13 +290,13 @@ const ChatBubble: React.FC<Props> = ({
|
|
|
291
290
|
: memori.avatarURL && memori.avatarURL.length > 0
|
|
292
291
|
? getResourceUrl({
|
|
293
292
|
type: 'avatar',
|
|
294
|
-
tenantID: tenant?.
|
|
293
|
+
tenantID: tenant?.name,
|
|
295
294
|
resourceURI: memori.avatarURL,
|
|
296
295
|
baseURL: baseUrl,
|
|
297
296
|
apiURL: apiUrl,
|
|
298
297
|
})
|
|
299
298
|
: getResourceUrl({
|
|
300
|
-
tenantID: tenant?.
|
|
299
|
+
tenantID: tenant?.name,
|
|
301
300
|
type: 'avatar',
|
|
302
301
|
baseURL: baseUrl || 'https://www.aisuru.com',
|
|
303
302
|
apiURL: apiUrl,
|
|
@@ -309,12 +308,12 @@ const ChatBubble: React.FC<Props> = ({
|
|
|
309
308
|
memori.avatarURL && memori.avatarURL.length > 0
|
|
310
309
|
? getResourceUrl({
|
|
311
310
|
type: 'avatar',
|
|
312
|
-
tenantID: tenant?.
|
|
311
|
+
tenantID: tenant?.name,
|
|
313
312
|
resourceURI: memori.avatarURL,
|
|
314
313
|
baseURL: baseUrl,
|
|
315
314
|
})
|
|
316
315
|
: getResourceUrl({
|
|
317
|
-
tenantID: tenant?.
|
|
316
|
+
tenantID: tenant?.name,
|
|
318
317
|
type: 'avatar',
|
|
319
318
|
baseURL: baseUrl,
|
|
320
319
|
});
|
|
@@ -397,11 +396,23 @@ const ChatBubble: React.FC<Props> = ({
|
|
|
397
396
|
{message.generatedByAI && showAIicon && (
|
|
398
397
|
<Tooltip
|
|
399
398
|
align="left"
|
|
400
|
-
content={
|
|
399
|
+
content={
|
|
400
|
+
t('generatedByAI') ||
|
|
401
|
+
(lang === 'it'
|
|
402
|
+
? 'Risposta generata da IA, può talvolta generare informazioni non corrette'
|
|
403
|
+
: 'Answer generated by AI, may occasionally generate incorrect informations')
|
|
404
|
+
}
|
|
401
405
|
className="memori-chat--bubble-action-icon memori-chat--bubble-action-icon--ai"
|
|
402
406
|
>
|
|
403
407
|
<span>
|
|
404
|
-
<AI
|
|
408
|
+
<AI
|
|
409
|
+
title={
|
|
410
|
+
t('generatedByAI') ||
|
|
411
|
+
(lang === 'it'
|
|
412
|
+
? 'Risposta generata da IA, può talvolta generare informazioni non corrette'
|
|
413
|
+
: 'Answer generated by AI, may occasionally generate incorrect informations')
|
|
414
|
+
}
|
|
415
|
+
/>
|
|
405
416
|
</span>
|
|
406
417
|
</Tooltip>
|
|
407
418
|
)}
|