@sage-rsc/talking-head-react 1.0.21 → 1.0.23
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/{fbxAnimationLoader-CVM-Jv5Y.cjs → fbxAnimationLoader-CPrVtkyX.cjs} +1 -1
- package/dist/{fbxAnimationLoader-DzYmybHd.js → fbxAnimationLoader-Ceym4Gbp.js} +1 -1
- package/dist/{index-BeFzGp0g.cjs → index-Bwi0p5fF.cjs} +4 -4
- package/dist/{index-3_k5n4iL.js → index-CiNyTJ1A.js} +748 -728
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/CurriculumLearning.jsx +80 -39
- package/src/components/TalkingHeadAvatar.jsx +10 -4
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./index-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./index-Bwi0p5fF.cjs");exports.CurriculumLearning=i.CurriculumLearning;exports.TalkingHeadAvatar=i.TalkingHeadAvatar;exports.TalkingHeadComponent=i.TalkingHeadComponent;exports.animations=i.animations;exports.getActiveTTSConfig=i.getActiveTTSConfig;exports.getAnimation=i.getAnimation;exports.getVoiceOptions=i.getVoiceOptions;exports.hasAnimation=i.hasAnimation;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -80,7 +80,8 @@ const CurriculumLearning = forwardRef(({
|
|
|
80
80
|
bodyMovement: avatarConfig.bodyMovement || "gesturing",
|
|
81
81
|
movementIntensity: avatarConfig.movementIntensity || 0.7,
|
|
82
82
|
showFullAvatar: avatarConfig.showFullAvatar !== undefined ? avatarConfig.showFullAvatar : true,
|
|
83
|
-
animations: animations
|
|
83
|
+
animations: animations,
|
|
84
|
+
lipsyncLang: 'en' // Default lipsync language
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
// Helper to get current lesson/question
|
|
@@ -143,9 +144,9 @@ const CurriculumLearning = forwardRef(({
|
|
|
143
144
|
avatarRef.current.playCelebration();
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
|
-
avatarRef.current.speakText(feedbackMessage);
|
|
147
|
+
avatarRef.current.speakText(feedbackMessage, { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
147
148
|
}
|
|
148
|
-
}, [animations.lessonComplete]);
|
|
149
|
+
}, [animations.lessonComplete, defaultAvatarConfig]);
|
|
149
150
|
|
|
150
151
|
// Complete entire curriculum
|
|
151
152
|
const completeCurriculum = useCallback(() => {
|
|
@@ -165,9 +166,9 @@ const CurriculumLearning = forwardRef(({
|
|
|
165
166
|
avatarRef.current.playCelebration();
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
|
-
avatarRef.current.speakText("Amazing! You've completed the entire curriculum! You're now ready to move on to more advanced topics. Well done!");
|
|
169
|
+
avatarRef.current.speakText("Amazing! You've completed the entire curriculum! You're now ready to move on to more advanced topics. Well done!", { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
169
170
|
}
|
|
170
|
-
}, [animations.curriculumComplete, curriculum]);
|
|
171
|
+
}, [animations.curriculumComplete, curriculum, defaultAvatarConfig]);
|
|
171
172
|
|
|
172
173
|
// Start asking questions
|
|
173
174
|
const startQuestions = useCallback(() => {
|
|
@@ -176,7 +177,17 @@ const CurriculumLearning = forwardRef(({
|
|
|
176
177
|
stateRef.current.currentQuestionIndex = 0;
|
|
177
178
|
stateRef.current.totalQuestions = currentLesson?.questions?.length || 0;
|
|
178
179
|
|
|
179
|
-
|
|
180
|
+
// Trigger custom action immediately (before avatar speaks) - includes first question
|
|
181
|
+
const firstQuestion = getCurrentQuestion();
|
|
182
|
+
callbacksRef.current.onCustomAction({
|
|
183
|
+
type: 'questionStart',
|
|
184
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
185
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
186
|
+
totalQuestions: stateRef.current.totalQuestions,
|
|
187
|
+
question: firstQuestion // Include first question in event
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (avatarRef.current) {
|
|
180
191
|
avatarRef.current.setMood("curious");
|
|
181
192
|
|
|
182
193
|
// Play custom animation if available
|
|
@@ -188,17 +199,18 @@ const CurriculumLearning = forwardRef(({
|
|
|
188
199
|
}
|
|
189
200
|
}
|
|
190
201
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
202
|
+
if (firstQuestion) {
|
|
203
|
+
// If there's a question, introduce it directly
|
|
204
|
+
if (firstQuestion.type === 'code_test') {
|
|
205
|
+
avatarRef.current.speakText(`Let's test your coding skills! Here's your first challenge: ${firstQuestion.question}`, { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
206
|
+
} else {
|
|
207
|
+
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
avatarRef.current.speakText("Now let me ask you some questions to test your understanding.", { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
211
|
+
}
|
|
200
212
|
}
|
|
201
|
-
}, [animations.questionStart, getCurrentLesson]);
|
|
213
|
+
}, [animations.questionStart, getCurrentLesson, getCurrentQuestion, defaultAvatarConfig]);
|
|
202
214
|
|
|
203
215
|
// Move to next question
|
|
204
216
|
const nextQuestion = useCallback(() => {
|
|
@@ -206,33 +218,44 @@ const CurriculumLearning = forwardRef(({
|
|
|
206
218
|
if (stateRef.current.currentQuestionIndex < (currentLesson?.questions?.length || 0) - 1) {
|
|
207
219
|
stateRef.current.currentQuestionIndex += 1;
|
|
208
220
|
|
|
209
|
-
|
|
210
|
-
|
|
221
|
+
// Trigger custom action BEFORE speaking (so UI can update immediately)
|
|
222
|
+
const nextQuestionObj = getCurrentQuestion();
|
|
223
|
+
callbacksRef.current.onCustomAction({
|
|
224
|
+
type: 'nextQuestion',
|
|
225
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
226
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
227
|
+
questionIndex: stateRef.current.currentQuestionIndex,
|
|
228
|
+
question: nextQuestionObj
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (avatarRef.current) {
|
|
232
|
+
avatarRef.current.setMood("happy");
|
|
211
233
|
avatarRef.current.setBodyMovement("idle");
|
|
212
234
|
|
|
213
235
|
// Play custom animation if available
|
|
214
236
|
if (animations.nextQuestion) {
|
|
215
|
-
|
|
237
|
+
try {
|
|
216
238
|
avatarRef.current.playAnimation(animations.nextQuestion, true);
|
|
217
|
-
|
|
239
|
+
} catch (error) {
|
|
218
240
|
console.warn('Failed to play nextQuestion animation:', error);
|
|
219
241
|
}
|
|
220
242
|
}
|
|
221
243
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
});
|
|
244
|
+
if (nextQuestionObj) {
|
|
245
|
+
// Speak the question text directly if it's a code test, otherwise introduce it
|
|
246
|
+
if (nextQuestionObj.type === 'code_test') {
|
|
247
|
+
avatarRef.current.speakText(`Here's your next coding challenge: ${nextQuestionObj.question}`, { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
248
|
+
} else {
|
|
249
|
+
avatarRef.current.speakText(`Here's the next question: ${nextQuestionObj.question}`, { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
avatarRef.current.speakText("Here's the next question.", { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
253
|
+
}
|
|
231
254
|
}
|
|
232
255
|
} else {
|
|
233
256
|
completeLesson();
|
|
234
257
|
}
|
|
235
|
-
}, [animations.nextQuestion, getCurrentLesson, completeLesson]);
|
|
258
|
+
}, [animations.nextQuestion, getCurrentLesson, completeLesson, getCurrentQuestion, defaultAvatarConfig]);
|
|
236
259
|
|
|
237
260
|
// Move to next lesson
|
|
238
261
|
const nextLesson = useCallback(() => {
|
|
@@ -245,12 +268,12 @@ const CurriculumLearning = forwardRef(({
|
|
|
245
268
|
stateRef.current.totalQuestions = 0;
|
|
246
269
|
|
|
247
270
|
if (avatarRef.current) {
|
|
248
|
-
avatarRef.current.speakText("Let's move on to the next lesson!");
|
|
271
|
+
avatarRef.current.speakText("Let's move on to the next lesson!", { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
249
272
|
}
|
|
250
273
|
} else {
|
|
251
274
|
completeCurriculum();
|
|
252
275
|
}
|
|
253
|
-
}, [curriculum, completeCurriculum]);
|
|
276
|
+
}, [curriculum, completeCurriculum, defaultAvatarConfig]);
|
|
254
277
|
|
|
255
278
|
// Start teaching the lesson
|
|
256
279
|
const startTeaching = useCallback(() => {
|
|
@@ -276,7 +299,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
276
299
|
avatarRef.current.setBodyMovement("gesturing");
|
|
277
300
|
}
|
|
278
301
|
|
|
279
|
-
avatarRef.current.speakText(currentLesson.avatar_script);
|
|
302
|
+
avatarRef.current.speakText(currentLesson.avatar_script, { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
280
303
|
|
|
281
304
|
callbacksRef.current.onLessonStart({
|
|
282
305
|
moduleIndex: stateRef.current.currentModuleIndex,
|
|
@@ -302,7 +325,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
302
325
|
}
|
|
303
326
|
}, 8000);
|
|
304
327
|
}
|
|
305
|
-
}, [animations.teaching, getCurrentLesson, startQuestions, completeLesson]);
|
|
328
|
+
}, [animations.teaching, getCurrentLesson, startQuestions, completeLesson, defaultAvatarConfig]);
|
|
306
329
|
|
|
307
330
|
// Handle answer selection
|
|
308
331
|
const handleAnswerSelect = useCallback((answer) => {
|
|
@@ -336,7 +359,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
336
359
|
const successMessage = currentQuestion.type === "code_test"
|
|
337
360
|
? `Great job! Your code passed all the tests! ${currentQuestion.explanation || ''}`
|
|
338
361
|
: `Excellent! That's correct! ${currentQuestion.explanation || ''}`;
|
|
339
|
-
avatarRef.current.speakText(successMessage);
|
|
362
|
+
avatarRef.current.speakText(successMessage, { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
340
363
|
} else {
|
|
341
364
|
avatarRef.current.setMood("sad");
|
|
342
365
|
if (animations.incorrect) {
|
|
@@ -350,10 +373,28 @@ const CurriculumLearning = forwardRef(({
|
|
|
350
373
|
const failureMessage = currentQuestion.type === "code_test"
|
|
351
374
|
? `Your code didn't pass all the tests. ${currentQuestion.explanation || 'Try again!'}`
|
|
352
375
|
: `Not quite right, but don't worry! ${currentQuestion.explanation || ''} Let's move on to the next question.`;
|
|
353
|
-
avatarRef.current.speakText(failureMessage);
|
|
376
|
+
avatarRef.current.speakText(failureMessage, { lipsyncLang: defaultAvatarConfig.lipsyncLang });
|
|
354
377
|
}
|
|
378
|
+
|
|
379
|
+
// Automatically move to next question after avatar finishes speaking
|
|
380
|
+
// Estimate speaking time: ~150 characters per second, minimum 2 seconds
|
|
381
|
+
const messageLength = (isCorrect
|
|
382
|
+
? (currentQuestion.type === "code_test"
|
|
383
|
+
? `Great job! Your code passed all the tests! ${currentQuestion.explanation || ''}`
|
|
384
|
+
: `Excellent! That's correct! ${currentQuestion.explanation || ''}`)
|
|
385
|
+
: (currentQuestion.type === "code_test"
|
|
386
|
+
? `Your code didn't pass all the tests. ${currentQuestion.explanation || 'Try again!'}`
|
|
387
|
+
: `Not quite right, but don't worry! ${currentQuestion.explanation || ''} Let's move on to the next question.`)).length;
|
|
388
|
+
const estimatedTime = Math.max(2000, (messageLength / 150) * 1000) + 500; // Add 500ms buffer
|
|
389
|
+
|
|
390
|
+
setTimeout(() => {
|
|
391
|
+
nextQuestion();
|
|
392
|
+
}, estimatedTime);
|
|
393
|
+
} else {
|
|
394
|
+
// If avatar not ready, move to next question immediately
|
|
395
|
+
nextQuestion();
|
|
355
396
|
}
|
|
356
|
-
}, [animations.correct, animations.incorrect, getCurrentQuestion, checkAnswer]);
|
|
397
|
+
}, [animations.correct, animations.incorrect, getCurrentQuestion, checkAnswer, nextQuestion, defaultAvatarConfig]);
|
|
357
398
|
|
|
358
399
|
// Handle code test result submission
|
|
359
400
|
const handleCodeTestResult = useCallback((testResult) => {
|
|
@@ -441,10 +482,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
441
482
|
getAvatarRef: () => avatarRef.current,
|
|
442
483
|
|
|
443
484
|
// Convenience methods that delegate to avatar (always check current ref)
|
|
444
|
-
speakText: async (text) => {
|
|
485
|
+
speakText: async (text, options = {}) => {
|
|
445
486
|
// Ensure audio context is resumed before speaking
|
|
446
487
|
await avatarRef.current?.resumeAudioContext?.();
|
|
447
|
-
avatarRef.current?.speakText(text);
|
|
488
|
+
avatarRef.current?.speakText(text, { ...options, lipsyncLang: options.lipsyncLang || defaultAvatarConfig.lipsyncLang });
|
|
448
489
|
},
|
|
449
490
|
resumeAudioContext: async () => {
|
|
450
491
|
// Try to resume through avatar ref first
|
|
@@ -256,17 +256,23 @@ const TalkingHeadAvatar = forwardRef(({
|
|
|
256
256
|
}
|
|
257
257
|
}, []);
|
|
258
258
|
|
|
259
|
-
const speakText = useCallback(async (textToSpeak) => {
|
|
259
|
+
const speakText = useCallback(async (textToSpeak, options = {}) => {
|
|
260
260
|
if (talkingHeadRef.current && isReady) {
|
|
261
261
|
try {
|
|
262
262
|
// Always resume audio context first (required for user interaction)
|
|
263
263
|
await resumeAudioContext();
|
|
264
264
|
|
|
265
|
+
// Ensure lipsyncLang is in options if not provided
|
|
266
|
+
const speakOptions = {
|
|
267
|
+
...options,
|
|
268
|
+
lipsyncLang: options.lipsyncLang || defaultAvatarConfig.lipsyncLang || 'en'
|
|
269
|
+
};
|
|
270
|
+
|
|
265
271
|
if (talkingHeadRef.current.lipsync && Object.keys(talkingHeadRef.current.lipsync).length > 0) {
|
|
266
272
|
if (talkingHeadRef.current.setSlowdownRate) {
|
|
267
273
|
talkingHeadRef.current.setSlowdownRate(1.05);
|
|
268
274
|
}
|
|
269
|
-
talkingHeadRef.current.speakText(textToSpeak);
|
|
275
|
+
talkingHeadRef.current.speakText(textToSpeak, speakOptions);
|
|
270
276
|
} else {
|
|
271
277
|
setTimeout(async () => {
|
|
272
278
|
await resumeAudioContext();
|
|
@@ -274,7 +280,7 @@ const TalkingHeadAvatar = forwardRef(({
|
|
|
274
280
|
if (talkingHeadRef.current.setSlowdownRate) {
|
|
275
281
|
talkingHeadRef.current.setSlowdownRate(1.05);
|
|
276
282
|
}
|
|
277
|
-
talkingHeadRef.current.speakText(textToSpeak);
|
|
283
|
+
talkingHeadRef.current.speakText(textToSpeak, speakOptions);
|
|
278
284
|
}
|
|
279
285
|
}, 500);
|
|
280
286
|
}
|
|
@@ -283,7 +289,7 @@ const TalkingHeadAvatar = forwardRef(({
|
|
|
283
289
|
setError(err.message || 'Failed to speak text');
|
|
284
290
|
}
|
|
285
291
|
}
|
|
286
|
-
}, [isReady, resumeAudioContext]);
|
|
292
|
+
}, [isReady, resumeAudioContext, defaultAvatarConfig.lipsyncLang]);
|
|
287
293
|
|
|
288
294
|
const stopSpeaking = useCallback(() => {
|
|
289
295
|
if (talkingHeadRef.current) {
|