diff --git a/.env.example b/.env.example
index a703b1098..6022734a9 100644
--- a/.env.example
+++ b/.env.example
@@ -2,17 +2,12 @@
## Then get your OpenAI API Key here: https://platform.openai.com/account/api-keys
OPENAI_API_KEY=XXXXXXXX
-## Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32`
-AUTH_SECRET=XXXXXXXX
-## Create a GitHub OAuth app here: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app
+# Update these with your Supabase details from your project settings > API
+# https://app.supabase.com/project/_/settings/api
+# In local dev you can get these by running `supabase status`.
+NEXT_PUBLIC_SUPABASE_URL=your-project-url
+NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
+
+## Follow GitHub Oauth setup steps from Supabase:
AUTH_GITHUB_ID=XXXXXXXX
AUTH_GITHUB_SECRET=XXXXXXXX
-## Support OAuth login on preview deployments, see: https://authjs.dev/guides/basics/deployment#securing-a-preview-deployment
-AUTH_REDIRECT_PROXY_URL=https://auth.example.com/api/auth
-
-# Instructions to create kv database here: https://vercel.com/docs/storage/vercel-kv/quickstart and
-KV_URL=XXXXXXXX
-KV_REST_API_URL=XXXXXXXX
-KV_REST_API_TOKEN=XXXXXXXX
-KV_REST_API_READ_ONLY_TOKEN=XXXXXXXX
-
diff --git a/README.md b/README.md
index 1105e31c6..acaa2bf18 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
- An open-source AI chatbot app template built with Next.js, the Vercel AI SDK, OpenAI, and Vercel KV.
+ An open-source AI chatbot app template built with Next.js, the Vercel AI SDK, OpenAI, and Supabase Auth and Postgres DB.
@@ -27,35 +27,40 @@
- Styling with [Tailwind CSS](https://tailwindcss.com)
- [Radix UI](https://radix-ui.com) for headless component primitives
- Icons from [Phosphor Icons](https://phosphoricons.com)
-- Chat History, rate limiting, and session storage with [Vercel KV](https://vercel.com/storage/kv)
-- [Next Auth](https://github.com/nextauthjs/next-auth) for authentication
+- Chat History with [Supabase Postgres DB](https://supabase.com)
+- [Supabase Auth](https://supabase.com/auth) for authentication
## Model Providers
This template ships with OpenAI `gpt-3.5-turbo` as the default. However, thanks to the [Vercel AI SDK](https://sdk.vercel.ai/docs), you can switch LLM providers to [Anthropic](https://anthropic.com), [Hugging Face](https://huggingface.co), or using [LangChain](https://js.langchain.com) with just a few lines of code.
-## Deploy Your Own
+
-## Creating a KV Database Instance
+## Running locally
-Follow the steps outlined in the [quick start guide](https://vercel.com/docs/storage/vercel-kv/quickstart#create-a-kv-database) provided by Vercel. This guide will assist you in creating and configuring your KV database instance on Vercel, enabling your application to interact with it.
+You will need to use the environment variables [defined in `.env.example`](.env.example) to run Next.js AI Chatbot. It's recommended you use [Vercel Environment Variables](https://vercel.com/docs/concepts/projects/environment-variables) for this, but a `.env` file is all that is necessary.
-Remember to update your environment variables (`KV_URL`, `KV_REST_API_URL`, `KV_REST_API_TOKEN`, `KV_REST_API_READ_ONLY_TOKEN`) in the `.env` file with the appropriate credentials provided during the KV database setup.
+> Note: You should not commit your `.env` file or it will expose secrets that will allow others to control access to your various OpenAI and authentication provider accounts.
+Copy the `.env.example` file and populate the required env vars:
-## Running locally
+```bash
+cp .env.example .env
+```
-You will need to use the environment variables [defined in `.env.example`](.env.example) to run Next.js AI Chatbot. It's recommended you use [Vercel Environment Variables](https://vercel.com/docs/concepts/projects/environment-variables) for this, but a `.env` file is all that is necessary.
+[Install the Supabase CLI](https://supabase.com/docs/guides/cli) and start the local Supabase stack:
-> Note: You should not commit your `.env` file or it will expose secrets that will allow others to control access to your various OpenAI and authentication provider accounts.
+```bash
+npm install supabase --save-dev
+npx supabase start
+```
-1. Install Vercel CLI: `npm i -g vercel`
-2. Link local instance with Vercel and GitHub accounts (creates `.vercel` directory): `vercel link`
-3. Download your environment variables: `vercel env pull`
+Install the local dependencies and start dev mode:
```bash
pnpm install
@@ -71,3 +76,4 @@ This library is created by [Vercel](https://vercel.com) and [Next.js](https://ne
- Jared Palmer ([@jaredpalmer](https://twitter.com/jaredpalmer)) - [Vercel](https://vercel.com)
- Shu Ding ([@shuding\_](https://twitter.com/shuding_)) - [Vercel](https://vercel.com)
- shadcn ([@shadcn](https://twitter.com/shadcn)) - [Contractor](https://shadcn.com)
+- Thor Schaeff ([@thorwebdev](https://twitter.com/thorwebdev)) - [Supabaseifier](https://thor.bio)
diff --git a/app/actions.ts b/app/actions.ts
index 2c8a5ddf9..db0aa5667 100644
--- a/app/actions.ts
+++ b/app/actions.ts
@@ -1,11 +1,15 @@
'use server'
+import { createServerActionClient } from '@supabase/auth-helpers-nextjs'
+import { cookies } from 'next/headers'
+import { Database } from '@/lib/db_types'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
-import { kv } from '@vercel/kv'
-import { auth } from '@/auth'
import { type Chat } from '@/lib/types'
+import { auth } from '@/auth'
+
+const supabase = createServerActionClient({ cookies })
export async function getChats(userId?: string | null) {
if (!userId) {
@@ -13,108 +17,81 @@ export async function getChats(userId?: string | null) {
}
try {
- const pipeline = kv.pipeline()
- const chats: string[] = await kv.zrange(`user:chat:${userId}`, 0, -1, {
- rev: true
- })
-
- for (const chat of chats) {
- pipeline.hgetall(chat)
- }
+ const { data } = await supabase
+ .from('chats')
+ .select('payload')
+ .order('payload->createdAt', { ascending: false })
+ .throwOnError()
- const results = await pipeline.exec()
-
- return results as Chat[]
+ return (data?.map(entry => entry.payload) as Chat[]) ?? []
} catch (error) {
return []
}
}
-export async function getChat(id: string, userId: string) {
- const chat = await kv.hgetall(`chat:${id}`)
-
- if (!chat || (userId && chat.userId !== userId)) {
- return null
- }
+export async function getChat(id: string) {
+ const { data } = await supabase
+ .from('chats')
+ .select('payload')
+ .eq('id', id)
+ .maybeSingle()
- return chat
+ return (data?.payload as Chat) ?? null
}
export async function removeChat({ id, path }: { id: string; path: string }) {
- const session = await auth()
-
- if (!session) {
- return {
- error: 'Unauthorized'
- }
- }
-
- const uid = await kv.hget(`chat:${id}`, 'userId')
+ try {
+ await supabase.from('chats').delete().eq('id', id).throwOnError()
- if (uid !== session?.user?.id) {
+ revalidatePath('/')
+ return revalidatePath(path)
+ } catch (error) {
return {
error: 'Unauthorized'
}
}
-
- await kv.del(`chat:${id}`)
- await kv.zrem(`user:chat:${session.user.id}`, `chat:${id}`)
-
- revalidatePath('/')
- return revalidatePath(path)
}
export async function clearChats() {
- const session = await auth()
-
- if (!session?.user?.id) {
+ try {
+ const session = await auth()
+ await supabase
+ .from('chats')
+ .delete()
+ .eq('user_id', session?.user.id)
+ .throwOnError()
+ revalidatePath('/')
+ return redirect('/')
+ } catch (error) {
+ console.log('clear chats error', error)
return {
error: 'Unauthorized'
}
}
-
- const chats: string[] = await kv.zrange(`user:chat:${session.user.id}`, 0, -1)
- if (!chats.length) {
- return redirect('/')
- }
- const pipeline = kv.pipeline()
-
- for (const chat of chats) {
- pipeline.del(chat)
- pipeline.zrem(`user:chat:${session.user.id}`, chat)
- }
-
- await pipeline.exec()
-
- revalidatePath('/')
- return redirect('/')
}
export async function getSharedChat(id: string) {
- const chat = await kv.hgetall(`chat:${id}`)
-
- if (!chat || !chat.sharePath) {
- return null
- }
-
- return chat
+ const { data } = await supabase
+ .from('chats')
+ .select('payload')
+ .eq('id', id)
+ .not('payload->sharePath', 'is', null)
+ .maybeSingle()
+
+ return (data?.payload as Chat) ?? null
}
export async function shareChat(chat: Chat) {
- const session = await auth()
-
- if (!session?.user?.id || session.user.id !== chat.userId) {
- return {
- error: 'Unauthorized'
- }
- }
-
const payload = {
...chat,
sharePath: `/share/${chat.id}`
}
- await kv.hmset(`chat:${chat.id}`, payload)
+ await supabase
+ .from('chats')
+ .update({ payload: payload as any })
+ .eq('id', chat.id)
+ .throwOnError()
return payload
}
diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts
deleted file mode 100644
index 883210bb2..000000000
--- a/app/api/auth/[...nextauth]/route.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { GET, POST } from '@/auth'
-export const runtime = 'edge'
diff --git a/app/api/auth/callback/route.ts b/app/api/auth/callback/route.ts
new file mode 100644
index 000000000..dbb00609f
--- /dev/null
+++ b/app/api/auth/callback/route.ts
@@ -0,0 +1,19 @@
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
+import { cookies } from 'next/headers'
+import { NextResponse } from 'next/server'
+
+export async function GET(request: Request) {
+ // The `/auth/callback` route is required for the server-side auth flow implemented
+ // by the Auth Helpers package. It exchanges an auth code for the user's session.
+ // https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-sign-in-with-code-exchange
+ const requestUrl = new URL(request.url)
+ const code = requestUrl.searchParams.get('code')
+
+ if (code) {
+ const supabase = createRouteHandlerClient({ cookies })
+ await supabase.auth.exchangeCodeForSession(code)
+ }
+
+ // URL to redirect to after sign in process completes
+ return NextResponse.redirect(requestUrl.origin)
+}
diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts
index 02e7571db..ae6c653a4 100644
--- a/app/api/chat/route.ts
+++ b/app/api/chat/route.ts
@@ -1,6 +1,8 @@
-import { kv } from '@vercel/kv'
import { OpenAIStream, StreamingTextResponse } from 'ai'
import { Configuration, OpenAIApi } from 'openai-edge'
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
+import { cookies } from 'next/headers'
+import { Database } from '@/lib/db_types'
import { auth } from '@/auth'
import { nanoid } from '@/lib/utils'
@@ -14,6 +16,7 @@ const configuration = new Configuration({
const openai = new OpenAIApi(configuration)
export async function POST(req: Request) {
+ const supabase = createRouteHandlerClient({ cookies })
const json = await req.json()
const { messages, previewToken } = json
const userId = (await auth())?.user.id
@@ -55,11 +58,8 @@ export async function POST(req: Request) {
}
]
}
- await kv.hmset(`chat:${id}`, payload)
- await kv.zadd(`user:chat:${userId}`, {
- score: createdAt,
- member: `chat:${id}`
- })
+ // Insert chat into database.
+ await supabase.from('chats').upsert({ id, payload }).throwOnError()
}
})
diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx
index 25d6c3063..01d2d3d4d 100644
--- a/app/chat/[id]/page.tsx
+++ b/app/chat/[id]/page.tsx
@@ -23,7 +23,7 @@ export async function generateMetadata({
return {}
}
- const chat = await getChat(params.id, session.user.id)
+ const chat = await getChat(params.id)
return {
title: chat?.title.toString().slice(0, 50) ?? 'Chat'
}
@@ -36,7 +36,7 @@ export default async function ChatPage({ params }: ChatPageProps) {
redirect(`/sign-in?next=/chat/${params.id}`)
}
- const chat = await getChat(params.id, session.user.id)
+ const chat = await getChat(params.id)
if (!chat) {
notFound()
diff --git a/app/share/[id]/opengraph-image.tsx b/app/share/[id]/opengraph-image.tsx
index aefc610ed..bef39d93f 100644
--- a/app/share/[id]/opengraph-image.tsx
+++ b/app/share/[id]/opengraph-image.tsx
@@ -90,7 +90,7 @@ export default async function Image({ params }: ImageProps) {
- Welcome to Next.js AI Chatbot!
+ Welcome to the Supabaseified Next.js AI Chatbot!
This is an open source AI chatbot app template built with{' '}
Next.js and{' '}
-
- Vercel KV
-
- .
+ Supabase.
You can start a conversation here or try the following examples:
diff --git a/components/footer.tsx b/components/footer.tsx
index 0a8fce3c2..8d96068a0 100644
--- a/components/footer.tsx
+++ b/components/footer.tsx
@@ -14,10 +14,7 @@ export function FooterText({ className, ...props }: React.ComponentProps<'p'>) {
>
Open source AI chatbot built with{' '}
Next.js and{' '}
-
- Vercel KV
-
- .
+ Supabase.
)
}
diff --git a/components/header.tsx b/components/header.tsx
index 3ff0dd6fc..050ef72c9 100644
--- a/components/header.tsx
+++ b/components/header.tsx
@@ -17,7 +17,6 @@ import { SidebarFooter } from '@/components/sidebar-footer'
import { ThemeToggle } from '@/components/theme-toggle'
import { ClearHistory } from '@/components/clear-history'
import { UserMenu } from '@/components/user-menu'
-import { LoginButton } from '@/components/login-button'
export async function Header() {
const session = await auth()
@@ -47,7 +46,7 @@ export async function Header() {
) : (
)}
@@ -55,7 +54,7 @@ export async function Header() {