Creating ChatGPT using NextJS, TailwindCSS and OpenAI's text-davinci-003

Creating ChatGPT using NextJS, TailwindCSS and OpenAI's text-davinci-003

Using NextJS 13's App Directory.

·

5 min read

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

  1. Install Tailwind CSS

    Install tailwindcss and its peer dependencies via yarn, and then run the init command to generate both tailwind.config.js and postcss.config.js.

     yarn add -D tailwindcss postcss autoprefixernpx tailwindcss init -p
    
  2. 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 your tailwind.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: [],
     };
    
  3. 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;
    
  4. Start your build process

    Run your build process with yarn run dev.

     yarn run dev
    
  5. 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

  1. 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.

  2. Go to Model and select text-davinci-003 for better overall response or you may use code-davinci-003 for code specific responses.

  3. Go to view code and select Node.js

  4. Now go to this link to copy or create your OpenAI's API key

  5. 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'
    
  6. Now install the openai package by running the following command:

      yarn add openai
    
  7. Now create response.ts in your pages/api and import the openai module in your page/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.

  1. Use useState hook to store data from the user in response.

  2. We would be using axios for making fetch requests from text-davinci-003

  3. 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.