talking-head-studio 0.2.8 → 0.3.0
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/dist/TalkingHead.d.ts +8 -0
- package/dist/TalkingHead.js +104 -7
- package/dist/TalkingHead.web.d.ts +3 -2
- package/dist/TalkingHead.web.js +17 -2
- package/dist/TalkingHeadVisualization.d.ts +35 -0
- package/dist/TalkingHeadVisualization.js +277 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +18 -0
- package/dist/api/studioApi.d.ts +38 -0
- package/dist/api/studioApi.js +235 -0
- package/dist/api/types.d.ts +87 -0
- package/dist/api/types.js +5 -0
- package/dist/assets/face-squeeze-local.glb +0 -0
- package/dist/filament/FilamentAvatar.d.ts +41 -0
- package/dist/filament/FilamentAvatar.js +737 -0
- package/dist/filament/faceSqueezeAssets.d.ts +1 -0
- package/dist/filament/faceSqueezeAssets.js +5 -0
- package/dist/filament/index.d.ts +5 -0
- package/dist/filament/index.js +22 -0
- package/dist/filament/morphTables.d.ts +5 -0
- package/dist/filament/morphTables.js +93 -0
- package/dist/filament/useAuthedFilamentUri.d.ts +11 -0
- package/dist/filament/useAuthedFilamentUri.js +126 -0
- package/dist/html.d.ts +7 -0
- package/dist/html.js +255 -56
- package/dist/index.d.ts +9 -2
- package/dist/index.js +13 -2
- package/dist/index.web.d.ts +6 -2
- package/dist/index.web.js +10 -2
- package/dist/utils/avatarUtils.d.ts +13 -0
- package/dist/utils/avatarUtils.js +56 -0
- package/dist/wardrobe/index.d.ts +2 -0
- package/dist/wardrobe/index.js +20 -0
- package/dist/wardrobe/useAvatarWardrobeHydration.d.ts +7 -0
- package/dist/wardrobe/useAvatarWardrobeHydration.js +34 -0
- package/dist/wardrobe/wardrobeStore.d.ts +30 -0
- package/dist/wardrobe/wardrobeStore.js +106 -0
- package/package.json +33 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useAvatarWardrobeHydration = useAvatarWardrobeHydration;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const studioApi_1 = require("../api/studioApi");
|
|
6
|
+
const wardrobeStore_1 = require("./wardrobeStore");
|
|
7
|
+
function useAvatarWardrobeHydration({ avatarId, accessories, }) {
|
|
8
|
+
const hydrateFromApi = (0, wardrobeStore_1.useWardrobeStore)((s) => s.hydrateFromApi);
|
|
9
|
+
const accessorySignature = (0, react_1.useMemo)(() => JSON.stringify(accessories ?? []), [accessories]);
|
|
10
|
+
(0, react_1.useEffect)(() => {
|
|
11
|
+
const nextAccessories = accessories ?? [];
|
|
12
|
+
if (!avatarId || nextAccessories.length === 0) {
|
|
13
|
+
hydrateFromApi([], {});
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
let cancelled = false;
|
|
17
|
+
void (0, studioApi_1.listAssets)()
|
|
18
|
+
.then((allAssets) => {
|
|
19
|
+
if (cancelled)
|
|
20
|
+
return;
|
|
21
|
+
const lookup = {};
|
|
22
|
+
for (const asset of allAssets) {
|
|
23
|
+
lookup[asset.id] = asset;
|
|
24
|
+
}
|
|
25
|
+
hydrateFromApi(nextAccessories, lookup);
|
|
26
|
+
})
|
|
27
|
+
.catch((err) => {
|
|
28
|
+
console.error('[useAvatarWardrobeHydration] Failed to hydrate wardrobe:', err);
|
|
29
|
+
});
|
|
30
|
+
return () => {
|
|
31
|
+
cancelled = true;
|
|
32
|
+
};
|
|
33
|
+
}, [accessorySignature, avatarId, hydrateFromApi]);
|
|
34
|
+
}
|
|
@@ -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.
|
|
3
|
+
"version": "0.3.0",
|
|
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,12 +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": {
|
|
98
|
+
"@react-three/drei": ">=9",
|
|
99
|
+
"@react-three/fiber": ">=8",
|
|
100
|
+
"expo-asset": ">=10",
|
|
101
|
+
"expo-file-system": ">=17",
|
|
82
102
|
"react": ">=18",
|
|
83
103
|
"react-native": ">=0.73",
|
|
104
|
+
"react-native-filament": ">=1",
|
|
84
105
|
"react-native-webview": ">=13",
|
|
85
|
-
"@react-three/fiber": ">=8",
|
|
86
|
-
"@react-three/drei": ">=9",
|
|
87
106
|
"three": ">=0.170"
|
|
88
107
|
},
|
|
89
108
|
"peerDependenciesMeta": {
|
|
@@ -93,6 +112,15 @@
|
|
|
93
112
|
"react-native-webview": {
|
|
94
113
|
"optional": true
|
|
95
114
|
},
|
|
115
|
+
"react-native-filament": {
|
|
116
|
+
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"expo-asset": {
|
|
119
|
+
"optional": true
|
|
120
|
+
},
|
|
121
|
+
"expo-file-system": {
|
|
122
|
+
"optional": true
|
|
123
|
+
},
|
|
96
124
|
"@react-three/fiber": {
|
|
97
125
|
"optional": true
|
|
98
126
|
},
|
|
@@ -126,6 +154,7 @@
|
|
|
126
154
|
"metro-react-native-babel-preset": "^0.77.0",
|
|
127
155
|
"multer": "^2.1.0",
|
|
128
156
|
"prettier": "^3.8.1",
|
|
157
|
+
"react-native-webview": "^13.16.0",
|
|
129
158
|
"react-test-renderer": "^19.2.4",
|
|
130
159
|
"ts-jest": "^29.4.6"
|
|
131
160
|
}
|