talking-head-studio 0.2.9 → 0.3.1

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 (35) hide show
  1. package/dist/TalkingHead.js +30 -2
  2. package/dist/TalkingHeadVisualization.d.ts +35 -0
  3. package/dist/TalkingHeadVisualization.js +277 -0
  4. package/dist/api/index.d.ts +2 -0
  5. package/dist/api/index.js +18 -0
  6. package/dist/api/studioApi.d.ts +38 -0
  7. package/dist/api/studioApi.js +235 -0
  8. package/dist/api/types.d.ts +87 -0
  9. package/dist/api/types.js +5 -0
  10. package/dist/assets/face-squeeze-local.glb +0 -0
  11. package/dist/filament/FilamentAvatar.d.ts +41 -0
  12. package/dist/filament/FilamentAvatar.js +737 -0
  13. package/dist/filament/faceSqueezeAssets.d.ts +1 -0
  14. package/dist/filament/faceSqueezeAssets.js +5 -0
  15. package/dist/filament/index.d.ts +6 -0
  16. package/dist/filament/index.js +24 -0
  17. package/dist/filament/morphTables.d.ts +5 -0
  18. package/dist/filament/morphTables.js +93 -0
  19. package/dist/filament/useAuthedFilamentUri.d.ts +11 -0
  20. package/dist/filament/useAuthedFilamentUri.js +126 -0
  21. package/dist/index.d.ts +10 -1
  22. package/dist/index.js +15 -2
  23. package/dist/index.web.d.ts +5 -1
  24. package/dist/index.web.js +10 -2
  25. package/dist/tts/useDirectVisemeStream.d.ts +21 -0
  26. package/dist/tts/useDirectVisemeStream.js +119 -0
  27. package/dist/utils/avatarUtils.d.ts +13 -0
  28. package/dist/utils/avatarUtils.js +56 -0
  29. package/dist/wardrobe/index.d.ts +2 -0
  30. package/dist/wardrobe/index.js +20 -0
  31. package/dist/wardrobe/useAvatarWardrobeHydration.d.ts +7 -0
  32. package/dist/wardrobe/useAvatarWardrobeHydration.js +34 -0
  33. package/dist/wardrobe/wardrobeStore.d.ts +30 -0
  34. package/dist/wardrobe/wardrobeStore.js +106 -0
  35. package/package.json +34 -2
@@ -0,0 +1,30 @@
1
+ import type { WearableAsset, EquippedAccessory } from '../api/types';
2
+ export interface AssetPlacement {
3
+ bone: string;
4
+ position: [number, number, number];
5
+ rotation: [number, number, number];
6
+ scale: number;
7
+ }
8
+ export type GizmoMode = 'translate' | 'rotate' | 'scale';
9
+ export interface WardrobeState {
10
+ /** slot -> equipped asset */
11
+ equipped: Record<string, WearableAsset>;
12
+ /** assetId -> placement transform */
13
+ placements: Record<string, AssetPlacement>;
14
+ /** asset currently being positioned (Filament mode) */
15
+ activeAssetId: string | null;
16
+ /** current gizmo operation mode */
17
+ gizmoMode: GizmoMode;
18
+ /** ID of the avatar currently synced to avoid redundant fetches */
19
+ syncedAvatarId: string | null;
20
+ equip: (slot: string, asset: WearableAsset) => void;
21
+ unequip: (slot: string) => void;
22
+ setPlacement: (assetId: string, placement: AssetPlacement) => void;
23
+ setActiveAsset: (assetId: string | null) => void;
24
+ setGizmoMode: (mode: GizmoMode) => void;
25
+ hydrateFromApi: (accessories: EquippedAccessory[], assetLookup: Record<string, WearableAsset>) => void;
26
+ syncWithAvatar: (avatarUrl: string) => Promise<void>;
27
+ serializeForApi: () => EquippedAccessory[];
28
+ reset: () => void;
29
+ }
30
+ export declare const useWardrobeStore: import("zustand").UseBoundStore<import("zustand").StoreApi<WardrobeState>>;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useWardrobeStore = void 0;
4
+ const zustand_1 = require("zustand");
5
+ const studioApi_1 = require("../api/studioApi");
6
+ // ---------------------------------------------------------------------------
7
+ // Inline avatar ID extraction (avoids dep on avatarUtils)
8
+ // ---------------------------------------------------------------------------
9
+ const AVATAR_PATH_RE = /\/v1\/avatars\/([^/]+)\/file/;
10
+ function extractAvatarIdFromUrl(url) {
11
+ if (!url)
12
+ return null;
13
+ const match = url.match(AVATAR_PATH_RE);
14
+ return match ? match[1] : null;
15
+ }
16
+ // ---------------------------------------------------------------------------
17
+ // Store
18
+ // ---------------------------------------------------------------------------
19
+ exports.useWardrobeStore = (0, zustand_1.create)()((set, get) => ({
20
+ equipped: {},
21
+ placements: {},
22
+ activeAssetId: null,
23
+ gizmoMode: 'translate',
24
+ syncedAvatarId: null,
25
+ equip: (slot, asset) => set((state) => ({
26
+ equipped: { ...state.equipped, [slot]: asset },
27
+ })),
28
+ unequip: (slot) => set((state) => {
29
+ const removed = state.equipped[slot];
30
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
31
+ const { [slot]: _removed, ...restEquipped } = state.equipped;
32
+ // Clean up placement data for the removed asset
33
+ const placements = { ...state.placements };
34
+ if (removed) {
35
+ delete placements[removed.id];
36
+ }
37
+ return {
38
+ equipped: restEquipped,
39
+ placements,
40
+ activeAssetId: removed && removed.id === state.activeAssetId
41
+ ? null
42
+ : state.activeAssetId,
43
+ };
44
+ }),
45
+ setPlacement: (assetId, placement) => set((state) => ({
46
+ placements: { ...state.placements, [assetId]: placement },
47
+ })),
48
+ setActiveAsset: (assetId) => set({ activeAssetId: assetId }),
49
+ setGizmoMode: (mode) => set({ gizmoMode: mode }),
50
+ hydrateFromApi: (accessories, assetLookup) => set(() => {
51
+ const equipped = {};
52
+ const placements = {};
53
+ for (const acc of accessories) {
54
+ const asset = assetLookup[acc.asset_id];
55
+ if (asset) {
56
+ equipped[asset.slot] = asset;
57
+ placements[acc.asset_id] = {
58
+ bone: acc.bone,
59
+ position: acc.position,
60
+ rotation: acc.rotation,
61
+ scale: acc.scale,
62
+ };
63
+ }
64
+ }
65
+ return { equipped, placements };
66
+ }),
67
+ syncWithAvatar: async (avatarUrl) => {
68
+ const avatarId = extractAvatarIdFromUrl(avatarUrl);
69
+ if (!avatarId)
70
+ return;
71
+ if (get().syncedAvatarId === avatarId)
72
+ return;
73
+ get().reset();
74
+ try {
75
+ const [avatar, allAssets] = await Promise.all([(0, studioApi_1.getAvatar)(avatarId), (0, studioApi_1.listAssets)()]);
76
+ const lookup = {};
77
+ for (const a of allAssets)
78
+ lookup[a.id] = a;
79
+ const accessories = avatar.appearance?.equippedAccessories ?? [];
80
+ get().hydrateFromApi(accessories, lookup);
81
+ set({ syncedAvatarId: avatarId });
82
+ }
83
+ catch (e) {
84
+ console.warn('[WardrobeStore] Failed to load avatar accessories:', e);
85
+ }
86
+ },
87
+ serializeForApi: () => {
88
+ const { equipped, placements } = get();
89
+ return Object.values(equipped).map((asset) => {
90
+ const p = placements[asset.id];
91
+ return {
92
+ asset_id: asset.id,
93
+ bone: p?.bone ?? 'Head',
94
+ position: p?.position ?? [0, 0, 0],
95
+ rotation: p?.rotation ?? [0, 0, 0],
96
+ scale: p?.scale ?? 1,
97
+ };
98
+ });
99
+ },
100
+ reset: () => set({
101
+ equipped: {},
102
+ placements: {},
103
+ activeAssetId: null,
104
+ gizmoMode: 'translate',
105
+ }),
106
+ }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talking-head-studio",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "Cross-platform 3D avatar component for React Native & web — lip-sync, gestures, accessories, and LLM integration. Powered by TalkingHead + Three.js.",
5
5
  "main": "dist/index.web.js",
6
6
  "browser": "dist/index.web.js",
@@ -28,6 +28,19 @@
28
28
  "./sketchfab": {
29
29
  "types": "./dist/sketchfab/index.d.ts",
30
30
  "default": "./dist/sketchfab/index.js"
31
+ },
32
+ "./api": {
33
+ "types": "./dist/api/index.d.ts",
34
+ "default": "./dist/api/index.js"
35
+ },
36
+ "./wardrobe": {
37
+ "types": "./dist/wardrobe/index.d.ts",
38
+ "default": "./dist/wardrobe/index.js"
39
+ },
40
+ "./filament": {
41
+ "react-native": "./dist/filament/index.js",
42
+ "types": "./dist/filament/index.d.ts",
43
+ "default": "./dist/filament/index.js"
31
44
  }
32
45
  },
33
46
  "files": [
@@ -35,7 +48,7 @@
35
48
  ],
36
49
  "scripts": {
37
50
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
38
- "build": "npm run clean && tsc --project tsconfig.json",
51
+ "build": "npm run clean && tsc --project tsconfig.json && mkdir -p dist/assets && cp src/assets/*.glb dist/assets/",
39
52
  "typecheck": "tsc --noEmit",
40
53
  "lint": "eslint 'src/**/*.{ts,tsx}'",
41
54
  "format": "prettier --write 'src/**/*.{ts,tsx}'",
@@ -78,11 +91,18 @@
78
91
  "url": "https://github.com/sitebay/talking-head-studio/issues"
79
92
  },
80
93
  "sideEffects": false,
94
+ "dependencies": {
95
+ "zustand": "^5.0.12"
96
+ },
81
97
  "peerDependencies": {
82
98
  "@react-three/drei": ">=9",
83
99
  "@react-three/fiber": ">=8",
100
+ "expo": ">=51",
101
+ "expo-asset": ">=10",
102
+ "expo-file-system": ">=17",
84
103
  "react": ">=18",
85
104
  "react-native": ">=0.73",
105
+ "react-native-filament": ">=1",
86
106
  "react-native-webview": ">=13",
87
107
  "three": ">=0.170"
88
108
  },
@@ -93,6 +113,18 @@
93
113
  "react-native-webview": {
94
114
  "optional": true
95
115
  },
116
+ "react-native-filament": {
117
+ "optional": true
118
+ },
119
+ "expo": {
120
+ "optional": true
121
+ },
122
+ "expo-asset": {
123
+ "optional": true
124
+ },
125
+ "expo-file-system": {
126
+ "optional": true
127
+ },
96
128
  "@react-three/fiber": {
97
129
  "optional": true
98
130
  },