talking-head-studio 0.4.9 → 0.4.11

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 (69) hide show
  1. package/README.md +227 -351
  2. package/dist/TalkingHead.d.ts +16 -25
  3. package/dist/TalkingHead.web.d.ts +6 -0
  4. package/dist/TalkingHead.web.js +18 -8
  5. package/dist/api/studioApi.js +25 -26
  6. package/dist/appearance/apply.js +2 -3
  7. package/dist/appearance/matchers.js +1 -2
  8. package/dist/appearance/schema.js +1 -2
  9. package/dist/core/avatar/backend.d.ts +130 -0
  10. package/dist/core/avatar/backend.js +4 -0
  11. package/dist/core/avatar/backends/gaussian.d.ts +49 -0
  12. package/dist/core/avatar/backends/gaussian.js +291 -0
  13. package/dist/core/avatar/backends/index.d.ts +3 -0
  14. package/dist/core/avatar/backends/index.js +7 -0
  15. package/dist/core/avatar/backends/morphTarget.d.ts +39 -0
  16. package/dist/core/avatar/backends/morphTarget.js +179 -0
  17. package/dist/core/avatar/faceControls.d.ts +40 -0
  18. package/dist/core/avatar/faceControls.js +138 -0
  19. package/dist/core/avatar/schema.d.ts +50 -0
  20. package/dist/core/avatar/schema.js +134 -0
  21. package/dist/core/avatar/visemes.d.ts +64 -0
  22. package/dist/core/avatar/visemes.js +72 -0
  23. package/dist/editor/AvatarCanvas.js +1 -2
  24. package/dist/editor/AvatarEditor.native.js +18 -9
  25. package/dist/editor/AvatarModel.js +1 -2
  26. package/dist/editor/FaceSqueezeEditor.js +19 -9
  27. package/dist/editor/FaceSqueezeEditor.web.js +2 -2
  28. package/dist/editor/RigidAccessory.js +1 -2
  29. package/dist/editor/SkinnedClothing.js +18 -9
  30. package/dist/editor/boneSnap.js +22 -12
  31. package/dist/editor/studioTheme.js +2 -2
  32. package/dist/html.js +1 -2
  33. package/dist/index.d.ts +15 -1
  34. package/dist/index.js +28 -5
  35. package/dist/platform/api/types.d.ts +10 -0
  36. package/dist/platform/api/types.js +2 -0
  37. package/dist/platform/marketplace/types.d.ts +32 -0
  38. package/dist/platform/marketplace/types.js +2 -0
  39. package/dist/platform/sdk/unity.d.ts +27 -0
  40. package/dist/platform/sdk/unity.js +2 -0
  41. package/dist/platform/sdk/unreal.d.ts +23 -0
  42. package/dist/platform/sdk/unreal.js +2 -0
  43. package/dist/platform/sdk/web.d.ts +16 -0
  44. package/dist/platform/sdk/web.js +2 -0
  45. package/dist/sketchfab/api.js +4 -5
  46. package/dist/sketchfab/useSketchfabSearch.js +1 -2
  47. package/dist/tts/useDirectVisemeStream.d.ts +2 -6
  48. package/dist/tts/useDirectVisemeStream.js +1 -2
  49. package/dist/tts/useMotionMarkers.d.ts +0 -1
  50. package/dist/tts/useMotionMarkers.js +1 -2
  51. package/dist/utils/avatarUtils.js +2 -3
  52. package/dist/utils/faceLandmarkerToShapeWeights.js +19 -10
  53. package/dist/voice/convertToWav.js +1 -2
  54. package/dist/voice/index.d.ts +3 -0
  55. package/dist/voice/index.js +6 -1
  56. package/dist/voice/useAudioPlayer.js +1 -2
  57. package/dist/voice/useAudioRecording.js +1 -2
  58. package/dist/voice/useFaceControls.d.ts +14 -0
  59. package/dist/voice/useFaceControls.js +81 -0
  60. package/dist/voice/useVoicePreview.d.ts +7 -0
  61. package/dist/voice/useVoicePreview.js +81 -0
  62. package/dist/wardrobe/index.d.ts +2 -0
  63. package/dist/wardrobe/index.js +3 -1
  64. package/dist/wardrobe/useAvatarWardrobeHydration.js +1 -2
  65. package/dist/wardrobe/useStudioAvatar.d.ts +29 -0
  66. package/dist/wardrobe/useStudioAvatar.js +177 -0
  67. package/dist/wgpu/WgpuAvatar.js +17 -7
  68. package/dist/wgpu/useAuthedModelUri.js +18 -9
  69. package/package.json +8 -4
@@ -15,15 +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
- exports.useBoneSnap = exports.snapPlacementToNearestBone = exports.getWorldPositionForPlacement = exports.getWorldPositionForBone = exports.findNearestBone = exports.HUMANOID_BONES = void 0;
36
+ exports.getWorldPositionForPlacement = exports.HUMANOID_BONES = void 0;
37
+ exports.findNearestBone = findNearestBone;
38
+ exports.getWorldPositionForBone = getWorldPositionForBone;
39
+ exports.snapPlacementToNearestBone = snapPlacementToNearestBone;
40
+ exports.useBoneSnap = useBoneSnap;
27
41
  const react_1 = require("react");
28
42
  const Haptics = __importStar(require("expo-haptics"));
29
43
  const THREE = __importStar(require("three"));
@@ -54,7 +68,6 @@ function findNearestBone(avatarScene, worldPos, maxDistance = 0.35) {
54
68
  });
55
69
  return best;
56
70
  }
57
- exports.findNearestBone = findNearestBone;
58
71
  /**
59
72
  * Returns the world-space position of a named bone in the avatar scene.
60
73
  * Returns the scene origin if the bone is not found.
@@ -68,13 +81,11 @@ function getWorldPositionForBone(avatarScene, boneName) {
68
81
  });
69
82
  return result;
70
83
  }
71
- exports.getWorldPositionForBone = getWorldPositionForBone;
72
84
  // Legacy alias kept for call-site compatibility
73
85
  exports.getWorldPositionForPlacement = getWorldPositionForBone;
74
86
  function snapPlacementToNearestBone(avatarScene, worldPos) {
75
87
  return findNearestBone(avatarScene, worldPos);
76
88
  }
77
- exports.snapPlacementToNearestBone = snapPlacementToNearestBone;
78
89
  /**
79
90
  * Hook that returns a snap function. Fires haptic feedback when a new bone
80
91
  * is snapped to.
@@ -90,4 +101,3 @@ function useBoneSnap(avatarScene) {
90
101
  return result;
91
102
  }, [avatarScene]);
92
103
  }
93
- exports.useBoneSnap = useBoneSnap;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.withAlpha = exports.studioTheme = void 0;
3
+ exports.studioTheme = void 0;
4
+ exports.withAlpha = withAlpha;
4
5
  exports.studioTheme = {
5
6
  colors: {
6
7
  background: '#111315',
@@ -86,4 +87,3 @@ function withAlpha(hexOrRgb, alphaHex) {
86
87
  }
87
88
  return hexOrRgb;
88
89
  }
89
- exports.withAlpha = withAlpha;
package/dist/html.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildAvatarHtml = void 0;
3
+ exports.buildAvatarHtml = buildAvatarHtml;
4
4
  const UPSTREAM_SAFE_MOOD_MAP = {
5
5
  neutral: 'neutral',
6
6
  happy: 'happy',
@@ -1179,4 +1179,3 @@ await init();
1179
1179
  </html>
1180
1180
  `;
1181
1181
  }
1182
- exports.buildAvatarHtml = buildAvatarHtml;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,17 @@
1
- export type { TalkingHeadAccessory, TalkingHeadLoadingState, TalkingHeadLoadingStage, TalkingHeadMood, TalkingHeadProps, TalkingHeadRef, TalkingHeadViseme, TalkingHeadVisemeCue, TalkingHeadVisemeSchedule, } from './TalkingHead';
1
+ export type { OculusViseme, ArkitBlendShape, AgentVisemePayload, VisemeCue } from './core/avatar/visemes';
2
+ export { OCULUS_VISEMES, ARKIT_TO_OCULUS, remapArkitToOculus, getArkitWeightsForViseme } from './core/avatar/visemes';
3
+ export type { FaceControl, ExpressionState, HeadPose, EyeGaze, Viseme } from './core/avatar/faceControls';
4
+ export { createNeutralExpression, visemeToExpression, applyVisemeToExpression } from './core/avatar/faceControls';
5
+ export type { AvatarBackend, CalibrationProfile, AvatarRenderTarget } from './core/avatar/backend';
6
+ export { MorphTargetBackend } from './core/avatar/backends';
7
+ export { GaussianBackend } from './core/avatar/backends';
8
+ export type { GaussianBackendConfig } from './core/avatar/backends';
9
+ export { walkAvatarSchema } from './core/avatar/schema';
10
+ export type { AvatarSchemaReport, MorphCoverage, SkeletonInfo, MeshStats, VísemeTier } from './core/avatar/schema';
2
11
  export { TalkingHead } from './TalkingHead';
12
+ export type { TalkingHeadRef, TalkingHeadProps, TalkingHeadMood, TalkingHeadAccessory, TalkingHeadLoadingState, TalkingHeadLoadingStage, TalkingHeadViseme, TalkingHeadVisemeCue, TalkingHeadVisemeSchedule, AvatarPlayerProps, AvatarPlayerRef, } from './TalkingHead';
13
+ export { TalkingHeadVisualization } from './TalkingHeadVisualization';
14
+ export type { TalkingHeadVisualizationRef } from './TalkingHeadVisualization';
3
15
  export { applyAppearanceToObject3D } from './appearance/apply';
4
16
  export { pickTargetForMaterialName } from './appearance/matchers';
5
17
  export { normalizeAppearance } from './appearance/schema';
@@ -9,3 +21,5 @@ export * from './wardrobe';
9
21
  export { useDirectVisemeStream } from './tts/useDirectVisemeStream';
10
22
  export type { VisemeStreamPayload } from './tts/useDirectVisemeStream';
11
23
  export { useMotionMarkers } from './tts/useMotionMarkers';
24
+ export { useFaceControlsFromVisemes } from './voice/useFaceControls';
25
+ export type { AvatarGlbParams, AvatarPlatformApi } from './platform/api/types';
package/dist/index.js CHANGED
@@ -14,20 +14,43 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.useMotionMarkers = exports.useDirectVisemeStream = exports.normalizeAppearance = exports.pickTargetForMaterialName = exports.applyAppearanceToObject3D = exports.TalkingHead = void 0;
17
+ exports.useFaceControlsFromVisemes = exports.useMotionMarkers = exports.useDirectVisemeStream = exports.normalizeAppearance = exports.pickTargetForMaterialName = exports.applyAppearanceToObject3D = exports.TalkingHeadVisualization = exports.TalkingHead = exports.walkAvatarSchema = exports.GaussianBackend = exports.MorphTargetBackend = exports.applyVisemeToExpression = exports.visemeToExpression = exports.createNeutralExpression = exports.getArkitWeightsForViseme = exports.remapArkitToOculus = exports.ARKIT_TO_OCULUS = exports.OCULUS_VISEMES = void 0;
18
+ var visemes_1 = require("./core/avatar/visemes");
19
+ Object.defineProperty(exports, "OCULUS_VISEMES", { enumerable: true, get: function () { return visemes_1.OCULUS_VISEMES; } });
20
+ Object.defineProperty(exports, "ARKIT_TO_OCULUS", { enumerable: true, get: function () { return visemes_1.ARKIT_TO_OCULUS; } });
21
+ Object.defineProperty(exports, "remapArkitToOculus", { enumerable: true, get: function () { return visemes_1.remapArkitToOculus; } });
22
+ Object.defineProperty(exports, "getArkitWeightsForViseme", { enumerable: true, get: function () { return visemes_1.getArkitWeightsForViseme; } });
23
+ var faceControls_1 = require("./core/avatar/faceControls");
24
+ Object.defineProperty(exports, "createNeutralExpression", { enumerable: true, get: function () { return faceControls_1.createNeutralExpression; } });
25
+ Object.defineProperty(exports, "visemeToExpression", { enumerable: true, get: function () { return faceControls_1.visemeToExpression; } });
26
+ Object.defineProperty(exports, "applyVisemeToExpression", { enumerable: true, get: function () { return faceControls_1.applyVisemeToExpression; } });
27
+ var backends_1 = require("./core/avatar/backends");
28
+ Object.defineProperty(exports, "MorphTargetBackend", { enumerable: true, get: function () { return backends_1.MorphTargetBackend; } });
29
+ var backends_2 = require("./core/avatar/backends");
30
+ Object.defineProperty(exports, "GaussianBackend", { enumerable: true, get: function () { return backends_2.GaussianBackend; } });
31
+ var schema_1 = require("./core/avatar/schema");
32
+ Object.defineProperty(exports, "walkAvatarSchema", { enumerable: true, get: function () { return schema_1.walkAvatarSchema; } });
33
+ // TalkingHead component + legacy type re-exports
18
34
  var TalkingHead_1 = require("./TalkingHead");
19
35
  Object.defineProperty(exports, "TalkingHead", { enumerable: true, get: function () { return TalkingHead_1.TalkingHead; } });
20
- // Export appearance utilities, but exclude AvatarAppearance — the canonical
21
- // AvatarAppearance (with equippedAccessories) comes from ./api below.
36
+ var TalkingHeadVisualization_1 = require("./TalkingHeadVisualization");
37
+ Object.defineProperty(exports, "TalkingHeadVisualization", { enumerable: true, get: function () { return TalkingHeadVisualization_1.TalkingHeadVisualization; } });
38
+ // Appearance
22
39
  var apply_1 = require("./appearance/apply");
23
40
  Object.defineProperty(exports, "applyAppearanceToObject3D", { enumerable: true, get: function () { return apply_1.applyAppearanceToObject3D; } });
24
41
  var matchers_1 = require("./appearance/matchers");
25
42
  Object.defineProperty(exports, "pickTargetForMaterialName", { enumerable: true, get: function () { return matchers_1.pickTargetForMaterialName; } });
26
- var schema_1 = require("./appearance/schema");
27
- Object.defineProperty(exports, "normalizeAppearance", { enumerable: true, get: function () { return schema_1.normalizeAppearance; } });
43
+ var schema_2 = require("./appearance/schema");
44
+ Object.defineProperty(exports, "normalizeAppearance", { enumerable: true, get: function () { return schema_2.normalizeAppearance; } });
45
+ // API
28
46
  __exportStar(require("./api"), exports);
47
+ // Wardrobe
29
48
  __exportStar(require("./wardrobe"), exports);
49
+ // TTS / Visemes
30
50
  var useDirectVisemeStream_1 = require("./tts/useDirectVisemeStream");
31
51
  Object.defineProperty(exports, "useDirectVisemeStream", { enumerable: true, get: function () { return useDirectVisemeStream_1.useDirectVisemeStream; } });
32
52
  var useMotionMarkers_1 = require("./tts/useMotionMarkers");
33
53
  Object.defineProperty(exports, "useMotionMarkers", { enumerable: true, get: function () { return useMotionMarkers_1.useMotionMarkers; } });
54
+ // Face controls hook
55
+ var useFaceControls_1 = require("./voice/useFaceControls");
56
+ Object.defineProperty(exports, "useFaceControlsFromVisemes", { enumerable: true, get: function () { return useFaceControls_1.useFaceControlsFromVisemes; } });
@@ -0,0 +1,10 @@
1
+ export interface AvatarGlbParams {
2
+ quality?: 'low' | 'medium' | 'high';
3
+ textureSize?: 256 | 512 | 1024 | 2048;
4
+ morphGroups?: ('oculus' | 'arkit' | 'expressions')[];
5
+ compression?: 'draco' | 'meshopt' | 'none';
6
+ textureFormat?: 'webp' | 'jpeg' | 'png';
7
+ }
8
+ export interface AvatarPlatformApi {
9
+ getAvatarUrl(id: string, params?: AvatarGlbParams): string;
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Rarity tiers for catalog items — determines availability and pricing.
3
+ */
4
+ export type RarityLevel = 'common' | 'uncommon' | 'rare' | 'legendary';
5
+ /**
6
+ * A single item in the avatar catalog (clothing, hair, accessory, etc.).
7
+ */
8
+ export interface CatalogItem {
9
+ id: string;
10
+ name: string;
11
+ /** Category tag, e.g. "hair", "outfit", "accessory", "body" */
12
+ category: string;
13
+ /** GLB asset URL */
14
+ glbUrl: string;
15
+ /** Preview image URL (2D thumbnail) */
16
+ thumbnailUrl?: string;
17
+ rarity: RarityLevel;
18
+ /** If set, item is only available to specific body types */
19
+ bodyTypeFilter?: string[];
20
+ /** Brand or collection tag */
21
+ collection?: string;
22
+ /** Price in platform credits; 0 = free */
23
+ price: number;
24
+ }
25
+ /**
26
+ * An avatar asset owned by a user (purchased or equipped).
27
+ */
28
+ export interface AvatarAsset {
29
+ catalogItemId: string;
30
+ /** When the user acquired this item */
31
+ acquiredAt: string;
32
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,27 @@
1
+ import type { AvatarGlbParams } from '../api/types';
2
+ /**
3
+ * Configuration passed to the Unity SDK AvatarLoader.
4
+ * Maps to the C# AvatarSDKConfig struct in the Unity plugin.
5
+ */
6
+ export interface UnitySdkConfig {
7
+ /** Base URL of the avatar platform API, e.g. "https://api.yourplatform.com" */
8
+ apiBaseUrl: string;
9
+ /** OAuth2 / API key for authenticated avatar downloads. */
10
+ apiKey?: string;
11
+ /** Default GLB quality params applied unless overridden per-avatar. */
12
+ defaultParams?: AvatarGlbParams;
13
+ }
14
+ /**
15
+ * Avatar metadata returned when a GLB is resolved for Unity.
16
+ * Mirrors the fields Unity's SkinnedMeshRenderer setup expects.
17
+ */
18
+ export interface UnityAvatarManifest {
19
+ id: string;
20
+ glbUrl: string;
21
+ /** Morph target names present in the GLB, ordered as they appear. */
22
+ morphTargets: string[];
23
+ /** Whether Oculus viseme morph targets are present. */
24
+ hasOculusVisemes: boolean;
25
+ /** Whether ARKit blendshape morph targets are present. */
26
+ hasArkit: boolean;
27
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,23 @@
1
+ import type { AvatarGlbParams } from '../api/types';
2
+ /**
3
+ * Configuration for the Unreal Engine plugin.
4
+ * Maps to the UE5 UAvatarSDKSettings data asset.
5
+ */
6
+ export interface UnrealSdkConfig {
7
+ /** Base URL of the avatar platform API. */
8
+ apiBaseUrl: string;
9
+ /** API key for authenticated requests. */
10
+ apiKey?: string;
11
+ /** Default GLB quality params. */
12
+ defaultParams?: AvatarGlbParams;
13
+ }
14
+ /**
15
+ * Blueprint-friendly avatar descriptor.
16
+ * Exposed as a UStruct in the Unreal plugin.
17
+ */
18
+ export interface UnrealAvatarDescriptor {
19
+ id: string;
20
+ glbUrl: string;
21
+ /** Morph target names for Blueprint binding. */
22
+ morphTargets: string[];
23
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,16 @@
1
+ import type { AvatarGlbParams } from '../api/types';
2
+ import type { AgentVisemePayload } from '../../core/avatar/visemes';
3
+ /**
4
+ * Minimal contract for the web SDK — fetch a GLB and drive morph targets.
5
+ * Implemented by any engine integration (Three.js, Babylon.js, etc.)
6
+ */
7
+ export interface AvatarWebSDK {
8
+ /** Build the GLB download URL for a given avatar id and quality params. */
9
+ getAvatarUrl(id: string, params?: AvatarGlbParams): string;
10
+ /** Schedule viseme morph-target animation from a TTS payload. */
11
+ scheduleVisemes(payload: AgentVisemePayload): void;
12
+ /** Drive jaw open/close from raw audio amplitude (0–1). */
13
+ sendAmplitude(amplitude: number): void;
14
+ /** Release GPU/memory resources. */
15
+ dispose(): void;
16
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getBestThumbnail = exports.downloadModel = exports.getDownloadUrl = exports.searchSketchfab = void 0;
3
+ exports.searchSketchfab = searchSketchfab;
4
+ exports.getDownloadUrl = getDownloadUrl;
5
+ exports.downloadModel = downloadModel;
6
+ exports.getBestThumbnail = getBestThumbnail;
4
7
  const SKETCHFAB_API_BASE = 'https://api.sketchfab.com/v3';
5
8
  const PAGE_SIZE = 24;
6
9
  async function searchSketchfab(options) {
@@ -23,7 +26,6 @@ async function searchSketchfab(options) {
23
26
  }
24
27
  return response.json();
25
28
  }
26
- exports.searchSketchfab = searchSketchfab;
27
29
  async function getDownloadUrl(uid, apiKey) {
28
30
  const response = await fetch(`${SKETCHFAB_API_BASE}/models/${uid}/download`, {
29
31
  headers: { Authorization: `Token ${apiKey}` },
@@ -34,7 +36,6 @@ async function getDownloadUrl(uid, apiKey) {
34
36
  const data = (await response.json());
35
37
  return data?.glb?.url ?? data?.gltf?.url ?? null;
36
38
  }
37
- exports.getDownloadUrl = getDownloadUrl;
38
39
  async function downloadModel(uid, name, apiKey) {
39
40
  const url = await getDownloadUrl(uid, apiKey);
40
41
  if (!url) {
@@ -48,7 +49,6 @@ async function downloadModel(uid, name, apiKey) {
48
49
  const cleanName = name.replace(/[^\w\s-]/g, '').trim() || 'model';
49
50
  return new File([blob], `${cleanName}.glb`, { type: 'model/gltf-binary' });
50
51
  }
51
- exports.downloadModel = downloadModel;
52
52
  function getBestThumbnail(thumbnails, targetWidth = 280) {
53
53
  if (!thumbnails?.images?.length) {
54
54
  return '';
@@ -56,4 +56,3 @@ function getBestThumbnail(thumbnails, targetWidth = 280) {
56
56
  const sorted = [...thumbnails.images].sort((left, right) => Math.abs(left.width - targetWidth) - Math.abs(right.width - targetWidth));
57
57
  return sorted[0].url;
58
58
  }
59
- exports.getBestThumbnail = getBestThumbnail;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useSketchfabSearch = void 0;
3
+ exports.useSketchfabSearch = useSketchfabSearch;
4
4
  const react_1 = require("react");
5
5
  const api_1 = require("./api");
6
6
  function useSketchfabSearch({ apiKey, initialQuery = 'character humanoid avatar', debounceMs = 400, }) {
@@ -79,4 +79,3 @@ function useSketchfabSearch({ apiKey, initialQuery = 'character humanoid avatar'
79
79
  prevPage,
80
80
  };
81
81
  }
82
- exports.useSketchfabSearch = useSketchfabSearch;
@@ -1,9 +1,5 @@
1
- import type { TalkingHeadVisemeSchedule } from "../TalkingHead";
2
- export type VisemeStreamPayload = {
3
- requestId?: string;
4
- durationMs?: number;
5
- cues?: TalkingHeadVisemeSchedule["cues"];
6
- };
1
+ import type { AgentVisemePayload } from "../core/avatar/visemes";
2
+ export type VisemeStreamPayload = AgentVisemePayload;
7
3
  type OpenStreamOptions = {
8
4
  requestId: string;
9
5
  ttsBaseUrl: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useDirectVisemeStream = void 0;
3
+ exports.useDirectVisemeStream = useDirectVisemeStream;
4
4
  const react_1 = require("react");
5
5
  const fetch_1 = require("expo/fetch");
6
6
  // How long to keep retrying a stream before giving up (ms).
@@ -100,7 +100,6 @@ function useDirectVisemeStream(onVisemes) {
100
100
  }, []);
101
101
  return { openStream };
102
102
  }
103
- exports.useDirectVisemeStream = useDirectVisemeStream;
104
103
  // ─── SSE parser ──────────────────────────────────────────────────────────────
105
104
  /**
106
105
  * Reads an SSE stream to completion, dispatching `event: visemes` messages.
@@ -1,3 +1,2 @@
1
- /// <reference types="react" />
2
1
  import type { TalkingHeadRef } from '../TalkingHead';
3
2
  export declare function useMotionMarkers(ref: React.RefObject<TalkingHeadRef | null>): (text: string) => string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useMotionMarkers = void 0;
3
+ exports.useMotionMarkers = useMotionMarkers;
4
4
  const react_1 = require("react");
5
5
  /**
6
6
  * Parses ::marker_name:: tokens out of LLM transcript text, fires the
@@ -22,4 +22,3 @@ function useMotionMarkers(ref) {
22
22
  return '';
23
23
  }), [ref]);
24
24
  }
25
- exports.useMotionMarkers = useMotionMarkers;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveLocalAssetUrl = exports.resolveLocalAssetUri = void 0;
3
+ exports.resolveLocalAssetUri = resolveLocalAssetUri;
4
+ exports.resolveLocalAssetUrl = resolveLocalAssetUrl;
4
5
  const expo_asset_1 = require("expo-asset");
5
6
  const react_native_1 = require("react-native");
6
7
  const expo_file_system_1 = require("expo-file-system");
@@ -20,7 +21,6 @@ async function resolveLocalAssetUri(module) {
20
21
  return null;
21
22
  }
22
23
  }
23
- exports.resolveLocalAssetUri = resolveLocalAssetUri;
24
24
  /**
25
25
  * Resolves a local Expo asset module (from require()) into a usable URL string.
26
26
  * On web, returns an absolute URL.
@@ -53,4 +53,3 @@ async function resolveLocalAssetUrl(module) {
53
53
  return null;
54
54
  }
55
55
  }
56
- exports.resolveLocalAssetUrl = resolveLocalAssetUrl;
@@ -28,15 +28,26 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
28
28
  }) : function(o, v) {
29
29
  o["default"] = v;
30
30
  });
31
- var __importStar = (this && this.__importStar) || function (mod) {
32
- if (mod && mod.__esModule) return mod;
33
- var result = {};
34
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
35
- __setModuleDefault(result, mod);
36
- return result;
37
- };
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
38
48
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.prefetchFaceLandmarker = exports.faceLandmarkerToShapeWeights = void 0;
49
+ exports.faceLandmarkerToShapeWeights = faceLandmarkerToShapeWeights;
50
+ exports.prefetchFaceLandmarker = prefetchFaceLandmarker;
40
51
  const MEDIAPIPE_CDN = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm';
41
52
  // ---------------------------------------------------------------------------
42
53
  // Singleton — built once, reused across calls
@@ -99,7 +110,6 @@ async function faceLandmarkerToShapeWeights(source, options = {}) {
99
110
  }
100
111
  return weights;
101
112
  }
102
- exports.faceLandmarkerToShapeWeights = faceLandmarkerToShapeWeights;
103
113
  /**
104
114
  * Load the FaceLandmarker WASM bundle eagerly — call this on app start
105
115
  * to avoid a cold-start delay when the user first uploads a photo.
@@ -109,4 +119,3 @@ function prefetchFaceLandmarker() {
109
119
  // Silently ignore prefetch failures — will retry on actual use
110
120
  });
111
121
  }
112
- exports.prefetchFaceLandmarker = prefetchFaceLandmarker;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.convertToWav = void 0;
3
+ exports.convertToWav = convertToWav;
4
4
  /**
5
5
  * Convert any audio blob to WAV format using the Web Audio API.
6
6
  * Handles WebM/opus output from MediaRecorder without requiring ffmpeg.
@@ -19,7 +19,6 @@ async function convertToWav(audioBlob) {
19
19
  await audioContext.close();
20
20
  }
21
21
  }
22
- exports.convertToWav = convertToWav;
23
22
  /**
24
23
  * Encode an AudioBuffer as a 16-bit PCM WAV blob.
25
24
  */
@@ -3,3 +3,6 @@ export type { UseAudioRecordingOptions, UseAudioRecordingReturn } from './useAud
3
3
  export { useAudioPlayer } from './useAudioPlayer';
4
4
  export type { UseAudioPlayerOptions, UseAudioPlayerReturn } from './useAudioPlayer';
5
5
  export { convertToWav } from './convertToWav';
6
+ export { useVoicePreview, configureVoicePreviewBaseUrl } from './useVoicePreview';
7
+ export { useFaceControlsFromVisemes } from './useFaceControls';
8
+ export type { FaceControl } from '../core/avatar/faceControls';
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.convertToWav = exports.useAudioPlayer = exports.useAudioRecording = void 0;
3
+ exports.useFaceControlsFromVisemes = exports.configureVoicePreviewBaseUrl = exports.useVoicePreview = exports.convertToWav = exports.useAudioPlayer = exports.useAudioRecording = void 0;
4
4
  var useAudioRecording_1 = require("./useAudioRecording");
5
5
  Object.defineProperty(exports, "useAudioRecording", { enumerable: true, get: function () { return useAudioRecording_1.useAudioRecording; } });
6
6
  var useAudioPlayer_1 = require("./useAudioPlayer");
7
7
  Object.defineProperty(exports, "useAudioPlayer", { enumerable: true, get: function () { return useAudioPlayer_1.useAudioPlayer; } });
8
8
  var convertToWav_1 = require("./convertToWav");
9
9
  Object.defineProperty(exports, "convertToWav", { enumerable: true, get: function () { return convertToWav_1.convertToWav; } });
10
+ var useVoicePreview_1 = require("./useVoicePreview");
11
+ Object.defineProperty(exports, "useVoicePreview", { enumerable: true, get: function () { return useVoicePreview_1.useVoicePreview; } });
12
+ Object.defineProperty(exports, "configureVoicePreviewBaseUrl", { enumerable: true, get: function () { return useVoicePreview_1.configureVoicePreviewBaseUrl; } });
13
+ var useFaceControls_1 = require("./useFaceControls");
14
+ Object.defineProperty(exports, "useFaceControlsFromVisemes", { enumerable: true, get: function () { return useFaceControls_1.useFaceControlsFromVisemes; } });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useAudioPlayer = void 0;
3
+ exports.useAudioPlayer = useAudioPlayer;
4
4
  const react_1 = require("react");
5
5
  function useAudioPlayer({ onError, } = {}) {
6
6
  const [isPlaying, setIsPlaying] = (0, react_1.useState)(false);
@@ -62,4 +62,3 @@ function useAudioPlayer({ onError, } = {}) {
62
62
  cleanup,
63
63
  };
64
64
  }
65
- exports.useAudioPlayer = useAudioPlayer;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useAudioRecording = void 0;
3
+ exports.useAudioRecording = useAudioRecording;
4
4
  const react_1 = require("react");
5
5
  const convertToWav_1 = require("./convertToWav");
6
6
  function useAudioRecording({ maxDurationSeconds = 29, onRecordingComplete, } = {}) {
@@ -163,4 +163,3 @@ function useAudioRecording({ maxDurationSeconds = 29, onRecordingComplete, } = {
163
163
  cancelRecording,
164
164
  };
165
165
  }
166
- exports.useAudioRecording = useAudioRecording;
@@ -0,0 +1,14 @@
1
+ import type { TalkingHeadVisemeSchedule } from '../TalkingHead';
2
+ import { type FaceControl } from '../core/avatar/faceControls';
3
+ /**
4
+ * useFaceControlsFromVisemes
5
+ *
6
+ * Given a TalkingHead viseme schedule (Rhubarb shape cues + startedAtMs),
7
+ * produce a time-varying FaceControl object (neutral head pose + ExpressionState).
8
+ *
9
+ * This hook is intentionally simple: it drives expression purely from the
10
+ * viseme cues and keeps head pose neutral (0 yaw/pitch/roll). Downstream
11
+ * backends can layer motion / camera moves on top.
12
+ */
13
+ export declare function useFaceControlsFromVisemes(schedule: TalkingHeadVisemeSchedule | null): FaceControl;
14
+ export type { FaceControl } from '../core/avatar/faceControls';
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ // src/voice/useFaceControls.ts
3
+ // Derive a canonical FaceControl (pose + expression) from a TalkingHead viseme schedule.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.useFaceControlsFromVisemes = useFaceControlsFromVisemes;
6
+ const react_1 = require("react");
7
+ const morphTables_1 = require("../wgpu/morphTables");
8
+ const faceControls_1 = require("../core/avatar/faceControls");
9
+ function sampleExpressionAtTime(schedule, nowMs) {
10
+ const neutral = (0, faceControls_1.createNeutralExpression)();
11
+ if (!schedule.cues || schedule.cues.length === 0)
12
+ return neutral;
13
+ const startedAt = schedule.startedAtMs ?? nowMs;
14
+ const t = Math.max(0, nowMs - startedAt);
15
+ const cues = schedule.cues.filter((cue) => t >= cue.startMs && t <= cue.endMs);
16
+ if (cues.length === 0)
17
+ return neutral;
18
+ let expr = neutral;
19
+ for (const cue of cues) {
20
+ const rhubarbKey = cue.viseme;
21
+ const oculusViseme = (morphTables_1.RHUBARB_TO_VISEME[rhubarbKey] ?? 'sil');
22
+ const w = typeof cue.weight === 'number' ? cue.weight : 1;
23
+ expr = (0, faceControls_1.applyVisemeToExpression)(expr, oculusViseme, w);
24
+ }
25
+ return expr;
26
+ }
27
+ /**
28
+ * useFaceControlsFromVisemes
29
+ *
30
+ * Given a TalkingHead viseme schedule (Rhubarb shape cues + startedAtMs),
31
+ * produce a time-varying FaceControl object (neutral head pose + ExpressionState).
32
+ *
33
+ * This hook is intentionally simple: it drives expression purely from the
34
+ * viseme cues and keeps head pose neutral (0 yaw/pitch/roll). Downstream
35
+ * backends can layer motion / camera moves on top.
36
+ */
37
+ function useFaceControlsFromVisemes(schedule) {
38
+ const [expr, setExpr] = (0, react_1.useState)(() => (0, faceControls_1.createNeutralExpression)());
39
+ const rafRef = (0, react_1.useRef)(null);
40
+ const scheduleRef = (0, react_1.useRef)(schedule);
41
+ (0, react_1.useEffect)(() => {
42
+ scheduleRef.current = schedule;
43
+ }, [schedule]);
44
+ (0, react_1.useEffect)(() => {
45
+ if (rafRef.current != null) {
46
+ cancelAnimationFrame(rafRef.current);
47
+ rafRef.current = null;
48
+ }
49
+ if (!schedule || !schedule.cues || schedule.cues.length === 0) {
50
+ setExpr((0, faceControls_1.createNeutralExpression)());
51
+ return;
52
+ }
53
+ const lastEnd = Math.max(...schedule.cues.map((c) => c.endMs ?? 0));
54
+ const loop = () => {
55
+ const s = scheduleRef.current;
56
+ if (!s || !s.cues || s.cues.length === 0) {
57
+ setExpr((0, faceControls_1.createNeutralExpression)());
58
+ return;
59
+ }
60
+ const now = Date.now();
61
+ const nextExpr = sampleExpressionAtTime(s, now);
62
+ setExpr(nextExpr);
63
+ const startedAt = s.startedAtMs ?? now;
64
+ const t = Math.max(0, now - startedAt);
65
+ if (t <= lastEnd + 200) {
66
+ rafRef.current = requestAnimationFrame(loop);
67
+ }
68
+ };
69
+ rafRef.current = requestAnimationFrame(loop);
70
+ return () => {
71
+ if (rafRef.current != null) {
72
+ cancelAnimationFrame(rafRef.current);
73
+ rafRef.current = null;
74
+ }
75
+ };
76
+ }, [schedule]);
77
+ return {
78
+ pose: { yaw: 0, pitch: 0, roll: 0 },
79
+ expr,
80
+ };
81
+ }