@sage-rsc/talking-head-react 1.6.2 → 1.6.3
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 +3 -3
- package/dist/index.js +1190 -1226
- package/package.json +1 -1
- package/src/components/SimpleTalkingAvatar.jsx +15 -69
- package/src/utils/animationLoader.js +1 -22
package/package.json
CHANGED
|
@@ -89,21 +89,10 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
89
89
|
// Priority 1: If both manifest and auto are provided, use manifest (more reliable)
|
|
90
90
|
if (animations.manifest && animations.auto) {
|
|
91
91
|
try {
|
|
92
|
-
console.log('🔄 Loading animations from manifest (priority over auto-discovery):', animations.manifest);
|
|
93
92
|
const manifestAnimations = await loadAnimationsFromManifest(animations.manifest);
|
|
94
93
|
setLoadedAnimations(manifestAnimations);
|
|
95
|
-
console.log('✅ Animations loaded from manifest:', manifestAnimations);
|
|
96
|
-
|
|
97
|
-
if (manifestAnimations._genderSpecific) {
|
|
98
|
-
console.log('👥 Gender-specific animations detected:', {
|
|
99
|
-
male: Object.keys(manifestAnimations._genderSpecific.male || {}),
|
|
100
|
-
female: Object.keys(manifestAnimations._genderSpecific.female || {}),
|
|
101
|
-
shared: Object.keys(manifestAnimations._genderSpecific.shared || {})
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
94
|
return; // Don't try auto-discovery if manifest loaded successfully
|
|
105
95
|
} catch (error) {
|
|
106
|
-
console.warn('⚠️ Manifest failed, falling back to auto-discovery:', error);
|
|
107
96
|
// Fall through to auto-discovery
|
|
108
97
|
}
|
|
109
98
|
}
|
|
@@ -111,39 +100,23 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
111
100
|
// Option 1: Load from manifest file only
|
|
112
101
|
if (animations.manifest && !animations.auto) {
|
|
113
102
|
try {
|
|
114
|
-
console.log('🔄 Loading animations from manifest:', animations.manifest);
|
|
115
103
|
const manifestAnimations = await loadAnimationsFromManifest(animations.manifest);
|
|
116
104
|
setLoadedAnimations(manifestAnimations);
|
|
117
|
-
console.log('✅ Animations loaded and set:', manifestAnimations);
|
|
118
|
-
|
|
119
|
-
// Log gender-specific groups if available
|
|
120
|
-
if (manifestAnimations._genderSpecific) {
|
|
121
|
-
console.log('👥 Gender-specific animations detected:', {
|
|
122
|
-
male: Object.keys(manifestAnimations._genderSpecific.male || {}),
|
|
123
|
-
female: Object.keys(manifestAnimations._genderSpecific.female || {}),
|
|
124
|
-
shared: Object.keys(manifestAnimations._genderSpecific.shared || {})
|
|
125
|
-
});
|
|
126
|
-
} else {
|
|
127
|
-
console.log('⚠️ No gender-specific animations found in manifest');
|
|
128
|
-
}
|
|
129
105
|
} catch (error) {
|
|
130
|
-
console.error('
|
|
106
|
+
console.error('Failed to load animation manifest:', error);
|
|
131
107
|
setLoadedAnimations(animations);
|
|
132
108
|
}
|
|
133
109
|
}
|
|
134
110
|
// Option 2: Auto-discover from folders (with manifest fallback)
|
|
135
111
|
else if (animations.auto) {
|
|
136
112
|
try {
|
|
137
|
-
console.log('🔄 Auto-discovering animations from folder:', animations.auto);
|
|
138
|
-
|
|
139
113
|
// First, try to load manifest as fallback
|
|
140
114
|
let manifestAnimations = null;
|
|
141
115
|
if (animations.manifest) {
|
|
142
116
|
try {
|
|
143
117
|
manifestAnimations = await loadAnimationsFromManifest(animations.manifest);
|
|
144
|
-
console.log('📦 Loaded manifest as fallback:', manifestAnimations);
|
|
145
118
|
} catch (e) {
|
|
146
|
-
|
|
119
|
+
// Silent fallback
|
|
147
120
|
}
|
|
148
121
|
}
|
|
149
122
|
|
|
@@ -169,7 +142,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
169
142
|
|
|
170
143
|
// If no animations discovered, use manifest fallback
|
|
171
144
|
if (!hasAnimations && manifestAnimations) {
|
|
172
|
-
console.log('⚠️ No animations auto-discovered, using manifest fallback');
|
|
173
145
|
setLoadedAnimations(manifestAnimations);
|
|
174
146
|
return;
|
|
175
147
|
}
|
|
@@ -227,7 +199,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
227
199
|
}
|
|
228
200
|
|
|
229
201
|
setLoadedAnimations(organized);
|
|
230
|
-
console.log('✅ Auto-discovered animations:', organized);
|
|
231
202
|
}
|
|
232
203
|
// If auto is an object, treat keys as group names and values as folder paths
|
|
233
204
|
else if (typeof animations.auto === 'object') {
|
|
@@ -235,21 +206,18 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
235
206
|
const hasAnimations = Object.values(discoveredAnimations).some(anims => Array.isArray(anims) && anims.length > 0);
|
|
236
207
|
|
|
237
208
|
if (!hasAnimations && manifestAnimations) {
|
|
238
|
-
console.log('⚠️ No animations auto-discovered, using manifest fallback');
|
|
239
209
|
setLoadedAnimations(manifestAnimations);
|
|
240
210
|
} else {
|
|
241
211
|
setLoadedAnimations(discoveredAnimations);
|
|
242
|
-
console.log('✅ Auto-discovered animations from folders:', discoveredAnimations);
|
|
243
212
|
}
|
|
244
213
|
}
|
|
245
214
|
} catch (error) {
|
|
246
|
-
console.error('
|
|
215
|
+
console.error('Failed to auto-discover animations:', error);
|
|
247
216
|
// Try manifest fallback
|
|
248
217
|
if (animations.manifest) {
|
|
249
218
|
try {
|
|
250
219
|
const manifestAnimations = await loadAnimationsFromManifest(animations.manifest);
|
|
251
220
|
setLoadedAnimations(manifestAnimations);
|
|
252
|
-
console.log('✅ Using manifest fallback after auto-discovery error');
|
|
253
221
|
} catch (e) {
|
|
254
222
|
setLoadedAnimations(animations);
|
|
255
223
|
}
|
|
@@ -260,7 +228,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
260
228
|
}
|
|
261
229
|
// Option 3: Use animations from props directly
|
|
262
230
|
else {
|
|
263
|
-
console.log('📝 Using animations from props (no manifest or auto):', animations);
|
|
264
231
|
setLoadedAnimations(animations);
|
|
265
232
|
}
|
|
266
233
|
};
|
|
@@ -343,12 +310,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
343
310
|
|
|
344
311
|
talkingHeadRef.current = new TalkingHead(containerRef.current, defaultOptions);
|
|
345
312
|
|
|
346
|
-
// Debug: Log avatar config to verify body type is passed
|
|
347
|
-
console.log('Avatar config being passed:', {
|
|
348
|
-
url: defaultAvatarConfig.url,
|
|
349
|
-
body: defaultAvatarConfig.body,
|
|
350
|
-
avatarMood: defaultAvatarConfig.avatarMood
|
|
351
|
-
});
|
|
352
313
|
|
|
353
314
|
await talkingHeadRef.current.showAvatar(defaultAvatarConfig, (ev) => {
|
|
354
315
|
if (ev.lengthComputable) {
|
|
@@ -357,10 +318,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
357
318
|
}
|
|
358
319
|
});
|
|
359
320
|
|
|
360
|
-
// Debug: Verify avatar body was set correctly
|
|
361
|
-
if (talkingHeadRef.current?.avatar) {
|
|
362
|
-
console.log('Avatar body after initialization:', talkingHeadRef.current.avatar.body);
|
|
363
|
-
}
|
|
364
321
|
|
|
365
322
|
setIsLoading(false);
|
|
366
323
|
setIsReady(true);
|
|
@@ -409,7 +366,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
409
366
|
const audioCtx = talkingHeadRef.current.audioCtx || talkingHeadRef.current.audioContext;
|
|
410
367
|
if (audioCtx && (audioCtx.state === 'suspended' || audioCtx.state === 'interrupted')) {
|
|
411
368
|
await audioCtx.resume();
|
|
412
|
-
console.log('Audio context resumed');
|
|
413
369
|
}
|
|
414
370
|
} catch (err) {
|
|
415
371
|
console.warn('Failed to resume audio context:', err);
|
|
@@ -420,7 +376,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
420
376
|
// Helper function to get random animation from a group (with gender support)
|
|
421
377
|
const getRandomAnimation = useCallback((groupName) => {
|
|
422
378
|
if (!loadedAnimations) {
|
|
423
|
-
console.warn('No animations loaded');
|
|
424
379
|
return null;
|
|
425
380
|
}
|
|
426
381
|
|
|
@@ -435,28 +390,19 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
435
390
|
// Try gender-specific first
|
|
436
391
|
if (genderGroups && genderGroups[groupName]) {
|
|
437
392
|
group = genderGroups[groupName];
|
|
438
|
-
console.log(`Using ${genderKey} animations for "${groupName}":`, group);
|
|
439
393
|
}
|
|
440
394
|
// Fallback to shared gender-specific animations
|
|
441
395
|
else if (loadedAnimations._genderSpecific.shared && loadedAnimations._genderSpecific.shared[groupName]) {
|
|
442
396
|
group = loadedAnimations._genderSpecific.shared[groupName];
|
|
443
|
-
console.log(`Using shared animations for "${groupName}":`, group);
|
|
444
397
|
}
|
|
445
398
|
}
|
|
446
399
|
|
|
447
400
|
// Fallback to root-level animations if gender-specific not found
|
|
448
401
|
if (!group && loadedAnimations[groupName]) {
|
|
449
402
|
group = loadedAnimations[groupName];
|
|
450
|
-
console.log(`Using root-level animations for "${groupName}":`, group);
|
|
451
403
|
}
|
|
452
404
|
|
|
453
405
|
if (!group) {
|
|
454
|
-
console.warn(`Animation group "${groupName}" not found. Available groups:`, Object.keys(loadedAnimations).filter(k => k !== '_genderSpecific'));
|
|
455
|
-
if (loadedAnimations._genderSpecific) {
|
|
456
|
-
const avatarGender = avatarBody?.toUpperCase() || 'F';
|
|
457
|
-
const genderKey = avatarGender === 'M' ? 'male' : 'female';
|
|
458
|
-
console.warn(`Gender-specific groups (${genderKey}):`, Object.keys(loadedAnimations._genderSpecific[genderKey] || {}));
|
|
459
|
-
}
|
|
460
406
|
return null;
|
|
461
407
|
}
|
|
462
408
|
|
|
@@ -471,20 +417,28 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
471
417
|
return group;
|
|
472
418
|
}
|
|
473
419
|
|
|
474
|
-
console.warn(`Animation group "${groupName}" is not a valid format (expected array or string):`, group);
|
|
475
420
|
return null;
|
|
476
421
|
}, [loadedAnimations, avatarBody]);
|
|
422
|
+
|
|
423
|
+
// Helper function to extract animation name from path
|
|
424
|
+
const getAnimationName = useCallback((animationPath) => {
|
|
425
|
+
if (!animationPath) return 'Unknown';
|
|
426
|
+
const filename = animationPath.split('/').pop();
|
|
427
|
+
return filename.replace('.fbx', '').replace(/[-_]/g, ' ');
|
|
428
|
+
}, []);
|
|
477
429
|
|
|
478
430
|
// Helper function to play random animation from a group with smooth transitions
|
|
479
431
|
const playRandomAnimation = useCallback((groupName, disablePositionLock = false, onFinished = null) => {
|
|
480
432
|
if (!talkingHeadRef.current) {
|
|
481
|
-
console.warn('TalkingHead not initialized yet');
|
|
482
433
|
return null;
|
|
483
434
|
}
|
|
484
435
|
|
|
485
436
|
const animationPath = getRandomAnimation(groupName);
|
|
486
437
|
if (animationPath) {
|
|
487
438
|
try {
|
|
439
|
+
const animationName = getAnimationName(animationPath);
|
|
440
|
+
console.log(`🎬 Playing animation: "${animationName}"`);
|
|
441
|
+
|
|
488
442
|
// Create callback that will play next animation if still speaking
|
|
489
443
|
const animationFinishedCallback = () => {
|
|
490
444
|
// If still speaking and same group, play next random animation
|
|
@@ -500,28 +454,23 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
500
454
|
|
|
501
455
|
// Play animation with callback - fade transitions are handled in playAnimation
|
|
502
456
|
talkingHeadRef.current.playAnimation(animationPath, null, 10, 0, 0.01, disablePositionLock, animationFinishedCallback);
|
|
503
|
-
console.log(`✅ Playing random animation from "${groupName}" group:`, animationPath);
|
|
504
457
|
|
|
505
458
|
return animationPath;
|
|
506
459
|
} catch (error) {
|
|
507
|
-
console.error(
|
|
460
|
+
console.error(`Failed to play animation:`, error);
|
|
508
461
|
return null;
|
|
509
462
|
}
|
|
510
|
-
} else {
|
|
511
|
-
console.warn(`⚠️ No animation found for group "${groupName}"`);
|
|
512
463
|
}
|
|
513
464
|
return null;
|
|
514
|
-
}, [getRandomAnimation]);
|
|
465
|
+
}, [getRandomAnimation, getAnimationName]);
|
|
515
466
|
|
|
516
467
|
// Speak text with proper callback handling
|
|
517
468
|
const speakText = useCallback(async (textToSpeak, options = {}) => {
|
|
518
469
|
if (!talkingHeadRef.current || !isReady) {
|
|
519
|
-
console.warn('Avatar not ready for speaking');
|
|
520
470
|
return;
|
|
521
471
|
}
|
|
522
472
|
|
|
523
473
|
if (!textToSpeak || textToSpeak.trim() === '') {
|
|
524
|
-
console.warn('No text provided to speak');
|
|
525
474
|
return;
|
|
526
475
|
}
|
|
527
476
|
|
|
@@ -532,8 +481,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
532
481
|
// Check both autoAnimationGroup prop and options.animationGroup
|
|
533
482
|
const animationGroup = options.animationGroup || autoAnimationGroup;
|
|
534
483
|
if (animationGroup && !options.skipAnimation) {
|
|
535
|
-
console.log(`🎬 Attempting to play animation from group: "${animationGroup}"`);
|
|
536
|
-
console.log(`📊 Current avatarBody: "${avatarBody}", loadedAnimations:`, loadedAnimations);
|
|
537
484
|
|
|
538
485
|
// Mark as speaking and track animation group for continuous playback
|
|
539
486
|
isSpeakingRef.current = true;
|
|
@@ -541,7 +488,6 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
541
488
|
|
|
542
489
|
playRandomAnimation(animationGroup);
|
|
543
490
|
} else {
|
|
544
|
-
console.log(`⏭️ Skipping animation (group: ${animationGroup}, skipAnimation: ${options.skipAnimation})`);
|
|
545
491
|
}
|
|
546
492
|
|
|
547
493
|
// Reset speech progress tracking
|
|
@@ -11,28 +11,15 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export async function loadAnimationsFromManifest(manifestPath) {
|
|
13
13
|
try {
|
|
14
|
-
console.log(`📥 Loading animation manifest from: ${manifestPath}`);
|
|
15
14
|
const response = await fetch(manifestPath);
|
|
16
15
|
if (!response.ok) {
|
|
17
16
|
throw new Error(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
18
17
|
}
|
|
19
18
|
const manifest = await response.json();
|
|
20
|
-
console.log('📦 Raw manifest loaded:', manifest);
|
|
21
|
-
|
|
22
19
|
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
20
|
return animations;
|
|
34
21
|
} catch (error) {
|
|
35
|
-
console.error('
|
|
22
|
+
console.error('Failed to load animation manifest:', error);
|
|
36
23
|
return {};
|
|
37
24
|
}
|
|
38
25
|
}
|
|
@@ -148,7 +135,6 @@ export async function autoDiscoverAnimationsFromFolder(folderPath, avatarBody =
|
|
|
148
135
|
.map(file => file.startsWith('/') ? file : `${normalizedPath}/${file}`);
|
|
149
136
|
|
|
150
137
|
if (fbxFiles.length > 0) {
|
|
151
|
-
console.log(`✅ Auto-discovered ${fbxFiles.length} animations from ${endpoint}`);
|
|
152
138
|
return fbxFiles;
|
|
153
139
|
}
|
|
154
140
|
}
|
|
@@ -182,7 +168,6 @@ export async function autoDiscoverAnimationsFromFolder(folderPath, avatarBody =
|
|
|
182
168
|
.map(file => file.startsWith('/') ? file : `${genderFolder}/${file}`);
|
|
183
169
|
|
|
184
170
|
if (fbxFiles.length > 0) {
|
|
185
|
-
console.log(`✅ Auto-discovered ${fbxFiles.length} animations from ${genderFolder}`);
|
|
186
171
|
animations.push(...fbxFiles);
|
|
187
172
|
}
|
|
188
173
|
}
|
|
@@ -205,9 +190,6 @@ export async function autoDiscoverAnimationsFromFolder(folderPath, avatarBody =
|
|
|
205
190
|
'idle1.fbx', 'idle2.fbx', 'idle_01.fbx', 'idle_02.fbx'
|
|
206
191
|
];
|
|
207
192
|
|
|
208
|
-
console.warn(`⚠️ Could not auto-discover animations from ${normalizedPath}. Consider using a manifest.json file or a server-side directory listing API.`);
|
|
209
|
-
console.info(`💡 Tip: Create a ${normalizedPath}/.list.json file with an array of FBX filenames, or use animations: { manifest: "/animations/manifest.json" }`);
|
|
210
|
-
|
|
211
193
|
return animations;
|
|
212
194
|
}
|
|
213
195
|
|
|
@@ -228,9 +210,6 @@ export async function autoLoadAnimationsFromFolders(folderPaths, avatarBody = 'F
|
|
|
228
210
|
const discoveredAnimations = await autoDiscoverAnimationsFromFolder(folderPath, avatarBody);
|
|
229
211
|
if (discoveredAnimations.length > 0) {
|
|
230
212
|
animations[groupName] = discoveredAnimations;
|
|
231
|
-
console.log(`✅ Auto-loaded ${discoveredAnimations.length} animations for group "${groupName}"`);
|
|
232
|
-
} else {
|
|
233
|
-
console.warn(`⚠️ No animations found in ${folderPath} for group "${groupName}"`);
|
|
234
213
|
}
|
|
235
214
|
} catch (error) {
|
|
236
215
|
console.error(`❌ Failed to auto-load animations from ${folderPath}:`, error);
|