├── .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 | 7 | 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 ? tool : tool} 107 |
108 |
109 |
110 |
111 | {userChat.map((ele, key) => { 112 | return ( 113 |
114 |
115 |
{ele}
116 |
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 |