@sage-rsc/talking-head-react 1.3.4 → 1.3.6
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/dist/index.cjs +2 -2
- package/dist/index.js +350 -311
- package/package.json +1 -1
- package/src/components/SimpleTalkingAvatar.jsx +49 -13
- package/src/lib/talkinghead.mjs +28 -0
- package/src/utils/animationLoader.js +19 -2
package/package.json
CHANGED
|
@@ -86,22 +86,27 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
86
86
|
const loadManifestAnimations = async () => {
|
|
87
87
|
if (animations.manifest) {
|
|
88
88
|
try {
|
|
89
|
+
console.log('🔄 Loading animations from manifest:', animations.manifest);
|
|
89
90
|
const manifestAnimations = await loadAnimationsFromManifest(animations.manifest);
|
|
90
91
|
setLoadedAnimations(manifestAnimations);
|
|
91
|
-
console.log('
|
|
92
|
+
console.log('✅ Animations loaded and set:', manifestAnimations);
|
|
92
93
|
|
|
93
94
|
// Log gender-specific groups if available
|
|
94
95
|
if (manifestAnimations._genderSpecific) {
|
|
95
|
-
console.log('Gender-specific animations detected:', {
|
|
96
|
+
console.log('👥 Gender-specific animations detected:', {
|
|
96
97
|
male: Object.keys(manifestAnimations._genderSpecific.male || {}),
|
|
97
|
-
female: Object.keys(manifestAnimations._genderSpecific.female || {})
|
|
98
|
+
female: Object.keys(manifestAnimations._genderSpecific.female || {}),
|
|
99
|
+
shared: Object.keys(manifestAnimations._genderSpecific.shared || {})
|
|
98
100
|
});
|
|
101
|
+
} else {
|
|
102
|
+
console.log('⚠️ No gender-specific animations found in manifest');
|
|
99
103
|
}
|
|
100
104
|
} catch (error) {
|
|
101
|
-
console.error('Failed to load animation manifest:', error);
|
|
105
|
+
console.error('❌ Failed to load animation manifest:', error);
|
|
102
106
|
setLoadedAnimations(animations);
|
|
103
107
|
}
|
|
104
108
|
} else {
|
|
109
|
+
console.log('📝 Using animations from props (no manifest):', animations);
|
|
105
110
|
setLoadedAnimations(animations);
|
|
106
111
|
}
|
|
107
112
|
};
|
|
@@ -260,11 +265,12 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
260
265
|
|
|
261
266
|
// Helper function to get random animation from a group (with gender support)
|
|
262
267
|
const getRandomAnimation = useCallback((groupName) => {
|
|
263
|
-
if (!loadedAnimations
|
|
268
|
+
if (!loadedAnimations) {
|
|
269
|
+
console.warn('No animations loaded');
|
|
264
270
|
return null;
|
|
265
271
|
}
|
|
266
272
|
|
|
267
|
-
let group =
|
|
273
|
+
let group = null;
|
|
268
274
|
|
|
269
275
|
// Check if gender-specific animations are available
|
|
270
276
|
if (loadedAnimations._genderSpecific) {
|
|
@@ -272,16 +278,34 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
272
278
|
const genderKey = avatarGender === 'M' ? 'male' : 'female';
|
|
273
279
|
const genderGroups = loadedAnimations._genderSpecific[genderKey];
|
|
274
280
|
|
|
275
|
-
// Try gender-specific first
|
|
281
|
+
// Try gender-specific first
|
|
276
282
|
if (genderGroups && genderGroups[groupName]) {
|
|
277
283
|
group = genderGroups[groupName];
|
|
278
|
-
console.log(`Using ${genderKey} animations for "${groupName}"
|
|
279
|
-
}
|
|
284
|
+
console.log(`Using ${genderKey} animations for "${groupName}":`, group);
|
|
285
|
+
}
|
|
286
|
+
// Fallback to shared gender-specific animations
|
|
287
|
+
else if (loadedAnimations._genderSpecific.shared && loadedAnimations._genderSpecific.shared[groupName]) {
|
|
280
288
|
group = loadedAnimations._genderSpecific.shared[groupName];
|
|
281
|
-
console.log(`Using shared animations for "${groupName}"
|
|
289
|
+
console.log(`Using shared animations for "${groupName}":`, group);
|
|
282
290
|
}
|
|
283
291
|
}
|
|
284
292
|
|
|
293
|
+
// Fallback to root-level animations if gender-specific not found
|
|
294
|
+
if (!group && loadedAnimations[groupName]) {
|
|
295
|
+
group = loadedAnimations[groupName];
|
|
296
|
+
console.log(`Using root-level animations for "${groupName}":`, group);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!group) {
|
|
300
|
+
console.warn(`Animation group "${groupName}" not found. Available groups:`, Object.keys(loadedAnimations).filter(k => k !== '_genderSpecific'));
|
|
301
|
+
if (loadedAnimations._genderSpecific) {
|
|
302
|
+
const avatarGender = avatarBody?.toUpperCase() || 'F';
|
|
303
|
+
const genderKey = avatarGender === 'M' ? 'male' : 'female';
|
|
304
|
+
console.warn(`Gender-specific groups (${genderKey}):`, Object.keys(loadedAnimations._genderSpecific[genderKey] || {}));
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
285
309
|
// If it's an array, randomly select one
|
|
286
310
|
if (Array.isArray(group) && group.length > 0) {
|
|
287
311
|
const randomIndex = Math.floor(Math.random() * group.length);
|
|
@@ -293,21 +317,29 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
293
317
|
return group;
|
|
294
318
|
}
|
|
295
319
|
|
|
320
|
+
console.warn(`Animation group "${groupName}" is not a valid format (expected array or string):`, group);
|
|
296
321
|
return null;
|
|
297
322
|
}, [loadedAnimations, avatarBody]);
|
|
298
323
|
|
|
299
324
|
// Helper function to play random animation from a group
|
|
300
325
|
const playRandomAnimation = useCallback((groupName, disablePositionLock = false) => {
|
|
326
|
+
if (!talkingHeadRef.current) {
|
|
327
|
+
console.warn('TalkingHead not initialized yet');
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
|
|
301
331
|
const animationPath = getRandomAnimation(groupName);
|
|
302
|
-
if (animationPath
|
|
332
|
+
if (animationPath) {
|
|
303
333
|
try {
|
|
304
334
|
talkingHeadRef.current.playAnimation(animationPath, null, 10, 0, 0.01, disablePositionLock);
|
|
305
|
-
console.log(
|
|
335
|
+
console.log(`✅ Playing random animation from "${groupName}" group:`, animationPath);
|
|
306
336
|
return animationPath;
|
|
307
337
|
} catch (error) {
|
|
308
|
-
console.
|
|
338
|
+
console.error(`❌ Failed to play random animation from "${groupName}" group:`, error);
|
|
309
339
|
return null;
|
|
310
340
|
}
|
|
341
|
+
} else {
|
|
342
|
+
console.warn(`⚠️ No animation found for group "${groupName}"`);
|
|
311
343
|
}
|
|
312
344
|
return null;
|
|
313
345
|
}, [getRandomAnimation]);
|
|
@@ -331,7 +363,11 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
331
363
|
// Check both autoAnimationGroup prop and options.animationGroup
|
|
332
364
|
const animationGroup = options.animationGroup || autoAnimationGroup;
|
|
333
365
|
if (animationGroup && !options.skipAnimation) {
|
|
366
|
+
console.log(`🎬 Attempting to play animation from group: "${animationGroup}"`);
|
|
367
|
+
console.log(`📊 Current avatarBody: "${avatarBody}", loadedAnimations:`, loadedAnimations);
|
|
334
368
|
playRandomAnimation(animationGroup);
|
|
369
|
+
} else {
|
|
370
|
+
console.log(`⏭️ Skipping animation (group: ${animationGroup}, skipAnimation: ${options.skipAnimation})`);
|
|
335
371
|
}
|
|
336
372
|
|
|
337
373
|
// Reset speech progress tracking
|
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -1608,6 +1608,34 @@ class TalkingHead {
|
|
|
1608
1608
|
}
|
|
1609
1609
|
}
|
|
1610
1610
|
}
|
|
1611
|
+
|
|
1612
|
+
// Apply shoulder adjustment to lower shoulders
|
|
1613
|
+
this.applyShoulderAdjustment();
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/**
|
|
1617
|
+
* Apply shoulder adjustment to lower shoulders to a more natural position
|
|
1618
|
+
*/
|
|
1619
|
+
applyShoulderAdjustment() {
|
|
1620
|
+
// Shoulder adjustment: reduce X-axis rotation by ~0.35 radians (20 degrees) to lower shoulders
|
|
1621
|
+
const shoulderAdjustment = -0.35; // Negative to lower shoulders
|
|
1622
|
+
const tempEuler = new THREE.Euler();
|
|
1623
|
+
|
|
1624
|
+
// Adjust left shoulder
|
|
1625
|
+
if (this.poseAvatar.props['LeftShoulder.quaternion']) {
|
|
1626
|
+
const leftShoulder = this.poseAvatar.props['LeftShoulder.quaternion'];
|
|
1627
|
+
tempEuler.setFromQuaternion(leftShoulder, 'XYZ');
|
|
1628
|
+
tempEuler.x += shoulderAdjustment; // Reduce X rotation to lower shoulder
|
|
1629
|
+
leftShoulder.setFromEuler(tempEuler, 'XYZ');
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// Adjust right shoulder
|
|
1633
|
+
if (this.poseAvatar.props['RightShoulder.quaternion']) {
|
|
1634
|
+
const rightShoulder = this.poseAvatar.props['RightShoulder.quaternion'];
|
|
1635
|
+
tempEuler.setFromQuaternion(rightShoulder, 'XYZ');
|
|
1636
|
+
tempEuler.x += shoulderAdjustment; // Reduce X rotation to lower shoulder
|
|
1637
|
+
rightShoulder.setFromEuler(tempEuler, 'XYZ');
|
|
1638
|
+
}
|
|
1611
1639
|
}
|
|
1612
1640
|
|
|
1613
1641
|
/**
|
|
@@ -11,11 +11,28 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export async function loadAnimationsFromManifest(manifestPath) {
|
|
13
13
|
try {
|
|
14
|
+
console.log(`📥 Loading animation manifest from: ${manifestPath}`);
|
|
14
15
|
const response = await fetch(manifestPath);
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
throw new Error(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
18
|
+
}
|
|
15
19
|
const manifest = await response.json();
|
|
16
|
-
|
|
20
|
+
console.log('📦 Raw manifest loaded:', manifest);
|
|
21
|
+
|
|
22
|
+
const animations = manifest.animations || {};
|
|
23
|
+
console.log('✅ Processed animations object:', animations);
|
|
24
|
+
|
|
25
|
+
if (animations._genderSpecific) {
|
|
26
|
+
console.log('👥 Gender-specific structure detected:', {
|
|
27
|
+
male: Object.keys(animations._genderSpecific.male || {}),
|
|
28
|
+
female: Object.keys(animations._genderSpecific.female || {}),
|
|
29
|
+
shared: Object.keys(animations._genderSpecific.shared || {})
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return animations;
|
|
17
34
|
} catch (error) {
|
|
18
|
-
console.error('Failed to load animation manifest:', error);
|
|
35
|
+
console.error('❌ Failed to load animation manifest:', error);
|
|
19
36
|
return {};
|
|
20
37
|
}
|
|
21
38
|
}
|