@sage-rsc/talking-head-react 1.0.46 → 1.0.48
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 +207 -162
- package/package.json +1 -1
- package/src/components/CurriculumLearning.jsx +122 -81
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,
|
|
@@ -79,7 +106,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
79
106
|
ttsApiKey: avatarConfig.ttsApiKey || null,
|
|
80
107
|
bodyMovement: avatarConfig.bodyMovement || "gesturing",
|
|
81
108
|
movementIntensity: avatarConfig.movementIntensity || 0.7,
|
|
82
|
-
showFullAvatar: avatarConfig.showFullAvatar !== undefined ? avatarConfig.showFullAvatar :
|
|
109
|
+
showFullAvatar: avatarConfig.showFullAvatar !== undefined ? avatarConfig.showFullAvatar : false,
|
|
83
110
|
animations: animations,
|
|
84
111
|
lipsyncLang: 'en'
|
|
85
112
|
});
|
|
@@ -114,7 +141,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
114
141
|
ttsApiKey: avatarConfig.ttsApiKey || null,
|
|
115
142
|
bodyMovement: avatarConfig.bodyMovement || "gesturing",
|
|
116
143
|
movementIntensity: avatarConfig.movementIntensity || 0.7,
|
|
117
|
-
showFullAvatar: avatarConfig.showFullAvatar !== undefined ? avatarConfig.showFullAvatar :
|
|
144
|
+
showFullAvatar: avatarConfig.showFullAvatar !== undefined ? avatarConfig.showFullAvatar : false,
|
|
118
145
|
animations: animations,
|
|
119
146
|
lipsyncLang: 'en'
|
|
120
147
|
};
|
|
@@ -159,7 +186,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
159
186
|
let feedbackMessage = `Congratulations! You've completed this lesson`;
|
|
160
187
|
if (stateRef.current.totalQuestions > 0) {
|
|
161
188
|
feedbackMessage += ` with a score of ${stateRef.current.score} out of ${stateRef.current.totalQuestions} (${percentage}%). `;
|
|
162
|
-
|
|
189
|
+
} else {
|
|
163
190
|
feedbackMessage += `! `;
|
|
164
191
|
}
|
|
165
192
|
|
|
@@ -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
|
|
|
@@ -301,7 +321,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
301
321
|
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
302
322
|
} else if (firstQuestion.type === 'true_false') {
|
|
303
323
|
avatarRef.current.speakText(`Let's start with some true or false questions. First question: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
304
|
-
|
|
324
|
+
} else {
|
|
305
325
|
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
306
326
|
}
|
|
307
327
|
} else if (avatarRef.current && avatarRef.current.isReady) {
|
|
@@ -338,7 +358,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
338
358
|
}
|
|
339
359
|
|
|
340
360
|
if (avatarRef.current && nextQuestionObj) {
|
|
341
|
-
|
|
361
|
+
avatarRef.current.setMood("happy");
|
|
342
362
|
avatarRef.current.setBodyMovement("idle");
|
|
343
363
|
|
|
344
364
|
// Play custom animation if available
|
|
@@ -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
|
|
|
407
|
-
|
|
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
|
+
|
|
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,22 +455,24 @@ 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
|
-
}
|
|
446
|
-
}
|
|
474
|
+
avatarRef.current.setBodyMovement("idle");
|
|
475
|
+
}
|
|
447
476
|
} else {
|
|
448
477
|
// No more modules or lessons - complete curriculum
|
|
449
478
|
if (completeCurriculumRef.current) {
|
|
@@ -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) => {
|
|
@@ -782,7 +823,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
782
823
|
ttsApiKey: null,
|
|
783
824
|
bodyMovement: "gesturing",
|
|
784
825
|
movementIntensity: 0.7,
|
|
785
|
-
showFullAvatar:
|
|
826
|
+
showFullAvatar: false,
|
|
786
827
|
animations: animations,
|
|
787
828
|
lipsyncLang: 'en'
|
|
788
829
|
};
|