A customizable chatgpt chatbot component library for Nextjs. Built with React, Tailwindcss, OpenAI, and Typescript.
Installation Instructions
Configuration Instructions
Themes
Endpoints
Function Calling
Usage
Customizations
Create Chattr App
License
Contributing
Future Development
Author
Sponsors
npm i chattr@latest
Before using chattr
, we need to configure a few things. First, ensure that you are on the latest versions of react, react-dom, and tailwindcss
. Feel free to try other versions, however do note that they have not been tested. Make sure tailwindcss is in dependencies and not devDependencies
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.3.6"
If you aren't on the latest versions, start a new branch. Then update your dependencies on that branch:
npm i react@latest react-dom@latest tailwindcss@latest @types/react@latest @types/react-dom@latest
Next, make sure that your tailwind.config.ts
file includes the following:
// tailwind.config.ts
{
//... Other configs ...,
theme: {
extend: {
colors: {
chattrWhite: 'rgb(255 255 255 / 1)', // white
chattrBlack: 'rgb(24 24 27 / 1)', // zinc-900
chattrPitchBlack: 'rgb(9 9 11 / 1)', // zinc-950
chattrPrimary: 'rgb(139 92 246 / 1)', // violet-500
chattrPrimaryDark: 'rgb(124 58 237 / 1)', // violet-600
chattrSecondary: 'rgb(113 113 122 / 0.8)', // zinc-500/80
chattrSecondaryDark: 'rgb(244 244 245 / 0.6)', //zinc-100/60
chattrGray: 'rgb(212 212 216 / 0.9)', // zinc-300/90
chattrGrayDark: 'rgb(244 244 245 / 0.15)', //zinc-100/15
chattrText: 'rgb(39 39 42 / 1)', // zinc-800
chattrTextDark: 'rgb(244 244 245 / 1)', // zinc-100
chattrBackgroundMuted: 'rgb(228 228 231 / 0.7)', //zinc-200/70
},
borderRadius: {
chattrRoundedSmall: '0.5rem',
chattrRoundedMedium: '0.85rem',
chattrRoundedLarge: '1rem',
},
animation: {
chattrLoader: 'chattrLoader 0.5s infinite alternate',
},
keyframes: {
chattrLoader: {
from: {
opacity: '1',
transform: 'translate3d(0, 0, 0)',
},
to: {
opacity: '0.25',
transform: 'translate3d(0, -0.2rem, 0)',
},
},
},
},
},
plugins: [],
}
export default config
And your globals.css
file looks like this:
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Animation styles for the ChattrLoader components */
@layer utilities {
.animation-delay-200 {
animation-delay: 0.15s;
}
.animation-delay-400 {
animation-delay: 0.3s;
}
}
/* Dot styles for the ChattrLoader components */
.chattrDotDefault {
@apply bg-chattrWhite mx-0.5 h-[6px] w-[6px] rounded-full;
}
.chattrDotMinimalist {
@apply bg-chattrSecondary dark:bg-chattrSecondaryDark mx-0.5 h-[6px] w-[6px] rounded-full;
}
This is for the overall chattr styles, and a custom loader that comes shipped with the chatbot in between states of sent messages. You can customize it, or create your own!
Next, you need an OPENAI_API_KEY
. If you don't have one already, click here to get one.
Once you have your key, install dotenv
if required and create a .env
file in the root of your project. Insert your api key there, along with any other api keys if you plan on using function calling. In production, remember to copy your api key, to your environment variables section.
OPENAI_API_KEY='YOUR_OPENAI_API_KEY'
WEATHER_APP_ID='YOUR_OPENWEATHERMAPS_API_KEY'
REPLICATE_API_TOKEN='YOUR_REPLICATE_TOKEN'
Chattr currently has two themes- Default
and Minimalist
. The default theme was styled by me, and the minimalist theme was styled originally by shadcn and has been highly customized to be used as a chattrbot. Shadcn himself even reposted the tweet of his chat component being used in chattr!
To see how the themes work, please visit the chattr repo
In order to use chattr, you have to create an endpoint that handles a post request to the chatGpt completions api.
If you're using the Default.Chattrbot
, you can copy and paste this route to app/api/chat-gpt/route.ts
as a starting point:
// app/api/chat-gpt/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function POST(req: NextRequest) {
try {
const {
prompt,
chattrBotName,
chattrBotHistory,
}: {
prompt: string
chattrBotName: string | number
chattrBotHistory: string
} = await req.json()
const chatHistory = JSON.stringify(chattrBotHistory)
const payload = {
model: 'gpt-4-1106-preview',
messages: [
{
role: 'system',
content: `
You are a chatbot named ${chattrBotName}.
Respond with any information that the user requests.
You can view the entire chat history here, where your role is the assistant, and the users role is user: ${chatHistory}.
This history is helpful if you need to recall any information or understand context from chat.
Use a professional tone in your responses.`,
},
{
role: 'assistant',
content: `Hey! Thanks for visiting. I'm ${chattrBotName}, you can ask me anything!`, // Replace with your own greeting
},
{
role: 'user',
content: prompt, // The users prompt
},
],
temperature: 0.7, // Your configs
frequency_penalty: 0,
presence_penalty: 0,
max_tokens: 75,
n: 1,
}
const response: Response = await fetch(
'https://api.openai.com/v1/chat/completions',
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
method: 'POST',
body: JSON.stringify(payload),
}
)
if (!response.ok) {
return NextResponse.json({
ok: false,
error:
'Looks like something went wrong fetching that answer! Try again later.',
})
}
const completion = await response.json()
return NextResponse.json({
ok: true,
content: { text: completion.choices[0].message.content },
})
} catch (error) {
console.log(error)
return NextResponse.json({
ok: false,
error: 'Looks like something went wrong. Try again later.',
})
}
}
You can view the default chattrbot and components here to understand how it works with the app/api/chat-gpt/route.ts
route.
If you're using the Minimalist.Chattrbot
, the route is a lot different in that it uses open ai's function calling feature.
A solution for the ui that I came up with is using a key value pair within the response object to tell the client what type of component to render. You can see it in action here.
You can copy and paste the following as a starting point to app/api/function-calling/route.ts
. Make sure to install replicate
and dayjs
if you'd like to use the create_video
and get_current_weather
functions in this example.
// app/api/function-calling/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { get_current_weather, create_image, create_video } from 'chattr'
export async function POST(req: NextRequest) {
try {
const {
prompt,
chattrBotName,
chattrBotHistory,
}: {
prompt: string
chattrBotName: string | number
chattrBotHistory: string
} = await req.json()
const chatHistory = JSON.stringify(chattrBotHistory)
const payload = {
model: 'gpt-4-1106-preview',
messages: [
{
role: 'system',
content: `
You are a chatbot named ${chattrBotName}.
Respond with any information that the user requests.
You can view the entire chat history here, where your role is the assistant, and the users role is user: ${chatHistory}.
This history is helpful if you need to recall any information or understand context from chat.
Use a professional tone in your responses.`,
},
{
role: 'assistant',
content: `Hey! Thanks for visiting. I'm ${chattrBotName}, you can ask me anything!`,
},
{
role: 'user',
content: prompt,
},
],
functions: [
// Define your functions see more at https://platform.openai.com/docs/guides/function-calling
{
name: 'get_current_weather',
description: 'Get the current weather',
parameters: {
type: 'object',
properties: {
zipcode: {
type: 'string',
description:
'The zipcode of the city. For example, 90210 for Beverly Hills. If the user passes in a city, retrieve any zip code for that city and use it as the zipcode value.',
},
state: {
type: 'string',
description:
'The state of the city. For example: CA if the user asks for the weather in Beverly Hills, or UT if the user asks for the weather in Salt Lake City, etc. If the user passes a zip code or a city name as a zip code, retrieve the state that belongs to that zip code and use it as the state value.',
},
},
required: ['zipcode', 'state'],
},
},
{
name: 'create_image',
description: 'Create an image for the given description',
parameters: {
type: 'object',
properties: {
description: {
type: 'string',
description: 'Description of what the image should be.',
},
},
required: ['description'],
},
},
{
name: 'create_video',
description: 'Create a video for a given description',
parameters: {
type: 'object',
properties: {
description: {
type: 'string',
description: 'Description of what the video should be.',
},
},
required: ['description'],
},
},
],
function_call: 'auto', // the completions api will automatically call the functions for you
temperature: 0.7,
frequency_penalty: 0,
presence_penalty: 0,
max_tokens: 75,
n: 1,
}
const response: Response = await fetch(
'https://api.openai.com/v1/chat/completions',
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
method: 'POST',
body: JSON.stringify(payload),
}
)
const completion = await response.json()
if (completion.choices[0].message.content === null) {
// If the content is null, that means it's a function call
const args = JSON.parse(
completion.choices[0].message.function_call.arguments
)
const functionCall = completion.choices[0].message.function_call.name
if (functionCall === 'get_current_weather') {
const {
temperature,
celcius,
location,
url,
description,
humidity,
wind,
clouds,
state,
} = await get_current_weather(args.zipcode, args.state)
return NextResponse.json({
ok: true,
ui: 'weather',
content: {
function_response: {
temperature: temperature,
celcius: celcius,
location: location,
url: url,
description: description,
humidity: humidity,
wind: wind,
clouds: clouds,
state: state,
},
},
})
} else if (functionCall === 'create_image') {
const { description, url } = await create_image(args.description)
return NextResponse.json({
ok: true,
ui: 'image',
content: {
function_response: {
description: description,
url: url,
},
},
})
} else if (functionCall === 'create_video') {
const { description, url } = await create_video(args.description)
return NextResponse.json({
ok: true,
ui: 'video',
content: {
function_response: {
description: description,
url: url,
},
},
})
} else {
return NextResponse.json({
ok: false,
error:
'Looks like something went wrong while generating that. Please try again! If the problem persists, let us know at hello@example.com.',
})
}
} else {
return NextResponse.json({
ok: true,
content: { text: completion.choices[0].message.content },
})
}
} catch (error) {
console.log(error)
return NextResponse.json({ ok: false, error: JSON.stringify(error) })
}
}
You can view the minimalist chattrbot here to understand how it works with the app/api/function-calling/route.ts
route.
It's worth mentioning that you should protect your routes with some type of authentication, or at the very least, use a rate limiter. @upstash/ratelimit @upstash/redis
is a great option. You can view the package here.
After you have setup the route you need, you can import a chattrbot! Just wrap it in a separate component with the use client
directive:
// components/chattr-example.tsx
'use client'
import { Default } from 'chattr'
//import { Minimalist } from 'chattr for function calling'
export default function ChattrExample() {
return <Default.Chattrbot />
//return <Minimalist.Chattrbot/>
}
Then import it wherever you'd like. For example, in your layout component:
// app/layout.tsx
import {
ThemeProvider,
Background,
Navigation,
Footer,
ChattrExample,
} from '@/components'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html
lang='en'
suppressHydrationWarning>
<body>
<ThemeProvider attribute='class'>
<Navigation />
<Background />
<main>{children}</main>
<Footer />
<ChattrExample />
</ThemeProvider>
</body>
</html>
)
}
If you need more control over the styles, you can view the chattr repo here. Copy existing code, create new bots, expand on existing bots, etc. The code is open source and yours to use!
If you need full customization over the components themselves, checkout the newly shipped create-chattr-app! It is a Next js boilerplate that ships with all chatbot component files, routes, and a landing page already setup for you. All you need is your api keys!
This project is covered under the MIT license.
If you would like to contribute, feel free to open a pull request! Experienced library developers would be awesome as this is my first real library.
More themes are coming, such as full screen layout instead of a widget. If you have any ideas, please let me know about them!
Made with <3 by Christian B. Martinez. Lets connect on Github or X (twitter)!
Hiring? I would love the opportunity to become apart of your team! contact me anytime.
If you like the project and it adds value to you, feel free to sponsor me if you'd like!