Skip to content

Commit

Permalink
Merge branch 'jrmy/hooks-refactor-drizzle' into live
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyphilemon committed Oct 10, 2024
2 parents 5323527 + 629f121 commit 512f052
Show file tree
Hide file tree
Showing 46 changed files with 392 additions and 318 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ OPENAI_API_KEY=****
# Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32`
AUTH_SECRET=****

/*
* The following keys below are automatically created and
* added to your environment when you deploy on vercel
*/

# Instructions to create kv database here: https://vercel.com/docs/storage/vercel-blob
BLOB_READ_WRITE_TOKEN=****

Expand Down
10 changes: 7 additions & 3 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
"extends": [
"next/core-web-vitals",
"plugin:import/recommended",
"plugin:import/typescript"
"plugin:import/typescript",
"prettier",
"plugin:tailwindcss/recommended"
],
"plugins": ["import"],
"plugins": ["import", "tailwindcss"],
"rules": {
"tailwindcss/no-custom-classname": "off",
"tailwindcss/classnames-order": "off",
"import/order": [
"error",
{
Expand Down Expand Up @@ -33,5 +37,5 @@
}
}
},
"ignorePatterns": ["**/shadcn/**"]
"ignorePatterns": ["**/components/ui/**"]
}
44 changes: 17 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,47 @@
<a href="https://chat.vercel.ai/">
<img alt="Next.js 14 and App Router-ready AI chatbot." src="https://chat.vercel.ai/opengraph-image.png">
<img alt="Next.js 14 and App Router-ready AI chatbot." src="app/(chat)/opengraph-image.png">
<h1 align="center">Next.js AI Chatbot</h1>
</a>

<p align="center">
An open-source AI chatbot app template built with Next.js, the Vercel AI SDK, OpenAI, and Vercel KV.
An Open-Source AI Chatbot Template Built With Next.js and the AI SDK by Vercel.
</p>

<p align="center">
<a href="#features"><strong>Features</strong></a> ·
<a href="#model-providers"><strong>Model Providers</strong></a> ·
<a href="#deploy-your-own"><strong>Deploy Your Own</strong></a> ·
<a href="#running-locally"><strong>Running locally</strong></a> ·
<a href="#authors"><strong>Authors</strong></a>
<a href="#running-locally"><strong>Running locally</strong></a>
</p>
<br/>

## Features

- [Next.js](https://nextjs.org) App Router
- React Server Components (RSCs), Suspense, and Server Actions
- [Vercel AI SDK](https://sdk.vercel.ai/docs) for streaming chat UI
- Support for OpenAI (default), Anthropic, Cohere, Hugging Face, or custom AI chat models and/or LangChain
- Advanced routing for seamless navigation and performance
- React Server Components (RSCs) and Server Actions for server-side rendering and increased performance
- [AI SDK](https://sdk.vercel.ai/docs)
- Unified API for generating text, structured objects, and tool calls with LLMs
- Hooks for building dynamic chat and generative user interfaces
- Supports OpenAI (default), Anthropic, Cohere, and other model providers
- [shadcn/ui](https://ui.shadcn.com)
- 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)
- [NextAuth.js](https://github.com/nextauthjs/next-auth) for authentication
- Component primitives from [Radix UI](https://radix-ui.com) for accessibility and flexibility
- Data Persistence
- [Vercel Postgres powered by Neon](https://vercel.com/storage/postgres) for saving chat history and user data
- [Vercel Blob](https://vercel.com/storage/blob) for efficient file storage
- [NextAuth.js](https://github.com/nextauthjs/next-auth)
- Simple and secure 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), [Cohere](https://cohere.com/), [Hugging Face](https://huggingface.co), or using [LangChain](https://js.langchain.com) with just a few lines of code.
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.

## Deploy Your Own

You can deploy your own version of the Next.js AI Chatbot to Vercel with one click:

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?demo-title=Next.js+Chat&demo-description=A+full-featured%2C+hackable+Next.js+AI+chatbot+built+by+Vercel+Labs&demo-url=https%3A%2F%2Fchat.vercel.ai%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F4aVPvWuTmBvzM5cEdRdqeW%2F4234f9baf160f68ffb385a43c3527645%2FCleanShot_2023-06-16_at_17.09.21.png&project-name=Next.js+Chat&repository-name=nextjs-chat&repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot&from=templates&skippable-integrations=1&env=OPENAI_API_KEY%2CAUTH_SECRET&envDescription=How+to+get+these+env+vars&envLink=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot%2Fblob%2Fmain%2F.env.example&teamCreateStatus=hidden&stores=[{"type":"kv"}])

## Creating a KV Database Instance

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.

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.
[![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}])

## Running locally

Expand All @@ -61,11 +59,3 @@ pnpm dev
```

Your app template should now be running on [localhost:3000](http://localhost:3000/).

## Authors

This library is created by [Vercel](https://vercel.com) and [Next.js](https://nextjs.org) team members, with contributions from:

- 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)) - [Vercel](https://vercel.com)
78 changes: 57 additions & 21 deletions app/(auth)/actions.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,85 @@
"use server";

import { z } from "zod";

import { createUser, getUser } from "@/db/queries";

import { signIn } from "./auth";

const authFormSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
});

export interface LoginActionState {
status: "idle" | "in_progress" | "success" | "failed";
status: "idle" | "in_progress" | "success" | "failed" | "invalid_data";
}

export const login = async (
_: LoginActionState,
formData: FormData,
): Promise<LoginActionState> => {
try {
const validatedData = authFormSchema.parse({
email: formData.get("email"),
password: formData.get("password"),
});

await signIn("credentials", {
email: formData.get("email") as string,
password: formData.get("password") as string,
email: validatedData.email,
password: validatedData.password,
redirect: false,
});

return { status: "success" } as LoginActionState;
} catch {
return { status: "failed" } as LoginActionState;
return { status: "success" };
} catch (error) {
if (error instanceof z.ZodError) {
return { status: "invalid_data" };
}

return { status: "failed" };
}
};

export interface RegisterActionState {
status: "idle" | "in_progress" | "success" | "failed" | "user_exists";
status:
| "idle"
| "in_progress"
| "success"
| "failed"
| "user_exists"
| "invalid_data";
}

export const register = async (_: RegisterActionState, formData: FormData) => {
let email = formData.get("email") as string;
let password = formData.get("password") as string;
let user = await getUser(email);

if (user.length > 0) {
return { status: "user_exists" } as RegisterActionState;
} else {
await createUser(email, password);
await signIn("credentials", {
email,
password,
redirect: false,
export const register = async (
_: RegisterActionState,
formData: FormData,
): Promise<RegisterActionState> => {
try {
const validatedData = authFormSchema.parse({
email: formData.get("email"),
password: formData.get("password"),
});

return { status: "success" } as RegisterActionState;
let [user] = await getUser(validatedData.email);

if (user) {
return { status: "user_exists" } as RegisterActionState;
} else {
await createUser(validatedData.email, validatedData.password);
await signIn("credentials", {
email: validatedData.email,
password: validatedData.password,
redirect: false,
});

return { status: "success" };
}
} catch (error) {
if (error instanceof z.ZodError) {
return { status: "invalid_data" };
}

return { status: "failed" };
}
};
4 changes: 1 addition & 3 deletions app/(auth)/auth.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { compare } from "bcrypt-ts";
import NextAuth, { User , Session } from "next-auth";
import NextAuth, { User, Session } from "next-auth";
import Credentials from "next-auth/providers/credentials";

import { getUser } from "@/db/queries";

import { authConfig } from "./auth.config";



interface ExtendedSession extends Session {
user: User;
}
Expand Down
20 changes: 14 additions & 6 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useActionState, useEffect } from "react";
import { useActionState, useEffect, useState } from "react";
import { toast } from "sonner";

import { Form } from "@/components/form";
import { SubmitButton } from "@/components/submit-button";
import { AuthForm } from "@/components/custom/auth-form";
import { SubmitButton } from "@/components/custom/submit-button";

import { login, LoginActionState } from "../actions";


export default function Page() {
const router = useRouter();

const [email, setEmail] = useState("");

const [state, formAction] = useActionState<LoginActionState, FormData>(
login,
{
Expand All @@ -24,11 +25,18 @@ export default function Page() {
useEffect(() => {
if (state.status === "failed") {
toast.error("Invalid credentials!");
} else if (state.status === "invalid_data") {
toast.error("Failed validating your submission!");
} else if (state.status === "success") {
router.refresh();
}
}, [state.status, router]);

const handleSubmit = (formData: FormData) => {
setEmail(formData.get("email") as string);
formAction(formData);
};

return (
<div className="flex h-screen w-screen items-center justify-center bg-background">
<div className="w-full max-w-md overflow-hidden rounded-2xl flex flex-col gap-12">
Expand All @@ -38,7 +46,7 @@ export default function Page() {
Use your email and password to sign in
</p>
</div>
<Form action={formAction}>
<AuthForm action={handleSubmit} defaultEmail={email}>
<SubmitButton>Sign in</SubmitButton>
<p className="text-center text-sm text-gray-600 mt-4 dark:text-zinc-400">
{"Don't have an account? "}
Expand All @@ -50,7 +58,7 @@ export default function Page() {
</Link>
{" for free."}
</p>
</Form>
</AuthForm>
</div>
</div>
);
Expand Down
19 changes: 14 additions & 5 deletions app/(auth)/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

import Link from "next/link";
import { useRouter } from "next/navigation";
import { useActionState, useEffect } from "react";
import { useActionState, useEffect, useState } from "react";
import { toast } from "sonner";

import { Form } from "@/components/form";
import { SubmitButton } from "@/components/submit-button";
import { AuthForm } from "@/components/custom/auth-form";
import { SubmitButton } from "@/components/custom/submit-button";

import { register, RegisterActionState } from "../actions";

export default function Page() {
const router = useRouter();

const [email, setEmail] = useState("");
const [state, formAction] = useActionState<RegisterActionState, FormData>(
register,
{
Expand All @@ -24,12 +26,19 @@ export default function Page() {
toast.error("Account already exists");
} else if (state.status === "failed") {
toast.error("Failed to create account");
} else if (state.status === "invalid_data") {
toast.error("Failed validating your submission!");
} else if (state.status === "success") {
toast.success("Account created successfully");
router.refresh();
}
}, [state, router]);

const handleSubmit = (formData: FormData) => {
setEmail(formData.get("email") as string);
formAction(formData);
};

return (
<div className="flex h-screen w-screen items-center justify-center bg-background">
<div className="w-full max-w-md overflow-hidden rounded-2xl gap-12 flex flex-col">
Expand All @@ -39,7 +48,7 @@ export default function Page() {
Create an account with your email and password
</p>
</div>
<Form action={formAction}>
<AuthForm action={handleSubmit} defaultEmail={email}>
<SubmitButton>Sign Up</SubmitButton>
<p className="text-center text-sm text-gray-600 mt-4 dark:text-zinc-400">
{"Already have an account? "}
Expand All @@ -51,7 +60,7 @@ export default function Page() {
</Link>
{" instead."}
</p>
</Form>
</AuthForm>
</div>
</div>
);
Expand Down
Loading

0 comments on commit 512f052

Please sign in to comment.