├── .env.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── api │ ├── auth │ │ ├── [...nextauth] │ │ │ ├── options.ts │ │ │ └── route.ts │ │ └── signin │ │ │ └── page.tsx │ └── chat │ │ ├── createNewChat │ │ └── route.ts │ │ ├── getChats │ │ └── route.ts │ │ └── route.ts ├── chat │ └── [[...chatId]] │ │ └── page.tsx ├── components │ ├── ChatSidebar.tsx │ └── Message.tsx ├── context │ └── AuthProvider.tsx ├── favicon.ico ├── globals.css ├── layout.tsx └── page.tsx ├── middleware.tsx ├── models └── chat.ts ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public └── gaterebot.webp ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=replace_me 2 | # CLERK_SECRET_KEY=replace_me 3 | NEXTAUTH_SECRET=replace_me 4 | GITHUB_ID=replace_me 5 | GITHUB_SECRET=replace_me 6 | GOOGLE_CLIENT_ID=replace_me 7 | GOOGLE_CLIENT_SECRET=replace_me 8 | OPENAI_API_KEY=replace_me 9 | MONGODB_URI=replace_me -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub deployments](https://img.shields.io/github/deployments/gateremark/gaterebot/production?style=flat&logo=vercel&logoColor=white&label=vercel) 2 | 3 | # GatereBot 4 | 5 | GatereBot is a ChatGPT-inspired project that leverages the OpenAI API for real-time natural language processing. It is built using NextJS, Tailwind CSS, OpenAI API, MongoDB and Mongoose for chat storage, React Markdown, Vercel AI SDK, and Next Auth for authentication. 6 | 7 |
8 | 9 | 10 | ![GatereBotAuth](https://firebasestorage.googleapis.com/v0/b/gateremark.appspot.com/o/gaterebot1.png?alt=media&token=9900d90c-15a6-4c02-bad1-a5a3db557f73) 11 | 12 | 13 |
14 | 15 | --- 16 | 17 |
18 | 19 | 20 | ![GatereBot](https://firebasestorage.googleapis.com/v0/b/gateremark.appspot.com/o/gaterebot3.png?alt=media&token=0f51faf8-f61b-4a51-9320-14e13f3e0e10) 21 | 22 | 23 |
24 | 25 | ## Table of Contents 26 | 27 | - [Features](#features) 28 | - [Technologies Used](#technologies) 29 | - [Installation](#installation) 30 | - [Deployment](#deployment) 31 | - [Contributing](#contributing) 32 | 33 | ## Features 34 | 35 | - Utilizes NextJS and Tailwind CSS for a responsive and modern UI 36 | - Real-time natural language processing using OpenAI API 37 | - Chat storage in MongoDB with Mongoose 38 | - User authentication with Next Auth 39 | - Integration with Vercel AI SDK for enhanced functionality 40 | - Supports rendering of React Markdown for rich content 41 | 42 | ## Technologies 43 | 44 | - [NextJS](https://nextjs.org/) : React framework for server-side rendering and optimal performance. 45 | - [Tailwind CSS](https://tailwindcss.com/) : A utility-first CSS framework for rapidly designing responsive web pages. 46 | - [OpenAI API](https://platform.openai.com/docs/overview) : Enables real-time natural language processing for user interactions. 47 | - [MongoDB](https://www.mongodb.com/) and [Mongoose](https://mongoosejs.com/) : NoSQL database and ODM for flexible data storage and modeling. 48 | - [React Markdown](https://www.npmjs.com/package/react-markdown) : Library for rendering Markdown content as React components. 49 | - [Vercel AI SDK](https://sdk.vercel.ai/docs) : Integrates AI capabilities into the application deployed on Vercel. 50 | - [Next Auth](https://next-auth.js.org/) : Authentication library for secure user access with social authentication providers. 51 | 52 | ## Installation 53 | 54 | 1. Clone the repository: 55 | 56 | ```bash 57 | git clone https://github.com/gateremark/gaterebot.git 58 | ``` 59 | 60 | 2. Install dependencies: 61 | 62 | ```bash 63 | cd gaterebot 64 | pnpm install 65 | ``` 66 | 67 | 3. Set up environment variables. Create a `.env` file in the root of your project and add the following: 68 | 69 | ```bash 70 | NEXTAUTH_SECRET=replace_me 71 | GITHUB_ID=replace_me 72 | GITHUB_SECRET=replace_me 73 | GOOGLE_CLIENT_ID=replace_me 74 | GOOGLE_CLIENT_SECRET=replace_me 75 | OPENAI_API_KEY=replace_me 76 | MONGODB_URI=replace_me 77 | ``` 78 | 79 | 4. Run the development server: 80 | 81 | ```bash 82 | pnpm run dev 83 | ``` 84 | 85 | 5. Open your browser and navigate to http://localhost:3000. 86 | 87 | ## Deployment 88 | 89 | GatereBot is deployed using Vercel. Any changes pushed to the main branch will trigger an automatic deployment. 90 | You can visit the live version of gaterebot at https://gaterebot.vercel.app/. 91 | 92 | ## Contributing 93 | 94 | Welcoming all contributions to improve the project! To contribute, please follow these steps: 95 | 96 | 1. Fork the repository. 97 | 98 | 2. Create a new branch for your feature: 99 | ```bash 100 | git checkout -b feature-name 101 | ``` 102 | 3. Make your changes and commit them: 103 | ```bash 104 | git commit -m "Description of your changes" 105 | ``` 106 | 4. Push your changes to your fork: 107 | ```bash 108 | git push origin feature-name 109 | ``` 110 | 5. Submit a pull request to the main branch of the original repository. 111 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/options.ts: -------------------------------------------------------------------------------- 1 | import type { NextAuthOptions } from "next-auth"; 2 | import GitHubProvider from "next-auth/providers/github"; 3 | import GoogleProvider from "next-auth/providers/google"; 4 | // import CredentialsProvider from "next-auth/providers/credentials"; 5 | 6 | export const options: NextAuthOptions = { 7 | providers: [ 8 | GitHubProvider({ 9 | clientId: process.env.GITHUB_ID as string, 10 | clientSecret: process.env.GITHUB_SECRET as string, 11 | httpOptions: { timeout: 10000 }, 12 | }), 13 | 14 | GoogleProvider({ 15 | clientId: process.env.GOOGLE_CLIENT_ID as string, 16 | clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, 17 | httpOptions: { timeout: 10000 }, 18 | }), 19 | 20 | // CredentialsProvider({ 21 | // // The name to display on the sign in form (e.g. "Sign in with...") 22 | // name: "Credentials", 23 | // // `credentials` is used to generate a form on the sign in page. 24 | // // You can specify which fields should be submitted, by adding keys to the `credentials` object. 25 | // // e.g. domain, username, password, 2FA token, etc. 26 | // // You can pass any HTML attribute to the tag through the object. 27 | // credentials: { 28 | // username: { 29 | // label: "Username", 30 | // type: "text", 31 | // placeholder: "A cool username...", 32 | // }, 33 | // password: { 34 | // label: "Password", 35 | // type: "password", 36 | // placeholder: "Your Password", 37 | // }, 38 | // }, 39 | // async authorize(credentials, req) { 40 | // // Add logic here to look up the user from the credentials supplied 41 | // const user = { 42 | // id: "1", 43 | // name: "Mark Gatere", 44 | // email: "mark5gatere@gmail.com", 45 | // }; 46 | 47 | // if (user) { 48 | // // Any object returned will be saved in `user` property of the JWT 49 | // return user; 50 | // } else { 51 | // // If you return null then an error will be displayed advising the user to check their details. 52 | // return null; 53 | 54 | // // You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter 55 | // } 56 | // }, 57 | // }), 58 | ], 59 | pages: { 60 | signIn: "/signin", 61 | }, 62 | callbacks: { 63 | async redirect({ url, baseUrl }) { 64 | return baseUrl; 65 | }, 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | import { options } from "./options"; 3 | 4 | const handler = NextAuth(options); 5 | 6 | export { handler as GET, handler as POST }; 7 | -------------------------------------------------------------------------------- /app/api/auth/signin/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { FaGithub } from "react-icons/fa6"; 4 | import { FcGoogle } from "react-icons/fc"; 5 | import Image from "next/image"; 6 | import { signIn, getProviders } from "next-auth/react"; 7 | import { useEffect, useState } from "react"; 8 | // import { useRouter } from "next/navigation"; 9 | import ReactTyped from "react-typed"; 10 | 11 | const Signin = () => { 12 | const [providers, setProviders] = useState | null>( 13 | null 14 | ); 15 | // const router = useRouter(); 16 | 17 | useEffect(() => { 18 | const fetchProviders = async () => { 19 | const providers = await getProviders(); 20 | // Handle providers data as needed 21 | // console.log(providers); 22 | setProviders(providers as Record | null); 23 | }; 24 | 25 | fetchProviders(); 26 | }, []); 27 | 28 | return ( 29 |
30 |
31 | gaterebot logo 39 |
40 |
41 | {" "} 42 | {providers && ( 43 | <> 44 |

Hello there!

45 |

46 | 59 |

60 | 61 | )} 62 |
63 | {providers ? ( 64 | Object.values(providers).map((provider) => ( 65 |
70 | {provider.id === "github" ? ( 71 | 78 | 79 | ) : ( 80 | 87 | )} 88 |
89 | )) 90 | ) : ( 91 |
92 | {" "} 93 |
94 |
95 | )} 96 |
97 |
98 |
99 | ); 100 | }; 101 | 102 | export default Signin; 103 | -------------------------------------------------------------------------------- /app/api/chat/createNewChat/route.ts: -------------------------------------------------------------------------------- 1 | import Chat from "@/models/chat"; 2 | import { NextResponse } from "next/server"; 3 | 4 | export async function POST(req: any) { 5 | try { 6 | const body = await req.json(); 7 | const chatData = body; 8 | // console.log(chatData); 9 | 10 | await Chat.create(chatData); 11 | 12 | return NextResponse.json({ message: "Chat Created" }, { status: 201 }); 13 | } catch (err) { 14 | // console.log(err); 15 | return NextResponse.json({ message: "Error", err }, { status: 500 }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/api/chat/getChats/route.ts: -------------------------------------------------------------------------------- 1 | import Chat from "@/models/chat"; 2 | import { NextResponse } from "next/server"; 3 | 4 | export async function GET() { 5 | try { 6 | const chats = await Chat.find().sort({ createdAt: -1 }); 7 | 8 | return NextResponse.json({ chats }, { status: 200 }); 9 | } catch (err) { 10 | // console.log(err); 11 | return NextResponse.json({ message: "Error", err }, { status: 500 }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import OpenAI from "openai"; 2 | import { OpenAIStream, StreamingTextResponse } from "ai"; 3 | 4 | // Create an OpenAI API client (that's edge friendly!) 5 | const openai = new OpenAI({ 6 | apiKey: process.env.OPENAI_API_KEY, 7 | }); 8 | 9 | // Set the runtime to edge for best performance 10 | export const runtime = "edge"; 11 | 12 | export async function POST(req: Request) { 13 | const { messages } = await req.json(); 14 | const initialMessage = { 15 | role: "system", 16 | content: 17 | "Your name is gaterebot. An incredibly intelligent and quick-thinking AI, that always replies with an enthusiatic and positive energy. You were created by [gateremark](https://bit.ly/gatere) and your response must be formatted as markdown.", 18 | }; 19 | 20 | // Ask OpenAI for a streaming completion given the prompt 21 | const response = await openai.chat.completions.create({ 22 | model: "gpt-3.5-turbo-1106", 23 | stream: true, 24 | // temperature: 0.6, 25 | // max_tokens: 300, 26 | messages: messages.concat(initialMessage), 27 | }); 28 | // Convert the response into a friendly text-stream 29 | const stream = OpenAIStream(response); 30 | // Respond with the stream 31 | return new StreamingTextResponse(stream); 32 | } 33 | -------------------------------------------------------------------------------- /app/chat/[[...chatId]]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { FormEvent } from "react"; 4 | import ChatSidebar from "@/app/components/ChatSidebar"; 5 | import { IoSend } from "react-icons/io5"; 6 | import { useChat } from "ai/react"; 7 | import Message from "@/app/components/Message"; 8 | import { useSession } from "next-auth/react"; 9 | import Image from "next/image"; 10 | import { useState, useEffect, useRef } from "react"; 11 | 12 | export default function Home() { 13 | const { data: session } = useSession(); 14 | const user = session?.user; 15 | const email = user?.email; 16 | const bottomRef = useRef(null); 17 | 18 | const { messages, input, handleInputChange, handleSubmit, isLoading } = 19 | useChat(); 20 | // console.log("Messages:", messages); 21 | // const [prompt, ai] = messages; 22 | // console.log("Prompt:", prompt?.content); 23 | // messages.map((m) => console.log("new content:", m.content)); 24 | // console.log("AI:", ai?.content); 25 | // const userData = prompt?.content + "\n" + ai?.content; 26 | // console.log("User Data:", userData); 27 | 28 | useEffect(() => { 29 | bottomRef.current?.scrollIntoView({ 30 | behavior: "smooth", 31 | block: "start", 32 | }); 33 | }, [messages]); 34 | 35 | const mongoHandleSubmit = async () => { 36 | try { 37 | // console.log("Sending data:", { 38 | // email: email, 39 | // title: input, 40 | // messages: messages, 41 | // }); 42 | 43 | const response = await fetch("/api/chat/createNewChat", { 44 | method: "POST", 45 | headers: { 46 | "Content-Type": "application/json", 47 | }, 48 | body: JSON.stringify({ 49 | email: email, 50 | title: input, 51 | messages: messages, 52 | }), 53 | }); 54 | 55 | const json = await response.json(); 56 | // console.log("Response:", json); 57 | } catch (error) { 58 | console.error("Error submitting the chat:", error); 59 | } 60 | }; 61 | 62 | // setTimeout(() => { 63 | // mongoHandleSubmit(); 64 | // }, 10000); 65 | // setTimeout(mongoHandleSubmit, 10000); 66 | 67 | const handleKeyDown = (e: React.KeyboardEvent) => { 68 | if (e.key === "Enter") { 69 | e.preventDefault(); 70 | const syntheticEvent = e as unknown as FormEvent; 71 | // Submit the form 72 | // handleSubmit(syntheticEvent); 73 | finalSubmit(e); 74 | } 75 | }; 76 | const finalSubmit = (e: any) => { 77 | e.preventDefault(); 78 | handleSubmit(e); 79 | mongoHandleSubmit(); 80 | }; 81 | // console.log(messages); 82 | // console.log("Messages:", messages[0]?.content); 83 | // console.log("Input:", input); 84 | 85 | return ( 86 | <> 87 | New Chat 88 | 89 |
90 | 91 |
92 | {!messages.length && !isLoading && ( 93 |
94 | gaterebot logo 102 |

103 | Ask me a question 104 |

105 |
106 | )} 107 |
108 | {messages.map((m) => ( 109 | 110 | ))} 111 |
112 |
113 | 114 |
119 |
123 |