@alia.onl/sdk 3.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 +1 -1
- package/src/components/AliaChatContent.tsx +62 -19
- package/src/components/AliaChatMessageList.tsx +437 -279
- package/src/components/AliaChatScreen.tsx +13 -5
- package/src/components/AliaChatSheet.tsx +11 -5
- 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/hooks/useAliaChat.ts +2 -1
- package/src/hooks/useSpeechToText.ts +21 -7
- package/src/index.ts +8 -1
- package/src/lib/content-utils.ts +23 -0
- package/src/lib/tool-registry.ts +55 -0
- package/src/types.ts +20 -7
- package/src/components/AliaChatSuggestions.tsx +0 -70
|
@@ -19,13 +19,11 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
19
19
|
import { AliaChatContent, type AliaChatContentRef } from './AliaChatContent';
|
|
20
20
|
import { AliaFace, type AliaExpression } from './AliaFace';
|
|
21
21
|
import { useAliaColors } from '../theme';
|
|
22
|
-
import type {
|
|
22
|
+
import type { WelcomeSuggestion } from './AliaWelcomeMessage';
|
|
23
23
|
|
|
24
24
|
export interface AliaChatScreenProps {
|
|
25
25
|
/** App context injected into system prompt */
|
|
26
26
|
clientContext?: string;
|
|
27
|
-
/** Quick action suggestions shown when chat is empty */
|
|
28
|
-
suggestions?: AliaChatSuggestion[];
|
|
29
27
|
/** Alia model (default: 'alia-v1') */
|
|
30
28
|
model?: string;
|
|
31
29
|
/** API URL override */
|
|
@@ -34,15 +32,23 @@ export interface AliaChatScreenProps {
|
|
|
34
32
|
headerLeft?: React.ReactNode;
|
|
35
33
|
/** Optional header right action (e.g., settings button) */
|
|
36
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[];
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
export function AliaChatScreen({
|
|
40
44
|
clientContext,
|
|
41
|
-
suggestions = [],
|
|
42
45
|
model,
|
|
43
46
|
apiUrl,
|
|
44
47
|
headerLeft,
|
|
45
48
|
headerRight,
|
|
49
|
+
welcomeGreeting,
|
|
50
|
+
welcomeSubtitle,
|
|
51
|
+
welcomeSuggestions,
|
|
46
52
|
}: AliaChatScreenProps) {
|
|
47
53
|
const colors = useAliaColors();
|
|
48
54
|
const insets = useSafeAreaInsets();
|
|
@@ -89,9 +95,11 @@ export function AliaChatScreen({
|
|
|
89
95
|
<AliaChatContent
|
|
90
96
|
ref={contentRef}
|
|
91
97
|
clientContext={clientContext}
|
|
92
|
-
suggestions={suggestions}
|
|
93
98
|
model={model}
|
|
94
99
|
apiUrl={apiUrl}
|
|
100
|
+
welcomeGreeting={welcomeGreeting}
|
|
101
|
+
welcomeSubtitle={welcomeSubtitle}
|
|
102
|
+
welcomeSuggestions={welcomeSuggestions}
|
|
95
103
|
/>
|
|
96
104
|
</View>
|
|
97
105
|
);
|
|
@@ -30,7 +30,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
30
30
|
import { AliaChatContent, type AliaChatContentRef } from './AliaChatContent';
|
|
31
31
|
import { AliaFace, type AliaExpression } from './AliaFace';
|
|
32
32
|
import { useAliaColors } from '../theme';
|
|
33
|
-
import type {
|
|
33
|
+
import type { WelcomeSuggestion } from './AliaWelcomeMessage';
|
|
34
34
|
|
|
35
35
|
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
|
|
36
36
|
|
|
@@ -43,12 +43,16 @@ const SPRING_CONFIG = {
|
|
|
43
43
|
export interface AliaChatSheetProps {
|
|
44
44
|
/** App context injected into system prompt */
|
|
45
45
|
clientContext?: string;
|
|
46
|
-
/** Quick action suggestions shown when chat is empty */
|
|
47
|
-
suggestions?: AliaChatSuggestion[];
|
|
48
46
|
/** Alia model (default: 'alia-v1') */
|
|
49
47
|
model?: string;
|
|
50
48
|
/** API URL override */
|
|
51
49
|
apiUrl?: string;
|
|
50
|
+
/** Welcome screen greeting */
|
|
51
|
+
welcomeGreeting?: string;
|
|
52
|
+
/** Welcome screen subtitle */
|
|
53
|
+
welcomeSubtitle?: string;
|
|
54
|
+
/** Welcome screen suggestions */
|
|
55
|
+
welcomeSuggestions?: WelcomeSuggestion[];
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
export interface AliaChatSheetRef {
|
|
@@ -57,7 +61,7 @@ export interface AliaChatSheetRef {
|
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
60
|
-
({ clientContext,
|
|
64
|
+
({ clientContext, model, apiUrl, welcomeGreeting, welcomeSubtitle, welcomeSuggestions }, ref) => {
|
|
61
65
|
const colors = useAliaColors();
|
|
62
66
|
const isDark = colors.isDark;
|
|
63
67
|
const insets = useSafeAreaInsets();
|
|
@@ -294,10 +298,12 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
294
298
|
<AliaChatContent
|
|
295
299
|
ref={contentRef}
|
|
296
300
|
clientContext={clientContext}
|
|
297
|
-
suggestions={suggestions}
|
|
298
301
|
model={model}
|
|
299
302
|
apiUrl={apiUrl}
|
|
300
303
|
scrollOffsetY={scrollOffsetY}
|
|
304
|
+
welcomeGreeting={welcomeGreeting}
|
|
305
|
+
welcomeSubtitle={welcomeSubtitle}
|
|
306
|
+
welcomeSuggestions={welcomeSuggestions}
|
|
301
307
|
/>
|
|
302
308
|
</GestureDetector>
|
|
303
309
|
</Animated.View>
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { View, Pressable } from 'react-native';
|
|
2
|
+
import { Text } from './ui/text';
|
|
3
|
+
import { Button } from './ui/button';
|
|
4
|
+
import { Check, X, ListChecks } from 'lucide-react-native';
|
|
5
|
+
import Animated, { FadeInUp } from 'react-native-reanimated';
|
|
6
|
+
import type { PlanStep } from '../types';
|
|
7
|
+
|
|
8
|
+
interface PlanPreviewCardProps {
|
|
9
|
+
steps: PlanStep[];
|
|
10
|
+
onApprove: () => void;
|
|
11
|
+
onReject: () => void;
|
|
12
|
+
approved?: boolean;
|
|
13
|
+
rejected?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function PlanPreviewCard({ steps, onApprove, onReject, approved, rejected }: PlanPreviewCardProps) {
|
|
17
|
+
if (approved) {
|
|
18
|
+
return (
|
|
19
|
+
<View className="flex-row items-center gap-1.5 py-1">
|
|
20
|
+
<Check size={12} className="text-green-500" />
|
|
21
|
+
<Text className="text-xs text-muted-foreground">Plan approved</Text>
|
|
22
|
+
</View>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (rejected) {
|
|
27
|
+
return (
|
|
28
|
+
<View className="flex-row items-center gap-1.5 py-1">
|
|
29
|
+
<X size={12} className="text-red-500" />
|
|
30
|
+
<Text className="text-xs text-muted-foreground">Plan cancelled</Text>
|
|
31
|
+
</View>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Animated.View entering={FadeInUp.springify()}>
|
|
37
|
+
<View className="border border-border rounded-xl bg-card p-4 gap-3 my-2">
|
|
38
|
+
<View className="flex-row items-center gap-2">
|
|
39
|
+
<ListChecks size={16} className="text-foreground" />
|
|
40
|
+
<Text className="text-sm font-semibold text-foreground">Here's my plan</Text>
|
|
41
|
+
</View>
|
|
42
|
+
|
|
43
|
+
<View className="gap-2">
|
|
44
|
+
{steps.map((step, i) => (
|
|
45
|
+
<View key={i} className="flex-row items-start gap-2.5">
|
|
46
|
+
<View className="w-5 h-5 rounded-full bg-muted items-center justify-center mt-0.5">
|
|
47
|
+
<Text className="text-[10px] font-bold text-muted-foreground">{i + 1}</Text>
|
|
48
|
+
</View>
|
|
49
|
+
<View className="flex-1">
|
|
50
|
+
<Text className="text-[13px] font-medium text-foreground">{step.action}</Text>
|
|
51
|
+
{step.description ? (
|
|
52
|
+
<Text className="text-xs text-muted-foreground">{step.description}</Text>
|
|
53
|
+
) : null}
|
|
54
|
+
</View>
|
|
55
|
+
</View>
|
|
56
|
+
))}
|
|
57
|
+
</View>
|
|
58
|
+
|
|
59
|
+
<View className="flex-row gap-2 pt-1">
|
|
60
|
+
<Button onPress={onApprove} size="sm" className="flex-1">
|
|
61
|
+
<View className="flex-row items-center gap-1.5">
|
|
62
|
+
<Check size={14} className="text-primary-foreground" />
|
|
63
|
+
<Text className="text-sm font-medium text-primary-foreground">Approve</Text>
|
|
64
|
+
</View>
|
|
65
|
+
</Button>
|
|
66
|
+
<Pressable
|
|
67
|
+
onPress={onReject}
|
|
68
|
+
className="flex-1 items-center justify-center py-2 rounded-lg active:opacity-70"
|
|
69
|
+
>
|
|
70
|
+
<Text className="text-sm text-muted-foreground">Cancel</Text>
|
|
71
|
+
</Pressable>
|
|
72
|
+
</View>
|
|
73
|
+
</View>
|
|
74
|
+
</Animated.View>
|
|
75
|
+
);
|
|
76
|
+
}
|