Build AI Template Engine to create amazing content with Next.js and ChatGPT
Tags: nextjs, chatgpt, javascript, react
Published: 2023-03-20 11:01:09

## TL;DR
Do you want to master ChatGPT API and build **your own** generative AI template engine for content creation? 
I will show you exactly how I have built and open sourced [](https://jema.ai), a jasper.ai clone for creating SEO, blog posts, marketing and basically any content that you will ever need to generate, with AI 🚀


I have launched it on [ProductHunt](https://www.producthunt.com/posts/jema-ai) - go ahead and [visit me](https://www.producthunt.com/posts/jema-ai).


You can check [this demo](https://jema.ai) already, it is super cool. And then, let me show you how to build one for yourself (or you can clone my [open source code](https://github.com/yuvalsuede/jasper-alternative-gpt). 

![Jeam.ai](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExY2E5NzNmZTNkYTI3MTZkNzIxZTdjMDg5OTVlMzVhMGY1YmNlODZkMSZjdD1n/Pm8cTtWy4kd7OO94n2/giphy.gif)

## About the technologies
We are going to use [Next.js](https://nextjs.org/) as our framework and TailwindCSS for styling. I am also going to use Next.js in its TypeScript version. If TS is not your cup of tea, don't worry, it is really straight forward. After so many years of being a web developer, I definitely find Next.js as the fastest go-to-market framework and really one of the best.

For our AI completion model (well, actually, chat model) we are going to use ChatGPT-3.5 turbo Rest API by [OpenAI](https://platform.openai.com/docs/guides/chat/introduction) which was released just recently, and is a real superpower.


## [Jema.ai](https://jema.ai) - the first open-source content creator platform.

About myself. I am a full stack developer with over 17 years of experience. My drive for life is building game-changing solutions. As an entrepreneur at heart , I ❤️ building end-to-end systems that not only look amazing and feel state-of-the-art, but also have real meaning and impact.

I've been working on a generative AI project for the past 7 months, developing impressive platforms for both content and image creation. When the ChatGPT API was released, I began constructing a Jasper.ai alternative capable of generating any type of content in mere seconds. The results were so outstanding that I couldn't resist open-sourcing the project and sharing it with the community.

I would be very grateful if you can help me out by starring the library ❤️‍🔥❤️‍🔥❤️‍🔥
https://github.com/yuvalsuede/jasper-alternative-gpt


## Let's start

Assuming that you have [Next.js](https://nextjs.org/docs/getting-started) project already installed, make sure you have added [Tailwind CSS](https://tailwindcss.com/docs/installation) to the project. 

A basic project structure will look somewhat like this one:
![Directories](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xuddr14chu8jgtosi8x.png)


The first thing I usually do is adding a `Layout` component. This will later facilitate adding more pages to our project. 
For Jema.ai I used the following layout:

```javascript
const Layout: React.FC<Props> = ({ children, title }) => {
    return (
        <Fragment>
            <div className="min-h-screen relative w-full md:flex md:flex-row">
                <div className="md:hidden z-10 fixed left-0 top-0 h-full">
                    <Sidebar items={SIDEBAR_ITEMS} />
                </div>
                <div className="hidden md:block md:relative ">
                    <Sidebar items={SIDEBAR_ITEMS} />
                </div>
                <main className="w-full md:flex-grow">
                    { title && <h1 className="text-black text-2xl font-bold mb-4 mt-10 pr-4 pl-4 pt-4">{title}</h1> }
                    {children}
                </main>
            </div>
        </Fragment>
    );
};

export default Layout;
```javascript

`SIDEBAR_ITEMS` is the list of our menu items, I have used the following, you can add your own of course. 
```
export const SIDEBAR_ITEMS: any = [
    {
        label: "Templates",
        url: "/",
    },
    {
        label: "Contact",
        url: "",
        target: "blank"
    },
];

```

For the sidebar create the following component

```javascript
const Sidebar: React.FC<Props> = ({items}) => {
    const [isOpen, setIsOpen] = useState(false);
    const router = useRouter();

    const handleClick = () => {
        router.push('/');
    };
    const isActive = (url: string): boolean => {
        return router.pathname === url;
    };
    const toggleSidebar = () => {
        setIsOpen(!isOpen);
    };

    return (
        <>
            <button
                className="
                z-50
                w-10 h-10 fixed top-4 left-4 z-10 md:hidden bg-white border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-primary"
                onClick={toggleSidebar}
            >
                <i className={`fas fa-${isOpen ? 'times' : 'bars'} text-primary`}/>
            </button>
            <aside
                className={`bg-white min-h-screen min-h-screen flex flex-col border-r border-gray-200 transition-transform duration-300 ease-in-out transform ${
                    isOpen ? 'translate-x-0' : '-translate-x-full'
                } md:translate-x-0 md:static fixed top-0 left-0 h-full md:min-h-0 md:relative md:w-60 overflow-y-hidden`}
            >

                <div className="w-60 flex flex-col items-center justify-center p-4 hover:cursor-pointer"
                     onClick={handleClick}>
                    <div className="flex flex-col">
                        <Image src={"/images/Jemaai-logo.png"} alt="Jema.ai" width="250" height="100"/>
                        <h1 className="text-lg font-normal text-gray-700 text-center">Open Source </h1>
                        <h1 className="text-lg font-semibold text-gray-700  text-center">Jasper <i
                            className="fas fa-arrow-right text-primary"/> alternative</h1>


                    </div>
                </div>
                <nav className="flex-1">
                    <ul className="py-4">
                        <li className="mb-10">
                            <div className="flex flex-row align-middle justify-center">
                                <a
                                    className="flex max-w-fit items-center justify-center space-x-2 rounded-full border border-gray-300 bg-white px-4 py-2 text-sm text-gray-600 shadow-md transition-colors hover:bg-gray-100 mt-5 animate-wobble"
                                    href="https://github.com/yuvalsuede/jasper-alternative-gpt"
                                    target="_blank"
                                    rel="noopener noreferrer"
                                >
                                    <Github/>
                                    <p>Star on Github</p>
                                </a>
                            </div>
                        </li>
                        {items.map((item, index) => (
                            <Fragment key={index}>
                                <li className="mb-2 ml-8">
                                    <a
                                        target={item?.target === 'blank' ? '_blank' : ''}
                                        href={item.url}
                                        className={`text-gray-500 hover:text-gray-700 transition duration-300 ${
                                            isActive(item.url) ? 'text-primary' : ''
                                        }`}>
                                        {item.label}
                                    </a>
                                </li>
                            </Fragment>
                        ))}


                    </ul>
                </nav>

            </aside>

        </>
    );
};

export default Sidebar;
```
Now this should give you a nice layout of a responsive sidebar and a place to populate our templates, exactly like in Jasper.ai. You should see something like this:


![Jema.ai sidebar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6egrrhmffyaz46vavqi6.png)

Of course you can add your own logo and menu items to make it yours.

Next, lets take care of the top categories of our Dashboard (homepage), where we can select our AI templates. Start with the selection menu. Here's a **secret: ** use give ChatGPT-4 a copy of the component that you wish to create (copy element using from any website that you like) and just ask it to create something similar. Here's my code for our `category list`:

```javascript
const categoriesData = [
    { id: "all", label: "All" },
    { id: "blog", label: "Blog" },
    { id: "linkedin", label: "LinkedIn" },
    { id: "email", label: "Email" },
    { id: "marketing", label: "Marketing" },
    { id: "ecommerce", label: "Ecommerce" },
    { id: "website", label: "Website" },
    { id: "ads", label: "Ads" },
    { id: "google", label: "Google" },
    { id: "seo", label: "SEO" },
    { id: "video", label: "Video" },
    { id: "social-media", label: "Social Media" },
];

// @ts-ignore
const CategoriesList = ({ onSelectedCategory }) => {
    const [selectedCategoryId, setSelectedCategoryId] = useState("all");

    useEffect(() => {
        onSelectedCategory(selectedCategoryId);
    }, [selectedCategoryId]);

    const handleCategoryChange = (categoryId: string) => {
        setSelectedCategoryId(categoryId);
    };

    return (
        <div className="flex flex-wrap gap-2 justify-start my-6">
            {categoriesData.map((category: Category) => (
                <button
                    key={category.id}
                    className={`cursor-pointer border inline-flex items-center justify-center mr-2 mb-2 px-3.5 py-1 text-sm font-medium rounded-full ${
                        selectedCategoryId === category.id
                            ? "border-blue-400 bg-blue-400 text-white hover:bg-opacity-100"
                            : "border-gray-300 bg-white text-gray-600 hover:text-gray-800 hover:shadow hover:ring-gray-200"
                    }`}
                    onClick={() => handleCategoryChange(category.id)}
                >
                    {category.label}
                </button>
            ))}
        </div>
    );
};

export default CategoriesList;
```
Notice the `onSelectedCategory` that we will later use to filter our templates. 

Next, create the cards grid for our template. I wanted it to have an icon, a title, description, and on click to navigate to my specific template page. I have started with the UI, and later added some logic to each template. 

We are going to create something like this one:


![Categories](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExM2YzOGVlZTAxYWMyZTRhZWRjODFiNzk5MzY5ODQ2MmQwYTA2M2Y5OCZjdD1n/9wBZMhP1kP2lq6etNU/giphy.gif)

Here is the code of our card grid wrapper. Notice the filter function that filters templates based on category selection made by the `onSelectedCategory` of our `CategoriesList` component.

```javascript
                <CategoriesList onSelectedCategory={handleSelectCategory}/>
                <div
                    className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-10 justify-items-center place-items-center">
                    {cards.filter((card) => selectedCategory === "all" || card?.categories?.includes(selectedCategory)).map((card, index) => (
                        <Card {...card} key={index}/>
                    ))}
                    <div style={{"minHeight": "300px", background: '#6366F1'}} className="relative p-6 h-full rounded-2xl shadow-sm bg-white">
                        <div
                            className="flex items-center justify-center w-16 h-16 text-3xl rounded-full mb-4 bg-white text-indigo-500">
                            <i className="fas fa-robot"></i>
                        </div>
                        <h3 className="mb-2 text-lg font-bold text-white">Want to integrate AI into your business?</h3>
                        <a href="https://www.linkedin.com/in/yuval-suede/" target="_blank"
                           rel="noopener noreferrer"
                           className="mt-5 inline-block px-6 py-2 text-sm font-medium bg-white text-indigo-500 rounded-full shadow-md hover:bg-opacity-90">Talk
                            to me
                        </a>
                    </div>

                </div>
```

You can check the full `Card.tsx` and `CategoriesList.tsx` on [my open source repo](https://github.com/yuvalsuede/jasper-alternative-gpt). Please don't forget to Start it on Github - this will help a lot  🚀 🔥 🎉

Lets talk a little bit about ChatGPT 3.5-turbo. 
Unlike older models, this is **not a text completion** model but a **Chat completion** model, which is entirely different - as this one does not require prior training at all (in fact, for now you cannot train it even if you want to). 

I really recommend playing with ChatGPT turbo model on OpenAI, it's free to try and really great. Go ahead and check their playground:
https://platform.openai.com/playground?mode=chat&model=gpt-3.5-turbo.

You can also have a look at their nice and easy documentation 

[here](https://platform.openai.com/docs/guides/chat).

Now here comes the great part. Adding chat completion to our project. Here is the call to `ChatGPT 3.5-turbo`:


```javascript
 const messages = [
            { role: "system", content: "You are a helpful assistant." },
            { role: "user", content: `Your task is: "${mainGoal}".\n\nHere are the details:\n${instruction}. 
            Please suggest 3 outputs. number them 1,2,3` },
        ];

        try {
            const response: any = await openai.createChatCompletion({
                model: "gpt-3.5-turbo",
                // @ts-ignore
                messages: messages,
                temperature: 1,
            });

            const reply = response?.data?.choices[0].message.content;
            res.status(200).json({ reply });
        } catch (error) {
            console.error("Error while making the API call:", error);
            res.status(500).json({ error: "Error while making the API call." });
        }
```

The above code is very straight forward. We start with a `user` message that tells that chat how to act (currently, it's a general role, but you can tell it that he/she is a doctor..).

Next we focus on user most important command. The structure is : `Your task is ... Here are some more details ...` and finally we ask it to generate 3 numbered outputs (this will be important for later parsing the result) 

Since I already know that instructions are a set of inputs of the user, based on my template, I have created the following function to make a list of instructions:

```javascript
const createInstruction = (inputs: TemplateInput[], inputsData: InputsData): string => {
    return inputs.map((input) => `${input.label}: ${inputsData[input.id]}`).join("\n");
};

```

I made a list of templates that can easily created and added to it. In fact, it takes about 5 minutes to add a new template, while drinking some coffee. 

Here's a basic template structure:

```javascript
    {
        "id": "a1b2-34c5-678d-90ef",
        "title": "LinkedIn Topic Ideas",
        "description": "Get inspired with LinkedIn topic ideas to share with your network.",
        "command": "Suggest LinkedIn topic ideas to share with my network.",
        "icon": "<i class='fas fab fa-linkedin text-primary'></i>",
        "categories": ["linkedin", "social_media"],
        "inputs": [
            {
                "id": "topic",
                "type": "text",
                "label": "Topic",
                "placeholder": "Marketing",
            },            {
                "id": "audience",
                "type": "text",
                "label": "Audience",
                "placeholder": "Marketers, companies, business owners",
            },
        ]
    }
```

- `id` is used to display the template in a new page (`/templates/:id` route)
- `categories` are used to filter templates based on our category selection. 
- `command` is the actual task that we send to ChatGPT ("mainGoal")
- `inputs` is a list of input by user, based on our template. You can have as many as you like, you can extend them and chatgpt will understand what you want. That actually works🔥

Now create any layout that you wish to ask users for their inputs, and send the request to ChatGPT (don't forget to set your `OPENAI_API_KEY`). Here we call to `api/chatgpt.ts` with the code above.

That's it, you are done!
You can fork the complete repository [here](https://github.com/yuvalsuede/jasper-alternative-gpt), and run it locally or deploy it to Vercel. 

Go ahead and build your own template engine. 
I encourage you to share your project in the comments 🤩 

Again, you can find the source code here:

https://github.com/yuvalsuede/jasper-alternative-gpt


## Can you help me?
I would be super grateful if you can star the repository 🤩

The more stars I get, the more tutorials like this I can create 🚀🚀🚀🚀🚀

https://github.com/yuvalsuede/jasper-alternative-gpt

![Categories](https://media.giphy.com/media/sXv0vaA4331Ti/giphy.gif
)