Creating ChatGPT using NextJS, TailwindCSS and OpenAI's text-davinci-003
Using NextJS 13's App Directory.
In this tutorial, we'll walk through the process of creating a chatbot using NextJS, TailwindCSS, and OpenAI's text-davinci-003 model. For this tutorial, we shall be using NextJS 13's new app directory. I have provided a link to the repository on GitHub for reference. The finished App would look like this.
Prerequisites
To follow along with this tutorial, you'll need the following:
Basic knowledge of NextJS and TailwindCSS.
Node.js and yarn(or any package manager for Node.js) installed on your machine.
A text editor or integrated development environment (IDE).
An OpenAI API key (you can sign up for one here or if you are already signed in you may directly proceed to this link).
Step 1: Set up a NextJS Project
First, let's set up a NextJS project. To do this, you'll need to install the NextJS command line interface (CLI) by running the following command using any package manager(yarn, pnpm or npm):
yarn create next-app my-project --typescript --eslint --experimental-app
Once the NextJS CLI is installed, navigate to the directory where you want to create your project and run the following command:
cd ./my-project
Replace "my-project" with the name you want to give your project
Step 2: Install and configure TailwindCSS
Install Tailwind CSS
Install
tailwindcss
and its peer dependencies via yarn, and then run the init command to generate bothtailwind.config.js
andpostcss.config.js
.yarn add -D tailwindcss postcss autoprefixernpx tailwindcss init -p
Configure your template paths
Add the paths to all of your template files in your
tailwind.config.js
file.Do remember to add
'./app/**/*.{js,ts,jsx,tsx}'
path in yourtailwind.config.js
since we are using the app directory which isn't auto-configured./** @type {import('tailwindcss').Config} */ module.exports = { content: [ './app/**/*.{js,ts,jsx,tsx}', './pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', ], theme: { extend: {}, }, plugins: [], };
Add the Tailwind directives to your CSS
Add the
@tailwind
directives for each of Tailwind’s layers to your/app/globals.css
file.@tailwind base; @tailwind components; @tailwind utilities;
Start your build process
Run your build process with
yarn run dev
.yarn run dev
Start using Tailwind in your project
Start using Tailwind’s utility classes to style your content in
/app/page.tsx
export default function Home() { return ( <main> <Form /> </main> ); }
Step 3: Set up the text-davinci-003 model
To use the text-davinci-003 model, you'll need to sign up for an OpenAI API key. Once you have an API key, you can use the
openai
package to interact with the model.Go to Model and select text-davinci-003 for better overall response or you may use code-davinci-003 for code specific responses.
Go to view code and select Node.js
Now go to this link to copy or create your OpenAI's API key
Create .env.local file in the root of your folder and paste your API key there as follows
OPENAI_API_KEY='YOUR OPENAI API KEY'
Now install the
openai
package by running the following command:yarn add openai
Now create response.ts in your
pages/api
and import theopenai
module in yourpage/api/response.ts
file and configure your response handler as follows:import { NextApiRequest, NextApiResponse } from 'next' import { Configuration, OpenAIApi } from 'openai' const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY, }); const openai = new OpenAIApi(configuration); export default async function handler (req: NextApiRequest, res: NextApiResponse) { try { const prompt = req.body.prompt; const response = await openai.createCompletion({ model: "text-davinci-003", prompt: `${prompt}`, temperature: 0.7, max_tokens: 3000, top_p: 1, frequency_penalty: 0.5, presence_penalty: 0, }); res.status(200).json({ bot: response.data.choices[0].text }); } catch (error) { console.error(error) res.status(500).json({ message: error || 'Something went wrong' }); } }
Step 4: Create Form Component to store data from the user.
Use useState hook to store data from the user in response.
We would be using axios for making fetch requests from text-davinci-003
Don't forget to use 'use client' on the top of Form Component, since all components in NextJS 13 app directory by default are server components.
'use client';
import { useEffect, useRef, useState } from 'react';
import axios from 'axios';
const Form = () => {
const messageInput = useRef<HTMLTextAreaElement | null>(null);
const [response, setResponse] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleEnter = (
e: React.KeyboardEvent<HTMLTextAreaElement> &
React.FormEvent<HTMLFormElement>
) => {
if (e.key === 'Enter' && isLoading === false) {
e.preventDefault();
setIsLoading(true);
handleSubmit(e);
}
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const message = messageInput.current?.value;
if (message !== undefined) {
const initialResponse: string[] = [...response, message];
setResponse(initialResponse);
messageInput.current!.value = '';
}
if (!message) {
return;
}
const { data } = await axios.post('/api/response', { prompt: message });
const totalResponse: string[] = [...response, message, data.bot];
setResponse(totalResponse);
localStorage.setItem('response', JSON.stringify(totalResponse));
setIsLoading(false);
messageInput.current!.value = '';
};
const handleReset = () => {
localStorage.removeItem('response');
setResponse([]);
};
useEffect(() => {
const storedResponse = localStorage.getItem('response');
if (storedResponse) {
setResponse(JSON.parse(storedResponse));
}
}, []);
return (
<div className='flex justify-center'>
<button
onClick={handleReset}
type='reset'
className='fixed top-[1.4rem] right-5 p-4 rounded-md bg-white text-gray-500 dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent'
>
Clear History
</button>
<div className='w-full mx-2 flex flex-col items-start gap-3 pt-6 last:mb-6 md:mx-auto md:max-w-3xl'>
{isLoading
? response.map((item: any, index: number) => {
return (
<div
key={index}
className={`${
index % 2 === 0 ? 'bg-blue-500' : 'bg-gray-500'
} p-3 rounded-lg`}
>
<p>{item}</p>
</div>
);
})
: response
? response.map((item: string, index: number) => {
return (
<div
key={index}
className={`${
index % 2 === 0 ? 'bg-blue-500' : 'bg-gray-500'
} p-3 rounded-lg`}
// style={{ textAlign: index % 2 === 0 ? 'left' : 'right' }}
>
<p>{item}</p>
</div>
);
})
: null}
</div>
<form
onSubmit={handleSubmit}
className='fixed bottom-0 w-full md:max-w-3xl bg-gray-700 rounded-md shadow-[0_0_10px_rgba(0,0,0,0.10)] mb-4'
>
<textarea
name='Message'
placeholder='Type your query'
ref={messageInput}
onKeyDown={handleEnter}
className='w-full resize-none bg-transparent outline-none pt-4 pl-4 translate-y-1'
/>
<button
disabled={isLoading}
type='submit'
className='absolute top-[1.4rem] right-5 p-1 rounded-md text-gray-500 dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent'
>
<svg
stroke='currentColor'
fill='currentColor'
strokeWidth='0'
viewBox='0 0 20 20'
className='h-4 w-4 rotate-90'
height='1em'
width='1em'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z'></path>
</svg>
</button>
</form>
</div>
);
};
export default Form;
And that’s it for the code section of the frontend part of this tutorial. If you have come this far, I must say you are doing great!
Enough for all this technical stuff, in the next section, we would be seeing a demo of the finished app.
Testing the finished App
The finished app looks like this: https://chatgpt.shivanshu.in/. The code for the project is also available on GitHub for your reference.