@alia.onl/sdk 1.0.0 → 2.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.
@@ -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
+ }