@sage-rsc/talking-head-react 1.0.41 → 1.0.43

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.js CHANGED
@@ -2734,9 +2734,9 @@ class Se {
2734
2734
  cameraPanEnable: !1,
2735
2735
  cameraZoomEnable: !1,
2736
2736
  lightAmbientColor: 16777215,
2737
- lightAmbientIntensity: 2,
2737
+ lightAmbientIntensity: 0.9,
2738
2738
  lightDirectColor: 8947882,
2739
- lightDirectIntensity: 30,
2739
+ lightDirectIntensity: 14,
2740
2740
  lightDirectPhi: 1,
2741
2741
  lightDirectTheta: 2,
2742
2742
  lightSpotIntensity: 0,
@@ -5251,10 +5251,10 @@ class Se {
5251
5251
  let u = "", l = "", c = 0, d = [], g = [];
5252
5252
  const y = Array.from(this.segmenter.segment(t), (x) => x.segment);
5253
5253
  for (let x = 0; x < y.length; x++) {
5254
- const v = x === y.length - 1, H = y[x].match(r);
5254
+ const v = x === y.length - 1, C = y[x].match(r);
5255
5255
  let p = y[x].match(s);
5256
5256
  const E = y[x].match(h), z = y[x].match(o);
5257
- if (p && !v && !E && y[x + 1].match(s) && (p = !1), i && (u += y[x]), H && (!n || n.every((I) => x < I[0] || x > I[1])) && (l += y[x]), (z || p || v) && (l.length && (l = this.lipsyncPreProcessText(l, a), l.length && d.push({
5257
+ if (p && !v && !E && y[x + 1].match(s) && (p = !1), i && (u += y[x]), C && (!n || n.every((I) => x < I[0] || x > I[1])) && (l += y[x]), (z || p || v) && (l.length && (l = this.lipsyncPreProcessText(l, a), l.length && d.push({
5258
5258
  mark: c,
5259
5259
  word: l
5260
5260
  })), u.length && (g.push({
@@ -5385,10 +5385,10 @@ class Se {
5385
5385
  let y = 0.6 + this.convertRange(g, [0, u], [0, 0.4]);
5386
5386
  if (u = Math.min(u, c.visemes.length * 200), d > 0)
5387
5387
  for (let x = 0; x < c.visemes.length; x++) {
5388
- const v = a + c.times[x] / d * u, H = c.durations[x] / d * u;
5388
+ const v = a + c.times[x] / d * u, C = c.durations[x] / d * u;
5389
5389
  o.push({
5390
5390
  template: { name: "viseme" },
5391
- ts: [v - Math.min(60, 2 * H / 3), v + Math.min(25, H / 2), v + H + Math.min(60, H / 2)],
5391
+ ts: [v - Math.min(60, 2 * C / 3), v + Math.min(25, C / 2), v + C + Math.min(60, C / 2)],
5392
5392
  vs: {
5393
5393
  ["viseme_" + c.visemes[x]]: [null, c.visemes[x] === "PP" || c.visemes[x] === "FF" ? 0.9 : y, 0]
5394
5394
  }
@@ -5496,8 +5496,8 @@ class Se {
5496
5496
  });
5497
5497
  }
5498
5498
  }
5499
- const H = [...t.anim, ...v];
5500
- this.audioPlaylist.push({ anim: H, audio: d }), this.onSubtitles = t.onSubtitles || null, this.resetLips(), t.mood && this.setMood(t.mood), this.playAudio(), s.onend = () => {
5499
+ const C = [...t.anim, ...v];
5500
+ this.audioPlaylist.push({ anim: C, audio: d }), this.onSubtitles = t.onSubtitles || null, this.resetLips(), t.mood && this.setMood(t.mood), this.playAudio(), s.onend = () => {
5501
5501
  e();
5502
5502
  }, s.onerror = (p) => {
5503
5503
  console.error("Speech synthesis error:", p.error), i(p.error);
@@ -6189,7 +6189,7 @@ class Se {
6189
6189
  t === null && (t = h), e === null && (e = a), N.copy(this.armature.quaternion), N.multiply(this.poseTarget.props["Hips.quaternion"]), N.multiply(this.poseTarget.props["Spine.quaternion"]), N.multiply(this.poseTarget.props["Spine1.quaternion"]), N.multiply(this.poseTarget.props["Spine2.quaternion"]), N.multiply(this.poseTarget.props["Neck.quaternion"]), N.multiply(this.poseTarget.props["Head.quaternion"]), T.setFromQuaternion(N);
6190
6190
  let u = T.x / (40 / 24), l = T.y / (9 / 4), c = Math.min(0.4, Math.max(-0.4, this.camera.rotation.x)), d = Math.min(0.4, Math.max(-0.4, this.camera.rotation.y)), g = Math.max(window.innerWidth - h, h), y = Math.max(window.innerHeight - a, a), x = this.convertRange(e, [a - y, a + y], [-0.3, 0.6]) - u + c, v = this.convertRange(t, [h - g, h + g], [-0.8, 0.8]) - l + d;
6191
6191
  x = Math.min(0.6, Math.max(-0.3, x)), v = Math.min(0.8, Math.max(-0.8, v));
6192
- let H = (Math.random() - 0.5) / 4, p = (Math.random() - 0.5) / 4;
6192
+ let C = (Math.random() - 0.5) / 4, p = (Math.random() - 0.5) / 4;
6193
6193
  if (i) {
6194
6194
  let E = this.animQueue.findIndex((I) => I.template.name === "lookat");
6195
6195
  E !== -1 && this.animQueue.splice(E, 1);
@@ -6197,9 +6197,9 @@ class Se {
6197
6197
  name: "lookat",
6198
6198
  dt: [750, i],
6199
6199
  vs: {
6200
- bodyRotateX: [x + H],
6200
+ bodyRotateX: [x + C],
6201
6201
  bodyRotateY: [v + p],
6202
- eyesRotateX: [-3 * H + 0.1],
6202
+ eyesRotateX: [-3 * C + 0.1],
6203
6203
  eyesRotateY: [-5 * p],
6204
6204
  browInnerUp: [[0, 0.7]],
6205
6205
  mouthLeft: [[0, 0.7]],
@@ -6577,7 +6577,7 @@ class Se {
6577
6577
  const x = t.iterations || 10;
6578
6578
  if (e)
6579
6579
  for (let v = 0; v < x; v++) {
6580
- let H = !1;
6580
+ let C = !1;
6581
6581
  for (let p = 0, E = y.length; p < E; p++) {
6582
6582
  const z = y[p].bone;
6583
6583
  z.matrixWorld.decompose(h, a, u), a.invert(), o.setFromMatrixPosition(g.matrixWorld), r.subVectors(o, h), r.applyQuaternion(a), r.normalize(), s.subVectors(e, h), s.applyQuaternion(a), s.normalize();
@@ -6590,9 +6590,9 @@ class Se {
6590
6590
  y[p].maxx !== void 0 ? y[p].maxx : 1 / 0,
6591
6591
  y[p].maxy !== void 0 ? y[p].maxy : 1 / 0,
6592
6592
  y[p].maxz !== void 0 ? y[p].maxz : 1 / 0
6593
- ))), z.updateMatrixWorld(!0), H = !0);
6593
+ ))), z.updateMatrixWorld(!0), C = !0);
6594
6594
  }
6595
- if (!H) break;
6595
+ if (!C) break;
6596
6596
  }
6597
6597
  n && y.forEach((v) => {
6598
6598
  this.poseTarget.props[v.link + ".quaternion"].copy(v.bone.quaternion), this.poseTarget.props[v.link + ".quaternion"].t = this.animClock, this.poseTarget.props[v.link + ".quaternion"].d = n;
@@ -6685,7 +6685,7 @@ const ke = ye(({
6685
6685
  style: y = {},
6686
6686
  animations: x = {}
6687
6687
  }, v) => {
6688
- const H = X(null), p = X(null), [E, z] = re(!0), [I, D] = re(null), [P, J] = re(!1), Y = xe(), S = n || Y.service;
6688
+ const C = X(null), p = X(null), [E, z] = re(!0), [I, D] = re(null), [P, J] = re(!1), Y = xe(), S = n || Y.service;
6689
6689
  let F;
6690
6690
  S === "browser" ? F = {
6691
6691
  service: "browser",
@@ -6726,9 +6726,9 @@ const ke = ye(({
6726
6726
  lipsyncModules: ["en"],
6727
6727
  cameraView: u
6728
6728
  }, Q = M(async () => {
6729
- if (!(!H.current || p.current))
6729
+ if (!(!C.current || p.current))
6730
6730
  try {
6731
- if (z(!0), D(null), p.current = new Se(H.current, V), p.current.controls && (p.current.controls.enableRotate = !1, p.current.controls.enableZoom = !1, p.current.controls.enablePan = !1, p.current.controls.enableDamping = !1), x && Object.keys(x).length > 0 && (p.current.customAnimations = x), await p.current.showAvatar(W, (U) => {
6731
+ if (z(!0), D(null), p.current = new Se(C.current, V), p.current.controls && (p.current.controls.enableRotate = !1, p.current.controls.enableZoom = !1, p.current.controls.enablePan = !1, p.current.controls.enableDamping = !1), x && Object.keys(x).length > 0 && (p.current.customAnimations = x), await p.current.showAvatar(W, (U) => {
6732
6732
  if (U.lengthComputable) {
6733
6733
  const O = Math.min(100, Math.round(U.loaded / U.total * 100));
6734
6734
  c(O);
@@ -6745,11 +6745,11 @@ const ke = ye(({
6745
6745
  console.warn("Error setting full body mode on initialization:", U);
6746
6746
  }
6747
6747
  p.current && p.current.controls && (p.current.controls.enableRotate = !1, p.current.controls.enableZoom = !1, p.current.controls.enablePan = !1, p.current.controls.enableDamping = !1, p.current.controls.update()), z(!1), J(!0), l(p.current);
6748
- const C = () => {
6748
+ const H = () => {
6749
6749
  document.visibilityState === "visible" ? p.current?.start() : p.current?.stop();
6750
6750
  };
6751
- return document.addEventListener("visibilitychange", C), () => {
6752
- document.removeEventListener("visibilitychange", C);
6751
+ return document.addEventListener("visibilitychange", H), () => {
6752
+ document.removeEventListener("visibilitychange", H);
6753
6753
  };
6754
6754
  } catch (L) {
6755
6755
  console.error("Error initializing TalkingHead:", L), D(L.message || "Failed to initialize avatar"), z(!1), d(L);
@@ -6758,17 +6758,17 @@ const ke = ye(({
6758
6758
  he(() => (Q(), () => {
6759
6759
  p.current && (p.current.stop(), p.current.dispose(), p.current = null);
6760
6760
  }), [Q]), he(() => {
6761
- if (!H.current || !p.current) return;
6761
+ if (!C.current || !p.current) return;
6762
6762
  const L = new ResizeObserver((U) => {
6763
6763
  for (const O of U)
6764
6764
  p.current && p.current.onResize && p.current.onResize();
6765
6765
  });
6766
- L.observe(H.current);
6767
- const C = () => {
6766
+ L.observe(C.current);
6767
+ const H = () => {
6768
6768
  p.current && p.current.onResize && p.current.onResize();
6769
6769
  };
6770
- return window.addEventListener("resize", C), () => {
6771
- L.disconnect(), window.removeEventListener("resize", C);
6770
+ return window.addEventListener("resize", H), () => {
6771
+ L.disconnect(), window.removeEventListener("resize", H);
6772
6772
  };
6773
6773
  }, [P]);
6774
6774
  const q = M(async () => {
@@ -6778,22 +6778,22 @@ const ke = ye(({
6778
6778
  } catch (L) {
6779
6779
  console.warn("Failed to resume audio context:", L);
6780
6780
  }
6781
- }, []), le = M(async (L, C = {}) => {
6781
+ }, []), le = M(async (L, H = {}) => {
6782
6782
  if (p.current && P)
6783
6783
  try {
6784
6784
  await q();
6785
6785
  const U = {
6786
- ...C,
6787
- lipsyncLang: C.lipsyncLang || W.lipsyncLang || "en"
6786
+ ...H,
6787
+ lipsyncLang: H.lipsyncLang || W.lipsyncLang || "en"
6788
6788
  };
6789
- if (C.onSpeechEnd && p.current) {
6789
+ if (H.onSpeechEnd && p.current) {
6790
6790
  const O = p.current, ne = O.onAudioEnd;
6791
6791
  let $ = null, pe = 0;
6792
6792
  const ze = 600, Ce = () => {
6793
6793
  if (pe++, pe > ze) {
6794
6794
  $ && (clearInterval($), $ = null);
6795
6795
  try {
6796
- C.onSpeechEnd();
6796
+ H.onSpeechEnd();
6797
6797
  } catch (ge) {
6798
6798
  console.error("Error in onSpeechEnd callback:", ge);
6799
6799
  }
@@ -6801,19 +6801,19 @@ const ke = ye(({
6801
6801
  }
6802
6802
  O && (!O.isSpeaking || O.isSpeaking === !1) && (!O.audioPlaylist || O.audioPlaylist.length === 0) && (!O.isAudioPlaying || O.isAudioPlaying === !1) && ($ && (clearInterval($), $ = null), setTimeout(() => {
6803
6803
  try {
6804
- C.onSpeechEnd();
6804
+ H.onSpeechEnd();
6805
6805
  } catch (ge) {
6806
6806
  console.error("Error in onSpeechEnd callback:", ge);
6807
6807
  }
6808
- }, 100));
6808
+ }, 10));
6809
6809
  };
6810
6810
  setTimeout(() => {
6811
- $ = setInterval(Ce, 100);
6812
- }, 500);
6811
+ $ = setInterval(Ce, 50);
6812
+ }, 100);
6813
6813
  }
6814
6814
  p.current.lipsync && Object.keys(p.current.lipsync).length > 0 ? (p.current.setSlowdownRate && p.current.setSlowdownRate(1.05), p.current.speakText(L, U)) : setTimeout(async () => {
6815
6815
  await q(), p.current && p.current.lipsync && (p.current.setSlowdownRate && p.current.setSlowdownRate(1.05), p.current.speakText(L, U));
6816
- }, 500);
6816
+ }, 100);
6817
6817
  } catch (U) {
6818
6818
  console.error("Error speaking text:", U), D(U.message || "Failed to speak text");
6819
6819
  }
@@ -6823,7 +6823,7 @@ const ke = ye(({
6823
6823
  p.current && p.current.setMood(L);
6824
6824
  }, []), b = M((L) => {
6825
6825
  p.current && p.current.setSlowdownRate && p.current.setSlowdownRate(L);
6826
- }, []), R = M((L, C = !1) => {
6826
+ }, []), R = M((L, H = !1) => {
6827
6827
  if (p.current && p.current.playAnimation) {
6828
6828
  if (x && x[L] && (L = x[L]), p.current.setShowFullAvatar)
6829
6829
  try {
@@ -6833,7 +6833,7 @@ const ke = ye(({
6833
6833
  }
6834
6834
  if (L.includes("."))
6835
6835
  try {
6836
- p.current.playAnimation(L, null, 10, 0, 0.01, C);
6836
+ p.current.playAnimation(L, null, 10, 0, 0.01, H);
6837
6837
  } catch (O) {
6838
6838
  console.warn(`Failed to play ${L}:`, O);
6839
6839
  try {
@@ -6847,7 +6847,7 @@ const ke = ye(({
6847
6847
  let ne = !1;
6848
6848
  for (const $ of O)
6849
6849
  try {
6850
- p.current.playAnimation(L + $, null, 10, 0, 0.01, C), ne = !0;
6850
+ p.current.playAnimation(L + $, null, 10, 0, 0.01, H), ne = !0;
6851
6851
  break;
6852
6852
  } catch {
6853
6853
  }
@@ -6878,8 +6878,8 @@ const ke = ye(({
6878
6878
  if (p.current && p.current.setShowFullAvatar && p.current.setBodyMovement)
6879
6879
  try {
6880
6880
  p.current.setShowFullAvatar(!0), p.current.setBodyMovement(L);
6881
- } catch (C) {
6882
- console.warn("Error setting body movement:", C);
6881
+ } catch (H) {
6882
+ console.warn("Error setting body movement:", H);
6883
6883
  }
6884
6884
  },
6885
6885
  setMovementIntensity: (L) => p.current?.setMovementIntensity(L),
@@ -6895,8 +6895,8 @@ const ke = ye(({
6895
6895
  if (p.current && p.current.setShowFullAvatar && p.current.playReaction)
6896
6896
  try {
6897
6897
  p.current.setShowFullAvatar(!0), p.current.playReaction(L);
6898
- } catch (C) {
6899
- console.warn("Error playing reaction:", C);
6898
+ } catch (H) {
6899
+ console.warn("Error playing reaction:", H);
6900
6900
  }
6901
6901
  },
6902
6902
  playCelebration: () => {
@@ -6911,8 +6911,8 @@ const ke = ye(({
6911
6911
  if (p.current && p.current.setShowFullAvatar)
6912
6912
  try {
6913
6913
  p.current.setShowFullAvatar(L);
6914
- } catch (C) {
6915
- console.warn("Error setting showFullAvatar:", C);
6914
+ } catch (H) {
6915
+ console.warn("Error setting showFullAvatar:", H);
6916
6916
  }
6917
6917
  },
6918
6918
  lockAvatarPosition: () => {
@@ -6945,7 +6945,7 @@ const ke = ye(({
6945
6945
  /* @__PURE__ */ se(
6946
6946
  "div",
6947
6947
  {
6948
- ref: H,
6948
+ ref: C,
6949
6949
  className: "talking-head-viewer",
6950
6950
  style: {
6951
6951
  width: "100%",
@@ -6992,7 +6992,7 @@ const $e = ye(({
6992
6992
  style: s = {},
6993
6993
  avatarConfig: o = {}
6994
6994
  }, r) => {
6995
- const h = X(null), a = X(null), [u, l] = re(!0), [c, d] = re(null), [g, y] = re(!1), x = xe(), v = o.ttsService || x.service, H = v === "browser" ? {
6995
+ const h = X(null), a = X(null), [u, l] = re(!0), [c, d] = re(null), [g, y] = re(!1), x = xe(), v = o.ttsService || x.service, C = v === "browser" ? {
6996
6996
  endpoint: "",
6997
6997
  apiKey: null,
6998
6998
  defaultVoice: "Google US English"
@@ -7008,7 +7008,7 @@ const $e = ye(({
7008
7008
  body: "F",
7009
7009
  avatarMood: "neutral",
7010
7010
  ttsLang: v === "browser" ? "en-US" : "en",
7011
- ttsVoice: o.ttsVoice || H.defaultVoice,
7011
+ ttsVoice: o.ttsVoice || C.defaultVoice,
7012
7012
  lipsyncLang: "en",
7013
7013
  // English lip-sync
7014
7014
  showFullAvatar: !0,
@@ -7017,8 +7017,8 @@ const $e = ye(({
7017
7017
  movementIntensity: 0.5,
7018
7018
  ...o
7019
7019
  }, E = {
7020
- ttsEndpoint: H.endpoint,
7021
- ttsApikey: H.apiKey,
7020
+ ttsEndpoint: C.endpoint,
7021
+ ttsApikey: C.apiKey,
7022
7022
  ttsService: v,
7023
7023
  lipsyncModules: ["en"],
7024
7024
  cameraView: "upper"
@@ -7253,7 +7253,7 @@ const et = ye(({
7253
7253
  onQuestionAnswer: s,
7254
7254
  onCurriculumComplete: o,
7255
7255
  onCustomAction: r
7256
- }), d = X(null), g = X(null), y = X(null), x = X(null), v = X(null), H = X(null), p = X(null), E = X(B?.curriculum || {
7256
+ }), d = X(null), g = X(null), y = X(null), x = X(null), v = X(null), C = X(null), p = X(null), E = X(B?.curriculum || {
7257
7257
  title: "Default Curriculum",
7258
7258
  description: "No curriculum data provided",
7259
7259
  language: "en",
@@ -7325,7 +7325,7 @@ const et = ye(({
7325
7325
  } catch {
7326
7326
  u.current.playCelebration();
7327
7327
  }
7328
- const w = E.current || { modules: [] }, L = w.modules[l.current.currentModuleIndex], C = l.current.currentLessonIndex < (L?.lessons?.length || 0) - 1, U = l.current.currentModuleIndex < (w.modules?.length || 0) - 1, O = C || U, ne = z.current || { lipsyncLang: "en" };
7328
+ const w = E.current || { modules: [] }, L = w.modules[l.current.currentModuleIndex], H = l.current.currentLessonIndex < (L?.lessons?.length || 0) - 1, U = l.current.currentModuleIndex < (w.modules?.length || 0) - 1, O = H || U, ne = z.current || { lipsyncLang: "en" };
7329
7329
  O ? u.current.speakText(R, {
7330
7330
  lipsyncLang: ne.lipsyncLang,
7331
7331
  onSpeechEnd: () => {
@@ -7365,7 +7365,7 @@ const et = ye(({
7365
7365
  questionIndex: l.current.currentQuestionIndex,
7366
7366
  totalQuestions: l.current.totalQuestions,
7367
7367
  question: R
7368
- }), u.current && R) {
7368
+ }), u.current && u.current.isReady && R) {
7369
7369
  if (u.current.setMood("curious"), e.questionStart)
7370
7370
  try {
7371
7371
  u.current.playAnimation(e.questionStart, !0);
@@ -7374,10 +7374,13 @@ const et = ye(({
7374
7374
  }
7375
7375
  const w = z.current || { lipsyncLang: "en" };
7376
7376
  R.type === "code_test" ? u.current.speakText(`Let's test your coding skills! Here's your first challenge: ${R.question}`, { lipsyncLang: w.lipsyncLang }) : R.type === "multiple_choice" ? u.current.speakText(`Now let me ask you some questions. Here's the first one: ${R.question}`, { lipsyncLang: w.lipsyncLang }) : R.type === "true_false" ? u.current.speakText(`Let's start with some true or false questions. First question: ${R.question}`, { lipsyncLang: w.lipsyncLang }) : u.current.speakText(`Now let me ask you some questions. Here's the first one: ${R.question}`, { lipsyncLang: w.lipsyncLang });
7377
- } else if (u.current) {
7377
+ } else if (u.current && u.current.isReady) {
7378
7378
  const w = z.current || { lipsyncLang: "en" };
7379
7379
  u.current.speakText("Now let me ask you some questions to test your understanding.", { lipsyncLang: w.lipsyncLang });
7380
- }
7380
+ } else
7381
+ setTimeout(() => {
7382
+ C.current && C.current();
7383
+ }, 100);
7381
7384
  }, [e.questionStart, I, D]), F = M(() => {
7382
7385
  const b = I();
7383
7386
  if (l.current.currentQuestionIndex < (b?.questions?.length || 0) - 1) {
@@ -7425,8 +7428,8 @@ const et = ye(({
7425
7428
  const b = I();
7426
7429
  let R = null;
7427
7430
  if (b?.avatar_script && b?.body) {
7428
- const w = b.avatar_script.trim(), L = b.body.trim(), C = w.match(/[.!?]$/) ? " " : ". ";
7429
- R = `${w}${C}${L}`;
7431
+ const w = b.avatar_script.trim(), L = b.body.trim(), H = w.match(/[.!?]$/) ? " " : ". ";
7432
+ R = `${w}${H}${L}`;
7430
7433
  } else
7431
7434
  R = b?.avatar_script || b?.body || null;
7432
7435
  if (u.current && u.current.isReady && R) {
@@ -7435,8 +7438,8 @@ const et = ye(({
7435
7438
  if (e.teaching)
7436
7439
  try {
7437
7440
  u.current.playAnimation(e.teaching, !0), w = !0;
7438
- } catch (C) {
7439
- console.warn("Failed to play teaching animation:", C);
7441
+ } catch (H) {
7442
+ console.warn("Failed to play teaching animation:", H);
7440
7443
  }
7441
7444
  w || u.current.setBodyMovement("gesturing");
7442
7445
  const L = z.current || { lipsyncLang: "en" };
@@ -7452,7 +7455,7 @@ const et = ye(({
7452
7455
  }), u.current.speakText(R, {
7453
7456
  lipsyncLang: L.lipsyncLang,
7454
7457
  onSpeechEnd: () => {
7455
- l.current.isTeaching = !1, b.questions && b.questions.length > 0 ? H.current && H.current() : y.current && y.current();
7458
+ l.current.isTeaching = !1, b.questions && b.questions.length > 0 ? C.current && C.current() : y.current && y.current();
7456
7459
  }
7457
7460
  });
7458
7461
  }
@@ -7474,9 +7477,9 @@ const et = ye(({
7474
7477
  u.current.setBodyMovement("happy");
7475
7478
  }
7476
7479
  u.current.setBodyMovement("gesturing");
7477
- const L = R.type === "code_test" ? `Great job! Your code passed all the tests! ${R.explanation || ""}` : `Excellent! That's correct! ${R.explanation || ""}`, C = z.current || { lipsyncLang: "en" };
7480
+ const L = R.type === "code_test" ? `Great job! Your code passed all the tests! ${R.explanation || ""}` : `Excellent! That's correct! ${R.explanation || ""}`, H = z.current || { lipsyncLang: "en" };
7478
7481
  u.current.speakText(L, {
7479
- lipsyncLang: C.lipsyncLang,
7482
+ lipsyncLang: H.lipsyncLang,
7480
7483
  onSpeechEnd: () => {
7481
7484
  x.current && x.current();
7482
7485
  }
@@ -7489,9 +7492,9 @@ const et = ye(({
7489
7492
  u.current.setBodyMovement("idle");
7490
7493
  }
7491
7494
  u.current.setBodyMovement("gesturing");
7492
- const L = R.type === "code_test" ? `Your code didn't pass all the tests. ${R.explanation || "Try again!"}` : `Not quite right, but don't worry! ${R.explanation || ""} Let's move on to the next question.`, C = z.current || { lipsyncLang: "en" };
7495
+ const L = R.type === "code_test" ? `Your code didn't pass all the tests. ${R.explanation || "Try again!"}` : `Not quite right, but don't worry! ${R.explanation || ""} Let's move on to the next question.`, H = z.current || { lipsyncLang: "en" };
7493
7496
  u.current.speakText(L, {
7494
- lipsyncLang: C.lipsyncLang,
7497
+ lipsyncLang: H.lipsyncLang,
7495
7498
  onSpeechEnd: () => {
7496
7499
  x.current && x.current();
7497
7500
  }
@@ -7534,10 +7537,10 @@ const et = ye(({
7534
7537
  const R = I(), w = R?.avatar_script || R?.body;
7535
7538
  h && w && setTimeout(() => {
7536
7539
  d.current && d.current();
7537
- }, 50);
7540
+ }, 10);
7538
7541
  }, [h, I]);
7539
7542
  He(() => {
7540
- d.current = V, g.current = W, y.current = J, x.current = F, v.current = Y, H.current = S, p.current = Q;
7543
+ d.current = V, g.current = W, y.current = J, x.current = F, v.current = Y, C.current = S, p.current = Q;
7541
7544
  }), fe(a, () => ({
7542
7545
  // Curriculum control methods
7543
7546
  startTeaching: V,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-rsc/talking-head-react",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
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",
@@ -279,7 +279,8 @@ const CurriculumLearning = forwardRef(({
279
279
  });
280
280
  }
281
281
 
282
- if (avatarRef.current && firstQuestion) {
282
+ // Ensure avatar is ready before speaking
283
+ if (avatarRef.current && avatarRef.current.isReady && firstQuestion) {
283
284
  avatarRef.current.setMood("curious");
284
285
 
285
286
  // Play custom animation if available
@@ -303,9 +304,16 @@ const CurriculumLearning = forwardRef(({
303
304
  } else {
304
305
  avatarRef.current.speakText(`Now let me ask you some questions. Here's the first one: ${firstQuestion.question}`, { lipsyncLang: config.lipsyncLang });
305
306
  }
306
- } else if (avatarRef.current) {
307
+ } else if (avatarRef.current && avatarRef.current.isReady) {
307
308
  const config = defaultAvatarConfigRef.current || { lipsyncLang: 'en' };
308
309
  avatarRef.current.speakText("Now let me ask you some questions to test your understanding.", { lipsyncLang: config.lipsyncLang });
310
+ } else {
311
+ // Avatar not ready yet, retry after a short delay
312
+ setTimeout(() => {
313
+ if (startQuestionsRef.current) {
314
+ startQuestionsRef.current();
315
+ }
316
+ }, 100);
309
317
  }
310
318
  }, [animations.questionStart, getCurrentLesson, getCurrentQuestion]);
311
319
 
@@ -671,7 +679,7 @@ const CurriculumLearning = forwardRef(({
671
679
  if (startTeachingRef.current) {
672
680
  startTeachingRef.current();
673
681
  }
674
- }, 50);
682
+ }, 10);
675
683
  }
676
684
  }, [autoStart, getCurrentLesson]);
677
685
 
@@ -289,7 +289,7 @@ const TalkingHeadAvatar = forwardRef(({
289
289
  // This is because onAudioEnd is only for streaming mode
290
290
  let checkInterval = null;
291
291
  let checkCount = 0;
292
- const maxChecks = 600; // 60 seconds max (100ms intervals)
292
+ const maxChecks = 600; // 60 seconds max (50ms intervals for faster detection)
293
293
 
294
294
  const checkSpeechFinished = () => {
295
295
  checkCount++;
@@ -317,21 +317,21 @@ const TalkingHeadAvatar = forwardRef(({
317
317
  checkInterval = null;
318
318
  }
319
319
 
320
- // Small delay to ensure everything is settled
320
+ // Small delay to ensure everything is settled - reduced for faster transitions
321
321
  setTimeout(() => {
322
322
  try {
323
323
  options.onSpeechEnd();
324
324
  } catch (e) {
325
325
  console.error('Error in onSpeechEnd callback:', e);
326
326
  }
327
- }, 100);
327
+ }, 10);
328
328
  }
329
329
  };
330
330
 
331
- // Start checking after a short delay (to allow speech to start)
331
+ // Start checking after a minimal delay (to allow speech to start)
332
332
  setTimeout(() => {
333
- checkInterval = setInterval(checkSpeechFinished, 100);
334
- }, 500);
333
+ checkInterval = setInterval(checkSpeechFinished, 50);
334
+ }, 100);
335
335
  }
336
336
 
337
337
  if (talkingHeadRef.current.lipsync && Object.keys(talkingHeadRef.current.lipsync).length > 0) {
@@ -348,7 +348,7 @@ const TalkingHeadAvatar = forwardRef(({
348
348
  }
349
349
  talkingHeadRef.current.speakText(textToSpeak, speakOptions);
350
350
  }
351
- }, 500);
351
+ }, 100);
352
352
  }
353
353
  } catch (err) {
354
354
  console.error('Error speaking text:', err);
@@ -168,9 +168,9 @@ class TalkingHead {
168
168
  cameraPanEnable: false,
169
169
  cameraZoomEnable: false,
170
170
  lightAmbientColor: 0xffffff,
171
- lightAmbientIntensity: 2,
171
+ lightAmbientIntensity: 0.9,
172
172
  lightDirectColor: 0x8888aa,
173
- lightDirectIntensity: 30,
173
+ lightDirectIntensity: 14,
174
174
  lightDirectPhi: 1,
175
175
  lightDirectTheta: 2,
176
176
  lightSpotIntensity: 0,