create-better-t-stack 3.12.5 → 3.12.7
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.mjs +1 -1
- package/dist/{src-D3EHRs6z.mjs → src-DNjxspj9.mjs} +32 -21
- package/package.json +2 -2
- package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +3 -3
- package/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs +32 -50
- package/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs +40 -57
- package/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs +32 -35
- package/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs +49 -37
- package/templates/auth/better-auth/web/nuxt/app/components/SignInForm.vue.hbs +40 -35
- package/templates/auth/better-auth/web/nuxt/app/components/SignUpForm.vue.hbs +47 -40
- package/templates/auth/better-auth/web/nuxt/app/middleware/auth.ts.hbs +9 -7
- package/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs +61 -29
- package/templates/auth/better-auth/web/nuxt/app/pages/login.vue.hbs +6 -3
- package/templates/backend/server/elysia/src/index.ts.hbs +8 -3
- package/templates/backend/server/express/src/index.ts.hbs +8 -3
- package/templates/backend/server/fastify/src/index.ts.hbs +8 -3
- package/templates/backend/server/hono/src/index.ts.hbs +16 -6
- package/templates/examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs +8 -3
- package/templates/examples/ai/fullstack/tanstack-start/src/routes/api/ai/$.ts.hbs +8 -3
- package/templates/examples/ai/native/bare/polyfills.js +14 -11
- package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +104 -124
- package/templates/examples/ai/web/nuxt/app/pages/ai.vue.hbs +22 -22
- package/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs +67 -80
- package/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs +115 -90
- package/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +60 -54
- package/templates/frontend/native/uniwind/app/+not-found.tsx.hbs +16 -21
- package/templates/frontend/native/uniwind/app/modal.tsx.hbs +18 -34
- package/templates/frontend/native/uniwind/package.json.hbs +10 -10
- package/templates/frontend/nuxt/app/components/Header.vue.hbs +25 -29
- package/templates/frontend/nuxt/app/layouts/default.vue.hbs +7 -8
- package/templates/frontend/nuxt/app/pages/index.vue.hbs +58 -39
- package/templates/frontend/nuxt/package.json.hbs +1 -1
- package/templates/frontend/react/tanstack-router/package.json.hbs +1 -1
- package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +5 -5
- package/templates/frontend/nuxt/app/components/Loader.vue.hbs +0 -5
- package/templates/frontend/nuxt/app/components/ModeToggle.vue.hbs +0 -25
|
@@ -7,17 +7,14 @@ import {
|
|
|
7
7
|
} from "@convex-dev/agent/react";
|
|
8
8
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
9
9
|
import { useMutation } from "convex/react";
|
|
10
|
-
import {
|
|
10
|
+
import { Button, Divider, Spinner, Surface, TextField, useThemeColor } from "heroui-native";
|
|
11
11
|
import { useRef, useEffect, useState } from "react";
|
|
12
12
|
import {
|
|
13
13
|
View,
|
|
14
14
|
Text,
|
|
15
|
-
TextInput,
|
|
16
|
-
Pressable,
|
|
17
15
|
ScrollView,
|
|
18
16
|
KeyboardAvoidingView,
|
|
19
17
|
Platform,
|
|
20
|
-
ActivityIndicator,
|
|
21
18
|
} from "react-native";
|
|
22
19
|
|
|
23
20
|
import { Container } from "@/components/container";
|
|
@@ -32,7 +29,7 @@ function MessageContent({
|
|
|
32
29
|
const [visibleText] = useSmoothText(text, {
|
|
33
30
|
startStreaming: isStreaming,
|
|
34
31
|
});
|
|
35
|
-
return <Text className="text-foreground leading-relaxed">{visibleText}</Text>;
|
|
32
|
+
return <Text className="text-foreground text-sm leading-relaxed">{visibleText}</Text>;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
export default function AIScreen() {
|
|
@@ -41,7 +38,6 @@ export default function AIScreen() {
|
|
|
41
38
|
const [isLoading, setIsLoading] = useState(false);
|
|
42
39
|
const scrollViewRef = useRef<ScrollView>(null);
|
|
43
40
|
const mutedColor = useThemeColor("muted");
|
|
44
|
-
const accentColor = useThemeColor("accent");
|
|
45
41
|
const foregroundColor = useThemeColor("foreground");
|
|
46
42
|
|
|
47
43
|
const createThread = useMutation(api.chat.createNewThread);
|
|
@@ -89,86 +85,80 @@ export default function AIScreen() {
|
|
|
89
85
|
className="flex-1"
|
|
90
86
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
91
87
|
>
|
|
92
|
-
<View className="flex-1 px-4 py-
|
|
93
|
-
<View className="mb-
|
|
94
|
-
<Text className="text-
|
|
95
|
-
|
|
96
|
-
</Text>
|
|
97
|
-
<Text className="text-muted">Chat with our AI assistant</Text>
|
|
88
|
+
<View className="flex-1 px-4 py-4">
|
|
89
|
+
<View className="py-4 mb-4">
|
|
90
|
+
<Text className="text-2xl font-semibold text-foreground tracking-tight">AI Chat</Text>
|
|
91
|
+
<Text className="text-muted text-sm mt-1">Chat with our AI assistant</Text>
|
|
98
92
|
</View>
|
|
93
|
+
|
|
99
94
|
<ScrollView
|
|
100
95
|
ref={scrollViewRef}
|
|
101
96
|
className="flex-1 mb-4"
|
|
102
97
|
showsVerticalScrollIndicator={false}
|
|
103
98
|
>
|
|
104
99
|
{!messages || messages.length === 0 ? (
|
|
105
|
-
<View className="flex-1 justify-center items-center">
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
</Text>
|
|
100
|
+
<View className="flex-1 justify-center items-center py-10">
|
|
101
|
+
<Ionicons name="chatbubble-ellipses-outline" size={32} color={mutedColor} />
|
|
102
|
+
<Text className="text-muted text-sm mt-3">Ask me anything to get started</Text>
|
|
109
103
|
</View>
|
|
110
104
|
) : (
|
|
111
|
-
<View className="gap-
|
|
105
|
+
<View className="gap-2">
|
|
112
106
|
{messages.map((message: UIMessage) => (
|
|
113
|
-
<
|
|
107
|
+
<Surface
|
|
114
108
|
key={message.key}
|
|
115
|
-
variant="secondary"
|
|
116
|
-
className={`p-3 ${message.role === "user" ? "ml-
|
|
109
|
+
variant={message.role === "user" ? "tertiary" : "secondary"}
|
|
110
|
+
className={`p-3 rounded-lg ${message.role === "user" ? "ml-10" : "mr-10"}`}
|
|
117
111
|
>
|
|
118
|
-
<Text className="text-
|
|
119
|
-
{message.role === "user" ? "You" : "AI
|
|
112
|
+
<Text className="text-xs font-medium mb-1 text-muted">
|
|
113
|
+
{message.role === "user" ? "You" : "AI"}
|
|
120
114
|
</Text>
|
|
121
115
|
<MessageContent
|
|
122
116
|
text={message.text ?? ""}
|
|
123
117
|
isStreaming={message.status === "streaming"}
|
|
124
118
|
/>
|
|
125
|
-
</
|
|
119
|
+
</Surface>
|
|
126
120
|
))}
|
|
127
121
|
{isLoading && !hasStreamingMessage && (
|
|
128
|
-
<
|
|
129
|
-
<Text className="text-
|
|
130
|
-
AI Assistant
|
|
131
|
-
</Text>
|
|
122
|
+
<Surface variant="secondary" className="p-3 mr-10 rounded-lg">
|
|
123
|
+
<Text className="text-xs font-medium mb-1 text-muted">AI</Text>
|
|
132
124
|
<View className="flex-row items-center gap-2">
|
|
133
|
-
<
|
|
134
|
-
<Text className="text-muted">Thinking...</Text>
|
|
125
|
+
<Spinner size="sm" />
|
|
126
|
+
<Text className="text-muted text-sm">Thinking...</Text>
|
|
135
127
|
</View>
|
|
136
|
-
</
|
|
128
|
+
</Surface>
|
|
137
129
|
)}
|
|
138
130
|
</View>
|
|
139
131
|
)}
|
|
140
132
|
</ScrollView>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
onSubmit
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
}
|
|
133
|
+
|
|
134
|
+
<Divider className="mb-3" />
|
|
135
|
+
|
|
136
|
+
<View className="flex-row items-center gap-2">
|
|
137
|
+
<View className="flex-1">
|
|
138
|
+
<TextField>
|
|
139
|
+
<TextField.Input
|
|
140
|
+
value={input}
|
|
141
|
+
onChangeText={setInput}
|
|
142
|
+
placeholder="Type a message..."
|
|
143
|
+
onSubmitEditing={onSubmit}
|
|
144
|
+
editable={!isLoading}
|
|
145
|
+
autoFocus
|
|
169
146
|
/>
|
|
170
|
-
</
|
|
147
|
+
</TextField>
|
|
171
148
|
</View>
|
|
149
|
+
<Button
|
|
150
|
+
isIconOnly
|
|
151
|
+
variant={input.trim() && !isLoading ? "primary" : "secondary"}
|
|
152
|
+
onPress={onSubmit}
|
|
153
|
+
isDisabled={!input.trim() || isLoading}
|
|
154
|
+
size="sm"
|
|
155
|
+
>
|
|
156
|
+
<Ionicons
|
|
157
|
+
name="arrow-up"
|
|
158
|
+
size={18}
|
|
159
|
+
color={input.trim() && !isLoading ? foregroundColor : mutedColor}
|
|
160
|
+
/>
|
|
161
|
+
</Button>
|
|
172
162
|
</View>
|
|
173
163
|
</View>
|
|
174
164
|
</KeyboardAvoidingView>
|
|
@@ -180,8 +170,6 @@ import { useRef, useEffect, useState } from "react";
|
|
|
180
170
|
import {
|
|
181
171
|
View,
|
|
182
172
|
Text,
|
|
183
|
-
TextInput,
|
|
184
|
-
Pressable,
|
|
185
173
|
ScrollView,
|
|
186
174
|
KeyboardAvoidingView,
|
|
187
175
|
Platform,
|
|
@@ -191,7 +179,7 @@ import { DefaultChatTransport } from "ai";
|
|
|
191
179
|
import { fetch as expoFetch } from "expo/fetch";
|
|
192
180
|
import { Ionicons } from "@expo/vector-icons";
|
|
193
181
|
import { Container } from "@/components/container";
|
|
194
|
-
import {
|
|
182
|
+
import { Button, Divider, ErrorView, Spinner, Surface, TextField, useThemeColor } from "heroui-native";
|
|
195
183
|
import { env } from "@{{projectName}}/env/native";
|
|
196
184
|
|
|
197
185
|
const generateAPIUrl = (relativePath: string) => {
|
|
@@ -215,10 +203,8 @@ export default function AIScreen() {
|
|
|
215
203
|
onError: (error) => console.error(error, "AI Chat Error"),
|
|
216
204
|
});
|
|
217
205
|
const scrollViewRef = useRef<ScrollView>(null);
|
|
218
|
-
const mutedColor = useThemeColor("muted");
|
|
219
|
-
const accentColor = useThemeColor("accent");
|
|
220
206
|
const foregroundColor = useThemeColor("foreground");
|
|
221
|
-
const
|
|
207
|
+
const mutedColor = useThemeColor("muted");
|
|
222
208
|
|
|
223
209
|
useEffect(() => {
|
|
224
210
|
scrollViewRef.current?.scrollToEnd({ animated: true });
|
|
@@ -236,14 +222,16 @@ export default function AIScreen() {
|
|
|
236
222
|
return (
|
|
237
223
|
<Container>
|
|
238
224
|
<View className="flex-1 justify-center items-center px-4">
|
|
239
|
-
<
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
225
|
+
<Surface variant="secondary" className="p-4 rounded-lg">
|
|
226
|
+
<ErrorView isInvalid>
|
|
227
|
+
<Text className="text-danger text-center font-medium mb-1">
|
|
228
|
+
{error.message}
|
|
229
|
+
</Text>
|
|
230
|
+
<Text className="text-muted text-center text-xs">
|
|
231
|
+
Please check your connection and try again.
|
|
232
|
+
</Text>
|
|
233
|
+
</ErrorView>
|
|
234
|
+
</Surface>
|
|
247
235
|
</View>
|
|
248
236
|
</Container>
|
|
249
237
|
);
|
|
@@ -255,93 +243,85 @@ export default function AIScreen() {
|
|
|
255
243
|
className="flex-1"
|
|
256
244
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
257
245
|
>
|
|
258
|
-
<View className="flex-1 px-4 py-
|
|
259
|
-
<View className="mb-
|
|
260
|
-
<Text className="text-
|
|
261
|
-
|
|
262
|
-
</Text>
|
|
263
|
-
<Text className="text-muted">
|
|
264
|
-
Chat with our AI assistant
|
|
265
|
-
</Text>
|
|
246
|
+
<View className="flex-1 px-4 py-4">
|
|
247
|
+
<View className="py-4 mb-4">
|
|
248
|
+
<Text className="text-2xl font-semibold text-foreground tracking-tight">AI Chat</Text>
|
|
249
|
+
<Text className="text-muted text-sm mt-1">Chat with our AI assistant</Text>
|
|
266
250
|
</View>
|
|
251
|
+
|
|
267
252
|
<ScrollView
|
|
268
253
|
ref={scrollViewRef}
|
|
269
254
|
className="flex-1 mb-4"
|
|
270
255
|
showsVerticalScrollIndicator={false}
|
|
271
256
|
>
|
|
272
257
|
{messages.length === 0 ? (
|
|
273
|
-
<View className="flex-1 justify-center items-center">
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
</Text>
|
|
258
|
+
<View className="flex-1 justify-center items-center py-10">
|
|
259
|
+
<Ionicons name="chatbubble-ellipses-outline" size={32} color={mutedColor} />
|
|
260
|
+
<Text className="text-muted text-sm mt-3">Ask me anything to get started</Text>
|
|
277
261
|
</View>
|
|
278
262
|
) : (
|
|
279
|
-
<View className="gap-
|
|
263
|
+
<View className="gap-2">
|
|
280
264
|
{messages.map((message) => (
|
|
281
|
-
<
|
|
265
|
+
<Surface
|
|
282
266
|
key={message.id}
|
|
283
|
-
variant={message.role === "user" ? "
|
|
284
|
-
className={`p-3 ${
|
|
285
|
-
message.role === "user"
|
|
286
|
-
? "ml-8 bg-accent/10"
|
|
287
|
-
: "mr-8"
|
|
288
|
-
}`}
|
|
267
|
+
variant={message.role === "user" ? "tertiary" : "secondary"}
|
|
268
|
+
className={`p-3 rounded-lg ${message.role === "user" ? "ml-10" : "mr-10"}`}
|
|
289
269
|
>
|
|
290
|
-
<Text className="text-
|
|
291
|
-
{message.role === "user" ? "You" : "AI
|
|
270
|
+
<Text className="text-xs font-medium mb-1 text-muted">
|
|
271
|
+
{message.role === "user" ? "You" : "AI"}
|
|
292
272
|
</Text>
|
|
293
273
|
<View className="gap-1">
|
|
294
274
|
{message.parts.map((part, i) =>
|
|
295
275
|
part.type === "text" ? (
|
|
296
276
|
<Text
|
|
297
277
|
key={`${message.id}-${i}`}
|
|
298
|
-
className="text-foreground leading-relaxed"
|
|
278
|
+
className="text-foreground text-sm leading-relaxed"
|
|
299
279
|
>
|
|
300
280
|
{part.text}
|
|
301
281
|
</Text>
|
|
302
282
|
) : (
|
|
303
283
|
<Text
|
|
304
284
|
key={`${message.id}-${i}`}
|
|
305
|
-
className="text-foreground leading-relaxed"
|
|
285
|
+
className="text-foreground text-sm leading-relaxed"
|
|
306
286
|
>
|
|
307
287
|
{JSON.stringify(part)}
|
|
308
288
|
</Text>
|
|
309
289
|
)
|
|
310
290
|
)}
|
|
311
291
|
</View>
|
|
312
|
-
</
|
|
292
|
+
</Surface>
|
|
313
293
|
))}
|
|
314
294
|
</View>
|
|
315
295
|
)}
|
|
316
296
|
</ScrollView>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
onSubmit
|
|
328
|
-
|
|
329
|
-
autoFocus={true}
|
|
330
|
-
/>
|
|
331
|
-
<Pressable
|
|
332
|
-
onPress={onSubmit}
|
|
333
|
-
disabled={!input.trim()}
|
|
334
|
-
className={`p-2 rounded-lg active:opacity-70 ${
|
|
335
|
-
input.trim() ? "bg-accent" : "bg-surface"
|
|
336
|
-
}`}
|
|
337
|
-
>
|
|
338
|
-
<Ionicons
|
|
339
|
-
name="send"
|
|
340
|
-
size={20}
|
|
341
|
-
color={input.trim() ? foregroundColor : mutedColor}
|
|
297
|
+
|
|
298
|
+
<Divider className="mb-3" />
|
|
299
|
+
|
|
300
|
+
<View className="flex-row items-center gap-2">
|
|
301
|
+
<View className="flex-1">
|
|
302
|
+
<TextField>
|
|
303
|
+
<TextField.Input
|
|
304
|
+
value={input}
|
|
305
|
+
onChangeText={setInput}
|
|
306
|
+
placeholder="Type a message..."
|
|
307
|
+
onSubmitEditing={onSubmit}
|
|
308
|
+
autoFocus
|
|
342
309
|
/>
|
|
343
|
-
</
|
|
310
|
+
</TextField>
|
|
344
311
|
</View>
|
|
312
|
+
<Button
|
|
313
|
+
isIconOnly
|
|
314
|
+
variant={input.trim() ? "primary" : "secondary"}
|
|
315
|
+
onPress={onSubmit}
|
|
316
|
+
isDisabled={!input.trim()}
|
|
317
|
+
size="sm"
|
|
318
|
+
>
|
|
319
|
+
<Ionicons
|
|
320
|
+
name="arrow-up"
|
|
321
|
+
size={18}
|
|
322
|
+
color={input.trim() ? foregroundColor : mutedColor}
|
|
323
|
+
/>
|
|
324
|
+
</Button>
|
|
345
325
|
</View>
|
|
346
326
|
</View>
|
|
347
327
|
</KeyboardAvoidingView>
|
|
@@ -23,32 +23,32 @@ async function handleSubmit(e: Event) {
|
|
|
23
23
|
e.preventDefault()
|
|
24
24
|
const userInput = input.value
|
|
25
25
|
input.value = ''
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
if (!userInput.trim()) return
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
chat.sendMessage({ text: userInput })
|
|
30
30
|
}
|
|
31
31
|
</script>
|
|
32
32
|
|
|
33
33
|
<template>
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@reload="chat.regenerate"
|
|
51
|
-
|
|
52
|
-
</
|
|
53
|
-
</
|
|
34
|
+
<UContainer class="h-full flex flex-col overflow-hidden py-4">
|
|
35
|
+
<div class="flex-1 min-h-0 overflow-y-auto">
|
|
36
|
+
<UChatMessages :messages="chat.messages" :status="chat.status">
|
|
37
|
+
<template #content="{ message }">
|
|
38
|
+
<div class="whitespace-pre-wrap">\{{ getTextFromMessage(message) }}</div>
|
|
39
|
+
</template>
|
|
40
|
+
</UChatMessages>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="shrink-0 pt-4 border-t border-gray-200 dark:border-gray-800">
|
|
44
|
+
<UChatPrompt
|
|
45
|
+
v-model="input"
|
|
46
|
+
:error="chat.error"
|
|
47
|
+
@submit="handleSubmit"
|
|
48
|
+
placeholder="Type your message..."
|
|
49
|
+
>
|
|
50
|
+
<UChatPromptSubmit :status="chat.status" @stop="() => chat.stop()" @reload="() => chat.regenerate()" />
|
|
51
|
+
</UChatPrompt>
|
|
52
|
+
</div>
|
|
53
|
+
</UContainer>
|
|
54
54
|
</template>
|