@sage-rsc/talking-head-react 1.0.30 → 1.0.32
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-BG4X_1Qa.cjs → fbxAnimationLoader-BrjaqtTU.cjs} +1 -1
- package/dist/{fbxAnimationLoader-CO1F6v-w.js → fbxAnimationLoader-YIg1EylY.js} +1 -1
- package/dist/index-CdCA-KAp.cjs +13 -0
- package/dist/{index-V_d1NQ-5.js → index-Dd7BPF7g.js} +1661 -1607
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/CurriculumLearning.jsx +190 -81
- package/dist/index-BFQNNYmP.cjs +0 -13
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-CdCA-KAp.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
|
@@ -58,45 +58,74 @@ const CurriculumLearning = forwardRef(({
|
|
|
58
58
|
const nextQuestionRef = useRef(null);
|
|
59
59
|
const completeCurriculumRef = useRef(null);
|
|
60
60
|
const startQuestionsRef = useRef(null);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
onLessonStart,
|
|
66
|
-
onLessonComplete,
|
|
67
|
-
onQuestionAnswer,
|
|
68
|
-
onCurriculumComplete,
|
|
69
|
-
onCustomAction
|
|
70
|
-
};
|
|
71
|
-
}, [onLessonStart, onLessonComplete, onQuestionAnswer, onCurriculumComplete, onCustomAction]);
|
|
72
|
-
|
|
73
|
-
const curriculum = curriculumData?.curriculum || {
|
|
61
|
+
const handleAnswerSelectRef = useRef(null);
|
|
62
|
+
|
|
63
|
+
// Store curriculum in ref to avoid dependency issues - initialize early
|
|
64
|
+
const curriculumRef = useRef(curriculumData?.curriculum || {
|
|
74
65
|
title: "Default Curriculum",
|
|
75
66
|
description: "No curriculum data provided",
|
|
76
67
|
language: "en",
|
|
77
68
|
modules: []
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Store avatar config in ref to avoid dependency issues - initialize early
|
|
72
|
+
const defaultAvatarConfigRef = useRef({
|
|
81
73
|
avatarUrl: avatarConfig.avatarUrl || "/avatars/brunette.glb",
|
|
82
74
|
avatarBody: avatarConfig.avatarBody || "F",
|
|
83
75
|
mood: avatarConfig.mood || "happy",
|
|
84
76
|
ttsLang: avatarConfig.ttsLang || "en",
|
|
85
|
-
ttsService: avatarConfig.ttsService || null,
|
|
86
|
-
ttsVoice: avatarConfig.ttsVoice || null,
|
|
77
|
+
ttsService: avatarConfig.ttsService || null,
|
|
78
|
+
ttsVoice: avatarConfig.ttsVoice || null,
|
|
87
79
|
ttsApiKey: avatarConfig.ttsApiKey || null,
|
|
88
80
|
bodyMovement: avatarConfig.bodyMovement || "gesturing",
|
|
89
81
|
movementIntensity: avatarConfig.movementIntensity || 0.7,
|
|
90
82
|
showFullAvatar: avatarConfig.showFullAvatar !== undefined ? avatarConfig.showFullAvatar : true,
|
|
91
83
|
animations: animations,
|
|
92
|
-
lipsyncLang: 'en'
|
|
93
|
-
};
|
|
84
|
+
lipsyncLang: 'en'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Update callbacks ref when they change
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
callbacksRef.current = {
|
|
90
|
+
onLessonStart,
|
|
91
|
+
onLessonComplete,
|
|
92
|
+
onQuestionAnswer,
|
|
93
|
+
onCurriculumComplete,
|
|
94
|
+
onCustomAction
|
|
95
|
+
};
|
|
96
|
+
}, [onLessonStart, onLessonComplete, onQuestionAnswer, onCurriculumComplete, onCustomAction]);
|
|
97
|
+
|
|
98
|
+
// Update curriculum and config refs when they change
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
curriculumRef.current = curriculumData?.curriculum || {
|
|
101
|
+
title: "Default Curriculum",
|
|
102
|
+
description: "No curriculum data provided",
|
|
103
|
+
language: "en",
|
|
104
|
+
modules: []
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
defaultAvatarConfigRef.current = {
|
|
108
|
+
avatarUrl: avatarConfig.avatarUrl || "/avatars/brunette.glb",
|
|
109
|
+
avatarBody: avatarConfig.avatarBody || "F",
|
|
110
|
+
mood: avatarConfig.mood || "happy",
|
|
111
|
+
ttsLang: avatarConfig.ttsLang || "en",
|
|
112
|
+
ttsService: avatarConfig.ttsService || null,
|
|
113
|
+
ttsVoice: avatarConfig.ttsVoice || null,
|
|
114
|
+
ttsApiKey: avatarConfig.ttsApiKey || null,
|
|
115
|
+
bodyMovement: avatarConfig.bodyMovement || "gesturing",
|
|
116
|
+
movementIntensity: avatarConfig.movementIntensity || 0.7,
|
|
117
|
+
showFullAvatar: avatarConfig.showFullAvatar !== undefined ? avatarConfig.showFullAvatar : true,
|
|
118
|
+
animations: animations,
|
|
119
|
+
lipsyncLang: 'en'
|
|
120
|
+
};
|
|
121
|
+
}, [curriculumData, avatarConfig, animations]);
|
|
94
122
|
|
|
95
123
|
// Helper to get current lesson/question
|
|
96
124
|
const getCurrentLesson = useCallback(() => {
|
|
125
|
+
const curriculum = curriculumRef.current || { modules: [] };
|
|
97
126
|
const module = curriculum.modules[stateRef.current.currentModuleIndex];
|
|
98
127
|
return module?.lessons[stateRef.current.currentLessonIndex];
|
|
99
|
-
}, [
|
|
128
|
+
}, []);
|
|
100
129
|
|
|
101
130
|
const getCurrentQuestion = useCallback(() => {
|
|
102
131
|
const lesson = getCurrentLesson();
|
|
@@ -130,7 +159,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
130
159
|
let feedbackMessage = `Congratulations! You've completed this lesson`;
|
|
131
160
|
if (stateRef.current.totalQuestions > 0) {
|
|
132
161
|
feedbackMessage += ` with a score of ${stateRef.current.score} out of ${stateRef.current.totalQuestions} (${percentage}%). `;
|
|
133
|
-
|
|
162
|
+
} else {
|
|
134
163
|
feedbackMessage += `! `;
|
|
135
164
|
}
|
|
136
165
|
|
|
@@ -161,26 +190,31 @@ const CurriculumLearning = forwardRef(({
|
|
|
161
190
|
});
|
|
162
191
|
|
|
163
192
|
if (avatarRef.current) {
|
|
164
|
-
|
|
193
|
+
avatarRef.current.setMood("happy");
|
|
165
194
|
if (animations.lessonComplete) {
|
|
166
195
|
try {
|
|
167
196
|
avatarRef.current.playAnimation(animations.lessonComplete, true);
|
|
168
|
-
|
|
169
|
-
|
|
197
|
+
} catch (error) {
|
|
198
|
+
avatarRef.current.playCelebration();
|
|
170
199
|
}
|
|
171
200
|
}
|
|
172
201
|
|
|
173
|
-
// Check if there's a next lesson available
|
|
202
|
+
// Check if there's a next lesson available (either in current module or next module)
|
|
203
|
+
const curriculum = curriculumRef.current || { modules: [] };
|
|
174
204
|
const currentModule = curriculum.modules[stateRef.current.currentModuleIndex];
|
|
175
|
-
const
|
|
205
|
+
const hasNextLessonInModule = stateRef.current.currentLessonIndex < (currentModule?.lessons?.length || 0) - 1;
|
|
206
|
+
const hasNextModule = stateRef.current.currentModuleIndex < (curriculum.modules?.length || 0) - 1;
|
|
207
|
+
const hasNextLesson = hasNextLessonInModule || hasNextModule;
|
|
208
|
+
|
|
209
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
176
210
|
|
|
177
211
|
if (hasNextLesson) {
|
|
178
212
|
// Wait for speech to finish, then automatically move to next lesson
|
|
179
213
|
avatarRef.current.speakText(feedbackMessage, {
|
|
180
|
-
lipsyncLang:
|
|
214
|
+
lipsyncLang: config.lipsyncLang,
|
|
181
215
|
onSpeechEnd: () => {
|
|
182
216
|
// Add a small delay after speech ends for natural flow
|
|
183
|
-
|
|
217
|
+
setTimeout(() => {
|
|
184
218
|
// Use ref to avoid circular dependency
|
|
185
219
|
if (nextLessonRef.current) {
|
|
186
220
|
nextLessonRef.current();
|
|
@@ -188,10 +222,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
188
222
|
}, 1000);
|
|
189
223
|
}
|
|
190
224
|
});
|
|
191
|
-
|
|
192
|
-
// This is the last lesson, complete curriculum instead
|
|
225
|
+
} else {
|
|
226
|
+
// This is the last lesson in the last module, complete curriculum instead
|
|
193
227
|
avatarRef.current.speakText(feedbackMessage, {
|
|
194
|
-
lipsyncLang:
|
|
228
|
+
lipsyncLang: config.lipsyncLang,
|
|
195
229
|
onSpeechEnd: () => {
|
|
196
230
|
// Add a small delay after speech ends for natural flow
|
|
197
231
|
setTimeout(() => {
|
|
@@ -203,18 +237,19 @@ const CurriculumLearning = forwardRef(({
|
|
|
203
237
|
});
|
|
204
238
|
}
|
|
205
239
|
}
|
|
206
|
-
}, [animations.lessonComplete
|
|
240
|
+
}, [animations.lessonComplete]);
|
|
207
241
|
|
|
208
242
|
// Complete entire curriculum
|
|
209
243
|
const completeCurriculum = useCallback(() => {
|
|
210
244
|
stateRef.current.curriculumCompleted = true;
|
|
211
245
|
|
|
246
|
+
const curriculum = curriculumRef.current || { modules: [] };
|
|
212
247
|
callbacksRef.current.onCurriculumComplete({
|
|
213
248
|
modules: curriculum.modules.length,
|
|
214
249
|
totalLessons: curriculum.modules.reduce((sum, mod) => sum + mod.lessons.length, 0)
|
|
215
250
|
});
|
|
216
|
-
|
|
217
|
-
|
|
251
|
+
|
|
252
|
+
if (avatarRef.current) {
|
|
218
253
|
avatarRef.current.setMood("celebrating");
|
|
219
254
|
if (animations.curriculumComplete) {
|
|
220
255
|
try {
|
|
@@ -223,9 +258,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
223
258
|
avatarRef.current.playCelebration();
|
|
224
259
|
}
|
|
225
260
|
}
|
|
226
|
-
|
|
261
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
262
|
+
avatarRef.current.speakText("Amazing! You've completed the entire curriculum! You're now ready to move on to more advanced topics. Well done!", { lipsyncLang: config.lipsyncLang });
|
|
227
263
|
}
|
|
228
|
-
}, [animations.curriculumComplete
|
|
264
|
+
}, [animations.curriculumComplete]);
|
|
229
265
|
|
|
230
266
|
// Start asking questions
|
|
231
267
|
const startQuestions = useCallback(() => {
|
|
@@ -260,20 +296,23 @@ const CurriculumLearning = forwardRef(({
|
|
|
260
296
|
}
|
|
261
297
|
}
|
|
262
298
|
|
|
299
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
300
|
+
|
|
263
301
|
// Introduce the first question properly based on type
|
|
264
302
|
if (firstQuestion.type === 'code_test') {
|
|
265
|
-
avatarRef.current.speakText(`Let's test your coding skills! Here's your first challenge: ${firstQuestion.question}`, { lipsyncLang:
|
|
303
|
+
avatarRef.current.speakText(`Let's test your coding skills! Here's your first challenge: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
266
304
|
} else if (firstQuestion.type === 'multiple_choice') {
|
|
267
|
-
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang:
|
|
305
|
+
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
268
306
|
} else if (firstQuestion.type === 'true_false') {
|
|
269
|
-
avatarRef.current.speakText(`Let's start with some true or false questions. First question: ${firstQuestion.question}`, { lipsyncLang:
|
|
270
|
-
|
|
271
|
-
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang:
|
|
307
|
+
avatarRef.current.speakText(`Let's start with some true or false questions. First question: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
308
|
+
} else {
|
|
309
|
+
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
272
310
|
}
|
|
273
311
|
} else if (avatarRef.current) {
|
|
274
|
-
|
|
312
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
313
|
+
avatarRef.current.speakText("Now let me ask you some questions to test your understanding.", { lipsyncLang: config.lipsyncLang });
|
|
275
314
|
}
|
|
276
|
-
}, [animations.questionStart, getCurrentLesson, getCurrentQuestion
|
|
315
|
+
}, [animations.questionStart, getCurrentLesson, getCurrentQuestion]);
|
|
277
316
|
|
|
278
317
|
// Move to next question
|
|
279
318
|
const nextQuestion = useCallback(() => {
|
|
@@ -296,34 +335,36 @@ const CurriculumLearning = forwardRef(({
|
|
|
296
335
|
}
|
|
297
336
|
|
|
298
337
|
if (avatarRef.current && nextQuestionObj) {
|
|
299
|
-
|
|
338
|
+
avatarRef.current.setMood("happy");
|
|
300
339
|
avatarRef.current.setBodyMovement("idle");
|
|
301
340
|
|
|
302
341
|
// Play custom animation if available
|
|
303
342
|
if (animations.nextQuestion) {
|
|
304
|
-
|
|
343
|
+
try {
|
|
305
344
|
avatarRef.current.playAnimation(animations.nextQuestion, true);
|
|
306
|
-
|
|
345
|
+
} catch (error) {
|
|
307
346
|
console.warn('Failed to play nextQuestion animation:', error);
|
|
308
347
|
}
|
|
309
348
|
}
|
|
310
349
|
|
|
350
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
351
|
+
|
|
311
352
|
// Speak the question text with proper introduction
|
|
312
353
|
if (nextQuestionObj.type === 'code_test') {
|
|
313
354
|
avatarRef.current.speakText(`Great! Now let's move on to your next coding challenge: ${nextQuestionObj.question}`, {
|
|
314
|
-
lipsyncLang:
|
|
355
|
+
lipsyncLang: config.lipsyncLang
|
|
315
356
|
});
|
|
316
357
|
} else if (nextQuestionObj.type === 'multiple_choice') {
|
|
317
358
|
avatarRef.current.speakText(`Alright! Here's your next question: ${nextQuestionObj.question}`, {
|
|
318
|
-
lipsyncLang:
|
|
359
|
+
lipsyncLang: config.lipsyncLang
|
|
319
360
|
});
|
|
320
361
|
} else if (nextQuestionObj.type === 'true_false') {
|
|
321
362
|
avatarRef.current.speakText(`Now let's try this one: ${nextQuestionObj.question}`, {
|
|
322
|
-
lipsyncLang:
|
|
363
|
+
lipsyncLang: config.lipsyncLang
|
|
323
364
|
});
|
|
324
365
|
} else {
|
|
325
366
|
avatarRef.current.speakText(`Here's the next question: ${nextQuestionObj.question}`, {
|
|
326
|
-
lipsyncLang:
|
|
367
|
+
lipsyncLang: config.lipsyncLang
|
|
327
368
|
});
|
|
328
369
|
}
|
|
329
370
|
}
|
|
@@ -333,12 +374,18 @@ const CurriculumLearning = forwardRef(({
|
|
|
333
374
|
completeLessonRef.current();
|
|
334
375
|
}
|
|
335
376
|
}
|
|
336
|
-
}, [animations.nextQuestion, getCurrentLesson, getCurrentQuestion
|
|
377
|
+
}, [animations.nextQuestion, getCurrentLesson, getCurrentQuestion]);
|
|
337
378
|
|
|
338
379
|
// Move to next lesson
|
|
339
380
|
const nextLesson = useCallback(() => {
|
|
381
|
+
const curriculum = curriculumRef.current || { modules: [] };
|
|
340
382
|
const currentModule = curriculum.modules[stateRef.current.currentModuleIndex];
|
|
341
|
-
|
|
383
|
+
|
|
384
|
+
// Check if there's a next lesson in the current module
|
|
385
|
+
const hasNextLessonInModule = stateRef.current.currentLessonIndex < (currentModule?.lessons?.length || 0) - 1;
|
|
386
|
+
|
|
387
|
+
if (hasNextLessonInModule) {
|
|
388
|
+
// Move to next lesson in current module
|
|
342
389
|
stateRef.current.currentLessonIndex += 1;
|
|
343
390
|
stateRef.current.currentQuestionIndex = 0;
|
|
344
391
|
stateRef.current.lessonCompleted = false;
|
|
@@ -366,11 +413,46 @@ const CurriculumLearning = forwardRef(({
|
|
|
366
413
|
}, 500);
|
|
367
414
|
}
|
|
368
415
|
} else {
|
|
369
|
-
|
|
370
|
-
|
|
416
|
+
// No more lessons in current module - check if there's a next module
|
|
417
|
+
const hasNextModule = stateRef.current.currentModuleIndex < (curriculum.modules?.length || 0) - 1;
|
|
418
|
+
|
|
419
|
+
if (hasNextModule) {
|
|
420
|
+
// Move to first lesson of next module
|
|
421
|
+
stateRef.current.currentModuleIndex += 1;
|
|
422
|
+
stateRef.current.currentLessonIndex = 0;
|
|
423
|
+
stateRef.current.currentQuestionIndex = 0;
|
|
424
|
+
stateRef.current.lessonCompleted = false;
|
|
425
|
+
stateRef.current.isQuestionMode = false;
|
|
426
|
+
stateRef.current.isTeaching = false;
|
|
427
|
+
stateRef.current.score = 0;
|
|
428
|
+
stateRef.current.totalQuestions = 0;
|
|
429
|
+
|
|
430
|
+
// Clear current question in UI
|
|
431
|
+
callbacksRef.current.onCustomAction({
|
|
432
|
+
type: 'lessonStart',
|
|
433
|
+
moduleIndex: stateRef.current.currentModuleIndex,
|
|
434
|
+
lessonIndex: stateRef.current.currentLessonIndex
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
if (avatarRef.current) {
|
|
438
|
+
avatarRef.current.setMood("happy");
|
|
439
|
+
avatarRef.current.setBodyMovement("idle");
|
|
440
|
+
|
|
441
|
+
// Automatically start teaching the next lesson after a brief pause
|
|
442
|
+
setTimeout(() => {
|
|
443
|
+
if (startTeachingRef.current) {
|
|
444
|
+
startTeachingRef.current();
|
|
445
|
+
}
|
|
446
|
+
}, 500);
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
// No more modules or lessons - complete curriculum
|
|
450
|
+
if (completeCurriculumRef.current) {
|
|
451
|
+
completeCurriculumRef.current();
|
|
452
|
+
}
|
|
371
453
|
}
|
|
372
454
|
}
|
|
373
|
-
}, [
|
|
455
|
+
}, []);
|
|
374
456
|
|
|
375
457
|
// Start teaching the lesson
|
|
376
458
|
const startTeaching = useCallback(() => {
|
|
@@ -396,7 +478,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
396
478
|
avatarRef.current.setBodyMovement("gesturing");
|
|
397
479
|
}
|
|
398
480
|
|
|
399
|
-
|
|
481
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
400
482
|
|
|
401
483
|
callbacksRef.current.onLessonStart({
|
|
402
484
|
moduleIndex: stateRef.current.currentModuleIndex,
|
|
@@ -412,23 +494,29 @@ const CurriculumLearning = forwardRef(({
|
|
|
412
494
|
lesson: currentLesson
|
|
413
495
|
});
|
|
414
496
|
|
|
415
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
497
|
+
// Wait for avatar to finish speaking before moving to questions
|
|
498
|
+
avatarRef.current.speakText(currentLesson.avatar_script, {
|
|
499
|
+
lipsyncLang: config.lipsyncLang,
|
|
500
|
+
onSpeechEnd: () => {
|
|
501
|
+
stateRef.current.isTeaching = false;
|
|
502
|
+
// Add a small delay after speech ends for natural flow
|
|
503
|
+
setTimeout(() => {
|
|
504
|
+
if (currentLesson.questions && currentLesson.questions.length > 0) {
|
|
505
|
+
// Use ref to avoid circular dependency
|
|
506
|
+
if (startQuestionsRef.current) {
|
|
507
|
+
startQuestionsRef.current();
|
|
508
|
+
}
|
|
423
509
|
} else {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
510
|
+
// No questions, complete the lesson using ref to avoid circular dependency
|
|
511
|
+
if (completeLessonRef.current) {
|
|
512
|
+
completeLessonRef.current();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}, 500);
|
|
428
516
|
}
|
|
429
|
-
}
|
|
517
|
+
});
|
|
430
518
|
}
|
|
431
|
-
}, [animations.teaching, getCurrentLesson
|
|
519
|
+
}, [animations.teaching, getCurrentLesson]);
|
|
432
520
|
|
|
433
521
|
// Handle answer selection
|
|
434
522
|
const handleAnswerSelect = useCallback((answer) => {
|
|
@@ -450,9 +538,9 @@ const CurriculumLearning = forwardRef(({
|
|
|
450
538
|
|
|
451
539
|
if (avatarRef.current) {
|
|
452
540
|
if (isCorrect) {
|
|
453
|
-
|
|
541
|
+
avatarRef.current.setMood("happy");
|
|
454
542
|
if (animations.correct) {
|
|
455
|
-
|
|
543
|
+
try {
|
|
456
544
|
avatarRef.current.playReaction("happy");
|
|
457
545
|
} catch (error) {
|
|
458
546
|
avatarRef.current.setBodyMovement("happy");
|
|
@@ -463,9 +551,11 @@ const CurriculumLearning = forwardRef(({
|
|
|
463
551
|
? `Great job! Your code passed all the tests! ${currentQuestion.explanation || ''}`
|
|
464
552
|
: `Excellent! That's correct! ${currentQuestion.explanation || ''}`;
|
|
465
553
|
|
|
554
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
555
|
+
|
|
466
556
|
// Wait for speech to finish before moving to next question
|
|
467
557
|
avatarRef.current.speakText(successMessage, {
|
|
468
|
-
lipsyncLang:
|
|
558
|
+
lipsyncLang: config.lipsyncLang,
|
|
469
559
|
onSpeechEnd: () => {
|
|
470
560
|
// Add a small delay after speech ends for natural flow
|
|
471
561
|
setTimeout(() => {
|
|
@@ -475,12 +565,12 @@ const CurriculumLearning = forwardRef(({
|
|
|
475
565
|
}, 500);
|
|
476
566
|
}
|
|
477
567
|
});
|
|
478
|
-
|
|
568
|
+
} else {
|
|
479
569
|
avatarRef.current.setMood("sad");
|
|
480
570
|
if (animations.incorrect) {
|
|
481
571
|
try {
|
|
482
572
|
avatarRef.current.playAnimation(animations.incorrect, true);
|
|
483
|
-
|
|
573
|
+
} catch (error) {
|
|
484
574
|
avatarRef.current.setBodyMovement("idle");
|
|
485
575
|
}
|
|
486
576
|
}
|
|
@@ -489,9 +579,11 @@ const CurriculumLearning = forwardRef(({
|
|
|
489
579
|
? `Your code didn't pass all the tests. ${currentQuestion.explanation || 'Try again!'}`
|
|
490
580
|
: `Not quite right, but don't worry! ${currentQuestion.explanation || ''} Let's move on to the next question.`;
|
|
491
581
|
|
|
582
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
583
|
+
|
|
492
584
|
// Wait for speech to finish before moving to next question
|
|
493
585
|
avatarRef.current.speakText(failureMessage, {
|
|
494
|
-
lipsyncLang:
|
|
586
|
+
lipsyncLang: config.lipsyncLang,
|
|
495
587
|
onSpeechEnd: () => {
|
|
496
588
|
// Add a small delay after speech ends for natural flow
|
|
497
589
|
setTimeout(() => {
|
|
@@ -508,7 +600,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
508
600
|
nextQuestionRef.current();
|
|
509
601
|
}
|
|
510
602
|
}
|
|
511
|
-
}, [animations.correct, animations.incorrect, getCurrentQuestion, checkAnswer
|
|
603
|
+
}, [animations.correct, animations.incorrect, getCurrentQuestion, checkAnswer]);
|
|
512
604
|
|
|
513
605
|
// Handle code test result submission
|
|
514
606
|
const handleCodeTestResult = useCallback((testResult) => {
|
|
@@ -552,7 +644,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
552
644
|
if (handleAnswerSelectRef.current) {
|
|
553
645
|
handleAnswerSelectRef.current(codeTestAnswer);
|
|
554
646
|
}
|
|
555
|
-
}, [getCurrentQuestion, checkAnswer
|
|
647
|
+
}, [getCurrentQuestion, checkAnswer]);
|
|
556
648
|
|
|
557
649
|
// Reset curriculum
|
|
558
650
|
const resetCurriculum = useCallback(() => {
|
|
@@ -615,7 +707,8 @@ const CurriculumLearning = forwardRef(({
|
|
|
615
707
|
speakText: async (text, options = {}) => {
|
|
616
708
|
// Ensure audio context is resumed before speaking
|
|
617
709
|
await avatarRef.current?.resumeAudioContext?.();
|
|
618
|
-
|
|
710
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
711
|
+
avatarRef.current?.speakText(text, { ...options, lipsyncLang: options.lipsyncLang || config.lipsyncLang });
|
|
619
712
|
},
|
|
620
713
|
resumeAudioContext: async () => {
|
|
621
714
|
// Try to resume through avatar ref first
|
|
@@ -667,6 +760,22 @@ const CurriculumLearning = forwardRef(({
|
|
|
667
760
|
isAvatarReady: () => avatarRef.current?.isReady || false
|
|
668
761
|
}), [startTeaching, startQuestions, handleAnswerSelect, handleCodeTestResult, nextQuestion, nextLesson, completeLesson, completeCurriculum, resetCurriculum, getCurrentQuestion, getCurrentLesson]);
|
|
669
762
|
|
|
763
|
+
// Get current config for render
|
|
764
|
+
const defaultAvatarConfig = defaultAvatarConfigRef.current || {
|
|
765
|
+
avatarUrl: "/avatars/brunette.glb",
|
|
766
|
+
avatarBody: "F",
|
|
767
|
+
mood: "happy",
|
|
768
|
+
ttsLang: "en",
|
|
769
|
+
ttsService: null,
|
|
770
|
+
ttsVoice: null,
|
|
771
|
+
ttsApiKey: null,
|
|
772
|
+
bodyMovement: "gesturing",
|
|
773
|
+
movementIntensity: 0.7,
|
|
774
|
+
showFullAvatar: true,
|
|
775
|
+
animations: animations,
|
|
776
|
+
lipsyncLang: 'en'
|
|
777
|
+
};
|
|
778
|
+
|
|
670
779
|
return (
|
|
671
780
|
<div style={{ width: '100%', height: '100%' }}>
|
|
672
781
|
<TalkingHeadAvatar
|
|
@@ -697,4 +806,4 @@ const CurriculumLearning = forwardRef(({
|
|
|
697
806
|
|
|
698
807
|
CurriculumLearning.displayName = 'CurriculumLearning';
|
|
699
808
|
|
|
700
|
-
export default CurriculumLearning;
|
|
809
|
+
export default CurriculumLearning;
|