talking-head-studio 0.4.10 → 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 (178) hide show
  1. package/README.md +299 -337
  2. package/dist/TalkingHead.d.ts +44 -28
  3. package/dist/TalkingHead.js +21 -2
  4. package/dist/TalkingHead.web.d.ts +37 -4
  5. package/dist/TalkingHead.web.js +28 -8
  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 +41 -28
  10. package/dist/appearance/apply.js +2 -3
  11. package/dist/appearance/matchers.js +1 -2
  12. package/dist/appearance/schema.js +1 -2
  13. package/dist/contract.d.ts +14 -0
  14. package/dist/contract.js +30 -0
  15. package/dist/core/avatar/avatarCapabilities.d.ts +60 -0
  16. package/dist/core/avatar/avatarCapabilities.js +100 -0
  17. package/dist/core/avatar/backend.d.ts +130 -0
  18. package/dist/core/avatar/backend.js +4 -0
  19. package/dist/core/avatar/backends/gaussian.d.ts +49 -0
  20. package/dist/core/avatar/backends/gaussian.js +293 -0
  21. package/dist/core/avatar/backends/index.d.ts +3 -0
  22. package/dist/core/avatar/backends/index.js +7 -0
  23. package/dist/core/avatar/backends/morphTarget.d.ts +39 -0
  24. package/dist/core/avatar/backends/morphTarget.js +179 -0
  25. package/dist/core/avatar/faceControls.d.ts +40 -0
  26. package/dist/core/avatar/faceControls.js +138 -0
  27. package/dist/core/avatar/motion.d.ts +1713 -0
  28. package/dist/core/avatar/motion.js +550 -0
  29. package/dist/core/avatar/motionRuntime.d.ts +46 -0
  30. package/dist/core/avatar/motionRuntime.js +84 -0
  31. package/dist/core/avatar/schema.d.ts +78 -0
  32. package/dist/core/avatar/schema.js +134 -0
  33. package/dist/core/avatar/visemes.d.ts +47 -1
  34. package/dist/core/avatar/visemes.js +114 -1
  35. package/dist/editor/AvatarCanvas.js +93 -3
  36. package/dist/editor/AvatarEditor.native.js +19 -9
  37. package/dist/editor/AvatarModel.js +2 -2
  38. package/dist/editor/FaceSqueezeEditor.d.ts +3 -1
  39. package/dist/editor/FaceSqueezeEditor.js +195 -121
  40. package/dist/editor/FaceSqueezeEditor.web.d.ts +3 -1
  41. package/dist/editor/FaceSqueezeEditor.web.js +32 -30
  42. package/dist/editor/RigidAccessory.js +18 -4
  43. package/dist/editor/SkinnedClothing.js +19 -9
  44. package/dist/editor/boneLockedDrag.d.ts +11 -0
  45. package/dist/editor/boneLockedDrag.js +68 -0
  46. package/dist/editor/boneSnap.js +22 -12
  47. package/dist/editor/boneSnap.web.d.ts +27 -0
  48. package/dist/editor/boneSnap.web.js +99 -0
  49. package/dist/editor/index.web.d.ts +10 -0
  50. package/dist/editor/index.web.js +26 -0
  51. package/dist/editor/sounds/haha.wav +0 -0
  52. package/dist/editor/sounds/owie.wav +0 -0
  53. package/dist/editor/sounds/stop.wav +0 -0
  54. package/dist/editor/studioTheme.d.ts +14 -14
  55. package/dist/editor/studioTheme.js +19 -16
  56. package/dist/editor/types.d.ts +1 -0
  57. package/dist/html/accessories.d.ts +7 -0
  58. package/dist/html/accessories.js +149 -0
  59. package/dist/html/motion.d.ts +1 -0
  60. package/dist/html/motion.js +189 -0
  61. package/dist/html/visemes.d.ts +7 -0
  62. package/dist/html/visemes.js +348 -0
  63. package/dist/html.d.ts +1 -1
  64. package/dist/html.js +56 -734
  65. package/dist/index.d.ts +19 -1
  66. package/dist/index.js +44 -5
  67. package/dist/index.web.d.ts +18 -1
  68. package/dist/index.web.js +36 -3
  69. package/dist/platform/api/types.d.ts +10 -0
  70. package/dist/platform/api/types.js +2 -0
  71. package/dist/platform/marketplace/types.d.ts +32 -0
  72. package/dist/platform/marketplace/types.js +2 -0
  73. package/dist/platform/sdk/unity.d.ts +27 -0
  74. package/dist/platform/sdk/unity.js +2 -0
  75. package/dist/platform/sdk/unreal.d.ts +23 -0
  76. package/dist/platform/sdk/unreal.js +2 -0
  77. package/dist/platform/sdk/web.d.ts +16 -0
  78. package/dist/platform/sdk/web.js +2 -0
  79. package/dist/sketchfab/api.js +5 -5
  80. package/dist/sketchfab/glbInspect.d.ts +22 -0
  81. package/dist/sketchfab/glbInspect.js +58 -0
  82. package/dist/sketchfab/index.d.ts +3 -0
  83. package/dist/sketchfab/index.js +8 -1
  84. package/dist/sketchfab/inspectRemote.d.ts +13 -0
  85. package/dist/sketchfab/inspectRemote.js +77 -0
  86. package/dist/sketchfab/types.d.ts +10 -0
  87. package/dist/sketchfab/useSketchfabSearch.js +1 -2
  88. package/dist/studio/AccessoryBrowserScreen.d.ts +6 -0
  89. package/dist/studio/AccessoryBrowserScreen.js +626 -0
  90. package/dist/studio/AccessoryPanel.d.ts +10 -0
  91. package/dist/studio/AccessoryPanel.js +396 -0
  92. package/dist/studio/AppearancePanel.d.ts +9 -0
  93. package/dist/studio/AppearancePanel.js +77 -0
  94. package/dist/studio/AvatarCreatorScreen.d.ts +5 -0
  95. package/dist/studio/AvatarCreatorScreen.js +806 -0
  96. package/dist/studio/AvatarEditorScreen.d.ts +14 -0
  97. package/dist/studio/AvatarEditorScreen.js +510 -0
  98. package/dist/studio/AvatarGrid.d.ts +23 -0
  99. package/dist/studio/AvatarGrid.js +257 -0
  100. package/dist/studio/ColorSwatch.d.ts +8 -0
  101. package/dist/studio/ColorSwatch.js +100 -0
  102. package/dist/studio/CreateVoiceProfileSheet.d.ts +8 -0
  103. package/dist/studio/CreateVoiceProfileSheet.js +242 -0
  104. package/dist/studio/DetailsPanel.d.ts +15 -0
  105. package/dist/studio/DetailsPanel.js +239 -0
  106. package/dist/studio/FilamentEditor.d.ts +2 -0
  107. package/dist/studio/FilamentEditor.js +6 -0
  108. package/dist/studio/PrecisionPanel.d.ts +2 -0
  109. package/dist/studio/PrecisionPanel.js +7 -0
  110. package/dist/studio/PublicGalleryScreen.d.ts +5 -0
  111. package/dist/studio/PublicGalleryScreen.js +358 -0
  112. package/dist/studio/SketchfabModelCard.d.ts +20 -0
  113. package/dist/studio/SketchfabModelCard.js +104 -0
  114. package/dist/studio/StudioBrowseHeader.d.ts +9 -0
  115. package/dist/studio/StudioBrowseHeader.js +28 -0
  116. package/dist/studio/StudioEmptyState.d.ts +8 -0
  117. package/dist/studio/StudioEmptyState.js +29 -0
  118. package/dist/studio/StudioFloatingAction.d.ts +13 -0
  119. package/dist/studio/StudioFloatingAction.js +42 -0
  120. package/dist/studio/StudioSectionHeader.d.ts +7 -0
  121. package/dist/studio/StudioSectionHeader.js +27 -0
  122. package/dist/studio/StudioSurfaceCard.d.ts +8 -0
  123. package/dist/studio/StudioSurfaceCard.js +20 -0
  124. package/dist/studio/VoicePanel.d.ts +15 -0
  125. package/dist/studio/VoicePanel.js +305 -0
  126. package/dist/studio/constants.d.ts +3 -0
  127. package/dist/studio/constants.js +6 -0
  128. package/dist/studio/index.d.ts +29 -0
  129. package/dist/studio/index.js +54 -0
  130. package/dist/studio/useSketchfabCapabilities.d.ts +31 -0
  131. package/dist/studio/useSketchfabCapabilities.js +82 -0
  132. package/dist/tts/useDirectVisemeStream.d.ts +2 -6
  133. package/dist/tts/useDirectVisemeStream.js +16 -12
  134. package/dist/tts/useMotionMarkers.d.ts +0 -1
  135. package/dist/tts/useMotionMarkers.js +1 -2
  136. package/dist/utils/avatarUtils.js +94 -8
  137. package/dist/utils/faceLandmarkerToShapeWeights.js +21 -14
  138. package/dist/voice/convertToWav.js +1 -2
  139. package/dist/voice/index.d.ts +3 -0
  140. package/dist/voice/index.js +6 -1
  141. package/dist/voice/useAudioPlayer.js +18 -6
  142. package/dist/voice/useAudioRecording.js +1 -2
  143. package/dist/voice/useFaceControls.d.ts +14 -0
  144. package/dist/voice/useFaceControls.js +81 -0
  145. package/dist/voice/useVoicePreview.d.ts +7 -0
  146. package/dist/voice/useVoicePreview.js +83 -0
  147. package/dist/wardrobe/index.d.ts +3 -0
  148. package/dist/wardrobe/index.js +8 -1
  149. package/dist/wardrobe/useAccessoryGestures.d.ts +20 -0
  150. package/dist/wardrobe/useAccessoryGestures.js +94 -0
  151. package/dist/wardrobe/useAvatarWardrobeHydration.js +9 -4
  152. package/dist/wardrobe/useStudioAvatar.d.ts +29 -0
  153. package/dist/wardrobe/useStudioAvatar.js +186 -0
  154. package/dist/wardrobe/wardrobeStore.d.ts +2 -0
  155. package/dist/wardrobe/wardrobeStore.js +12 -2
  156. package/dist/wgpu/R3FWebGpuCanvas.d.ts +15 -0
  157. package/dist/wgpu/R3FWebGpuCanvas.js +176 -0
  158. package/dist/wgpu/WgpuAvatar.d.ts +26 -2
  159. package/dist/wgpu/WgpuAvatar.js +313 -46
  160. package/dist/wgpu/accessoryDefaults.d.ts +12 -0
  161. package/dist/wgpu/accessoryDefaults.js +19 -0
  162. package/dist/wgpu/blobShim.d.ts +2 -0
  163. package/dist/wgpu/blobShim.js +191 -0
  164. package/dist/wgpu/index.d.ts +1 -0
  165. package/dist/wgpu/index.js +4 -1
  166. package/dist/wgpu/loadGLTFFromUri.d.ts +2 -0
  167. package/dist/wgpu/loadGLTFFromUri.js +75 -0
  168. package/dist/wgpu/morphTables.js +21 -10
  169. package/dist/wgpu/motionState.d.ts +20 -0
  170. package/dist/wgpu/motionState.js +31 -0
  171. package/dist/wgpu/patchThreeForRN.d.ts +28 -0
  172. package/dist/wgpu/patchThreeForRN.js +292 -0
  173. package/dist/wgpu/scenePlacement.d.ts +5 -0
  174. package/dist/wgpu/scenePlacement.js +50 -0
  175. package/dist/wgpu/useAuthedModelUri.js +22 -11
  176. package/dist/wgpu/useNativeGLTF.d.ts +7 -0
  177. package/dist/wgpu/useNativeGLTF.js +36 -0
  178. package/package.json +102 -32
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
2
  import { type StyleProp, type ViewStyle } from 'react-native';
3
- export type TalkingHeadMood = 'neutral' | 'happy' | 'sad' | 'angry' | 'excited' | 'thinking' | 'concerned' | 'surprised';
3
+ export type TalkingHeadMood = 'neutral' | 'happy' | 'sad' | 'angry' | 'fear' | 'disgust' | 'love' | 'sleep' | 'excited' | 'thinking' | 'concerned' | 'surprised';
4
+ import type { AgentVisemePayload, OculusViseme, VisemeCue } from './core/avatar/visemes';
5
+ import type { MotionKey, TalkingHeadGesture, TalkingHeadPose } from './core/avatar/motion';
4
6
  export type TalkingHeadLoadingStage = 'booting' | 'fetching_model' | 'loading_avatar' | 'loading_fallback' | 'ready';
5
7
  export interface TalkingHeadLoadingState {
6
8
  stage: TalkingHeadLoadingStage;
@@ -9,36 +11,20 @@ export interface TalkingHeadLoadingState {
9
11
  /**
10
12
  * Standard viseme keys supported by the avatar.
11
13
  * Use with sendViseme() from your TTS viseme callbacks.
14
+ * @deprecated Use OculusViseme from @yourplatform/avatar/core/avatar/visemes
12
15
  */
13
- export type TalkingHeadViseme = 'sil' | 'PP' | 'FF' | 'TH' | 'DD' | 'kk' | 'CH' | 'SS' | 'nn' | 'RR' | 'aa' | 'ee' | 'ih' | 'oh' | 'ou';
14
- /** Rhubarb mouth shape cue (Preston Blair set: A-H, X) */
15
- export interface TalkingHeadVisemeCue {
16
- startMs: number;
17
- endMs: number;
18
- viseme: 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'X';
19
- }
16
+ export type TalkingHeadViseme = OculusViseme;
17
+ /**
18
+ * Rhubarb mouth shape cue (Preston Blair set: A-H, X)
19
+ * @deprecated Use VisemeCue from @yourplatform/avatar/core/avatar/visemes
20
+ */
21
+ export type TalkingHeadVisemeCue = VisemeCue;
20
22
  /**
21
23
  * A full viseme schedule from the Rhubarb sidecar endpoint.
22
24
  * Pass to scheduleVisemes() when agent_visemes arrives on the data channel.
25
+ * @deprecated Use AgentVisemePayload from @yourplatform/avatar/core/avatar/visemes
23
26
  */
24
- export interface TalkingHeadVisemeSchedule {
25
- /** Matches X-TTS-Request-Id / agent_visemes.requestId */
26
- requestId?: string;
27
- /**
28
- * Wall-clock ms at which the TTS request was fired (agent side).
29
- * Used as the scheduling anchor plus AUDIO_PIPELINE_DELAY_MS.
30
- */
31
- startedAtMs?: number;
32
- /**
33
- * Wall-clock ms at which audio actually began playing in the speaker.
34
- * When present, used directly as the scheduling anchor with no additional
35
- * pipeline offset — more accurate than startedAtMs on fast connections.
36
- * Stamp this from the LiveKit onAudioPlaybackStarted callback if available.
37
- */
38
- audioStartedAtMs?: number;
39
- durationMs?: number;
40
- cues: TalkingHeadVisemeCue[];
41
- }
27
+ export type TalkingHeadVisemeSchedule = AgentVisemePayload;
42
28
  export interface TalkingHeadAccessory {
43
29
  id: string;
44
30
  url: string;
@@ -66,6 +52,9 @@ export interface TalkingHeadProps {
66
52
  /** Base URL for vendored assets. When set, replaces all cdn.jsdelivr.net references. */
67
53
  vendorBaseUrl?: string | null;
68
54
  }
55
+ /** @deprecated Use AvatarPlayerProps */
56
+ export type TalkingHeadPropsAlias = TalkingHeadProps;
57
+ export type AvatarPlayerProps = TalkingHeadProps;
69
58
  export interface TalkingHeadRef {
70
59
  sendAmplitude: (amplitude: number) => void;
71
60
  /**
@@ -100,7 +89,34 @@ export interface TalkingHeadRef {
100
89
  setSkinColor: (color: string) => void;
101
90
  setEyeColor: (color: string) => void;
102
91
  setAccessories: (accessories: TalkingHeadAccessory[]) => void;
103
- /** Dispatch a named motion/gesture to the avatar (e.g. 'wave_right', 'dance_idle'). */
104
- dispatchMotion: (name: string) => void;
92
+ /** Play a procedural motion (e.g. 'attack', 'defend', 'groove'). */
93
+ dispatchMotion(name: MotionKey): void;
94
+ dispatchMotion(name: string): void;
95
+ /** Stop the current procedural motion and return to rest. */
96
+ stopMotion: () => void;
97
+ /** Play an upstream TalkingHead hand gesture (e.g. 'thumbup'). */
98
+ playGesture: (name: TalkingHeadGesture | string, opts?: {
99
+ dur?: number;
100
+ mirror?: boolean;
101
+ ms?: number;
102
+ }) => void;
103
+ /** Stop the current gesture, easing out over `ms`. */
104
+ stopGesture: (ms?: number) => void;
105
+ /** Strike a pose — a built-in template name (e.g. 'oneknee') or a pose-file URL. */
106
+ playPose: (urlOrTemplate: TalkingHeadPose | string, dur?: number) => void;
107
+ /** Release the current pose and return to the default stance. */
108
+ stopPose: () => void;
109
+ /** Play a full body animation from a GLB/FBX URL (e.g. a combat move). */
110
+ playAnimation: (url: string, opts?: {
111
+ dur?: number;
112
+ index?: number;
113
+ }) => void;
114
+ /** Stop the current body animation. */
115
+ stopAnimation: () => void;
116
+ /** Turn head/eyes toward viewport coordinates (px), easing over `ms`. */
117
+ lookAt: (x: number, y: number, ms?: number) => void;
105
118
  }
119
+ /** @deprecated Use AvatarPlayerRef */
120
+ export type TalkingHeadRefAlias = TalkingHeadRef;
121
+ export type AvatarPlayerRef = TalkingHeadRef;
106
122
  export declare const TalkingHead: React.ForwardRefExoticComponent<TalkingHeadProps & React.RefAttributes<TalkingHeadRef>>;
@@ -6,6 +6,7 @@ const react_1 = require("react");
6
6
  const react_native_1 = require("react-native");
7
7
  const react_native_webview_1 = require("react-native-webview");
8
8
  const html_1 = require("./html");
9
+ const shouldLogDebugMessages = __DEV__;
9
10
  exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'neutral', cameraView = 'upper', cameraDistance = -0.5, hairColor, skinColor, eyeColor, accessories, onLoadingChange, onReady, onError, onAvatarState, onVoiceMood, style, vendorBaseUrl, }, ref) => {
10
11
  const webViewRef = (0, react_1.useRef)(null);
11
12
  const readyRef = (0, react_1.useRef)(false);
@@ -129,6 +130,14 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
129
130
  }
130
131
  },
131
132
  dispatchMotion: (name) => post({ type: 'motion', name }),
133
+ stopMotion: () => post({ type: 'stop_motion' }),
134
+ playGesture: (name, opts) => post({ type: 'gesture', name, dur: opts?.dur, mirror: opts?.mirror, ms: opts?.ms }),
135
+ stopGesture: (ms) => post({ type: 'stop_gesture', ms }),
136
+ playPose: (url, dur) => post({ type: 'pose', url, dur }),
137
+ stopPose: () => post({ type: 'stop_pose' }),
138
+ playAnimation: (url, opts) => post({ type: 'animation', url, dur: opts?.dur, index: opts?.index }),
139
+ stopAnimation: () => post({ type: 'stop_animation' }),
140
+ lookAt: (x, y, ms) => post({ type: 'look_at', x, y, ms }),
132
141
  }), [post]);
133
142
  // Sync mood via postMessage only — never causes a WebView reload
134
143
  (0, react_1.useEffect)(() => {
@@ -231,7 +240,7 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
231
240
  else if (msg.type === 'voiceMood') {
232
241
  onVoiceMoodRef.current?.(msg.mood);
233
242
  }
234
- else if (msg.type === 'log') {
243
+ else if (msg.type === 'log' && shouldLogDebugMessages) {
235
244
  console.log('[TalkingHead]', msg.message);
236
245
  }
237
246
  }
@@ -249,7 +258,17 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
249
258
  const { statusCode, description, url } = event.nativeEvent;
250
259
  onErrorRef.current?.(`[http ${statusCode}] ${description || url || 'Avatar request failed'}`);
251
260
  }, []);
252
- return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.container, style], children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: webViewRef, source: { html, baseUrl: webViewBaseUrl }, style: styles.webview, javaScriptEnabled: true, domStorageEnabled: true, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, onMessage: onMessage, onError: handleWebViewError, onHttpError: handleWebViewHttpError, onLoadStart: () => console.log('[TalkingHead] WebView load start'), onLoadEnd: () => console.log('[TalkingHead] WebView load end'), onLoadProgress: (event) => console.log('[TalkingHead] WebView progress', event.nativeEvent.progress), originWhitelist: ['*'], allowFileAccess: true, allowFileAccessFromFileURLs: true, allowUniversalAccessFromFileURLs: true, mixedContentMode: "always" }, webViewKey) }));
261
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.container, style], children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: webViewRef, source: { html, baseUrl: webViewBaseUrl }, style: styles.webview, javaScriptEnabled: true, domStorageEnabled: true, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, onMessage: onMessage, onError: handleWebViewError, onHttpError: handleWebViewHttpError, onLoadStart: () => {
262
+ if (shouldLogDebugMessages)
263
+ console.log('[TalkingHead] WebView load start');
264
+ }, onLoadEnd: () => {
265
+ if (shouldLogDebugMessages)
266
+ console.log('[TalkingHead] WebView load end');
267
+ }, onLoadProgress: (event) => {
268
+ if (shouldLogDebugMessages) {
269
+ console.log('[TalkingHead] WebView progress', event.nativeEvent.progress);
270
+ }
271
+ }, originWhitelist: ['*'], allowFileAccess: true, allowFileAccessFromFileURLs: true, allowUniversalAccessFromFileURLs: true, mixedContentMode: "always" }, webViewKey) }));
253
272
  });
254
273
  exports.TalkingHead.displayName = 'TalkingHead';
255
274
  const styles = react_native_1.StyleSheet.create({
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
- import type { TalkingHeadLoadingState, TalkingHeadVisemeCue, TalkingHeadVisemeSchedule } from './TalkingHead';
3
- export type { TalkingHeadLoadingState, TalkingHeadVisemeCue, TalkingHeadVisemeSchedule };
4
- export type TalkingHeadMood = 'neutral' | 'happy' | 'sad' | 'angry' | 'excited' | 'thinking' | 'concerned' | 'surprised';
2
+ import type { TalkingHeadLoadingState, TalkingHeadLoadingStage, TalkingHeadViseme, TalkingHeadVisemeCue, TalkingHeadVisemeSchedule } from './TalkingHead';
3
+ import type { MotionKey, TalkingHeadGesture, TalkingHeadPose } from './core/avatar/motion';
4
+ export type { TalkingHeadLoadingStage, TalkingHeadLoadingState, TalkingHeadViseme, TalkingHeadVisemeCue, TalkingHeadVisemeSchedule, };
5
+ export type TalkingHeadMood = 'neutral' | 'happy' | 'sad' | 'angry' | 'fear' | 'disgust' | 'love' | 'sleep' | 'excited' | 'thinking' | 'concerned' | 'surprised';
5
6
  export interface TalkingHeadAccessory {
6
7
  id: string;
7
8
  url: string;
@@ -26,8 +27,12 @@ export interface TalkingHeadProps {
26
27
  onAvatarState?: (state: string) => void;
27
28
  style?: React.CSSProperties;
28
29
  }
30
+ /** @deprecated Use AvatarPlayerProps */
31
+ export type TalkingHeadPropsAlias = TalkingHeadProps;
32
+ export type AvatarPlayerProps = TalkingHeadProps;
29
33
  export interface TalkingHeadRef {
30
34
  sendAmplitude: (amplitude: number) => void;
35
+ sendViseme: (viseme: TalkingHeadViseme, weight?: number) => void;
31
36
  scheduleVisemes: (schedule: TalkingHeadVisemeSchedule) => void;
32
37
  clearVisemes: () => void;
33
38
  setMood: (mood: TalkingHeadMood) => void;
@@ -35,6 +40,34 @@ export interface TalkingHeadRef {
35
40
  setSkinColor: (color: string) => void;
36
41
  setEyeColor: (color: string) => void;
37
42
  setAccessories: (accessories: TalkingHeadAccessory[]) => void;
38
- dispatchMotion: (name: string) => void;
43
+ /** Play a procedural motion (e.g. 'attack', 'defend', 'groove'). */
44
+ dispatchMotion(name: MotionKey): void;
45
+ dispatchMotion(name: string): void;
46
+ /** Stop the current procedural motion and return to rest. */
47
+ stopMotion: () => void;
48
+ /** Play an upstream TalkingHead hand gesture (e.g. 'thumbup'). */
49
+ playGesture: (name: TalkingHeadGesture | string, opts?: {
50
+ dur?: number;
51
+ mirror?: boolean;
52
+ ms?: number;
53
+ }) => void;
54
+ /** Stop the current gesture, easing out over `ms`. */
55
+ stopGesture: (ms?: number) => void;
56
+ /** Strike a pose — a built-in template name (e.g. 'oneknee') or a pose-file URL. */
57
+ playPose: (urlOrTemplate: TalkingHeadPose | string, dur?: number) => void;
58
+ /** Release the current pose and return to the default stance. */
59
+ stopPose: () => void;
60
+ /** Play a full body animation from a GLB/FBX URL (e.g. a combat move). */
61
+ playAnimation: (url: string, opts?: {
62
+ dur?: number;
63
+ index?: number;
64
+ }) => void;
65
+ /** Stop the current body animation. */
66
+ stopAnimation: () => void;
67
+ /** Turn head/eyes toward viewport coordinates (px), easing over `ms`. */
68
+ lookAt: (x: number, y: number, ms?: number) => void;
39
69
  }
70
+ /** @deprecated Use AvatarPlayerRef */
71
+ export type TalkingHeadRefAlias = TalkingHeadRef;
72
+ export type AvatarPlayerRef = TalkingHeadRef;
40
73
  export declare const TalkingHead: React.ForwardRefExoticComponent<TalkingHeadProps & React.RefAttributes<TalkingHeadRef>>;
@@ -15,18 +15,29 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
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
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
36
  exports.TalkingHead = void 0;
27
37
  const jsx_runtime_1 = require("react/jsx-runtime");
28
38
  const react_1 = __importStar(require("react"));
29
39
  const html_1 = require("./html");
40
+ const shouldLogDebugMessages = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';
30
41
  const containerStyle = {
31
42
  overflow: 'hidden',
32
43
  borderRadius: 12,
@@ -69,6 +80,7 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
69
80
  }, []);
70
81
  (0, react_1.useImperativeHandle)(ref, () => ({
71
82
  sendAmplitude: (amplitude) => post({ type: 'amplitude', value: amplitude }),
83
+ sendViseme: (viseme, weight = 1.0) => post({ type: 'viseme', viseme, weight }),
72
84
  scheduleVisemes: (schedule) => post({ type: 'schedule_visemes', schedule }),
73
85
  clearVisemes: () => post({ type: 'clear_visemes' }),
74
86
  setMood: (nextMood) => {
@@ -98,6 +110,14 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
98
110
  }
99
111
  },
100
112
  dispatchMotion: (name) => post({ type: 'motion', name }),
113
+ stopMotion: () => post({ type: 'stop_motion' }),
114
+ playGesture: (name, opts) => post({ type: 'gesture', name, dur: opts?.dur, mirror: opts?.mirror, ms: opts?.ms }),
115
+ stopGesture: (ms) => post({ type: 'stop_gesture', ms }),
116
+ playPose: (url, dur) => post({ type: 'pose', url, dur }),
117
+ stopPose: () => post({ type: 'stop_pose' }),
118
+ playAnimation: (url, opts) => post({ type: 'animation', url, dur: opts?.dur, index: opts?.index }),
119
+ stopAnimation: () => post({ type: 'stop_animation' }),
120
+ lookAt: (x, y, ms) => post({ type: 'look_at', x, y, ms }),
101
121
  }), [post]);
102
122
  (0, react_1.useEffect)(() => {
103
123
  pendingMoodRef.current = mood;
@@ -192,7 +212,7 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
192
212
  else if (msg.type === 'avatarState') {
193
213
  onAvatarStateRef.current?.(msg.state);
194
214
  }
195
- else if (msg.type === 'log') {
215
+ else if (msg.type === 'log' && shouldLogDebugMessages) {
196
216
  console.log('[TalkingHead]', msg.message);
197
217
  }
198
218
  }
@@ -1,3 +1,4 @@
1
+ import './wgpu/blobShim';
1
2
  import React from 'react';
2
3
  import { ViewStyle, StyleProp } from 'react-native';
3
4
  import { type TalkingHeadMood, type TalkingHeadAccessory, type TalkingHeadViseme, type TalkingHeadVisemeSchedule } from './TalkingHead';
@@ -27,6 +28,27 @@ export interface TalkingHeadVisualizationRef {
27
28
  clearVisemes: () => void;
28
29
  /** Trigger a named motion on the avatar (e.g. 'celebrate', 'groove', 'wave') */
29
30
  playMotion: (name: string) => void;
31
+ /** Stop the current procedural motion and return to rest. */
32
+ stopMotion: () => void;
33
+ /** Play an upstream TalkingHead gesture or native procedural fallback. */
34
+ playGesture: (name: string, opts?: {
35
+ dur?: number;
36
+ mirror?: boolean;
37
+ ms?: number;
38
+ }) => void;
39
+ /** Stop the current gesture. */
40
+ stopGesture: (ms?: number) => void;
41
+ /** Strike a pose by built-in name or pose URL. */
42
+ playPose: (name: string, dur?: number) => void;
43
+ /** Release the current pose and return to rest. */
44
+ stopPose: () => void;
45
+ /** Play a hosted animation clip on renderers that support it. */
46
+ playAnimation: (url: string, opts?: {
47
+ dur?: number;
48
+ index?: number;
49
+ }) => void;
50
+ /** Stop the current hosted animation clip. */
51
+ stopAnimation: () => void;
30
52
  }
31
53
  /**
32
54
  * TalkingHeadVisualization — optimized component for rendering the 3D avatar.
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TalkingHeadVisualization = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ // IMPORTANT: blobShim must be first — installs Blob+createObjectURL patch
6
+ // before @react-three/fiber's polyfills() runs.
7
+ require("./wgpu/blobShim");
5
8
  /* eslint-disable @typescript-eslint/no-unused-expressions */
6
9
  const react_1 = require("react");
7
10
  const react_native_1 = require("react-native");
@@ -60,7 +63,7 @@ exports.TalkingHeadVisualization = (0, react_1.forwardRef)(({ style, avatarUrl,
60
63
  setFallbackUrl(_fallbackDataUri);
61
64
  return;
62
65
  }
63
- getFallbackAvatarUrl().then((u) => u && setFallbackUrl(u));
66
+ void getFallbackAvatarUrl().then((u) => u && setFallbackUrl(u));
64
67
  }, []);
65
68
  const [mood, setMood] = (0, react_1.useState)(initialMood);
66
69
  const [isAvatarReady, setIsAvatarReady] = (0, react_1.useState)(false);
@@ -98,7 +101,7 @@ exports.TalkingHeadVisualization = (0, react_1.forwardRef)(({ style, avatarUrl,
98
101
  cues: pendingSchedule.cues.length,
99
102
  ageMs,
100
103
  });
101
- activeAvatar()?.scheduleVisemes(pendingSchedule);
104
+ activeAvatar()?.scheduleVisemes?.(pendingSchedule);
102
105
  onVisemeScheduleApplied?.({
103
106
  requestId: pendingSchedule.requestId ?? null,
104
107
  appliedAtMs: Date.now(),
@@ -108,10 +111,24 @@ exports.TalkingHeadVisualization = (0, react_1.forwardRef)(({ style, avatarUrl,
108
111
  }, [onVisemeScheduleApplied, activeAvatar]);
109
112
  (0, react_1.useImperativeHandle)(ref, () => ({
110
113
  setMood: (m) => setMood(m),
111
- sendAmplitude: (a) => activeAvatar()?.sendAmplitude(a),
112
- sendViseme: (viseme, weight) => activeAvatar()?.sendViseme(viseme, weight),
113
- clearVisemes: () => activeAvatar()?.clearVisemes(),
114
- playMotion: (name) => avatarRef.current?.dispatchMotion(name),
114
+ sendAmplitude: (a) => activeAvatar()?.sendAmplitude?.(a),
115
+ sendViseme: (viseme, weight) => activeAvatar()?.sendViseme?.(viseme, weight),
116
+ clearVisemes: () => activeAvatar()?.clearVisemes?.(),
117
+ playMotion: (name) => {
118
+ const avatar = activeAvatar();
119
+ if (avatar?.playMotion) {
120
+ avatar.playMotion(name);
121
+ return;
122
+ }
123
+ avatar?.dispatchMotion?.(name);
124
+ },
125
+ stopMotion: () => activeAvatar()?.stopMotion?.(),
126
+ playGesture: (name, opts) => activeAvatar()?.playGesture?.(name, opts),
127
+ stopGesture: (ms) => activeAvatar()?.stopGesture?.(ms),
128
+ playPose: (name, dur) => activeAvatar()?.playPose?.(name, dur),
129
+ stopPose: () => activeAvatar()?.stopPose?.(),
130
+ playAnimation: (url, opts) => activeAvatar()?.playAnimation?.(url, opts),
131
+ stopAnimation: () => activeAvatar()?.stopAnimation?.(),
115
132
  scheduleVisemes: (schedule) => {
116
133
  const scheduleKey = `${schedule.requestId ?? 'anonymous'}:${schedule.startedAtMs ?? 0}`;
117
134
  if (lastScheduledVisemeKeyRef.current === scheduleKey)
@@ -119,7 +136,7 @@ exports.TalkingHeadVisualization = (0, react_1.forwardRef)(({ style, avatarUrl,
119
136
  const av = activeAvatar();
120
137
  // WgpuAvatar buffers pending schedules internally — no ready gate needed.
121
138
  // WebView (avatarRef) still needs the ready gate.
122
- if (!av || (!wgpuRef.current && !isAvatarReady)) {
139
+ if (!av?.scheduleVisemes || (!wgpuRef.current && !isAvatarReady)) {
123
140
  pendingVisemeScheduleRef.current = schedule;
124
141
  return;
125
142
  }
@@ -140,8 +157,11 @@ exports.TalkingHeadVisualization = (0, react_1.forwardRef)(({ style, avatarUrl,
140
157
  lastScheduledVisemeKeyRef.current = null;
141
158
  }, [avatarUrl]);
142
159
  const handleError = (0, react_1.useCallback)((message) => {
143
- console.warn('[TalkingHeadVisualization] Avatar load failed, switching to fallback:', message);
160
+ console.warn('[TalkingHeadVisualization] Avatar error:', message);
144
161
  setAvatarError(message);
162
+ // Only fall back to local GLB for hard load failures, not morph-target warnings
163
+ if (message.includes('morph') || message.includes('No mesh'))
164
+ return;
145
165
  setUseFallback(true);
146
166
  }, []);
147
167
  // Effective avatar URL: use remote if available, fallback GLB if error or missing
@@ -169,7 +189,7 @@ exports.TalkingHeadVisualization = (0, react_1.forwardRef)(({ style, avatarUrl,
169
189
  ready: isAvatarReady,
170
190
  });
171
191
  const av = activeAvatar();
172
- if (!av || (!wgpuRef.current && !isAvatarReady)) {
192
+ if (!av?.scheduleVisemes || (!wgpuRef.current && !isAvatarReady)) {
173
193
  pendingVisemeScheduleRef.current = visemeSchedule;
174
194
  return;
175
195
  }
@@ -193,7 +213,7 @@ exports.TalkingHeadVisualization = (0, react_1.forwardRef)(({ style, avatarUrl,
193
213
  if (!effectiveAvatarUrl) {
194
214
  return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [style, styles.placeholder] }));
195
215
  }
196
- return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [style, styles.container], pointerEvents: "box-none", children: [(0, jsx_runtime_1.jsx)(TalkingHead_1.TalkingHead, { ref: avatarRef, avatarUrl: effectiveAvatarUrl, authToken: authToken, cameraView: cameraView, cameraDistance: cameraDistance, accessories: accessories, mood: mood, onLoadingChange: handleLoadingChange, onReady: handleReady, onError: handleError, onVoiceMood: onVoiceMood, style: react_native_1.StyleSheet.absoluteFill, vendorBaseUrl: vendorBaseUrl }), !isAvatarReady && ((0, jsx_runtime_1.jsx)(react_native_1.View, { testID: "talking-head-loading", style: styles.loadingOverlay, pointerEvents: "none", children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.loadingCard, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.loadingBadge, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingBadgeText, children: "AVATAR" }) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingTitle, children: avatarError ? 'Avatar failed to load' : getLoadingLabel(loadingState.stage) }), avatarError ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingHint, children: avatarError })) : typeof loadingState.progress === 'number' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.loadingPercent, children: [loadingState.progress, "%"] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.progressTrack, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flex: Math.max(6, loadingState.progress ?? 0), height: '100%', borderRadius: 999, backgroundColor: '#5eead4' } }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flex: 100 - Math.max(6, loadingState.progress ?? 0) } })] })] })) : ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingHint, children: "Preparing the avatar scene\u2026" }))] }) }))] }));
216
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [style, styles.container], pointerEvents: "box-none", children: [(0, jsx_runtime_1.jsx)(TalkingHead_1.TalkingHead, { ref: avatarRef, avatarUrl: effectiveAvatarUrl, authToken: authToken, cameraView: cameraView, cameraDistance: cameraDistance, accessories: accessories, mood: mood, onLoadingChange: handleLoadingChange, onReady: handleReady, onError: handleError, onVoiceMood: onVoiceMood, style: react_native_1.StyleSheet.absoluteFillObject, vendorBaseUrl: vendorBaseUrl }), !isAvatarReady && ((0, jsx_runtime_1.jsx)(react_native_1.View, { testID: "talking-head-loading", style: styles.loadingOverlay, pointerEvents: "none", children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.loadingCard, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.loadingBadge, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingBadgeText, children: "AVATAR" }) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingTitle, children: avatarError ? 'Avatar failed to load' : getLoadingLabel(loadingState.stage) }), avatarError ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingHint, children: avatarError })) : typeof loadingState.progress === 'number' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.loadingPercent, children: [loadingState.progress, "%"] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.progressTrack, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flex: Math.max(6, loadingState.progress ?? 0), height: '100%', borderRadius: 999, backgroundColor: '#5eead4' } }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flex: 100 - Math.max(6, loadingState.progress ?? 0) } })] })] })) : ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.loadingHint, children: "Preparing the avatar scene\u2026" }))] }) }))] }));
197
217
  });
198
218
  exports.TalkingHeadVisualization.displayName = 'TalkingHeadVisualization';
199
219
  const styles = react_native_1.StyleSheet.create({
@@ -9,7 +9,18 @@ export declare function getMyAvatars(): Promise<Avatar[]>;
9
9
  export declare function getAvatar(id: string): Promise<Avatar>;
10
10
  export declare function updateAvatar(id: string, data: AvatarUpdate): Promise<Avatar>;
11
11
  export declare function deleteAvatar(id: string): Promise<void>;
12
- export declare function createAvatar(fileUri: string, name: string, description?: string): Promise<Avatar>;
12
+ export interface CreateAvatarOptions {
13
+ /** Sketchfab model uid this avatar was imported from. */
14
+ sketchfabUid?: string;
15
+ /** Whether the GLB has morph targets (enables real lipsync). */
16
+ sketchfabHasMorphs?: boolean;
17
+ }
18
+ export declare function createAvatar(fileUri: string, name: string, description?: string, options?: CreateAvatarOptions): Promise<Avatar>;
19
+ /**
20
+ * Sketchfab uids that previously imported with working morph targets.
21
+ * Used to pin "verified" models to the top of search results.
22
+ */
23
+ export declare function getVerifiedSketchfabUids(): Promise<string[]>;
13
24
  export declare function getPublicAvatars(): Promise<PublicAvatar[]>;
14
25
  export declare function getVoiceProfileSamples(profileId: string): Promise<ProfileSample[]>;
15
26
  export declare function getVoiceProfiles(): Promise<VoiceProfile[]>;
@@ -1,21 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadVoiceSample = exports.createVoiceProfile = exports.assetFileUrl = exports.suggestPlacement = exports.deleteAsset = exports.uploadAsset = exports.getAsset = exports.listAssets = exports.thumbnailHeaders = exports.avatarAnimatedThumbnailUrl = exports.avatarThumbnailUrl = exports.avatarFileUrl = exports.removeDefaultVoice = exports.setDefaultVoice = exports.getVoiceProfiles = exports.getVoiceProfileSamples = exports.getPublicAvatars = exports.createAvatar = exports.deleteAvatar = exports.updateAvatar = exports.getAvatar = exports.getMyAvatars = exports.studioFetch = exports.getToken = exports.configureAvatarApi = void 0;
3
+ exports.configureAvatarApi = configureAvatarApi;
4
+ exports.getToken = getToken;
5
+ exports.studioFetch = studioFetch;
6
+ exports.getMyAvatars = getMyAvatars;
7
+ exports.getAvatar = getAvatar;
8
+ exports.updateAvatar = updateAvatar;
9
+ exports.deleteAvatar = deleteAvatar;
10
+ exports.createAvatar = createAvatar;
11
+ exports.getVerifiedSketchfabUids = getVerifiedSketchfabUids;
12
+ exports.getPublicAvatars = getPublicAvatars;
13
+ exports.getVoiceProfileSamples = getVoiceProfileSamples;
14
+ exports.getVoiceProfiles = getVoiceProfiles;
15
+ exports.setDefaultVoice = setDefaultVoice;
16
+ exports.removeDefaultVoice = removeDefaultVoice;
17
+ exports.avatarFileUrl = avatarFileUrl;
18
+ exports.avatarThumbnailUrl = avatarThumbnailUrl;
19
+ exports.avatarAnimatedThumbnailUrl = avatarAnimatedThumbnailUrl;
20
+ exports.thumbnailHeaders = thumbnailHeaders;
21
+ exports.listAssets = listAssets;
22
+ exports.getAsset = getAsset;
23
+ exports.uploadAsset = uploadAsset;
24
+ exports.deleteAsset = deleteAsset;
25
+ exports.suggestPlacement = suggestPlacement;
26
+ exports.assetFileUrl = assetFileUrl;
27
+ exports.createVoiceProfile = createVoiceProfile;
28
+ exports.uploadVoiceSample = uploadVoiceSample;
4
29
  // ---------------------------------------------------------------------------
5
30
  // Injectable configuration
6
31
  // ---------------------------------------------------------------------------
7
- let _baseUrl = process.env.EXPO_PUBLIC_BACKEND_URL ?? '';
32
+ let _baseUrl = typeof process !== 'undefined'
33
+ ? process.env.EXPO_PUBLIC_BACKEND_URL ?? ''
34
+ : '';
8
35
  let _getToken = null;
9
36
  function configureAvatarApi(opts) {
10
37
  if (opts.baseUrl !== undefined)
11
38
  _baseUrl = opts.baseUrl;
12
39
  _getToken = opts.getToken;
13
40
  }
14
- exports.configureAvatarApi = configureAvatarApi;
15
41
  async function getToken() {
16
42
  return _getToken ? _getToken() : null;
17
43
  }
18
- exports.getToken = getToken;
19
44
  // ---------------------------------------------------------------------------
20
45
  // Core fetch wrappers
21
46
  // ---------------------------------------------------------------------------
@@ -48,7 +73,6 @@ async function studioFetch(path, options) {
48
73
  }
49
74
  return res.json();
50
75
  }
51
- exports.studioFetch = studioFetch;
52
76
  /** POST/PUT with FormData, sharing auth token logic. */
53
77
  async function studioFetchForm(path, method, formData) {
54
78
  const token = await getToken();
@@ -76,11 +100,9 @@ async function studioFetchForm(path, method, formData) {
76
100
  function getMyAvatars() {
77
101
  return studioFetch('/v1/avatars');
78
102
  }
79
- exports.getMyAvatars = getMyAvatars;
80
103
  function getAvatar(id) {
81
104
  return studioFetch(`/v1/avatars/${id}`);
82
105
  }
83
- exports.getAvatar = getAvatar;
84
106
  function updateAvatar(id, data) {
85
107
  return studioFetch(`/v1/avatars/${id}`, {
86
108
  method: 'PATCH',
@@ -88,12 +110,10 @@ function updateAvatar(id, data) {
88
110
  body: JSON.stringify(data),
89
111
  });
90
112
  }
91
- exports.updateAvatar = updateAvatar;
92
113
  function deleteAvatar(id) {
93
114
  return studioFetch(`/v1/avatars/${id}`, { method: 'DELETE' });
94
115
  }
95
- exports.deleteAvatar = deleteAvatar;
96
- async function createAvatar(fileUri, name, description) {
116
+ async function createAvatar(fileUri, name, description, options) {
97
117
  const formData = new FormData();
98
118
  formData.append('file', {
99
119
  uri: fileUri,
@@ -104,24 +124,31 @@ async function createAvatar(fileUri, name, description) {
104
124
  if (description) {
105
125
  formData.append('description', description);
106
126
  }
127
+ if (options?.sketchfabUid) {
128
+ formData.append('sketchfab_uid', options.sketchfabUid);
129
+ formData.append('sketchfab_has_morphs', options.sketchfabHasMorphs ? 'true' : 'false');
130
+ }
107
131
  return studioFetchForm('/v1/avatars', 'POST', formData);
108
132
  }
109
- exports.createAvatar = createAvatar;
133
+ /**
134
+ * Sketchfab uids that previously imported with working morph targets.
135
+ * Used to pin "verified" models to the top of search results.
136
+ */
137
+ function getVerifiedSketchfabUids() {
138
+ return studioFetch('/v1/sketchfab/verified').then((r) => r.uids);
139
+ }
110
140
  function getPublicAvatars() {
111
141
  return studioFetch('/v1/avatars/public');
112
142
  }
113
- exports.getPublicAvatars = getPublicAvatars;
114
143
  // ---------------------------------------------------------------------------
115
144
  // Voice profile endpoints
116
145
  // ---------------------------------------------------------------------------
117
146
  function getVoiceProfileSamples(profileId) {
118
147
  return studioFetch(`/profiles/${profileId}/samples`);
119
148
  }
120
- exports.getVoiceProfileSamples = getVoiceProfileSamples;
121
149
  function getVoiceProfiles() {
122
150
  return studioFetch('/profiles');
123
151
  }
124
- exports.getVoiceProfiles = getVoiceProfiles;
125
152
  function setDefaultVoice(avatarId, profileId) {
126
153
  return studioFetch(`/v1/avatars/${avatarId}/default-voice`, {
127
154
  method: 'PUT',
@@ -129,13 +156,11 @@ function setDefaultVoice(avatarId, profileId) {
129
156
  body: JSON.stringify({ profile_id: profileId }),
130
157
  });
131
158
  }
132
- exports.setDefaultVoice = setDefaultVoice;
133
159
  function removeDefaultVoice(avatarId) {
134
160
  return studioFetch(`/v1/avatars/${avatarId}/default-voice`, {
135
161
  method: 'DELETE',
136
162
  });
137
163
  }
138
- exports.removeDefaultVoice = removeDefaultVoice;
139
164
  // ---------------------------------------------------------------------------
140
165
  // URL helpers
141
166
  // ---------------------------------------------------------------------------
@@ -143,27 +168,23 @@ function avatarFileUrl(avatar) {
143
168
  const base = `${_baseUrl}${avatar.url}`;
144
169
  return avatar.updated_at ? `${base}?v=${encodeURIComponent(avatar.updated_at)}` : base;
145
170
  }
146
- exports.avatarFileUrl = avatarFileUrl;
147
171
  function avatarThumbnailUrl(avatar) {
148
172
  const url = avatar.thumbnail_url || avatar.animated_thumbnail_url;
149
173
  if (!url)
150
174
  return null;
151
175
  return `${_baseUrl}${url}`;
152
176
  }
153
- exports.avatarThumbnailUrl = avatarThumbnailUrl;
154
177
  function avatarAnimatedThumbnailUrl(avatar) {
155
178
  if (!avatar.animated_thumbnail_url)
156
179
  return null;
157
180
  return `${_baseUrl}${avatar.animated_thumbnail_url}`;
158
181
  }
159
- exports.avatarAnimatedThumbnailUrl = avatarAnimatedThumbnailUrl;
160
182
  async function thumbnailHeaders() {
161
183
  const token = await getToken();
162
184
  if (!token)
163
185
  return {};
164
186
  return { Authorization: `Bearer ${token}` };
165
187
  }
166
- exports.thumbnailHeaders = thumbnailHeaders;
167
188
  // ---------------------------------------------------------------------------
168
189
  // Wearable Asset endpoints
169
190
  // ---------------------------------------------------------------------------
@@ -171,11 +192,9 @@ function listAssets(category) {
171
192
  const query = category ? `?category=${encodeURIComponent(category)}` : '';
172
193
  return studioFetch(`/v1/assets${query}`);
173
194
  }
174
- exports.listAssets = listAssets;
175
195
  function getAsset(id) {
176
196
  return studioFetch(`/v1/assets/${id}`);
177
197
  }
178
- exports.getAsset = getAsset;
179
198
  async function uploadAsset(fileUri, meta) {
180
199
  const formData = new FormData();
181
200
  formData.append('file', {
@@ -198,20 +217,16 @@ async function uploadAsset(fileUri, meta) {
198
217
  formData.append('hides_body_parts', JSON.stringify(meta.hides_body_parts));
199
218
  return studioFetchForm('/v1/assets', 'POST', formData);
200
219
  }
201
- exports.uploadAsset = uploadAsset;
202
220
  function deleteAsset(id) {
203
221
  return studioFetch(`/v1/assets/${id}`, { method: 'DELETE' });
204
222
  }
205
- exports.deleteAsset = deleteAsset;
206
223
  function suggestPlacement(assetId, avatarId) {
207
224
  return studioFetch(`/v1/assets/${assetId}/suggest-placement?avatar_id=${encodeURIComponent(avatarId)}`, { method: 'POST' });
208
225
  }
209
- exports.suggestPlacement = suggestPlacement;
210
226
  function assetFileUrl(asset) {
211
227
  const base = `${_baseUrl}${asset.url}`;
212
228
  return asset.updated_at ? `${base}?v=${encodeURIComponent(asset.updated_at)}` : base;
213
229
  }
214
- exports.assetFileUrl = assetFileUrl;
215
230
  async function createVoiceProfile(name, language = 'en') {
216
231
  return studioFetch('/profiles', {
217
232
  method: 'POST',
@@ -221,7 +236,6 @@ async function createVoiceProfile(name, language = 'en') {
221
236
  body: JSON.stringify({ name, language }),
222
237
  });
223
238
  }
224
- exports.createVoiceProfile = createVoiceProfile;
225
239
  async function uploadVoiceSample(profileId, fileUri, fileName, referenceText) {
226
240
  const formData = new FormData();
227
241
  formData.append('reference_text', referenceText);
@@ -233,4 +247,3 @@ async function uploadVoiceSample(profileId, fileUri, fileName, referenceText) {
233
247
  });
234
248
  return studioFetchForm(`/profiles/${profileId}/samples`, 'POST', formData);
235
249
  }
236
- exports.uploadVoiceSample = uploadVoiceSample;