Skip to content

Commit 1ec3f04

Browse files
committed
working clone of chatgpt website
0 parents  commit 1ec3f04

25 files changed

+3393
-0
lines changed

.eslintrc.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

.gitignore

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# local env files
28+
.env*.local
29+
30+
# vercel
31+
.vercel
32+
33+
# typescript
34+
*.tsbuildinfo
35+
next-env.d.ts
36+
37+
38+
# dotenv variables
39+
.env

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
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).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
```
14+
15+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
16+
17+
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
18+
19+
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
20+
21+
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
22+
23+
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
24+
25+
## Learn More
26+
27+
To learn more about Next.js, take a look at the following resources:
28+
29+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
30+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
31+
32+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
33+
34+
## Deploy on Vercel
35+
36+
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.
37+
38+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

next.config.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
reactStrictMode: false,
4+
env: {
5+
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
6+
MIXPANEL_PROJECT_TOKEN: process.env.MIXPANEL_PROJECT_TOKEN,
7+
APP_ENV: process.env.APP_ENV,
8+
APP_NAME: process.env.APP_NAME
9+
}
10+
}
11+
12+
module.exports = nextConfig

package.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "nextjs-tailwindcss-chatgpt-clone",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "next lint"
10+
},
11+
"dependencies": {
12+
"@types/node": "18.15.11",
13+
"@types/react": "18.0.37",
14+
"@types/react-dom": "18.0.11",
15+
"autoprefixer": "10.4.14",
16+
"axios": "^1.3.5",
17+
"dotenv": "^16.0.3",
18+
"eslint": "8.38.0",
19+
"eslint-config-next": "13.3.0",
20+
"mixpanel-browser": "^2.46.0",
21+
"next": "13.3.0",
22+
"openai": "^3.2.1",
23+
"postcss": "8.4.22",
24+
"react": "18.2.0",
25+
"react-dom": "18.2.0",
26+
"react-icons": "^4.8.0",
27+
"tailwindcss": "3.3.1",
28+
"typescript": "5.0.4",
29+
"uuid": "^9.0.0"
30+
},
31+
"devDependencies": {
32+
"@types/mixpanel-browser": "^2.38.1",
33+
"@types/uuid": "^9.0.1"
34+
}
35+
}

postcss.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}

public/favicon.ico

25.3 KB
Binary file not shown.

src/components/Chat.tsx

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import { useEffect, useRef, useState } from "react";
2+
import { FiSend } from "react-icons/fi";
3+
import { BsChevronDown, BsPlusLg } from "react-icons/bs";
4+
import { RxHamburgerMenu } from "react-icons/rx";
5+
import useAnalytics from "@/hooks/useAnalytics";
6+
import useAutoResizeTextArea from "@/hooks/useAutoResizeTextArea";
7+
import Message from "./Message";
8+
import { DEFAULT_OPENAI_MODEL } from "@/shared/Constants";
9+
10+
const Chat = (props: any) => {
11+
const { toggleComponentVisibility } = props;
12+
13+
const [isLoading, setIsLoading] = useState(false);
14+
const [errorMessage, setErrorMessage] = useState("");
15+
const [showEmptyChat, setShowEmptyChat] = useState(true);
16+
const [conversation, setConversation] = useState<any[]>([]);
17+
const [message, setMessage] = useState("");
18+
const { trackEvent } = useAnalytics();
19+
const textAreaRef = useAutoResizeTextArea();
20+
const bottomOfChatRef = useRef<HTMLDivElement>(null);
21+
22+
const selectedModel = DEFAULT_OPENAI_MODEL;
23+
24+
useEffect(() => {
25+
if (textAreaRef.current) {
26+
textAreaRef.current.style.height = "24px";
27+
textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`;
28+
}
29+
}, [message, textAreaRef]);
30+
31+
useEffect(() => {
32+
if (bottomOfChatRef.current) {
33+
bottomOfChatRef.current.scrollIntoView({ behavior: "smooth" });
34+
}
35+
}, [conversation]);
36+
37+
const sendMessage = async (e: any) => {
38+
e.preventDefault();
39+
40+
// Don't send empty messages
41+
if (message.length < 1) {
42+
setErrorMessage("Please enter a message.");
43+
return;
44+
} else {
45+
setErrorMessage("");
46+
}
47+
48+
trackEvent("send.message", { message: message });
49+
setIsLoading(true);
50+
51+
// Add the message to the conversation
52+
setConversation([
53+
...conversation,
54+
{ content: message, role: "user" },
55+
{ content: null, role: "system" },
56+
]);
57+
58+
// Clear the message & remove empty chat
59+
setMessage("");
60+
setShowEmptyChat(false);
61+
62+
try {
63+
const response = await fetch(`/api/openai`, {
64+
method: "POST",
65+
headers: {
66+
"Content-Type": "application/json",
67+
},
68+
body: JSON.stringify({
69+
messages: [...conversation, { content: message, role: "user" }],
70+
model: selectedModel,
71+
}),
72+
});
73+
74+
if (response.ok) {
75+
const data = await response.json();
76+
77+
// Add the message to the conversation
78+
setConversation([
79+
...conversation,
80+
{ content: message, role: "user" },
81+
{ content: data.message, role: "system" },
82+
]);
83+
} else {
84+
console.error(response);
85+
setErrorMessage(response.statusText);
86+
}
87+
88+
setIsLoading(false);
89+
} catch (error: any) {
90+
console.error(error);
91+
setErrorMessage(error.message);
92+
93+
setIsLoading(false);
94+
}
95+
};
96+
97+
const handleKeypress = (e: any) => {
98+
// It's triggers by pressing the enter key
99+
if (e.keyCode == 13 && !e.shiftKey) {
100+
sendMessage(e);
101+
e.preventDefault();
102+
}
103+
};
104+
105+
return (
106+
<div className="flex max-w-full flex-1 flex-col">
107+
<div className="sticky top-0 z-10 flex items-center border-b border-white/20 bg-gray-800 pl-1 pt-1 text-gray-200 sm:pl-3 md:hidden">
108+
<button
109+
type="button"
110+
className="-ml-0.5 -mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white dark:hover:text-white"
111+
onClick={toggleComponentVisibility}
112+
>
113+
<span className="sr-only">Open sidebar</span>
114+
<RxHamburgerMenu className="h-6 w-6 text-white" />
115+
</button>
116+
<h1 className="flex-1 text-center text-base font-normal">New chat</h1>
117+
<button type="button" className="px-3">
118+
<BsPlusLg className="h-6 w-6" />
119+
</button>
120+
</div>
121+
<div className="relative h-full w-full transition-width flex flex-col overflow-hidden items-stretch flex-1">
122+
<div className="flex-1 overflow-hidden">
123+
<div className="react-scroll-to-bottom--css-ikyem-79elbk h-full dark:bg-gray-800">
124+
<div className="react-scroll-to-bottom--css-ikyem-1n7m0yu">
125+
{!showEmptyChat && conversation.length > 0 ? (
126+
<div className="flex flex-col items-center text-sm bg-gray-800">
127+
<div className="flex w-full items-center justify-center gap-1 border-b border-black/10 bg-gray-50 p-3 text-gray-500 dark:border-gray-900/50 dark:bg-gray-700 dark:text-gray-300">
128+
Model: {selectedModel.name}
129+
</div>
130+
{conversation.map((message, index) => (
131+
<Message key={index} message={message} />
132+
))}
133+
<div className="w-full h-32 md:h-48 flex-shrink-0"></div>
134+
<div ref={bottomOfChatRef}></div>
135+
</div>
136+
) : null}
137+
{showEmptyChat ? (
138+
<div className="py-10 relative w-full flex flex-col h-full">
139+
<div className="flex items-center justify-center gap-2">
140+
<div className="relative w-full md:w-1/2 lg:w-1/3 xl:w-1/4">
141+
<button
142+
className="relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:border-gray-400 focus:outline-none focus:ring-1 focus:ring-gray-400 dark:border-white/20 dark:bg-gray-800 sm:text-sm align-center"
143+
id="headlessui-listbox-button-:r0:"
144+
type="button"
145+
aria-haspopup="true"
146+
aria-expanded="false"
147+
data-headlessui-state=""
148+
aria-labelledby="headlessui-listbox-label-:r1: headlessui-listbox-button-:r0:"
149+
>
150+
<label
151+
className="block text-xs text-gray-700 dark:text-gray-500 text-center"
152+
id="headlessui-listbox-label-:r1:"
153+
data-headlessui-state=""
154+
>
155+
Model
156+
</label>
157+
<span className="inline-flex w-full truncate">
158+
<span className="flex h-6 items-center gap-1 truncate text-white">
159+
{selectedModel.name}
160+
</span>
161+
</span>
162+
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
163+
<BsChevronDown className="h-4 w-4 text-gray-400" />
164+
</span>
165+
</button>
166+
</div>
167+
</div>
168+
<h1 className="text-2xl sm:text-4xl font-semibold text-center text-gray-200 dark:text-gray-600 flex gap-2 items-center justify-center h-screen">
169+
ChatGPT Clone
170+
</h1>
171+
</div>
172+
) : null}
173+
<div className="flex flex-col items-center text-sm dark:bg-gray-800"></div>
174+
</div>
175+
</div>
176+
</div>
177+
<div className="absolute bottom-0 left-0 w-full border-t md:border-t-0 dark:border-white/20 md:border-transparent md:dark:border-transparent md:bg-vert-light-gradient bg-white dark:bg-gray-800 md:!bg-transparent dark:md:bg-vert-dark-gradient pt-2">
178+
<form className="stretch mx-2 flex flex-row gap-3 last:mb-2 md:mx-4 md:last:mb-6 lg:mx-auto lg:max-w-2xl xl:max-w-3xl">
179+
<div className="relative flex flex-col h-full flex-1 items-stretch md:flex-col">
180+
{errorMessage ? (
181+
<div className="mb-2 md:mb-0">
182+
<div className="h-full flex ml-1 md:w-full md:m-auto md:mb-2 gap-0 md:gap-2 justify-center">
183+
<span className="text-red-500 text-sm">{errorMessage}</span>
184+
</div>
185+
</div>
186+
) : null}
187+
<div className="flex flex-col w-full py-2 flex-grow md:py-3 md:pl-4 relative border border-black/10 bg-white dark:border-gray-900/50 dark:text-white dark:bg-gray-700 rounded-md shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:shadow-[0_0_15px_rgba(0,0,0,0.10)]">
188+
<textarea
189+
ref={textAreaRef}
190+
value={message}
191+
tabIndex={0}
192+
data-id="root"
193+
style={{
194+
height: "24px",
195+
maxHeight: "200px",
196+
overflowY: "hidden",
197+
}}
198+
// rows={1}
199+
placeholder="Send a message..."
200+
className="m-0 w-full resize-none border-0 bg-transparent p-0 pr-7 focus:ring-0 focus-visible:ring-0 dark:bg-transparent pl-2 md:pl-0"
201+
onChange={(e) => setMessage(e.target.value)}
202+
onKeyDown={handleKeypress}
203+
></textarea>
204+
<button
205+
disabled={isLoading || message?.length === 0}
206+
onClick={sendMessage}
207+
className="absolute p-1 rounded-md bottom-1.5 md:bottom-2.5 bg-transparent disabled:bg-gray-500 right-1 md:right-2 disabled:opacity-40"
208+
>
209+
<FiSend className="h-4 w-4 mr-1 text-white " />
210+
</button>
211+
</div>
212+
</div>
213+
</form>
214+
<div className="px-3 pt-2 pb-3 text-center text-xs text-black/50 dark:text-white/50 md:px-4 md:pt-3 md:pb-6">
215+
<span>
216+
ChatGPT Clone may produce inaccurate information about people,
217+
places, or facts.
218+
</span>
219+
</div>
220+
</div>
221+
</div>
222+
</div>
223+
);
224+
};
225+
226+
export default Chat;

0 commit comments

Comments
 (0)