Skip to content

Commit d39747d

Browse files
committed
Merge branch 'mobile-app' of github.com:PostHog/Array into mobile-app
2 parents f215bf4 + b0684b3 commit d39747d

File tree

19 files changed

+341
-439
lines changed

19 files changed

+341
-439
lines changed

apps/mobile/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"format": "biome format --write ."
1414
},
1515
"dependencies": {
16+
"@expo/ui": "0.2.0-beta.9",
1617
"@react-native-async-storage/async-storage": "^2.2.0",
1718
"@tanstack/react-query": "^5.90.12",
1819
"date-fns": "^4.1.0",

apps/mobile/src/app/_layout.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function RootLayoutNav() {
4040
name="chat"
4141
options={{
4242
headerShown: true,
43+
headerBackTitle: "",
4344
headerStyle: { backgroundColor: "#09090b" },
4445
headerTintColor: "#fff",
4546
}}
@@ -48,6 +49,10 @@ function RootLayoutNav() {
4849
name="agent"
4950
options={{
5051
headerShown: true,
52+
title: "New task",
53+
headerBackTitle: "Back",
54+
headerStyle: { backgroundColor: "#09090b" },
55+
headerTintColor: "#f97316",
5156
}}
5257
/>
5358
</Stack>

apps/mobile/src/app/agent.tsx

Lines changed: 73 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,53 @@
1-
import { Link, useRouter } from "expo-router";
1+
import { useRouter } from "expo-router";
22
import { useCallback, useEffect, useState } from "react";
33
import {
44
ActivityIndicator,
55
FlatList,
66
Pressable,
7-
RefreshControl,
8-
Text,
97
TextInput,
108
View,
119
} from "react-native";
12-
import { SafeAreaView } from "react-native-safe-area-context";
10+
import { Text } from "../components/text";
1311
import {
1412
createTask,
1513
getGithubRepositories,
1614
getIntegrations,
17-
getTasks,
1815
runTaskInCloud,
1916
} from "../features/agent/lib/agentApi";
20-
import type { Integration, Task } from "../features/agent/types/agent";
17+
import type { Integration } from "../features/agent/types/agent";
2118

22-
export default function AgentScreen() {
19+
export default function NewTaskScreen() {
2320
const router = useRouter();
24-
const [tasks, setTasks] = useState<Task[]>([]);
25-
const [loading, setLoading] = useState(true);
26-
const [refreshing, setRefreshing] = useState(false);
27-
28-
const [showNewTask, setShowNewTask] = useState(false);
2921
const [integrations, setIntegrations] = useState<Integration[]>([]);
3022
const [repositories, setRepositories] = useState<string[]>([]);
3123
const [selectedRepo, setSelectedRepo] = useState<string | null>(null);
3224
const [prompt, setPrompt] = useState("");
3325
const [creating, setCreating] = useState(false);
34-
const [loadingRepos, setLoadingRepos] = useState(false);
35-
36-
const fetchTasks = useCallback(async () => {
37-
try {
38-
const data = await getTasks();
39-
setTasks(
40-
data.sort(
41-
(a, b) =>
42-
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
43-
),
44-
);
45-
} catch (error) {
46-
console.error("Failed to fetch tasks:", error);
47-
}
48-
}, []);
26+
const [loadingRepos, setLoadingRepos] = useState(true);
4927

5028
const loadIntegrations = useCallback(async () => {
5129
try {
30+
setLoadingRepos(true);
5231
const data = await getIntegrations();
5332
const githubIntegrations = data.filter((i) => i.kind === "github");
5433
setIntegrations(githubIntegrations);
5534

5635
if (githubIntegrations.length > 0) {
57-
setLoadingRepos(true);
5836
const allRepos: string[] = [];
5937
for (const integration of githubIntegrations) {
6038
const repos = await getGithubRepositories(integration.id);
6139
allRepos.push(...repos);
6240
}
6341
setRepositories(allRepos.sort());
64-
setLoadingRepos(false);
6542
}
6643
} catch (error) {
6744
console.error("Failed to fetch integrations:", error);
45+
} finally {
6846
setLoadingRepos(false);
6947
}
7048
}, []);
7149

7250
useEffect(() => {
73-
setLoading(true);
74-
fetchTasks().finally(() => setLoading(false));
75-
}, [fetchTasks]);
76-
77-
const onRefresh = useCallback(async () => {
78-
setRefreshing(true);
79-
await fetchTasks();
80-
setRefreshing(false);
81-
}, [fetchTasks]);
82-
83-
const handleNewTask = useCallback(() => {
84-
setShowNewTask(true);
8551
loadIntegrations();
8652
}, [loadIntegrations]);
8753

@@ -101,10 +67,6 @@ export default function AgentScreen() {
10167

10268
await runTaskInCloud(task.id);
10369

104-
setShowNewTask(false);
105-
setPrompt("");
106-
setSelectedRepo(null);
107-
10870
router.push(`/agent/${task.id}`);
10971
} catch (error) {
11072
console.error("Failed to create task:", error);
@@ -113,183 +75,80 @@ export default function AgentScreen() {
11375
}
11476
}, [prompt, selectedRepo, integrations, router]);
11577

116-
const renderTask = useCallback(
117-
({ item }: { item: Task }) => (
118-
<Link href={`/agent/${item.id}`} asChild>
119-
<Pressable className="bg-neutral-800 rounded-xl p-4 mb-3">
120-
<Text className="text-white font-medium mb-1" numberOfLines={2}>
121-
{item.title || item.description}
78+
return (
79+
<View className="flex-1 bg-dark px-3 pt-4">
80+
<Text className="mb-2 text-gray-500 text-xs">Repository</Text>
81+
{loadingRepos ? (
82+
<View className="mb-4 items-center rounded-lg border border-dark-border p-4">
83+
<ActivityIndicator size="small" color="#f97316" />
84+
<Text className="mt-2 text-dark-text-muted text-sm">
85+
Loading repositories...
86+
</Text>
87+
</View>
88+
) : repositories.length === 0 ? (
89+
<View className="mb-4 rounded-lg border border-dark-border p-4">
90+
<Text className="text-center text-dark-text-muted text-sm">
91+
No GitHub integrations found. Please add a GitHub integration in
92+
PostHog settings.
12293
</Text>
123-
{item.repository && (
124-
<Text className="text-neutral-400 text-sm">{item.repository}</Text>
125-
)}
126-
<View className="flex-row justify-between items-center mt-2">
127-
<Text className="text-neutral-500 text-xs">
128-
{new Date(item.created_at).toLocaleDateString()}
129-
</Text>
130-
{item.latest_run && (
131-
<View
132-
className={`px-2 py-1 rounded ${
133-
item.latest_run.status === "completed"
134-
? "bg-green-900"
135-
: item.latest_run.status === "failed"
136-
? "bg-red-900"
137-
: "bg-blue-900"
94+
</View>
95+
) : (
96+
<View className="mb-4 max-h-48 rounded-lg border border-dark-border">
97+
<FlatList
98+
data={repositories}
99+
keyExtractor={(item) => item}
100+
renderItem={({ item }) => (
101+
<Pressable
102+
onPress={() => setSelectedRepo(item)}
103+
className={`border-dark-border border-b px-3 py-3 ${
104+
selectedRepo === item ? "bg-amber-500/20" : ""
138105
}`}
139106
>
140-
<Text className="text-white text-xs">
141-
{item.latest_run.status}
107+
<Text
108+
className={`text-sm ${
109+
selectedRepo === item ? "text-amber-400" : "text-gray-400"
110+
}`}
111+
>
112+
{item}
142113
</Text>
143-
</View>
114+
</Pressable>
144115
)}
145-
</View>
146-
</Pressable>
147-
</Link>
148-
),
149-
[],
150-
);
151-
152-
if (showNewTask) {
153-
return (
154-
<SafeAreaView className="flex-1 bg-neutral-900">
155-
<View className="flex-1 px-4 pt-4">
156-
<View className="flex-row items-center justify-between mb-6">
157-
<Text className="text-white text-xl font-bold">
158-
New Conversation
159-
</Text>
160-
<Pressable onPress={() => setShowNewTask(false)}>
161-
<Text className="text-blue-500">Cancel</Text>
162-
</Pressable>
163-
</View>
164-
165-
<Text className="text-neutral-400 text-sm mb-2">Repository</Text>
166-
{loadingRepos ? (
167-
<View className="bg-neutral-800 rounded-xl p-4 mb-4 items-center">
168-
<ActivityIndicator size="small" color="#6b7280" />
169-
<Text className="text-neutral-500 text-sm mt-2">
170-
Loading repositories...
171-
</Text>
172-
</View>
173-
) : repositories.length === 0 ? (
174-
<View className="bg-neutral-800 rounded-xl p-4 mb-4">
175-
<Text className="text-neutral-500 text-center">
176-
No GitHub integrations found. Please add a GitHub integration in
177-
PostHog settings.
178-
</Text>
179-
</View>
180-
) : (
181-
<View className="bg-neutral-800 rounded-xl mb-4 max-h-48">
182-
<FlatList
183-
data={repositories}
184-
keyExtractor={(item) => item}
185-
renderItem={({ item }) => (
186-
<Pressable
187-
onPress={() => setSelectedRepo(item)}
188-
className={`px-4 py-3 border-b border-neutral-700 ${
189-
selectedRepo === item ? "bg-blue-900" : ""
190-
}`}
191-
>
192-
<Text
193-
className={`${
194-
selectedRepo === item
195-
? "text-white"
196-
: "text-neutral-300"
197-
}`}
198-
>
199-
{item}
200-
</Text>
201-
</Pressable>
202-
)}
203-
/>
204-
</View>
205-
)}
206-
207-
<Text className="text-neutral-400 text-sm mb-2">
208-
What would you like the agent to do?
209-
</Text>
210-
<TextInput
211-
className="bg-neutral-800 text-white px-4 py-3 rounded-xl mb-4 min-h-[100px]"
212-
placeholder="Describe your task..."
213-
placeholderTextColor="#6b7280"
214-
value={prompt}
215-
onChangeText={setPrompt}
216-
multiline
217-
textAlignVertical="top"
218116
/>
219-
220-
<Pressable
221-
onPress={handleCreateTask}
222-
disabled={!prompt.trim() || !selectedRepo || creating}
223-
className={`rounded-xl p-4 ${
224-
prompt.trim() && selectedRepo && !creating
225-
? "bg-blue-600"
226-
: "bg-neutral-700"
227-
}`}
228-
>
229-
{creating ? (
230-
<ActivityIndicator size="small" color="#fff" />
231-
) : (
232-
<Text className="text-white text-center font-medium">
233-
Start Conversation
234-
</Text>
235-
)}
236-
</Pressable>
237117
</View>
238-
</SafeAreaView>
239-
);
240-
}
241-
242-
return (
243-
<SafeAreaView className="flex-1 bg-neutral-900">
244-
<View className="flex-1 px-4 pt-4">
245-
<View className="flex-row items-center justify-between mb-6">
246-
<View>
247-
<Text className="text-white text-xl font-bold">Array Agent</Text>
248-
<Text className="text-neutral-400 text-sm">
249-
Your agent conversations
250-
</Text>
251-
</View>
252-
<Pressable
253-
onPress={handleNewTask}
254-
className="bg-blue-600 px-4 py-2 rounded-lg"
255-
>
256-
<Text className="text-white font-medium">New</Text>
257-
</Pressable>
258-
</View>
259-
260-
{loading ? (
261-
<View className="flex-1 items-center justify-center">
262-
<ActivityIndicator size="large" color="#6b7280" />
263-
</View>
264-
) : tasks.length === 0 ? (
265-
<View className="flex-1 items-center justify-center">
266-
<Text className="text-neutral-500 text-center mb-4">
267-
No conversations yet
268-
</Text>
269-
<Pressable
270-
onPress={handleNewTask}
271-
className="bg-blue-600 px-6 py-3 rounded-xl"
272-
>
273-
<Text className="text-white font-medium">
274-
Start a Conversation
275-
</Text>
276-
</Pressable>
277-
</View>
118+
)}
119+
120+
<Text className="mb-2 text-gray-500 text-xs">Task description</Text>
121+
<TextInput
122+
className="mb-4 min-h-[100px] rounded-lg border border-dark-border px-3 py-3 font-mono text-sm text-white"
123+
placeholder="What would you like the agent to do?"
124+
placeholderTextColor="#6b7280"
125+
value={prompt}
126+
onChangeText={setPrompt}
127+
multiline
128+
textAlignVertical="top"
129+
/>
130+
131+
<Pressable
132+
onPress={handleCreateTask}
133+
disabled={!prompt.trim() || !selectedRepo || creating}
134+
className={`rounded-lg py-3 ${
135+
prompt.trim() && selectedRepo && !creating
136+
? "bg-orange-500"
137+
: "bg-dark-surface"
138+
}`}
139+
>
140+
{creating ? (
141+
<ActivityIndicator size="small" color="#fff" />
278142
) : (
279-
<FlatList
280-
data={tasks}
281-
renderItem={renderTask}
282-
keyExtractor={(item) => item.id}
283-
refreshControl={
284-
<RefreshControl
285-
refreshing={refreshing}
286-
onRefresh={onRefresh}
287-
tintColor="#6b7280"
288-
/>
289-
}
290-
/>
143+
<Text
144+
className={`text-center font-medium ${
145+
prompt.trim() && selectedRepo ? "text-white" : "text-gray-500"
146+
}`}
147+
>
148+
Create task
149+
</Text>
291150
)}
292-
</View>
293-
</SafeAreaView>
151+
</Pressable>
152+
</View>
294153
);
295154
}

0 commit comments

Comments
 (0)