├── .env.example
├── .eslintrc.json
├── public
├── favicon.ico
├── assets
│ └── images
│ │ └── icons
│ │ ├── dark-moon.png
│ │ ├── light-sun.png
│ │ ├── send-message.png
│ │ ├── message-send.svg
│ │ └── dark-moon.svg
├── vercel.svg
├── thirteen.svg
└── next.svg
├── postcss.config.js
├── next.config.js
├── pages
├── _app.tsx
├── _document.tsx
├── typeWriter.tsx
├── index.tsx
├── api
│ └── generate.ts
└── container.tsx
├── tailwind.config.js
├── .gitignore
├── context
└── DarkContext.tsx
├── README.md
├── tsconfig.json
├── package.json
├── utils
└── OpenAIStream.ts
└── styles
├── globals.css
└── Home.module.css
/.env.example:
--------------------------------------------------------------------------------
1 | OPENAI_API_KEY = YOUR API KEY HERE
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WonderEagle/Next-chatGPT/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/assets/images/icons/dark-moon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WonderEagle/Next-chatGPT/HEAD/public/assets/images/icons/dark-moon.png
--------------------------------------------------------------------------------
/public/assets/images/icons/light-sun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WonderEagle/Next-chatGPT/HEAD/public/assets/images/icons/light-sun.png
--------------------------------------------------------------------------------
/public/assets/images/icons/send-message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WonderEagle/Next-chatGPT/HEAD/public/assets/images/icons/send-message.png
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '@/styles/globals.css'
2 | import type { AppProps } from 'next/app'
3 |
4 | export default function App({ Component, pageProps }: AppProps) {
5 | return
6 | }
7 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./app/**/*.{js,ts,jsx,tsx}",
5 | "./pages/**/*.{js,ts,jsx,tsx}",
6 | "./components/**/*.{js,ts,jsx,tsx}",
7 |
8 | // Or if using `src` directory:
9 | "./src/**/*.{js,ts,jsx,tsx}",
10 | ],
11 | theme: {
12 | extend: {},
13 | },
14 | plugins: [require("daisyui")],
15 | };
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | /.env
7 | .env
8 | .pnp.js
9 |
10 | # testing
11 | /coverage
12 |
13 | # next.js
14 | /.next/
15 | /out/
16 |
17 | # production
18 | /build
19 |
20 | # misc
21 | .DS_Store
22 | *.pem
23 |
24 | # debug
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 | .pnpm-debug.log*
29 |
30 | # local env files
31 | .env*.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 |
--------------------------------------------------------------------------------
/pages/typeWriter.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 |
4 | const TypeWriter = ({ typeContent }: any) => {
5 | const [typeLetter, setTypeLetter] = useState("");
6 |
7 | let i = 0;
8 |
9 | const handleTyping = () => {
10 | setTypeLetter(typeContent.substring(0, i))
11 | i++;
12 | }
13 |
14 | useEffect(() => {
15 | const interval = setInterval(() => handleTyping(), 200);
16 | return () => clearInterval(interval);
17 | }, []);
18 |
19 | return ({typeLetter}
);
20 | }
21 |
22 | export default TypeWriter;
--------------------------------------------------------------------------------
/context/DarkContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useState } from 'react';
2 |
3 |
4 | const DarkContext = createContext({});
5 |
6 | const DarkProvider = (props: any) => {
7 |
8 | const [darkMode, setDarkMode] = useState(false);
9 |
10 | const toggleDarkMode = () => {
11 | setDarkMode(!darkMode);
12 | console.log(darkMode)
13 | }
14 |
15 | return (
16 |
17 | {props.children}
18 |
19 | )
20 | }
21 |
22 | export { DarkContext, DarkProvider };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [`ChatGPT`](http://135.181.49.37/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Chat-GPT Next.js APP
4 |
5 | First, please create .env file in root folder of project directory.
6 |
7 | ```bash
8 | OPENAI_API_KEY = YOUR API KEY HERE
9 | ```
10 |
11 | Second, run the development server:
12 |
13 | ```bash
14 | npm run dev
15 | # or
16 | yarn dev
17 | # or
18 | pnpm dev
19 | ```
20 |
21 | ## How to get OpenAI API key.
22 |
23 | [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys)
24 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "paths": {
18 | "@/*": ["./*"]
19 | }
20 | },
21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
22 | "exclude": ["node_modules"]
23 | }
24 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 |
3 | import { DarkProvider } from '@/context/DarkContext';
4 |
5 | import Container from './container';
6 |
7 | export default function Home() {
8 |
9 | return (
10 | <>
11 |
12 |
13 | ChatGPT BOT
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | >
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-project",
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.14.2",
13 | "@types/react": "18.0.28",
14 | "@types/react-dom": "18.0.11",
15 | "daisyui": "^2.51.5",
16 | "eslint": "8.35.0",
17 | "eslint-config-next": "13.2.2",
18 | "eventsource-parser": "^0.1.0",
19 | "next": "13.2.2",
20 | "openai": "^3.1.0",
21 | "react": "18.2.0",
22 | "react-dom": "18.2.0",
23 | "typescript": "4.9.5"
24 | },
25 | "devDependencies": {
26 | "autoprefixer": "^10.4.13",
27 | "postcss": "^8.4.21",
28 | "tailwindcss": "^3.2.7"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/api/generate.ts:
--------------------------------------------------------------------------------
1 | import { OpenAIStream, OpenAIStreamPayload } from "../../utils/OpenAIStream";
2 |
3 | type RequestData = {
4 | messageText: string;
5 | };
6 |
7 |
8 | if (!process.env.OPENAI_API_KEY) {
9 | throw new Error("Missing env var from OpenAI");
10 | }
11 |
12 | export const config = {
13 | runtime: "edge",
14 | };
15 |
16 | let message_junk = "";
17 |
18 | const handler = async (req: Request): Promise => {
19 |
20 | const { messageText } = (await req.json()) as RequestData;
21 |
22 | message_junk += `${messageText} \n` ;
23 |
24 | if (!messageText) {
25 | return new Response("No prompt in the request, Please check README.md else please contact via my mail: zhenghu61919@gmail.com", { status: 400 });
26 | }
27 |
28 | const payload: OpenAIStreamPayload = {
29 | model: "text-davinci-003",
30 | prompt: message_junk,
31 | temperature: 0.7,
32 | top_p: 1,
33 | frequency_penalty: 0,
34 | presence_penalty: 0,
35 | max_tokens: 1000,
36 | stream: true,
37 | n: 1,
38 | };
39 |
40 | const stream = await OpenAIStream(payload);
41 | return new Response(stream);
42 | };
43 |
44 | export default handler;
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/OpenAIStream.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createParser,
3 | ParsedEvent,
4 | ReconnectInterval,
5 | } from "eventsource-parser";
6 |
7 | export interface OpenAIStreamPayload {
8 | model: string;
9 | prompt: string;
10 | temperature: number;
11 | top_p: number;
12 | frequency_penalty: number;
13 | presence_penalty: number;
14 | max_tokens: number;
15 | stream: boolean;
16 | n: number;
17 | }
18 |
19 | export async function OpenAIStream(payload: OpenAIStreamPayload) {
20 | const encoder = new TextEncoder();
21 | const decoder = new TextDecoder();
22 |
23 | let counter = 0;
24 |
25 | const res = await fetch("https://api.openai.com/v1/completions", {
26 | headers: {
27 | "Content-Type": "application/json",
28 | Authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ""}`,
29 | },
30 | method: "POST",
31 | body: JSON.stringify(payload),
32 | });
33 |
34 | const stream = new ReadableStream({
35 | async start(controller) {
36 | // callback
37 | function onParse(event: ParsedEvent | ReconnectInterval) {
38 | if (event.type === "event") {
39 | const data = event.data;
40 | // https://beta.openai.com/docs/api-reference/completions/create#completions/create-stream
41 | if (data === "[DONE]") {
42 | controller.close();
43 | return;
44 | }
45 | try {
46 | const json = JSON.parse(data);
47 | const text = json.choices[0].text;
48 | if (counter < 2 && (text.match(/\n/) || []).length) {
49 | // this is a prefix character (i.e., "\n\n"), do nothing
50 | return;
51 | }
52 | const queue = encoder.encode(text);
53 | controller.enqueue(queue);
54 | counter++;
55 | } catch (e) {
56 | // maybe parse error
57 | controller.error(e);
58 | }
59 | }
60 | }
61 |
62 | // stream response (SSE) from OpenAI may be fragmented into multiple chunks
63 | // this ensures we properly read chunks and invoke an event for each SSE event stream
64 | const parser = createParser(onParse);
65 | // https://web.dev/streams/#asynchronous-iteration
66 | for await (const chunk of res.body as any) {
67 | parser.feed(decoder.decode(chunk));
68 | }
69 | },
70 | });
71 |
72 | return stream;
73 | }
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .header-true {
6 | @apply bg-[#212121]
7 | }
8 |
9 | .header-false {
10 | @apply bg-white
11 | }
12 |
13 | .text-true {
14 | @apply text-white;
15 | }
16 |
17 | .text-false {
18 | @apply text-sky-400;
19 | }
20 |
21 | .bg-true {
22 | @apply bg-[#212121];
23 | }
24 |
25 | .bg-false {
26 | @apply bg-sky-100;
27 | }
28 |
29 | .container-bg-true {
30 | @apply bg-[#0f0f0f];
31 | }
32 |
33 |
34 | .container-bg-false {
35 | @apply bg-sky-100;
36 | }
37 |
38 | .input-bg-true {
39 | @apply bg-[#212121];
40 | }
41 |
42 | .input-bg-false {
43 | @apply bg-sky-50;
44 | }
45 |
46 | .input-border-true {
47 | @apply border-gray-300
48 | }
49 |
50 | .input-border-false {
51 | @apply border-sky-300
52 | }
53 |
54 | .input-text-true {
55 | @apply text-white;
56 | }
57 |
58 | .input-text-false {
59 | @apply text-sky-900;
60 | }
61 |
62 | .button-bg-true {
63 | @apply bg-[#8774e1];
64 | }
65 |
66 | .button-bg-false {
67 | @apply bg-sky-500;
68 | }
69 |
70 | .input-user-chat-bg-true {
71 | @apply bg-[#8774e1];
72 | }
73 |
74 | .input-user-chat-bg-false {
75 | @apply bg-[#efffde];
76 | }
77 |
78 | .input-user-chat-color-true {
79 | @apply text-white;
80 | }
81 |
82 | .input-user-chat-color-false {
83 | @apply text-black;
84 | }
85 |
86 | .input-bot-chat-bg-true {
87 | @apply bg-[#212121];
88 | }
89 |
90 | .isLoading-true {
91 | @apply opacity-10;
92 | }
93 |
94 | .input-bot-chat-bg-false {
95 | @apply bg-white;
96 | }
97 |
98 |
99 | .chat-container::-webkit-scrollbar {
100 | display: none;
101 | }
102 |
103 | .lds-ellipsis {
104 | display: inline-block;
105 | position: relative;
106 | width: 80px;
107 | height: 80px;
108 | }
109 | .lds-ellipsis div {
110 | position: absolute;
111 | top: 33px;
112 | width: 8px;
113 | height: 8px;
114 | border-radius: 50%;
115 | background: gray;
116 | animation-timing-function: cubic-bezier(0, 1, 1, 0);
117 | }
118 | .lds-ellipsis div:nth-child(1) {
119 | left: 4px;
120 | animation: lds-ellipsis1 0.6s infinite;
121 | }
122 | .lds-ellipsis div:nth-child(2) {
123 | left: 4px;
124 | animation: lds-ellipsis2 0.6s infinite;
125 | }
126 | .lds-ellipsis div:nth-child(3) {
127 | left: 16px;
128 | animation: lds-ellipsis2 0.6s infinite;
129 | }
130 | .lds-ellipsis div:nth-child(4) {
131 | left: 28px;
132 | animation: lds-ellipsis3 0.6s infinite;
133 | }
134 | @keyframes lds-ellipsis1 {
135 | 0% {
136 | transform: scale(0);
137 | }
138 | 100% {
139 | transform: scale(1);
140 | }
141 | }
142 | @keyframes lds-ellipsis3 {
143 | 0% {
144 | transform: scale(1);
145 | }
146 | 100% {
147 | transform: scale(0);
148 | }
149 | }
150 | @keyframes lds-ellipsis2 {
151 | 0% {
152 | transform: translate(0, 0);
153 | }
154 | 100% {
155 | transform: translate(12px, 0);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | align-items: center;
6 | padding: 6rem;
7 | min-height: 100vh;
8 | }
9 |
10 | .description {
11 | display: inherit;
12 | justify-content: inherit;
13 | align-items: inherit;
14 | font-size: 0.85rem;
15 | max-width: var(--max-width);
16 | width: 100%;
17 | z-index: 2;
18 | font-family: var(--font-mono);
19 | }
20 |
21 | .description a {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | gap: 0.5rem;
26 | }
27 |
28 | .description p {
29 | position: relative;
30 | margin: 0;
31 | padding: 1rem;
32 | background-color: rgba(var(--callout-rgb), 0.5);
33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34 | border-radius: var(--border-radius);
35 | }
36 |
37 | .code {
38 | font-weight: 700;
39 | font-family: var(--font-mono);
40 | }
41 |
42 | .grid {
43 | display: grid;
44 | grid-template-columns: repeat(4, minmax(25%, auto));
45 | width: var(--max-width);
46 | max-width: 100%;
47 | }
48 |
49 | .card {
50 | padding: 1rem 1.2rem;
51 | border-radius: var(--border-radius);
52 | background: rgba(var(--card-rgb), 0);
53 | border: 1px solid rgba(var(--card-border-rgb), 0);
54 | transition: background 200ms, border 200ms;
55 | }
56 |
57 | .card span {
58 | display: inline-block;
59 | transition: transform 200ms;
60 | }
61 |
62 | .card h2 {
63 | font-weight: 600;
64 | margin-bottom: 0.7rem;
65 | }
66 |
67 | .card p {
68 | margin: 0;
69 | opacity: 0.6;
70 | font-size: 0.9rem;
71 | line-height: 1.5;
72 | max-width: 30ch;
73 | }
74 |
75 | .center {
76 | display: flex;
77 | justify-content: center;
78 | align-items: center;
79 | position: relative;
80 | padding: 4rem 0;
81 | }
82 |
83 | .center::before {
84 | background: var(--secondary-glow);
85 | border-radius: 50%;
86 | width: 480px;
87 | height: 360px;
88 | margin-left: -400px;
89 | }
90 |
91 | .center::after {
92 | background: var(--primary-glow);
93 | width: 240px;
94 | height: 180px;
95 | z-index: -1;
96 | }
97 |
98 | .center::before,
99 | .center::after {
100 | content: '';
101 | left: 50%;
102 | position: absolute;
103 | filter: blur(45px);
104 | transform: translateZ(0);
105 | }
106 |
107 | .logo,
108 | .thirteen {
109 | position: relative;
110 | }
111 |
112 | .thirteen {
113 | display: flex;
114 | justify-content: center;
115 | align-items: center;
116 | width: 75px;
117 | height: 75px;
118 | padding: 25px 10px;
119 | margin-left: 16px;
120 | transform: translateZ(0);
121 | border-radius: var(--border-radius);
122 | overflow: hidden;
123 | box-shadow: 0px 2px 8px -1px #0000001a;
124 | }
125 |
126 | .thirteen::before,
127 | .thirteen::after {
128 | content: '';
129 | position: absolute;
130 | z-index: -1;
131 | }
132 |
133 | /* Conic Gradient Animation */
134 | .thirteen::before {
135 | animation: 6s rotate linear infinite;
136 | width: 200%;
137 | height: 200%;
138 | background: var(--tile-border);
139 | }
140 |
141 | /* Inner Square */
142 | .thirteen::after {
143 | inset: 0;
144 | padding: 1px;
145 | border-radius: var(--border-radius);
146 | background: linear-gradient(
147 | to bottom right,
148 | rgba(var(--tile-start-rgb), 1),
149 | rgba(var(--tile-end-rgb), 1)
150 | );
151 | background-clip: content-box;
152 | }
153 |
154 | /* Enable hover only on non-touch devices */
155 | @media (hover: hover) and (pointer: fine) {
156 | .card:hover {
157 | background: rgba(var(--card-rgb), 0.1);
158 | border: 1px solid rgba(var(--card-border-rgb), 0.15);
159 | }
160 |
161 | .card:hover span {
162 | transform: translateX(4px);
163 | }
164 | }
165 |
166 | @media (prefers-reduced-motion) {
167 | .thirteen::before {
168 | animation: none;
169 | }
170 |
171 | .card:hover span {
172 | transform: none;
173 | }
174 | }
175 |
176 | /* Mobile */
177 | @media (max-width: 700px) {
178 | .content {
179 | padding: 4rem;
180 | }
181 |
182 | .grid {
183 | grid-template-columns: 1fr;
184 | margin-bottom: 120px;
185 | max-width: 320px;
186 | text-align: center;
187 | }
188 |
189 | .card {
190 | padding: 1rem 2.5rem;
191 | }
192 |
193 | .card h2 {
194 | margin-bottom: 0.5rem;
195 | }
196 |
197 | .center {
198 | padding: 8rem 0 6rem;
199 | }
200 |
201 | .center::before {
202 | transform: none;
203 | height: 300px;
204 | }
205 |
206 | .description {
207 | font-size: 0.8rem;
208 | }
209 |
210 | .description a {
211 | padding: 1rem;
212 | }
213 |
214 | .description p,
215 | .description div {
216 | display: flex;
217 | justify-content: center;
218 | position: fixed;
219 | width: 100%;
220 | }
221 |
222 | .description p {
223 | align-items: center;
224 | inset: 0 0 auto;
225 | padding: 2rem 1rem 1.4rem;
226 | border-radius: 0;
227 | border: none;
228 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
229 | background: linear-gradient(
230 | to bottom,
231 | rgba(var(--background-start-rgb), 1),
232 | rgba(var(--callout-rgb), 0.5)
233 | );
234 | background-clip: padding-box;
235 | backdrop-filter: blur(24px);
236 | }
237 |
238 | .description div {
239 | align-items: flex-end;
240 | pointer-events: none;
241 | inset: auto 0 0;
242 | padding: 2rem;
243 | height: 200px;
244 | background: linear-gradient(
245 | to bottom,
246 | transparent 0%,
247 | rgb(var(--background-end-rgb)) 40%
248 | );
249 | z-index: 1;
250 | }
251 | }
252 |
253 | /* Tablet and Smaller Desktop */
254 | @media (min-width: 701px) and (max-width: 1120px) {
255 | .grid {
256 | grid-template-columns: repeat(2, 50%);
257 | }
258 | }
259 |
260 | @media (prefers-color-scheme: dark) {
261 | .vercelLogo {
262 | filter: invert(1);
263 | }
264 |
265 | .logo,
266 | .thirteen img {
267 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
268 | }
269 | }
270 |
271 | @keyframes rotate {
272 | from {
273 | transform: rotate(360deg);
274 | }
275 | to {
276 | transform: rotate(0deg);
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/pages/container.tsx:
--------------------------------------------------------------------------------
1 | import { DarkContext } from "@/context/DarkContext";
2 | import { useState, useEffect, useRef, useContext } from "react";
3 |
4 | import Image from "next/image";
5 |
6 | import TypeWriter from "./typeWriter";
7 |
8 | const Container = () => {
9 |
10 | const { darkMode, toggleDarkMode }: any = useContext(DarkContext);
11 |
12 | const scrollContainer = useRef(null);
13 | const focus = useRef(null);
14 |
15 | const [messageText, setMessageText] = useState('');
16 |
17 | const [isLoading, setIsLoading] = useState(false);
18 |
19 | const [userChat, setUserChat] = useState([]);
20 | const [botChat, setBotChat] = useState([]);
21 |
22 |
23 | const botResponse = async () => {
24 | setIsLoading(true);
25 | const response = await fetch("/api/generate", {
26 | method: "POST",
27 | headers: {
28 | "Content-Type": "application/json",
29 | },
30 | body: JSON.stringify({
31 | messageText,
32 | }),
33 | });
34 | console.log("Edge function returned.");
35 |
36 | if (!response.ok) {
37 | throw new Error(response.statusText);
38 | }
39 |
40 | // This data is a ReadableStream
41 | const data = response.body;
42 | if (!data) {
43 | return;
44 | }
45 |
46 | const reader = data.getReader();
47 | const decoder = new TextDecoder();
48 | let done = false;
49 |
50 | let botReply = "";
51 |
52 | while (!done) {
53 | const { value, done: doneReading } = await reader.read();
54 | done = doneReading;
55 | const botMessage = decoder.decode(value);
56 | botReply += botMessage;
57 | }
58 | botReply += "\n";
59 | setBotChat([...botChat, botReply]);
60 | setIsLoading(false);
61 | }
62 |
63 | const handleScroll = (ref: any) => {
64 | ref.scrollTo({
65 | top: ref.scrollHeight,
66 | left: 0,
67 | behavior: "smooth",
68 | });
69 | };
70 |
71 |
72 |
73 | const sendMessage = () => {
74 | if (isLoading) return;
75 | if ((messageText.trim().length !== 0)) {
76 | botResponse();
77 | }
78 | setUserChat((messageText.trim().length === 0) ? userChat : [...userChat, messageText]);
79 | setMessageText("");
80 | }
81 |
82 | const handleEnterKey = (e: any) => {
83 | if (e.key === 'Enter' && !e.shiftKey) {
84 | sendMessage();
85 | }
86 | }
87 |
88 | useEffect(()=> {
89 | if(isLoading === false){
90 | focus?.current?.focus();
91 | }
92 | }, [isLoading])
93 |
94 |
95 | useEffect(() => {
96 | handleScroll(scrollContainer.current);
97 | }, [userChat, botChat])
98 |
99 |
100 |
101 | return (
102 |
103 |
104 |
ChatGPT
105 |
106 | {darkMode ? : }
107 |
108 |
109 |
110 |
111 | {userChat.map((ele, key) => {
112 | return (
113 |
114 |
117 | {botChat[key] &&
118 |
119 | {botChat[key].split("\n").map((ele: any, indkey: any) => {
120 | return
{ele}
121 | })}
122 |
}
123 |
124 | )
125 | })}
126 | {isLoading &&
}
127 |
128 |
129 |
130 | {isLoading ?
131 |
:
136 |
}
141 |
142 |
143 |
144 |
145 | )
146 | }
147 |
148 | export default Container;
--------------------------------------------------------------------------------
/public/assets/images/icons/message-send.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/images/icons/dark-moon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------