@sage-rsc/talking-head-react 1.0.51 → 1.0.53
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 +990 -910
- package/package.json +1 -1
- package/src/components/CurriculumLearning.jsx +182 -13
package/package.json
CHANGED
|
@@ -299,9 +299,11 @@ const CurriculumLearning = forwardRef(({
|
|
|
299
299
|
});
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
avatarRef.current
|
|
302
|
+
// Function to speak the first question
|
|
303
|
+
const speakFirstQuestion = () => {
|
|
304
|
+
if (!avatarRef.current || !firstQuestion) return;
|
|
305
|
+
|
|
306
|
+
avatarRef.current.setMood("happy");
|
|
305
307
|
|
|
306
308
|
// Play custom animation if available
|
|
307
309
|
if (animations.questionStart) {
|
|
@@ -324,16 +326,30 @@ const CurriculumLearning = forwardRef(({
|
|
|
324
326
|
} else {
|
|
325
327
|
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
326
328
|
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// Try to speak immediately if avatar is ready
|
|
332
|
+
if (avatarRef.current && avatarRef.current.isReady && firstQuestion) {
|
|
333
|
+
speakFirstQuestion();
|
|
327
334
|
} else if (avatarRef.current && avatarRef.current.isReady) {
|
|
335
|
+
// No question but avatar ready - just announce
|
|
328
336
|
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
329
337
|
avatarRef.current.speakText("Now let me ask you some questions to test your understanding.", { lipsyncLang: config.lipsyncLang });
|
|
330
338
|
} else {
|
|
331
|
-
// Avatar not ready yet
|
|
332
|
-
|
|
333
|
-
if (
|
|
334
|
-
|
|
339
|
+
// Avatar not ready yet - wait for it to be ready, then speak
|
|
340
|
+
const checkReady = setInterval(() => {
|
|
341
|
+
if (avatarRef.current && avatarRef.current.isReady) {
|
|
342
|
+
clearInterval(checkReady);
|
|
343
|
+
if (firstQuestion) {
|
|
344
|
+
speakFirstQuestion();
|
|
345
|
+
}
|
|
335
346
|
}
|
|
336
347
|
}, 100);
|
|
348
|
+
|
|
349
|
+
// Timeout after 5 seconds
|
|
350
|
+
setTimeout(() => {
|
|
351
|
+
clearInterval(checkReady);
|
|
352
|
+
}, 5000);
|
|
337
353
|
}
|
|
338
354
|
}, [animations.questionStart, getCurrentLesson, getCurrentQuestion]);
|
|
339
355
|
|
|
@@ -357,15 +373,18 @@ const CurriculumLearning = forwardRef(({
|
|
|
357
373
|
});
|
|
358
374
|
}
|
|
359
375
|
|
|
360
|
-
|
|
376
|
+
// Function to speak the next question
|
|
377
|
+
const speakNextQuestion = () => {
|
|
378
|
+
if (!avatarRef.current || !nextQuestionObj) return;
|
|
379
|
+
|
|
361
380
|
avatarRef.current.setMood("happy");
|
|
362
381
|
avatarRef.current.setBodyMovement("idle");
|
|
363
|
-
|
|
382
|
+
|
|
364
383
|
// Play custom animation if available
|
|
365
384
|
if (animations.nextQuestion) {
|
|
366
385
|
try {
|
|
367
386
|
avatarRef.current.playAnimation(animations.nextQuestion, true);
|
|
368
|
-
|
|
387
|
+
} catch (error) {
|
|
369
388
|
console.warn('Failed to play nextQuestion animation:', error);
|
|
370
389
|
}
|
|
371
390
|
}
|
|
@@ -385,11 +404,29 @@ const CurriculumLearning = forwardRef(({
|
|
|
385
404
|
avatarRef.current.speakText(`Now let's try this one: ${nextQuestionObj.question}`, {
|
|
386
405
|
lipsyncLang: config.lipsyncLang
|
|
387
406
|
});
|
|
388
|
-
|
|
407
|
+
} else {
|
|
389
408
|
avatarRef.current.speakText(`Here's the next question: ${nextQuestionObj.question}`, {
|
|
390
409
|
lipsyncLang: config.lipsyncLang
|
|
391
410
|
});
|
|
392
411
|
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Try to speak immediately if avatar is ready
|
|
415
|
+
if (avatarRef.current && avatarRef.current.isReady && nextQuestionObj) {
|
|
416
|
+
speakNextQuestion();
|
|
417
|
+
} else if (nextQuestionObj) {
|
|
418
|
+
// Avatar not ready yet - wait for it to be ready, then speak
|
|
419
|
+
const checkReady = setInterval(() => {
|
|
420
|
+
if (avatarRef.current && avatarRef.current.isReady) {
|
|
421
|
+
clearInterval(checkReady);
|
|
422
|
+
speakNextQuestion();
|
|
423
|
+
}
|
|
424
|
+
}, 100);
|
|
425
|
+
|
|
426
|
+
// Timeout after 5 seconds
|
|
427
|
+
setTimeout(() => {
|
|
428
|
+
clearInterval(checkReady);
|
|
429
|
+
}, 5000);
|
|
393
430
|
}
|
|
394
431
|
} else {
|
|
395
432
|
// No more questions - notify parent that all questions are done
|
|
@@ -438,7 +475,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
438
475
|
|
|
439
476
|
if (avatarRef.current) {
|
|
440
477
|
avatarRef.current.setMood("happy");
|
|
441
|
-
|
|
478
|
+
avatarRef.current.setBodyMovement("idle");
|
|
442
479
|
}
|
|
443
480
|
} else {
|
|
444
481
|
// No more lessons in current module - check if there's a next module
|
|
@@ -610,7 +647,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
610
647
|
if (animations.incorrect) {
|
|
611
648
|
try {
|
|
612
649
|
avatarRef.current.playAnimation(animations.incorrect, true);
|
|
613
|
-
|
|
650
|
+
} catch (error) {
|
|
614
651
|
avatarRef.current.setBodyMovement("idle");
|
|
615
652
|
}
|
|
616
653
|
}
|
|
@@ -695,6 +732,136 @@ const CurriculumLearning = forwardRef(({
|
|
|
695
732
|
}
|
|
696
733
|
}, [getCurrentQuestion, checkAnswer]);
|
|
697
734
|
|
|
735
|
+
// Move to previous question
|
|
736
|
+
const previousQuestion = useCallback(() => {
|
|
737
|
+
if (stateRef.current.currentQuestionIndex > 0) {
|
|
738
|
+
stateRef.current.currentQuestionIndex -= 1;
|
|
739
|
+
|
|
740
|
+
// Trigger custom action for UI update
|
|
741
|
+
const prevQuestionObj = getCurrentQuestion();
|
|
742
|
+
if (prevQuestionObj) {
|
|
743
|
+
callbacksRef.current.onCustomAction({
|
|
744
|
+
type: 'questionStart',
|
|
745
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
746
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
747
|
+
questionIndex: stateRef.current.currentQuestionIndex,
|
|
748
|
+
totalQuestions: stateRef.current.totalQuestions,
|
|
749
|
+
question: prevQuestionObj
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Function to speak the previous question
|
|
754
|
+
const speakPrevQuestion = () => {
|
|
755
|
+
if (!avatarRef.current || !prevQuestionObj) return;
|
|
756
|
+
|
|
757
|
+
avatarRef.current.setMood("happy");
|
|
758
|
+
avatarRef.current.setBodyMovement("idle");
|
|
759
|
+
|
|
760
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
761
|
+
|
|
762
|
+
// Speak the question text
|
|
763
|
+
if (prevQuestionObj.type === 'code_test') {
|
|
764
|
+
avatarRef.current.speakText(`Let's go back to this coding challenge: ${prevQuestionObj.question}`, {
|
|
765
|
+
lipsyncLang: config.lipsyncLang
|
|
766
|
+
});
|
|
767
|
+
} else {
|
|
768
|
+
avatarRef.current.speakText(`Going back to: ${prevQuestionObj.question}`, {
|
|
769
|
+
lipsyncLang: config.lipsyncLang
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
// Try to speak immediately if avatar is ready
|
|
775
|
+
if (avatarRef.current && avatarRef.current.isReady && prevQuestionObj) {
|
|
776
|
+
speakPrevQuestion();
|
|
777
|
+
} else if (prevQuestionObj) {
|
|
778
|
+
// Avatar not ready yet - wait for it to be ready, then speak
|
|
779
|
+
const checkReady = setInterval(() => {
|
|
780
|
+
if (avatarRef.current && avatarRef.current.isReady) {
|
|
781
|
+
clearInterval(checkReady);
|
|
782
|
+
speakPrevQuestion();
|
|
783
|
+
}
|
|
784
|
+
}, 100);
|
|
785
|
+
|
|
786
|
+
setTimeout(() => {
|
|
787
|
+
clearInterval(checkReady);
|
|
788
|
+
}, 5000);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}, [getCurrentQuestion]);
|
|
792
|
+
|
|
793
|
+
// Move to previous lesson
|
|
794
|
+
const previousLesson = useCallback(() => {
|
|
795
|
+
const curriculum = curriculumRef.current || { modules: [] };
|
|
796
|
+
const currentModule = curriculum.modules[stateRef.current.currentModuleIndex];
|
|
797
|
+
|
|
798
|
+
// Check if there's a previous lesson in the current module
|
|
799
|
+
const hasPrevLessonInModule = stateRef.current.currentLessonIndex > 0;
|
|
800
|
+
|
|
801
|
+
if (hasPrevLessonInModule) {
|
|
802
|
+
// Move to previous lesson in current module
|
|
803
|
+
stateRef.current.currentLessonIndex -= 1;
|
|
804
|
+
stateRef.current.currentQuestionIndex = 0;
|
|
805
|
+
stateRef.current.lessonCompleted = false;
|
|
806
|
+
stateRef.current.isQuestionMode = false;
|
|
807
|
+
stateRef.current.isTeaching = false;
|
|
808
|
+
stateRef.current.score = 0;
|
|
809
|
+
stateRef.current.totalQuestions = 0;
|
|
810
|
+
|
|
811
|
+
// Notify parent that lesson has changed
|
|
812
|
+
callbacksRef.current.onCustomAction({
|
|
813
|
+
type: 'lessonStart',
|
|
814
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
815
|
+
lessonIndex: stateRef.current.currentLessonIndex
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
callbacksRef.current.onLessonStart({
|
|
819
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
820
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
821
|
+
lesson: getCurrentLesson()
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
if (avatarRef.current) {
|
|
825
|
+
avatarRef.current.setMood("happy");
|
|
826
|
+
avatarRef.current.setBodyMovement("idle");
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
// No previous lesson in current module - check if there's a previous module
|
|
830
|
+
const hasPrevModule = stateRef.current.currentModuleIndex > 0;
|
|
831
|
+
|
|
832
|
+
if (hasPrevModule) {
|
|
833
|
+
// Move to last lesson of previous module
|
|
834
|
+
const prevModule = curriculum.modules[stateRef.current.currentModuleIndex - 1];
|
|
835
|
+
stateRef.current.currentModuleIndex -= 1;
|
|
836
|
+
stateRef.current.currentLessonIndex = (prevModule?.lessons?.length || 1) - 1;
|
|
837
|
+
stateRef.current.currentQuestionIndex = 0;
|
|
838
|
+
stateRef.current.lessonCompleted = false;
|
|
839
|
+
stateRef.current.isQuestionMode = false;
|
|
840
|
+
stateRef.current.isTeaching = false;
|
|
841
|
+
stateRef.current.score = 0;
|
|
842
|
+
stateRef.current.totalQuestions = 0;
|
|
843
|
+
|
|
844
|
+
// Notify parent that lesson has changed
|
|
845
|
+
callbacksRef.current.onCustomAction({
|
|
846
|
+
type: 'lessonStart',
|
|
847
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
848
|
+
lessonIndex: stateRef.current.currentLessonIndex
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
callbacksRef.current.onLessonStart({
|
|
852
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
853
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
854
|
+
lesson: getCurrentLesson()
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
if (avatarRef.current) {
|
|
858
|
+
avatarRef.current.setMood("happy");
|
|
859
|
+
avatarRef.current.setBodyMovement("idle");
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}, [getCurrentLesson]);
|
|
864
|
+
|
|
698
865
|
// Reset curriculum
|
|
699
866
|
const resetCurriculum = useCallback(() => {
|
|
700
867
|
stateRef.current.currentModuleIndex = 0;
|
|
@@ -744,7 +911,9 @@ const CurriculumLearning = forwardRef(({
|
|
|
744
911
|
handleAnswerSelect,
|
|
745
912
|
handleCodeTestResult,
|
|
746
913
|
nextQuestion,
|
|
914
|
+
previousQuestion,
|
|
747
915
|
nextLesson,
|
|
916
|
+
previousLesson,
|
|
748
917
|
completeLesson,
|
|
749
918
|
completeCurriculum,
|
|
750
919
|
resetCurriculum,
|