@sage-rsc/talking-head-react 1.0.52 → 1.0.54

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.0.52",
3
+ "version": "1.0.54",
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",
@@ -303,7 +303,7 @@ const CurriculumLearning = forwardRef(({
303
303
  const speakFirstQuestion = () => {
304
304
  if (!avatarRef.current || !firstQuestion) return;
305
305
 
306
- avatarRef.current.setMood("curious");
306
+ avatarRef.current.setMood("happy");
307
307
 
308
308
  // Play custom animation if available
309
309
  if (animations.questionStart) {
@@ -404,7 +404,7 @@ const CurriculumLearning = forwardRef(({
404
404
  avatarRef.current.speakText(`Now let's try this one: ${nextQuestionObj.question}`, {
405
405
  lipsyncLang: config.lipsyncLang
406
406
  });
407
- } else {
407
+ } else {
408
408
  avatarRef.current.speakText(`Here's the next question: ${nextQuestionObj.question}`, {
409
409
  lipsyncLang: config.lipsyncLang
410
410
  });
@@ -459,11 +459,18 @@ const CurriculumLearning = forwardRef(({
459
459
  stateRef.current.score = 0;
460
460
  stateRef.current.totalQuestions = 0;
461
461
 
462
+ // Check if there's a next lesson available after this one
463
+ const currentModuleAfterMove = curriculum.modules[stateRef.current.currentModuleIndex];
464
+ const hasNextLessonInModuleAfterMove = stateRef.current.currentLessonIndex < (currentModuleAfterMove?.lessons?.length || 0) - 1;
465
+ const hasNextModuleAfterMove = stateRef.current.currentModuleIndex < (curriculum.modules?.length || 0) - 1;
466
+ const hasNextLessonAfterMove = hasNextLessonInModuleAfterMove || hasNextModuleAfterMove;
467
+
462
468
  // Clear current question in UI and notify parent
463
469
  callbacksRef.current.onCustomAction({
464
470
  type: 'lessonStart',
465
471
  moduleIndex: stateRef.current.currentModuleIndex,
466
- lessonIndex: stateRef.current.currentLessonIndex
472
+ lessonIndex: stateRef.current.currentLessonIndex,
473
+ hasNextLesson: hasNextLessonAfterMove
467
474
  });
468
475
 
469
476
  // Notify parent that lesson has changed - parent decides when to start teaching
@@ -475,7 +482,7 @@ const CurriculumLearning = forwardRef(({
475
482
 
476
483
  if (avatarRef.current) {
477
484
  avatarRef.current.setMood("happy");
478
- avatarRef.current.setBodyMovement("idle");
485
+ avatarRef.current.setBodyMovement("idle");
479
486
  }
480
487
  } else {
481
488
  // No more lessons in current module - check if there's a next module
@@ -492,11 +499,18 @@ const CurriculumLearning = forwardRef(({
492
499
  stateRef.current.score = 0;
493
500
  stateRef.current.totalQuestions = 0;
494
501
 
502
+ // Check if there's a next lesson available after this one
503
+ const currentModuleAfterMove = curriculum.modules[stateRef.current.currentModuleIndex];
504
+ const hasNextLessonInModuleAfterMove = stateRef.current.currentLessonIndex < (currentModuleAfterMove?.lessons?.length || 0) - 1;
505
+ const hasNextModuleAfterMove = stateRef.current.currentModuleIndex < (curriculum.modules?.length || 0) - 1;
506
+ const hasNextLessonAfterMove = hasNextLessonInModuleAfterMove || hasNextModuleAfterMove;
507
+
495
508
  // Clear current question in UI and notify parent
496
509
  callbacksRef.current.onCustomAction({
497
510
  type: 'lessonStart',
498
511
  moduleIndex: stateRef.current.currentModuleIndex,
499
- lessonIndex: stateRef.current.currentLessonIndex
512
+ lessonIndex: stateRef.current.currentLessonIndex,
513
+ hasNextLesson: hasNextLessonAfterMove
500
514
  });
501
515
 
502
516
  // Notify parent that lesson has changed - parent decides when to start teaching
@@ -647,7 +661,7 @@ const CurriculumLearning = forwardRef(({
647
661
  if (animations.incorrect) {
648
662
  try {
649
663
  avatarRef.current.playAnimation(animations.incorrect, true);
650
- } catch (error) {
664
+ } catch (error) {
651
665
  avatarRef.current.setBodyMovement("idle");
652
666
  }
653
667
  }
@@ -732,6 +746,136 @@ const CurriculumLearning = forwardRef(({
732
746
  }
733
747
  }, [getCurrentQuestion, checkAnswer]);
734
748
 
749
+ // Move to previous question
750
+ const previousQuestion = useCallback(() => {
751
+ if (stateRef.current.currentQuestionIndex > 0) {
752
+ stateRef.current.currentQuestionIndex -= 1;
753
+
754
+ // Trigger custom action for UI update
755
+ const prevQuestionObj = getCurrentQuestion();
756
+ if (prevQuestionObj) {
757
+ callbacksRef.current.onCustomAction({
758
+ type: 'questionStart',
759
+ moduleIndex: stateRef.current.currentModuleIndex,
760
+ lessonIndex: stateRef.current.currentLessonIndex,
761
+ questionIndex: stateRef.current.currentQuestionIndex,
762
+ totalQuestions: stateRef.current.totalQuestions,
763
+ question: prevQuestionObj
764
+ });
765
+ }
766
+
767
+ // Function to speak the previous question
768
+ const speakPrevQuestion = () => {
769
+ if (!avatarRef.current || !prevQuestionObj) return;
770
+
771
+ avatarRef.current.setMood("happy");
772
+ avatarRef.current.setBodyMovement("idle");
773
+
774
+ const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
775
+
776
+ // Speak the question text
777
+ if (prevQuestionObj.type === 'code_test') {
778
+ avatarRef.current.speakText(`Let's go back to this coding challenge: ${prevQuestionObj.question}`, {
779
+ lipsyncLang: config.lipsyncLang
780
+ });
781
+ } else {
782
+ avatarRef.current.speakText(`Going back to: ${prevQuestionObj.question}`, {
783
+ lipsyncLang: config.lipsyncLang
784
+ });
785
+ }
786
+ };
787
+
788
+ // Try to speak immediately if avatar is ready
789
+ if (avatarRef.current && avatarRef.current.isReady && prevQuestionObj) {
790
+ speakPrevQuestion();
791
+ } else if (prevQuestionObj) {
792
+ // Avatar not ready yet - wait for it to be ready, then speak
793
+ const checkReady = setInterval(() => {
794
+ if (avatarRef.current && avatarRef.current.isReady) {
795
+ clearInterval(checkReady);
796
+ speakPrevQuestion();
797
+ }
798
+ }, 100);
799
+
800
+ setTimeout(() => {
801
+ clearInterval(checkReady);
802
+ }, 5000);
803
+ }
804
+ }
805
+ }, [getCurrentQuestion]);
806
+
807
+ // Move to previous lesson
808
+ const previousLesson = useCallback(() => {
809
+ const curriculum = curriculumRef.current || { modules: [] };
810
+ const currentModule = curriculum.modules[stateRef.current.currentModuleIndex];
811
+
812
+ // Check if there's a previous lesson in the current module
813
+ const hasPrevLessonInModule = stateRef.current.currentLessonIndex > 0;
814
+
815
+ if (hasPrevLessonInModule) {
816
+ // Move to previous lesson in current module
817
+ stateRef.current.currentLessonIndex -= 1;
818
+ stateRef.current.currentQuestionIndex = 0;
819
+ stateRef.current.lessonCompleted = false;
820
+ stateRef.current.isQuestionMode = false;
821
+ stateRef.current.isTeaching = false;
822
+ stateRef.current.score = 0;
823
+ stateRef.current.totalQuestions = 0;
824
+
825
+ // Notify parent that lesson has changed
826
+ callbacksRef.current.onCustomAction({
827
+ type: 'lessonStart',
828
+ moduleIndex: stateRef.current.currentModuleIndex,
829
+ lessonIndex: stateRef.current.currentLessonIndex
830
+ });
831
+
832
+ callbacksRef.current.onLessonStart({
833
+ moduleIndex: stateRef.current.currentModuleIndex,
834
+ lessonIndex: stateRef.current.currentLessonIndex,
835
+ lesson: getCurrentLesson()
836
+ });
837
+
838
+ if (avatarRef.current) {
839
+ avatarRef.current.setMood("happy");
840
+ avatarRef.current.setBodyMovement("idle");
841
+ }
842
+ } else {
843
+ // No previous lesson in current module - check if there's a previous module
844
+ const hasPrevModule = stateRef.current.currentModuleIndex > 0;
845
+
846
+ if (hasPrevModule) {
847
+ // Move to last lesson of previous module
848
+ const prevModule = curriculum.modules[stateRef.current.currentModuleIndex - 1];
849
+ stateRef.current.currentModuleIndex -= 1;
850
+ stateRef.current.currentLessonIndex = (prevModule?.lessons?.length || 1) - 1;
851
+ stateRef.current.currentQuestionIndex = 0;
852
+ stateRef.current.lessonCompleted = false;
853
+ stateRef.current.isQuestionMode = false;
854
+ stateRef.current.isTeaching = false;
855
+ stateRef.current.score = 0;
856
+ stateRef.current.totalQuestions = 0;
857
+
858
+ // Notify parent that lesson has changed
859
+ callbacksRef.current.onCustomAction({
860
+ type: 'lessonStart',
861
+ moduleIndex: stateRef.current.currentModuleIndex,
862
+ lessonIndex: stateRef.current.currentLessonIndex
863
+ });
864
+
865
+ callbacksRef.current.onLessonStart({
866
+ moduleIndex: stateRef.current.currentModuleIndex,
867
+ lessonIndex: stateRef.current.currentLessonIndex,
868
+ lesson: getCurrentLesson()
869
+ });
870
+
871
+ if (avatarRef.current) {
872
+ avatarRef.current.setMood("happy");
873
+ avatarRef.current.setBodyMovement("idle");
874
+ }
875
+ }
876
+ }
877
+ }, [getCurrentLesson]);
878
+
735
879
  // Reset curriculum
736
880
  const resetCurriculum = useCallback(() => {
737
881
  stateRef.current.currentModuleIndex = 0;
@@ -781,7 +925,9 @@ const CurriculumLearning = forwardRef(({
781
925
  handleAnswerSelect,
782
926
  handleCodeTestResult,
783
927
  nextQuestion,
928
+ previousQuestion,
784
929
  nextLesson,
930
+ previousLesson,
785
931
  completeLesson,
786
932
  completeCurriculum,
787
933
  resetCurriculum,