├── .env.example ├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── app ├── api │ ├── hello │ │ └── route.ts │ ├── models │ │ └── route.ts │ └── response-not-working-with-streaming │ │ └── route.ts ├── favicon.ico ├── globals.css ├── layout.tsx └── page.tsx ├── components └── Form.tsx ├── next.config.js ├── package.json ├── pages └── api │ └── response.ts ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── next.svg ├── thirteen.svg └── vercel.svg ├── tailwind.config.js ├── tsconfig.json └── utils └── OpenAIStream.ts /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY='YOUR OPENAI API KEY' 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | .vercel 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 shivanshubisht 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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 `app/page.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. 39 | -------------------------------------------------------------------------------- /app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET(request: Request) { 2 | return new Response('Hello, Next.js!') 3 | } 4 | -------------------------------------------------------------------------------- /app/api/models/route.ts: -------------------------------------------------------------------------------- 1 | import { Configuration, OpenAIApi } from 'openai' 2 | 3 | const configuration = new Configuration({ 4 | apiKey: process.env.OPENAI_API_KEY, 5 | }) 6 | 7 | const openai = new OpenAIApi(configuration) 8 | 9 | export async function GET(request: Request) { 10 | const response = await openai.listModels() 11 | return new Response(JSON.stringify(response.data)) 12 | } 13 | -------------------------------------------------------------------------------- /app/api/response-not-working-with-streaming/route.ts: -------------------------------------------------------------------------------- 1 | import { OpenAIStream, OpenAIStreamPayload } from '@/utils/OpenAIStream' 2 | 3 | type RequestData = { 4 | currentModel: string 5 | message: string 6 | } 7 | 8 | if (!process.env.OPENAI_API_KEY) { 9 | throw new Error('Missing env var from OpenAI') 10 | } 11 | 12 | export const runtime = 'edge' 13 | 14 | export async function POST(request: Request) { 15 | const { currentModel, message } = (await request.json()) as RequestData 16 | 17 | if (!message) { 18 | return new Response('No message in the request', { status: 400 }) 19 | } 20 | 21 | const payload: OpenAIStreamPayload = { 22 | model: 'gpt-3.5-turbo', 23 | // model: `${currentModel}`, 24 | messages: [{ role: 'user', content: message }], 25 | temperature: 0.7, 26 | top_p: 1, 27 | frequency_penalty: 0, 28 | presence_penalty: 0, 29 | max_tokens: 2048, 30 | stream: true, 31 | n: 1, 32 | } 33 | 34 | const stream = await OpenAIStream(payload) 35 | return new Response(stream) 36 | } 37 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Risk-taker0501/ChatGPT_Nextjs/ef2df775e5d096738d39da058a950cbf08c927c2/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | @media (prefers-color-scheme: dark) { 10 | html { 11 | color-scheme: dark; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | 3 | export const metadata = { 4 | title: 'ChatGPT', 5 | description: 'A chatbot powered by GPT-3', 6 | } 7 | 8 | export default function RootLayout({ 9 | children, 10 | }: { 11 | children: React.ReactNode 12 | }) { 13 | return ( 14 | 15 | {children} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Inter } from 'next/font/google'; 2 | import Form from '@/components/Form'; 3 | 4 | const inter = Inter({ subsets: ['latin'] }); 5 | 6 | export default function Home() { 7 | return ( 8 |
9 |
10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /components/Form.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { useRef, useState } from 'react' 3 | import useSWR from 'swr' 4 | 5 | interface ModelType { 6 | object: 'engine' 7 | id: string 8 | ready: boolean 9 | owner: string 10 | permissions: null 11 | created: string 12 | } 13 | 14 | const Form = () => { 15 | const messageInput = useRef(null) 16 | const [response, setResponse] = useState([]) 17 | const [isLoading, setIsLoading] = useState(false) 18 | const [models, setModels] = useState([]) 19 | const [currentModel, setCurrentModel] = useState('gpt-3.5-turbo') 20 | 21 | const handleEnter = ( 22 | e: React.KeyboardEvent & 23 | React.FormEvent 24 | ) => { 25 | if (e.key === 'Enter' && isLoading === false) { 26 | e.preventDefault() 27 | setIsLoading(true) 28 | handleSubmit(e) 29 | } 30 | } 31 | 32 | const handleSubmit = async (e: React.FormEvent) => { 33 | e.preventDefault() 34 | const message = messageInput.current?.value 35 | if (message !== undefined) { 36 | setResponse((prev) => [...prev, message]) 37 | messageInput.current!.value = '' 38 | } 39 | 40 | if (!message) { 41 | return 42 | } 43 | 44 | const response = await fetch('/api/response', { 45 | method: 'POST', 46 | headers: { 47 | 'Content-Type': 'application/json', 48 | }, 49 | body: JSON.stringify({ 50 | message, 51 | currentModel, 52 | }), 53 | }) 54 | console.log('Edge function returned.') 55 | 56 | console.log(response) 57 | 58 | if (!response.ok) { 59 | throw new Error(response.statusText) 60 | } 61 | 62 | const data = response.body 63 | if (!data) { 64 | return 65 | } 66 | 67 | const reader = data.getReader() 68 | const decoder = new TextDecoder() 69 | let done = false 70 | 71 | setResponse((prev) => [...prev, message]) 72 | 73 | let currentResponse: string[] = [] 74 | while (!done) { 75 | const { value, done: doneReading } = await reader.read() 76 | done = doneReading 77 | const chunkValue = decoder.decode(value) 78 | // currentResponse = [...currentResponse, message, chunkValue]; 79 | currentResponse = [...currentResponse, chunkValue] 80 | setResponse((prev) => [...prev.slice(0, -1), currentResponse.join('')]) 81 | } 82 | // breaks text indent on refresh due to streaming 83 | // localStorage.setItem('response', JSON.stringify(currentResponse)); 84 | setIsLoading(false) 85 | } 86 | 87 | const handleReset = () => { 88 | localStorage.removeItem('response') 89 | setResponse([]) 90 | } 91 | 92 | useSWR('fetchingResponse', async () => { 93 | const storedResponse = localStorage.getItem('response') 94 | if (storedResponse) { 95 | setResponse(JSON.parse(storedResponse)) 96 | } 97 | }) 98 | 99 | const fetcher = async () => { 100 | const models = await (await fetch('/api/models')).json() 101 | setModels(models.data) 102 | const modelIndex = models.data.findIndex( 103 | (model: ModelType) => model.id === 'gpt-3.5-turbo' 104 | ) 105 | setCurrentModel(models.data[modelIndex].id) 106 | return models 107 | } 108 | 109 | useSWR('fetchingModels', fetcher) 110 | 111 | const handleModelChange = (e: React.ChangeEvent) => { 112 | setCurrentModel(e.target.value) 113 | } 114 | 115 | return ( 116 |
117 | 128 | 129 | 136 |
137 | {isLoading 138 | ? response.map((item: any, index: number) => { 139 | return ( 140 |
146 |

{item}

147 |
148 | ) 149 | }) 150 | : response 151 | ? response.map((item: string, index: number) => { 152 | return ( 153 |
159 |

{item}

160 |
161 | ) 162 | }) 163 | : null} 164 |
165 | 169 |