@alia.onl/sdk 2.0.0 → 3.1.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 +25 -3
- package/src/components/AliaChatContent.tsx +356 -0
- package/src/components/AliaChatInput.tsx +173 -26
- package/src/components/AliaChatMessageList.tsx +459 -168
- package/src/components/AliaChatScreen.tsx +140 -0
- package/src/components/AliaChatSheet.tsx +41 -44
- package/src/components/AliaWelcomeMessage.tsx +75 -0
- package/src/components/Markdown.tsx +58 -114
- package/src/components/PlanPreviewCard.tsx +76 -0
- package/src/components/ResearchProgressCard.tsx +159 -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/hooks/useAliaChat.ts +18 -8
- package/src/hooks/useDocumentPicker.ts +37 -0
- package/src/hooks/useImagePicker.ts +38 -0
- package/src/hooks/useSpeechToText.ts +25 -9
- package/src/index.ts +41 -3
- package/src/lib/content-utils.ts +23 -0
- package/src/lib/keyboard.native.tsx +6 -0
- package/src/lib/keyboard.tsx +25 -0
- package/src/lib/tool-registry.ts +55 -0
- package/src/lib/utils.ts +14 -0
- package/src/optional-deps.d.ts +17 -0
- package/src/types.ts +27 -10
- package/src/components/AliaChatSuggestions.tsx +0 -70
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-screen Alia chat component for use as a route/screen in Oxy ecosystem apps.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```tsx
|
|
6
|
+
* import { AliaChatScreen } from '@alia.onl/sdk';
|
|
7
|
+
*
|
|
8
|
+
* <AliaChatScreen
|
|
9
|
+
* clientContext="Notes app — user is viewing note #42"
|
|
10
|
+
* suggestions={[{ label: 'Summarize', prompt: 'Summarize this note' }]}
|
|
11
|
+
* headerLeft={<BackButton />}
|
|
12
|
+
* />
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
|
17
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
18
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
19
|
+
import { AliaChatContent, type AliaChatContentRef } from './AliaChatContent';
|
|
20
|
+
import { AliaFace, type AliaExpression } from './AliaFace';
|
|
21
|
+
import { useAliaColors } from '../theme';
|
|
22
|
+
import type { WelcomeSuggestion } from './AliaWelcomeMessage';
|
|
23
|
+
|
|
24
|
+
export interface AliaChatScreenProps {
|
|
25
|
+
/** App context injected into system prompt */
|
|
26
|
+
clientContext?: string;
|
|
27
|
+
/** Alia model (default: 'alia-v1') */
|
|
28
|
+
model?: string;
|
|
29
|
+
/** API URL override */
|
|
30
|
+
apiUrl?: string;
|
|
31
|
+
/** Optional header left action (e.g., back button) */
|
|
32
|
+
headerLeft?: React.ReactNode;
|
|
33
|
+
/** Optional header right action (e.g., settings button) */
|
|
34
|
+
headerRight?: React.ReactNode;
|
|
35
|
+
/** Welcome screen greeting */
|
|
36
|
+
welcomeGreeting?: string;
|
|
37
|
+
/** Welcome screen subtitle */
|
|
38
|
+
welcomeSubtitle?: string;
|
|
39
|
+
/** Welcome screen suggestions */
|
|
40
|
+
welcomeSuggestions?: WelcomeSuggestion[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function AliaChatScreen({
|
|
44
|
+
clientContext,
|
|
45
|
+
model,
|
|
46
|
+
apiUrl,
|
|
47
|
+
headerLeft,
|
|
48
|
+
headerRight,
|
|
49
|
+
welcomeGreeting,
|
|
50
|
+
welcomeSubtitle,
|
|
51
|
+
welcomeSuggestions,
|
|
52
|
+
}: AliaChatScreenProps) {
|
|
53
|
+
const colors = useAliaColors();
|
|
54
|
+
const insets = useSafeAreaInsets();
|
|
55
|
+
const contentRef = useRef<AliaChatContentRef>(null);
|
|
56
|
+
|
|
57
|
+
const [faceExpression, setFaceExpression] = useState<AliaExpression>('Idle A');
|
|
58
|
+
const [hasMessages, setHasMessages] = useState(false);
|
|
59
|
+
|
|
60
|
+
// Poll content ref for header state
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const interval = setInterval(() => {
|
|
63
|
+
if (contentRef.current) {
|
|
64
|
+
setFaceExpression(contentRef.current.faceExpression);
|
|
65
|
+
setHasMessages(contentRef.current.messages.length > 0);
|
|
66
|
+
}
|
|
67
|
+
}, 200);
|
|
68
|
+
return () => clearInterval(interval);
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const handleClear = useCallback(() => {
|
|
72
|
+
contentRef.current?.clear();
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<View style={[styles.container, { backgroundColor: colors.background, paddingTop: insets.top }]}>
|
|
77
|
+
{/* Header */}
|
|
78
|
+
<View style={styles.header}>
|
|
79
|
+
<View style={styles.headerLeft}>
|
|
80
|
+
{headerLeft}
|
|
81
|
+
<AliaFace size={28} expression={faceExpression} />
|
|
82
|
+
<Text style={[styles.headerTitle, { color: colors.text }]}>Alia</Text>
|
|
83
|
+
</View>
|
|
84
|
+
<View style={styles.headerRight}>
|
|
85
|
+
{hasMessages && (
|
|
86
|
+
<TouchableOpacity onPress={handleClear} style={styles.clearButton}>
|
|
87
|
+
<Text style={[styles.clearText, { color: colors.secondaryText }]}>Clear</Text>
|
|
88
|
+
</TouchableOpacity>
|
|
89
|
+
)}
|
|
90
|
+
{headerRight}
|
|
91
|
+
</View>
|
|
92
|
+
</View>
|
|
93
|
+
|
|
94
|
+
{/* Chat content */}
|
|
95
|
+
<AliaChatContent
|
|
96
|
+
ref={contentRef}
|
|
97
|
+
clientContext={clientContext}
|
|
98
|
+
model={model}
|
|
99
|
+
apiUrl={apiUrl}
|
|
100
|
+
welcomeGreeting={welcomeGreeting}
|
|
101
|
+
welcomeSubtitle={welcomeSubtitle}
|
|
102
|
+
welcomeSuggestions={welcomeSuggestions}
|
|
103
|
+
/>
|
|
104
|
+
</View>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const styles = StyleSheet.create({
|
|
109
|
+
container: {
|
|
110
|
+
flex: 1,
|
|
111
|
+
},
|
|
112
|
+
header: {
|
|
113
|
+
flexDirection: 'row',
|
|
114
|
+
alignItems: 'center',
|
|
115
|
+
justifyContent: 'space-between',
|
|
116
|
+
paddingHorizontal: 16,
|
|
117
|
+
paddingVertical: 12,
|
|
118
|
+
},
|
|
119
|
+
headerLeft: {
|
|
120
|
+
flexDirection: 'row',
|
|
121
|
+
alignItems: 'center',
|
|
122
|
+
gap: 10,
|
|
123
|
+
},
|
|
124
|
+
headerTitle: {
|
|
125
|
+
fontSize: 18,
|
|
126
|
+
fontWeight: '600',
|
|
127
|
+
},
|
|
128
|
+
headerRight: {
|
|
129
|
+
flexDirection: 'row',
|
|
130
|
+
alignItems: 'center',
|
|
131
|
+
gap: 8,
|
|
132
|
+
},
|
|
133
|
+
clearButton: {
|
|
134
|
+
paddingHorizontal: 10,
|
|
135
|
+
paddingVertical: 6,
|
|
136
|
+
},
|
|
137
|
+
clearText: {
|
|
138
|
+
fontSize: 14,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
@@ -27,13 +27,10 @@ import Animated, {
|
|
|
27
27
|
withTiming,
|
|
28
28
|
} from 'react-native-reanimated';
|
|
29
29
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
30
|
-
import {
|
|
31
|
-
import { AliaChatMessageList } from './AliaChatMessageList';
|
|
32
|
-
import { AliaChatInput } from './AliaChatInput';
|
|
33
|
-
import { AliaChatSuggestions } from './AliaChatSuggestions';
|
|
30
|
+
import { AliaChatContent, type AliaChatContentRef } from './AliaChatContent';
|
|
34
31
|
import { AliaFace, type AliaExpression } from './AliaFace';
|
|
35
32
|
import { useAliaColors } from '../theme';
|
|
36
|
-
import type {
|
|
33
|
+
import type { WelcomeSuggestion } from './AliaWelcomeMessage';
|
|
37
34
|
|
|
38
35
|
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
|
|
39
36
|
|
|
@@ -46,12 +43,16 @@ const SPRING_CONFIG = {
|
|
|
46
43
|
export interface AliaChatSheetProps {
|
|
47
44
|
/** App context injected into system prompt */
|
|
48
45
|
clientContext?: string;
|
|
49
|
-
/** Quick action suggestions shown when chat is empty */
|
|
50
|
-
suggestions?: AliaChatSuggestion[];
|
|
51
46
|
/** Alia model (default: 'alia-v1') */
|
|
52
47
|
model?: string;
|
|
53
48
|
/** API URL override */
|
|
54
49
|
apiUrl?: string;
|
|
50
|
+
/** Welcome screen greeting */
|
|
51
|
+
welcomeGreeting?: string;
|
|
52
|
+
/** Welcome screen subtitle */
|
|
53
|
+
welcomeSubtitle?: string;
|
|
54
|
+
/** Welcome screen suggestions */
|
|
55
|
+
welcomeSuggestions?: WelcomeSuggestion[];
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export interface AliaChatSheetRef {
|
|
@@ -60,20 +61,33 @@ export interface AliaChatSheetRef {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
63
|
-
({ clientContext,
|
|
64
|
+
({ clientContext, model, apiUrl, welcomeGreeting, welcomeSubtitle, welcomeSuggestions }, ref) => {
|
|
64
65
|
const colors = useAliaColors();
|
|
65
66
|
const isDark = colors.isDark;
|
|
66
67
|
const insets = useSafeAreaInsets();
|
|
67
68
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
const
|
|
69
|
+
// Content ref for reading state
|
|
70
|
+
const contentRef = useRef<AliaChatContentRef>(null);
|
|
71
|
+
const [faceExpression, setFaceExpression] = useState<AliaExpression>('Idle A');
|
|
72
|
+
const [hasMessages, setHasMessages] = useState(false);
|
|
71
73
|
|
|
72
74
|
// Sheet visibility
|
|
73
75
|
const [rendered, setRendered] = useState(false);
|
|
74
76
|
const hasClosedRef = useRef(false);
|
|
75
77
|
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
76
78
|
|
|
79
|
+
// Poll content ref for header state (lightweight, only when rendered)
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!rendered) return;
|
|
82
|
+
const interval = setInterval(() => {
|
|
83
|
+
if (contentRef.current) {
|
|
84
|
+
setFaceExpression(contentRef.current.faceExpression);
|
|
85
|
+
setHasMessages(contentRef.current.messages.length > 0);
|
|
86
|
+
}
|
|
87
|
+
}, 200);
|
|
88
|
+
return () => clearInterval(interval);
|
|
89
|
+
}, [rendered]);
|
|
90
|
+
|
|
77
91
|
// Reanimated shared values
|
|
78
92
|
const translateY = useSharedValue(SCREEN_HEIGHT);
|
|
79
93
|
const backdropOpacity = useSharedValue(0);
|
|
@@ -200,16 +214,9 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
200
214
|
[insets.top],
|
|
201
215
|
);
|
|
202
216
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const lastMsg = messages[messages.length - 1];
|
|
207
|
-
if (lastMsg?.role === 'assistant' && lastMsg.toolInvocations?.some(t => t.state === 'call')) return 'Searching A';
|
|
208
|
-
if (lastMsg?.thinking) return 'Thinking';
|
|
209
|
-
return 'Writing E';
|
|
210
|
-
}, [messages, isStreaming]);
|
|
211
|
-
|
|
212
|
-
const showSuggestions = messages.length === 0 && suggestions.length > 0;
|
|
217
|
+
const handleClear = useCallback(() => {
|
|
218
|
+
contentRef.current?.clear();
|
|
219
|
+
}, []);
|
|
213
220
|
|
|
214
221
|
if (!rendered) return null;
|
|
215
222
|
|
|
@@ -256,8 +263,8 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
256
263
|
</Text>
|
|
257
264
|
</View>
|
|
258
265
|
<View style={styles.headerRight}>
|
|
259
|
-
{
|
|
260
|
-
<TouchableOpacity onPress={
|
|
266
|
+
{hasMessages && (
|
|
267
|
+
<TouchableOpacity onPress={handleClear} style={styles.clearButton}>
|
|
261
268
|
<Text
|
|
262
269
|
style={[
|
|
263
270
|
styles.clearText,
|
|
@@ -286,26 +293,19 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
286
293
|
</View>
|
|
287
294
|
</View>
|
|
288
295
|
|
|
289
|
-
{/*
|
|
296
|
+
{/* Chat content */}
|
|
290
297
|
<GestureDetector gesture={nativeGesture}>
|
|
291
|
-
<
|
|
292
|
-
{
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
scrollOffsetY={scrollOffsetY}
|
|
302
|
-
/>
|
|
303
|
-
)}
|
|
304
|
-
</View>
|
|
298
|
+
<AliaChatContent
|
|
299
|
+
ref={contentRef}
|
|
300
|
+
clientContext={clientContext}
|
|
301
|
+
model={model}
|
|
302
|
+
apiUrl={apiUrl}
|
|
303
|
+
scrollOffsetY={scrollOffsetY}
|
|
304
|
+
welcomeGreeting={welcomeGreeting}
|
|
305
|
+
welcomeSubtitle={welcomeSubtitle}
|
|
306
|
+
welcomeSuggestions={welcomeSuggestions}
|
|
307
|
+
/>
|
|
305
308
|
</GestureDetector>
|
|
306
|
-
|
|
307
|
-
{/* Input */}
|
|
308
|
-
<AliaChatInput onSend={send} isStreaming={isStreaming} />
|
|
309
309
|
</Animated.View>
|
|
310
310
|
</GestureDetector>
|
|
311
311
|
</GestureHandlerRootView>
|
|
@@ -384,7 +384,4 @@ const styles = StyleSheet.create({
|
|
|
384
384
|
justifyContent: 'center',
|
|
385
385
|
borderRadius: 16,
|
|
386
386
|
},
|
|
387
|
-
chatArea: {
|
|
388
|
-
flex: 1,
|
|
389
|
-
},
|
|
390
387
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Pressable, StyleSheet, View } from 'react-native';
|
|
2
|
+
import { BlurView } from 'expo-blur';
|
|
3
|
+
import { Text } from './ui/text';
|
|
4
|
+
import Animated, { FadeIn } from 'react-native-reanimated';
|
|
5
|
+
import { AliaFace } from './AliaFace';
|
|
6
|
+
import type { WelcomeSuggestion } from '../types';
|
|
7
|
+
|
|
8
|
+
export type { WelcomeSuggestion };
|
|
9
|
+
|
|
10
|
+
export interface AliaWelcomeMessageProps {
|
|
11
|
+
greeting: string;
|
|
12
|
+
subtitle?: string;
|
|
13
|
+
suggestions?: WelcomeSuggestion[];
|
|
14
|
+
onSuggestionPress?: (text: string) => void;
|
|
15
|
+
faceSize?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function AliaWelcomeMessage({
|
|
19
|
+
greeting,
|
|
20
|
+
subtitle,
|
|
21
|
+
suggestions = [],
|
|
22
|
+
onSuggestionPress,
|
|
23
|
+
faceSize = 64,
|
|
24
|
+
}: AliaWelcomeMessageProps) {
|
|
25
|
+
return (
|
|
26
|
+
<View className="flex-1 items-center justify-center px-4">
|
|
27
|
+
<View className="w-full max-w-2xl">
|
|
28
|
+
{/* Face + Title */}
|
|
29
|
+
<View className="items-start mb-8">
|
|
30
|
+
<View className="mb-4">
|
|
31
|
+
<AliaFace size={faceSize} expression="Greeting" />
|
|
32
|
+
</View>
|
|
33
|
+
<View className="space-y-2">
|
|
34
|
+
<Text className="text-3xl font-bold tracking-tight text-foreground">
|
|
35
|
+
{greeting}
|
|
36
|
+
</Text>
|
|
37
|
+
{subtitle ? (
|
|
38
|
+
<Text className="text-xl font-medium text-muted-foreground">
|
|
39
|
+
{subtitle}
|
|
40
|
+
</Text>
|
|
41
|
+
) : null}
|
|
42
|
+
</View>
|
|
43
|
+
</View>
|
|
44
|
+
|
|
45
|
+
{/* Suggestion Grid */}
|
|
46
|
+
{suggestions.length > 0 && (
|
|
47
|
+
<Animated.View entering={FadeIn.duration(400)}>
|
|
48
|
+
<View className="flex-row flex-wrap gap-2">
|
|
49
|
+
{suggestions.map((item) => (
|
|
50
|
+
<Pressable
|
|
51
|
+
key={item.id}
|
|
52
|
+
className="flex-1 min-w-[35%] flex-col items-start rounded-3xl border border-border overflow-hidden active:bg-muted/50"
|
|
53
|
+
onPress={() => onSuggestionPress?.(item.description)}
|
|
54
|
+
>
|
|
55
|
+
<BlurView intensity={60} tint="default" style={StyleSheet.absoluteFill} />
|
|
56
|
+
<View className="p-4 w-full">
|
|
57
|
+
<Text className="text-sm font-medium text-surface-foreground mb-1" numberOfLines={1}>
|
|
58
|
+
{item.title}
|
|
59
|
+
</Text>
|
|
60
|
+
<Text
|
|
61
|
+
className="text-xs text-muted-foreground line-clamp-1"
|
|
62
|
+
numberOfLines={1}
|
|
63
|
+
>
|
|
64
|
+
{item.description}
|
|
65
|
+
</Text>
|
|
66
|
+
</View>
|
|
67
|
+
</Pressable>
|
|
68
|
+
))}
|
|
69
|
+
</View>
|
|
70
|
+
</Animated.View>
|
|
71
|
+
)}
|
|
72
|
+
</View>
|
|
73
|
+
</View>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { View, Text, Platform, Linking } from 'react-native';
|
|
3
3
|
import Markdown from 'react-native-markdown-display';
|
|
4
4
|
import { useAliaColors } from '../theme';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const BODY_TEXT = { fontSize: 16, lineHeight: 28 } as const;
|
|
7
|
+
const HEADING_TEXT = { fontSize: 16, lineHeight: 22 } as const;
|
|
8
|
+
const SANS_FONT = Platform.select({ ios: 'Inter', android: 'Inter', default: 'Inter, sans-serif' })!;
|
|
9
|
+
const MONO_FONT = Platform.select({ ios: 'Courier', android: 'monospace', default: 'monospace' })!;
|
|
10
|
+
|
|
11
|
+
function createRules(isDark: boolean, textColor: string) {
|
|
7
12
|
return {
|
|
8
13
|
heading1: (node: any, children: any) => (
|
|
9
14
|
<Text
|
|
@@ -16,7 +21,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
16
21
|
heading2: (node: any, children: any) => (
|
|
17
22
|
<Text
|
|
18
23
|
key={node.key}
|
|
19
|
-
style={{ marginBottom: 8, marginTop: 12, fontWeight: '600',
|
|
24
|
+
style={{ marginBottom: 8, marginTop: 12, ...HEADING_TEXT, fontWeight: '600', color: textColor }}
|
|
20
25
|
>
|
|
21
26
|
{children}
|
|
22
27
|
</Text>
|
|
@@ -24,7 +29,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
24
29
|
heading3: (node: any, children: any) => (
|
|
25
30
|
<Text
|
|
26
31
|
key={node.key}
|
|
27
|
-
style={{ marginBottom: 6, marginTop: 8, fontWeight: '600',
|
|
32
|
+
style={{ marginBottom: 6, marginTop: 8, ...HEADING_TEXT, fontWeight: '600', color: textColor }}
|
|
28
33
|
>
|
|
29
34
|
{children}
|
|
30
35
|
</Text>
|
|
@@ -32,7 +37,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
32
37
|
heading4: (node: any, children: any) => (
|
|
33
38
|
<Text
|
|
34
39
|
key={node.key}
|
|
35
|
-
style={{ marginBottom: 6, marginTop: 8, fontWeight: '500',
|
|
40
|
+
style={{ marginBottom: 6, marginTop: 8, ...HEADING_TEXT, fontWeight: '500', color: textColor }}
|
|
36
41
|
>
|
|
37
42
|
{children}
|
|
38
43
|
</Text>
|
|
@@ -40,7 +45,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
40
45
|
heading5: (node: any, children: any) => (
|
|
41
46
|
<Text
|
|
42
47
|
key={node.key}
|
|
43
|
-
style={{ marginBottom: 4, marginTop: 8, fontWeight: '500',
|
|
48
|
+
style={{ marginBottom: 4, marginTop: 8, ...HEADING_TEXT, fontWeight: '500', color: textColor }}
|
|
44
49
|
>
|
|
45
50
|
{children}
|
|
46
51
|
</Text>
|
|
@@ -48,7 +53,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
48
53
|
heading6: (node: any, children: any) => (
|
|
49
54
|
<Text
|
|
50
55
|
key={node.key}
|
|
51
|
-
style={{ marginBottom: 4, marginTop: 8, fontWeight: '500',
|
|
56
|
+
style={{ marginBottom: 4, marginTop: 8, ...HEADING_TEXT, fontWeight: '500', color: textColor }}
|
|
52
57
|
>
|
|
53
58
|
{children}
|
|
54
59
|
</Text>
|
|
@@ -67,7 +72,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
67
72
|
padding: 12,
|
|
68
73
|
}}
|
|
69
74
|
>
|
|
70
|
-
<Text style={{ fontSize: 13, fontFamily:
|
|
75
|
+
<Text style={{ fontSize: 13, fontFamily: MONO_FONT }}>{children}</Text>
|
|
71
76
|
</View>
|
|
72
77
|
) : (
|
|
73
78
|
<Text
|
|
@@ -77,7 +82,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
77
82
|
paddingHorizontal: 4,
|
|
78
83
|
paddingVertical: 1,
|
|
79
84
|
fontSize: 13,
|
|
80
|
-
fontFamily:
|
|
85
|
+
fontFamily: MONO_FONT,
|
|
81
86
|
borderRadius: 3,
|
|
82
87
|
}}
|
|
83
88
|
>
|
|
@@ -93,8 +98,8 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
93
98
|
|
|
94
99
|
return (
|
|
95
100
|
<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>
|
|
101
|
+
<Text style={{ marginRight: 8, minWidth: 14, ...BODY_TEXT, color: '#71717A' }}>{bullet}</Text>
|
|
102
|
+
<Text style={{ flex: 1, ...BODY_TEXT, color: textColor }}>{children}</Text>
|
|
98
103
|
</View>
|
|
99
104
|
);
|
|
100
105
|
},
|
|
@@ -105,12 +110,12 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
105
110
|
<View key={node.key} style={{ marginVertical: 8 }}>{children}</View>
|
|
106
111
|
),
|
|
107
112
|
strong: (node: any, children: any) => (
|
|
108
|
-
<Text key={node.key} style={{ fontWeight: '600', color: textColor }}>{children}</Text>
|
|
113
|
+
<Text key={node.key} style={{ fontWeight: '600', ...BODY_TEXT, color: textColor }}>{children}</Text>
|
|
109
114
|
),
|
|
110
115
|
link: (node: any, children: any) => (
|
|
111
116
|
<Text
|
|
112
117
|
key={node.key}
|
|
113
|
-
style={{ color: isDark ? '#60a5fa' : '#2563eb', textDecorationLine: 'underline' }}
|
|
118
|
+
style={{ color: isDark ? '#60a5fa' : '#2563eb', textDecorationLine: 'underline', ...BODY_TEXT }}
|
|
114
119
|
onPress={() => {
|
|
115
120
|
if (node.attributes?.href) Linking.openURL(node.attributes.href);
|
|
116
121
|
}}
|
|
@@ -119,7 +124,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
119
124
|
</Text>
|
|
120
125
|
),
|
|
121
126
|
paragraph: (node: any, children: any) => (
|
|
122
|
-
<Text key={node.key} style={{ marginBottom: 8, color: textColor }}>{children}</Text>
|
|
127
|
+
<Text key={node.key} style={{ marginBottom: 8, ...BODY_TEXT, color: textColor }}>{children}</Text>
|
|
123
128
|
),
|
|
124
129
|
blockquote: (node: any, children: any) => (
|
|
125
130
|
<View
|
|
@@ -157,63 +162,21 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
157
162
|
};
|
|
158
163
|
}
|
|
159
164
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
},
|
|
165
|
+
function createStyles(isDark: boolean, textColor: string, borderColor: string, mutedBg: string) {
|
|
166
|
+
return {
|
|
167
|
+
body: { ...BODY_TEXT, color: textColor, fontFamily: SANS_FONT },
|
|
168
|
+
text: { ...BODY_TEXT, color: textColor, fontFamily: SANS_FONT },
|
|
169
|
+
paragraph: { marginTop: 0, marginBottom: 8 },
|
|
170
|
+
strong: { fontWeight: '600' as const },
|
|
171
|
+
em: { fontStyle: 'italic' as const },
|
|
172
|
+
s: { textDecorationLine: 'line-through' as const },
|
|
173
|
+
bullet_list_icon: { marginLeft: 0, marginRight: 8, ...BODY_TEXT },
|
|
174
|
+
ordered_list_icon: { marginLeft: 0, marginRight: 8, ...BODY_TEXT },
|
|
175
|
+
bullet_list_content: { flex: 1 },
|
|
176
|
+
ordered_list_content: { flex: 1 },
|
|
214
177
|
code_inline: {
|
|
215
178
|
fontSize: 13,
|
|
216
|
-
fontFamily:
|
|
179
|
+
fontFamily: MONO_FONT,
|
|
217
180
|
backgroundColor: isDark ? '#27272a' : '#f4f4f5',
|
|
218
181
|
borderWidth: 0,
|
|
219
182
|
borderRadius: 3,
|
|
@@ -222,7 +185,7 @@ export function AliaMarkdown({ content }: { content: string }) {
|
|
|
222
185
|
},
|
|
223
186
|
code_block: {
|
|
224
187
|
fontSize: 13,
|
|
225
|
-
fontFamily:
|
|
188
|
+
fontFamily: MONO_FONT,
|
|
226
189
|
backgroundColor: isDark ? '#18181b' : '#f4f4f5',
|
|
227
190
|
borderWidth: 1,
|
|
228
191
|
borderColor: isDark ? '#3f3f46' : '#e4e4e7',
|
|
@@ -231,58 +194,39 @@ export function AliaMarkdown({ content }: { content: string }) {
|
|
|
231
194
|
},
|
|
232
195
|
fence: {
|
|
233
196
|
fontSize: 13,
|
|
234
|
-
fontFamily:
|
|
197
|
+
fontFamily: MONO_FONT,
|
|
235
198
|
backgroundColor: isDark ? '#18181b' : '#f4f4f5',
|
|
236
199
|
borderWidth: 1,
|
|
237
200
|
borderColor: isDark ? '#3f3f46' : '#e4e4e7',
|
|
238
201
|
borderRadius: 6,
|
|
239
202
|
padding: 12,
|
|
240
203
|
},
|
|
241
|
-
table: {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
},
|
|
247
|
-
|
|
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
|
-
},
|
|
204
|
+
table: { borderWidth: 1, borderColor, borderRadius: 6, marginVertical: 8 },
|
|
205
|
+
thead: { backgroundColor: mutedBg },
|
|
206
|
+
th: { flex: 1, padding: 8, fontWeight: '600' as const, ...BODY_TEXT },
|
|
207
|
+
td: { flex: 1, padding: 8, ...BODY_TEXT },
|
|
208
|
+
tr: { borderBottomWidth: 1, borderColor, flexDirection: 'row' as const },
|
|
209
|
+
link: { textDecorationLine: 'underline' as const, color: isDark ? '#60a5fa' : '#2563eb', ...BODY_TEXT },
|
|
210
|
+
hr: { backgroundColor: isDark ? '#3f3f46' : '#d4d4d8', height: 1, marginVertical: 16 },
|
|
279
211
|
heading1: { fontSize: 18, fontWeight: '600' as const },
|
|
280
|
-
heading2: {
|
|
281
|
-
heading3: {
|
|
282
|
-
heading4: {
|
|
283
|
-
heading5: {
|
|
284
|
-
heading6: {
|
|
212
|
+
heading2: { ...HEADING_TEXT, fontWeight: '600' as const },
|
|
213
|
+
heading3: { ...HEADING_TEXT, fontWeight: '600' as const },
|
|
214
|
+
heading4: { ...HEADING_TEXT, fontWeight: '500' as const },
|
|
215
|
+
heading5: { ...HEADING_TEXT, fontWeight: '500' as const },
|
|
216
|
+
heading6: { ...HEADING_TEXT, fontWeight: '500' as const },
|
|
285
217
|
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function AliaMarkdown({ content }: { content: string }) {
|
|
221
|
+
const colors = useAliaColors();
|
|
222
|
+
const isDark = colors.isDark;
|
|
223
|
+
const textColor = isDark ? '#ffffff' : '#0a0a0a';
|
|
224
|
+
|
|
225
|
+
const customRules = useMemo(() => createRules(isDark, textColor), [isDark]);
|
|
226
|
+
const markdownStyles = useMemo(
|
|
227
|
+
() => createStyles(isDark, textColor, colors.border, colors.muted),
|
|
228
|
+
[isDark, colors.border, colors.muted],
|
|
229
|
+
);
|
|
286
230
|
|
|
287
231
|
return <Markdown rules={customRules} style={markdownStyles}>{content}</Markdown>;
|
|
288
232
|
}
|