create-better-t-stack 3.10.0 → 3.11.0
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/dist/cli.mjs +1 -1
- package/dist/index.d.mts +7 -3
- package/dist/index.mjs +1 -1
- package/dist/{src-QkFdHtZE.mjs → src-XVvJUQ_h.mjs} +257 -83
- package/package.json +8 -8
- package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +1 -1
- package/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs +17 -0
- package/templates/examples/ai/convex/packages/backend/convex/agent.ts.hbs +9 -0
- package/templates/examples/ai/convex/packages/backend/convex/chat.ts.hbs +67 -0
- package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +301 -3
- package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +296 -10
- package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +180 -1
- package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +172 -9
- package/templates/examples/ai/web/react/react-router/src/routes/ai.tsx.hbs +156 -6
- package/templates/examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs +156 -4
- package/templates/examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs +159 -6
- package/templates/frontend/react/web-base/src/index.css.hbs +1 -1
- package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +0 -7
- package/templates/examples/ai/web/react/base/src/components/response.tsx.hbs +0 -22
|
@@ -1,3 +1,297 @@
|
|
|
1
|
+
{{#if (eq backend "convex")}}
|
|
2
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
3
|
+
import {
|
|
4
|
+
useUIMessages,
|
|
5
|
+
useSmoothText,
|
|
6
|
+
type UIMessage,
|
|
7
|
+
} from "@convex-dev/agent/react";
|
|
8
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
9
|
+
import { useMutation } from "convex/react";
|
|
10
|
+
import React, { useRef, useEffect, useState } from "react";
|
|
11
|
+
import {
|
|
12
|
+
View,
|
|
13
|
+
Text,
|
|
14
|
+
TextInput,
|
|
15
|
+
TouchableOpacity,
|
|
16
|
+
ScrollView,
|
|
17
|
+
KeyboardAvoidingView,
|
|
18
|
+
Platform,
|
|
19
|
+
ActivityIndicator,
|
|
20
|
+
} from "react-native";
|
|
21
|
+
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
22
|
+
|
|
23
|
+
import { Container } from "@/components/container";
|
|
24
|
+
|
|
25
|
+
function MessageContent({
|
|
26
|
+
text,
|
|
27
|
+
isStreaming,
|
|
28
|
+
style,
|
|
29
|
+
}: {
|
|
30
|
+
text: string;
|
|
31
|
+
isStreaming: boolean;
|
|
32
|
+
style: object;
|
|
33
|
+
}) {
|
|
34
|
+
const [visibleText] = useSmoothText(text, {
|
|
35
|
+
startStreaming: isStreaming,
|
|
36
|
+
});
|
|
37
|
+
return <Text style={style}>{visibleText}</Text>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default function AIScreen() {
|
|
41
|
+
const { theme } = useUnistyles();
|
|
42
|
+
const [input, setInput] = useState("");
|
|
43
|
+
const [threadId, setThreadId] = useState<string | null>(null);
|
|
44
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
45
|
+
const scrollViewRef = useRef<ScrollView>(null);
|
|
46
|
+
|
|
47
|
+
const createThread = useMutation(api.chat.createNewThread);
|
|
48
|
+
const sendMessage = useMutation(api.chat.sendMessage);
|
|
49
|
+
|
|
50
|
+
const { results: messages } = useUIMessages(
|
|
51
|
+
api.chat.listMessages,
|
|
52
|
+
threadId ? { threadId } : "skip",
|
|
53
|
+
{ initialNumItems: 50, stream: true },
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const hasStreamingMessage = messages?.some(
|
|
57
|
+
(m: UIMessage) => m.status === "streaming",
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
scrollViewRef.current?.scrollToEnd({ animated: true });
|
|
62
|
+
}, [messages]);
|
|
63
|
+
|
|
64
|
+
const onSubmit = async () => {
|
|
65
|
+
const value = input.trim();
|
|
66
|
+
if (!value || isLoading) return;
|
|
67
|
+
|
|
68
|
+
setIsLoading(true);
|
|
69
|
+
setInput("");
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
let currentThreadId = threadId;
|
|
73
|
+
if (!currentThreadId) {
|
|
74
|
+
currentThreadId = await createThread();
|
|
75
|
+
setThreadId(currentThreadId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await sendMessage({ threadId: currentThreadId, prompt: value });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error("Failed to send message:", error);
|
|
81
|
+
} finally {
|
|
82
|
+
setIsLoading(false);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Container>
|
|
88
|
+
<KeyboardAvoidingView
|
|
89
|
+
style={styles.container}
|
|
90
|
+
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
91
|
+
>
|
|
92
|
+
<View style={styles.content}>
|
|
93
|
+
<View style={styles.header}>
|
|
94
|
+
<Text style={styles.headerTitle}>AI Chat</Text>
|
|
95
|
+
<Text style={styles.headerSubtitle}>
|
|
96
|
+
Chat with our AI assistant
|
|
97
|
+
</Text>
|
|
98
|
+
</View>
|
|
99
|
+
|
|
100
|
+
<ScrollView
|
|
101
|
+
ref={scrollViewRef}
|
|
102
|
+
style={styles.messagesContainer}
|
|
103
|
+
showsVerticalScrollIndicator={false}
|
|
104
|
+
>
|
|
105
|
+
{!messages || messages.length === 0 ? (
|
|
106
|
+
<View style={styles.emptyContainer}>
|
|
107
|
+
<Text style={styles.emptyText}>
|
|
108
|
+
Ask me anything to get started!
|
|
109
|
+
</Text>
|
|
110
|
+
</View>
|
|
111
|
+
) : (
|
|
112
|
+
<View style={styles.messagesWrapper}>
|
|
113
|
+
{messages.map((message: UIMessage) => (
|
|
114
|
+
<View
|
|
115
|
+
key={message.key}
|
|
116
|
+
style={[
|
|
117
|
+
styles.messageContainer,
|
|
118
|
+
message.role === "user"
|
|
119
|
+
? styles.userMessage
|
|
120
|
+
: styles.assistantMessage,
|
|
121
|
+
]}
|
|
122
|
+
>
|
|
123
|
+
<Text style={styles.messageRole}>
|
|
124
|
+
{message.role === "user" ? "You" : "AI Assistant"}
|
|
125
|
+
</Text>
|
|
126
|
+
<MessageContent
|
|
127
|
+
text={message.text ?? ""}
|
|
128
|
+
isStreaming={message.status === "streaming"}
|
|
129
|
+
style={styles.messageContent}
|
|
130
|
+
/>
|
|
131
|
+
</View>
|
|
132
|
+
))}
|
|
133
|
+
{isLoading && !hasStreamingMessage && (
|
|
134
|
+
<View style={[styles.messageContainer, styles.assistantMessage]}>
|
|
135
|
+
<Text style={styles.messageRole}>AI Assistant</Text>
|
|
136
|
+
<View style={styles.loadingContainer}>
|
|
137
|
+
<ActivityIndicator size="small" color={theme.colors.primary} />
|
|
138
|
+
<Text style={styles.loadingText}>Thinking...</Text>
|
|
139
|
+
</View>
|
|
140
|
+
</View>
|
|
141
|
+
)}
|
|
142
|
+
</View>
|
|
143
|
+
)}
|
|
144
|
+
</ScrollView>
|
|
145
|
+
|
|
146
|
+
<View style={styles.inputSection}>
|
|
147
|
+
<View style={styles.inputContainer}>
|
|
148
|
+
<TextInput
|
|
149
|
+
value={input}
|
|
150
|
+
onChangeText={setInput}
|
|
151
|
+
placeholder="Type your message..."
|
|
152
|
+
placeholderTextColor={theme.colors.border}
|
|
153
|
+
style={styles.textInput}
|
|
154
|
+
onSubmitEditing={(e) => {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
onSubmit();
|
|
157
|
+
}}
|
|
158
|
+
editable={!isLoading}
|
|
159
|
+
autoFocus={true}
|
|
160
|
+
/>
|
|
161
|
+
<TouchableOpacity
|
|
162
|
+
onPress={onSubmit}
|
|
163
|
+
disabled={!input.trim() || isLoading}
|
|
164
|
+
style={[
|
|
165
|
+
styles.sendButton,
|
|
166
|
+
(!input.trim() || isLoading) && styles.sendButtonDisabled,
|
|
167
|
+
]}
|
|
168
|
+
>
|
|
169
|
+
<Ionicons
|
|
170
|
+
name="send"
|
|
171
|
+
size={20}
|
|
172
|
+
color={
|
|
173
|
+
input.trim() && !isLoading
|
|
174
|
+
? theme.colors.background
|
|
175
|
+
: theme.colors.border
|
|
176
|
+
}
|
|
177
|
+
/>
|
|
178
|
+
</TouchableOpacity>
|
|
179
|
+
</View>
|
|
180
|
+
</View>
|
|
181
|
+
</View>
|
|
182
|
+
</KeyboardAvoidingView>
|
|
183
|
+
</Container>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const styles = StyleSheet.create((theme) => ({
|
|
188
|
+
container: {
|
|
189
|
+
flex: 1,
|
|
190
|
+
},
|
|
191
|
+
content: {
|
|
192
|
+
flex: 1,
|
|
193
|
+
paddingHorizontal: theme.spacing.md,
|
|
194
|
+
paddingVertical: theme.spacing.lg,
|
|
195
|
+
},
|
|
196
|
+
header: {
|
|
197
|
+
marginBottom: theme.spacing.lg,
|
|
198
|
+
},
|
|
199
|
+
headerTitle: {
|
|
200
|
+
fontSize: 28,
|
|
201
|
+
fontWeight: "bold",
|
|
202
|
+
color: theme.colors.typography,
|
|
203
|
+
marginBottom: theme.spacing.sm,
|
|
204
|
+
},
|
|
205
|
+
headerSubtitle: {
|
|
206
|
+
fontSize: 16,
|
|
207
|
+
color: theme.colors.typography,
|
|
208
|
+
},
|
|
209
|
+
messagesContainer: {
|
|
210
|
+
flex: 1,
|
|
211
|
+
marginBottom: theme.spacing.md,
|
|
212
|
+
},
|
|
213
|
+
emptyContainer: {
|
|
214
|
+
flex: 1,
|
|
215
|
+
justifyContent: "center",
|
|
216
|
+
alignItems: "center",
|
|
217
|
+
},
|
|
218
|
+
emptyText: {
|
|
219
|
+
textAlign: "center",
|
|
220
|
+
color: theme.colors.typography,
|
|
221
|
+
fontSize: 18,
|
|
222
|
+
},
|
|
223
|
+
messagesWrapper: {
|
|
224
|
+
gap: theme.spacing.md,
|
|
225
|
+
},
|
|
226
|
+
messageContainer: {
|
|
227
|
+
padding: theme.spacing.md,
|
|
228
|
+
borderRadius: 8,
|
|
229
|
+
},
|
|
230
|
+
userMessage: {
|
|
231
|
+
backgroundColor: theme.colors.primary + "20",
|
|
232
|
+
marginLeft: theme.spacing.xl,
|
|
233
|
+
alignSelf: "flex-end",
|
|
234
|
+
},
|
|
235
|
+
assistantMessage: {
|
|
236
|
+
backgroundColor: theme.colors.background,
|
|
237
|
+
marginRight: theme.spacing.xl,
|
|
238
|
+
borderWidth: 1,
|
|
239
|
+
borderColor: theme.colors.border,
|
|
240
|
+
},
|
|
241
|
+
messageRole: {
|
|
242
|
+
fontSize: 14,
|
|
243
|
+
fontWeight: "600",
|
|
244
|
+
marginBottom: theme.spacing.sm,
|
|
245
|
+
color: theme.colors.typography,
|
|
246
|
+
},
|
|
247
|
+
messageContent: {
|
|
248
|
+
color: theme.colors.typography,
|
|
249
|
+
lineHeight: 20,
|
|
250
|
+
},
|
|
251
|
+
loadingContainer: {
|
|
252
|
+
flexDirection: "row",
|
|
253
|
+
alignItems: "center",
|
|
254
|
+
gap: theme.spacing.sm,
|
|
255
|
+
},
|
|
256
|
+
loadingText: {
|
|
257
|
+
color: theme.colors.typography,
|
|
258
|
+
opacity: 0.7,
|
|
259
|
+
},
|
|
260
|
+
inputSection: {
|
|
261
|
+
borderTopWidth: 1,
|
|
262
|
+
borderTopColor: theme.colors.border,
|
|
263
|
+
paddingTop: theme.spacing.md,
|
|
264
|
+
},
|
|
265
|
+
inputContainer: {
|
|
266
|
+
flexDirection: "row",
|
|
267
|
+
alignItems: "flex-end",
|
|
268
|
+
gap: theme.spacing.sm,
|
|
269
|
+
},
|
|
270
|
+
textInput: {
|
|
271
|
+
flex: 1,
|
|
272
|
+
borderWidth: 1,
|
|
273
|
+
borderColor: theme.colors.border,
|
|
274
|
+
borderRadius: 8,
|
|
275
|
+
paddingHorizontal: theme.spacing.md,
|
|
276
|
+
paddingVertical: theme.spacing.sm,
|
|
277
|
+
color: theme.colors.typography,
|
|
278
|
+
backgroundColor: theme.colors.background,
|
|
279
|
+
fontSize: 16,
|
|
280
|
+
minHeight: 40,
|
|
281
|
+
maxHeight: 120,
|
|
282
|
+
},
|
|
283
|
+
sendButton: {
|
|
284
|
+
backgroundColor: theme.colors.primary,
|
|
285
|
+
padding: theme.spacing.sm,
|
|
286
|
+
borderRadius: 8,
|
|
287
|
+
justifyContent: "center",
|
|
288
|
+
alignItems: "center",
|
|
289
|
+
},
|
|
290
|
+
sendButtonDisabled: {
|
|
291
|
+
backgroundColor: theme.colors.border,
|
|
292
|
+
},
|
|
293
|
+
}));
|
|
294
|
+
{{else}}
|
|
1
295
|
import React, { useRef, useEffect, useState } from "react";
|
|
2
296
|
import {
|
|
3
297
|
View,
|
|
@@ -256,15 +550,6 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
256
550
|
color: theme.colors.typography,
|
|
257
551
|
lineHeight: 20,
|
|
258
552
|
},
|
|
259
|
-
toolInvocations: {
|
|
260
|
-
fontSize: 12,
|
|
261
|
-
color: theme.colors.typography,
|
|
262
|
-
fontFamily: "monospace",
|
|
263
|
-
backgroundColor: theme.colors.border + "40",
|
|
264
|
-
padding: theme.spacing.sm,
|
|
265
|
-
borderRadius: 4,
|
|
266
|
-
marginTop: theme.spacing.sm,
|
|
267
|
-
},
|
|
268
553
|
inputSection: {
|
|
269
554
|
borderTopWidth: 1,
|
|
270
555
|
borderTopColor: theme.colors.border,
|
|
@@ -298,4 +583,5 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
298
583
|
sendButtonDisabled: {
|
|
299
584
|
backgroundColor: theme.colors.border,
|
|
300
585
|
},
|
|
301
|
-
}));
|
|
586
|
+
}));
|
|
587
|
+
{{/if}}
|
|
@@ -1,3 +1,181 @@
|
|
|
1
|
+
{{#if (eq backend "convex")}}
|
|
2
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
3
|
+
import {
|
|
4
|
+
useUIMessages,
|
|
5
|
+
useSmoothText,
|
|
6
|
+
type UIMessage,
|
|
7
|
+
} from "@convex-dev/agent/react";
|
|
8
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
9
|
+
import { useMutation } from "convex/react";
|
|
10
|
+
import { Card, useThemeColor } from "heroui-native";
|
|
11
|
+
import { useRef, useEffect, useState } from "react";
|
|
12
|
+
import {
|
|
13
|
+
View,
|
|
14
|
+
Text,
|
|
15
|
+
TextInput,
|
|
16
|
+
Pressable,
|
|
17
|
+
ScrollView,
|
|
18
|
+
KeyboardAvoidingView,
|
|
19
|
+
Platform,
|
|
20
|
+
ActivityIndicator,
|
|
21
|
+
} from "react-native";
|
|
22
|
+
|
|
23
|
+
import { Container } from "@/components/container";
|
|
24
|
+
|
|
25
|
+
function MessageContent({
|
|
26
|
+
text,
|
|
27
|
+
isStreaming,
|
|
28
|
+
}: {
|
|
29
|
+
text: string;
|
|
30
|
+
isStreaming: boolean;
|
|
31
|
+
}) {
|
|
32
|
+
const [visibleText] = useSmoothText(text, {
|
|
33
|
+
startStreaming: isStreaming,
|
|
34
|
+
});
|
|
35
|
+
return <Text className="text-foreground leading-relaxed">{visibleText}</Text>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function AIScreen() {
|
|
39
|
+
const [input, setInput] = useState("");
|
|
40
|
+
const [threadId, setThreadId] = useState<string | null>(null);
|
|
41
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
42
|
+
const scrollViewRef = useRef<ScrollView>(null);
|
|
43
|
+
const mutedColor = useThemeColor("muted");
|
|
44
|
+
const accentColor = useThemeColor("accent");
|
|
45
|
+
const foregroundColor = useThemeColor("foreground");
|
|
46
|
+
|
|
47
|
+
const createThread = useMutation(api.chat.createNewThread);
|
|
48
|
+
const sendMessage = useMutation(api.chat.sendMessage);
|
|
49
|
+
|
|
50
|
+
const { results: messages } = useUIMessages(
|
|
51
|
+
api.chat.listMessages,
|
|
52
|
+
threadId ? { threadId } : "skip",
|
|
53
|
+
{ initialNumItems: 50, stream: true },
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const hasStreamingMessage = messages?.some(
|
|
57
|
+
(m: UIMessage) => m.status === "streaming",
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
scrollViewRef.current?.scrollToEnd({ animated: true });
|
|
62
|
+
}, [messages]);
|
|
63
|
+
|
|
64
|
+
const onSubmit = async () => {
|
|
65
|
+
const value = input.trim();
|
|
66
|
+
if (!value || isLoading) return;
|
|
67
|
+
|
|
68
|
+
setIsLoading(true);
|
|
69
|
+
setInput("");
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
let currentThreadId = threadId;
|
|
73
|
+
if (!currentThreadId) {
|
|
74
|
+
currentThreadId = await createThread();
|
|
75
|
+
setThreadId(currentThreadId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await sendMessage({ threadId: currentThreadId, prompt: value });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error("Failed to send message:", error);
|
|
81
|
+
} finally {
|
|
82
|
+
setIsLoading(false);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Container>
|
|
88
|
+
<KeyboardAvoidingView
|
|
89
|
+
className="flex-1"
|
|
90
|
+
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
91
|
+
>
|
|
92
|
+
<View className="flex-1 px-4 py-6">
|
|
93
|
+
<View className="mb-6">
|
|
94
|
+
<Text className="text-foreground text-2xl font-bold mb-2">
|
|
95
|
+
AI Chat
|
|
96
|
+
</Text>
|
|
97
|
+
<Text className="text-muted">Chat with our AI assistant</Text>
|
|
98
|
+
</View>
|
|
99
|
+
<ScrollView
|
|
100
|
+
ref={scrollViewRef}
|
|
101
|
+
className="flex-1 mb-4"
|
|
102
|
+
showsVerticalScrollIndicator={false}
|
|
103
|
+
>
|
|
104
|
+
{!messages || messages.length === 0 ? (
|
|
105
|
+
<View className="flex-1 justify-center items-center">
|
|
106
|
+
<Text className="text-center text-muted text-lg">
|
|
107
|
+
Ask me anything to get started!
|
|
108
|
+
</Text>
|
|
109
|
+
</View>
|
|
110
|
+
) : (
|
|
111
|
+
<View className="gap-3">
|
|
112
|
+
{messages.map((message: UIMessage) => (
|
|
113
|
+
<Card
|
|
114
|
+
key={message.key}
|
|
115
|
+
variant="secondary"
|
|
116
|
+
className={`p-3 ${message.role === "user" ? "ml-8 bg-accent/10" : "mr-8"}`}
|
|
117
|
+
>
|
|
118
|
+
<Text className="text-sm font-semibold mb-1 text-foreground">
|
|
119
|
+
{message.role === "user" ? "You" : "AI Assistant"}
|
|
120
|
+
</Text>
|
|
121
|
+
<MessageContent
|
|
122
|
+
text={message.text ?? ""}
|
|
123
|
+
isStreaming={message.status === "streaming"}
|
|
124
|
+
/>
|
|
125
|
+
</Card>
|
|
126
|
+
))}
|
|
127
|
+
{isLoading && !hasStreamingMessage && (
|
|
128
|
+
<Card variant="secondary" className="p-3 mr-8">
|
|
129
|
+
<Text className="text-sm font-semibold mb-1 text-foreground">
|
|
130
|
+
AI Assistant
|
|
131
|
+
</Text>
|
|
132
|
+
<View className="flex-row items-center gap-2">
|
|
133
|
+
<ActivityIndicator size="small" color={accentColor} />
|
|
134
|
+
<Text className="text-muted">Thinking...</Text>
|
|
135
|
+
</View>
|
|
136
|
+
</Card>
|
|
137
|
+
)}
|
|
138
|
+
</View>
|
|
139
|
+
)}
|
|
140
|
+
</ScrollView>
|
|
141
|
+
<View className="border-t border-divider pt-4">
|
|
142
|
+
<View className="flex-row items-end gap-2">
|
|
143
|
+
<TextInput
|
|
144
|
+
value={input}
|
|
145
|
+
onChangeText={setInput}
|
|
146
|
+
placeholder="Type your message..."
|
|
147
|
+
placeholderTextColor={mutedColor}
|
|
148
|
+
className="flex-1 border border-divider rounded-lg px-3 py-2 text-foreground bg-surface min-h-10 max-h-30"
|
|
149
|
+
onSubmitEditing={(e) => {
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
onSubmit();
|
|
152
|
+
}}
|
|
153
|
+
editable={!isLoading}
|
|
154
|
+
autoFocus={true}
|
|
155
|
+
/>
|
|
156
|
+
<Pressable
|
|
157
|
+
onPress={onSubmit}
|
|
158
|
+
disabled={!input.trim() || isLoading}
|
|
159
|
+
className={`p-2 rounded-lg active:opacity-70 ${
|
|
160
|
+
input.trim() && !isLoading ? "bg-accent" : "bg-surface"
|
|
161
|
+
}`}
|
|
162
|
+
>
|
|
163
|
+
<Ionicons
|
|
164
|
+
name="send"
|
|
165
|
+
size={20}
|
|
166
|
+
color={
|
|
167
|
+
input.trim() && !isLoading ? foregroundColor : mutedColor
|
|
168
|
+
}
|
|
169
|
+
/>
|
|
170
|
+
</Pressable>
|
|
171
|
+
</View>
|
|
172
|
+
</View>
|
|
173
|
+
</View>
|
|
174
|
+
</KeyboardAvoidingView>
|
|
175
|
+
</Container>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
{{else}}
|
|
1
179
|
import { useRef, useEffect, useState } from "react";
|
|
2
180
|
import {
|
|
3
181
|
View,
|
|
@@ -168,4 +346,5 @@ export default function AIScreen() {
|
|
|
168
346
|
</KeyboardAvoidingView>
|
|
169
347
|
</Container>
|
|
170
348
|
);
|
|
171
|
-
}
|
|
349
|
+
}
|
|
350
|
+
{{/if}}
|