Chatbots have become increasingly popular over the years, and for a good reason. They offer businesses and individuals an easy way to communicate with their audience and customers, 24/7. With the advancements in Natural Language Processing (NLP) technology, building a chatbot that can hold human-like conversations has become easier than ever. In this ChatGPT API tutorial, we will guide you through building your own chatbot using ChatGPT API, NextJS, TailwindCSS, and DaisyUI. Not only that, but we also have a video tutorial that accompanies this article, providing a step-by-step guide for those who prefer visual aids.
Let’s get started!
Table of Contents
- Create Project & Install Dependencies
- Setup TailwindCSS and DaisyUI
- Setting up OpenAI
- OpenAI API Endpoint
- Chatbot Logic
- Conclusion
Note: The finished project can be cloned from GitHub.
Create Project & Install Dependencies
First, we create a new project using TypeScript. Leave everything during the setup process on default. You can call your application what you like; we call it openai-chatgpt
.
npx create-next-app@latest --typescript
cd openai-chatgpt
Code language: Bash (bash)
Then we need to install all of the dependencies:
npm install openai
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npm install daisyui
Code language: Bash (bash)
Setup TailwindCSS and DaisyUI
Open tailwind.config.js
and paste the code below:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [require("daisyui")],
}
Code language: JavaScript (javascript)
Open /styles/global.css
and paste the code below, overwriting everything in it:
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body {
padding: 0;
margin: 0;
width: 100%;
}
a {
text-decoration: none;
}
* {
box-sizing: border-box;
}
Code language: CSS (css)
You can delete all other files inside of the /styles/
folder so that only global.css
remains.
Setting up OpenAI
First, you need to create a OpenAI account. They offer a free trial so you can start testing right away. Once your account is created, navigate to API Keys and create a new secret key.
Note: Never share this key or secret with anyone.
Once the key is created, make sure to copy it.
Environment Variables
Create a new file in your root folder called .env.local and add a variable called OPENAI_API_KEY to it. Paste your OpenAI API key in quotes next to it like so:
OPENAI_API_KEY="sk-ZbuaBakBei3mfngn3n12BlbkFJCHAxmG3c************"
Code language: JavaScript (javascript)
OpenAI API Endpoint
Create a file called /pages/api/openAIChat.ts
and paste the following code:
import type { 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
) {
console.log("req.body", req.body.messages)
const completion = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: req.body.messages,
})
res.status(200).json({ result: completion.data })
}
Code language: TypeScript (typescript)
This is an API endpoint function that can be used with Next.js to create a chatbot. It uses the OpenAI API to generate responses to user messages in a chat interface.
The import
statements at the beginning of the code import necessary libraries for the code to work.
const configuration
creates a new OpenAI API configuration object that includes an API key, which is stored in the process.env.OPENAI_API_KEY
environment variable.
const openai
creates a new instance of the OpenAIApi class, which is used to interact with the OpenAI API.
The export default async function handler
is the actual API endpoint function that receives incoming requests and sends back responses. It takes in a NextApiRequest
object, which contains the request data, and a NextApiResponse
object, which is used to send the response back to the client.
Inside the function, console.log("req.body", req.body.messages)
logs the messages received in the request body.
const completion
is a variable that stores the result of the openai.createChatCompletion()
function, which sends the user’s message to the OpenAI API and generates a response. The model
parameter specifies the language model to use, in this case, “gpt-3.5-turbo”. The messages
parameter contains an array of messages in the chat interface.
Finally, res.status(200).json({ result: completion.data })
sends a JSON response to the client with the generated response from the OpenAI API. The completion.data
property contains the actual response text.
In summary, this API endpoint function uses the OpenAI API to generate responses to user messages in a chat interface and then sends those responses back to the client.
Chatbot Logic
Next, we are writing our chatbot logic. Open the index.tsx file and delete everything in it. Paste the code below, so we start with a clean slate:
import React, { useRef } from "react"
export default function Home() {
return (
<div className='w-full'>
<div className='flex flex-col items-center justify-center mt-40 text-center'>
<h1 className='text-6xl'>Hi there, I am AVA</h1>
</div>
</div>
)
}
Code language: TypeScript (typescript)
This is a React component that returns a simple HTML layout with a title, using the TailwindCSS library for styling.
The import
statement imports React
and the useRef
hook from the react
library.
The export default function Home()
function defines the React component.
The function returns a div with two nested divs. The outermost div has a w-full
class, which sets the width of the div to 100% of the parent container.
The innermost div has flex
, flex-col
, items-center
, justify-center
, mt-40
, and text-center
classes. These classes use the TailwindCSS utility classes to create a flexbox container that is vertically and horizontally centered on the page, with 40px of margin from the top.
The h1
element inside the innermost div has a text-6xl
class, which sets the font size to 6 times the default font size. The text inside the h1
element is “Hi there, I am AVA”, which is the title of the chatbot.
In summary, this code creates a simple React component that displays a title for the chatbot using TailwindCSS for styling.
States
Next, we need to create some states that we will use for our Chatbot:
...
export default function Home() {
// States
const [value, setValue] = React.useState<string>("")
const [conversation, setConversation] = React.useState<Conversation[]>([])
const inputRef = useRef<HTMLInputElement>(null)
...
Code language: TypeScript (typescript)
Input Handler
Next, we need to add a way to enter a prompt and handle the input:
import React, { useRef } from "react"
export default function Home() {
// States
const [value, setValue] = React.useState<string>("")
const [conversation, setConversation] = React.useState<Conversation[]>([])
const inputRef = useRef<HTMLInputElement>(null)
const handleInput = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
},
[]
)
return (
<div className='w-full'>
<div className='flex flex-col items-center justify-center mt-40 text-center'>
<h1 className='text-6xl'>Hi there, I am AVA</h1>
<div className='my-12'>
<p className='mb-6 font-bold'>Please type your prompt</p>
<input
placeholder='Type here'
className='w-full max-w-xs input input-bordered input-secondary'
value={value}
onChange={handleInput}
onKeyDown={handleKeyDown}
/>
</div>
)
}
Code language: TypeScript (typescript)
value
: This state is defined using theuseState
hook to keep track of the current value of the input field. The initial value is an empty string. ThesetValue
function is used to update the value state when the input field changes.conversation
: This state is also defined using theuseState
hook to keep track of the conversation history. It is an array of conversation objects, where each object contains a user message and the chatbot’s response. ThesetConversation
function is used to update the conversation state.inputRef
: This state is defined using theuseRef
hook to create a reference to the input field. This is useful for focusing the input field programmatically later on in the code.handleInput
: This function is defined using theuseCallback
hook to handle changes to the input field. It takes in an event parameter that represents the change event, and updates thevalue
state with the new value of the input field. This function is passed as theonChange
handler for the input field.- The
input
field: This field is defined with several properties, includingplaceholder
,className
,value
,onChange
, andonKeyDown
. Theplaceholder
property sets the text to display when the input field is empty. TheclassName
property sets the styling for the input field using TailwindCSS classes. Thevalue
property is set to the current value of the input field stored in thevalue
state. TheonChange
property is set to thehandleInput
function, which is called when the input field changes. TheonKeyDown
property is not defined yet, but it will be added later to handle submitting the user’s message to the chatbot.
In summary, the newly added code defines states for value
and conversation
, creates a reference to the input field using inputRef
, defines a handleInput
function to handle changes to the input field, and adds an input
field with several properties to capture the user’s prompt.
Chatbot Logic
Now we need to implement the chatbot logic:
import React, { useRef } from "react"
interface Conversation {
role: string
content: string
}
export default function Home() {
// States
const [value, setValue] = React.useState<string>("")
const [conversation, setConversation] = React.useState<Conversation[]>([])
const inputRef = useRef<HTMLInputElement>(null)
const handleInput = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
},
[]
)
const handleKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
const chatHistory = [...conversation, { role: "user", content: value }]
const response = await fetch("/api/openAIChat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ messages: chatHistory }),
})
const data = await response.json()
setValue("")
setConversation([
...chatHistory,
{ role: "assistant", content: data.result.choices[0].message.content },
])
}
}
return (
<div className='w-full'>
<div className='flex flex-col items-center justify-center mt-40 text-center'>
<h1 className='text-6xl'>Hi there, I am AVA</h1>
<div className='my-12'>
<p className='mb-6 font-bold'>Please type your prompt</p>
<input
placeholder='Type here'
className='w-full max-w-xs input input-bordered input-secondary'
value={value}
onChange={handleInput}
onKeyDown={handleKeyDown}
/>
</div>
<div className='textarea'>
{conversation.map((item, index) => (
<React.Fragment key={index}>
<br />
{item.role === "assistant" ? (
<div className='chat chat-end'>
<div className='chat-bubble chat-bubble-secondary'>
<strong className='badge badge-primary'>AVA</strong>
<br />
{item.content}
</div>
</div>
) : (
<div className='chat chat-start'>
<div className='chat-bubble chat-bubble-primary'>
<strong className='badge badge-secondary'>User</strong>
<br />
{item.content}
</div>
</div>
)}
</React.Fragment>
))}
</div>
</div>
</div>
)
}
Code language: TypeScript (typescript)
This newly added code is responsible for submitting the user’s prompt to the chatbot API, receiving a response, and updating the conversation history with the new message. Here’s an explanation of the new code:
interface Conversation
: This interface defines the shape of each conversation item in theconversation
state. It includes two properties:role
, which is a string that indicates whether the message is from the user or the chatbot, andcontent
, which is the text content of the message.handleKeyDown
: This function is called when the user presses a key while focused on the input field. It takes in an event parameter that represents the keyboard event. If the key pressed is “Enter”, the function sends a POST request to the/api/openAIChat
endpoint with the current chat history and the user’s message. The function then parses the response JSON data, resets the input field’s value, and updates theconversation
state with the chat history and the chatbot’s response.- The
textarea
div: This div contains the chat history, which is an array of conversation objects. Theconversation.map
function is used to iterate over each item in the conversation and render it as a chat bubble. Each bubble is either from the user or the chatbot, depending on therole
property of the conversation object. The chat bubbles are styled using TailwindCSS classes.
In summary, this code handles submitting the user’s prompt to the chatbot API, receiving a response, and updating the conversation history with the new message. The conversation history is displayed in the chat interface as a series of chat bubbles styled using TailwindCSS.
Reset Conversations
Finally, we want to be able to reset our dialogue and implement a button, therefore:
import React, { useRef } from "react"
interface Conversation {
role: string
content: string
}
export default function Home() {
// States
const [value, setValue] = React.useState<string>("")
const [conversation, setConversation] = React.useState<Conversation[]>([])
const inputRef = useRef<HTMLInputElement>(null)
const handleInput = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
},
[]
)
const handleKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
const chatHistory = [...conversation, { role: "user", content: value }]
const response = await fetch("/api/openAIChat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ messages: chatHistory }),
})
const data = await response.json()
setValue("")
setConversation([
...chatHistory,
{ role: "assistant", content: data.result.choices[0].message.content },
])
}
}
const handleRefresh = () => {
inputRef.current?.focus()
setValue("")
setConversation([])
}
return (
<div className='w-full'>
<div className='flex flex-col items-center justify-center mt-40 text-center'>
<h1 className='text-6xl'>Hi there, I am AVA</h1>
<div className='my-12'>
<p className='mb-6 font-bold'>Please type your prompt</p>
<input
placeholder='Type here'
className='w-full max-w-xs input input-bordered input-secondary'
value={value}
onChange={handleInput}
onKeyDown={handleKeyDown}
/>
<button
className='btn btn-primary btn-xs mt-6'
onClick={handleRefresh}
>
Start New Conversation
</button>
</div>
<div className='textarea'>
{conversation.map((item, index) => (
<React.Fragment key={index}>
<br />
{item.role === "assistant" ? (
<div className='chat chat-end'>
<div className='chat-bubble chat-bubble-secondary'>
<strong className='badge badge-primary'>AVA</strong>
<br />
{item.content}
</div>
</div>
) : (
<div className='chat chat-start'>
<div className='chat-bubble chat-bubble-primary'>
<strong className='badge badge-secondary'>User</strong>
<br />
{item.content}
</div>
</div>
)}
</React.Fragment>
))}
</div>
</div>
</div>
)
}
Code language: TypeScript (typescript)
This final addition adds a “Start New Conversation” button to the chat interface. When the button is clicked, the chat history and input field are reset to their initial values. Here’s an explanation of the new code:
handleRefresh
: This function is called when the “Start New Conversation” button is clicked. It uses theinputRef
reference to focus on the input field, sets thevalue
state to an empty string, and sets theconversation
state to an empty array.- The
button
: This button is added to the bottom of the input field using TailwindCSS classes. It has a “btn-primary” style and calls thehandleRefresh
function when clicked.
In summary, this final addition adds a “Start New Conversation” button to the chat interface, allowing the user to reset the chat history and input field to their initial values. The button is styled using TailwindCSS classes and calls the handleRefresh
function when clicked.
Conclusion
The article is a tutorial on how to build a chatbot using ChatGPT API, NextJS, TailwindCSS, and DaisyUI. It walks through the steps of setting up the NextJS app, configuring the ChatGPT API endpoint, creating a conversation history, styling the chat interface with TailwindCSS and DaisyUI, handling user input and sending it to the ChatGPT API, and displaying the chatbot’s response.
The tutorial also shows how to add a “Start New Conversation” button to the chat interface to allow the user to reset the conversation history and input field. Overall, the tutorial provides a comprehensive guide on how to build a functional chatbot using modern web development tools.