create-better-t-stack 3.2.22 → 3.2.23-canary.b8d62867
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/README.md +2 -2
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/{src-WIwtBCHf.js → src-VoUvj8ZF.js} +106 -66
- package/package.json +1 -1
- package/templates/addons/biome/biome.json.hbs +5 -0
- package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +5 -5
- package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +1 -1
- package/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs +127 -0
- package/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs +138 -0
- package/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs +91 -0
- package/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs +102 -0
- package/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs +186 -0
- package/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs +131 -0
- package/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs +150 -0
- package/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs +9 -1
- package/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs +5 -0
- package/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs +5 -0
- package/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs +123 -0
- package/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs +90 -0
- package/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs +116 -0
- package/templates/auth/better-auth/server/base/src/index.ts.hbs +5 -5
- package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +287 -0
- package/templates/examples/ai/native/{nativewind → bare}/polyfills.js +1 -0
- package/templates/examples/ai/native/{nativewind → uniwind}/app/(drawer)/ai.tsx.hbs +52 -51
- package/templates/examples/ai/native/uniwind/polyfills.js +26 -0
- package/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs +521 -0
- package/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs +295 -0
- package/templates/extras/bunfig.toml.hbs +3 -3
- package/templates/frontend/native/bare/_gitignore +18 -0
- package/templates/frontend/native/{nativewind → bare}/app/(drawer)/(tabs)/_layout.tsx.hbs +7 -12
- package/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs +43 -0
- package/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs +43 -0
- package/templates/frontend/native/{nativewind → bare}/app/(drawer)/_layout.tsx.hbs +24 -1
- package/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs +234 -0
- package/templates/frontend/native/bare/app/+not-found.tsx.hbs +65 -0
- package/templates/frontend/native/bare/app/_layout.tsx.hbs +163 -0
- package/templates/frontend/native/bare/app/modal.tsx.hbs +34 -0
- package/templates/frontend/native/{nativewind → bare}/app.json.hbs +1 -0
- package/templates/frontend/native/bare/components/container.tsx.hbs +25 -0
- package/templates/frontend/native/bare/components/header-button.tsx.hbs +47 -0
- package/templates/frontend/native/{nativewind → bare}/components/tabbar-icon.tsx.hbs +1 -0
- package/templates/frontend/native/{nativewind → bare}/lib/android-navigation-bar.tsx.hbs +1 -0
- package/templates/frontend/native/{nativewind → bare}/lib/constants.ts.hbs +1 -0
- package/templates/frontend/native/bare/lib/use-color-scheme.ts.hbs +20 -0
- package/templates/frontend/native/bare/metro.config.js.hbs +9 -0
- package/templates/frontend/native/{nativewind → bare}/package.json.hbs +1 -2
- package/templates/frontend/native/bare/tsconfig.json.hbs +11 -0
- package/templates/frontend/native/{nativewind → uniwind}/_gitignore +4 -8
- package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs +46 -0
- package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs +15 -0
- package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs +15 -0
- package/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs +83 -0
- package/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +151 -0
- package/templates/frontend/native/uniwind/app/+not-found.tsx.hbs +32 -0
- package/templates/frontend/native/uniwind/app/_layout.tsx.hbs +131 -0
- package/templates/frontend/native/uniwind/app/modal.tsx.hbs +53 -0
- package/templates/frontend/native/uniwind/app.json.hbs +19 -0
- package/templates/frontend/native/uniwind/components/container.tsx.hbs +33 -0
- package/templates/frontend/native/uniwind/components/theme-toggle.tsx.hbs +35 -0
- package/templates/frontend/native/uniwind/contexts/app-theme-context.tsx.hbs +62 -0
- package/templates/frontend/native/uniwind/global.css +5 -0
- package/templates/frontend/native/uniwind/metro.config.js.hbs +13 -0
- package/templates/frontend/native/uniwind/package.json.hbs +54 -0
- package/templates/frontend/native/{nativewind → uniwind}/tsconfig.json.hbs +4 -8
- package/templates/auth/better-auth/convex/native/nativewind/components/sign-in.tsx.hbs +0 -86
- package/templates/auth/better-auth/convex/native/nativewind/components/sign-up.tsx.hbs +0 -97
- package/templates/auth/better-auth/native/nativewind/app/(drawer)/index.tsx.hbs +0 -95
- package/templates/auth/better-auth/native/nativewind/components/sign-in.tsx.hbs +0 -93
- package/templates/auth/better-auth/native/nativewind/components/sign-up.tsx.hbs +0 -104
- package/templates/examples/todo/native/nativewind/app/(drawer)/todos.tsx.hbs +0 -295
- package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx.hbs +0 -19
- package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx.hbs +0 -19
- package/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +0 -178
- package/templates/frontend/native/nativewind/app/+not-found.tsx.hbs +0 -29
- package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +0 -175
- package/templates/frontend/native/nativewind/app/modal.tsx.hbs +0 -14
- package/templates/frontend/native/nativewind/babel.config.js.hbs +0 -14
- package/templates/frontend/native/nativewind/components/container.tsx.hbs +0 -8
- package/templates/frontend/native/nativewind/components/header-button.tsx.hbs +0 -26
- package/templates/frontend/native/nativewind/global.css +0 -50
- package/templates/frontend/native/nativewind/lib/use-color-scheme.ts.hbs +0 -12
- package/templates/frontend/native/nativewind/metro.config.js.hbs +0 -12
- package/templates/frontend/native/nativewind/tailwind.config.js.hbs +0 -59
- /package/templates/auth/clerk/convex/native/base/app/(auth)/{sign-out.tsx.hbs → sign-up.tsx.hbs} +0 -0
|
@@ -16,7 +16,7 @@ export const auth = betterAuth<BetterAuthOptions>({
|
|
|
16
16
|
}),
|
|
17
17
|
trustedOrigins: [
|
|
18
18
|
process.env.CORS_ORIGIN || "",
|
|
19
|
-
{{#if (or (includes frontend "native-
|
|
19
|
+
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
20
20
|
"mybettertapp://", "exp://"
|
|
21
21
|
{{/if}}
|
|
22
22
|
],
|
|
@@ -77,7 +77,7 @@ export const auth = betterAuth<BetterAuthOptions>({
|
|
|
77
77
|
}),
|
|
78
78
|
trustedOrigins: [
|
|
79
79
|
process.env.CORS_ORIGIN || "",
|
|
80
|
-
{{#if (or (includes frontend "native-
|
|
80
|
+
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
81
81
|
"mybettertapp://", "exp://"
|
|
82
82
|
{{/if}}
|
|
83
83
|
],
|
|
@@ -138,7 +138,7 @@ export const auth = betterAuth<BetterAuthOptions>({
|
|
|
138
138
|
}),
|
|
139
139
|
trustedOrigins: [
|
|
140
140
|
env.CORS_ORIGIN,
|
|
141
|
-
{{#if (or (includes frontend "native-
|
|
141
|
+
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
142
142
|
"mybettertapp://", "exp://"
|
|
143
143
|
{{/if}}
|
|
144
144
|
],
|
|
@@ -206,7 +206,7 @@ export const auth = betterAuth<BetterAuthOptions>({
|
|
|
206
206
|
database: mongodbAdapter(client),
|
|
207
207
|
trustedOrigins: [
|
|
208
208
|
process.env.CORS_ORIGIN || "",
|
|
209
|
-
{{#if (or (includes frontend "native-
|
|
209
|
+
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
210
210
|
"mybettertapp://", "exp://"
|
|
211
211
|
{{/if}}
|
|
212
212
|
],
|
|
@@ -258,7 +258,7 @@ export const auth = betterAuth<BetterAuthOptions>({
|
|
|
258
258
|
database: "", // Invalid configuration
|
|
259
259
|
trustedOrigins: [
|
|
260
260
|
process.env.CORS_ORIGIN || "",
|
|
261
|
-
{{#if (or (includes frontend "native-
|
|
261
|
+
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
262
262
|
"mybettertapp://", "exp://"
|
|
263
263
|
{{/if}}
|
|
264
264
|
],
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { useRef, useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TextInput,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
ScrollView,
|
|
8
|
+
KeyboardAvoidingView,
|
|
9
|
+
Platform,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
} from "react-native";
|
|
12
|
+
import { useChat } from "@ai-sdk/react";
|
|
13
|
+
import { DefaultChatTransport } from "ai";
|
|
14
|
+
import { fetch as expoFetch } from "expo/fetch";
|
|
15
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
16
|
+
import { Container } from "@/components/container";
|
|
17
|
+
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
18
|
+
import { NAV_THEME } from "@/lib/constants";
|
|
19
|
+
|
|
20
|
+
const generateAPIUrl = (relativePath: string) => {
|
|
21
|
+
const serverUrl = process.env.EXPO_PUBLIC_SERVER_URL;
|
|
22
|
+
if (!serverUrl) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"EXPO_PUBLIC_SERVER_URL environment variable is not defined"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const path = relativePath.startsWith("/") ? relativePath : `/${relativePath}`;
|
|
28
|
+
return serverUrl.concat(path);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default function AIScreen() {
|
|
32
|
+
const { colorScheme } = useColorScheme();
|
|
33
|
+
const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light;
|
|
34
|
+
const [input, setInput] = useState("");
|
|
35
|
+
const { messages, error, sendMessage } = useChat({
|
|
36
|
+
transport: new DefaultChatTransport({
|
|
37
|
+
fetch: expoFetch as unknown as typeof globalThis.fetch,
|
|
38
|
+
api: generateAPIUrl("/ai"),
|
|
39
|
+
}),
|
|
40
|
+
onError: (error) => console.error(error, "AI Chat Error"),
|
|
41
|
+
});
|
|
42
|
+
const scrollViewRef = useRef<ScrollView>(null);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
scrollViewRef.current?.scrollToEnd({ animated: true });
|
|
46
|
+
}, [messages]);
|
|
47
|
+
|
|
48
|
+
function onSubmit() {
|
|
49
|
+
const value = input.trim();
|
|
50
|
+
if (value) {
|
|
51
|
+
sendMessage({ text: value });
|
|
52
|
+
setInput("");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (error) {
|
|
57
|
+
return (
|
|
58
|
+
<Container>
|
|
59
|
+
<View style={styles.errorContainer}>
|
|
60
|
+
<View style={[styles.errorCard, { backgroundColor: theme.notification + "20", borderColor: theme.notification }]}>
|
|
61
|
+
<Text style={[styles.errorTitle, { color: theme.notification }]}>
|
|
62
|
+
Error: {error.message}
|
|
63
|
+
</Text>
|
|
64
|
+
<Text style={[styles.errorText, { color: theme.text, opacity: 0.7 }]}>
|
|
65
|
+
Please check your connection and try again.
|
|
66
|
+
</Text>
|
|
67
|
+
</View>
|
|
68
|
+
</View>
|
|
69
|
+
</Container>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Container>
|
|
75
|
+
<KeyboardAvoidingView
|
|
76
|
+
style={styles.container}
|
|
77
|
+
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
78
|
+
>
|
|
79
|
+
<View style={styles.content}>
|
|
80
|
+
<View style={styles.header}>
|
|
81
|
+
<Text style={[styles.headerTitle, { color: theme.text }]}>
|
|
82
|
+
AI Chat
|
|
83
|
+
</Text>
|
|
84
|
+
<Text style={[styles.headerSubtitle, { color: theme.text, opacity: 0.7 }]}>
|
|
85
|
+
Chat with our AI assistant
|
|
86
|
+
</Text>
|
|
87
|
+
</View>
|
|
88
|
+
<ScrollView
|
|
89
|
+
ref={scrollViewRef}
|
|
90
|
+
style={styles.scrollView}
|
|
91
|
+
showsVerticalScrollIndicator={false}
|
|
92
|
+
>
|
|
93
|
+
{messages.length === 0 ? (
|
|
94
|
+
<View style={styles.emptyContainer}>
|
|
95
|
+
<Text style={[styles.emptyText, { color: theme.text, opacity: 0.7 }]}>
|
|
96
|
+
Ask me anything to get started!
|
|
97
|
+
</Text>
|
|
98
|
+
</View>
|
|
99
|
+
) : (
|
|
100
|
+
<View style={styles.messagesList}>
|
|
101
|
+
{messages.map((message) => (
|
|
102
|
+
<View
|
|
103
|
+
key={message.id}
|
|
104
|
+
style={[
|
|
105
|
+
styles.messageCard,
|
|
106
|
+
{
|
|
107
|
+
backgroundColor: message.role === "user"
|
|
108
|
+
? theme.primary + "20"
|
|
109
|
+
: theme.card,
|
|
110
|
+
borderColor: theme.border,
|
|
111
|
+
alignSelf: message.role === "user" ? "flex-end" : "flex-start",
|
|
112
|
+
marginLeft: message.role === "user" ? 32 : 0,
|
|
113
|
+
marginRight: message.role === "user" ? 0 : 32,
|
|
114
|
+
},
|
|
115
|
+
]}
|
|
116
|
+
>
|
|
117
|
+
<Text style={[styles.messageRole, { color: theme.text }]}>
|
|
118
|
+
{message.role === "user" ? "You" : "AI Assistant"}
|
|
119
|
+
</Text>
|
|
120
|
+
<View style={styles.messageParts}>
|
|
121
|
+
{message.parts.map((part, i) =>
|
|
122
|
+
part.type === "text" ? (
|
|
123
|
+
<Text
|
|
124
|
+
key={`${message.id}-${i}`}
|
|
125
|
+
style={[styles.messageText, { color: theme.text }]}
|
|
126
|
+
>
|
|
127
|
+
{part.text}
|
|
128
|
+
</Text>
|
|
129
|
+
) : (
|
|
130
|
+
<Text
|
|
131
|
+
key={`${message.id}-${i}`}
|
|
132
|
+
style={[styles.messageText, { color: theme.text }]}
|
|
133
|
+
>
|
|
134
|
+
{JSON.stringify(part)}
|
|
135
|
+
</Text>
|
|
136
|
+
)
|
|
137
|
+
)}
|
|
138
|
+
</View>
|
|
139
|
+
</View>
|
|
140
|
+
))}
|
|
141
|
+
</View>
|
|
142
|
+
)}
|
|
143
|
+
</ScrollView>
|
|
144
|
+
<View style={[styles.inputContainer, { borderTopColor: theme.border }]}>
|
|
145
|
+
<View style={styles.inputRow}>
|
|
146
|
+
<TextInput
|
|
147
|
+
value={input}
|
|
148
|
+
onChangeText={setInput}
|
|
149
|
+
placeholder="Type your message..."
|
|
150
|
+
placeholderTextColor={theme.text}
|
|
151
|
+
style={[
|
|
152
|
+
styles.input,
|
|
153
|
+
{
|
|
154
|
+
color: theme.text,
|
|
155
|
+
borderColor: theme.border,
|
|
156
|
+
backgroundColor: theme.background,
|
|
157
|
+
},
|
|
158
|
+
]}
|
|
159
|
+
onSubmitEditing={(e) => {
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
onSubmit();
|
|
162
|
+
}}
|
|
163
|
+
autoFocus={true}
|
|
164
|
+
multiline
|
|
165
|
+
/>
|
|
166
|
+
<TouchableOpacity
|
|
167
|
+
onPress={onSubmit}
|
|
168
|
+
disabled={!input.trim()}
|
|
169
|
+
style={[
|
|
170
|
+
styles.sendButton,
|
|
171
|
+
{
|
|
172
|
+
backgroundColor: input.trim() ? theme.primary : theme.border,
|
|
173
|
+
opacity: input.trim() ? 1 : 0.5,
|
|
174
|
+
},
|
|
175
|
+
]}
|
|
176
|
+
>
|
|
177
|
+
<Ionicons
|
|
178
|
+
name="send"
|
|
179
|
+
size={20}
|
|
180
|
+
color="#ffffff"
|
|
181
|
+
/>
|
|
182
|
+
</TouchableOpacity>
|
|
183
|
+
</View>
|
|
184
|
+
</View>
|
|
185
|
+
</View>
|
|
186
|
+
</KeyboardAvoidingView>
|
|
187
|
+
</Container>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const styles = StyleSheet.create({
|
|
192
|
+
container: {
|
|
193
|
+
flex: 1,
|
|
194
|
+
},
|
|
195
|
+
content: {
|
|
196
|
+
flex: 1,
|
|
197
|
+
padding: 16,
|
|
198
|
+
},
|
|
199
|
+
header: {
|
|
200
|
+
marginBottom: 16,
|
|
201
|
+
},
|
|
202
|
+
headerTitle: {
|
|
203
|
+
fontSize: 20,
|
|
204
|
+
fontWeight: "bold",
|
|
205
|
+
marginBottom: 4,
|
|
206
|
+
},
|
|
207
|
+
headerSubtitle: {
|
|
208
|
+
fontSize: 14,
|
|
209
|
+
},
|
|
210
|
+
scrollView: {
|
|
211
|
+
flex: 1,
|
|
212
|
+
marginBottom: 16,
|
|
213
|
+
},
|
|
214
|
+
emptyContainer: {
|
|
215
|
+
flex: 1,
|
|
216
|
+
justifyContent: "center",
|
|
217
|
+
alignItems: "center",
|
|
218
|
+
},
|
|
219
|
+
emptyText: {
|
|
220
|
+
fontSize: 16,
|
|
221
|
+
textAlign: "center",
|
|
222
|
+
},
|
|
223
|
+
messagesList: {
|
|
224
|
+
gap: 8,
|
|
225
|
+
paddingBottom: 16,
|
|
226
|
+
},
|
|
227
|
+
messageCard: {
|
|
228
|
+
borderWidth: 1,
|
|
229
|
+
padding: 12,
|
|
230
|
+
maxWidth: "80%",
|
|
231
|
+
},
|
|
232
|
+
messageRole: {
|
|
233
|
+
fontSize: 12,
|
|
234
|
+
fontWeight: "bold",
|
|
235
|
+
marginBottom: 4,
|
|
236
|
+
},
|
|
237
|
+
messageParts: {
|
|
238
|
+
gap: 4,
|
|
239
|
+
},
|
|
240
|
+
messageText: {
|
|
241
|
+
fontSize: 14,
|
|
242
|
+
lineHeight: 20,
|
|
243
|
+
},
|
|
244
|
+
inputContainer: {
|
|
245
|
+
borderTopWidth: 1,
|
|
246
|
+
paddingTop: 12,
|
|
247
|
+
},
|
|
248
|
+
inputRow: {
|
|
249
|
+
flexDirection: "row",
|
|
250
|
+
alignItems: "flex-end",
|
|
251
|
+
gap: 8,
|
|
252
|
+
},
|
|
253
|
+
input: {
|
|
254
|
+
flex: 1,
|
|
255
|
+
borderWidth: 1,
|
|
256
|
+
padding: 8,
|
|
257
|
+
fontSize: 14,
|
|
258
|
+
minHeight: 36,
|
|
259
|
+
maxHeight: 100,
|
|
260
|
+
},
|
|
261
|
+
sendButton: {
|
|
262
|
+
padding: 8,
|
|
263
|
+
justifyContent: "center",
|
|
264
|
+
alignItems: "center",
|
|
265
|
+
},
|
|
266
|
+
errorContainer: {
|
|
267
|
+
flex: 1,
|
|
268
|
+
justifyContent: "center",
|
|
269
|
+
alignItems: "center",
|
|
270
|
+
padding: 16,
|
|
271
|
+
},
|
|
272
|
+
errorCard: {
|
|
273
|
+
borderWidth: 1,
|
|
274
|
+
padding: 16,
|
|
275
|
+
},
|
|
276
|
+
errorTitle: {
|
|
277
|
+
fontSize: 16,
|
|
278
|
+
fontWeight: "bold",
|
|
279
|
+
marginBottom: 8,
|
|
280
|
+
textAlign: "center",
|
|
281
|
+
},
|
|
282
|
+
errorText: {
|
|
283
|
+
fontSize: 14,
|
|
284
|
+
textAlign: "center",
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
5
5
|
TextInput,
|
|
6
|
-
|
|
6
|
+
Pressable,
|
|
7
7
|
ScrollView,
|
|
8
8
|
KeyboardAvoidingView,
|
|
9
9
|
Platform,
|
|
@@ -13,14 +13,16 @@ import { DefaultChatTransport } from "ai";
|
|
|
13
13
|
import { fetch as expoFetch } from "expo/fetch";
|
|
14
14
|
import { Ionicons } from "@expo/vector-icons";
|
|
15
15
|
import { Container } from "@/components/container";
|
|
16
|
+
import { Card, useThemeColor } from "heroui-native";
|
|
16
17
|
|
|
17
18
|
const generateAPIUrl = (relativePath: string) => {
|
|
18
19
|
const serverUrl = process.env.EXPO_PUBLIC_SERVER_URL;
|
|
19
20
|
if (!serverUrl) {
|
|
20
|
-
throw new Error(
|
|
21
|
+
throw new Error(
|
|
22
|
+
"EXPO_PUBLIC_SERVER_URL environment variable is not defined"
|
|
23
|
+
);
|
|
21
24
|
}
|
|
22
|
-
|
|
23
|
-
const path = relativePath.startsWith('/') ? relativePath : `/${relativePath}`;
|
|
25
|
+
const path = relativePath.startsWith("/") ? relativePath : `/${relativePath}`;
|
|
24
26
|
return serverUrl.concat(path);
|
|
25
27
|
};
|
|
26
28
|
|
|
@@ -29,12 +31,15 @@ export default function AIScreen() {
|
|
|
29
31
|
const { messages, error, sendMessage } = useChat({
|
|
30
32
|
transport: new DefaultChatTransport({
|
|
31
33
|
fetch: expoFetch as unknown as typeof globalThis.fetch,
|
|
32
|
-
api: generateAPIUrl(
|
|
34
|
+
api: generateAPIUrl("/ai"),
|
|
33
35
|
}),
|
|
34
|
-
onError: error => console.error(error,
|
|
36
|
+
onError: (error) => console.error(error, "AI Chat Error"),
|
|
35
37
|
});
|
|
36
|
-
|
|
37
38
|
const scrollViewRef = useRef<ScrollView>(null);
|
|
39
|
+
const mutedColor = useThemeColor("muted");
|
|
40
|
+
const accentColor = useThemeColor("accent");
|
|
41
|
+
const foregroundColor = useThemeColor("foreground");
|
|
42
|
+
const dangerColor = useThemeColor("danger");
|
|
38
43
|
|
|
39
44
|
useEffect(() => {
|
|
40
45
|
scrollViewRef.current?.scrollToEnd({ animated: true });
|
|
@@ -52,12 +57,14 @@ export default function AIScreen() {
|
|
|
52
57
|
return (
|
|
53
58
|
<Container>
|
|
54
59
|
<View className="flex-1 justify-center items-center px-4">
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
<Card variant="flat" className="p-4 bg-danger/10">
|
|
61
|
+
<Text className="text-danger text-center text-lg mb-2 font-semibold">
|
|
62
|
+
Error: {error.message}
|
|
63
|
+
</Text>
|
|
64
|
+
<Text className="text-muted text-center">
|
|
65
|
+
Please check your connection and try again.
|
|
66
|
+
</Text>
|
|
67
|
+
</Card>
|
|
61
68
|
</View>
|
|
62
69
|
</Container>
|
|
63
70
|
);
|
|
@@ -65,7 +72,7 @@ export default function AIScreen() {
|
|
|
65
72
|
|
|
66
73
|
return (
|
|
67
74
|
<Container>
|
|
68
|
-
<KeyboardAvoidingView
|
|
75
|
+
<KeyboardAvoidingView
|
|
69
76
|
className="flex-1"
|
|
70
77
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
71
78
|
>
|
|
@@ -74,11 +81,10 @@ export default function AIScreen() {
|
|
|
74
81
|
<Text className="text-foreground text-2xl font-bold mb-2">
|
|
75
82
|
AI Chat
|
|
76
83
|
</Text>
|
|
77
|
-
<Text className="text-muted
|
|
84
|
+
<Text className="text-muted">
|
|
78
85
|
Chat with our AI assistant
|
|
79
86
|
</Text>
|
|
80
87
|
</View>
|
|
81
|
-
|
|
82
88
|
<ScrollView
|
|
83
89
|
ref={scrollViewRef}
|
|
84
90
|
className="flex-1 mb-4"
|
|
@@ -86,85 +92,80 @@ export default function AIScreen() {
|
|
|
86
92
|
>
|
|
87
93
|
{messages.length === 0 ? (
|
|
88
94
|
<View className="flex-1 justify-center items-center">
|
|
89
|
-
<Text className="text-center text-muted
|
|
95
|
+
<Text className="text-center text-muted text-lg">
|
|
90
96
|
Ask me anything to get started!
|
|
91
97
|
</Text>
|
|
92
98
|
</View>
|
|
93
99
|
) : (
|
|
94
|
-
<View className="
|
|
100
|
+
<View className="gap-3">
|
|
95
101
|
{messages.map((message) => (
|
|
96
|
-
<
|
|
102
|
+
<Card
|
|
97
103
|
key={message.id}
|
|
98
|
-
|
|
104
|
+
variant={message.role === "user" ? "flat" : "secondary"}
|
|
105
|
+
className={`p-3 ${
|
|
99
106
|
message.role === "user"
|
|
100
|
-
? "bg-
|
|
101
|
-
: "
|
|
107
|
+
? "ml-8 bg-accent/10"
|
|
108
|
+
: "mr-8"
|
|
102
109
|
}`}
|
|
103
110
|
>
|
|
104
111
|
<Text className="text-sm font-semibold mb-1 text-foreground">
|
|
105
112
|
{message.role === "user" ? "You" : "AI Assistant"}
|
|
106
113
|
</Text>
|
|
107
|
-
<View className="
|
|
108
|
-
{message.parts.map((part, i) =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
return (
|
|
114
|
+
<View className="gap-1">
|
|
115
|
+
{message.parts.map((part, i) =>
|
|
116
|
+
part.type === "text" ? (
|
|
117
|
+
<Text
|
|
118
|
+
key={`${message.id}-${i}`}
|
|
119
|
+
className="text-foreground leading-relaxed"
|
|
120
|
+
>
|
|
121
|
+
{part.text}
|
|
122
|
+
</Text>
|
|
123
|
+
) : (
|
|
120
124
|
<Text
|
|
121
125
|
key={`${message.id}-${i}`}
|
|
122
126
|
className="text-foreground leading-relaxed"
|
|
123
127
|
>
|
|
124
128
|
{JSON.stringify(part)}
|
|
125
129
|
</Text>
|
|
126
|
-
)
|
|
127
|
-
|
|
130
|
+
)
|
|
131
|
+
)}
|
|
128
132
|
</View>
|
|
129
|
-
</
|
|
133
|
+
</Card>
|
|
130
134
|
))}
|
|
131
135
|
</View>
|
|
132
136
|
)}
|
|
133
137
|
</ScrollView>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<View className="flex-row items-end space-x-2">
|
|
138
|
+
<View className="border-t border-divider pt-4">
|
|
139
|
+
<View className="flex-row items-end gap-2">
|
|
137
140
|
<TextInput
|
|
138
141
|
value={input}
|
|
139
142
|
onChangeText={setInput}
|
|
140
143
|
placeholder="Type your message..."
|
|
141
|
-
placeholderTextColor=
|
|
142
|
-
className="flex-1 border border-
|
|
144
|
+
placeholderTextColor={mutedColor}
|
|
145
|
+
className="flex-1 border border-divider rounded-lg px-3 py-2 text-foreground bg-surface min-h-[40px] max-h-[120px]"
|
|
143
146
|
onSubmitEditing={(e) => {
|
|
144
147
|
e.preventDefault();
|
|
145
148
|
onSubmit();
|
|
146
149
|
}}
|
|
147
150
|
autoFocus={true}
|
|
148
151
|
/>
|
|
149
|
-
<
|
|
152
|
+
<Pressable
|
|
150
153
|
onPress={onSubmit}
|
|
151
154
|
disabled={!input.trim()}
|
|
152
|
-
className={`p-2 rounded-
|
|
153
|
-
input.trim()
|
|
154
|
-
? "bg-primary"
|
|
155
|
-
: "bg-muted"
|
|
155
|
+
className={`p-2 rounded-lg active:opacity-70 ${
|
|
156
|
+
input.trim() ? "bg-accent" : "bg-surface"
|
|
156
157
|
}`}
|
|
157
158
|
>
|
|
158
159
|
<Ionicons
|
|
159
160
|
name="send"
|
|
160
161
|
size={20}
|
|
161
|
-
color={input.trim() ?
|
|
162
|
+
color={input.trim() ? foregroundColor : mutedColor}
|
|
162
163
|
/>
|
|
163
|
-
</
|
|
164
|
+
</Pressable>
|
|
164
165
|
</View>
|
|
165
166
|
</View>
|
|
166
167
|
</View>
|
|
167
168
|
</KeyboardAvoidingView>
|
|
168
169
|
</Container>
|
|
169
170
|
);
|
|
170
|
-
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import structuredClone from "@ungap/structured-clone";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
|
|
4
|
+
if (Platform.OS !== "web") {
|
|
5
|
+
const setupPolyfills = async () => {
|
|
6
|
+
const { polyfillGlobal } = await import(
|
|
7
|
+
"react-native/Libraries/Utilities/PolyfillFunctions"
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
const { TextEncoderStream, TextDecoderStream } = await import(
|
|
11
|
+
"@stardazed/streams-text-encoding"
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
if (!("structuredClone" in global)) {
|
|
15
|
+
polyfillGlobal("structuredClone", () => structuredClone);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
polyfillGlobal("TextEncoderStream", () => TextEncoderStream);
|
|
19
|
+
polyfillGlobal("TextDecoderStream", () => TextDecoderStream);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
setupPolyfills();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {};
|
|
26
|
+
|