-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat nextjs14,shadcn,nextauth,prisma,postgres
- Loading branch information
Showing
90 changed files
with
3,152 additions
and
4,360 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
/node_modules | ||
/.pnp | ||
.pnp.js | ||
.yarn/install-state.gz | ||
|
||
# testing | ||
/coverage | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,36 @@ | ||
# ChatGPT clone using OpenAI API | ||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). | ||
|
||
This clone is made with React and Node and uses OpenAI API. | ||
## Getting Started | ||
|
||
- get your api key from https://openai.com/api/ | ||
First, run the development server: | ||
|
||
## Prerequisites | ||
```bash | ||
npm run dev | ||
# or | ||
yarn dev | ||
# or | ||
pnpm dev | ||
# or | ||
bun dev | ||
``` | ||
|
||
Make sure you have installed all of the following prerequisites on your development machine: | ||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. | ||
|
||
- Git - [Download & Install Git](https://git-scm.com/downloads). OSX and Linux machines typically have this already installed. | ||
- Node.js - [Download & Install Node.js](https://nodejs.org/en/download/) and the npm package manager. If you encounter any problems, you can also use this [GitHub Gist](https://gist.github.com/isaacs/579814) to install Node.js. | ||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. | ||
|
||
## Cloning The GitHub Repository | ||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. | ||
|
||
The recommended way to get ChatGPT clone is to use git to directly clone the repository: | ||
## Learn More | ||
|
||
```bash | ||
$ git clone https://github.com/nisabmohd/ChatGPT.git | ||
``` | ||
To learn more about Next.js, take a look at the following resources: | ||
|
||
## Running Your Application | ||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. | ||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. | ||
|
||
open terminal/bash in this repo and enter below commands to start the application | ||
|
||
```bash | ||
$ npm run dev | ||
``` | ||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! | ||
|
||
- Your application should run on port 3000 with the _development_ environment configuration, so in your browser just go to [http://localhost:3000](http://localhost:3000) | ||
## Deploy on Vercel | ||
|
||
## Preview | ||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | ||
|
||
<img src="./images/login.png" /> | ||
<img src="./images/signup.png" /> | ||
<img src="./images/chat.png" /> | ||
<img src="./images/chat-light.png" /> | ||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import prisma from "@/prisma/client"; | ||
|
||
type Register = { | ||
email: string; | ||
username: string; | ||
password: string; | ||
}; | ||
|
||
//TODO: | ||
export async function registerUser({ email, password, username }: Register) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
"use server"; | ||
|
||
import { getUser } from "@/lib/auth"; | ||
import { generateRandomId } from "@/lib/utils"; | ||
import prisma from "@/prisma/client"; | ||
import { revalidatePath } from "next/cache"; | ||
import { redirect } from "next/navigation"; | ||
import OpenAI from "openai"; | ||
|
||
export type Message = { | ||
message: string; | ||
apiKey: string; | ||
conversationId: string; | ||
}; | ||
|
||
export type NewMessage = Omit<Message, "conversationId">; | ||
|
||
export type JSONMessage = { | ||
id: string; | ||
question: string; | ||
answer: string | undefined; | ||
}; | ||
|
||
export async function newChat(params: NewMessage) { | ||
const session = await getUser(); | ||
if (!session?.user) redirect("/login"); | ||
let id: string | undefined; | ||
let error: undefined | { message: string }; | ||
try { | ||
const responseMessage = await createCompletion( | ||
params.apiKey, | ||
params.message | ||
); | ||
const newConversationId = generateRandomId(8); | ||
const newMessageJson = [ | ||
{ | ||
id: newConversationId, | ||
question: params.message, | ||
answer: responseMessage.message.content, | ||
}, | ||
]; | ||
const dataRef = await prisma.conversation.create({ | ||
data: { | ||
messages: newMessageJson, | ||
name: params.message, | ||
userId: session.user.id, | ||
}, | ||
}); | ||
id = dataRef.id; | ||
} catch (err) { | ||
if (err instanceof Error) error = { message: err.message }; | ||
} | ||
console.log(error); | ||
|
||
if (error) return error; | ||
redirect(`/chat/${id}`); | ||
} | ||
|
||
export async function chat(params: Message) { | ||
let error: undefined | { message: string }; | ||
try { | ||
const responseMessage = await createCompletion( | ||
params.apiKey, | ||
params.message | ||
); | ||
const newConversationId = generateRandomId(8); | ||
const dataRef = await prisma.conversation.findUnique({ | ||
where: { | ||
id: params.conversationId, | ||
}, | ||
}); | ||
const updatedMessageJson = [ | ||
...JSON.parse(JSON.stringify(dataRef?.messages)!), | ||
{ | ||
id: newConversationId, | ||
question: params.message, | ||
answer: responseMessage.message.content, | ||
}, | ||
]; | ||
await prisma.conversation.update({ | ||
where: { | ||
id: params.conversationId, | ||
}, | ||
data: { | ||
messages: updatedMessageJson, | ||
}, | ||
}); | ||
} catch (err) { | ||
if (err instanceof Error) error = { message: err.message }; | ||
} | ||
console.log(error); | ||
|
||
if (error) return error; | ||
revalidatePath(`/chat/${params.conversationId}`); | ||
} | ||
|
||
async function createCompletion(apiKey: string, message: string) { | ||
const ai = new OpenAI({ | ||
apiKey, | ||
}); | ||
const chatCompletion = await ai.chat.completions.create({ | ||
messages: [{ role: "user", content: message }], | ||
model: "gpt-3.5-turbo", | ||
}); | ||
return chatCompletion.choices[0]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
"use client"; | ||
|
||
import { JSONMessage, chat } from "@/actions/chat"; | ||
import Submit from "@/components/submit"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Skeleton } from "@/components/ui/skeleton"; | ||
import { useToast } from "@/components/ui/use-toast"; | ||
import { generateRandomId } from "@/lib/utils"; | ||
import { useRouter } from "next/navigation"; | ||
import { ElementRef, useEffect, useOptimistic, useRef } from "react"; | ||
|
||
type ChatProps = { | ||
messages: JSONMessage[]; | ||
id: string; | ||
}; | ||
|
||
export default function Chat({ messages, id }: ChatProps) { | ||
const scrollRef = useRef<ElementRef<"div">>(null); | ||
const [optimisticMessages, addOptimisticMessage] = useOptimistic( | ||
messages, | ||
(state, newMessage: string) => [ | ||
...state, | ||
{ | ||
answer: undefined, | ||
id: generateRandomId(4), | ||
question: newMessage, | ||
}, | ||
] | ||
); | ||
|
||
useEffect(() => { | ||
scrollRef.current?.scrollIntoView({ behavior: "smooth" }); | ||
}, [optimisticMessages]); | ||
|
||
return ( | ||
<div className="grow"> | ||
<div className="flex flex-col items-start gap-12 pb-10 min-h-[75vh] sm:w-[95%]"> | ||
{optimisticMessages.map((message) => ( | ||
<div className="flex flex-col items-start gap-4 " key={message.id}> | ||
<h4 className="text-xl font-medium dark:text-sky-200 text-sky-700"> | ||
{message.question} | ||
</h4> | ||
{!message.answer ? ( | ||
<div className="w-96 flex flex-col gap-3"> | ||
<Skeleton className="w-[100%] h-[20px] rounded-md" /> | ||
<Skeleton className="w-[60%] h-[20px] rounded-md" /> | ||
</div> | ||
) : ( | ||
<p className="dark:text-slate-300 text-slate-900 whitespace-pre-wrap"> | ||
{message.answer} | ||
</p> | ||
)} | ||
</div> | ||
))} | ||
</div> | ||
<div ref={scrollRef}></div> | ||
<div className="mt-5 bottom-0 sticky pb-8 pt-1 bg-background"> | ||
<ChatInput id={id} addMessage={addOptimisticMessage} /> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
type ConversationComponent = { | ||
id: string; | ||
addMessage: (msg: string) => void; | ||
}; | ||
|
||
function ChatInput({ addMessage, id }: ConversationComponent) { | ||
const inputRef = useRef<ElementRef<"input">>(null); | ||
const router = useRouter(); | ||
const { toast } = useToast(); | ||
|
||
async function handleSubmit(formData: FormData) { | ||
const message = formData.get("message") as string; | ||
if (!message) return; | ||
const apiKey = localStorage.getItem("apiKey"); | ||
if (!apiKey) { | ||
toast({ | ||
title: "No API key found!", | ||
description: 'Please add API key from "My account" section', | ||
}); | ||
return; | ||
} | ||
if (inputRef.current) { | ||
inputRef.current.value = ""; | ||
} | ||
addMessage(message); | ||
const err = await chat({ | ||
apiKey, | ||
conversationId: id, | ||
message, | ||
}); | ||
|
||
if (err?.message) { | ||
toast({ | ||
title: err.message, | ||
}); | ||
} | ||
} | ||
|
||
return ( | ||
<form | ||
action={handleSubmit} | ||
className="flex flex-row items-center gap-2 pr-5" | ||
> | ||
<Input | ||
ref={inputRef} | ||
autoComplete="off" | ||
name="message" | ||
placeholder="Ask me something..." | ||
className="h-12" | ||
/> | ||
<Submit /> | ||
</form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Skeleton } from "@/components/ui/skeleton"; | ||
|
||
export default function ChatLoading() { | ||
return ( | ||
<div> | ||
<div className="flex flex-col gap-12 min-h-[80vh]"> | ||
<div className="flex flex-col gap-4"> | ||
<Skeleton className="max-w-[700px] h-[40px] rounded-md" /> | ||
<Skeleton className="w-[70%] h-[20px] rounded-md" /> | ||
<Skeleton className="w-[30%] h-[20px] rounded-md" /> | ||
</div> | ||
<div className="flex flex-col gap-4"> | ||
<Skeleton className="max-w-[700px] h-[40px] rounded-md" /> | ||
<Skeleton className="w-[82%] h-[20px] rounded-md" /> | ||
<Skeleton className="w-[45%] h-[20px] rounded-md" /> | ||
</div> | ||
</div> | ||
<div className=" flex flex-row items-center gap-4 mt-5 bottom-0 sticky pb-8 pt-1 bg-background"> | ||
<Skeleton className="w-[95%] h-[55px] rounded-md" /> | ||
<Skeleton className="w-[3%] h-[55px] rounded-md" /> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { JSONMessage } from "@/actions/chat"; | ||
import prisma from "@/prisma/client"; | ||
import { notFound } from "next/navigation"; | ||
import Chat from "./chat"; | ||
|
||
type PageParams = { | ||
params: { | ||
id: string; | ||
}; | ||
}; | ||
|
||
export default async function ChatSpecificPage({ params: { id } }: PageParams) { | ||
const res = await prisma.conversation.findUnique({ | ||
where: { | ||
id, | ||
}, | ||
}); | ||
if (!res) return notFound(); | ||
const allMessages = res.messages as JSONMessage[]; | ||
return <Chat id={id} messages={allMessages} />; | ||
} |
Oops, something went wrong.