@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.
Files changed (44) hide show
  1. package/package.json +37 -3
  2. package/src/components/AliaChatContent.tsx +313 -0
  3. package/src/components/AliaChatInput.tsx +174 -27
  4. package/src/components/AliaChatMessageList.tsx +220 -34
  5. package/src/components/AliaChatScreen.tsx +132 -0
  6. package/src/components/AliaChatSheet.tsx +36 -49
  7. package/src/components/AliaChatSuggestions.tsx +1 -1
  8. package/src/components/AliaFace.tsx +402 -0
  9. package/src/components/Markdown.tsx +288 -0
  10. package/src/components/Reasoning.tsx +154 -0
  11. package/src/components/ThinkingIndicator.tsx +134 -0
  12. package/src/components/ui/button.tsx +104 -0
  13. package/src/components/ui/chat-text-input.tsx +164 -0
  14. package/src/components/ui/dropdown-menu.tsx +2 -0
  15. package/src/components/ui/dropdown-menu.web.tsx +355 -0
  16. package/src/components/ui/prompt-input/actions.tsx +28 -0
  17. package/src/components/ui/prompt-input/add-menu.tsx +83 -0
  18. package/src/components/ui/prompt-input/attachments.tsx +183 -0
  19. package/src/components/ui/prompt-input/autocomplete.tsx +168 -0
  20. package/src/components/ui/prompt-input/context.tsx +71 -0
  21. package/src/components/ui/prompt-input/mic-button.tsx +53 -0
  22. package/src/components/ui/prompt-input/prompt-input.tsx +276 -0
  23. package/src/components/ui/prompt-input/submit-button.tsx +49 -0
  24. package/src/components/ui/prompt-input/textarea.tsx +64 -0
  25. package/src/components/ui/text.tsx +29 -0
  26. package/src/components/voice/AudioWaveVisualizer.tsx +377 -0
  27. package/src/components/voice/VoiceControls.tsx +180 -0
  28. package/src/components/voice/VoiceOverlay.tsx +43 -0
  29. package/src/hooks/useAliaChat.ts +278 -91
  30. package/src/hooks/useAudioLevelMonitor.ts +142 -0
  31. package/src/hooks/useAudioLevels.ts +51 -0
  32. package/src/hooks/useDocumentPicker.ts +37 -0
  33. package/src/hooks/useImagePicker.ts +38 -0
  34. package/src/hooks/useSoundEffects.ts +128 -0
  35. package/src/hooks/useSpeechToText.ts +182 -0
  36. package/src/hooks/useTTS.ts +233 -0
  37. package/src/hooks/useVoiceRoom.ts +420 -0
  38. package/src/index.ts +71 -6
  39. package/src/lib/keyboard.native.tsx +6 -0
  40. package/src/lib/keyboard.tsx +25 -0
  41. package/src/lib/utils.ts +14 -0
  42. package/src/optional-deps.d.ts +86 -0
  43. package/src/theme.ts +25 -2
  44. 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
+ }