@alia.onl/sdk 1.0.0 → 3.0.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/package.json +37 -3
- package/src/components/AliaChatContent.tsx +313 -0
- package/src/components/AliaChatInput.tsx +174 -27
- package/src/components/AliaChatMessageList.tsx +220 -34
- package/src/components/AliaChatScreen.tsx +132 -0
- package/src/components/AliaChatSheet.tsx +36 -49
- package/src/components/AliaChatSuggestions.tsx +1 -1
- package/src/components/AliaFace.tsx +402 -0
- package/src/components/Markdown.tsx +288 -0
- package/src/components/Reasoning.tsx +154 -0
- package/src/components/ThinkingIndicator.tsx +134 -0
- package/src/components/ui/button.tsx +104 -0
- package/src/components/ui/chat-text-input.tsx +164 -0
- package/src/components/ui/dropdown-menu.tsx +2 -0
- package/src/components/ui/dropdown-menu.web.tsx +355 -0
- package/src/components/ui/prompt-input/actions.tsx +28 -0
- package/src/components/ui/prompt-input/add-menu.tsx +83 -0
- package/src/components/ui/prompt-input/attachments.tsx +183 -0
- package/src/components/ui/prompt-input/autocomplete.tsx +168 -0
- package/src/components/ui/prompt-input/context.tsx +71 -0
- package/src/components/ui/prompt-input/mic-button.tsx +53 -0
- package/src/components/ui/prompt-input/prompt-input.tsx +276 -0
- package/src/components/ui/prompt-input/submit-button.tsx +49 -0
- package/src/components/ui/prompt-input/textarea.tsx +64 -0
- package/src/components/ui/text.tsx +29 -0
- package/src/components/voice/AudioWaveVisualizer.tsx +377 -0
- package/src/components/voice/VoiceControls.tsx +180 -0
- package/src/components/voice/VoiceOverlay.tsx +43 -0
- package/src/hooks/useAliaChat.ts +278 -91
- package/src/hooks/useAudioLevelMonitor.ts +142 -0
- package/src/hooks/useAudioLevels.ts +51 -0
- package/src/hooks/useDocumentPicker.ts +37 -0
- package/src/hooks/useImagePicker.ts +38 -0
- package/src/hooks/useSoundEffects.ts +128 -0
- package/src/hooks/useSpeechToText.ts +182 -0
- package/src/hooks/useTTS.ts +233 -0
- package/src/hooks/useVoiceRoom.ts +420 -0
- package/src/index.ts +71 -6
- package/src/lib/keyboard.native.tsx +6 -0
- package/src/lib/keyboard.tsx +25 -0
- package/src/lib/utils.ts +14 -0
- package/src/optional-deps.d.ts +86 -0
- package/src/theme.ts +25 -2
- package/src/types.ts +63 -1
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { View, Text } 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
|
+
import { useAliaColors } from "../theme";
|
|
16
|
+
|
|
17
|
+
// ─── Animated SVG wrappers ──────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const AnimatedPath = Animated.createAnimatedComponent(Path);
|
|
20
|
+
const AnimatedEllipse = Animated.createAnimatedComponent(Ellipse);
|
|
21
|
+
|
|
22
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
export type AliaExpression =
|
|
25
|
+
| "Idle A"
|
|
26
|
+
| "Interesting"
|
|
27
|
+
| "Top Left"
|
|
28
|
+
| "Greeting"
|
|
29
|
+
| "Searching A"
|
|
30
|
+
| "Thinking"
|
|
31
|
+
| "Error"
|
|
32
|
+
| "Writing E"
|
|
33
|
+
| "Searching F"
|
|
34
|
+
| "Looking Down";
|
|
35
|
+
|
|
36
|
+
export interface AliaFaceProps {
|
|
37
|
+
expression?: AliaExpression;
|
|
38
|
+
size?: number;
|
|
39
|
+
showLabel?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Expression data (numeric decomposition of SVG paths) ───────────────────
|
|
43
|
+
//
|
|
44
|
+
// leftEye: [cx, cy, r]
|
|
45
|
+
// rightEye: [cx, cy, r]
|
|
46
|
+
// leftBrow: [sx, sy, cx, cy, ex, ey] from M sx sy Q cx cy ex ey
|
|
47
|
+
// noseBrow: [sx, sy, c1x, c1y, c2x, c2y, from M sx sy C c1x c1y, c2x c2y, ex ey
|
|
48
|
+
// ex, ey, lx1, ly1, lx2, ly2] L lx1 ly1 L lx2 ly2
|
|
49
|
+
|
|
50
|
+
interface ExpressionData {
|
|
51
|
+
leftEye: [number, number, number];
|
|
52
|
+
rightEye: [number, number, number];
|
|
53
|
+
leftBrow: [number, number, number, number, number, number];
|
|
54
|
+
noseBrow: [
|
|
55
|
+
number, number, number, number, number, number,
|
|
56
|
+
number, number, number, number, number, number,
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const EXPRESSIONS: Record<AliaExpression, ExpressionData> = {
|
|
61
|
+
"Idle A": {
|
|
62
|
+
leftEye: [123, 118, 11],
|
|
63
|
+
rightEye: [182, 126, 11],
|
|
64
|
+
leftBrow: [91, 90, 127, 53, 154, 85],
|
|
65
|
+
noseBrow: [224, 98, 195, 57, 177, 79, 160, 110, 98, 225, 149, 230],
|
|
66
|
+
},
|
|
67
|
+
Interesting: {
|
|
68
|
+
leftEye: [127, 104, 11],
|
|
69
|
+
rightEye: [191, 107, 11],
|
|
70
|
+
leftBrow: [97, 91, 122, 68, 141, 76],
|
|
71
|
+
noseBrow: [233, 79, 208, 54, 166, 59, 143, 104, 89, 192, 133, 212],
|
|
72
|
+
},
|
|
73
|
+
"Top Left": {
|
|
74
|
+
leftEye: [135, 74, 11],
|
|
75
|
+
rightEye: [181, 101, 11],
|
|
76
|
+
leftBrow: [114, 56, 145, 28, 169, 52],
|
|
77
|
+
noseBrow: [227, 104, 209, 66, 180, 68, 162, 87, 94, 174, 137, 195],
|
|
78
|
+
},
|
|
79
|
+
Greeting: {
|
|
80
|
+
leftEye: [123, 120, 11],
|
|
81
|
+
rightEye: [182, 126, 11],
|
|
82
|
+
leftBrow: [91, 70, 127, 33, 154, 65],
|
|
83
|
+
noseBrow: [224, 75, 195, 30, 177, 79, 160, 110, 98, 225, 149, 230],
|
|
84
|
+
},
|
|
85
|
+
"Searching A": {
|
|
86
|
+
leftEye: [103, 98, 11],
|
|
87
|
+
rightEye: [162, 106, 11],
|
|
88
|
+
leftBrow: [71, 70, 107, 33, 134, 65],
|
|
89
|
+
noseBrow: [204, 78, 175, 37, 157, 59, 140, 90, 78, 205, 129, 210],
|
|
90
|
+
},
|
|
91
|
+
Thinking: {
|
|
92
|
+
leftEye: [123, 118, 11],
|
|
93
|
+
rightEye: [182, 126, 11],
|
|
94
|
+
leftBrow: [91, 85, 127, 48, 154, 80],
|
|
95
|
+
noseBrow: [235, 85, 205, 65, 177, 79, 160, 110, 98, 225, 149, 230],
|
|
96
|
+
},
|
|
97
|
+
Error: {
|
|
98
|
+
leftEye: [110, 240, 11],
|
|
99
|
+
rightEye: [170, 250, 9],
|
|
100
|
+
leftBrow: [80, 210, 110, 200, 140, 220],
|
|
101
|
+
noseBrow: [250, 250, 230, 180, 203, 203, 180, 220, 120, 280, 160, 280],
|
|
102
|
+
},
|
|
103
|
+
"Writing E": {
|
|
104
|
+
leftEye: [148, 118, 11],
|
|
105
|
+
rightEye: [207, 126, 11],
|
|
106
|
+
leftBrow: [116, 90, 152, 53, 179, 85],
|
|
107
|
+
noseBrow: [249, 98, 220, 57, 202, 79, 185, 110, 123, 225, 174, 230],
|
|
108
|
+
},
|
|
109
|
+
"Searching F": {
|
|
110
|
+
leftEye: [143, 98, 11],
|
|
111
|
+
rightEye: [202, 106, 11],
|
|
112
|
+
leftBrow: [111, 70, 147, 33, 174, 65],
|
|
113
|
+
noseBrow: [244, 78, 215, 37, 197, 59, 180, 90, 118, 205, 169, 210],
|
|
114
|
+
},
|
|
115
|
+
"Looking Down": {
|
|
116
|
+
leftEye: [106, 146, 11],
|
|
117
|
+
rightEye: [168, 146, 11],
|
|
118
|
+
leftBrow: [62, 107, 96, 61, 124, 84],
|
|
119
|
+
noseBrow: [203, 90, 182, 65, 140, 85, 133, 130, 119, 238, 159, 222],
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const DEFAULT_EXPRESSION: AliaExpression = "Idle A";
|
|
124
|
+
const MORPH_CONFIG = { duration: 600, easing: Easing.bezier(0.4, 0, 0.2, 1) };
|
|
125
|
+
|
|
126
|
+
// ─── Component ──────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
export function AliaFace({
|
|
129
|
+
expression = DEFAULT_EXPRESSION,
|
|
130
|
+
size = 120,
|
|
131
|
+
showLabel = false,
|
|
132
|
+
}: AliaFaceProps) {
|
|
133
|
+
const colors = useAliaColors();
|
|
134
|
+
const strokeColor = colors.isDark ? "#ffffff" : "#000000";
|
|
135
|
+
const circleBg = colors.background;
|
|
136
|
+
const circleBorder = colors.border;
|
|
137
|
+
|
|
138
|
+
const initial = EXPRESSIONS[DEFAULT_EXPRESSION];
|
|
139
|
+
|
|
140
|
+
// ── Expression shared values (24 total) ──
|
|
141
|
+
|
|
142
|
+
// Left eye: cx, cy, r
|
|
143
|
+
const leX = useSharedValue(initial.leftEye[0]);
|
|
144
|
+
const leY = useSharedValue(initial.leftEye[1]);
|
|
145
|
+
const leR = useSharedValue(initial.leftEye[2]);
|
|
146
|
+
|
|
147
|
+
// Right eye: cx, cy, r
|
|
148
|
+
const reX = useSharedValue(initial.rightEye[0]);
|
|
149
|
+
const reY = useSharedValue(initial.rightEye[1]);
|
|
150
|
+
const reR = useSharedValue(initial.rightEye[2]);
|
|
151
|
+
|
|
152
|
+
// Left brow: M(sx,sy) Q(cx,cy, ex,ey)
|
|
153
|
+
const lb0 = useSharedValue(initial.leftBrow[0]);
|
|
154
|
+
const lb1 = useSharedValue(initial.leftBrow[1]);
|
|
155
|
+
const lb2 = useSharedValue(initial.leftBrow[2]);
|
|
156
|
+
const lb3 = useSharedValue(initial.leftBrow[3]);
|
|
157
|
+
const lb4 = useSharedValue(initial.leftBrow[4]);
|
|
158
|
+
const lb5 = useSharedValue(initial.leftBrow[5]);
|
|
159
|
+
|
|
160
|
+
// Nose+right brow: M(sx,sy) C(c1x,c1y, c2x,c2y, ex,ey) L(lx1,ly1) L(lx2,ly2)
|
|
161
|
+
const nb0 = useSharedValue(initial.noseBrow[0]);
|
|
162
|
+
const nb1 = useSharedValue(initial.noseBrow[1]);
|
|
163
|
+
const nb2 = useSharedValue(initial.noseBrow[2]);
|
|
164
|
+
const nb3 = useSharedValue(initial.noseBrow[3]);
|
|
165
|
+
const nb4 = useSharedValue(initial.noseBrow[4]);
|
|
166
|
+
const nb5 = useSharedValue(initial.noseBrow[5]);
|
|
167
|
+
const nb6 = useSharedValue(initial.noseBrow[6]);
|
|
168
|
+
const nb7 = useSharedValue(initial.noseBrow[7]);
|
|
169
|
+
const nb8 = useSharedValue(initial.noseBrow[8]);
|
|
170
|
+
const nb9 = useSharedValue(initial.noseBrow[9]);
|
|
171
|
+
const nb10 = useSharedValue(initial.noseBrow[10]);
|
|
172
|
+
const nb11 = useSharedValue(initial.noseBrow[11]);
|
|
173
|
+
|
|
174
|
+
// ── Animation shared values ──
|
|
175
|
+
|
|
176
|
+
const breatheY = useSharedValue(0);
|
|
177
|
+
const blinkScale = useSharedValue(1);
|
|
178
|
+
const tw0 = useSharedValue(0); // thinking wave: left brow
|
|
179
|
+
const tw1 = useSharedValue(0); // thinking wave: left eye
|
|
180
|
+
const tw2 = useSharedValue(0); // thinking wave: right eye
|
|
181
|
+
const tw3 = useSharedValue(0); // thinking wave: nose brow
|
|
182
|
+
const lookX = useSharedValue(0); // look-around: eye X offset
|
|
183
|
+
|
|
184
|
+
// ── Breathe (always on) ──
|
|
185
|
+
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
breatheY.value = withRepeat(
|
|
188
|
+
withSequence(
|
|
189
|
+
withTiming(-4, { duration: 2500, easing: Easing.inOut(Easing.ease) }),
|
|
190
|
+
withTiming(4, { duration: 2500, easing: Easing.inOut(Easing.ease) }),
|
|
191
|
+
),
|
|
192
|
+
-1,
|
|
193
|
+
true,
|
|
194
|
+
);
|
|
195
|
+
return () => cancelAnimation(breatheY);
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
// ── Blink (always on) ──
|
|
199
|
+
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
blinkScale.value = withRepeat(
|
|
202
|
+
withSequence(
|
|
203
|
+
withTiming(1, { duration: 3500, easing: Easing.linear }),
|
|
204
|
+
withTiming(0.1, { duration: 80, easing: Easing.linear }),
|
|
205
|
+
withTiming(1, { duration: 120, easing: Easing.linear }),
|
|
206
|
+
),
|
|
207
|
+
-1,
|
|
208
|
+
false,
|
|
209
|
+
);
|
|
210
|
+
return () => cancelAnimation(blinkScale);
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
// ── Expression morph ──
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
const target = EXPRESSIONS[expression] ?? EXPRESSIONS[DEFAULT_EXPRESSION];
|
|
217
|
+
|
|
218
|
+
leX.value = withTiming(target.leftEye[0], MORPH_CONFIG);
|
|
219
|
+
leY.value = withTiming(target.leftEye[1], MORPH_CONFIG);
|
|
220
|
+
leR.value = withTiming(target.leftEye[2], MORPH_CONFIG);
|
|
221
|
+
|
|
222
|
+
reX.value = withTiming(target.rightEye[0], MORPH_CONFIG);
|
|
223
|
+
reY.value = withTiming(target.rightEye[1], MORPH_CONFIG);
|
|
224
|
+
reR.value = withTiming(target.rightEye[2], MORPH_CONFIG);
|
|
225
|
+
|
|
226
|
+
lb0.value = withTiming(target.leftBrow[0], MORPH_CONFIG);
|
|
227
|
+
lb1.value = withTiming(target.leftBrow[1], MORPH_CONFIG);
|
|
228
|
+
lb2.value = withTiming(target.leftBrow[2], MORPH_CONFIG);
|
|
229
|
+
lb3.value = withTiming(target.leftBrow[3], MORPH_CONFIG);
|
|
230
|
+
lb4.value = withTiming(target.leftBrow[4], MORPH_CONFIG);
|
|
231
|
+
lb5.value = withTiming(target.leftBrow[5], MORPH_CONFIG);
|
|
232
|
+
|
|
233
|
+
nb0.value = withTiming(target.noseBrow[0], MORPH_CONFIG);
|
|
234
|
+
nb1.value = withTiming(target.noseBrow[1], MORPH_CONFIG);
|
|
235
|
+
nb2.value = withTiming(target.noseBrow[2], MORPH_CONFIG);
|
|
236
|
+
nb3.value = withTiming(target.noseBrow[3], MORPH_CONFIG);
|
|
237
|
+
nb4.value = withTiming(target.noseBrow[4], MORPH_CONFIG);
|
|
238
|
+
nb5.value = withTiming(target.noseBrow[5], MORPH_CONFIG);
|
|
239
|
+
nb6.value = withTiming(target.noseBrow[6], MORPH_CONFIG);
|
|
240
|
+
nb7.value = withTiming(target.noseBrow[7], MORPH_CONFIG);
|
|
241
|
+
nb8.value = withTiming(target.noseBrow[8], MORPH_CONFIG);
|
|
242
|
+
nb9.value = withTiming(target.noseBrow[9], MORPH_CONFIG);
|
|
243
|
+
nb10.value = withTiming(target.noseBrow[10], MORPH_CONFIG);
|
|
244
|
+
nb11.value = withTiming(target.noseBrow[11], MORPH_CONFIG);
|
|
245
|
+
}, [expression]);
|
|
246
|
+
|
|
247
|
+
// ── Thinking wave (conditional) ──
|
|
248
|
+
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
const waves = [tw0, tw1, tw2, tw3];
|
|
251
|
+
if (expression === "Thinking") {
|
|
252
|
+
waves.forEach((sv, i) => {
|
|
253
|
+
sv.value = withDelay(
|
|
254
|
+
i * 200,
|
|
255
|
+
withRepeat(
|
|
256
|
+
withSequence(
|
|
257
|
+
withTiming(-3, {
|
|
258
|
+
duration: 1250,
|
|
259
|
+
easing: Easing.inOut(Easing.ease),
|
|
260
|
+
}),
|
|
261
|
+
withTiming(3, {
|
|
262
|
+
duration: 1250,
|
|
263
|
+
easing: Easing.inOut(Easing.ease),
|
|
264
|
+
}),
|
|
265
|
+
),
|
|
266
|
+
-1,
|
|
267
|
+
true,
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
} else {
|
|
272
|
+
waves.forEach((sv) => {
|
|
273
|
+
cancelAnimation(sv);
|
|
274
|
+
sv.value = withTiming(0, { duration: 300 });
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return () => waves.forEach((sv) => cancelAnimation(sv));
|
|
278
|
+
}, [expression]);
|
|
279
|
+
|
|
280
|
+
// ── Look around (conditional) ──
|
|
281
|
+
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
if (expression === "Searching F") {
|
|
284
|
+
lookX.value = withRepeat(
|
|
285
|
+
withSequence(
|
|
286
|
+
withTiming(-6, {
|
|
287
|
+
duration: 1500,
|
|
288
|
+
easing: Easing.inOut(Easing.ease),
|
|
289
|
+
}),
|
|
290
|
+
withTiming(8, {
|
|
291
|
+
duration: 2000,
|
|
292
|
+
easing: Easing.inOut(Easing.ease),
|
|
293
|
+
}),
|
|
294
|
+
withTiming(0, {
|
|
295
|
+
duration: 500,
|
|
296
|
+
easing: Easing.inOut(Easing.ease),
|
|
297
|
+
}),
|
|
298
|
+
),
|
|
299
|
+
-1,
|
|
300
|
+
false,
|
|
301
|
+
);
|
|
302
|
+
} else {
|
|
303
|
+
cancelAnimation(lookX);
|
|
304
|
+
lookX.value = withTiming(0, { duration: 300 });
|
|
305
|
+
}
|
|
306
|
+
return () => cancelAnimation(lookX);
|
|
307
|
+
}, [expression]);
|
|
308
|
+
|
|
309
|
+
// ── Animated props ──
|
|
310
|
+
|
|
311
|
+
const leftBrowProps = useAnimatedProps(() => ({
|
|
312
|
+
d: `M ${lb0.value} ${lb1.value + tw0.value} Q ${lb2.value} ${lb3.value + tw0.value} ${lb4.value} ${lb5.value + tw0.value}`,
|
|
313
|
+
}));
|
|
314
|
+
|
|
315
|
+
const noseBrowProps = useAnimatedProps(() => ({
|
|
316
|
+
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}`,
|
|
317
|
+
}));
|
|
318
|
+
|
|
319
|
+
const leftEyeProps = useAnimatedProps(() => ({
|
|
320
|
+
cx: leX.value + lookX.value,
|
|
321
|
+
cy: leY.value + tw1.value,
|
|
322
|
+
rx: leR.value,
|
|
323
|
+
ry: leR.value * blinkScale.value,
|
|
324
|
+
}));
|
|
325
|
+
|
|
326
|
+
const rightEyeProps = useAnimatedProps(() => ({
|
|
327
|
+
cx: reX.value + lookX.value,
|
|
328
|
+
cy: reY.value + tw2.value,
|
|
329
|
+
rx: reR.value,
|
|
330
|
+
ry: reR.value * blinkScale.value,
|
|
331
|
+
}));
|
|
332
|
+
|
|
333
|
+
// ── Breathe style on wrapper ──
|
|
334
|
+
|
|
335
|
+
const breatheStyle = useAnimatedStyle(() => ({
|
|
336
|
+
transform: [{ translateY: breatheY.value }],
|
|
337
|
+
}));
|
|
338
|
+
|
|
339
|
+
// ── Render ──
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<View style={{ alignItems: "center" }}>
|
|
343
|
+
<View
|
|
344
|
+
style={{
|
|
345
|
+
width: size,
|
|
346
|
+
height: size,
|
|
347
|
+
borderRadius: size / 2,
|
|
348
|
+
backgroundColor: circleBg,
|
|
349
|
+
alignItems: "center",
|
|
350
|
+
justifyContent: "center",
|
|
351
|
+
borderWidth: 1,
|
|
352
|
+
borderColor: circleBorder,
|
|
353
|
+
}}
|
|
354
|
+
>
|
|
355
|
+
<Animated.View style={breatheStyle}>
|
|
356
|
+
<Svg
|
|
357
|
+
width={size * 0.95}
|
|
358
|
+
height={size * 0.95}
|
|
359
|
+
viewBox="35 35 250 250"
|
|
360
|
+
>
|
|
361
|
+
{/* Left brow */}
|
|
362
|
+
<AnimatedPath
|
|
363
|
+
animatedProps={leftBrowProps}
|
|
364
|
+
stroke={strokeColor}
|
|
365
|
+
strokeWidth={15}
|
|
366
|
+
strokeLinecap="round"
|
|
367
|
+
fill="none"
|
|
368
|
+
/>
|
|
369
|
+
|
|
370
|
+
{/* Left eye */}
|
|
371
|
+
<AnimatedEllipse
|
|
372
|
+
animatedProps={leftEyeProps}
|
|
373
|
+
fill={strokeColor}
|
|
374
|
+
/>
|
|
375
|
+
|
|
376
|
+
{/* Right eye */}
|
|
377
|
+
<AnimatedEllipse
|
|
378
|
+
animatedProps={rightEyeProps}
|
|
379
|
+
fill={strokeColor}
|
|
380
|
+
/>
|
|
381
|
+
|
|
382
|
+
{/* Nose + right brow */}
|
|
383
|
+
<AnimatedPath
|
|
384
|
+
animatedProps={noseBrowProps}
|
|
385
|
+
stroke={strokeColor}
|
|
386
|
+
strokeWidth={15}
|
|
387
|
+
strokeLinecap="round"
|
|
388
|
+
strokeLinejoin="round"
|
|
389
|
+
fill="none"
|
|
390
|
+
/>
|
|
391
|
+
</Svg>
|
|
392
|
+
</Animated.View>
|
|
393
|
+
</View>
|
|
394
|
+
|
|
395
|
+
{showLabel && (
|
|
396
|
+
<Text style={{ fontSize: 10, color: colors.mutedForeground, marginTop: 8, textTransform: 'uppercase', letterSpacing: 3, fontWeight: '700' }}>
|
|
397
|
+
{expression}
|
|
398
|
+
</Text>
|
|
399
|
+
)}
|
|
400
|
+
</View>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, Platform, Linking } from 'react-native';
|
|
3
|
+
import Markdown from 'react-native-markdown-display';
|
|
4
|
+
import { useAliaColors } from '../theme';
|
|
5
|
+
|
|
6
|
+
function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
7
|
+
return {
|
|
8
|
+
heading1: (node: any, children: any) => (
|
|
9
|
+
<Text
|
|
10
|
+
key={node.key}
|
|
11
|
+
style={{ marginBottom: 8, marginTop: 12, fontSize: 18, fontWeight: '600', lineHeight: 24, color: textColor }}
|
|
12
|
+
>
|
|
13
|
+
{children}
|
|
14
|
+
</Text>
|
|
15
|
+
),
|
|
16
|
+
heading2: (node: any, children: any) => (
|
|
17
|
+
<Text
|
|
18
|
+
key={node.key}
|
|
19
|
+
style={{ marginBottom: 8, marginTop: 12, fontWeight: '600', lineHeight: 22, color: textColor }}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</Text>
|
|
23
|
+
),
|
|
24
|
+
heading3: (node: any, children: any) => (
|
|
25
|
+
<Text
|
|
26
|
+
key={node.key}
|
|
27
|
+
style={{ marginBottom: 6, marginTop: 8, fontWeight: '600', lineHeight: 22, color: textColor }}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</Text>
|
|
31
|
+
),
|
|
32
|
+
heading4: (node: any, children: any) => (
|
|
33
|
+
<Text
|
|
34
|
+
key={node.key}
|
|
35
|
+
style={{ marginBottom: 6, marginTop: 8, fontWeight: '500', lineHeight: 22, color: textColor }}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</Text>
|
|
39
|
+
),
|
|
40
|
+
heading5: (node: any, children: any) => (
|
|
41
|
+
<Text
|
|
42
|
+
key={node.key}
|
|
43
|
+
style={{ marginBottom: 4, marginTop: 8, fontWeight: '500', lineHeight: 22, color: textColor }}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</Text>
|
|
47
|
+
),
|
|
48
|
+
heading6: (node: any, children: any) => (
|
|
49
|
+
<Text
|
|
50
|
+
key={node.key}
|
|
51
|
+
style={{ marginBottom: 4, marginTop: 8, fontWeight: '500', lineHeight: 22, color: textColor }}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</Text>
|
|
55
|
+
),
|
|
56
|
+
code: (node: any, children: any, parent: any) => {
|
|
57
|
+
return parent.length > 1 ? (
|
|
58
|
+
<View
|
|
59
|
+
key={node.key}
|
|
60
|
+
style={{
|
|
61
|
+
marginVertical: 8,
|
|
62
|
+
overflow: 'hidden',
|
|
63
|
+
borderRadius: 6,
|
|
64
|
+
backgroundColor: isDark ? '#18181b' : '#f4f4f5',
|
|
65
|
+
borderWidth: 1,
|
|
66
|
+
borderColor: isDark ? '#3f3f46' : '#e4e4e7',
|
|
67
|
+
padding: 12,
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<Text style={{ fontSize: 13, fontFamily: monoFont }}>{children}</Text>
|
|
71
|
+
</View>
|
|
72
|
+
) : (
|
|
73
|
+
<Text
|
|
74
|
+
key={node.key}
|
|
75
|
+
style={{
|
|
76
|
+
backgroundColor: isDark ? '#27272a' : '#f4f4f5',
|
|
77
|
+
paddingHorizontal: 4,
|
|
78
|
+
paddingVertical: 1,
|
|
79
|
+
fontSize: 13,
|
|
80
|
+
fontFamily: monoFont,
|
|
81
|
+
borderRadius: 3,
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{children}
|
|
85
|
+
</Text>
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
list_item: (node: any, children: any, parent: any) => {
|
|
89
|
+
const isOrdered = parent[parent.length - 1]?.type === 'ordered_list';
|
|
90
|
+
const bullet = isOrdered
|
|
91
|
+
? `${parent[parent.length - 1].children.indexOf(node) + 1}.`
|
|
92
|
+
: '\u2022';
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<View key={node.key} style={{ flexDirection: 'row', paddingVertical: 2, paddingLeft: 16 }}>
|
|
96
|
+
<Text style={{ marginRight: 8, minWidth: 14, color: '#71717A' }}>{bullet}</Text>
|
|
97
|
+
<Text style={{ flex: 1, color: textColor }}>{children}</Text>
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
ordered_list: (node: any, children: any) => (
|
|
102
|
+
<View key={node.key} style={{ marginVertical: 8 }}>{children}</View>
|
|
103
|
+
),
|
|
104
|
+
unordered_list: (node: any, children: any) => (
|
|
105
|
+
<View key={node.key} style={{ marginVertical: 8 }}>{children}</View>
|
|
106
|
+
),
|
|
107
|
+
strong: (node: any, children: any) => (
|
|
108
|
+
<Text key={node.key} style={{ fontWeight: '600', color: textColor }}>{children}</Text>
|
|
109
|
+
),
|
|
110
|
+
link: (node: any, children: any) => (
|
|
111
|
+
<Text
|
|
112
|
+
key={node.key}
|
|
113
|
+
style={{ color: isDark ? '#60a5fa' : '#2563eb', textDecorationLine: 'underline' }}
|
|
114
|
+
onPress={() => {
|
|
115
|
+
if (node.attributes?.href) Linking.openURL(node.attributes.href);
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
{children}
|
|
119
|
+
</Text>
|
|
120
|
+
),
|
|
121
|
+
paragraph: (node: any, children: any) => (
|
|
122
|
+
<Text key={node.key} style={{ marginBottom: 8, color: textColor }}>{children}</Text>
|
|
123
|
+
),
|
|
124
|
+
blockquote: (node: any, children: any) => (
|
|
125
|
+
<View
|
|
126
|
+
key={node.key}
|
|
127
|
+
style={{
|
|
128
|
+
marginVertical: 8,
|
|
129
|
+
borderLeftWidth: 4,
|
|
130
|
+
borderLeftColor: isDark ? '#52525b' : '#d4d4d8',
|
|
131
|
+
backgroundColor: isDark ? 'rgba(39,39,42,0.5)' : 'rgba(244,244,245,0.5)',
|
|
132
|
+
paddingHorizontal: 12,
|
|
133
|
+
paddingVertical: 4,
|
|
134
|
+
borderTopRightRadius: 6,
|
|
135
|
+
borderBottomRightRadius: 6,
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
{children}
|
|
139
|
+
</View>
|
|
140
|
+
),
|
|
141
|
+
hr: (node: any) => (
|
|
142
|
+
<View
|
|
143
|
+
key={node.key}
|
|
144
|
+
style={{
|
|
145
|
+
marginVertical: 16,
|
|
146
|
+
height: 1,
|
|
147
|
+
backgroundColor: isDark ? '#3f3f46' : '#d4d4d8',
|
|
148
|
+
}}
|
|
149
|
+
/>
|
|
150
|
+
),
|
|
151
|
+
text: (node: any) => {
|
|
152
|
+
return node.content;
|
|
153
|
+
},
|
|
154
|
+
body: (node: any, children: any) => {
|
|
155
|
+
return <View key={node.key}>{children}</View>;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function AliaMarkdown({ content }: { content: string }) {
|
|
161
|
+
const colors = useAliaColors();
|
|
162
|
+
const isDark = colors.isDark;
|
|
163
|
+
|
|
164
|
+
const textColor = isDark ? '#ffffff' : '#0a0a0a';
|
|
165
|
+
const sansFont = Platform.select({ ios: 'Inter', android: 'Inter', default: 'Inter, sans-serif' });
|
|
166
|
+
const monoFont = Platform.select({ ios: 'Courier', android: 'monospace', default: 'monospace' });
|
|
167
|
+
|
|
168
|
+
const customRules = createRules(isDark, textColor, monoFont);
|
|
169
|
+
|
|
170
|
+
const markdownStyles = {
|
|
171
|
+
body: {
|
|
172
|
+
color: textColor,
|
|
173
|
+
fontSize: 16,
|
|
174
|
+
lineHeight: 28,
|
|
175
|
+
fontFamily: sansFont,
|
|
176
|
+
},
|
|
177
|
+
text: {
|
|
178
|
+
color: textColor,
|
|
179
|
+
fontSize: 16,
|
|
180
|
+
lineHeight: 28,
|
|
181
|
+
fontFamily: sansFont,
|
|
182
|
+
},
|
|
183
|
+
paragraph: {
|
|
184
|
+
marginTop: 0,
|
|
185
|
+
marginBottom: 8,
|
|
186
|
+
},
|
|
187
|
+
strong: {
|
|
188
|
+
fontWeight: '600' as const,
|
|
189
|
+
},
|
|
190
|
+
em: {
|
|
191
|
+
fontStyle: 'italic' as const,
|
|
192
|
+
},
|
|
193
|
+
s: {
|
|
194
|
+
textDecorationLine: 'line-through' as const,
|
|
195
|
+
},
|
|
196
|
+
bullet_list_icon: {
|
|
197
|
+
marginLeft: 0,
|
|
198
|
+
marginRight: 8,
|
|
199
|
+
fontSize: 16,
|
|
200
|
+
lineHeight: 28,
|
|
201
|
+
},
|
|
202
|
+
ordered_list_icon: {
|
|
203
|
+
marginLeft: 0,
|
|
204
|
+
marginRight: 8,
|
|
205
|
+
fontSize: 16,
|
|
206
|
+
lineHeight: 28,
|
|
207
|
+
},
|
|
208
|
+
bullet_list_content: {
|
|
209
|
+
flex: 1,
|
|
210
|
+
},
|
|
211
|
+
ordered_list_content: {
|
|
212
|
+
flex: 1,
|
|
213
|
+
},
|
|
214
|
+
code_inline: {
|
|
215
|
+
fontSize: 13,
|
|
216
|
+
fontFamily: monoFont,
|
|
217
|
+
backgroundColor: isDark ? '#27272a' : '#f4f4f5',
|
|
218
|
+
borderWidth: 0,
|
|
219
|
+
borderRadius: 3,
|
|
220
|
+
paddingHorizontal: 4,
|
|
221
|
+
paddingVertical: 1,
|
|
222
|
+
},
|
|
223
|
+
code_block: {
|
|
224
|
+
fontSize: 13,
|
|
225
|
+
fontFamily: monoFont,
|
|
226
|
+
backgroundColor: isDark ? '#18181b' : '#f4f4f5',
|
|
227
|
+
borderWidth: 1,
|
|
228
|
+
borderColor: isDark ? '#3f3f46' : '#e4e4e7',
|
|
229
|
+
borderRadius: 6,
|
|
230
|
+
padding: 12,
|
|
231
|
+
},
|
|
232
|
+
fence: {
|
|
233
|
+
fontSize: 13,
|
|
234
|
+
fontFamily: monoFont,
|
|
235
|
+
backgroundColor: isDark ? '#18181b' : '#f4f4f5',
|
|
236
|
+
borderWidth: 1,
|
|
237
|
+
borderColor: isDark ? '#3f3f46' : '#e4e4e7',
|
|
238
|
+
borderRadius: 6,
|
|
239
|
+
padding: 12,
|
|
240
|
+
},
|
|
241
|
+
table: {
|
|
242
|
+
borderWidth: 1,
|
|
243
|
+
borderColor: colors.border,
|
|
244
|
+
borderRadius: 6,
|
|
245
|
+
marginVertical: 8,
|
|
246
|
+
},
|
|
247
|
+
thead: {
|
|
248
|
+
backgroundColor: colors.muted,
|
|
249
|
+
},
|
|
250
|
+
th: {
|
|
251
|
+
flex: 1,
|
|
252
|
+
padding: 8,
|
|
253
|
+
fontWeight: '600' as const,
|
|
254
|
+
fontSize: 16,
|
|
255
|
+
lineHeight: 28,
|
|
256
|
+
},
|
|
257
|
+
td: {
|
|
258
|
+
flex: 1,
|
|
259
|
+
padding: 8,
|
|
260
|
+
fontSize: 16,
|
|
261
|
+
lineHeight: 28,
|
|
262
|
+
},
|
|
263
|
+
tr: {
|
|
264
|
+
borderBottomWidth: 1,
|
|
265
|
+
borderColor: colors.border,
|
|
266
|
+
flexDirection: 'row' as const,
|
|
267
|
+
},
|
|
268
|
+
link: {
|
|
269
|
+
textDecorationLine: 'underline' as const,
|
|
270
|
+
color: isDark ? '#60a5fa' : '#2563eb',
|
|
271
|
+
fontSize: 16,
|
|
272
|
+
lineHeight: 28,
|
|
273
|
+
},
|
|
274
|
+
hr: {
|
|
275
|
+
backgroundColor: isDark ? '#3f3f46' : '#d4d4d8',
|
|
276
|
+
height: 1,
|
|
277
|
+
marginVertical: 16,
|
|
278
|
+
},
|
|
279
|
+
heading1: { fontSize: 18, fontWeight: '600' as const },
|
|
280
|
+
heading2: { fontSize: 16, fontWeight: '600' as const },
|
|
281
|
+
heading3: { fontSize: 16, fontWeight: '600' as const },
|
|
282
|
+
heading4: { fontSize: 16, fontWeight: '500' as const },
|
|
283
|
+
heading5: { fontSize: 16, fontWeight: '500' as const },
|
|
284
|
+
heading6: { fontSize: 16, fontWeight: '500' as const },
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
return <Markdown rules={customRules} style={markdownStyles}>{content}</Markdown>;
|
|
288
|
+
}
|