@alia.onl/sdk 3.0.0 → 3.1.1
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 +47 -19
- package/src/components/AliaChatContent.tsx +62 -19
- package/src/components/AliaChatMessageList.tsx +437 -280
- package/src/components/AliaChatScreen.tsx +21 -48
- package/src/components/AliaChatSheet.tsx +19 -31
- package/src/components/AliaFace.tsx +108 -58
- package/src/components/AliaWelcomeMessage.tsx +75 -0
- package/src/components/Markdown.tsx +94 -130
- package/src/components/PlanPreviewCard.tsx +76 -0
- package/src/components/Reasoning.tsx +1 -3
- package/src/components/ResearchProgressCard.tsx +158 -0
- package/src/components/ThinkingIndicator.tsx +6 -21
- package/src/components/voice/AudioWaveVisualizer.tsx +72 -22
- package/src/components/voice/VoiceControls.tsx +25 -42
- package/src/components/voice/VoiceOverlay.tsx +5 -1
- package/src/hooks/useAliaChat.ts +2 -1
- package/src/hooks/useSpeechToText.ts +21 -7
- package/src/hooks/useVoiceRoom.ts +24 -8
- package/src/index.ts +10 -3
- package/src/lib/content-utils.ts +23 -0
- package/src/lib/tool-registry.ts +55 -0
- package/src/theme.ts +2 -41
- package/src/types.ts +20 -7
- package/src/components/AliaChatInput.tsx +0 -294
- package/src/components/AliaChatSuggestions.tsx +0 -70
|
@@ -1,9 +1,33 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, Text, Platform, Linking } from 'react-native';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { View, Text, Platform, Linking, useColorScheme } from 'react-native';
|
|
3
3
|
import Markdown from 'react-native-markdown-display';
|
|
4
|
-
import {
|
|
4
|
+
import type { AliaColors } from '../theme';
|
|
5
|
+
|
|
6
|
+
// Hardcoded fallback colors for standalone SDK usage (when no color override is passed).
|
|
7
|
+
// The main app always passes resolved colors via the `colors` prop.
|
|
8
|
+
const FALLBACK_LIGHT: Pick<AliaColors, 'text' | 'muted' | 'border' | 'primary' | 'mutedForeground'> = {
|
|
9
|
+
text: '#11181C',
|
|
10
|
+
muted: '#F4F4F5',
|
|
11
|
+
border: '#E5E5EA',
|
|
12
|
+
primary: '#7C3AED',
|
|
13
|
+
mutedForeground: '#71717A',
|
|
14
|
+
};
|
|
15
|
+
const FALLBACK_DARK: typeof FALLBACK_LIGHT = {
|
|
16
|
+
text: '#ECEDEE',
|
|
17
|
+
muted: '#27272A',
|
|
18
|
+
border: '#2C2C2E',
|
|
19
|
+
primary: '#A78BFA',
|
|
20
|
+
mutedForeground: '#A1A1AA',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const BODY_TEXT = { fontSize: 16, lineHeight: 28 } as const;
|
|
24
|
+
const HEADING_TEXT = { fontSize: 16, lineHeight: 22 } as const;
|
|
25
|
+
const SANS_FONT = Platform.select({ ios: 'Inter', android: 'Inter', default: 'Inter, sans-serif' })!;
|
|
26
|
+
const MONO_FONT = Platform.select({ ios: 'Courier', android: 'monospace', default: 'monospace' })!;
|
|
27
|
+
|
|
28
|
+
function createRules(colors: AliaColors) {
|
|
29
|
+
const { text: textColor, muted, border, primary, mutedForeground } = colors;
|
|
5
30
|
|
|
6
|
-
function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
7
31
|
return {
|
|
8
32
|
heading1: (node: any, children: any) => (
|
|
9
33
|
<Text
|
|
@@ -16,7 +40,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
16
40
|
heading2: (node: any, children: any) => (
|
|
17
41
|
<Text
|
|
18
42
|
key={node.key}
|
|
19
|
-
style={{ marginBottom: 8, marginTop: 12, fontWeight: '600',
|
|
43
|
+
style={{ marginBottom: 8, marginTop: 12, ...HEADING_TEXT, fontWeight: '600', color: textColor }}
|
|
20
44
|
>
|
|
21
45
|
{children}
|
|
22
46
|
</Text>
|
|
@@ -24,7 +48,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
24
48
|
heading3: (node: any, children: any) => (
|
|
25
49
|
<Text
|
|
26
50
|
key={node.key}
|
|
27
|
-
style={{ marginBottom: 6, marginTop: 8, fontWeight: '600',
|
|
51
|
+
style={{ marginBottom: 6, marginTop: 8, ...HEADING_TEXT, fontWeight: '600', color: textColor }}
|
|
28
52
|
>
|
|
29
53
|
{children}
|
|
30
54
|
</Text>
|
|
@@ -32,7 +56,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
32
56
|
heading4: (node: any, children: any) => (
|
|
33
57
|
<Text
|
|
34
58
|
key={node.key}
|
|
35
|
-
style={{ marginBottom: 6, marginTop: 8, fontWeight: '500',
|
|
59
|
+
style={{ marginBottom: 6, marginTop: 8, ...HEADING_TEXT, fontWeight: '500', color: textColor }}
|
|
36
60
|
>
|
|
37
61
|
{children}
|
|
38
62
|
</Text>
|
|
@@ -40,7 +64,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
40
64
|
heading5: (node: any, children: any) => (
|
|
41
65
|
<Text
|
|
42
66
|
key={node.key}
|
|
43
|
-
style={{ marginBottom: 4, marginTop: 8, fontWeight: '500',
|
|
67
|
+
style={{ marginBottom: 4, marginTop: 8, ...HEADING_TEXT, fontWeight: '500', color: textColor }}
|
|
44
68
|
>
|
|
45
69
|
{children}
|
|
46
70
|
</Text>
|
|
@@ -48,7 +72,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
48
72
|
heading6: (node: any, children: any) => (
|
|
49
73
|
<Text
|
|
50
74
|
key={node.key}
|
|
51
|
-
style={{ marginBottom: 4, marginTop: 8, fontWeight: '500',
|
|
75
|
+
style={{ marginBottom: 4, marginTop: 8, ...HEADING_TEXT, fontWeight: '500', color: textColor }}
|
|
52
76
|
>
|
|
53
77
|
{children}
|
|
54
78
|
</Text>
|
|
@@ -61,23 +85,23 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
61
85
|
marginVertical: 8,
|
|
62
86
|
overflow: 'hidden',
|
|
63
87
|
borderRadius: 6,
|
|
64
|
-
backgroundColor:
|
|
88
|
+
backgroundColor: muted,
|
|
65
89
|
borderWidth: 1,
|
|
66
|
-
borderColor:
|
|
90
|
+
borderColor: border,
|
|
67
91
|
padding: 12,
|
|
68
92
|
}}
|
|
69
93
|
>
|
|
70
|
-
<Text style={{ fontSize: 13, fontFamily:
|
|
94
|
+
<Text style={{ fontSize: 13, fontFamily: MONO_FONT }}>{children}</Text>
|
|
71
95
|
</View>
|
|
72
96
|
) : (
|
|
73
97
|
<Text
|
|
74
98
|
key={node.key}
|
|
75
99
|
style={{
|
|
76
|
-
backgroundColor:
|
|
100
|
+
backgroundColor: muted,
|
|
77
101
|
paddingHorizontal: 4,
|
|
78
102
|
paddingVertical: 1,
|
|
79
103
|
fontSize: 13,
|
|
80
|
-
fontFamily:
|
|
104
|
+
fontFamily: MONO_FONT,
|
|
81
105
|
borderRadius: 3,
|
|
82
106
|
}}
|
|
83
107
|
>
|
|
@@ -93,8 +117,8 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
93
117
|
|
|
94
118
|
return (
|
|
95
119
|
<View key={node.key} style={{ flexDirection: 'row', paddingVertical: 2, paddingLeft: 16 }}>
|
|
96
|
-
<Text style={{ marginRight: 8, minWidth: 14, color:
|
|
97
|
-
<Text style={{ flex: 1, color: textColor }}>{children}</Text>
|
|
120
|
+
<Text style={{ marginRight: 8, minWidth: 14, ...BODY_TEXT, color: mutedForeground }}>{bullet}</Text>
|
|
121
|
+
<Text style={{ flex: 1, ...BODY_TEXT, color: textColor }}>{children}</Text>
|
|
98
122
|
</View>
|
|
99
123
|
);
|
|
100
124
|
},
|
|
@@ -105,12 +129,12 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
105
129
|
<View key={node.key} style={{ marginVertical: 8 }}>{children}</View>
|
|
106
130
|
),
|
|
107
131
|
strong: (node: any, children: any) => (
|
|
108
|
-
<Text key={node.key} style={{ fontWeight: '600', color: textColor }}>{children}</Text>
|
|
132
|
+
<Text key={node.key} style={{ fontWeight: '600', ...BODY_TEXT, color: textColor }}>{children}</Text>
|
|
109
133
|
),
|
|
110
134
|
link: (node: any, children: any) => (
|
|
111
135
|
<Text
|
|
112
136
|
key={node.key}
|
|
113
|
-
style={{ color:
|
|
137
|
+
style={{ color: primary, textDecorationLine: 'underline', ...BODY_TEXT }}
|
|
114
138
|
onPress={() => {
|
|
115
139
|
if (node.attributes?.href) Linking.openURL(node.attributes.href);
|
|
116
140
|
}}
|
|
@@ -119,7 +143,7 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
119
143
|
</Text>
|
|
120
144
|
),
|
|
121
145
|
paragraph: (node: any, children: any) => (
|
|
122
|
-
<Text key={node.key} style={{ marginBottom: 8, color: textColor }}>{children}</Text>
|
|
146
|
+
<Text key={node.key} style={{ marginBottom: 8, ...BODY_TEXT, color: textColor }}>{children}</Text>
|
|
123
147
|
),
|
|
124
148
|
blockquote: (node: any, children: any) => (
|
|
125
149
|
<View
|
|
@@ -127,8 +151,8 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
127
151
|
style={{
|
|
128
152
|
marginVertical: 8,
|
|
129
153
|
borderLeftWidth: 4,
|
|
130
|
-
borderLeftColor:
|
|
131
|
-
backgroundColor:
|
|
154
|
+
borderLeftColor: border,
|
|
155
|
+
backgroundColor: muted,
|
|
132
156
|
paddingHorizontal: 12,
|
|
133
157
|
paddingVertical: 4,
|
|
134
158
|
borderTopRightRadius: 6,
|
|
@@ -144,77 +168,34 @@ function createRules(isDark: boolean, textColor: string, monoFont: string) {
|
|
|
144
168
|
style={{
|
|
145
169
|
marginVertical: 16,
|
|
146
170
|
height: 1,
|
|
147
|
-
backgroundColor:
|
|
171
|
+
backgroundColor: border,
|
|
148
172
|
}}
|
|
149
173
|
/>
|
|
150
174
|
),
|
|
151
|
-
text: (node: any) => {
|
|
152
|
-
return node.content;
|
|
153
|
-
},
|
|
154
175
|
body: (node: any, children: any) => {
|
|
155
|
-
return <View key={node.key}>{children}</View>;
|
|
176
|
+
return <View key={node.key} style={{ marginBottom: -8 }}>{children}</View>;
|
|
156
177
|
},
|
|
157
178
|
};
|
|
158
179
|
}
|
|
159
180
|
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
const isDark = colors.isDark;
|
|
181
|
+
function createStyles(colors: AliaColors) {
|
|
182
|
+
const { text: textColor, muted, border, primary, mutedForeground } = colors;
|
|
163
183
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
},
|
|
184
|
+
return {
|
|
185
|
+
body: { ...BODY_TEXT, color: textColor, fontFamily: SANS_FONT },
|
|
186
|
+
text: { ...BODY_TEXT, color: textColor, fontFamily: SANS_FONT },
|
|
187
|
+
paragraph: { marginTop: 0, marginBottom: 8 },
|
|
188
|
+
strong: { fontWeight: '600' as const },
|
|
189
|
+
em: { fontStyle: 'italic' as const },
|
|
190
|
+
s: { textDecorationLine: 'line-through' as const },
|
|
191
|
+
bullet_list_icon: { marginLeft: 0, marginRight: 8, ...BODY_TEXT, color: mutedForeground },
|
|
192
|
+
ordered_list_icon: { marginLeft: 0, marginRight: 8, ...BODY_TEXT, color: mutedForeground },
|
|
193
|
+
bullet_list_content: { flex: 1 },
|
|
194
|
+
ordered_list_content: { flex: 1 },
|
|
214
195
|
code_inline: {
|
|
215
196
|
fontSize: 13,
|
|
216
|
-
fontFamily:
|
|
217
|
-
backgroundColor:
|
|
197
|
+
fontFamily: MONO_FONT,
|
|
198
|
+
backgroundColor: muted,
|
|
218
199
|
borderWidth: 0,
|
|
219
200
|
borderRadius: 3,
|
|
220
201
|
paddingHorizontal: 4,
|
|
@@ -222,67 +203,50 @@ export function AliaMarkdown({ content }: { content: string }) {
|
|
|
222
203
|
},
|
|
223
204
|
code_block: {
|
|
224
205
|
fontSize: 13,
|
|
225
|
-
fontFamily:
|
|
226
|
-
backgroundColor:
|
|
206
|
+
fontFamily: MONO_FONT,
|
|
207
|
+
backgroundColor: muted,
|
|
227
208
|
borderWidth: 1,
|
|
228
|
-
borderColor:
|
|
209
|
+
borderColor: border,
|
|
229
210
|
borderRadius: 6,
|
|
230
211
|
padding: 12,
|
|
231
212
|
},
|
|
232
213
|
fence: {
|
|
233
214
|
fontSize: 13,
|
|
234
|
-
fontFamily:
|
|
235
|
-
backgroundColor:
|
|
215
|
+
fontFamily: MONO_FONT,
|
|
216
|
+
backgroundColor: muted,
|
|
236
217
|
borderWidth: 1,
|
|
237
|
-
borderColor:
|
|
218
|
+
borderColor: border,
|
|
238
219
|
borderRadius: 6,
|
|
239
220
|
padding: 12,
|
|
240
221
|
},
|
|
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
|
-
},
|
|
222
|
+
table: { borderWidth: 1, borderColor: border, borderRadius: 12, marginVertical: 8, overflow: 'hidden' as const },
|
|
223
|
+
thead: { backgroundColor: muted },
|
|
224
|
+
th: { flex: 1, padding: 8, fontWeight: '600' as const, ...BODY_TEXT, color: textColor },
|
|
225
|
+
td: { flex: 1, padding: 8, ...BODY_TEXT, color: textColor },
|
|
226
|
+
tr: { borderBottomWidth: 1, borderColor: border, flexDirection: 'row' as const },
|
|
227
|
+
link: { textDecorationLine: 'underline' as const, color: primary, ...BODY_TEXT },
|
|
228
|
+
hr: { backgroundColor: border, height: 1, marginVertical: 16 },
|
|
279
229
|
heading1: { fontSize: 18, fontWeight: '600' as const },
|
|
280
|
-
heading2: {
|
|
281
|
-
heading3: {
|
|
282
|
-
heading4: {
|
|
283
|
-
heading5: {
|
|
284
|
-
heading6: {
|
|
230
|
+
heading2: { ...HEADING_TEXT, fontWeight: '600' as const },
|
|
231
|
+
heading3: { ...HEADING_TEXT, fontWeight: '600' as const },
|
|
232
|
+
heading4: { ...HEADING_TEXT, fontWeight: '500' as const },
|
|
233
|
+
heading5: { ...HEADING_TEXT, fontWeight: '500' as const },
|
|
234
|
+
heading6: { ...HEADING_TEXT, fontWeight: '500' as const },
|
|
285
235
|
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface AliaMarkdownProps {
|
|
239
|
+
content: string;
|
|
240
|
+
colors?: Partial<AliaColors>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function AliaMarkdown({ content, colors: colorOverrides }: AliaMarkdownProps) {
|
|
244
|
+
const scheme = useColorScheme();
|
|
245
|
+
const fallback = scheme === 'dark' ? FALLBACK_DARK : FALLBACK_LIGHT;
|
|
246
|
+
const colors = { ...fallback, ...colorOverrides } as AliaColors;
|
|
247
|
+
|
|
248
|
+
const customRules = useMemo(() => createRules(colors), [colors.text, colors.muted, colors.border, colors.primary, colors.mutedForeground]);
|
|
249
|
+
const markdownStyles = useMemo(() => createStyles(colors), [colors.text, colors.muted, colors.border, colors.primary, colors.mutedForeground]);
|
|
286
250
|
|
|
287
251
|
return <Markdown rules={customRules} style={markdownStyles}>{content}</Markdown>;
|
|
288
252
|
}
|
|
@@ -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
|
+
}
|
|
@@ -8,7 +8,7 @@ import Animated, {
|
|
|
8
8
|
withSequence,
|
|
9
9
|
cancelAnimation,
|
|
10
10
|
} from 'react-native-reanimated';
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
|
|
13
13
|
// Context
|
|
14
14
|
type ReasoningContextType = {
|
|
@@ -80,7 +80,6 @@ export function Reasoning({ isStreaming = false, defaultOpen = true, duration: e
|
|
|
80
80
|
// Trigger — shows "Thinking for Xs..." label with pulsing icon
|
|
81
81
|
export function ReasoningTrigger({ onPress }: ReasoningTriggerProps) {
|
|
82
82
|
const { isStreaming, isOpen, setIsOpen, duration } = useReasoning();
|
|
83
|
-
const colors = useAliaColors();
|
|
84
83
|
const pulseOpacity = useSharedValue(1);
|
|
85
84
|
|
|
86
85
|
useEffect(() => {
|
|
@@ -114,7 +113,6 @@ export function ReasoningTrigger({ onPress }: ReasoningTriggerProps) {
|
|
|
114
113
|
// Content — renders thinking text (collapsible)
|
|
115
114
|
export function ReasoningContent({ children }: ReasoningContentProps) {
|
|
116
115
|
const { isOpen, isStreaming } = useReasoning();
|
|
117
|
-
const colors = useAliaColors();
|
|
118
116
|
|
|
119
117
|
if (!isOpen) return null;
|
|
120
118
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { View, Pressable } from 'react-native';
|
|
3
|
+
import { Text } from './ui/text';
|
|
4
|
+
import { cn } from '../lib/utils';
|
|
5
|
+
import {
|
|
6
|
+
Search,
|
|
7
|
+
BookOpen,
|
|
8
|
+
CheckCircle,
|
|
9
|
+
ChevronDown,
|
|
10
|
+
ChevronUp,
|
|
11
|
+
ExternalLink,
|
|
12
|
+
Loader2,
|
|
13
|
+
} from 'lucide-react-native';
|
|
14
|
+
import type { ResearchProgress } from '../types';
|
|
15
|
+
|
|
16
|
+
interface ResearchProgressCardProps {
|
|
17
|
+
progress: ResearchProgress;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PHASE_LABELS: Record<string, string> = {
|
|
21
|
+
decomposing: 'Planning research',
|
|
22
|
+
searching: 'Searching the web',
|
|
23
|
+
reading: 'Reading sources',
|
|
24
|
+
synthesizing: 'Synthesizing report',
|
|
25
|
+
follow_up: 'Follow-up research',
|
|
26
|
+
finalizing: 'Polishing report',
|
|
27
|
+
complete: 'Research complete',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const PHASE_ORDER = ['decomposing', 'searching', 'synthesizing', 'follow_up', 'finalizing', 'complete'];
|
|
31
|
+
|
|
32
|
+
export function ResearchProgressCard({ progress }: ResearchProgressCardProps) {
|
|
33
|
+
const [showSources, setShowSources] = useState(false);
|
|
34
|
+
const [showQuestions, setShowQuestions] = useState(true);
|
|
35
|
+
|
|
36
|
+
const phaseIndex = PHASE_ORDER.indexOf(progress.phase || '');
|
|
37
|
+
const isComplete = progress.phase === 'complete' || progress.isComplete;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View className="mx-3 my-2 rounded-xl border border-border bg-card p-4 gap-3">
|
|
41
|
+
{/* Header */}
|
|
42
|
+
<View className="flex-row items-center gap-2">
|
|
43
|
+
<View className="w-8 h-8 rounded-full bg-primary/10 items-center justify-center">
|
|
44
|
+
{isComplete ? (
|
|
45
|
+
<CheckCircle size={16} className="text-green-500" />
|
|
46
|
+
) : (
|
|
47
|
+
<Search size={16} className="text-primary" />
|
|
48
|
+
)}
|
|
49
|
+
</View>
|
|
50
|
+
<View className="flex-1">
|
|
51
|
+
<Text className="text-sm font-semibold text-foreground">
|
|
52
|
+
Deep Research
|
|
53
|
+
</Text>
|
|
54
|
+
<Text className="text-xs text-muted-foreground">
|
|
55
|
+
{progress.message || PHASE_LABELS[progress.phase || ''] || 'Researching...'}
|
|
56
|
+
</Text>
|
|
57
|
+
</View>
|
|
58
|
+
{progress.sourcesFound !== undefined && (
|
|
59
|
+
<View className="bg-muted rounded-full px-2 py-0.5">
|
|
60
|
+
<Text className="text-[10px] text-muted-foreground">
|
|
61
|
+
{progress.sourcesFound} sources
|
|
62
|
+
</Text>
|
|
63
|
+
</View>
|
|
64
|
+
)}
|
|
65
|
+
</View>
|
|
66
|
+
|
|
67
|
+
{/* Phase progress */}
|
|
68
|
+
<View className="flex-row gap-1">
|
|
69
|
+
{PHASE_ORDER.map((phase, i) => (
|
|
70
|
+
<View
|
|
71
|
+
key={phase}
|
|
72
|
+
className={cn(
|
|
73
|
+
'flex-1 h-1 rounded-full',
|
|
74
|
+
i <= phaseIndex
|
|
75
|
+
? (isComplete ? 'bg-green-500' : 'bg-primary')
|
|
76
|
+
: 'bg-muted',
|
|
77
|
+
)}
|
|
78
|
+
/>
|
|
79
|
+
))}
|
|
80
|
+
</View>
|
|
81
|
+
|
|
82
|
+
{/* Sub-questions */}
|
|
83
|
+
{progress.subQuestions && progress.subQuestions.length > 0 && (
|
|
84
|
+
<View>
|
|
85
|
+
<Pressable
|
|
86
|
+
onPress={() => setShowQuestions(!showQuestions)}
|
|
87
|
+
className="flex-row items-center gap-1 mb-1"
|
|
88
|
+
>
|
|
89
|
+
<BookOpen size={12} className="text-muted-foreground" />
|
|
90
|
+
<Text className="text-xs font-medium text-muted-foreground">
|
|
91
|
+
Research angles ({progress.subQuestions.length})
|
|
92
|
+
</Text>
|
|
93
|
+
{showQuestions ? (
|
|
94
|
+
<ChevronUp size={12} className="text-muted-foreground" />
|
|
95
|
+
) : (
|
|
96
|
+
<ChevronDown size={12} className="text-muted-foreground" />
|
|
97
|
+
)}
|
|
98
|
+
</Pressable>
|
|
99
|
+
{showQuestions && (
|
|
100
|
+
<View className="gap-1 pl-4">
|
|
101
|
+
{progress.subQuestions.map((q, i) => (
|
|
102
|
+
<Text key={i} className="text-xs text-muted-foreground">
|
|
103
|
+
{i + 1}. {q}
|
|
104
|
+
</Text>
|
|
105
|
+
))}
|
|
106
|
+
</View>
|
|
107
|
+
)}
|
|
108
|
+
</View>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
{/* Current search query indicator */}
|
|
112
|
+
{progress.currentQuery && !isComplete && (
|
|
113
|
+
<View className="flex-row items-center gap-2 bg-muted/50 rounded-lg px-3 py-1.5">
|
|
114
|
+
<Loader2 size={12} className="text-muted-foreground" />
|
|
115
|
+
<Text className="text-xs text-muted-foreground flex-1" numberOfLines={1}>
|
|
116
|
+
{progress.currentQuery}
|
|
117
|
+
</Text>
|
|
118
|
+
</View>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{/* Sources list (collapsed by default) */}
|
|
122
|
+
{progress.sources && progress.sources.length > 0 && (
|
|
123
|
+
<View>
|
|
124
|
+
<Pressable
|
|
125
|
+
onPress={() => setShowSources(!showSources)}
|
|
126
|
+
className="flex-row items-center gap-1"
|
|
127
|
+
>
|
|
128
|
+
<ExternalLink size={12} className="text-muted-foreground" />
|
|
129
|
+
<Text className="text-xs font-medium text-muted-foreground">
|
|
130
|
+
Sources ({progress.sources.length})
|
|
131
|
+
</Text>
|
|
132
|
+
{showSources ? (
|
|
133
|
+
<ChevronUp size={12} className="text-muted-foreground" />
|
|
134
|
+
) : (
|
|
135
|
+
<ChevronDown size={12} className="text-muted-foreground" />
|
|
136
|
+
)}
|
|
137
|
+
</Pressable>
|
|
138
|
+
{showSources && (
|
|
139
|
+
<View className="gap-1 pl-4 mt-1">
|
|
140
|
+
{progress.sources.map((s) => (
|
|
141
|
+
<Text key={s.id} className="text-xs text-primary" numberOfLines={1}>
|
|
142
|
+
[{s.id}] {s.title}
|
|
143
|
+
</Text>
|
|
144
|
+
))}
|
|
145
|
+
</View>
|
|
146
|
+
)}
|
|
147
|
+
</View>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{/* Search count */}
|
|
151
|
+
{progress.totalSearches !== undefined && isComplete && (
|
|
152
|
+
<Text className="text-[10px] text-muted-foreground text-right">
|
|
153
|
+
{progress.totalSearches} searches performed
|
|
154
|
+
</Text>
|
|
155
|
+
)}
|
|
156
|
+
</View>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { View, Text
|
|
2
|
+
import { View, Text } from "react-native";
|
|
3
3
|
import Animated, {
|
|
4
4
|
useSharedValue,
|
|
5
5
|
useAnimatedStyle,
|
|
@@ -8,7 +8,6 @@ import Animated, {
|
|
|
8
8
|
withSequence,
|
|
9
9
|
Easing,
|
|
10
10
|
} from "react-native-reanimated";
|
|
11
|
-
import { useAliaColors } from "../theme";
|
|
12
11
|
|
|
13
12
|
const thinkingPhrases = [
|
|
14
13
|
"Thinking...",
|
|
@@ -33,7 +32,6 @@ const workingPhrases = [
|
|
|
33
32
|
];
|
|
34
33
|
|
|
35
34
|
export function ThinkingIndicator({ isWorking = false, statusText }: { isWorking?: boolean; statusText?: string }) {
|
|
36
|
-
const colors = useAliaColors();
|
|
37
35
|
const phrases = isWorking ? workingPhrases : thinkingPhrases;
|
|
38
36
|
const [phraseIndex, setPhraseIndex] = useState(() =>
|
|
39
37
|
Math.floor(Math.random() * phrases.length)
|
|
@@ -104,31 +102,18 @@ export function ThinkingIndicator({ isWorking = false, statusText }: { isWorking
|
|
|
104
102
|
const shownText = statusText || displayText;
|
|
105
103
|
|
|
106
104
|
return (
|
|
107
|
-
<View
|
|
105
|
+
<View className="flex-row items-center gap-2 py-2">
|
|
108
106
|
<Animated.View style={spinStyle}>
|
|
109
|
-
<Text
|
|
107
|
+
<Text className="text-base text-muted-foreground">✱</Text>
|
|
110
108
|
</Animated.View>
|
|
111
|
-
<View
|
|
112
|
-
<Text
|
|
109
|
+
<View className="flex-row items-center">
|
|
110
|
+
<Text className="text-base text-muted-foreground">{shownText}</Text>
|
|
113
111
|
{(statusText || isTyping) && (
|
|
114
112
|
<Animated.View style={cursorStyle}>
|
|
115
|
-
<Text
|
|
113
|
+
<Text className="text-base text-muted-foreground">|</Text>
|
|
116
114
|
</Animated.View>
|
|
117
115
|
)}
|
|
118
116
|
</View>
|
|
119
117
|
</View>
|
|
120
118
|
);
|
|
121
119
|
}
|
|
122
|
-
|
|
123
|
-
const styles = StyleSheet.create({
|
|
124
|
-
container: {
|
|
125
|
-
flexDirection: 'row',
|
|
126
|
-
alignItems: 'center',
|
|
127
|
-
gap: 8,
|
|
128
|
-
paddingVertical: 8,
|
|
129
|
-
},
|
|
130
|
-
textRow: {
|
|
131
|
-
flexDirection: 'row',
|
|
132
|
-
alignItems: 'center',
|
|
133
|
-
},
|
|
134
|
-
});
|