@sage-rsc/talking-head-react 1.1.2 → 1.1.3

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
@@ -1,10 +1,10 @@
1
1
  import { jsxs as Pe, jsx as me } from "react/jsx-runtime";
2
- import { forwardRef as Me, useRef as D, useState as ce, useEffect as de, useCallback as T, useImperativeHandle as Ee, useLayoutEffect as Xe } from "react";
2
+ import { forwardRef as Me, useRef as N, useState as ce, useEffect as de, useCallback as T, useImperativeHandle as Ee, useLayoutEffect as Xe } from "react";
3
3
  import * as f from "three";
4
4
  import { OrbitControls as Ye } from "three/addons/controls/OrbitControls.js";
5
5
  import { GLTFLoader as je } from "three/addons/loaders/GLTFLoader.js";
6
6
  import { DRACOLoader as Qe } from "three/addons/loaders/DRACOLoader.js";
7
- import { FBXLoader as De } from "three/addons/loaders/FBXLoader.js";
7
+ import { FBXLoader as Ne } from "three/addons/loaders/FBXLoader.js";
8
8
  import { RoomEnvironment as qe } from "three/addons/environments/RoomEnvironment.js";
9
9
  import _e from "three/addons/libs/stats.module.js";
10
10
  let m, re, he;
@@ -2637,7 +2637,7 @@ new f.Vector3(0, 0, 1);
2637
2637
  const mt = new f.Vector3(1, 0, 0);
2638
2638
  new f.Vector3(0, 1, 0);
2639
2639
  new f.Vector3(0, 0, 1);
2640
- class Be {
2640
+ class De {
2641
2641
  /**
2642
2642
  * Avatar.
2643
2643
  * @typedef {Object} Avatar
@@ -5264,10 +5264,10 @@ class Be {
5264
5264
  let h = "", a = "", c = 0, d = [], g = [];
5265
5265
  const x = Array.from(this.segmenter.segment(t), (b) => b.segment);
5266
5266
  for (let b = 0; b < x.length; b++) {
5267
- const I = b === x.length - 1, B = x[b].match(l);
5267
+ const I = b === x.length - 1, D = x[b].match(l);
5268
5268
  let p = x[b].match(s);
5269
5269
  const M = x[b].match(u), z = x[b].match(o);
5270
- if (p && !I && !M && x[b + 1].match(s) && (p = !1), n && (h += x[b]), B && (!i || i.every((y) => b < y[0] || b > y[1])) && (a += x[b]), (z || p || I) && (a.length && (a = this.lipsyncPreProcessText(a, r), a.length && d.push({
5270
+ if (p && !I && !M && x[b + 1].match(s) && (p = !1), n && (h += x[b]), D && (!i || i.every((y) => b < y[0] || b > y[1])) && (a += x[b]), (z || p || I) && (a.length && (a = this.lipsyncPreProcessText(a, r), a.length && d.push({
5271
5271
  mark: c,
5272
5272
  word: a
5273
5273
  })), h.length && (g.push({
@@ -5398,10 +5398,10 @@ class Be {
5398
5398
  let x = 0.6 + this.convertRange(g, [0, h], [0, 0.4]);
5399
5399
  if (h = Math.min(h, c.visemes.length * 200), d > 0)
5400
5400
  for (let b = 0; b < c.visemes.length; b++) {
5401
- const I = r + c.times[b] / d * h, B = c.durations[b] / d * h;
5401
+ const I = r + c.times[b] / d * h, D = c.durations[b] / d * h;
5402
5402
  o.push({
5403
5403
  template: { name: "viseme" },
5404
- ts: [I - Math.min(60, 2 * B / 3), I + Math.min(25, B / 2), I + B + Math.min(60, B / 2)],
5404
+ ts: [I - Math.min(60, 2 * D / 3), I + Math.min(25, D / 2), I + D + Math.min(60, D / 2)],
5405
5405
  vs: {
5406
5406
  ["viseme_" + c.visemes[b]]: [null, c.visemes[b] === "PP" || c.visemes[b] === "FF" ? 0.9 : x, 0]
5407
5407
  }
@@ -5509,8 +5509,8 @@ class Be {
5509
5509
  });
5510
5510
  }
5511
5511
  }
5512
- const B = [...t.anim, ...I];
5513
- this.audioPlaylist.push({ anim: B, audio: d }), this.onSubtitles = t.onSubtitles || null, this.resetLips(), t.mood && this.setMood(t.mood), this.playAudio(), s.onend = () => {
5512
+ const D = [...t.anim, ...I];
5513
+ this.audioPlaylist.push({ anim: D, audio: d }), this.onSubtitles = t.onSubtitles || null, this.resetLips(), t.mood && this.setMood(t.mood), this.playAudio(), s.onend = () => {
5514
5514
  e();
5515
5515
  }, s.onerror = (p) => {
5516
5516
  console.error("Speech synthesis error:", p.error), n(p.error);
@@ -6202,7 +6202,7 @@ class Be {
6202
6202
  t === null && (t = u), e === null && (e = r), Q.copy(this.armature.quaternion), Q.multiply(this.poseTarget.props["Hips.quaternion"]), Q.multiply(this.poseTarget.props["Spine.quaternion"]), Q.multiply(this.poseTarget.props["Spine1.quaternion"]), Q.multiply(this.poseTarget.props["Spine2.quaternion"]), Q.multiply(this.poseTarget.props["Neck.quaternion"]), Q.multiply(this.poseTarget.props["Head.quaternion"]), V.setFromQuaternion(Q);
6203
6203
  let h = V.x / (40 / 24), a = V.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 - u, u), x = Math.max(window.innerHeight - r, r), b = this.convertRange(e, [r - x, r + x], [-0.3, 0.6]) - h + c, I = this.convertRange(t, [u - g, u + g], [-0.8, 0.8]) - a + d;
6204
6204
  b = Math.min(0.6, Math.max(-0.3, b)), I = Math.min(0.8, Math.max(-0.8, I));
6205
- let B = (Math.random() - 0.5) / 4, p = (Math.random() - 0.5) / 4;
6205
+ let D = (Math.random() - 0.5) / 4, p = (Math.random() - 0.5) / 4;
6206
6206
  if (n) {
6207
6207
  let M = this.animQueue.findIndex((y) => y.template.name === "lookat");
6208
6208
  M !== -1 && this.animQueue.splice(M, 1);
@@ -6210,9 +6210,9 @@ class Be {
6210
6210
  name: "lookat",
6211
6211
  dt: [750, n],
6212
6212
  vs: {
6213
- bodyRotateX: [b + B],
6213
+ bodyRotateX: [b + D],
6214
6214
  bodyRotateY: [I + p],
6215
- eyesRotateX: [-3 * B + 0.1],
6215
+ eyesRotateX: [-3 * D + 0.1],
6216
6216
  eyesRotateY: [-5 * p],
6217
6217
  browInnerUp: [[0, 0.7]],
6218
6218
  mouthLeft: [[0, 0.7]],
@@ -6402,7 +6402,7 @@ class Be {
6402
6402
  let n = t;
6403
6403
  if (n.startsWith("CC_Base_") && (n = n.replace("CC_Base_", "")), n = n.replace(/^mixamorig/i, ""), e.has(n))
6404
6404
  return n;
6405
- if (n.match(/^Spine\d+$/)) {
6405
+ if (this._mappingDebugLog || (this._mappingDebugLog = /* @__PURE__ */ new Set()), this._mappingDebugLog.size < 5 && !this._mappingDebugLog.has(t) && (this._mappingDebugLog.add(t), console.debug(`Mapping attempt: "${t}" -> "${n}" (not found in available bones)`)), n.match(/^Spine\d+$/)) {
6406
6406
  const a = n.match(/\d+/)?.[0];
6407
6407
  if (a) {
6408
6408
  const c = `Spine${parseInt(a)}`;
@@ -6474,7 +6474,9 @@ class Be {
6474
6474
  if (e.has(a))
6475
6475
  return a;
6476
6476
  }
6477
- const s = n.toLowerCase(), o = s.match(/^[rl]_index(\d+)$/);
6477
+ const s = n.toLowerCase();
6478
+ n.charAt(0).toUpperCase() + n.slice(1).toLowerCase();
6479
+ const o = s.match(/^[rl]_index(\d+)$/);
6478
6480
  if (o) {
6479
6481
  const a = o[1], d = `${s.startsWith("r") ? "Right" : "Left"}HandIndex${a}`;
6480
6482
  if (e.has(d))
@@ -6509,6 +6511,8 @@ class Be {
6509
6511
  if (e.has(c))
6510
6512
  return c;
6511
6513
  }
6514
+ if (s.includes("upperarmtwist") || s.includes("forearmtwist"))
6515
+ return null;
6512
6516
  if (s.match(/^[rl]_forearm/)) {
6513
6517
  const c = `${s.startsWith("r") ? "Right" : "Left"}ForeArm`;
6514
6518
  if (e.has(c))
@@ -6533,7 +6537,11 @@ class Be {
6533
6537
  */
6534
6538
  filterAnimationTracks(t, e) {
6535
6539
  const n = [], i = /* @__PURE__ */ new Set(), s = /* @__PURE__ */ new Map();
6536
- return t.tracks.forEach((o) => {
6540
+ return this._loggedAvailableBones || (console.log(
6541
+ "Available avatar bones:",
6542
+ Array.from(e).sort().slice(0, 50).join(", "),
6543
+ e.size > 50 ? `... (${e.size} total)` : ""
6544
+ ), this._loggedAvailableBones = !0), t.tracks.forEach((o) => {
6537
6545
  const l = o.name.split("."), u = l[0], r = l[1], h = this.mapBoneName(u, e);
6538
6546
  if (h) {
6539
6547
  const a = `${h}.${r}`, c = o.clone();
@@ -6586,7 +6594,7 @@ class Be {
6586
6594
  } catch (c) {
6587
6595
  console.warn(`Could not verify file existence for ${t}, attempting to load anyway:`, c);
6588
6596
  }
6589
- const h = new De();
6597
+ const h = new Ne();
6590
6598
  let a;
6591
6599
  try {
6592
6600
  a = await h.loadAsync(t, e);
@@ -6620,12 +6628,12 @@ class Be {
6620
6628
  const x = {};
6621
6629
  c.tracks.forEach((I) => {
6622
6630
  I.name = I.name.replaceAll("mixamorig", "");
6623
- const B = I.name.split(".");
6624
- if (B[1] === "position") {
6631
+ const D = I.name.split(".");
6632
+ if (D[1] === "position") {
6625
6633
  for (let p = 0; p < I.values.length; p++)
6626
6634
  I.values[p] = I.values[p] * s;
6627
6635
  x[I.name] = new f.Vector3(I.values[0], I.values[1], I.values[2]);
6628
- } else B[1] === "quaternion" ? x[I.name] = new f.Quaternion(I.values[0], I.values[1], I.values[2], I.values[3]) : B[1] === "rotation" && (x[B[0] + ".quaternion"] = new f.Quaternion().setFromEuler(new f.Euler(I.values[0], I.values[1], I.values[2], "XYZ")).normalize());
6636
+ } else D[1] === "quaternion" ? x[I.name] = new f.Quaternion(I.values[0], I.values[1], I.values[2], I.values[3]) : D[1] === "rotation" && (x[D[0] + ".quaternion"] = new f.Quaternion().setFromEuler(new f.Euler(I.values[0], I.values[1], I.values[2], "XYZ")).normalize());
6629
6637
  });
6630
6638
  const b = { props: x };
6631
6639
  x["Hips.position"] && (x["Hips.position"].y < 0.5 ? b.lying = !0 : b.standing = !0), this.animClips.push({
@@ -6669,7 +6677,7 @@ class Be {
6669
6677
  let l = this.animQueue.find((u) => u.template.name === "pose");
6670
6678
  l && (l.ts[0] = this.animClock + n * 1e3 + 2e3), this.setPoseFromTemplate(o);
6671
6679
  } else {
6672
- let u = await new De().loadAsync(t, e);
6680
+ let u = await new Ne().loadAsync(t, e);
6673
6681
  if (u && u.animations && u.animations[i]) {
6674
6682
  let r = u.animations[i];
6675
6683
  const h = {};
@@ -6766,7 +6774,7 @@ class Be {
6766
6774
  const b = t.iterations || 10;
6767
6775
  if (e)
6768
6776
  for (let I = 0; I < b; I++) {
6769
- let B = !1;
6777
+ let D = !1;
6770
6778
  for (let p = 0, M = x.length; p < M; p++) {
6771
6779
  const z = x[p].bone;
6772
6780
  z.matrixWorld.decompose(u, r, h), r.invert(), o.setFromMatrixPosition(g.matrixWorld), l.subVectors(o, u), l.applyQuaternion(r), l.normalize(), s.subVectors(e, u), s.applyQuaternion(r), s.normalize();
@@ -6779,9 +6787,9 @@ class Be {
6779
6787
  x[p].maxx !== void 0 ? x[p].maxx : 1 / 0,
6780
6788
  x[p].maxy !== void 0 ? x[p].maxy : 1 / 0,
6781
6789
  x[p].maxz !== void 0 ? x[p].maxz : 1 / 0
6782
- ))), z.updateMatrixWorld(!0), B = !0);
6790
+ ))), z.updateMatrixWorld(!0), D = !0);
6783
6791
  }
6784
- if (!B) break;
6792
+ if (!D) break;
6785
6793
  }
6786
6794
  i && x.forEach((I) => {
6787
6795
  this.poseTarget.props[I.link + ".quaternion"].copy(I.bone.quaternion), this.poseTarget.props[I.link + ".quaternion"].t = this.animClock, this.poseTarget.props[I.link + ".quaternion"].d = i;
@@ -6874,7 +6882,7 @@ const Ve = Me(({
6874
6882
  style: x = {},
6875
6883
  animations: b = {}
6876
6884
  }, I) => {
6877
- const B = D(null), p = D(null), M = D(r), z = D(null), y = D(null), E = D(!1), P = D({ remainingText: null, originalText: null, options: null }), W = D([]), oe = D(0), [S, Z] = ce(!0), [_, X] = ce(null), [$, se] = ce(!1), [ae, pe] = ce(!1);
6885
+ const D = N(null), p = N(null), M = N(r), z = N(null), y = N(null), E = N(!1), P = N({ remainingText: null, originalText: null, options: null }), W = N([]), oe = N(0), [S, Z] = ce(!0), [_, X] = ce(null), [$, se] = ce(!1), [ae, pe] = ce(!1);
6878
6886
  de(() => {
6879
6887
  E.current = ae;
6880
6888
  }, [ae]), de(() => {
@@ -6921,23 +6929,23 @@ const Ve = Me(({
6921
6929
  lipsyncModules: ["en"],
6922
6930
  cameraView: h
6923
6931
  }, k = T(async () => {
6924
- if (!(!B.current || p.current))
6932
+ if (!(!D.current || p.current))
6925
6933
  try {
6926
- if (Z(!0), X(null), p.current = new Be(B.current, R), p.current.controls && (p.current.controls.enableRotate = !1, p.current.controls.enableZoom = !1, p.current.controls.enablePan = !1, p.current.controls.enableDamping = !1), b && Object.keys(b).length > 0 && (p.current.customAnimations = b), await p.current.showAvatar(v, (N) => {
6927
- if (N.lengthComputable) {
6928
- const J = Math.min(100, Math.round(N.loaded / N.total * 100));
6934
+ if (Z(!0), X(null), p.current = new De(D.current, R), p.current.controls && (p.current.controls.enableRotate = !1, p.current.controls.enableZoom = !1, p.current.controls.enablePan = !1, p.current.controls.enableDamping = !1), b && Object.keys(b).length > 0 && (p.current.customAnimations = b), await p.current.showAvatar(v, (B) => {
6935
+ if (B.lengthComputable) {
6936
+ const J = Math.min(100, Math.round(B.loaded / B.total * 100));
6929
6937
  c(J);
6930
6938
  }
6931
- }), await new Promise((N) => {
6939
+ }), await new Promise((B) => {
6932
6940
  const J = () => {
6933
- p.current.lipsync && Object.keys(p.current.lipsync).length > 0 ? N() : setTimeout(J, 100);
6941
+ p.current.lipsync && Object.keys(p.current.lipsync).length > 0 ? B() : setTimeout(J, 100);
6934
6942
  };
6935
6943
  J();
6936
6944
  }), p.current && p.current.setShowFullAvatar)
6937
6945
  try {
6938
6946
  p.current.setShowFullAvatar(r);
6939
- } catch (N) {
6940
- console.warn("Error setting full body mode on initialization:", N);
6947
+ } catch (B) {
6948
+ console.warn("Error setting full body mode on initialization:", B);
6941
6949
  }
6942
6950
  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), se(!0), a(p.current);
6943
6951
  const F = () => {
@@ -6953,12 +6961,12 @@ const Ve = Me(({
6953
6961
  de(() => (k(), () => {
6954
6962
  p.current && (p.current.stop(), p.current.dispose(), p.current = null);
6955
6963
  }), [k]), de(() => {
6956
- if (!B.current || !p.current) return;
6957
- const L = new ResizeObserver((N) => {
6958
- for (const J of N)
6964
+ if (!D.current || !p.current) return;
6965
+ const L = new ResizeObserver((B) => {
6966
+ for (const J of B)
6959
6967
  p.current && p.current.onResize && p.current.onResize();
6960
6968
  });
6961
- L.observe(B.current);
6969
+ L.observe(D.current);
6962
6970
  const F = () => {
6963
6971
  p.current && p.current.onResize && p.current.onResize();
6964
6972
  };
@@ -6977,7 +6985,7 @@ const Ve = Me(({
6977
6985
  if (p.current && $)
6978
6986
  try {
6979
6987
  y.current && (clearInterval(y.current), y.current = null), z.current = { text: L, options: F }, P.current = { remainingText: null, originalText: null, options: null };
6980
- const N = /[!\.\?\n\p{Extended_Pictographic}]/ug, J = L.split(N).map((Y) => Y.trim()).filter((Y) => Y.length > 0);
6988
+ const B = /[!\.\?\n\p{Extended_Pictographic}]/ug, J = L.split(B).map((Y) => Y.trim()).filter((Y) => Y.length > 0);
6981
6989
  W.current = J, oe.current = 0, pe(!1), E.current = !1, await H();
6982
6990
  const ge = {
6983
6991
  ...F,
@@ -6996,8 +7004,8 @@ const Ve = Me(({
6996
7004
  be = !0;
6997
7005
  try {
6998
7006
  F.onSpeechEnd();
6999
- } catch (Ne) {
7000
- console.error("Error in onSpeechEnd callback (timeout):", Ne);
7007
+ } catch (Be) {
7008
+ console.error("Error in onSpeechEnd callback (timeout):", Be);
7001
7009
  }
7002
7010
  }
7003
7011
  return;
@@ -7018,8 +7026,8 @@ const Ve = Me(({
7018
7026
  p.current.lipsync && Object.keys(p.current.lipsync).length > 0 ? (p.current.setSlowdownRate && p.current.setSlowdownRate(1.05), p.current.speakText(L, ge)) : setTimeout(async () => {
7019
7027
  await H(), p.current && p.current.lipsync && (p.current.setSlowdownRate && p.current.setSlowdownRate(1.05), p.current.speakText(L, ge));
7020
7028
  }, 100);
7021
- } catch (N) {
7022
- console.error("Error speaking text:", N), X(N.message || "Failed to speak text");
7029
+ } catch (B) {
7030
+ console.error("Error speaking text:", B), X(B.message || "Failed to speak text");
7023
7031
  }
7024
7032
  }, [$, H, v.lipsyncLang]), K = T(() => {
7025
7033
  p.current && (p.current.stopSpeaking(), p.current.setSlowdownRate && p.current.setSlowdownRate(1), z.current = null, pe(!1));
@@ -7028,16 +7036,16 @@ const Ve = Me(({
7028
7036
  const L = p.current;
7029
7037
  if (L.isSpeaking || L.audioPlaylist && L.audioPlaylist.length > 0 || L.speechQueue && L.speechQueue.length > 0) {
7030
7038
  y.current && (clearInterval(y.current), y.current = null);
7031
- let N = "";
7039
+ let B = "";
7032
7040
  if (z.current && W.current.length > 0) {
7033
7041
  const J = W.current.length, ge = L.speechQueue ? L.speechQueue.filter((Ae) => Ae && Ae.text && Array.isArray(Ae.text) && Ae.text.length > 0).length : 0, Y = L.audioPlaylist && L.audioPlaylist.length > 0, ue = ge + (Y ? 1 : 0), Se = J - ue;
7034
- if (ue > 0 && Se < J && (N = W.current.slice(Se).join(". ").trim(), !N && ge > 0 && L.speechQueue)) {
7042
+ if (ue > 0 && Se < J && (B = W.current.slice(Se).join(". ").trim(), !B && ge > 0 && L.speechQueue)) {
7035
7043
  const be = L.speechQueue.filter((ye) => ye && ye.text && Array.isArray(ye.text) && ye.text.length > 0).map((ye) => ye.text.map((ke) => ke.word || "").filter((ke) => ke.length > 0).join(" ")).filter((ye) => ye.length > 0).join(" ");
7036
- be && be.trim() && (N = be.trim());
7044
+ be && be.trim() && (B = be.trim());
7037
7045
  }
7038
7046
  }
7039
7047
  z.current && (P.current = {
7040
- remainingText: N || null,
7048
+ remainingText: B || null,
7041
7049
  originalText: z.current.text,
7042
7050
  options: z.current.options
7043
7051
  }), L.speechQueue && (L.speechQueue.length = 0), p.current.pauseSpeaking(), E.current = !0, pe(!0);
@@ -7056,12 +7064,12 @@ const Ve = Me(({
7056
7064
  return;
7057
7065
  }
7058
7066
  pe(!1), E.current = !1, await H();
7059
- const N = {
7067
+ const B = {
7060
7068
  ...F,
7061
7069
  lipsyncLang: F.lipsyncLang || v.lipsyncLang || "en"
7062
7070
  };
7063
7071
  try {
7064
- await U(L, N);
7072
+ await U(L, B);
7065
7073
  } catch (J) {
7066
7074
  console.error("Error resuming speech:", J), pe(!1), E.current = !1;
7067
7075
  }
@@ -7194,7 +7202,7 @@ const Ve = Me(({
7194
7202
  /* @__PURE__ */ me(
7195
7203
  "div",
7196
7204
  {
7197
- ref: B,
7205
+ ref: D,
7198
7206
  className: "talking-head-viewer",
7199
7207
  style: {
7200
7208
  width: "100%",
@@ -7241,7 +7249,7 @@ const pt = Me(({
7241
7249
  style: s = {},
7242
7250
  avatarConfig: o = {}
7243
7251
  }, l) => {
7244
- const u = D(null), r = D(null), [h, a] = ce(!0), [c, d] = ce(null), [g, x] = ce(!1), b = Fe(), I = o.ttsService || b.service, B = I === "browser" ? {
7252
+ const u = N(null), r = N(null), [h, a] = ce(!0), [c, d] = ce(null), [g, x] = ce(!1), b = Fe(), I = o.ttsService || b.service, D = I === "browser" ? {
7245
7253
  endpoint: "",
7246
7254
  apiKey: null,
7247
7255
  defaultVoice: "Google US English"
@@ -7257,7 +7265,7 @@ const pt = Me(({
7257
7265
  body: "F",
7258
7266
  avatarMood: "neutral",
7259
7267
  ttsLang: I === "browser" ? "en-US" : "en",
7260
- ttsVoice: o.ttsVoice || B.defaultVoice,
7268
+ ttsVoice: o.ttsVoice || D.defaultVoice,
7261
7269
  lipsyncLang: "en",
7262
7270
  // English lip-sync
7263
7271
  showFullAvatar: !0,
@@ -7266,15 +7274,15 @@ const pt = Me(({
7266
7274
  movementIntensity: 0.5,
7267
7275
  ...o
7268
7276
  }, M = {
7269
- ttsEndpoint: B.endpoint,
7270
- ttsApikey: B.apiKey,
7277
+ ttsEndpoint: D.endpoint,
7278
+ ttsApikey: D.apiKey,
7271
7279
  ttsService: I,
7272
7280
  lipsyncModules: ["en"],
7273
7281
  cameraView: "upper"
7274
7282
  }, z = T(async () => {
7275
7283
  if (!(!u.current || r.current))
7276
7284
  try {
7277
- if (a(!0), d(null), r.current = new Be(u.current, M), await r.current.showAvatar(p, (_) => {
7285
+ if (a(!0), d(null), r.current = new De(u.current, M), await r.current.showAvatar(p, (_) => {
7278
7286
  if (_.lengthComputable) {
7279
7287
  const X = Math.min(100, Math.round(_.loaded / _.total * 100));
7280
7288
  t(X);
@@ -7493,10 +7501,10 @@ const gt = Me(({
7493
7501
  },
7494
7502
  className: b = "",
7495
7503
  style: I = {},
7496
- animations: B = {},
7504
+ animations: D = {},
7497
7505
  autoSpeak: p = !1
7498
7506
  }, M) => {
7499
- const z = D(null), y = D(null), E = D(h), P = D(null), W = D(null), oe = D(!1), S = D({ remainingText: null, originalText: null, options: null }), Z = D([]), [_, X] = ce(!0), [$, se] = ce(null), [ae, pe] = ce(!1), [ee, le] = ce(!1);
7507
+ const z = N(null), y = N(null), E = N(h), P = N(null), W = N(null), oe = N(!1), S = N({ remainingText: null, originalText: null, options: null }), Z = N([]), [_, X] = ce(!0), [$, se] = ce(null), [ae, pe] = ce(!1), [ee, le] = ce(!1);
7500
7508
  de(() => {
7501
7509
  oe.current = ee;
7502
7510
  }, [ee]), de(() => {
@@ -7544,7 +7552,7 @@ const gt = Me(({
7544
7552
  }, U = T(async () => {
7545
7553
  if (!(!z.current || y.current))
7546
7554
  try {
7547
- X(!0), se(null), y.current = new Be(z.current, H), console.log("Avatar config being passed:", {
7555
+ X(!0), se(null), y.current = new De(z.current, H), console.log("Avatar config being passed:", {
7548
7556
  url: k.url,
7549
7557
  body: k.body,
7550
7558
  avatarMood: k.avatarMood
@@ -7585,7 +7593,7 @@ const gt = Me(({
7585
7593
  return;
7586
7594
  }
7587
7595
  await K(), S.current = { remainingText: null, originalText: null, options: null }, Z.current = [], P.current = { text: C, options: te }, W.current && (clearInterval(W.current), W.current = null), le(!1), oe.current = !1;
7588
- const L = C.split(/[.!?]+/).filter((N) => N.trim().length > 0);
7596
+ const L = C.split(/[.!?]+/).filter((B) => B.trim().length > 0);
7589
7597
  Z.current = L;
7590
7598
  const F = {
7591
7599
  lipsyncLang: te.lipsyncLang || "en",
@@ -7595,8 +7603,8 @@ const gt = Me(({
7595
7603
  };
7596
7604
  try {
7597
7605
  y.current.speakText(C, F);
7598
- } catch (N) {
7599
- console.error("Error speaking text:", N), se(N.message || "Failed to speak text");
7606
+ } catch (B) {
7607
+ console.error("Error speaking text:", B), se(B.message || "Failed to speak text");
7600
7608
  }
7601
7609
  }, [ae, x, K]);
7602
7610
  de(() => {
@@ -7609,7 +7617,7 @@ const gt = Me(({
7609
7617
  if (C || te.length > 0 || L.length > 0) {
7610
7618
  W.current && (clearInterval(W.current), W.current = null);
7611
7619
  let F = "";
7612
- L.length > 0 && (F = L.map((N) => N.text && Array.isArray(N.text) ? N.text.map((J) => J.word).join(" ") : N.text || "").join(" ")), S.current = {
7620
+ L.length > 0 && (F = L.map((B) => B.text && Array.isArray(B.text) ? B.text.map((J) => J.word).join(" ") : B.text || "").join(" ")), S.current = {
7613
7621
  remainingText: F || null,
7614
7622
  originalText: P.current?.text || null,
7615
7623
  options: P.current?.options || null
@@ -7704,7 +7712,7 @@ const yt = Me(({
7704
7712
  },
7705
7713
  autoStart: u = !1
7706
7714
  }, r) => {
7707
- const h = D(null), a = D({
7715
+ const h = N(null), a = N({
7708
7716
  currentModuleIndex: 0,
7709
7717
  currentLessonIndex: 0,
7710
7718
  currentQuestionIndex: 0,
@@ -7714,18 +7722,18 @@ const yt = Me(({
7714
7722
  curriculumCompleted: !1,
7715
7723
  score: 0,
7716
7724
  totalQuestions: 0
7717
- }), c = D({
7725
+ }), c = N({
7718
7726
  onLessonStart: n,
7719
7727
  onLessonComplete: i,
7720
7728
  onQuestionAnswer: s,
7721
7729
  onCurriculumComplete: o,
7722
7730
  onCustomAction: l
7723
- }), d = D(null), g = D(null), x = D(null), b = D(null), I = D(null), B = D(null), p = D(null), M = D(G?.curriculum || {
7731
+ }), d = N(null), g = N(null), x = N(null), b = N(null), I = N(null), D = N(null), p = N(null), M = N(G?.curriculum || {
7724
7732
  title: "Default Curriculum",
7725
7733
  description: "No curriculum data provided",
7726
7734
  language: "en",
7727
7735
  modules: []
7728
- }), z = D({
7736
+ }), z = N({
7729
7737
  avatarUrl: t.avatarUrl || "/avatars/brunette.glb",
7730
7738
  avatarBody: t.avatarBody || "F",
7731
7739
  mood: t.mood || "happy",
@@ -8176,7 +8184,7 @@ const yt = Me(({
8176
8184
  }, 10);
8177
8185
  }, [u, y]);
8178
8186
  Xe(() => {
8179
- d.current = X, g.current = _, x.current = W, b.current = Z, I.current = oe, B.current = S, p.current = $;
8187
+ d.current = X, g.current = _, x.current = W, b.current = Z, I.current = oe, D.current = S, p.current = $;
8180
8188
  }), Ee(r, () => ({
8181
8189
  // Curriculum control methods
8182
8190
  startTeaching: X,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-rsc/talking-head-react",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
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",
@@ -2253,11 +2253,11 @@ class TalkingHead {
2253
2253
  if (this.lockedPosition && this.armature) {
2254
2254
  // Enforce the locked position - keep avatar exactly where it was locked
2255
2255
  // This prevents FBX animations from moving the avatar
2256
- this.armature.position.set(
2257
- this.lockedPosition.x,
2256
+ this.armature.position.set(
2257
+ this.lockedPosition.x,
2258
2258
  this.lockedPosition.y,
2259
- this.lockedPosition.z
2260
- );
2259
+ this.lockedPosition.z
2260
+ );
2261
2261
  }
2262
2262
  }
2263
2263
 
@@ -5354,6 +5354,7 @@ class TalkingHead {
5354
5354
 
5355
5355
  // Remove common prefixes
5356
5356
  let normalized = fbxBoneName;
5357
+ const originalNormalized = normalized;
5357
5358
 
5358
5359
  // Remove CC_Base prefix (Character Creator)
5359
5360
  if (normalized.startsWith('CC_Base_')) {
@@ -5367,6 +5368,15 @@ class TalkingHead {
5367
5368
  if (availableBones.has(normalized)) {
5368
5369
  return normalized;
5369
5370
  }
5371
+
5372
+ // Debug: Log first few failed mappings to help identify patterns
5373
+ if (!this._mappingDebugLog) {
5374
+ this._mappingDebugLog = new Set();
5375
+ }
5376
+ if (this._mappingDebugLog.size < 5 && !this._mappingDebugLog.has(fbxBoneName)) {
5377
+ this._mappingDebugLog.add(fbxBoneName);
5378
+ console.debug(`Mapping attempt: "${fbxBoneName}" -> "${normalized}" (not found in available bones)`);
5379
+ }
5370
5380
 
5371
5381
  // Handle numbered bones (e.g., Spine01 -> Spine1)
5372
5382
  if (normalized.match(/^Spine\d+$/)) {
@@ -5459,8 +5469,9 @@ class TalkingHead {
5459
5469
 
5460
5470
  // Pattern-based matching for CC_Base and similar naming conventions
5461
5471
  const lowerNormalized = normalized.toLowerCase();
5472
+ const upperFirst = normalized.charAt(0).toUpperCase() + normalized.slice(1).toLowerCase();
5462
5473
 
5463
- // Pattern: R_Index1/2/3 -> RightHandIndex1/2/3
5474
+ // Pattern: R_Index1/2/3 or r_index1/2/3 -> RightHandIndex1/2/3
5464
5475
  const indexMatch = lowerNormalized.match(/^[rl]_index(\d+)$/);
5465
5476
  if (indexMatch) {
5466
5477
  const digit = indexMatch[1];
@@ -5515,7 +5526,7 @@ class TalkingHead {
5515
5526
  }
5516
5527
  }
5517
5528
 
5518
- // Pattern: R_Upperarm -> RightArm
5529
+ // Pattern: R_Upperarm -> RightArm (case insensitive)
5519
5530
  if (lowerNormalized.match(/^[rl]_upperarm/)) {
5520
5531
  const side = lowerNormalized.startsWith('r') ? 'Right' : 'Left';
5521
5532
  const candidate = `${side}Arm`;
@@ -5524,6 +5535,11 @@ class TalkingHead {
5524
5535
  }
5525
5536
  }
5526
5537
 
5538
+ // Pattern: R_UpperarmTwist01/02 -> ignore (twist bones)
5539
+ if (lowerNormalized.includes('upperarmtwist') || lowerNormalized.includes('forearmtwist')) {
5540
+ return null;
5541
+ }
5542
+
5527
5543
  // Pattern: R_Forearm -> RightForeArm
5528
5544
  if (lowerNormalized.match(/^[rl]_forearm/)) {
5529
5545
  const side = lowerNormalized.startsWith('r') ? 'Right' : 'Left';
@@ -5564,6 +5580,13 @@ class TalkingHead {
5564
5580
  const missingBones = new Set();
5565
5581
  const mappedBones = new Map(); // Track mappings for logging
5566
5582
 
5583
+ // Debug: Log available bones (first time only)
5584
+ if (!this._loggedAvailableBones) {
5585
+ console.log('Available avatar bones:', Array.from(availableBones).sort().slice(0, 50).join(', '),
5586
+ availableBones.size > 50 ? `... (${availableBones.size} total)` : '');
5587
+ this._loggedAvailableBones = true;
5588
+ }
5589
+
5567
5590
  clip.tracks.forEach(track => {
5568
5591
  // Extract bone name from track name (e.g., "CC_Base_R_Index3.position" -> "CC_Base_R_Index3")
5569
5592
  const trackNameParts = track.name.split('.');