@sage-rsc/talking-head-react 1.0.46 → 1.0.47
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 +204 -159
- package/package.json +1 -1
- package/src/components/CurriculumLearning.jsx +115 -74
package/package.json
CHANGED
|
@@ -4,8 +4,14 @@ import TalkingHeadAvatar from './TalkingHeadAvatar';
|
|
|
4
4
|
/**
|
|
5
5
|
* CurriculumLearning - A controller component for curriculum-based learning
|
|
6
6
|
*
|
|
7
|
-
* This component manages curriculum flow
|
|
8
|
-
* All
|
|
7
|
+
* This component manages curriculum flow interactively. It has no UI elements.
|
|
8
|
+
* All progression is controlled by the parent app via ref methods (e.g., startTeaching, nextQuestion, nextLesson).
|
|
9
|
+
* The component exposes methods that can be called from buttons or other UI controls in the consuming app.
|
|
10
|
+
*
|
|
11
|
+
* INTERACTIVE MODE: The component does NOT automatically progress. Instead, it:
|
|
12
|
+
* - Reads content when methods are called (startTeaching, startQuestions, nextQuestion, etc.)
|
|
13
|
+
* - Triggers events via onCustomAction when actions complete (teachingComplete, answerFeedbackComplete, etc.)
|
|
14
|
+
* - Waits for parent app to call the next method via ref
|
|
9
15
|
*
|
|
10
16
|
* @param {Object} props
|
|
11
17
|
* @param {Object} props.curriculumData - Curriculum data object
|
|
@@ -16,7 +22,28 @@ import TalkingHeadAvatar from './TalkingHeadAvatar';
|
|
|
16
22
|
* @param {Function} props.onQuestionAnswer - Callback when question is answered
|
|
17
23
|
* @param {Function} props.onCurriculumComplete - Callback when curriculum completes
|
|
18
24
|
* @param {Function} props.onCustomAction - Callback for custom actions (receives action type and data)
|
|
19
|
-
*
|
|
25
|
+
* - 'teachingComplete': Fired when teaching finishes. Check data.hasQuestions to know if questions are available.
|
|
26
|
+
* - 'answerFeedbackComplete': Fired when answer feedback finishes. Check data.hasNextQuestion to know if more questions exist.
|
|
27
|
+
* - 'lessonCompleteFeedbackDone': Fired when lesson completion feedback finishes. Check data.hasNextLesson to know if more lessons exist.
|
|
28
|
+
* - 'allQuestionsComplete': Fired when all questions in a lesson are done. Parent should call completeLesson() when ready.
|
|
29
|
+
* @param {boolean} props.autoStart - Whether to auto-start teaching when avatar is ready (only affects initial start, not progression)
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // In your app component:
|
|
33
|
+
* const curriculumRef = useRef(null);
|
|
34
|
+
*
|
|
35
|
+
* // Call methods via ref to control progression:
|
|
36
|
+
* <button onClick={() => curriculumRef.current?.startTeaching()}>Start Lesson</button>
|
|
37
|
+
* <button onClick={() => curriculumRef.current?.startQuestions()}>Start Questions</button>
|
|
38
|
+
* <button onClick={() => curriculumRef.current?.nextQuestion()}>Next Question</button>
|
|
39
|
+
* <button onClick={() => curriculumRef.current?.nextLesson()}>Next Lesson</button>
|
|
40
|
+
*
|
|
41
|
+
* // Listen to events to enable/disable buttons:
|
|
42
|
+
* onCustomAction={(action) => {
|
|
43
|
+
* if (action.type === 'teachingComplete') {
|
|
44
|
+
* setCanStartQuestions(action.hasQuestions);
|
|
45
|
+
* }
|
|
46
|
+
* }}
|
|
20
47
|
*/
|
|
21
48
|
const CurriculumLearning = forwardRef(({
|
|
22
49
|
curriculumData = null,
|
|
@@ -208,29 +235,22 @@ const CurriculumLearning = forwardRef(({
|
|
|
208
235
|
|
|
209
236
|
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
210
237
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
// Immediately complete curriculum with no delay
|
|
228
|
-
if (completeCurriculumRef.current) {
|
|
229
|
-
completeCurriculumRef.current();
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
}
|
|
238
|
+
// Read completion feedback - no automatic progression, user controls via buttons
|
|
239
|
+
avatarRef.current.speakText(feedbackMessage, {
|
|
240
|
+
lipsyncLang: config.lipsyncLang,
|
|
241
|
+
onSpeechEnd: () => {
|
|
242
|
+
// Notify parent that lesson completion feedback is done - parent decides next action
|
|
243
|
+
callbacksRef.current.onCustomAction({
|
|
244
|
+
type: 'lessonCompleteFeedbackDone',
|
|
245
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
246
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
247
|
+
score: stateRef.current.score,
|
|
248
|
+
totalQuestions: stateRef.current.totalQuestions,
|
|
249
|
+
percentage: percentage,
|
|
250
|
+
hasNextLesson: hasNextLesson
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
});
|
|
234
254
|
}
|
|
235
255
|
}, [animations.lessonComplete]);
|
|
236
256
|
|
|
@@ -372,10 +392,15 @@ const CurriculumLearning = forwardRef(({
|
|
|
372
392
|
}
|
|
373
393
|
}
|
|
374
394
|
} else {
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
395
|
+
// No more questions - notify parent that all questions are done
|
|
396
|
+
// Parent can call completeLesson() when ready
|
|
397
|
+
callbacksRef.current.onCustomAction({
|
|
398
|
+
type: 'allQuestionsComplete',
|
|
399
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
400
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
401
|
+
totalQuestions: stateRef.current.totalQuestions,
|
|
402
|
+
score: stateRef.current.score
|
|
403
|
+
});
|
|
379
404
|
}
|
|
380
405
|
}, [animations.nextQuestion, getCurrentLesson, getCurrentQuestion]);
|
|
381
406
|
|
|
@@ -397,21 +422,23 @@ const CurriculumLearning = forwardRef(({
|
|
|
397
422
|
stateRef.current.score = 0;
|
|
398
423
|
stateRef.current.totalQuestions = 0;
|
|
399
424
|
|
|
400
|
-
// Clear current question in UI
|
|
425
|
+
// Clear current question in UI and notify parent
|
|
401
426
|
callbacksRef.current.onCustomAction({
|
|
402
427
|
type: 'lessonStart',
|
|
403
428
|
moduleIndex: stateRef.current.currentModuleIndex,
|
|
404
429
|
lessonIndex: stateRef.current.currentLessonIndex
|
|
405
430
|
});
|
|
406
431
|
|
|
432
|
+
// Notify parent that lesson has changed - parent decides when to start teaching
|
|
433
|
+
callbacksRef.current.onLessonStart({
|
|
434
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
435
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
436
|
+
lesson: getCurrentLesson()
|
|
437
|
+
});
|
|
438
|
+
|
|
407
439
|
if (avatarRef.current) {
|
|
408
|
-
|
|
440
|
+
avatarRef.current.setMood("happy");
|
|
409
441
|
avatarRef.current.setBodyMovement("idle");
|
|
410
|
-
|
|
411
|
-
// Immediately start teaching the next lesson
|
|
412
|
-
if (startTeachingRef.current) {
|
|
413
|
-
startTeachingRef.current();
|
|
414
|
-
}
|
|
415
442
|
}
|
|
416
443
|
} else {
|
|
417
444
|
// No more lessons in current module - check if there's a next module
|
|
@@ -428,21 +455,23 @@ const CurriculumLearning = forwardRef(({
|
|
|
428
455
|
stateRef.current.score = 0;
|
|
429
456
|
stateRef.current.totalQuestions = 0;
|
|
430
457
|
|
|
431
|
-
// Clear current question in UI
|
|
458
|
+
// Clear current question in UI and notify parent
|
|
432
459
|
callbacksRef.current.onCustomAction({
|
|
433
460
|
type: 'lessonStart',
|
|
434
461
|
moduleIndex: stateRef.current.currentModuleIndex,
|
|
435
462
|
lessonIndex: stateRef.current.currentLessonIndex
|
|
436
463
|
});
|
|
437
464
|
|
|
438
|
-
|
|
465
|
+
// Notify parent that lesson has changed - parent decides when to start teaching
|
|
466
|
+
callbacksRef.current.onLessonStart({
|
|
467
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
468
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
469
|
+
lesson: getCurrentLesson()
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (avatarRef.current) {
|
|
439
473
|
avatarRef.current.setMood("happy");
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// Immediately start teaching the next lesson
|
|
443
|
-
if (startTeachingRef.current) {
|
|
444
|
-
startTeachingRef.current();
|
|
445
|
-
}
|
|
474
|
+
avatarRef.current.setBodyMovement("idle");
|
|
446
475
|
}
|
|
447
476
|
} else {
|
|
448
477
|
// No more modules or lessons - complete curriculum
|
|
@@ -508,23 +537,19 @@ const CurriculumLearning = forwardRef(({
|
|
|
508
537
|
lesson: currentLesson
|
|
509
538
|
});
|
|
510
539
|
|
|
511
|
-
//
|
|
540
|
+
// Read teaching content - no automatic progression, user controls via buttons
|
|
512
541
|
avatarRef.current.speakText(teachingText, {
|
|
513
542
|
lipsyncLang: config.lipsyncLang,
|
|
514
|
-
|
|
543
|
+
onSpeechEnd: () => {
|
|
515
544
|
stateRef.current.isTeaching = false;
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (completeLessonRef.current) {
|
|
525
|
-
completeLessonRef.current();
|
|
526
|
-
}
|
|
527
|
-
}
|
|
545
|
+
// Notify parent that teaching is complete - parent decides next action
|
|
546
|
+
callbacksRef.current.onCustomAction({
|
|
547
|
+
type: 'teachingComplete',
|
|
548
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
549
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
550
|
+
lesson: currentLesson,
|
|
551
|
+
hasQuestions: currentLesson.questions && currentLesson.questions.length > 0
|
|
552
|
+
});
|
|
528
553
|
}
|
|
529
554
|
});
|
|
530
555
|
}
|
|
@@ -565,14 +590,19 @@ const CurriculumLearning = forwardRef(({
|
|
|
565
590
|
|
|
566
591
|
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
567
592
|
|
|
568
|
-
//
|
|
593
|
+
// Read feedback - no automatic progression, user controls via buttons
|
|
569
594
|
avatarRef.current.speakText(successMessage, {
|
|
570
595
|
lipsyncLang: config.lipsyncLang,
|
|
571
596
|
onSpeechEnd: () => {
|
|
572
|
-
//
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
597
|
+
// Notify parent that feedback is complete - parent decides next action
|
|
598
|
+
callbacksRef.current.onCustomAction({
|
|
599
|
+
type: 'answerFeedbackComplete',
|
|
600
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
601
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
602
|
+
questionIndex: stateRef.current.currentQuestionIndex,
|
|
603
|
+
isCorrect: true,
|
|
604
|
+
hasNextQuestion: stateRef.current.currentQuestionIndex < (getCurrentLesson()?.questions?.length || 0) - 1
|
|
605
|
+
});
|
|
576
606
|
}
|
|
577
607
|
});
|
|
578
608
|
} else {
|
|
@@ -591,24 +621,35 @@ const CurriculumLearning = forwardRef(({
|
|
|
591
621
|
|
|
592
622
|
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
593
623
|
|
|
594
|
-
//
|
|
624
|
+
// Read feedback - no automatic progression, user controls via buttons
|
|
595
625
|
avatarRef.current.speakText(failureMessage, {
|
|
596
626
|
lipsyncLang: config.lipsyncLang,
|
|
597
627
|
onSpeechEnd: () => {
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
628
|
+
// Notify parent that feedback is complete - parent decides next action
|
|
629
|
+
callbacksRef.current.onCustomAction({
|
|
630
|
+
type: 'answerFeedbackComplete',
|
|
631
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
632
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
633
|
+
questionIndex: stateRef.current.currentQuestionIndex,
|
|
634
|
+
isCorrect: false,
|
|
635
|
+
hasNextQuestion: stateRef.current.currentQuestionIndex < (getCurrentLesson()?.questions?.length || 0) - 1
|
|
636
|
+
});
|
|
602
637
|
}
|
|
603
638
|
});
|
|
604
639
|
}
|
|
605
640
|
} else {
|
|
606
|
-
//
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
641
|
+
// Avatar not ready - notify parent
|
|
642
|
+
callbacksRef.current.onCustomAction({
|
|
643
|
+
type: 'answerFeedbackComplete',
|
|
644
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
645
|
+
lessonIndex: stateRef.current.currentLessonIndex,
|
|
646
|
+
questionIndex: stateRef.current.currentQuestionIndex,
|
|
647
|
+
isCorrect: isCorrect,
|
|
648
|
+
hasNextQuestion: stateRef.current.currentQuestionIndex < (getCurrentLesson()?.questions?.length || 0) - 1,
|
|
649
|
+
avatarNotReady: true
|
|
650
|
+
});
|
|
610
651
|
}
|
|
611
|
-
}, [animations.correct, animations.incorrect, getCurrentQuestion, checkAnswer]);
|
|
652
|
+
}, [animations.correct, animations.incorrect, getCurrentQuestion, getCurrentLesson, checkAnswer]);
|
|
612
653
|
|
|
613
654
|
// Handle code test result submission
|
|
614
655
|
const handleCodeTestResult = useCallback((testResult) => {
|