@alia.onl/sdk 3.1.2 → 3.1.4

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.
@@ -1,452 +0,0 @@
1
- import React, { useEffect, useMemo } from "react";
2
- import { View, Text, Image, ImageSourcePropType } from "react-native";
3
- import Svg, { Path, Ellipse } from "react-native-svg";
4
- import Animated, {
5
- useSharedValue,
6
- useAnimatedProps,
7
- useAnimatedStyle,
8
- withTiming,
9
- withRepeat,
10
- withSequence,
11
- withDelay,
12
- cancelAnimation,
13
- Easing,
14
- } from "react-native-reanimated";
15
-
16
- // ─── Animated SVG wrappers ──────────────────────────────────────────────────
17
-
18
- const AnimatedPath = Animated.createAnimatedComponent(Path);
19
- const AnimatedEllipse = Animated.createAnimatedComponent(Ellipse);
20
-
21
- // ─── Types ──────────────────────────────────────────────────────────────────
22
-
23
- export type AliaExpression =
24
- | "Idle A"
25
- | "Interesting"
26
- | "Top Left"
27
- | "Greeting"
28
- | "Searching A"
29
- | "Thinking"
30
- | "Error"
31
- | "Writing E"
32
- | "Searching F"
33
- | "Looking Down";
34
-
35
- export interface AliaAccessory {
36
- layer: 'front' | 'behind';
37
- image: ImageSourcePropType;
38
- /** Optional position overrides (ratios 0-1, scale multiplier, rotation in degrees) */
39
- position?: { x: number; y: number; scale: number; rotation: number };
40
- }
41
-
42
- export interface AliaFaceProps {
43
- expression?: AliaExpression;
44
- size?: number;
45
- showLabel?: boolean;
46
- /** Accessory overlays rendered on/around the face circle */
47
- accessories?: AliaAccessory[];
48
- }
49
-
50
- // ─── Expression data (numeric decomposition of SVG paths) ───────────────────
51
- //
52
- // leftEye: [cx, cy, r]
53
- // rightEye: [cx, cy, r]
54
- // leftBrow: [sx, sy, cx, cy, ex, ey] from M sx sy Q cx cy ex ey
55
- // noseBrow: [sx, sy, c1x, c1y, c2x, c2y, from M sx sy C c1x c1y, c2x c2y, ex ey
56
- // ex, ey, lx1, ly1, lx2, ly2] L lx1 ly1 L lx2 ly2
57
-
58
- interface ExpressionData {
59
- leftEye: [number, number, number];
60
- rightEye: [number, number, number];
61
- leftBrow: [number, number, number, number, number, number];
62
- noseBrow: [
63
- number, number, number, number, number, number,
64
- number, number, number, number, number, number,
65
- ];
66
- }
67
-
68
- const EXPRESSIONS: Record<AliaExpression, ExpressionData> = {
69
- "Idle A": {
70
- leftEye: [123, 118, 11],
71
- rightEye: [182, 126, 11],
72
- leftBrow: [91, 90, 127, 53, 154, 85],
73
- noseBrow: [224, 98, 195, 57, 177, 79, 160, 110, 98, 225, 149, 230],
74
- },
75
- Interesting: {
76
- leftEye: [127, 104, 11],
77
- rightEye: [191, 107, 11],
78
- leftBrow: [97, 91, 122, 68, 141, 76],
79
- noseBrow: [233, 79, 208, 54, 166, 59, 143, 104, 89, 192, 133, 212],
80
- },
81
- "Top Left": {
82
- leftEye: [135, 74, 11],
83
- rightEye: [181, 101, 11],
84
- leftBrow: [114, 56, 145, 28, 169, 52],
85
- noseBrow: [227, 104, 209, 66, 180, 68, 162, 87, 94, 174, 137, 195],
86
- },
87
- Greeting: {
88
- leftEye: [123, 120, 11],
89
- rightEye: [182, 126, 11],
90
- leftBrow: [91, 70, 127, 33, 154, 65],
91
- noseBrow: [224, 75, 195, 30, 177, 79, 160, 110, 98, 225, 149, 230],
92
- },
93
- "Searching A": {
94
- leftEye: [103, 98, 11],
95
- rightEye: [162, 106, 11],
96
- leftBrow: [71, 70, 107, 33, 134, 65],
97
- noseBrow: [204, 78, 175, 37, 157, 59, 140, 90, 78, 205, 129, 210],
98
- },
99
- Thinking: {
100
- leftEye: [123, 118, 11],
101
- rightEye: [182, 126, 11],
102
- leftBrow: [91, 85, 127, 48, 154, 80],
103
- noseBrow: [235, 85, 205, 65, 177, 79, 160, 110, 98, 225, 149, 230],
104
- },
105
- Error: {
106
- leftEye: [110, 240, 11],
107
- rightEye: [170, 250, 9],
108
- leftBrow: [80, 210, 110, 200, 140, 220],
109
- noseBrow: [250, 250, 230, 180, 203, 203, 180, 220, 120, 280, 160, 280],
110
- },
111
- "Writing E": {
112
- leftEye: [148, 118, 11],
113
- rightEye: [207, 126, 11],
114
- leftBrow: [116, 90, 152, 53, 179, 85],
115
- noseBrow: [249, 98, 220, 57, 202, 79, 185, 110, 123, 225, 174, 230],
116
- },
117
- "Searching F": {
118
- leftEye: [143, 98, 11],
119
- rightEye: [202, 106, 11],
120
- leftBrow: [111, 70, 147, 33, 174, 65],
121
- noseBrow: [244, 78, 215, 37, 197, 59, 180, 90, 118, 205, 169, 210],
122
- },
123
- "Looking Down": {
124
- leftEye: [106, 146, 11],
125
- rightEye: [168, 146, 11],
126
- leftBrow: [62, 107, 96, 61, 124, 84],
127
- noseBrow: [203, 90, 182, 65, 140, 85, 133, 130, 119, 238, 159, 222],
128
- },
129
- };
130
-
131
- const DEFAULT_EXPRESSION: AliaExpression = "Idle A";
132
- const MORPH_CONFIG = { duration: 600, easing: Easing.bezier(0.4, 0, 0.2, 1) };
133
-
134
- // ─── Component ──────────────────────────────────────────────────────────────
135
-
136
- export function AliaFace({
137
- expression = DEFAULT_EXPRESSION,
138
- size = 120,
139
- showLabel = false,
140
- accessories,
141
- }: AliaFaceProps) {
142
- const strokeColor = 'currentColor';
143
-
144
- const initial = EXPRESSIONS[DEFAULT_EXPRESSION];
145
-
146
- // ── Expression shared values (24 total) ──
147
-
148
- // Left eye: cx, cy, r
149
- const leX = useSharedValue(initial.leftEye[0]);
150
- const leY = useSharedValue(initial.leftEye[1]);
151
- const leR = useSharedValue(initial.leftEye[2]);
152
-
153
- // Right eye: cx, cy, r
154
- const reX = useSharedValue(initial.rightEye[0]);
155
- const reY = useSharedValue(initial.rightEye[1]);
156
- const reR = useSharedValue(initial.rightEye[2]);
157
-
158
- // Left brow: M(sx,sy) Q(cx,cy, ex,ey)
159
- const lb0 = useSharedValue(initial.leftBrow[0]);
160
- const lb1 = useSharedValue(initial.leftBrow[1]);
161
- const lb2 = useSharedValue(initial.leftBrow[2]);
162
- const lb3 = useSharedValue(initial.leftBrow[3]);
163
- const lb4 = useSharedValue(initial.leftBrow[4]);
164
- const lb5 = useSharedValue(initial.leftBrow[5]);
165
-
166
- // Nose+right brow: M(sx,sy) C(c1x,c1y, c2x,c2y, ex,ey) L(lx1,ly1) L(lx2,ly2)
167
- const nb0 = useSharedValue(initial.noseBrow[0]);
168
- const nb1 = useSharedValue(initial.noseBrow[1]);
169
- const nb2 = useSharedValue(initial.noseBrow[2]);
170
- const nb3 = useSharedValue(initial.noseBrow[3]);
171
- const nb4 = useSharedValue(initial.noseBrow[4]);
172
- const nb5 = useSharedValue(initial.noseBrow[5]);
173
- const nb6 = useSharedValue(initial.noseBrow[6]);
174
- const nb7 = useSharedValue(initial.noseBrow[7]);
175
- const nb8 = useSharedValue(initial.noseBrow[8]);
176
- const nb9 = useSharedValue(initial.noseBrow[9]);
177
- const nb10 = useSharedValue(initial.noseBrow[10]);
178
- const nb11 = useSharedValue(initial.noseBrow[11]);
179
-
180
- // ── Animation shared values ──
181
-
182
- const breatheY = useSharedValue(0);
183
- const blinkScale = useSharedValue(1);
184
- const tw0 = useSharedValue(0); // thinking wave: left brow
185
- const tw1 = useSharedValue(0); // thinking wave: left eye
186
- const tw2 = useSharedValue(0); // thinking wave: right eye
187
- const tw3 = useSharedValue(0); // thinking wave: nose brow
188
- const lookX = useSharedValue(0); // look-around: eye X offset
189
-
190
- // ── Breathe (always on) ──
191
-
192
- useEffect(() => {
193
- breatheY.value = withRepeat(
194
- withSequence(
195
- withTiming(-4, { duration: 2500, easing: Easing.inOut(Easing.ease) }),
196
- withTiming(4, { duration: 2500, easing: Easing.inOut(Easing.ease) }),
197
- ),
198
- -1,
199
- true,
200
- );
201
- return () => cancelAnimation(breatheY);
202
- }, []);
203
-
204
- // ── Blink (always on) ──
205
-
206
- useEffect(() => {
207
- blinkScale.value = withRepeat(
208
- withSequence(
209
- withTiming(1, { duration: 3500, easing: Easing.linear }),
210
- withTiming(0.1, { duration: 80, easing: Easing.linear }),
211
- withTiming(1, { duration: 120, easing: Easing.linear }),
212
- ),
213
- -1,
214
- false,
215
- );
216
- return () => cancelAnimation(blinkScale);
217
- }, []);
218
-
219
- // ── Expression morph ──
220
-
221
- useEffect(() => {
222
- const target = EXPRESSIONS[expression] ?? EXPRESSIONS[DEFAULT_EXPRESSION];
223
-
224
- leX.value = withTiming(target.leftEye[0], MORPH_CONFIG);
225
- leY.value = withTiming(target.leftEye[1], MORPH_CONFIG);
226
- leR.value = withTiming(target.leftEye[2], MORPH_CONFIG);
227
-
228
- reX.value = withTiming(target.rightEye[0], MORPH_CONFIG);
229
- reY.value = withTiming(target.rightEye[1], MORPH_CONFIG);
230
- reR.value = withTiming(target.rightEye[2], MORPH_CONFIG);
231
-
232
- lb0.value = withTiming(target.leftBrow[0], MORPH_CONFIG);
233
- lb1.value = withTiming(target.leftBrow[1], MORPH_CONFIG);
234
- lb2.value = withTiming(target.leftBrow[2], MORPH_CONFIG);
235
- lb3.value = withTiming(target.leftBrow[3], MORPH_CONFIG);
236
- lb4.value = withTiming(target.leftBrow[4], MORPH_CONFIG);
237
- lb5.value = withTiming(target.leftBrow[5], MORPH_CONFIG);
238
-
239
- nb0.value = withTiming(target.noseBrow[0], MORPH_CONFIG);
240
- nb1.value = withTiming(target.noseBrow[1], MORPH_CONFIG);
241
- nb2.value = withTiming(target.noseBrow[2], MORPH_CONFIG);
242
- nb3.value = withTiming(target.noseBrow[3], MORPH_CONFIG);
243
- nb4.value = withTiming(target.noseBrow[4], MORPH_CONFIG);
244
- nb5.value = withTiming(target.noseBrow[5], MORPH_CONFIG);
245
- nb6.value = withTiming(target.noseBrow[6], MORPH_CONFIG);
246
- nb7.value = withTiming(target.noseBrow[7], MORPH_CONFIG);
247
- nb8.value = withTiming(target.noseBrow[8], MORPH_CONFIG);
248
- nb9.value = withTiming(target.noseBrow[9], MORPH_CONFIG);
249
- nb10.value = withTiming(target.noseBrow[10], MORPH_CONFIG);
250
- nb11.value = withTiming(target.noseBrow[11], MORPH_CONFIG);
251
- }, [expression]);
252
-
253
- // ── Thinking wave (conditional) ──
254
-
255
- useEffect(() => {
256
- const waves = [tw0, tw1, tw2, tw3];
257
- if (expression === "Thinking") {
258
- waves.forEach((sv, i) => {
259
- sv.value = withDelay(
260
- i * 200,
261
- withRepeat(
262
- withSequence(
263
- withTiming(-3, {
264
- duration: 1250,
265
- easing: Easing.inOut(Easing.ease),
266
- }),
267
- withTiming(3, {
268
- duration: 1250,
269
- easing: Easing.inOut(Easing.ease),
270
- }),
271
- ),
272
- -1,
273
- true,
274
- ),
275
- );
276
- });
277
- } else {
278
- waves.forEach((sv) => {
279
- cancelAnimation(sv);
280
- sv.value = withTiming(0, { duration: 300 });
281
- });
282
- }
283
- return () => waves.forEach((sv) => cancelAnimation(sv));
284
- }, [expression]);
285
-
286
- // ── Look around (conditional) ──
287
-
288
- useEffect(() => {
289
- if (expression === "Searching F") {
290
- lookX.value = withRepeat(
291
- withSequence(
292
- withTiming(-6, {
293
- duration: 1500,
294
- easing: Easing.inOut(Easing.ease),
295
- }),
296
- withTiming(8, {
297
- duration: 2000,
298
- easing: Easing.inOut(Easing.ease),
299
- }),
300
- withTiming(0, {
301
- duration: 500,
302
- easing: Easing.inOut(Easing.ease),
303
- }),
304
- ),
305
- -1,
306
- false,
307
- );
308
- } else {
309
- cancelAnimation(lookX);
310
- lookX.value = withTiming(0, { duration: 300 });
311
- }
312
- return () => cancelAnimation(lookX);
313
- }, [expression]);
314
-
315
- // ── Animated props ──
316
-
317
- const leftBrowProps = useAnimatedProps(() => ({
318
- d: `M ${lb0.value} ${lb1.value + tw0.value} Q ${lb2.value} ${lb3.value + tw0.value} ${lb4.value} ${lb5.value + tw0.value}`,
319
- }));
320
-
321
- const noseBrowProps = useAnimatedProps(() => ({
322
- d: `M ${nb0.value} ${nb1.value + tw3.value} C ${nb2.value} ${nb3.value + tw3.value}, ${nb4.value} ${nb5.value + tw3.value}, ${nb6.value} ${nb7.value + tw3.value} L ${nb8.value} ${nb9.value + tw3.value} L ${nb10.value} ${nb11.value + tw3.value}`,
323
- }));
324
-
325
- const leftEyeProps = useAnimatedProps(() => ({
326
- cx: leX.value + lookX.value,
327
- cy: leY.value + tw1.value,
328
- rx: leR.value,
329
- ry: leR.value * blinkScale.value,
330
- }));
331
-
332
- const rightEyeProps = useAnimatedProps(() => ({
333
- cx: reX.value + lookX.value,
334
- cy: reY.value + tw2.value,
335
- rx: reR.value,
336
- ry: reR.value * blinkScale.value,
337
- }));
338
-
339
- // ── Breathe style on wrapper ──
340
-
341
- const breatheStyle = useAnimatedStyle(() => ({
342
- transform: [{ translateY: breatheY.value }],
343
- }));
344
-
345
- // ── Accessories ──
346
-
347
- const { behind, front } = useMemo(() => {
348
- if (!accessories?.length) return { behind: [] as AliaAccessory[], front: [] as AliaAccessory[] };
349
- const b: AliaAccessory[] = [];
350
- const f: AliaAccessory[] = [];
351
- for (const acc of accessories) {
352
- if (acc.layer === 'behind') b.push(acc);
353
- else f.push(acc);
354
- }
355
- return { behind: b, front: f };
356
- }, [accessories]);
357
-
358
- const accessoryStyle = useMemo(
359
- () => ({ position: 'absolute' as const, width: size, height: size, top: 0, left: 0 }),
360
- [size],
361
- );
362
-
363
- // ── Render ──
364
-
365
- return (
366
- <View style={{ alignItems: "center" }}>
367
- <View style={{ width: size, height: size, overflow: 'visible' as const }}>
368
- {/* Behind-layer accessories (e.g. headphones) */}
369
- {behind.map((acc, i) => (
370
- <Image key={`behind-${i}`} source={acc.image} style={acc.position ? {
371
- position: 'absolute',
372
- width: size * acc.position.scale,
373
- height: size * acc.position.scale,
374
- top: (acc.position.y - 0.5) * size + (size - size * acc.position.scale) / 2,
375
- left: (acc.position.x - 0.5) * size + (size - size * acc.position.scale) / 2,
376
- transform: [{ rotate: `${acc.position.rotation}deg` }],
377
- } : accessoryStyle} />
378
- ))}
379
-
380
- {/* Face circle */}
381
- <View
382
- className="bg-background border-primary/20 text-foreground"
383
- style={{
384
- width: size,
385
- height: size,
386
- borderRadius: size / 2,
387
- alignItems: "center",
388
- justifyContent: "center",
389
- borderWidth: 1,
390
- }}
391
- >
392
- <Animated.View style={breatheStyle}>
393
- <Svg
394
- width={size * 0.95}
395
- height={size * 0.95}
396
- viewBox="35 35 250 250"
397
- >
398
- {/* Left brow */}
399
- <AnimatedPath
400
- animatedProps={leftBrowProps}
401
- stroke={strokeColor}
402
- strokeWidth={15}
403
- strokeLinecap="round"
404
- fill="none"
405
- />
406
-
407
- {/* Left eye */}
408
- <AnimatedEllipse
409
- animatedProps={leftEyeProps}
410
- fill={strokeColor}
411
- />
412
-
413
- {/* Right eye */}
414
- <AnimatedEllipse
415
- animatedProps={rightEyeProps}
416
- fill={strokeColor}
417
- />
418
-
419
- {/* Nose + right brow */}
420
- <AnimatedPath
421
- animatedProps={noseBrowProps}
422
- stroke={strokeColor}
423
- strokeWidth={15}
424
- strokeLinecap="round"
425
- strokeLinejoin="round"
426
- fill="none"
427
- />
428
- </Svg>
429
- </Animated.View>
430
- </View>
431
-
432
- {/* Front-layer accessories (e.g. hat, glasses, tie) */}
433
- {front.map((acc, i) => (
434
- <Image key={`front-${i}`} source={acc.image} style={acc.position ? {
435
- position: 'absolute',
436
- width: size * acc.position.scale,
437
- height: size * acc.position.scale,
438
- top: (acc.position.y - 0.5) * size + (size - size * acc.position.scale) / 2,
439
- left: (acc.position.x - 0.5) * size + (size - size * acc.position.scale) / 2,
440
- transform: [{ rotate: `${acc.position.rotation}deg` }],
441
- } : accessoryStyle} />
442
- ))}
443
- </View>
444
-
445
- {showLabel && (
446
- <Text className="text-muted-foreground" style={{ fontSize: 10, marginTop: 8, textTransform: 'uppercase', letterSpacing: 3, fontWeight: '700' }}>
447
- {expression}
448
- </Text>
449
- )}
450
- </View>
451
- );
452
- }