create-better-t-stack 3.12.5 → 3.12.6
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-Ci2sRCrb.mjs} +31 -20
- package/package.json +2 -2
- 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/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
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import z from 'zod'
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import * as z from 'zod'
|
|
3
|
+
import type { FormSubmitEvent, AuthFormField } from '@nuxt/ui'
|
|
4
|
+
|
|
5
|
+
const { $authClient } = useNuxtApp()
|
|
5
6
|
|
|
6
7
|
const emit = defineEmits(['switchToSignUp'])
|
|
7
8
|
|
|
8
9
|
const toast = useToast()
|
|
9
10
|
const loading = ref(false)
|
|
10
11
|
|
|
12
|
+
const fields: AuthFormField[] = [
|
|
13
|
+
{
|
|
14
|
+
name: 'email',
|
|
15
|
+
type: 'email',
|
|
16
|
+
label: 'Email',
|
|
17
|
+
placeholder: 'Enter your email',
|
|
18
|
+
required: true
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'password',
|
|
22
|
+
type: 'password',
|
|
23
|
+
label: 'Password',
|
|
24
|
+
placeholder: 'Enter your password',
|
|
25
|
+
required: true
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
|
|
11
29
|
const schema = z.object({
|
|
12
30
|
email: z.email('Invalid email address'),
|
|
13
31
|
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
@@ -15,12 +33,7 @@ const schema = z.object({
|
|
|
15
33
|
|
|
16
34
|
type Schema = z.output<typeof schema>
|
|
17
35
|
|
|
18
|
-
|
|
19
|
-
email: '',
|
|
20
|
-
password: '',
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
|
36
|
+
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|
24
37
|
loading.value = true
|
|
25
38
|
try {
|
|
26
39
|
await $authClient.signIn.email(
|
|
@@ -39,7 +52,7 @@ async function onSubmit (event: FormSubmitEvent<Schema>) {
|
|
|
39
52
|
},
|
|
40
53
|
)
|
|
41
54
|
} catch (error: any) {
|
|
42
|
-
|
|
55
|
+
toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })
|
|
43
56
|
} finally {
|
|
44
57
|
loading.value = false
|
|
45
58
|
}
|
|
@@ -47,31 +60,23 @@ async function onSubmit (event: FormSubmitEvent<Schema>) {
|
|
|
47
60
|
</script>
|
|
48
61
|
|
|
49
62
|
<template>
|
|
50
|
-
<div class="
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<UInput v-model="state.password" type="password" class="w-full" />
|
|
60
|
-
</UFormField>
|
|
61
|
-
|
|
62
|
-
<UButton type="submit" block :loading="loading">
|
|
63
|
-
Sign In
|
|
64
|
-
</UButton>
|
|
65
|
-
</UForm>
|
|
66
|
-
|
|
67
|
-
<div class="mt-4 text-center">
|
|
68
|
-
<UButton
|
|
69
|
-
variant="link"
|
|
70
|
-
@click="$emit('switchToSignUp')"
|
|
71
|
-
class="text-primary hover:text-primary-dark"
|
|
63
|
+
<div class="flex flex-col items-center justify-center gap-4 p-4">
|
|
64
|
+
<UPageCard class="w-full max-w-md">
|
|
65
|
+
<UAuthForm
|
|
66
|
+
:schema="schema"
|
|
67
|
+
:fields="fields"
|
|
68
|
+
title="Welcome Back"
|
|
69
|
+
icon="i-lucide-log-in"
|
|
70
|
+
:submit="{ label: 'Sign In', loading }"
|
|
71
|
+
@submit="onSubmit"
|
|
72
72
|
>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
<template #description>
|
|
74
|
+
Need an account?
|
|
75
|
+
<ULink class="text-primary font-medium" @click="$emit('switchToSignUp')">
|
|
76
|
+
Sign Up
|
|
77
|
+
</ULink>
|
|
78
|
+
</template>
|
|
79
|
+
</UAuthForm>
|
|
80
|
+
</UPageCard>
|
|
76
81
|
</div>
|
|
77
82
|
</template>
|
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import z from 'zod'
|
|
3
|
-
import type { FormSubmitEvent } from '
|
|
4
|
-
|
|
2
|
+
import * as z from 'zod'
|
|
3
|
+
import type { FormSubmitEvent, AuthFormField } from '@nuxt/ui'
|
|
4
|
+
|
|
5
|
+
const { $authClient } = useNuxtApp()
|
|
5
6
|
|
|
6
7
|
const emit = defineEmits(['switchToSignIn'])
|
|
7
8
|
|
|
8
9
|
const toast = useToast()
|
|
9
10
|
const loading = ref(false)
|
|
10
11
|
|
|
12
|
+
const fields: AuthFormField[] = [
|
|
13
|
+
{
|
|
14
|
+
name: 'name',
|
|
15
|
+
type: 'text',
|
|
16
|
+
label: 'Name',
|
|
17
|
+
placeholder: 'Enter your name',
|
|
18
|
+
required: true
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'email',
|
|
22
|
+
type: 'email',
|
|
23
|
+
label: 'Email',
|
|
24
|
+
placeholder: 'Enter your email',
|
|
25
|
+
required: true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'password',
|
|
29
|
+
type: 'password',
|
|
30
|
+
label: 'Password',
|
|
31
|
+
placeholder: 'Enter your password',
|
|
32
|
+
required: true
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
|
|
11
36
|
const schema = z.object({
|
|
12
37
|
name: z.string().min(2, 'Name must be at least 2 characters'),
|
|
13
38
|
email: z.email('Invalid email address'),
|
|
@@ -16,13 +41,7 @@ const schema = z.object({
|
|
|
16
41
|
|
|
17
42
|
type Schema = z.output<typeof schema>
|
|
18
43
|
|
|
19
|
-
|
|
20
|
-
name: '',
|
|
21
|
-
email: '',
|
|
22
|
-
password: '',
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
|
44
|
+
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|
26
45
|
loading.value = true
|
|
27
46
|
try {
|
|
28
47
|
await $authClient.signUp.email(
|
|
@@ -42,7 +61,7 @@ async function onSubmit (event: FormSubmitEvent<Schema>) {
|
|
|
42
61
|
},
|
|
43
62
|
)
|
|
44
63
|
} catch (error: any) {
|
|
45
|
-
|
|
64
|
+
toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })
|
|
46
65
|
} finally {
|
|
47
66
|
loading.value = false
|
|
48
67
|
}
|
|
@@ -50,35 +69,23 @@ async function onSubmit (event: FormSubmitEvent<Schema>) {
|
|
|
50
69
|
</script>
|
|
51
70
|
|
|
52
71
|
<template>
|
|
53
|
-
<div class="
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<UInput v-model="state.email" type="email" class="w-full" />
|
|
63
|
-
</UFormField>
|
|
64
|
-
|
|
65
|
-
<UFormField label="Password" name="password">
|
|
66
|
-
<UInput v-model="state.password" type="password" class="w-full" />
|
|
67
|
-
</UFormField>
|
|
68
|
-
|
|
69
|
-
<UButton type="submit" block :loading="loading">
|
|
70
|
-
Sign Up
|
|
71
|
-
</UButton>
|
|
72
|
-
</UForm>
|
|
73
|
-
|
|
74
|
-
<div class="mt-4 text-center">
|
|
75
|
-
<UButton
|
|
76
|
-
variant="link"
|
|
77
|
-
@click="$emit('switchToSignIn')"
|
|
78
|
-
class="text-primary hover:text-primary-dark"
|
|
72
|
+
<div class="flex flex-col items-center justify-center gap-4 p-4">
|
|
73
|
+
<UPageCard class="w-full max-w-md">
|
|
74
|
+
<UAuthForm
|
|
75
|
+
:schema="schema"
|
|
76
|
+
:fields="fields"
|
|
77
|
+
title="Create Account"
|
|
78
|
+
icon="i-lucide-user-plus"
|
|
79
|
+
:submit="{ label: 'Sign Up', loading }"
|
|
80
|
+
@submit="onSubmit"
|
|
79
81
|
>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
<template #description>
|
|
83
|
+
Already have an account?
|
|
84
|
+
<ULink class="text-primary font-medium" @click="$emit('switchToSignIn')">
|
|
85
|
+
Sign In
|
|
86
|
+
</ULink>
|
|
87
|
+
</template>
|
|
88
|
+
</UAuthForm>
|
|
89
|
+
</UPageCard>
|
|
83
90
|
</div>
|
|
84
91
|
</template>
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export default defineNuxtRouteMiddleware(async (to, from) => {
|
|
2
|
-
if (import.meta.server) return
|
|
2
|
+
if (import.meta.server) return;
|
|
3
3
|
|
|
4
|
-
const { $authClient } = useNuxtApp()
|
|
5
|
-
const session = $authClient.useSession()
|
|
4
|
+
const { $authClient } = useNuxtApp();
|
|
5
|
+
const session = $authClient.useSession();
|
|
6
6
|
|
|
7
|
-
if (session.value.isPending
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
if (session.value.isPending) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (!session.value.data) {
|
|
12
|
+
return navigateTo("/login");
|
|
11
13
|
}
|
|
12
14
|
});
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
{{#if (eq api "orpc")}}
|
|
3
3
|
import { useQuery } from '@tanstack/vue-query'
|
|
4
4
|
{{/if}}
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
const { $authClient, $orpc } = useNuxtApp()
|
|
6
7
|
|
|
7
8
|
definePageMeta({
|
|
8
9
|
middleware: ['auth']
|
|
9
10
|
})
|
|
10
11
|
|
|
11
|
-
const { $orpc } = useNuxtApp()
|
|
12
|
-
|
|
13
12
|
const session = $authClient.useSession()
|
|
13
|
+
|
|
14
14
|
{{#if (eq payments "polar")}}
|
|
15
15
|
const customerState = ref<any>(null)
|
|
16
16
|
{{/if}}
|
|
@@ -34,34 +34,66 @@ const hasProSubscription = computed(() =>
|
|
|
34
34
|
customerState.value?.activeSubscriptions?.length! > 0
|
|
35
35
|
)
|
|
36
36
|
{{/if}}
|
|
37
|
-
|
|
38
37
|
</script>
|
|
39
38
|
|
|
40
39
|
<template>
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
<UContainer class="py-8">
|
|
41
|
+
<UPageHeader
|
|
42
|
+
title="Dashboard"
|
|
43
|
+
:description="session?.data?.user ? `Welcome back, ${session.data.user.name}!` : 'Loading...'"
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<div class="mt-6 space-y-4">
|
|
47
|
+
{{#if (eq api "orpc")}}
|
|
48
|
+
<UCard>
|
|
49
|
+
<template #header>
|
|
50
|
+
<div class="font-medium">Private Data</div>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<USkeleton v-if="privateData.status.value === 'pending'" class="h-6 w-48" />
|
|
54
|
+
|
|
55
|
+
<UAlert
|
|
56
|
+
v-else-if="privateData.status.value === 'error'"
|
|
57
|
+
color="error"
|
|
58
|
+
icon="i-lucide-alert-circle"
|
|
59
|
+
title="Error loading data"
|
|
60
|
+
:description="privateData.error.value?.message || 'Failed to load private data'"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<div v-else-if="privateData.data.value" class="flex items-center gap-2">
|
|
64
|
+
<UIcon name="i-lucide-check-circle" class="text-success" />
|
|
65
|
+
<span>\{{ privateData.data.value.message }}</span>
|
|
66
|
+
</div>
|
|
67
|
+
</UCard>
|
|
68
|
+
{{/if}}
|
|
69
|
+
|
|
70
|
+
{{#if (eq payments "polar")}}
|
|
71
|
+
<UCard>
|
|
72
|
+
<template #header>
|
|
73
|
+
<div class="font-medium">Subscription</div>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<div class="flex items-center justify-between">
|
|
77
|
+
<div class="flex items-center gap-2">
|
|
78
|
+
<UIcon :name="hasProSubscription ? 'i-lucide-crown' : 'i-lucide-user'" :class="hasProSubscription ? 'text-warning' : 'text-muted'" />
|
|
79
|
+
<span>Plan: \{{ hasProSubscription ? "Pro" : "Free" }}</span>
|
|
80
|
+
</div>
|
|
81
|
+
<UButton
|
|
82
|
+
v-if="hasProSubscription"
|
|
83
|
+
variant="outline"
|
|
84
|
+
@click="() => { $authClient.customer.portal() }"
|
|
85
|
+
>
|
|
86
|
+
Manage Subscription
|
|
87
|
+
</UButton>
|
|
88
|
+
<UButton
|
|
89
|
+
v-else
|
|
90
|
+
@click="() => { $authClient.checkout({ slug: 'pro' }) }"
|
|
91
|
+
>
|
|
92
|
+
Upgrade to Pro
|
|
93
|
+
</UButton>
|
|
94
|
+
</div>
|
|
95
|
+
</UCard>
|
|
96
|
+
{{/if}}
|
|
45
97
|
</div>
|
|
46
|
-
|
|
47
|
-
<div v-if="privateData.status.value === 'pending'">Loading private data...</div>
|
|
48
|
-
<div v-else-if="privateData.status.value === 'error'">Error loading private data: \{{ privateData.error.value?.message }}</div>
|
|
49
|
-
<p v-else-if="privateData.data.value">API: \{{ privateData.data.value.message }}</p>
|
|
50
|
-
{{/if}}
|
|
51
|
-
{{#if (eq payments "polar")}}
|
|
52
|
-
<p class="mb-2">Plan: \{{ hasProSubscription ? "Pro" : "Free" }}</p>
|
|
53
|
-
<UButton
|
|
54
|
-
v-if="hasProSubscription"
|
|
55
|
-
@click="() => { $authClient.customer.portal() }"
|
|
56
|
-
>
|
|
57
|
-
Manage Subscription
|
|
58
|
-
</UButton>
|
|
59
|
-
<UButton
|
|
60
|
-
v-else
|
|
61
|
-
@click="() => { $authClient.checkout({ slug: 'pro' }) }"
|
|
62
|
-
>
|
|
63
|
-
Upgrade to Pro
|
|
64
|
-
</UButton>
|
|
65
|
-
{{/if}}
|
|
66
|
-
</div>
|
|
98
|
+
</UContainer>
|
|
67
99
|
</template>
|
|
@@ -14,11 +14,14 @@ watchEffect(() => {
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
-
<
|
|
18
|
-
<
|
|
17
|
+
<UContainer class="py-8">
|
|
18
|
+
<div v-if="session.isPending" class="flex flex-col items-center justify-center gap-4 py-12">
|
|
19
|
+
<UIcon name="i-lucide-loader-2" class="animate-spin text-4xl text-primary" />
|
|
20
|
+
<span class="text-muted">Loading...</span>
|
|
21
|
+
</div>
|
|
19
22
|
<div v-else-if="!session.data">
|
|
20
23
|
<SignInForm v-if="showSignIn" @switch-to-sign-up="showSignIn = false" />
|
|
21
24
|
<SignUpForm v-else @switch-to-sign-in="showSignIn = true" />
|
|
22
25
|
</div>
|
|
23
|
-
</
|
|
26
|
+
</UContainer>
|
|
24
27
|
</template>
|
|
@@ -6,7 +6,8 @@ import { Elysia } from "elysia";
|
|
|
6
6
|
import { cors } from "@elysiajs/cors";
|
|
7
7
|
{{#if (includes examples "ai")}}
|
|
8
8
|
import { google } from "@ai-sdk/google";
|
|
9
|
-
import { convertToModelMessages, streamText } from "ai";
|
|
9
|
+
import { convertToModelMessages, streamText, wrapLanguageModel } from "ai";
|
|
10
|
+
import { devToolsMiddleware } from "@ai-sdk/devtools";
|
|
10
11
|
{{/if}}
|
|
11
12
|
{{#if (eq api "trpc")}}
|
|
12
13
|
import { createContext } from "@{{projectName}}/api/context";
|
|
@@ -103,9 +104,13 @@ const app = new Elysia()
|
|
|
103
104
|
.post("/ai", async (context) => {
|
|
104
105
|
const body = await context.request.json();
|
|
105
106
|
const uiMessages = body.messages || [];
|
|
106
|
-
const
|
|
107
|
+
const model = wrapLanguageModel({
|
|
107
108
|
model: google("gemini-2.5-flash"),
|
|
108
|
-
|
|
109
|
+
middleware: devToolsMiddleware(),
|
|
110
|
+
});
|
|
111
|
+
const result = streamText({
|
|
112
|
+
model,
|
|
113
|
+
messages: await convertToModelMessages(uiMessages)
|
|
109
114
|
});
|
|
110
115
|
|
|
111
116
|
return result.toUIMessageStreamResponse();
|
|
@@ -18,8 +18,9 @@ import { createContext } from "@{{projectName}}/api/context";
|
|
|
18
18
|
import cors from "cors";
|
|
19
19
|
import express from "express";
|
|
20
20
|
{{#if (includes examples "ai")}}
|
|
21
|
-
import { streamText, type UIMessage, convertToModelMessages } from "ai";
|
|
21
|
+
import { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from "ai";
|
|
22
22
|
import { google } from "@ai-sdk/google";
|
|
23
|
+
import { devToolsMiddleware } from "@ai-sdk/devtools";
|
|
23
24
|
{{/if}}
|
|
24
25
|
{{#if (eq auth "better-auth")}}
|
|
25
26
|
import { auth } from "@{{projectName}}/auth";
|
|
@@ -104,9 +105,13 @@ app.use(express.json());
|
|
|
104
105
|
{{#if (includes examples "ai")}}
|
|
105
106
|
app.post("/ai", async (req, res) => {
|
|
106
107
|
const { messages = [] } = (req.body || {}) as { messages: UIMessage[] };
|
|
107
|
-
const
|
|
108
|
+
const model = wrapLanguageModel({
|
|
108
109
|
model: google("gemini-2.5-flash"),
|
|
109
|
-
|
|
110
|
+
middleware: devToolsMiddleware(),
|
|
111
|
+
});
|
|
112
|
+
const result = streamText({
|
|
113
|
+
model,
|
|
114
|
+
messages: await convertToModelMessages(messages),
|
|
110
115
|
});
|
|
111
116
|
result.pipeUIMessageStreamToResponse(res);
|
|
112
117
|
});
|
|
@@ -23,8 +23,9 @@ import { createContext } from "@{{projectName}}/api/context";
|
|
|
23
23
|
{{/if}}
|
|
24
24
|
|
|
25
25
|
{{#if (includes examples "ai")}}
|
|
26
|
-
import { streamText, type UIMessage, convertToModelMessages } from "ai";
|
|
26
|
+
import { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from "ai";
|
|
27
27
|
import { google } from "@ai-sdk/google";
|
|
28
|
+
import { devToolsMiddleware } from "@ai-sdk/devtools";
|
|
28
29
|
{{/if}}
|
|
29
30
|
|
|
30
31
|
{{#if (eq auth "better-auth")}}
|
|
@@ -160,9 +161,13 @@ interface AiRequestBody {
|
|
|
160
161
|
|
|
161
162
|
fastify.post('/ai', async function (request) {
|
|
162
163
|
const { messages } = request.body as AiRequestBody;
|
|
163
|
-
const
|
|
164
|
+
const model = wrapLanguageModel({
|
|
164
165
|
model: google('gemini-2.5-flash'),
|
|
165
|
-
|
|
166
|
+
middleware: devToolsMiddleware(),
|
|
167
|
+
});
|
|
168
|
+
const result = streamText({
|
|
169
|
+
model,
|
|
170
|
+
messages: await convertToModelMessages(messages),
|
|
166
171
|
});
|
|
167
172
|
|
|
168
173
|
return result.toUIMessageStreamResponse();
|
|
@@ -20,12 +20,14 @@ import { Hono } from "hono";
|
|
|
20
20
|
import { cors } from "hono/cors";
|
|
21
21
|
import { logger } from "hono/logger";
|
|
22
22
|
{{#if (and (includes examples "ai") (or (eq runtime "bun") (eq runtime "node")))}}
|
|
23
|
-
import { streamText, convertToModelMessages } from "ai";
|
|
23
|
+
import { streamText, convertToModelMessages, wrapLanguageModel } from "ai";
|
|
24
24
|
import { google } from "@ai-sdk/google";
|
|
25
|
+
import { devToolsMiddleware } from "@ai-sdk/devtools";
|
|
25
26
|
{{/if}}
|
|
26
27
|
{{#if (and (includes examples "ai") (eq runtime "workers"))}}
|
|
27
|
-
import { streamText, convertToModelMessages } from "ai";
|
|
28
|
+
import { streamText, convertToModelMessages, wrapLanguageModel } from "ai";
|
|
28
29
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
30
|
+
import { devToolsMiddleware } from "@ai-sdk/devtools";
|
|
29
31
|
{{/if}}
|
|
30
32
|
|
|
31
33
|
const app = new Hono();
|
|
@@ -110,9 +112,13 @@ app.use(
|
|
|
110
112
|
app.post("/ai", async (c) => {
|
|
111
113
|
const body = await c.req.json();
|
|
112
114
|
const uiMessages = body.messages || [];
|
|
113
|
-
const
|
|
115
|
+
const model = wrapLanguageModel({
|
|
114
116
|
model: google("gemini-2.5-flash"),
|
|
115
|
-
|
|
117
|
+
middleware: devToolsMiddleware(),
|
|
118
|
+
});
|
|
119
|
+
const result = streamText({
|
|
120
|
+
model,
|
|
121
|
+
messages: await convertToModelMessages(uiMessages),
|
|
116
122
|
});
|
|
117
123
|
|
|
118
124
|
return result.toUIMessageStreamResponse();
|
|
@@ -126,9 +132,13 @@ app.post("/ai", async (c) => {
|
|
|
126
132
|
const google = createGoogleGenerativeAI({
|
|
127
133
|
apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY,
|
|
128
134
|
});
|
|
129
|
-
const
|
|
135
|
+
const model = wrapLanguageModel({
|
|
130
136
|
model: google("gemini-2.5-flash"),
|
|
131
|
-
|
|
137
|
+
middleware: devToolsMiddleware(),
|
|
138
|
+
});
|
|
139
|
+
const result = streamText({
|
|
140
|
+
model,
|
|
141
|
+
messages: await convertToModelMessages(uiMessages),
|
|
132
142
|
});
|
|
133
143
|
|
|
134
144
|
return result.toUIMessageStreamResponse();
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { google } from "@ai-sdk/google";
|
|
2
|
-
import { streamText, type UIMessage, convertToModelMessages } from "ai";
|
|
2
|
+
import { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from "ai";
|
|
3
|
+
import { devToolsMiddleware } from "@ai-sdk/devtools";
|
|
3
4
|
|
|
4
5
|
export const maxDuration = 30;
|
|
5
6
|
|
|
6
7
|
export async function POST(req: Request) {
|
|
7
8
|
const { messages }: { messages: UIMessage[] } = await req.json();
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
+
const model = wrapLanguageModel({
|
|
10
11
|
model: google("gemini-2.5-flash"),
|
|
11
|
-
|
|
12
|
+
middleware: devToolsMiddleware(),
|
|
13
|
+
});
|
|
14
|
+
const result = streamText({
|
|
15
|
+
model,
|
|
16
|
+
messages: await convertToModelMessages(messages),
|
|
12
17
|
});
|
|
13
18
|
|
|
14
19
|
return result.toUIMessageStreamResponse();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
2
|
import { google } from "@ai-sdk/google";
|
|
3
|
-
import { streamText, type UIMessage, convertToModelMessages } from "ai";
|
|
3
|
+
import { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from "ai";
|
|
4
|
+
import { devToolsMiddleware } from "@ai-sdk/devtools";
|
|
4
5
|
|
|
5
6
|
export const Route = createFileRoute("/api/ai/$")({
|
|
6
7
|
server: {
|
|
@@ -9,9 +10,13 @@ export const Route = createFileRoute("/api/ai/$")({
|
|
|
9
10
|
try {
|
|
10
11
|
const { messages }: { messages: UIMessage[] } = await request.json();
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const model = wrapLanguageModel({
|
|
13
14
|
model: google("gemini-2.5-flash"),
|
|
14
|
-
|
|
15
|
+
middleware: devToolsMiddleware(),
|
|
16
|
+
});
|
|
17
|
+
const result = streamText({
|
|
18
|
+
model,
|
|
19
|
+
messages: await convertToModelMessages(messages),
|
|
15
20
|
});
|
|
16
21
|
|
|
17
22
|
return result.toUIMessageStreamResponse();
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import structuredClone from '@ungap/structured-clone';
|
|
3
3
|
|
|
4
|
-
if (Platform.OS !==
|
|
4
|
+
if (Platform.OS !== 'web') {
|
|
5
5
|
const setupPolyfills = async () => {
|
|
6
|
-
const { polyfillGlobal } = await import(
|
|
6
|
+
const { polyfillGlobal } = await import(
|
|
7
|
+
'react-native/Libraries/Utilities/PolyfillFunctions'
|
|
8
|
+
);
|
|
7
9
|
|
|
8
|
-
const { TextEncoderStream, TextDecoderStream } =
|
|
9
|
-
|
|
10
|
+
const { TextEncoderStream, TextDecoderStream } = await import(
|
|
11
|
+
'@stardazed/streams-text-encoding'
|
|
12
|
+
);
|
|
10
13
|
|
|
11
|
-
if (!(
|
|
12
|
-
polyfillGlobal(
|
|
14
|
+
if (!('structuredClone' in global)) {
|
|
15
|
+
polyfillGlobal('structuredClone', () => structuredClone);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
polyfillGlobal(
|
|
16
|
-
polyfillGlobal(
|
|
18
|
+
polyfillGlobal('TextEncoderStream', () => TextEncoderStream);
|
|
19
|
+
polyfillGlobal('TextDecoderStream', () => TextDecoderStream);
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
setupPolyfills();
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
export {};
|
|
25
|
+
export {};
|