@sage-rsc/talking-head-react 1.2.2 → 1.2.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.2.2",
3
+ "version": "1.2.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",
@@ -27,7 +27,10 @@ import { getActiveTTSConfig, ELEVENLABS_CONFIG, DEEPGRAM_CONFIG } from '../confi
27
27
  * @param {Function} props.onSpeechEnd - Callback when speech ends
28
28
  * @param {string} props.className - Additional CSS classes
29
29
  * @param {Object} props.style - Additional inline styles
30
- * @param {Object} props.animations - Object mapping animation names to FBX file paths
30
+ * @param {Object} props.animations - Object mapping animation names to FBX file paths, or animation groups
31
+ * Can be: { "dance": "/animations/dance.fbx" } (single animation)
32
+ * Or: { "talking": ["/animations/talk1.fbx", "/animations/talk2.fbx"] } (group)
33
+ * @param {string} props.autoAnimationGroup - Animation group to automatically play when speaking (e.g., "talking")
31
34
  * @param {boolean} props.autoSpeak - Whether to automatically speak the text prop when ready
32
35
  * @param {Object} ref - Ref to access component methods
33
36
  */
@@ -51,6 +54,7 @@ const SimpleTalkingAvatar = forwardRef(({
51
54
  className = "",
52
55
  style = {},
53
56
  animations = {},
57
+ autoAnimationGroup = null, // e.g., "talking" - will randomly select from this group when speaking
54
58
  autoSpeak = false
55
59
  }, ref) => {
56
60
  const containerRef = useRef(null);
@@ -220,6 +224,44 @@ const SimpleTalkingAvatar = forwardRef(({
220
224
  }
221
225
  }, []);
222
226
 
227
+ // Helper function to get random animation from a group
228
+ const getRandomAnimation = useCallback((groupName) => {
229
+ if (!animations || !animations[groupName]) {
230
+ return null;
231
+ }
232
+
233
+ const group = animations[groupName];
234
+
235
+ // If it's an array, randomly select one
236
+ if (Array.isArray(group) && group.length > 0) {
237
+ const randomIndex = Math.floor(Math.random() * group.length);
238
+ return group[randomIndex];
239
+ }
240
+
241
+ // If it's a string, return it directly
242
+ if (typeof group === 'string') {
243
+ return group;
244
+ }
245
+
246
+ return null;
247
+ }, [animations]);
248
+
249
+ // Helper function to play random animation from a group
250
+ const playRandomAnimation = useCallback((groupName, disablePositionLock = false) => {
251
+ const animationPath = getRandomAnimation(groupName);
252
+ if (animationPath && talkingHeadRef.current) {
253
+ try {
254
+ talkingHeadRef.current.playAnimation(animationPath, null, 10, 0, 0.01, disablePositionLock);
255
+ console.log(`Playing random animation from "${groupName}" group:`, animationPath);
256
+ return animationPath;
257
+ } catch (error) {
258
+ console.warn(`Failed to play random animation from "${groupName}" group:`, error);
259
+ return null;
260
+ }
261
+ }
262
+ return null;
263
+ }, [getRandomAnimation]);
264
+
223
265
  // Speak text with proper callback handling
224
266
  const speakText = useCallback(async (textToSpeak, options = {}) => {
225
267
  if (!talkingHeadRef.current || !isReady) {
@@ -235,6 +277,13 @@ const SimpleTalkingAvatar = forwardRef(({
235
277
  // Always resume audio context first (required for user interaction)
236
278
  await resumeAudioContext();
237
279
 
280
+ // Play random animation from autoAnimationGroup if specified
281
+ // Check both autoAnimationGroup prop and options.animationGroup
282
+ const animationGroup = options.animationGroup || autoAnimationGroup;
283
+ if (animationGroup && !options.skipAnimation) {
284
+ playRandomAnimation(animationGroup);
285
+ }
286
+
238
287
  // Reset speech progress tracking
239
288
  speechProgressRef.current = { remainingText: null, originalText: null, options: null };
240
289
  originalSentencesRef.current = [];
@@ -279,7 +328,7 @@ const SimpleTalkingAvatar = forwardRef(({
279
328
  console.error('Error speaking text:', err);
280
329
  setError(err.message || 'Failed to speak text');
281
330
  }
282
- }, [isReady, onSpeechEnd, resumeAudioContext]);
331
+ }, [isReady, onSpeechEnd, resumeAudioContext, autoAnimationGroup, playRandomAnimation]);
283
332
 
284
333
  // Auto-speak text when ready and autoSpeak is true
285
334
  useEffect(() => {
@@ -394,6 +443,12 @@ const SimpleTalkingAvatar = forwardRef(({
394
443
  talkingHeadRef.current.playAnimation(animationName, null, 10, 0, 0.01, disablePositionLock);
395
444
  }
396
445
  },
446
+ playRandomAnimation: (groupName, disablePositionLock = false) => {
447
+ return playRandomAnimation(groupName, disablePositionLock);
448
+ },
449
+ getRandomAnimation: (groupName) => {
450
+ return getRandomAnimation(groupName);
451
+ },
397
452
  playReaction: (reactionType) => talkingHeadRef.current?.playReaction(reactionType),
398
453
  playCelebration: () => talkingHeadRef.current?.playCelebration(),
399
454
  setShowFullAvatar: (show) => {