@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.
@@ -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 { useAliaColors } from '../theme';
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', lineHeight: 22, color: textColor }}
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', lineHeight: 22, color: textColor }}
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', lineHeight: 22, color: textColor }}
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', lineHeight: 22, color: textColor }}
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', lineHeight: 22, color: textColor }}
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: isDark ? '#18181b' : '#f4f4f5',
88
+ backgroundColor: muted,
65
89
  borderWidth: 1,
66
- borderColor: isDark ? '#3f3f46' : '#e4e4e7',
90
+ borderColor: border,
67
91
  padding: 12,
68
92
  }}
69
93
  >
70
- <Text style={{ fontSize: 13, fontFamily: monoFont }}>{children}</Text>
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: isDark ? '#27272a' : '#f4f4f5',
100
+ backgroundColor: muted,
77
101
  paddingHorizontal: 4,
78
102
  paddingVertical: 1,
79
103
  fontSize: 13,
80
- fontFamily: monoFont,
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: '#71717A' }}>{bullet}</Text>
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: isDark ? '#60a5fa' : '#2563eb', textDecorationLine: 'underline' }}
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: isDark ? '#52525b' : '#d4d4d8',
131
- backgroundColor: isDark ? 'rgba(39,39,42,0.5)' : 'rgba(244,244,245,0.5)',
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: isDark ? '#3f3f46' : '#d4d4d8',
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
- export function AliaMarkdown({ content }: { content: string }) {
161
- const colors = useAliaColors();
162
- const isDark = colors.isDark;
181
+ function createStyles(colors: AliaColors) {
182
+ const { text: textColor, muted, border, primary, mutedForeground } = colors;
163
183
 
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
- },
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: monoFont,
217
- backgroundColor: isDark ? '#27272a' : '#f4f4f5',
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: monoFont,
226
- backgroundColor: isDark ? '#18181b' : '#f4f4f5',
206
+ fontFamily: MONO_FONT,
207
+ backgroundColor: muted,
227
208
  borderWidth: 1,
228
- borderColor: isDark ? '#3f3f46' : '#e4e4e7',
209
+ borderColor: border,
229
210
  borderRadius: 6,
230
211
  padding: 12,
231
212
  },
232
213
  fence: {
233
214
  fontSize: 13,
234
- fontFamily: monoFont,
235
- backgroundColor: isDark ? '#18181b' : '#f4f4f5',
215
+ fontFamily: MONO_FONT,
216
+ backgroundColor: muted,
236
217
  borderWidth: 1,
237
- borderColor: isDark ? '#3f3f46' : '#e4e4e7',
218
+ borderColor: border,
238
219
  borderRadius: 6,
239
220
  padding: 12,
240
221
  },
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
- },
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: { 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 },
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
- import { useAliaColors } from '../theme';
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, StyleSheet } from "react-native";
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 style={styles.container}>
105
+ <View className="flex-row items-center gap-2 py-2">
108
106
  <Animated.View style={spinStyle}>
109
- <Text style={{ fontSize: 16, color: colors.mutedForeground }}>✱</Text>
107
+ <Text className="text-base text-muted-foreground">✱</Text>
110
108
  </Animated.View>
111
- <View style={styles.textRow}>
112
- <Text style={{ fontSize: 16, color: colors.mutedForeground }}>{shownText}</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 style={{ fontSize: 16, color: colors.mutedForeground }}>|</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
- });