Welcome friends to Undernet Chat's repository!
Undernet Chat is GPT-powered a web application that enables you to talk with your Underground monster friends from Undertale. This repository encompasses both the front and back-end of the app. In less than 24 hours, this AI chatbot received >1,200 API Hits! And a couple of days ago it reached >1,000,000 token processed!
So I decided to make this app public and hope the awesum Undertale community can expand upon this love letter of mine to Undertale
Try it yourself here: https://underchat.vercel.app/
- Tech Stack
- Getting Started
- Project Structure Breakdown
- Help the Undernet Chat Grow!!
- Credits
- About Me & My Contact
- NextJS 14.0.4 (App Router)
- NodeJS v20.10.0
- OpenAI API v4.24.2
- Vercel AI SDK v2.2.31
- Vercel Analytics v1.1.1
- Vercel Speed Insights v1.0.3
- Tailwind CSS v3.3.0
- TypeScript
- ShadCN/UI
To launch this project all you need to do is just
- Clone this repository
https://github.com/audiprevio/underchat-public-v02.git
2. Install all the dependencies
npm install
or
yarn install
- Create a .env file and Add your own ChatGPT API key in the following format
OPENAI_API_KEY=your api key
- Launch it on your local (it will run on :3000 by default)
npm run dev
- Chat with your monster frens!
This project relies a lot on NextJS (13 and up), Open AI API, as well as the truly life saver Vercel AI SDK
If you want to know more about all those techs I suggest you watch this really helpful tutorial below, which I use to make Undernet Chat
Anyway on to the project structure:
src/app/api/
Hosts the APIs that powers all each of the characters' AI chat bot, part of the NextJS.
For instance in
/api/chatsans
you can find:
const systemMessage: ChatCompletionMessage = {
role: "assistant",
content:
"You are Sans from undertale, keep the witty-ness and maintainthe fun! Be Sans and the user is talking to you as the main character of the game. make dry jokes. and dont say nyeh heh heh because you are not papyrus, you are sans. add spaces on each line after a sentence. Limit your responses to 20 words or less. You always answer in lower case."
};
const response = await openai.chat.completions.create({
model: "gpt-3.5-turbo-1106",
stream: true,
messages: [systemMessage,...messages],
})
The systemMessage
essentially serves as the personality prompt for the character and the response
is basically the way to call the GPT model we want to use in processing the messages from user. You can change it to GPT-4 if you like to, but since I haven't got the access yet (despite already passing the $1 API limit), I still uses GPT 3.5. Also stream: true
enables the text streaming effect.
Yes, well, this is a problem
that I haven't been able to solve, yet. Since we're using a mixture of OpenAI API & Vercel AI SDK, I haven't been able to make a systemMessage
that's powered by a dynamic array of prompts that corresponds to each characters' personality.
Ideally there should be 1 single API with different prompts that can be switched on and off depending on the page you are in, but that would mean we need to modify the systemMessage using POST right? That's kinda tricky when I tried it, because the POST request is done in (read the section below), and it is handled by the useChat
hook created by Vercel in a manner that I haven't been able to figure out how to customize
src/app/
The page.tsx
in /
serves as the primary entry page for the app. In this page, basic information about the app and some disclaimers are given before the users enter
src/app/chat
Since UnderChat's UX needs to feel like you are using a CELLphone, I made this main contact list so users can browse the monster they want to talk to. The component that manages contact <ContactsComponent />
is also rendered here
const characters = [
{ name: "sans", image: sans, link: "/chat/sans" },
{ name: "Toriel", image: toriel, link: "/chat/toriel" },
{ name: "Alphys", image: alphys, link: "/chat/alphys" },
{ name: "Napstablook", image: napstablook, link: "/chat/napstablook" },
{ name: "METTATON ?", image: mettaton, link: "/chat/mettaton" },
{ name: "PAPYRUS", image: papyrus, link: "/chat/papyrus" },
{ name: "King Asgore", image: asgore, link: "/chat/asgore" },
{ name: "Undyne", image: undyne, link: "/chat/undyne" },
{ name: "Asriel", image: asriel, link: "/chat/asriel" },
{ name: "TEMMIE!!!", image: temmie, link: "/chat/temmie" },
];
export const ContactsComponent = () => {
return (
<div className=" h-[58vh] md:h-[65vh] px-4 md:px-6">
{characters.map((character) => (
<Link href={character.link} key={character.name}>
<Button className="hover:bg-transparent transition-none bg-transparent my-4 flex flex-row justify-start w-[75vw] md:w-[30vw] md:h-[5vw] h-[16vw] border-white border-2 rounded-none text-xl">
<Image src={character.image} alt={character.name} width={34.25} height={32.5} />
<div className="text-3xl pl-4">{character.name}</div>
</Button>
</Link>
))}
</div>
);
};
I've made the ContactsComponent easily expandable, to add more characters into the contact list you can just add the characters array.
src/app/chat/
The chatroom pages lies under that folder, each of the pages are named after the corresponding character /toriel
, /sans
The Chatroom page serves as the place that we use to inject the <ChatContainer />
with the following props:
characterName
characterImage
apiEndpoint
chatInputPlaceholder
so in /chat/undyne
the page.tsx
will pass
characterName="Undyne"
characterImage={Undyne}
apiEndpoint={apiEndpoint}
chatInputPlaceholder="Say something nice to Undyne?"
This is related to the structure of the Chat Room, read it below
This is basically just a short help and credits page
As you might read above each of the chatroom is filled with props from the page level. The main purpose of this is to create a dynamic chat room with different characters without having to create different <ChatContainer />
for each characters.
page.tsx
|
passes props to
|
<ChatContainer />
The ChatContainer
then generates the chatroom based on the corresponding character.
The component uses UseChat
hook by Vercel to POST user message and generate the AI message
const {
messages,
input,
handleInputChange,
handleSubmit,
setMessages,
isLoading,
error,
} = useChat({
api: apiEndpoint,
});
This is FAR easier when it comes to Front-end UI development. You can easily map out messages that's from the user and from the AI characters.
I follow Coding in Flow's UI styling, I highly recommend you watch the YouTube video to find out more:
Other components are stored in the
src/app/components/
since this project is using ShadCN the folder /components/ui
is generated by shadcn
in src/app/utils
I created some variables that can be reused throughout the project, namely the openai API key & tailwind class tweaking merge
Help more humans enjoy the awesome experience that is talking with monsters from the underground by expanding this project!
Create forks and features and everything you can to build on top of this app!
- BIG THANKS to Toby Fox for, well, creating Undertale
- Coding in Flow a.k.a Florian Walther for the awesome NextJS 14, Vercel AI SDK, and ChatGPT tutorial
I love undertale, I worked in the field of UX Research & Design for >2 years and now currently diving deeper into tech by exploring Front-end
Get to know more about me and my works here: (audiprevio.dev)[https://audiprevio.dev]
Thanks for reading and happy hacking! Nyeh heh heh heh!