@sage-rsc/talking-head-react 1.0.30 → 1.0.31
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-CO1F6v-w.js → fbxAnimationLoader-C0BDPJ-P.js} +1 -1
- package/dist/{fbxAnimationLoader-BG4X_1Qa.cjs → fbxAnimationLoader-C1sYMpqd.cjs} +1 -1
- package/dist/index-WXvj5jje.cjs +13 -0
- package/dist/{index-V_d1NQ-5.js → index-iUfJQ70v.js} +824 -781
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/CurriculumLearning.jsx +127 -65
- 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-WXvj5jje.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,23 +190,26 @@ 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
197
|
} catch (error) {
|
|
169
|
-
|
|
198
|
+
avatarRef.current.playCelebration();
|
|
170
199
|
}
|
|
171
200
|
}
|
|
172
201
|
|
|
173
202
|
// Check if there's a next lesson available
|
|
203
|
+
const curriculum = curriculumRef.current || { modules: [] };
|
|
174
204
|
const currentModule = curriculum.modules[stateRef.current.currentModuleIndex];
|
|
175
205
|
const hasNextLesson = stateRef.current.currentLessonIndex < (currentModule?.lessons?.length || 0) - 1;
|
|
176
206
|
|
|
207
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
208
|
+
|
|
177
209
|
if (hasNextLesson) {
|
|
178
210
|
// Wait for speech to finish, then automatically move to next lesson
|
|
179
211
|
avatarRef.current.speakText(feedbackMessage, {
|
|
180
|
-
lipsyncLang:
|
|
212
|
+
lipsyncLang: config.lipsyncLang,
|
|
181
213
|
onSpeechEnd: () => {
|
|
182
214
|
// Add a small delay after speech ends for natural flow
|
|
183
215
|
setTimeout(() => {
|
|
@@ -188,10 +220,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
188
220
|
}, 1000);
|
|
189
221
|
}
|
|
190
222
|
});
|
|
191
|
-
|
|
223
|
+
} else {
|
|
192
224
|
// This is the last lesson, complete curriculum instead
|
|
193
225
|
avatarRef.current.speakText(feedbackMessage, {
|
|
194
|
-
lipsyncLang:
|
|
226
|
+
lipsyncLang: config.lipsyncLang,
|
|
195
227
|
onSpeechEnd: () => {
|
|
196
228
|
// Add a small delay after speech ends for natural flow
|
|
197
229
|
setTimeout(() => {
|
|
@@ -203,18 +235,19 @@ const CurriculumLearning = forwardRef(({
|
|
|
203
235
|
});
|
|
204
236
|
}
|
|
205
237
|
}
|
|
206
|
-
}, [animations.lessonComplete
|
|
238
|
+
}, [animations.lessonComplete]);
|
|
207
239
|
|
|
208
240
|
// Complete entire curriculum
|
|
209
241
|
const completeCurriculum = useCallback(() => {
|
|
210
242
|
stateRef.current.curriculumCompleted = true;
|
|
211
243
|
|
|
244
|
+
const curriculum = curriculumRef.current || { modules: [] };
|
|
212
245
|
callbacksRef.current.onCurriculumComplete({
|
|
213
246
|
modules: curriculum.modules.length,
|
|
214
247
|
totalLessons: curriculum.modules.reduce((sum, mod) => sum + mod.lessons.length, 0)
|
|
215
248
|
});
|
|
216
|
-
|
|
217
|
-
|
|
249
|
+
|
|
250
|
+
if (avatarRef.current) {
|
|
218
251
|
avatarRef.current.setMood("celebrating");
|
|
219
252
|
if (animations.curriculumComplete) {
|
|
220
253
|
try {
|
|
@@ -223,9 +256,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
223
256
|
avatarRef.current.playCelebration();
|
|
224
257
|
}
|
|
225
258
|
}
|
|
226
|
-
|
|
259
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
260
|
+
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
261
|
}
|
|
228
|
-
}, [animations.curriculumComplete
|
|
262
|
+
}, [animations.curriculumComplete]);
|
|
229
263
|
|
|
230
264
|
// Start asking questions
|
|
231
265
|
const startQuestions = useCallback(() => {
|
|
@@ -260,20 +294,23 @@ const CurriculumLearning = forwardRef(({
|
|
|
260
294
|
}
|
|
261
295
|
}
|
|
262
296
|
|
|
297
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
298
|
+
|
|
263
299
|
// Introduce the first question properly based on type
|
|
264
300
|
if (firstQuestion.type === 'code_test') {
|
|
265
|
-
avatarRef.current.speakText(`Let's test your coding skills! Here's your first challenge: ${firstQuestion.question}`, { lipsyncLang:
|
|
301
|
+
avatarRef.current.speakText(`Let's test your coding skills! Here's your first challenge: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
266
302
|
} 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:
|
|
303
|
+
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
268
304
|
} 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:
|
|
305
|
+
avatarRef.current.speakText(`Let's start with some true or false questions. First question: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
306
|
+
} else {
|
|
307
|
+
avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
|
|
272
308
|
}
|
|
273
309
|
} else if (avatarRef.current) {
|
|
274
|
-
|
|
310
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
311
|
+
avatarRef.current.speakText("Now let me ask you some questions to test your understanding.", { lipsyncLang: config.lipsyncLang });
|
|
275
312
|
}
|
|
276
|
-
}, [animations.questionStart, getCurrentLesson, getCurrentQuestion
|
|
313
|
+
}, [animations.questionStart, getCurrentLesson, getCurrentQuestion]);
|
|
277
314
|
|
|
278
315
|
// Move to next question
|
|
279
316
|
const nextQuestion = useCallback(() => {
|
|
@@ -296,34 +333,36 @@ const CurriculumLearning = forwardRef(({
|
|
|
296
333
|
}
|
|
297
334
|
|
|
298
335
|
if (avatarRef.current && nextQuestionObj) {
|
|
299
|
-
|
|
336
|
+
avatarRef.current.setMood("happy");
|
|
300
337
|
avatarRef.current.setBodyMovement("idle");
|
|
301
338
|
|
|
302
339
|
// Play custom animation if available
|
|
303
340
|
if (animations.nextQuestion) {
|
|
304
|
-
|
|
341
|
+
try {
|
|
305
342
|
avatarRef.current.playAnimation(animations.nextQuestion, true);
|
|
306
|
-
|
|
343
|
+
} catch (error) {
|
|
307
344
|
console.warn('Failed to play nextQuestion animation:', error);
|
|
308
345
|
}
|
|
309
346
|
}
|
|
310
347
|
|
|
348
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
349
|
+
|
|
311
350
|
// Speak the question text with proper introduction
|
|
312
351
|
if (nextQuestionObj.type === 'code_test') {
|
|
313
352
|
avatarRef.current.speakText(`Great! Now let's move on to your next coding challenge: ${nextQuestionObj.question}`, {
|
|
314
|
-
lipsyncLang:
|
|
353
|
+
lipsyncLang: config.lipsyncLang
|
|
315
354
|
});
|
|
316
355
|
} else if (nextQuestionObj.type === 'multiple_choice') {
|
|
317
356
|
avatarRef.current.speakText(`Alright! Here's your next question: ${nextQuestionObj.question}`, {
|
|
318
|
-
lipsyncLang:
|
|
357
|
+
lipsyncLang: config.lipsyncLang
|
|
319
358
|
});
|
|
320
359
|
} else if (nextQuestionObj.type === 'true_false') {
|
|
321
360
|
avatarRef.current.speakText(`Now let's try this one: ${nextQuestionObj.question}`, {
|
|
322
|
-
lipsyncLang:
|
|
361
|
+
lipsyncLang: config.lipsyncLang
|
|
323
362
|
});
|
|
324
363
|
} else {
|
|
325
364
|
avatarRef.current.speakText(`Here's the next question: ${nextQuestionObj.question}`, {
|
|
326
|
-
lipsyncLang:
|
|
365
|
+
lipsyncLang: config.lipsyncLang
|
|
327
366
|
});
|
|
328
367
|
}
|
|
329
368
|
}
|
|
@@ -333,10 +372,11 @@ const CurriculumLearning = forwardRef(({
|
|
|
333
372
|
completeLessonRef.current();
|
|
334
373
|
}
|
|
335
374
|
}
|
|
336
|
-
}, [animations.nextQuestion, getCurrentLesson, getCurrentQuestion
|
|
375
|
+
}, [animations.nextQuestion, getCurrentLesson, getCurrentQuestion]);
|
|
337
376
|
|
|
338
377
|
// Move to next lesson
|
|
339
378
|
const nextLesson = useCallback(() => {
|
|
379
|
+
const curriculum = curriculumRef.current || { modules: [] };
|
|
340
380
|
const currentModule = curriculum.modules[stateRef.current.currentModuleIndex];
|
|
341
381
|
if (stateRef.current.currentLessonIndex < (currentModule?.lessons?.length || 0) - 1) {
|
|
342
382
|
stateRef.current.currentLessonIndex += 1;
|
|
@@ -355,7 +395,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
355
395
|
});
|
|
356
396
|
|
|
357
397
|
if (avatarRef.current) {
|
|
358
|
-
|
|
398
|
+
avatarRef.current.setMood("happy");
|
|
359
399
|
avatarRef.current.setBodyMovement("idle");
|
|
360
400
|
|
|
361
401
|
// Automatically start teaching the next lesson after a brief pause
|
|
@@ -370,7 +410,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
370
410
|
completeCurriculumRef.current();
|
|
371
411
|
}
|
|
372
412
|
}
|
|
373
|
-
}, [
|
|
413
|
+
}, []);
|
|
374
414
|
|
|
375
415
|
// Start teaching the lesson
|
|
376
416
|
const startTeaching = useCallback(() => {
|
|
@@ -384,10 +424,10 @@ const CurriculumLearning = forwardRef(({
|
|
|
384
424
|
// Play animation if available (can be overridden via custom action)
|
|
385
425
|
let animationPlayed = false;
|
|
386
426
|
if (animations.teaching) {
|
|
387
|
-
|
|
427
|
+
try {
|
|
388
428
|
avatarRef.current.playAnimation(animations.teaching, true);
|
|
389
429
|
animationPlayed = true;
|
|
390
|
-
|
|
430
|
+
} catch (error) {
|
|
391
431
|
console.warn('Failed to play teaching animation:', error);
|
|
392
432
|
}
|
|
393
433
|
}
|
|
@@ -396,7 +436,8 @@ const CurriculumLearning = forwardRef(({
|
|
|
396
436
|
avatarRef.current.setBodyMovement("gesturing");
|
|
397
437
|
}
|
|
398
438
|
|
|
399
|
-
|
|
439
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
440
|
+
avatarRef.current.speakText(currentLesson.avatar_script, { lipsyncLang: config.lipsyncLang });
|
|
400
441
|
|
|
401
442
|
callbacksRef.current.onLessonStart({
|
|
402
443
|
moduleIndex: stateRef.current.currentModuleIndex,
|
|
@@ -420,7 +461,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
420
461
|
if (startQuestionsRef.current) {
|
|
421
462
|
startQuestionsRef.current();
|
|
422
463
|
}
|
|
423
|
-
|
|
464
|
+
} else {
|
|
424
465
|
// No questions, complete the lesson using ref to avoid circular dependency
|
|
425
466
|
if (completeLessonRef.current) {
|
|
426
467
|
completeLessonRef.current();
|
|
@@ -428,7 +469,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
428
469
|
}
|
|
429
470
|
}, 8000);
|
|
430
471
|
}
|
|
431
|
-
}, [animations.teaching, getCurrentLesson
|
|
472
|
+
}, [animations.teaching, getCurrentLesson]);
|
|
432
473
|
|
|
433
474
|
// Handle answer selection
|
|
434
475
|
const handleAnswerSelect = useCallback((answer) => {
|
|
@@ -450,11 +491,11 @@ const CurriculumLearning = forwardRef(({
|
|
|
450
491
|
|
|
451
492
|
if (avatarRef.current) {
|
|
452
493
|
if (isCorrect) {
|
|
453
|
-
|
|
494
|
+
avatarRef.current.setMood("happy");
|
|
454
495
|
if (animations.correct) {
|
|
455
|
-
|
|
496
|
+
try {
|
|
456
497
|
avatarRef.current.playReaction("happy");
|
|
457
|
-
|
|
498
|
+
} catch (error) {
|
|
458
499
|
avatarRef.current.setBodyMovement("happy");
|
|
459
500
|
}
|
|
460
501
|
}
|
|
@@ -463,9 +504,11 @@ const CurriculumLearning = forwardRef(({
|
|
|
463
504
|
? `Great job! Your code passed all the tests! ${currentQuestion.explanation || ''}`
|
|
464
505
|
: `Excellent! That's correct! ${currentQuestion.explanation || ''}`;
|
|
465
506
|
|
|
507
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
508
|
+
|
|
466
509
|
// Wait for speech to finish before moving to next question
|
|
467
510
|
avatarRef.current.speakText(successMessage, {
|
|
468
|
-
lipsyncLang:
|
|
511
|
+
lipsyncLang: config.lipsyncLang,
|
|
469
512
|
onSpeechEnd: () => {
|
|
470
513
|
// Add a small delay after speech ends for natural flow
|
|
471
514
|
setTimeout(() => {
|
|
@@ -475,12 +518,12 @@ const CurriculumLearning = forwardRef(({
|
|
|
475
518
|
}, 500);
|
|
476
519
|
}
|
|
477
520
|
});
|
|
478
|
-
|
|
521
|
+
} else {
|
|
479
522
|
avatarRef.current.setMood("sad");
|
|
480
523
|
if (animations.incorrect) {
|
|
481
524
|
try {
|
|
482
525
|
avatarRef.current.playAnimation(animations.incorrect, true);
|
|
483
|
-
|
|
526
|
+
} catch (error) {
|
|
484
527
|
avatarRef.current.setBodyMovement("idle");
|
|
485
528
|
}
|
|
486
529
|
}
|
|
@@ -489,9 +532,11 @@ const CurriculumLearning = forwardRef(({
|
|
|
489
532
|
? `Your code didn't pass all the tests. ${currentQuestion.explanation || 'Try again!'}`
|
|
490
533
|
: `Not quite right, but don't worry! ${currentQuestion.explanation || ''} Let's move on to the next question.`;
|
|
491
534
|
|
|
535
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
536
|
+
|
|
492
537
|
// Wait for speech to finish before moving to next question
|
|
493
538
|
avatarRef.current.speakText(failureMessage, {
|
|
494
|
-
lipsyncLang:
|
|
539
|
+
lipsyncLang: config.lipsyncLang,
|
|
495
540
|
onSpeechEnd: () => {
|
|
496
541
|
// Add a small delay after speech ends for natural flow
|
|
497
542
|
setTimeout(() => {
|
|
@@ -508,7 +553,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
508
553
|
nextQuestionRef.current();
|
|
509
554
|
}
|
|
510
555
|
}
|
|
511
|
-
}, [animations.correct, animations.incorrect, getCurrentQuestion, checkAnswer
|
|
556
|
+
}, [animations.correct, animations.incorrect, getCurrentQuestion, checkAnswer]);
|
|
512
557
|
|
|
513
558
|
// Handle code test result submission
|
|
514
559
|
const handleCodeTestResult = useCallback((testResult) => {
|
|
@@ -552,7 +597,7 @@ const CurriculumLearning = forwardRef(({
|
|
|
552
597
|
if (handleAnswerSelectRef.current) {
|
|
553
598
|
handleAnswerSelectRef.current(codeTestAnswer);
|
|
554
599
|
}
|
|
555
|
-
}, [getCurrentQuestion, checkAnswer
|
|
600
|
+
}, [getCurrentQuestion, checkAnswer]);
|
|
556
601
|
|
|
557
602
|
// Reset curriculum
|
|
558
603
|
const resetCurriculum = useCallback(() => {
|
|
@@ -615,7 +660,8 @@ const CurriculumLearning = forwardRef(({
|
|
|
615
660
|
speakText: async (text, options = {}) => {
|
|
616
661
|
// Ensure audio context is resumed before speaking
|
|
617
662
|
await avatarRef.current?.resumeAudioContext?.();
|
|
618
|
-
|
|
663
|
+
const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
|
|
664
|
+
avatarRef.current?.speakText(text, { ...options, lipsyncLang: options.lipsyncLang || config.lipsyncLang });
|
|
619
665
|
},
|
|
620
666
|
resumeAudioContext: async () => {
|
|
621
667
|
// Try to resume through avatar ref first
|
|
@@ -667,6 +713,22 @@ const CurriculumLearning = forwardRef(({
|
|
|
667
713
|
isAvatarReady: () => avatarRef.current?.isReady || false
|
|
668
714
|
}), [startTeaching, startQuestions, handleAnswerSelect, handleCodeTestResult, nextQuestion, nextLesson, completeLesson, completeCurriculum, resetCurriculum, getCurrentQuestion, getCurrentLesson]);
|
|
669
715
|
|
|
716
|
+
// Get current config for render
|
|
717
|
+
const defaultAvatarConfig = defaultAvatarConfigRef.current || {
|
|
718
|
+
avatarUrl: "/avatars/brunette.glb",
|
|
719
|
+
avatarBody: "F",
|
|
720
|
+
mood: "happy",
|
|
721
|
+
ttsLang: "en",
|
|
722
|
+
ttsService: null,
|
|
723
|
+
ttsVoice: null,
|
|
724
|
+
ttsApiKey: null,
|
|
725
|
+
bodyMovement: "gesturing",
|
|
726
|
+
movementIntensity: 0.7,
|
|
727
|
+
showFullAvatar: true,
|
|
728
|
+
animations: animations,
|
|
729
|
+
lipsyncLang: 'en'
|
|
730
|
+
};
|
|
731
|
+
|
|
670
732
|
return (
|
|
671
733
|
<div style={{ width: '100%', height: '100%' }}>
|
|
672
734
|
<TalkingHeadAvatar
|
|
@@ -697,4 +759,4 @@ const CurriculumLearning = forwardRef(({
|
|
|
697
759
|
|
|
698
760
|
CurriculumLearning.displayName = 'CurriculumLearning';
|
|
699
761
|
|
|
700
|
-
export default CurriculumLearning;
|
|
762
|
+
export default CurriculumLearning;
|