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.
- package/README.md +299 -337
- package/dist/TalkingHead.d.ts +44 -28
- package/dist/TalkingHead.js +21 -2
- package/dist/TalkingHead.web.d.ts +37 -4
- package/dist/TalkingHead.web.js +28 -8
- package/dist/TalkingHeadVisualization.d.ts +22 -0
- package/dist/TalkingHeadVisualization.js +30 -10
- package/dist/api/studioApi.d.ts +12 -1
- package/dist/api/studioApi.js +41 -28
- package/dist/appearance/apply.js +2 -3
- package/dist/appearance/matchers.js +1 -2
- package/dist/appearance/schema.js +1 -2
- package/dist/contract.d.ts +14 -0
- package/dist/contract.js +30 -0
- package/dist/core/avatar/avatarCapabilities.d.ts +60 -0
- package/dist/core/avatar/avatarCapabilities.js +100 -0
- package/dist/core/avatar/backend.d.ts +130 -0
- package/dist/core/avatar/backend.js +4 -0
- package/dist/core/avatar/backends/gaussian.d.ts +49 -0
- package/dist/core/avatar/backends/gaussian.js +293 -0
- package/dist/core/avatar/backends/index.d.ts +3 -0
- package/dist/core/avatar/backends/index.js +7 -0
- package/dist/core/avatar/backends/morphTarget.d.ts +39 -0
- package/dist/core/avatar/backends/morphTarget.js +179 -0
- package/dist/core/avatar/faceControls.d.ts +40 -0
- package/dist/core/avatar/faceControls.js +138 -0
- package/dist/core/avatar/motion.d.ts +1713 -0
- package/dist/core/avatar/motion.js +550 -0
- package/dist/core/avatar/motionRuntime.d.ts +46 -0
- package/dist/core/avatar/motionRuntime.js +84 -0
- package/dist/core/avatar/schema.d.ts +78 -0
- package/dist/core/avatar/schema.js +134 -0
- package/dist/core/avatar/visemes.d.ts +47 -1
- package/dist/core/avatar/visemes.js +114 -1
- package/dist/editor/AvatarCanvas.js +93 -3
- package/dist/editor/AvatarEditor.native.js +19 -9
- package/dist/editor/AvatarModel.js +2 -2
- package/dist/editor/FaceSqueezeEditor.d.ts +3 -1
- package/dist/editor/FaceSqueezeEditor.js +195 -121
- package/dist/editor/FaceSqueezeEditor.web.d.ts +3 -1
- package/dist/editor/FaceSqueezeEditor.web.js +32 -30
- package/dist/editor/RigidAccessory.js +18 -4
- package/dist/editor/SkinnedClothing.js +19 -9
- package/dist/editor/boneLockedDrag.d.ts +11 -0
- package/dist/editor/boneLockedDrag.js +68 -0
- package/dist/editor/boneSnap.js +22 -12
- package/dist/editor/boneSnap.web.d.ts +27 -0
- package/dist/editor/boneSnap.web.js +99 -0
- package/dist/editor/index.web.d.ts +10 -0
- package/dist/editor/index.web.js +26 -0
- package/dist/editor/sounds/haha.wav +0 -0
- package/dist/editor/sounds/owie.wav +0 -0
- package/dist/editor/sounds/stop.wav +0 -0
- package/dist/editor/studioTheme.d.ts +14 -14
- package/dist/editor/studioTheme.js +19 -16
- package/dist/editor/types.d.ts +1 -0
- package/dist/html/accessories.d.ts +7 -0
- package/dist/html/accessories.js +149 -0
- package/dist/html/motion.d.ts +1 -0
- package/dist/html/motion.js +189 -0
- package/dist/html/visemes.d.ts +7 -0
- package/dist/html/visemes.js +348 -0
- package/dist/html.d.ts +1 -1
- package/dist/html.js +56 -734
- package/dist/index.d.ts +19 -1
- package/dist/index.js +44 -5
- package/dist/index.web.d.ts +18 -1
- package/dist/index.web.js +36 -3
- package/dist/platform/api/types.d.ts +10 -0
- package/dist/platform/api/types.js +2 -0
- package/dist/platform/marketplace/types.d.ts +32 -0
- package/dist/platform/marketplace/types.js +2 -0
- package/dist/platform/sdk/unity.d.ts +27 -0
- package/dist/platform/sdk/unity.js +2 -0
- package/dist/platform/sdk/unreal.d.ts +23 -0
- package/dist/platform/sdk/unreal.js +2 -0
- package/dist/platform/sdk/web.d.ts +16 -0
- package/dist/platform/sdk/web.js +2 -0
- package/dist/sketchfab/api.js +5 -5
- package/dist/sketchfab/glbInspect.d.ts +22 -0
- package/dist/sketchfab/glbInspect.js +58 -0
- package/dist/sketchfab/index.d.ts +3 -0
- package/dist/sketchfab/index.js +8 -1
- package/dist/sketchfab/inspectRemote.d.ts +13 -0
- package/dist/sketchfab/inspectRemote.js +77 -0
- package/dist/sketchfab/types.d.ts +10 -0
- package/dist/sketchfab/useSketchfabSearch.js +1 -2
- package/dist/studio/AccessoryBrowserScreen.d.ts +6 -0
- package/dist/studio/AccessoryBrowserScreen.js +626 -0
- package/dist/studio/AccessoryPanel.d.ts +10 -0
- package/dist/studio/AccessoryPanel.js +396 -0
- package/dist/studio/AppearancePanel.d.ts +9 -0
- package/dist/studio/AppearancePanel.js +77 -0
- package/dist/studio/AvatarCreatorScreen.d.ts +5 -0
- package/dist/studio/AvatarCreatorScreen.js +806 -0
- package/dist/studio/AvatarEditorScreen.d.ts +14 -0
- package/dist/studio/AvatarEditorScreen.js +510 -0
- package/dist/studio/AvatarGrid.d.ts +23 -0
- package/dist/studio/AvatarGrid.js +257 -0
- package/dist/studio/ColorSwatch.d.ts +8 -0
- package/dist/studio/ColorSwatch.js +100 -0
- package/dist/studio/CreateVoiceProfileSheet.d.ts +8 -0
- package/dist/studio/CreateVoiceProfileSheet.js +242 -0
- package/dist/studio/DetailsPanel.d.ts +15 -0
- package/dist/studio/DetailsPanel.js +239 -0
- package/dist/studio/FilamentEditor.d.ts +2 -0
- package/dist/studio/FilamentEditor.js +6 -0
- package/dist/studio/PrecisionPanel.d.ts +2 -0
- package/dist/studio/PrecisionPanel.js +7 -0
- package/dist/studio/PublicGalleryScreen.d.ts +5 -0
- package/dist/studio/PublicGalleryScreen.js +358 -0
- package/dist/studio/SketchfabModelCard.d.ts +20 -0
- package/dist/studio/SketchfabModelCard.js +104 -0
- package/dist/studio/StudioBrowseHeader.d.ts +9 -0
- package/dist/studio/StudioBrowseHeader.js +28 -0
- package/dist/studio/StudioEmptyState.d.ts +8 -0
- package/dist/studio/StudioEmptyState.js +29 -0
- package/dist/studio/StudioFloatingAction.d.ts +13 -0
- package/dist/studio/StudioFloatingAction.js +42 -0
- package/dist/studio/StudioSectionHeader.d.ts +7 -0
- package/dist/studio/StudioSectionHeader.js +27 -0
- package/dist/studio/StudioSurfaceCard.d.ts +8 -0
- package/dist/studio/StudioSurfaceCard.js +20 -0
- package/dist/studio/VoicePanel.d.ts +15 -0
- package/dist/studio/VoicePanel.js +305 -0
- package/dist/studio/constants.d.ts +3 -0
- package/dist/studio/constants.js +6 -0
- package/dist/studio/index.d.ts +29 -0
- package/dist/studio/index.js +54 -0
- package/dist/studio/useSketchfabCapabilities.d.ts +31 -0
- package/dist/studio/useSketchfabCapabilities.js +82 -0
- package/dist/tts/useDirectVisemeStream.d.ts +2 -6
- package/dist/tts/useDirectVisemeStream.js +16 -12
- package/dist/tts/useMotionMarkers.d.ts +0 -1
- package/dist/tts/useMotionMarkers.js +1 -2
- package/dist/utils/avatarUtils.js +94 -8
- package/dist/utils/faceLandmarkerToShapeWeights.js +21 -14
- package/dist/voice/convertToWav.js +1 -2
- package/dist/voice/index.d.ts +3 -0
- package/dist/voice/index.js +6 -1
- package/dist/voice/useAudioPlayer.js +18 -6
- package/dist/voice/useAudioRecording.js +1 -2
- package/dist/voice/useFaceControls.d.ts +14 -0
- package/dist/voice/useFaceControls.js +81 -0
- package/dist/voice/useVoicePreview.d.ts +7 -0
- package/dist/voice/useVoicePreview.js +83 -0
- package/dist/wardrobe/index.d.ts +3 -0
- package/dist/wardrobe/index.js +8 -1
- package/dist/wardrobe/useAccessoryGestures.d.ts +20 -0
- package/dist/wardrobe/useAccessoryGestures.js +94 -0
- package/dist/wardrobe/useAvatarWardrobeHydration.js +9 -4
- package/dist/wardrobe/useStudioAvatar.d.ts +29 -0
- package/dist/wardrobe/useStudioAvatar.js +186 -0
- package/dist/wardrobe/wardrobeStore.d.ts +2 -0
- package/dist/wardrobe/wardrobeStore.js +12 -2
- package/dist/wgpu/R3FWebGpuCanvas.d.ts +15 -0
- package/dist/wgpu/R3FWebGpuCanvas.js +176 -0
- package/dist/wgpu/WgpuAvatar.d.ts +26 -2
- package/dist/wgpu/WgpuAvatar.js +313 -46
- package/dist/wgpu/accessoryDefaults.d.ts +12 -0
- package/dist/wgpu/accessoryDefaults.js +19 -0
- package/dist/wgpu/blobShim.d.ts +2 -0
- package/dist/wgpu/blobShim.js +191 -0
- package/dist/wgpu/index.d.ts +1 -0
- package/dist/wgpu/index.js +4 -1
- package/dist/wgpu/loadGLTFFromUri.d.ts +2 -0
- package/dist/wgpu/loadGLTFFromUri.js +75 -0
- package/dist/wgpu/morphTables.js +21 -10
- package/dist/wgpu/motionState.d.ts +20 -0
- package/dist/wgpu/motionState.js +31 -0
- package/dist/wgpu/patchThreeForRN.d.ts +28 -0
- package/dist/wgpu/patchThreeForRN.js +292 -0
- package/dist/wgpu/scenePlacement.d.ts +5 -0
- package/dist/wgpu/scenePlacement.js +50 -0
- package/dist/wgpu/useAuthedModelUri.js +22 -11
- package/dist/wgpu/useNativeGLTF.d.ts +7 -0
- package/dist/wgpu/useNativeGLTF.js +36 -0
- package/package.json +102 -32
package/dist/TalkingHead.d.ts
CHANGED
|
@@ -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 =
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
/**
|
|
104
|
-
dispatchMotion
|
|
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>>;
|
package/dist/TalkingHead.js
CHANGED
|
@@ -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: () =>
|
|
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
|
-
|
|
4
|
-
export type
|
|
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
|
-
|
|
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>>;
|
package/dist/TalkingHead.web.js
CHANGED
|
@@ -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 (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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) =>
|
|
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
|
|
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.
|
|
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({
|
package/dist/api/studioApi.d.ts
CHANGED
|
@@ -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
|
|
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[]>;
|
package/dist/api/studioApi.js
CHANGED
|
@@ -1,21 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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;
|