@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-rsc/talking-head-react",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "A reusable React component for 3D talking avatars with lip-sync and text-to-speech",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -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('Failed to load animation manifest:', 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
- console.warn('⚠️ Could not load manifest fallback:', e);
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('Failed to auto-discover animations:', 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(`❌ Failed to play random animation from "${groupName}" group:`, 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('Failed to load animation manifest:', 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);