Skip to content

Commit 334e0b2

Browse files
authored
Initial commit
0 parents  commit 334e0b2

140 files changed

Lines changed: 22903 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Get your OpenAI API Key here for chat models: https://platform.openai.com/account/api-keys
2+
OPENAI_API_KEY=****
3+
4+
# Get your Fireworks AI API Key here for reasoning models: https://fireworks.ai/account/api-keys
5+
FIREWORKS_API_KEY=****
6+
7+
# Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32`
8+
AUTH_SECRET=****
9+
10+
# The following keys below are automatically created and
11+
# added to your environment when you deploy on vercel
12+
13+
# Instructions to create a Vercel Blob Store here: https://vercel.com/docs/storage/vercel-blob
14+
BLOB_READ_WRITE_TOKEN=****
15+
16+
# Instructions to create a database here: https://vercel.com/docs/storage/vercel-postgres/quickstart
17+
POSTGRES_URL=****

.eslintrc.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"extends": [
3+
"next/core-web-vitals",
4+
"plugin:import/recommended",
5+
"plugin:import/typescript",
6+
"prettier",
7+
"plugin:tailwindcss/recommended"
8+
],
9+
"plugins": ["tailwindcss"],
10+
"rules": {
11+
"tailwindcss/no-custom-classname": "off",
12+
"tailwindcss/classnames-order": "off"
13+
},
14+
"settings": {
15+
"import/resolver": {
16+
"typescript": {
17+
"alwaysTryTypes": true
18+
}
19+
}
20+
},
21+
"ignorePatterns": ["**/components/ui/**"]
22+
}

.github/workflows/lint.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Lint
2+
on:
3+
push:
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-22.04
8+
strategy:
9+
matrix:
10+
node-version: [20]
11+
steps:
12+
- uses: actions/checkout@v4
13+
- name: Install pnpm
14+
uses: pnpm/action-setup@v4
15+
with:
16+
version: 9
17+
- name: Use Node.js ${{ matrix.node-version }}
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: ${{ matrix.node-version }}
21+
cache: 'pnpm'
22+
- name: Install dependencies
23+
run: pnpm install
24+
- name: Run lint
25+
run: pnpm lint

.gitignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
node_modules
5+
.pnp
6+
.pnp.js
7+
8+
# testing
9+
coverage
10+
11+
# next.js
12+
.next/
13+
out/
14+
build
15+
16+
# misc
17+
.DS_Store
18+
*.pem
19+
20+
# debug
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*
24+
.pnpm-debug.log*
25+
26+
# local env files
27+
.env.local
28+
.env.development.local
29+
.env.test.local
30+
.env.production.local
31+
32+
# turbo
33+
.turbo
34+
35+
.env
36+
.vercel
37+
.vscode
38+
.env*.local

LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2024 Vercel, Inc.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<a href="https://chat.vercel.ai/">
2+
<img alt="Next.js 14 and App Router-ready AI chatbot." src="app/(chat)/opengraph-image.png">
3+
<h1 align="center">Next.js AI Chatbot</h1>
4+
</a>
5+
6+
<p align="center">
7+
An Open-Source AI Chatbot Template Built With Next.js and the AI SDK by Vercel.
8+
</p>
9+
10+
<p align="center">
11+
<a href="#features"><strong>Features</strong></a> ·
12+
<a href="#model-providers"><strong>Model Providers</strong></a> ·
13+
<a href="#deploy-your-own"><strong>Deploy Your Own</strong></a> ·
14+
<a href="#running-locally"><strong>Running locally</strong></a>
15+
</p>
16+
<br/>
17+
18+
## Features
19+
20+
- [Next.js](https://nextjs.org) App Router
21+
- Advanced routing for seamless navigation and performance
22+
- React Server Components (RSCs) and Server Actions for server-side rendering and increased performance
23+
- [AI SDK](https://sdk.vercel.ai/docs)
24+
- Unified API for generating text, structured objects, and tool calls with LLMs
25+
- Hooks for building dynamic chat and generative user interfaces
26+
- Supports OpenAI (default), Anthropic, Cohere, and other model providers
27+
- [shadcn/ui](https://ui.shadcn.com)
28+
- Styling with [Tailwind CSS](https://tailwindcss.com)
29+
- Component primitives from [Radix UI](https://radix-ui.com) for accessibility and flexibility
30+
- Data Persistence
31+
- [Vercel Postgres powered by Neon](https://vercel.com/storage/postgres) for saving chat history and user data
32+
- [Vercel Blob](https://vercel.com/storage/blob) for efficient file storage
33+
- [NextAuth.js](https://github.com/nextauthjs/next-auth)
34+
- Simple and secure authentication
35+
36+
## Model Providers
37+
38+
This template ships with OpenAI `gpt-4o` as the default. However, with the [AI SDK](https://sdk.vercel.ai/docs), you can switch LLM providers to [OpenAI](https://openai.com), [Anthropic](https://anthropic.com), [Cohere](https://cohere.com/), and [many more](https://sdk.vercel.ai/providers/ai-sdk-providers) with just a few lines of code.
39+
40+
## Deploy Your Own
41+
42+
You can deploy your own version of the Next.js AI Chatbot to Vercel with one click:
43+
44+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fai-chatbot&env=AUTH_SECRET,OPENAI_API_KEY&envDescription=Learn%20more%20about%20how%20to%20get%20the%20API%20Keys%20for%20the%20application&envLink=https%3A%2F%2Fgithub.com%2Fvercel%2Fai-chatbot%2Fblob%2Fmain%2F.env.example&demo-title=AI%20Chatbot&demo-description=An%20Open-Source%20AI%20Chatbot%20Template%20Built%20With%20Next.js%20and%20the%20AI%20SDK%20by%20Vercel.&demo-url=https%3A%2F%2Fchat.vercel.ai&stores=[{%22type%22:%22postgres%22},{%22type%22:%22blob%22}])
45+
46+
## Running locally
47+
48+
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/projects/environment-variables) for this, but a `.env` file is all that is necessary.
49+
50+
> 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.
51+
52+
1. Install Vercel CLI: `npm i -g vercel`
53+
2. Link local instance with Vercel and GitHub accounts (creates `.vercel` directory): `vercel link`
54+
3. Download your environment variables: `vercel env pull`
55+
56+
```bash
57+
pnpm install
58+
pnpm dev
59+
```
60+
61+
Your app template should now be running on [localhost:3000](http://localhost:3000/).

app/(auth)/actions.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use server';
2+
3+
import { z } from 'zod';
4+
5+
import { createUser, getUser } from '@/lib/db/queries';
6+
7+
import { signIn } from './auth';
8+
9+
const authFormSchema = z.object({
10+
email: z.string().email(),
11+
password: z.string().min(6),
12+
});
13+
14+
export interface LoginActionState {
15+
status: 'idle' | 'in_progress' | 'success' | 'failed' | 'invalid_data';
16+
}
17+
18+
export const login = async (
19+
_: LoginActionState,
20+
formData: FormData,
21+
): Promise<LoginActionState> => {
22+
try {
23+
const validatedData = authFormSchema.parse({
24+
email: formData.get('email'),
25+
password: formData.get('password'),
26+
});
27+
28+
await signIn('credentials', {
29+
email: validatedData.email,
30+
password: validatedData.password,
31+
redirect: false,
32+
});
33+
34+
return { status: 'success' };
35+
} catch (error) {
36+
if (error instanceof z.ZodError) {
37+
return { status: 'invalid_data' };
38+
}
39+
40+
return { status: 'failed' };
41+
}
42+
};
43+
44+
export interface RegisterActionState {
45+
status:
46+
| 'idle'
47+
| 'in_progress'
48+
| 'success'
49+
| 'failed'
50+
| 'user_exists'
51+
| 'invalid_data';
52+
}
53+
54+
export const register = async (
55+
_: RegisterActionState,
56+
formData: FormData,
57+
): Promise<RegisterActionState> => {
58+
try {
59+
const validatedData = authFormSchema.parse({
60+
email: formData.get('email'),
61+
password: formData.get('password'),
62+
});
63+
64+
const [user] = await getUser(validatedData.email);
65+
66+
if (user) {
67+
return { status: 'user_exists' } as RegisterActionState;
68+
}
69+
await createUser(validatedData.email, validatedData.password);
70+
await signIn('credentials', {
71+
email: validatedData.email,
72+
password: validatedData.password,
73+
redirect: false,
74+
});
75+
76+
return { status: 'success' };
77+
} catch (error) {
78+
if (error instanceof z.ZodError) {
79+
return { status: 'invalid_data' };
80+
}
81+
82+
return { status: 'failed' };
83+
}
84+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { GET, POST } from '@/app/(auth)/auth';

app/(auth)/auth.config.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { NextAuthConfig } from 'next-auth';
2+
3+
export const authConfig = {
4+
pages: {
5+
signIn: '/login',
6+
newUser: '/',
7+
},
8+
providers: [
9+
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js
10+
// while this file is also used in non-Node.js environments
11+
],
12+
callbacks: {
13+
authorized({ auth, request: { nextUrl } }) {
14+
const isLoggedIn = !!auth?.user;
15+
const isOnChat = nextUrl.pathname.startsWith('/');
16+
const isOnRegister = nextUrl.pathname.startsWith('/register');
17+
const isOnLogin = nextUrl.pathname.startsWith('/login');
18+
19+
if (isLoggedIn && (isOnLogin || isOnRegister)) {
20+
return Response.redirect(new URL('/', nextUrl as unknown as URL));
21+
}
22+
23+
if (isOnRegister || isOnLogin) {
24+
return true; // Always allow access to register and login pages
25+
}
26+
27+
if (isOnChat) {
28+
if (isLoggedIn) return true;
29+
return false; // Redirect unauthenticated users to login page
30+
}
31+
32+
if (isLoggedIn) {
33+
return Response.redirect(new URL('/', nextUrl as unknown as URL));
34+
}
35+
36+
return true;
37+
},
38+
},
39+
} satisfies NextAuthConfig;

app/(auth)/auth.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { compare } from 'bcrypt-ts';
2+
import NextAuth, { type User, type Session } from 'next-auth';
3+
import Credentials from 'next-auth/providers/credentials';
4+
5+
import { getUser } from '@/lib/db/queries';
6+
7+
import { authConfig } from './auth.config';
8+
9+
interface ExtendedSession extends Session {
10+
user: User;
11+
}
12+
13+
export const {
14+
handlers: { GET, POST },
15+
auth,
16+
signIn,
17+
signOut,
18+
} = NextAuth({
19+
...authConfig,
20+
providers: [
21+
Credentials({
22+
credentials: {},
23+
async authorize({ email, password }: any) {
24+
const users = await getUser(email);
25+
if (users.length === 0) return null;
26+
// biome-ignore lint: Forbidden non-null assertion.
27+
const passwordsMatch = await compare(password, users[0].password!);
28+
if (!passwordsMatch) return null;
29+
return users[0] as any;
30+
},
31+
}),
32+
],
33+
callbacks: {
34+
async jwt({ token, user }) {
35+
if (user) {
36+
token.id = user.id;
37+
}
38+
39+
return token;
40+
},
41+
async session({
42+
session,
43+
token,
44+
}: {
45+
session: ExtendedSession;
46+
token: any;
47+
}) {
48+
if (session.user) {
49+
session.user.id = token.id as string;
50+
}
51+
52+
return session;
53+
},
54+
},
55+
});

0 commit comments

Comments
 (0)