Skip to content

Commit

Permalink
feat nextjs14,shadcn,nextauth,prisma,postgres
Browse files Browse the repository at this point in the history
  • Loading branch information
nisabmohd committed Jan 25, 2024
1 parent 0f544cf commit b40febc
Show file tree
Hide file tree
Showing 90 changed files with 3,152 additions and 4,360 deletions.
3 changes: 0 additions & 3 deletions .env.example

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage
Expand Down
21 changes: 0 additions & 21 deletions LICENSE

This file was deleted.

47 changes: 23 additions & 24 deletions README.md
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.
10 changes: 10 additions & 0 deletions actions/auth.ts
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) {}
106 changes: 106 additions & 0 deletions actions/chat.ts
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];
}
117 changes: 117 additions & 0 deletions app/(private-layout)/chat/[id]/chat.tsx
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>
);
}
24 changes: 24 additions & 0 deletions app/(private-layout)/chat/[id]/loading.tsx
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>
);
}
21 changes: 21 additions & 0 deletions app/(private-layout)/chat/[id]/page.tsx
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} />;
}
Loading

0 comments on commit b40febc

Please sign in to comment.