Skip to content

Commit 54f4b2a

Browse files
committed
streaming
1 parent 8dda38d commit 54f4b2a

File tree

4 files changed

+116
-58
lines changed

4 files changed

+116
-58
lines changed

apps/mobile/src/app/(auth)/tasks.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { Text, View } from "react-native";
1+
import { Link } from "expo-router";
2+
import { Text, TouchableOpacity, View } from "react-native";
23
import { SafeAreaView } from "react-native-safe-area-context";
34
import { useAuthStore } from "../../stores/authStore";
45

56
export default function TasksScreen() {
6-
const { cloudRegion, projectId } = useAuthStore();
7+
const { cloudRegion, projectId, logout } = useAuthStore();
78

89
return (
910
<SafeAreaView className="flex-1 bg-dark-bg">
@@ -32,10 +33,25 @@ export default function TasksScreen() {
3233
</View>
3334
</View>
3435

36+
{/* Chat Link */}
37+
<Link href="/chat" className="mb-6 rounded-xl bg-blue-600 p-4">
38+
<Text className="text-center font-medium text-white">
39+
Chat with Max
40+
</Text>
41+
</Link>
42+
3543
{/* Empty State */}
3644
<View className="flex-1 items-center justify-center">
3745
<Text className="text-base text-dark-text-muted">No tasks yet</Text>
3846
</View>
47+
48+
{/* Sign Out */}
49+
<TouchableOpacity
50+
onPress={logout}
51+
className="mb-6 rounded-xl bg-dark-surface p-4"
52+
>
53+
<Text className="text-center font-medium text-red-500">Sign out</Text>
54+
</TouchableOpacity>
3955
</View>
4056
</SafeAreaView>
4157
);

apps/mobile/src/app/chat.tsx

Lines changed: 89 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@ import { useState } from "react";
22
import {
33
KeyboardAvoidingView,
44
Platform,
5+
ScrollView,
56
Text,
67
TextInput,
78
TouchableOpacity,
89
View,
910
} from "react-native";
1011
import { SafeAreaView } from "react-native-safe-area-context";
11-
import { MessagesList } from "../components/MessagesList";
1212
import { useMaxStore } from "../stores/maxStore";
1313

1414
export default function ChatScreen() {
1515
const [inputText, setInputText] = useState("");
16-
const { thread, streamingActive, askMax, stopGeneration, resetThread } =
17-
useMaxStore();
16+
const {
17+
thread,
18+
conversation,
19+
streamingActive,
20+
askMax,
21+
stopGeneration,
22+
resetThread,
23+
} = useMaxStore();
1824

1925
const handleSend = async () => {
2026
const trimmed = inputText.trim();
@@ -24,69 +30,98 @@ export default function ChatScreen() {
2430
await askMax(trimmed);
2531
};
2632

27-
const handleStop = () => {
28-
stopGeneration();
29-
};
30-
3133
return (
32-
<SafeAreaView
33-
className="flex-1 bg-dark-bg"
34-
edges={["top", "left", "right"]}
35-
>
34+
<SafeAreaView className="flex-1 bg-black" edges={["top", "left", "right"]}>
3635
<KeyboardAvoidingView
3736
behavior={Platform.OS === "ios" ? "padding" : "height"}
3837
className="flex-1"
39-
keyboardVerticalOffset={0}
4038
>
4139
{/* Header */}
42-
<View className="flex-row items-center justify-between border-dark-border border-b px-6 pt-4 pb-2">
43-
<Text className="font-bold text-white text-xl">Max</Text>
44-
{thread.length > 0 && (
40+
<View className="flex-row items-center justify-between border-gray-800 border-b px-4 py-3">
41+
<Text className="font-bold text-lg text-white">Max Chat</Text>
42+
<View className="flex-row gap-3">
43+
{streamingActive && (
44+
<TouchableOpacity onPress={stopGeneration}>
45+
<Text className="text-red-500">Stop</Text>
46+
</TouchableOpacity>
47+
)}
4548
<TouchableOpacity onPress={resetThread}>
46-
<Text className="text-blue-500">New chat</Text>
49+
<Text className="text-blue-500">Reset</Text>
4750
</TouchableOpacity>
48-
)}
51+
</View>
4952
</View>
5053

51-
{/* Messages */}
52-
<View className="flex-1">
53-
<MessagesList messages={thread} isLoading={streamingActive} />
54-
</View>
54+
{/* JSON Output */}
55+
<ScrollView className="flex-1 p-4">
56+
{/* Conversation metadata */}
57+
{conversation && (
58+
<View className="mb-4">
59+
<Text className="mb-1 font-bold text-green-400">
60+
Conversation:
61+
</Text>
62+
<Text className="font-mono text-green-300 text-xs">
63+
{JSON.stringify(conversation, null, 2)}
64+
</Text>
65+
</View>
66+
)}
5567

56-
{/* Input area */}
57-
<View className="border-dark-border border-t px-4 py-3">
58-
<View className="flex-row items-end gap-2">
59-
<TextInput
60-
className="max-h-[120px] min-h-[44px] flex-1 rounded-2xl bg-dark-border px-4 py-3 text-base text-white"
61-
placeholder="Ask Max..."
62-
placeholderTextColor="#6B7280"
63-
value={inputText}
64-
onChangeText={setInputText}
65-
onSubmitEditing={handleSend}
66-
multiline
67-
editable={!streamingActive}
68-
returnKeyType="send"
69-
blurOnSubmit={false}
70-
/>
71-
{streamingActive ? (
72-
<TouchableOpacity
73-
onPress={handleStop}
74-
className="h-11 w-11 items-center justify-center rounded-full bg-red-600"
75-
>
76-
<View className="h-4 w-4 rounded-sm bg-white" />
77-
</TouchableOpacity>
78-
) : (
79-
<TouchableOpacity
80-
onPress={handleSend}
81-
disabled={!inputText.trim()}
82-
className={`h-11 w-11 items-center justify-center rounded-full ${
83-
inputText.trim() ? "bg-blue-600" : "bg-dark-border"
84-
}`}
85-
>
86-
<Text className="text-lg text-white"></Text>
87-
</TouchableOpacity>
88-
)}
68+
{/* Status */}
69+
<View className="mb-4">
70+
<Text className="text-gray-400">
71+
Streaming: {streamingActive ? "true" : "false"}
72+
</Text>
73+
<Text className="text-gray-400">Messages: {thread.length}</Text>
8974
</View>
75+
76+
{/* Messages */}
77+
{thread.map((message, index) => (
78+
<View key={message.id || `msg-${index}`} className="mb-4">
79+
<Text className="mb-1 font-bold text-yellow-400">
80+
[{index}] {message.type} ({message.status})
81+
</Text>
82+
<Text className="font-mono text-white text-xs">
83+
{JSON.stringify(message, null, 2)}
84+
</Text>
85+
</View>
86+
))}
87+
88+
{thread.length === 0 && !streamingActive && (
89+
<Text className="text-center text-gray-500">
90+
Send a message to start
91+
</Text>
92+
)}
93+
94+
{thread.length > 0 && !streamingActive && (
95+
<TouchableOpacity onPress={resetThread} className="mt-4 py-2">
96+
<Text className="text-center text-blue-500 underline">
97+
Start a new chat
98+
</Text>
99+
</TouchableOpacity>
100+
)}
101+
</ScrollView>
102+
103+
{/* Input */}
104+
<View className="flex-row items-center gap-2 border-gray-800 border-t p-4">
105+
<TextInput
106+
className="flex-1 rounded-lg bg-gray-800 px-4 py-3 text-white"
107+
placeholder="Type a message..."
108+
placeholderTextColor="#666"
109+
value={inputText}
110+
onChangeText={setInputText}
111+
onSubmitEditing={handleSend}
112+
editable={!streamingActive}
113+
/>
114+
<TouchableOpacity
115+
onPress={handleSend}
116+
disabled={!inputText.trim() || streamingActive}
117+
className={`rounded-lg px-4 py-3 ${
118+
inputText.trim() && !streamingActive
119+
? "bg-blue-600"
120+
: "bg-gray-700"
121+
}`}
122+
>
123+
<Text className="text-white">Send</Text>
124+
</TouchableOpacity>
90125
</View>
91126
</KeyboardAvoidingView>
92127
</SafeAreaView>

apps/mobile/src/constants/oauth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const OAUTH_SCOPES = [
99
"project:read",
1010
"task:write",
1111
"integration:read",
12+
"conversation:write",
1213
];
1314

1415
// Token refresh settings

apps/mobile/src/stores/maxStore.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { fetch } from "expo/fetch";
12
import { create } from "zustand";
23
import { getCloudUrlFromRegion } from "../constants/oauth";
34
import {
@@ -49,7 +50,11 @@ function generateUUID(): string {
4950
}
5051

5152
export const useMaxStore = create<MaxState>((set, get) => ({
52-
conversation: null,
53+
conversation: {
54+
id: generateUUID(),
55+
title: "New chat",
56+
status: ConversationStatus.Idle,
57+
},
5358
thread: [],
5459
streamingActive: false,
5560
conversationLoading: false,
@@ -115,6 +120,7 @@ export const useMaxStore = create<MaxState>((set, get) => ({
115120
);
116121

117122
if (!response.ok) {
123+
console.log(await response.text());
118124
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
119125
}
120126

@@ -157,7 +163,7 @@ export const useMaxStore = create<MaxState>((set, get) => ({
157163
}
158164
}
159165
} catch (error) {
160-
if (error instanceof DOMException && error.name === "AbortError") {
166+
if ((error as any).name === "AbortError") {
161167
// Request was cancelled, don't show error
162168
console.log("Request cancelled");
163169
} else {

0 commit comments

Comments
 (0)