talking-head-studio 0.4.11 → 0.4.12

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.
Files changed (142) hide show
  1. package/README.md +279 -193
  2. package/dist/TalkingHead.d.ts +28 -3
  3. package/dist/TalkingHead.js +21 -2
  4. package/dist/TalkingHead.web.d.ts +31 -4
  5. package/dist/TalkingHead.web.js +11 -1
  6. package/dist/TalkingHeadVisualization.d.ts +22 -0
  7. package/dist/TalkingHeadVisualization.js +30 -10
  8. package/dist/api/studioApi.d.ts +12 -1
  9. package/dist/api/studioApi.js +16 -2
  10. package/dist/contract.d.ts +14 -0
  11. package/dist/contract.js +30 -0
  12. package/dist/core/avatar/avatarCapabilities.d.ts +60 -0
  13. package/dist/core/avatar/avatarCapabilities.js +100 -0
  14. package/dist/core/avatar/backends/gaussian.js +6 -4
  15. package/dist/core/avatar/motion.d.ts +1713 -0
  16. package/dist/core/avatar/motion.js +550 -0
  17. package/dist/core/avatar/motionRuntime.d.ts +46 -0
  18. package/dist/core/avatar/motionRuntime.js +84 -0
  19. package/dist/core/avatar/schema.d.ts +33 -5
  20. package/dist/core/avatar/visemes.d.ts +16 -1
  21. package/dist/core/avatar/visemes.js +48 -1
  22. package/dist/editor/AvatarCanvas.js +92 -1
  23. package/dist/editor/AvatarEditor.native.js +1 -0
  24. package/dist/editor/AvatarModel.js +1 -0
  25. package/dist/editor/FaceSqueezeEditor.d.ts +3 -1
  26. package/dist/editor/FaceSqueezeEditor.js +176 -112
  27. package/dist/editor/FaceSqueezeEditor.web.d.ts +3 -1
  28. package/dist/editor/FaceSqueezeEditor.web.js +30 -28
  29. package/dist/editor/RigidAccessory.js +17 -2
  30. package/dist/editor/SkinnedClothing.js +1 -0
  31. package/dist/editor/boneLockedDrag.d.ts +11 -0
  32. package/dist/editor/boneLockedDrag.js +68 -0
  33. package/dist/editor/boneSnap.web.d.ts +27 -0
  34. package/dist/editor/boneSnap.web.js +99 -0
  35. package/dist/editor/index.web.d.ts +10 -0
  36. package/dist/editor/index.web.js +26 -0
  37. package/dist/editor/sounds/haha.wav +0 -0
  38. package/dist/editor/sounds/owie.wav +0 -0
  39. package/dist/editor/sounds/stop.wav +0 -0
  40. package/dist/editor/studioTheme.d.ts +14 -14
  41. package/dist/editor/studioTheme.js +17 -14
  42. package/dist/editor/types.d.ts +1 -0
  43. package/dist/html/accessories.d.ts +7 -0
  44. package/dist/html/accessories.js +149 -0
  45. package/dist/html/motion.d.ts +1 -0
  46. package/dist/html/motion.js +189 -0
  47. package/dist/html/visemes.d.ts +7 -0
  48. package/dist/html/visemes.js +348 -0
  49. package/dist/html.d.ts +1 -1
  50. package/dist/html.js +55 -732
  51. package/dist/index.d.ts +7 -3
  52. package/dist/index.js +17 -1
  53. package/dist/index.web.d.ts +18 -1
  54. package/dist/index.web.js +36 -3
  55. package/dist/sketchfab/api.js +1 -0
  56. package/dist/sketchfab/glbInspect.d.ts +22 -0
  57. package/dist/sketchfab/glbInspect.js +58 -0
  58. package/dist/sketchfab/index.d.ts +3 -0
  59. package/dist/sketchfab/index.js +8 -1
  60. package/dist/sketchfab/inspectRemote.d.ts +13 -0
  61. package/dist/sketchfab/inspectRemote.js +77 -0
  62. package/dist/sketchfab/types.d.ts +10 -0
  63. package/dist/studio/AccessoryBrowserScreen.d.ts +6 -0
  64. package/dist/studio/AccessoryBrowserScreen.js +626 -0
  65. package/dist/studio/AccessoryPanel.d.ts +10 -0
  66. package/dist/studio/AccessoryPanel.js +396 -0
  67. package/dist/studio/AppearancePanel.d.ts +9 -0
  68. package/dist/studio/AppearancePanel.js +77 -0
  69. package/dist/studio/AvatarCreatorScreen.d.ts +5 -0
  70. package/dist/studio/AvatarCreatorScreen.js +806 -0
  71. package/dist/studio/AvatarEditorScreen.d.ts +14 -0
  72. package/dist/studio/AvatarEditorScreen.js +510 -0
  73. package/dist/studio/AvatarGrid.d.ts +23 -0
  74. package/dist/studio/AvatarGrid.js +257 -0
  75. package/dist/studio/ColorSwatch.d.ts +8 -0
  76. package/dist/studio/ColorSwatch.js +100 -0
  77. package/dist/studio/CreateVoiceProfileSheet.d.ts +8 -0
  78. package/dist/studio/CreateVoiceProfileSheet.js +242 -0
  79. package/dist/studio/DetailsPanel.d.ts +15 -0
  80. package/dist/studio/DetailsPanel.js +239 -0
  81. package/dist/studio/FilamentEditor.d.ts +2 -0
  82. package/dist/studio/FilamentEditor.js +6 -0
  83. package/dist/studio/PrecisionPanel.d.ts +2 -0
  84. package/dist/studio/PrecisionPanel.js +7 -0
  85. package/dist/studio/PublicGalleryScreen.d.ts +5 -0
  86. package/dist/studio/PublicGalleryScreen.js +358 -0
  87. package/dist/studio/SketchfabModelCard.d.ts +20 -0
  88. package/dist/studio/SketchfabModelCard.js +104 -0
  89. package/dist/studio/StudioBrowseHeader.d.ts +9 -0
  90. package/dist/studio/StudioBrowseHeader.js +28 -0
  91. package/dist/studio/StudioEmptyState.d.ts +8 -0
  92. package/dist/studio/StudioEmptyState.js +29 -0
  93. package/dist/studio/StudioFloatingAction.d.ts +13 -0
  94. package/dist/studio/StudioFloatingAction.js +42 -0
  95. package/dist/studio/StudioSectionHeader.d.ts +7 -0
  96. package/dist/studio/StudioSectionHeader.js +27 -0
  97. package/dist/studio/StudioSurfaceCard.d.ts +8 -0
  98. package/dist/studio/StudioSurfaceCard.js +20 -0
  99. package/dist/studio/VoicePanel.d.ts +15 -0
  100. package/dist/studio/VoicePanel.js +305 -0
  101. package/dist/studio/constants.d.ts +3 -0
  102. package/dist/studio/constants.js +6 -0
  103. package/dist/studio/index.d.ts +29 -0
  104. package/dist/studio/index.js +54 -0
  105. package/dist/studio/useSketchfabCapabilities.d.ts +31 -0
  106. package/dist/studio/useSketchfabCapabilities.js +82 -0
  107. package/dist/tts/useDirectVisemeStream.js +15 -10
  108. package/dist/utils/avatarUtils.js +92 -5
  109. package/dist/utils/faceLandmarkerToShapeWeights.js +2 -4
  110. package/dist/voice/useAudioPlayer.js +17 -4
  111. package/dist/voice/useVoicePreview.js +4 -2
  112. package/dist/wardrobe/index.d.ts +1 -0
  113. package/dist/wardrobe/index.js +6 -1
  114. package/dist/wardrobe/useAccessoryGestures.d.ts +20 -0
  115. package/dist/wardrobe/useAccessoryGestures.js +94 -0
  116. package/dist/wardrobe/useAvatarWardrobeHydration.js +8 -2
  117. package/dist/wardrobe/useStudioAvatar.js +11 -2
  118. package/dist/wardrobe/wardrobeStore.d.ts +2 -0
  119. package/dist/wardrobe/wardrobeStore.js +12 -2
  120. package/dist/wgpu/R3FWebGpuCanvas.d.ts +15 -0
  121. package/dist/wgpu/R3FWebGpuCanvas.js +176 -0
  122. package/dist/wgpu/WgpuAvatar.d.ts +26 -2
  123. package/dist/wgpu/WgpuAvatar.js +296 -39
  124. package/dist/wgpu/accessoryDefaults.d.ts +12 -0
  125. package/dist/wgpu/accessoryDefaults.js +19 -0
  126. package/dist/wgpu/blobShim.d.ts +2 -0
  127. package/dist/wgpu/blobShim.js +191 -0
  128. package/dist/wgpu/index.d.ts +1 -0
  129. package/dist/wgpu/index.js +4 -1
  130. package/dist/wgpu/loadGLTFFromUri.d.ts +2 -0
  131. package/dist/wgpu/loadGLTFFromUri.js +75 -0
  132. package/dist/wgpu/morphTables.js +21 -10
  133. package/dist/wgpu/motionState.d.ts +20 -0
  134. package/dist/wgpu/motionState.js +31 -0
  135. package/dist/wgpu/patchThreeForRN.d.ts +28 -0
  136. package/dist/wgpu/patchThreeForRN.js +292 -0
  137. package/dist/wgpu/scenePlacement.d.ts +5 -0
  138. package/dist/wgpu/scenePlacement.js +50 -0
  139. package/dist/wgpu/useAuthedModelUri.js +4 -2
  140. package/dist/wgpu/useNativeGLTF.d.ts +7 -0
  141. package/dist/wgpu/useNativeGLTF.js +36 -0
  142. package/package.json +97 -31
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+ /**
3
+ * patchThreeForRN — patches Three.js loaders for React Native compatibility.
4
+ *
5
+ * Problem: GLTFLoader (three-stdlib) calls URL.createObjectURL(blob) when
6
+ * extracting embedded textures from a GLB's buffer views. React Native's
7
+ * Hermes / JSC runtime does not implement URL.createObjectURL, so every
8
+ * texture inside the GLB fails with "Cannot create URL for blob!".
9
+ *
10
+ * Fix strategy (3 layers):
11
+ *
12
+ * Layer 1 — URL.createObjectURL polyfill
13
+ * Instead of throwing, store the Blob in a module-level Map under a
14
+ * synthetic "rnblob://<id>" key and return that key as the "URL".
15
+ * URL.revokeObjectURL deletes the entry.
16
+ *
17
+ * Layer 2 — THREE.ImageLoader patch
18
+ * When ImageLoader.load() receives an "rnblob://" URL, retrieve the
19
+ * stored Blob, read it as base64 via FileReader, build a "data:" URI,
20
+ * then set it as img.src so expo-gl can decode it natively.
21
+ *
22
+ * Layer 3 — THREE.TextureLoader / THREE.FileLoader safety net
23
+ * Same interception for any direct TextureLoader usage and for
24
+ * FileLoader which is also used internally by GLTFLoader.
25
+ *
26
+ * Call installThreeRNPatches() once at module load time, before any
27
+ * useGLTF / GLTFLoader usage. Pass debug=true for verbose logging.
28
+ */
29
+ /* eslint-disable @typescript-eslint/no-explicit-any -- Three.js loader monkey patches must touch dynamic private fields across RN runtimes. */
30
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ var desc = Object.getOwnPropertyDescriptor(m, k);
33
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
34
+ desc = { enumerable: true, get: function() { return m[k]; } };
35
+ }
36
+ Object.defineProperty(o, k2, desc);
37
+ }) : (function(o, m, k, k2) {
38
+ if (k2 === undefined) k2 = k;
39
+ o[k2] = m[k];
40
+ }));
41
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
42
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
43
+ }) : function(o, v) {
44
+ o["default"] = v;
45
+ });
46
+ var __importStar = (this && this.__importStar) || (function () {
47
+ var ownKeys = function(o) {
48
+ ownKeys = Object.getOwnPropertyNames || function (o) {
49
+ var ar = [];
50
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
51
+ return ar;
52
+ };
53
+ return ownKeys(o);
54
+ };
55
+ return function (mod) {
56
+ if (mod && mod.__esModule) return mod;
57
+ var result = {};
58
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
59
+ __setModuleDefault(result, mod);
60
+ return result;
61
+ };
62
+ })();
63
+ Object.defineProperty(exports, "__esModule", { value: true });
64
+ exports.installThreeRNPatches = installThreeRNPatches;
65
+ const react_native_1 = require("react-native");
66
+ const base64_js_1 = require("base64-js");
67
+ const THREE = __importStar(require("three"));
68
+ const blobShim_1 = require("./blobShim");
69
+ let installed = false;
70
+ // blobStore is only used if something still produces rnblob:// URLs (shouldn't happen
71
+ // after blobShim returns data: URIs directly, but kept as safety net)
72
+ const blobStore = new Map();
73
+ /** Re-patch URL.createObjectURL on the current globalThis.URL.
74
+ * Must run every time installThreeRNPatches() is called because libraries like
75
+ * @livekit/react-native call setupURLPolyfill() which replaces globalThis.URL
76
+ * with a new whatwg-url object whose createObjectURL always throws
77
+ * "Cannot create URL for blob!". */
78
+ function repatchUrlCreateObjectURL() {
79
+ const URLObj = globalThis.URL;
80
+ if (!URLObj)
81
+ return;
82
+ if (typeof URLObj.createObjectURL === 'function' &&
83
+ URLObj.createObjectURL.__blobShim) {
84
+ return; // already our patch — no-op
85
+ }
86
+ const patchedCreateObjectURL = (blob) => {
87
+ const bytes = blobShim_1.blobBytesStore.get(blob);
88
+ if (bytes) {
89
+ const mime = blob.type || 'application/octet-stream';
90
+ const b64 = (0, base64_js_1.fromByteArray)(bytes);
91
+ return `data:${mime};base64,${b64}`;
92
+ }
93
+ throw new Error('[BlobShim] createObjectURL: no bytes stashed for this Blob');
94
+ };
95
+ patchedCreateObjectURL.__blobShim = true;
96
+ URLObj.createObjectURL = patchedCreateObjectURL;
97
+ URLObj.revokeObjectURL = (_url) => { };
98
+ }
99
+ function isInlineImageUrl(url) {
100
+ return url.startsWith('rnblob://') || url.startsWith('data:image/');
101
+ }
102
+ function getMimeTypeFromDataUri(dataUri) {
103
+ const mimeMatch = dataUri.match(/^data:([^;,]+)[;,]/i);
104
+ return mimeMatch?.[1] ?? 'image/png';
105
+ }
106
+ function decodeDataUriBytes(dataUri) {
107
+ const marker = ';base64,';
108
+ const markerIndex = dataUri.indexOf(marker);
109
+ if (markerIndex < 0) {
110
+ throw new Error('[ThreeRNPatch] Unsupported data URI encoding');
111
+ }
112
+ const base64 = dataUri.slice(markerIndex + marker.length);
113
+ return (0, base64_js_1.toByteArray)(base64);
114
+ }
115
+ async function bytesFromBlob(blob) {
116
+ if (typeof blob.arrayBuffer === 'function') {
117
+ return new Uint8Array(await blob.arrayBuffer());
118
+ }
119
+ const dataUri = await new Promise((resolve, reject) => {
120
+ const reader = new FileReader();
121
+ reader.onload = () => resolve(String(reader.result));
122
+ reader.onerror = () => reject(new Error('[ThreeRNPatch] FileReader failed while decoding blob'));
123
+ reader.readAsDataURL(blob);
124
+ });
125
+ return decodeDataUriBytes(dataUri);
126
+ }
127
+ function installThreeRNPatches(debug = false) {
128
+ if (react_native_1.Platform.OS === 'web')
129
+ return;
130
+ const log = (...args) => {
131
+ if (debug)
132
+ console.log('[ThreeRNPatch]', ...args);
133
+ };
134
+ // Layer 1: Re-patch URL.createObjectURL on the *current* globalThis.URL.
135
+ // This must run every call (not guarded by `installed`) because libraries like
136
+ // @livekit/react-native call setupURLPolyfill() which replaces globalThis.URL
137
+ // with a new whatwg-url object whose createObjectURL always throws.
138
+ // We re-apply our override so GLTFLoader's `const URL = self.URL` gets our patch.
139
+ repatchUrlCreateObjectURL();
140
+ log('URL.createObjectURL re-patched on current globalThis.URL');
141
+ if (installed)
142
+ return;
143
+ installed = true;
144
+ log('Installing Three.js RN patches…');
145
+ // -----------------------------------------------------------------------
146
+ // Layer 2: THREE.ImageLoader patch — patch prototype to avoid ESM live-binding restrictions
147
+ // three.webgpu.js is an ESM file; its named exports are getter-only live bindings
148
+ // from the consumer's perspective, so assigning THREE.ImageLoader = X throws.
149
+ // Patching the prototype achieves the same effect without touching the namespace.
150
+ // -----------------------------------------------------------------------
151
+ const origImageLoaderLoad = THREE.ImageLoader.prototype.load;
152
+ THREE.ImageLoader.prototype.load = function patchedImageLoad(url, onLoad, onProgress, onError) {
153
+ if (!url || !isInlineImageUrl(url)) {
154
+ return origImageLoaderLoad.call(this, url, onLoad, onProgress, onError);
155
+ }
156
+ if (url.startsWith('data:image/')) {
157
+ const img = _makeImageFromDataUri(url, getMimeTypeFromDataUri(url));
158
+ onLoad?.(img);
159
+ return img;
160
+ }
161
+ const blob = blobStore.get(url);
162
+ if (!blob) {
163
+ log(`ImageLoader: no blob found for ${url} — returning placeholder`);
164
+ const placeholder = _makePlaceholder();
165
+ onLoad?.(placeholder);
166
+ return placeholder;
167
+ }
168
+ log(`ImageLoader: reading blob ${url} size=${blob.size} type=${blob.type}`);
169
+ const reader = new FileReader();
170
+ reader.onload = () => {
171
+ const dataUri = reader.result;
172
+ log(`ImageLoader: blob → data URI (${dataUri.length} chars)`);
173
+ const img = _makeImageFromDataUri(dataUri, blob.type || 'image/png');
174
+ onLoad?.(img);
175
+ };
176
+ reader.onerror = () => {
177
+ const err = new Error(`[ThreeRNPatch] FileReader failed for ${url}`);
178
+ log(`ImageLoader: FileReader error for ${url}`);
179
+ onError?.(err);
180
+ };
181
+ reader.readAsDataURL(blob);
182
+ return _makePlaceholder();
183
+ };
184
+ log('Patched THREE.ImageLoader.prototype.load');
185
+ // -----------------------------------------------------------------------
186
+ // Layer 3: THREE.TextureLoader safety net — same prototype-patch approach
187
+ // -----------------------------------------------------------------------
188
+ const origTextureLoaderLoad = THREE.TextureLoader.prototype.load;
189
+ THREE.TextureLoader.prototype.load = function patchedTextureLoad(url, onLoad, onProgress, onError) {
190
+ if (!url || !isInlineImageUrl(url)) {
191
+ return origTextureLoaderLoad.call(this, url, onLoad, onProgress, onError);
192
+ }
193
+ if (url.startsWith('data:image/')) {
194
+ const tex = new THREE.Texture();
195
+ tex.image = _makeImageFromDataUri(url, getMimeTypeFromDataUri(url));
196
+ tex.needsUpdate = true;
197
+ onLoad?.(tex);
198
+ return tex;
199
+ }
200
+ const blob = blobStore.get(url);
201
+ if (!blob) {
202
+ log(`TextureLoader: no blob for ${url} — returning empty texture`);
203
+ const tex = new THREE.Texture();
204
+ setTimeout(() => onLoad?.(tex), 0);
205
+ return tex;
206
+ }
207
+ log(`TextureLoader: reading blob ${url} size=${blob.size}`);
208
+ const tex = new THREE.Texture();
209
+ const reader = new FileReader();
210
+ reader.onload = () => {
211
+ const dataUri = reader.result;
212
+ const img = _makeImageFromDataUri(dataUri, blob.type || 'image/png');
213
+ tex.image = img;
214
+ tex.needsUpdate = true;
215
+ onLoad?.(tex);
216
+ };
217
+ reader.onerror = () => onError?.(new ErrorEvent('error'));
218
+ reader.readAsDataURL(blob);
219
+ return tex;
220
+ };
221
+ log('Patched THREE.TextureLoader.prototype.load');
222
+ const ImageBitmapLoader = THREE.ImageBitmapLoader;
223
+ const origImageBitmapLoad = ImageBitmapLoader?.prototype?.load;
224
+ if (origImageBitmapLoad) {
225
+ ImageBitmapLoader.prototype.load = function patchedImageBitmapLoad(url, onLoad, onProgress, onError) {
226
+ if (!url || !isInlineImageUrl(url)) {
227
+ return origImageBitmapLoad.call(this, url, onLoad, onProgress, onError);
228
+ }
229
+ const loadInlineBitmap = async () => {
230
+ try {
231
+ const bytes = url.startsWith('data:image/')
232
+ ? decodeDataUriBytes(url)
233
+ : await (async () => {
234
+ const blob = blobStore.get(url);
235
+ if (!blob)
236
+ return null;
237
+ return await bytesFromBlob(blob);
238
+ })();
239
+ if (!bytes) {
240
+ throw new Error(`[ThreeRNPatch] No blob found for ${url}`);
241
+ }
242
+ if (typeof createImageBitmap !== 'function') {
243
+ throw new Error('[ThreeRNPatch] createImageBitmap is unavailable');
244
+ }
245
+ const imageBitmap = await createImageBitmap(bytes);
246
+ onLoad?.(imageBitmap);
247
+ }
248
+ catch (error) {
249
+ onError?.(error instanceof Error ? error : new Error(String(error)));
250
+ }
251
+ };
252
+ void loadInlineBitmap();
253
+ return undefined;
254
+ };
255
+ log('Patched THREE.ImageBitmapLoader.prototype.load');
256
+ }
257
+ log('Three.js RN patches installed — embedded GLB textures will now load via FileReader+dataURI');
258
+ }
259
+ // ---------------------------------------------------------------------------
260
+ // Helpers
261
+ // ---------------------------------------------------------------------------
262
+ /**
263
+ * Build a minimal HTMLImageElement-like object from a data URI.
264
+ * expo-gl / react-three-fiber / @react-three/fiber/native accept this shape
265
+ * for texture.image on native (it gets handed to gl.texImage2D via Expo's NativeImageLoader).
266
+ */
267
+ function _makeImageFromDataUri(dataUri, _mimeType) {
268
+ return {
269
+ src: dataUri,
270
+ width: 0, // Three.js reads these after decoding; 0 is fine pre-decode
271
+ height: 0,
272
+ complete: false,
273
+ naturalWidth: 0,
274
+ naturalHeight: 0,
275
+ crossOrigin: '',
276
+ // expo-gl NativeImageLoader checks for 'uri' as well
277
+ uri: dataUri,
278
+ };
279
+ }
280
+ /** 1×1 transparent pixel stand-in (used when we have no blob data at all). */
281
+ function _makePlaceholder() {
282
+ return {
283
+ src: '',
284
+ width: 1,
285
+ height: 1,
286
+ complete: true,
287
+ naturalWidth: 1,
288
+ naturalHeight: 1,
289
+ crossOrigin: '',
290
+ uri: '',
291
+ };
292
+ }
@@ -0,0 +1,5 @@
1
+ import * as THREE from 'three';
2
+ export declare function computeObjectPlacement(object: THREE.Object3D, target: THREE.Vector3, desiredMaxDimension?: number): {
3
+ position: THREE.Vector3;
4
+ scale: number;
5
+ } | null;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.computeObjectPlacement = computeObjectPlacement;
37
+ const THREE = __importStar(require("three"));
38
+ function computeObjectPlacement(object, target, desiredMaxDimension = 1.5) {
39
+ const box = new THREE.Box3().setFromObject(object);
40
+ if (box.isEmpty())
41
+ return null;
42
+ const size = box.getSize(new THREE.Vector3());
43
+ const maxDimension = Math.max(size.x, size.y, size.z);
44
+ if (!Number.isFinite(maxDimension) || maxDimension <= 0)
45
+ return null;
46
+ const scale = desiredMaxDimension / maxDimension;
47
+ const center = box.getCenter(new THREE.Vector3());
48
+ const position = target.clone().sub(center.multiplyScalar(scale));
49
+ return { position, scale };
50
+ }
@@ -61,7 +61,7 @@ function useAuthedModelUri(remoteUrl) {
61
61
  return;
62
62
  }
63
63
  let cancelled = false;
64
- (async () => {
64
+ void (async () => {
65
65
  try {
66
66
  const token = await (0, studioApi_1.getToken)();
67
67
  // Strip query params (access_token changes per session) so the same GLB is cached across sessions
@@ -93,7 +93,9 @@ function useAuthedModelUri(remoteUrl) {
93
93
  });
94
94
  // Verify we got a valid GLB — check size and magic bytes ("glTF")
95
95
  const downloaded = await FileSystem.getInfoAsync(dlResult.uri);
96
- const downloadedSize = downloaded.size;
96
+ const downloadedSize = 'size' in downloaded && typeof downloaded.size === 'number'
97
+ ? downloaded.size
98
+ : undefined;
97
99
  if (!downloaded.exists || !downloadedSize || downloadedSize < 1000) {
98
100
  console.error('[useAuthedModelUri] Download too small (' + (downloadedSize ?? 0) + 'B), likely an error response');
99
101
  failedUrls.add(remoteUrl);
@@ -0,0 +1,7 @@
1
+ import type { GLTF } from 'three-stdlib';
2
+ type NativeGLTFState = {
3
+ gltf: GLTF | null;
4
+ error: Error | null;
5
+ };
6
+ export declare function useNativeGLTF(uri: string | null): NativeGLTFState;
7
+ export {};
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useNativeGLTF = useNativeGLTF;
4
+ const react_1 = require("react");
5
+ const loadGLTFFromUri_1 = require("./loadGLTFFromUri");
6
+ const gltfPromiseCache = new Map();
7
+ function getOrLoad(uri) {
8
+ const cached = gltfPromiseCache.get(uri);
9
+ if (cached)
10
+ return cached;
11
+ const promise = (0, loadGLTFFromUri_1.loadGLTFFromUri)(uri);
12
+ gltfPromiseCache.set(uri, promise);
13
+ return promise;
14
+ }
15
+ function useNativeGLTF(uri) {
16
+ const [state, setState] = (0, react_1.useState)({ gltf: null, error: null });
17
+ (0, react_1.useEffect)(() => {
18
+ if (!uri) {
19
+ setState({ gltf: null, error: null });
20
+ return;
21
+ }
22
+ let cancelled = false;
23
+ setState({ gltf: null, error: null });
24
+ getOrLoad(uri).then((gltf) => {
25
+ if (!cancelled)
26
+ setState({ gltf, error: null });
27
+ }, (error) => {
28
+ if (!cancelled)
29
+ setState({ gltf: null, error });
30
+ });
31
+ return () => {
32
+ cancelled = true;
33
+ };
34
+ }, [uri]);
35
+ return state;
36
+ }
package/package.json CHANGED
@@ -1,26 +1,31 @@
1
1
  {
2
2
  "name": "talking-head-studio",
3
- "version": "0.4.11",
3
+ "version": "0.4.12",
4
4
  "description": "Cross-platform 3D avatar component for React Native & web — lip-sync, gestures, accessories, and LLM integration. Powered by TalkingHead + Three.js.",
5
5
  "main": "dist/index.web.js",
6
6
  "browser": "dist/index.web.js",
7
7
  "react-native": "dist/index.js",
8
- "types": "dist/index.web.d.ts",
8
+ "types": "dist/index.d.ts",
9
9
  "source": "src/index.ts",
10
10
  "exports": {
11
11
  ".": {
12
12
  "react-native": "./dist/index.js",
13
- "types": "./dist/index.web.d.ts",
13
+ "types": "./dist/index.d.ts",
14
14
  "default": "./dist/index.web.js"
15
15
  },
16
16
  "./editor": {
17
17
  "types": "./dist/editor/index.d.ts",
18
+ "browser": "./dist/editor/index.web.js",
18
19
  "default": "./dist/editor/index.js"
19
20
  },
20
21
  "./voice": {
21
22
  "types": "./dist/voice/index.d.ts",
22
23
  "default": "./dist/voice/index.js"
23
24
  },
25
+ "./contract": {
26
+ "types": "./dist/contract.d.ts",
27
+ "default": "./dist/contract.js"
28
+ },
24
29
  "./appearance": {
25
30
  "types": "./dist/appearance/index.d.ts",
26
31
  "default": "./dist/appearance/index.js"
@@ -42,6 +47,14 @@
42
47
  "types": "./dist/wgpu/index.d.ts",
43
48
  "default": "./dist/wgpu/index.js"
44
49
  },
50
+ "./studio": {
51
+ "types": "./dist/studio/index.d.ts",
52
+ "default": "./dist/studio/index.js"
53
+ },
54
+ "./html": {
55
+ "types": "./dist/html.d.ts",
56
+ "default": "./dist/html.js"
57
+ },
45
58
  "./dist/assets/*.glb": "./dist/assets/*.glb"
46
59
  },
47
60
  "files": [
@@ -49,12 +62,13 @@
49
62
  ],
50
63
  "scripts": {
51
64
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
52
- "build": "npm run clean && tsc --project tsconfig.json && mkdir -p dist/assets && cp src/assets/*.glb dist/assets/",
65
+ "build": "npm run clean && tsc --project tsconfig.json && node tools/copy-build-assets.mjs",
53
66
  "typecheck": "tsc --noEmit",
54
67
  "lint": "eslint 'src/**/*.{ts,tsx}'",
55
68
  "format": "prettier --write 'src/**/*.{ts,tsx}'",
56
69
  "test": "jest",
57
- "prepublishOnly": "npm run lint && npm run typecheck && npm test -- --runInBand && npm run build",
70
+ "test:metadata": "node --test 'tests/*.test.mjs'",
71
+ "prepublishOnly": "npm run lint && npm run typecheck && npm test -- --runInBand && npm run test:metadata && npm run build",
58
72
  "build:creator": "vite build --config packages/avatar-creator/vite.config.ts"
59
73
  },
60
74
  "keywords": [
@@ -97,24 +111,44 @@
97
111
  ],
98
112
  "sideEffects": false,
99
113
  "dependencies": {
114
+ "@gltf-transform/core": "^4.3.0",
100
115
  "@mediapipe/tasks-vision": "^0.10.34",
101
- "expo-gl": "^55.0.10",
116
+ "base64-js": "^1.5.1",
102
117
  "zustand": "^5.0.12"
103
118
  },
104
119
  "peerDependencies": {
120
+ "@expo/vector-icons": ">=14",
121
+ "@gorhom/bottom-sheet": ">=5",
105
122
  "@react-three/drei": ">=9",
106
123
  "@react-three/fiber": ">=8",
124
+ "@shopify/flash-list": ">=1",
107
125
  "expo": ">=51",
108
126
  "expo-asset": ">=10",
109
127
  "expo-audio": ">=0.3",
128
+ "expo-blur": ">=13",
129
+ "expo-document-picker": ">=12",
110
130
  "expo-file-system": ">=17",
131
+ "expo-haptics": ">=13",
132
+ "expo-image": ">=1",
133
+ "moti": ">=0.29",
111
134
  "react": ">=18",
112
- "react-native": ">=0.73",
135
+ "react-native": ">=0.81",
136
+ "react-native-reanimated": ">=3",
113
137
  "react-native-webview": ">=13",
114
138
  "react-native-wgpu": ">=0.1",
115
- "three": ">=0.170"
139
+ "three": ">=0.170",
140
+ "three-stdlib": ">=2"
116
141
  },
117
142
  "peerDependenciesMeta": {
143
+ "@expo/vector-icons": {
144
+ "optional": true
145
+ },
146
+ "@gorhom/bottom-sheet": {
147
+ "optional": true
148
+ },
149
+ "@shopify/flash-list": {
150
+ "optional": true
151
+ },
118
152
  "react-native": {
119
153
  "optional": true
120
154
  },
@@ -133,9 +167,24 @@
133
167
  "expo-audio": {
134
168
  "optional": true
135
169
  },
170
+ "expo-blur": {
171
+ "optional": true
172
+ },
173
+ "expo-document-picker": {
174
+ "optional": true
175
+ },
136
176
  "expo-file-system": {
137
177
  "optional": true
138
178
  },
179
+ "expo-haptics": {
180
+ "optional": true
181
+ },
182
+ "expo-image": {
183
+ "optional": true
184
+ },
185
+ "moti": {
186
+ "optional": true
187
+ },
139
188
  "@react-three/fiber": {
140
189
  "optional": true
141
190
  },
@@ -144,49 +193,66 @@
144
193
  },
145
194
  "three": {
146
195
  "optional": true
196
+ },
197
+ "three-stdlib": {
198
+ "optional": true
199
+ },
200
+ "react-native-reanimated": {
201
+ "optional": true
147
202
  }
148
203
  },
149
204
  "devDependencies": {
150
205
  "@babel/core": "^7.29.0",
151
- "@babel/preset-env": "^7.29.0",
206
+ "@babel/preset-env": "^7.29.2",
152
207
  "@babel/preset-typescript": "^7.28.5",
153
208
  "@expo/vector-icons": "^15.1.1",
209
+ "@gorhom/bottom-sheet": "^5.2.8",
154
210
  "@react-native/babel-preset": "^0.84.1",
155
- "@react-three/drei": "^9.122.0",
156
- "@react-three/fiber": "^8.18.0",
211
+ "@react-three/drei": "^10.7.7",
212
+ "@react-three/fiber": "^9.5.0",
213
+ "@shopify/flash-list": "2.0.2",
157
214
  "@testing-library/react-native": "^13.3.3",
158
215
  "@types/jest": "^30.0.0",
216
+ "@types/node": "^25.5.0",
159
217
  "@types/react": "^19.2.14",
160
- "@types/react-dom": "^19.0.0",
218
+ "@types/react-dom": "^19.2.3",
161
219
  "@types/react-native": "^0.73.0",
162
- "@types/three": "^0.180.0",
163
- "@typescript-eslint/eslint-plugin": "^8.56.1",
164
- "@typescript-eslint/parser": "^8.56.1",
165
- "@vitejs/plugin-react": "^4.7.0",
166
- "babel-jest": "^30.2.0",
167
- "eslint": "^9.39.3",
220
+ "@types/three": "^0.183.1",
221
+ "@typescript-eslint/eslint-plugin": "^8.57.2",
222
+ "@typescript-eslint/parser": "^8.57.2",
223
+ "@vitejs/plugin-react": "^6.0.1",
224
+ "babel-jest": "^30.3.0",
225
+ "eslint": "^9.39.4",
168
226
  "eslint-config-prettier": "^10.1.8",
169
227
  "eslint-plugin-react": "^7.37.5",
170
228
  "eslint-plugin-react-hooks": "^7.0.1",
171
229
  "eslint-plugin-react-native": "^5.0.0",
172
- "expo-audio": "^55.0.9",
173
- "expo-haptics": "^55.0.9",
230
+ "expo-asset": "55.0.13",
231
+ "expo-audio": "55.0.11",
232
+ "expo-blur": "55.0.12",
233
+ "expo-document-picker": "55.0.11",
234
+ "expo-file-system": "55.0.15",
235
+ "expo-haptics": "55.0.11",
236
+ "expo-image": "55.0.8",
174
237
  "express": "^5.2.1",
175
- "jest": "^30.2.0",
176
- "jest-environment-jsdom": "^30.2.0",
238
+ "jest": "^30.3.0",
239
+ "jest-environment-jsdom": "^30.3.0",
177
240
  "metro-react-native-babel-preset": "^0.77.0",
178
241
  "moti": "^0.30.0",
179
- "multer": "^2.1.0",
242
+ "multer": "^2.1.1",
180
243
  "prettier": "^3.8.1",
181
- "react": "^18.3.1",
182
- "react-dom": "^18.3.1",
183
- "react-native-gesture-handler": "^2.30.0",
184
- "react-native-reanimated": "^4.2.3",
185
- "react-native-webview": "^13.16.0",
186
- "react-test-renderer": "^18.3.1",
187
- "three": "^0.180.0",
244
+ "react": "19.2.0",
245
+ "react-dom": "19.2.0",
246
+ "react-native-gesture-handler": "~2.30.0",
247
+ "react-native-reanimated": "4.2.1",
248
+ "react-native-webview": "13.16.0",
249
+ "react-native-wgpu": "0.5.11",
250
+ "react-native-worklets": "0.7.2",
251
+ "react-test-renderer": "19.2.0",
252
+ "three": "^0.183.2",
253
+ "three-stdlib": "^2.36.1",
188
254
  "ts-jest": "^29.4.6",
189
- "typescript": "^5.3.3",
255
+ "typescript": "^6.0.2",
190
256
  "vite": "^8.0.3"
191
257
  }
192
258
  }