@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.cjs +2 -2
- package/dist/index.js +73 -65
- package/package.json +1 -1
- package/src/lib/talkinghead.mjs +29 -6
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
|
|
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
|
|
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
|
|
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,
|
|
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]),
|
|
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,
|
|
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 *
|
|
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
|
|
5513
|
-
this.audioPlaylist.push({ anim:
|
|
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
|
|
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 +
|
|
6213
|
+
bodyRotateX: [b + D],
|
|
6214
6214
|
bodyRotateY: [I + p],
|
|
6215
|
-
eyesRotateX: [-3 *
|
|
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()
|
|
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
|
|
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
|
|
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
|
|
6624
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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),
|
|
6790
|
+
))), z.updateMatrixWorld(!0), D = !0);
|
|
6783
6791
|
}
|
|
6784
|
-
if (!
|
|
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
|
|
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 (!(!
|
|
6932
|
+
if (!(!D.current || p.current))
|
|
6925
6933
|
try {
|
|
6926
|
-
if (Z(!0), X(null), p.current = new
|
|
6927
|
-
if (
|
|
6928
|
-
const J = Math.min(100, Math.round(
|
|
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((
|
|
6939
|
+
}), await new Promise((B) => {
|
|
6932
6940
|
const J = () => {
|
|
6933
|
-
p.current.lipsync && Object.keys(p.current.lipsync).length > 0 ?
|
|
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 (
|
|
6940
|
-
console.warn("Error setting full body mode on initialization:",
|
|
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 (!
|
|
6957
|
-
const L = new ResizeObserver((
|
|
6958
|
-
for (const J of
|
|
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(
|
|
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
|
|
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 (
|
|
7000
|
-
console.error("Error in onSpeechEnd callback (timeout):",
|
|
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 (
|
|
7022
|
-
console.error("Error speaking 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
|
|
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 && (
|
|
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() && (
|
|
7044
|
+
be && be.trim() && (B = be.trim());
|
|
7037
7045
|
}
|
|
7038
7046
|
}
|
|
7039
7047
|
z.current && (P.current = {
|
|
7040
|
-
remainingText:
|
|
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
|
|
7067
|
+
const B = {
|
|
7060
7068
|
...F,
|
|
7061
7069
|
lipsyncLang: F.lipsyncLang || v.lipsyncLang || "en"
|
|
7062
7070
|
};
|
|
7063
7071
|
try {
|
|
7064
|
-
await U(L,
|
|
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:
|
|
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 =
|
|
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 ||
|
|
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:
|
|
7270
|
-
ttsApikey:
|
|
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
|
|
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:
|
|
7504
|
+
animations: D = {},
|
|
7497
7505
|
autoSpeak: p = !1
|
|
7498
7506
|
}, M) => {
|
|
7499
|
-
const z =
|
|
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
|
|
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((
|
|
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 (
|
|
7599
|
-
console.error("Error speaking 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((
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -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
|
-
|
|
2257
|
-
|
|
2256
|
+
this.armature.position.set(
|
|
2257
|
+
this.lockedPosition.x,
|
|
2258
2258
|
this.lockedPosition.y,
|
|
2259
|
-
|
|
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('.');
|