DEV Community

Cover image for Build AI Template Engine to create amazing content with Next.js and ChatGPT
Yuval
Yuval

Posted on • Updated on

 

Build AI Template Engine to create amazing content with Next.js and ChatGPT

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 , 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 - go ahead and visit me.

You can check this demo 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.

Jeam.ai

About the technologies

We are going to use Next.js 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 which was released just recently, and is a real superpower.

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 project already installed, make sure you have added Tailwind CSS to the project.

A basic project structure will look somewhat like this one:
Directories

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:

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;
Enter fullscreen mode Exit fullscreen mode


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"
    },
];

Enter fullscreen mode Exit fullscreen mode

For the sidebar create the following component

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;
Enter fullscreen mode Exit fullscreen mode

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

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:

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;
Enter fullscreen mode Exit fullscreen mode

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

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.

                <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>
Enter fullscreen mode Exit fullscreen mode

You can check the full Card.tsx and CategoriesList.tsx on my open source repo. 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.

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

 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." });
        }
Enter fullscreen mode Exit fullscreen mode

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:

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

Enter fullscreen mode Exit fullscreen mode

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:

    {
        "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",
            },
        ]
    }
Enter fullscreen mode Exit fullscreen mode
  • 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, 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

Top comments (9)

Collapse
 
henryjohn21 profile image
henryjohn21

Wow!
Love it,
Thank you for making this

Collapse
 
suede profile image
Yuval

Thanks Henryjohn21! I truly appreciate it❀️

Collapse
 
suede profile image
Yuval

Looking forward to seeing your results!

Collapse
 
nevodavid profile image
Nevo David

Hi Yuval!
Thank you so much for sharing this.
I have personally used Jasper in the past,
This looks like a nice alternative.
Maybe you can also build the Jasper chrome extension?

Collapse
 
suede profile image
Yuval

Hello Nevo, thank you so much!
This is a great idea, and I am actually working on it as well :D

Collapse
 
nevodavid profile image
Nevo David

Awesome, I can't wait 🀩

Collapse
 
vincentdorian profile image
Vincent

Really love the idea! ❀️
It would be wise to put a validation for limit if you would publish this as a service so people can not burn your tokens! Β πŸ˜‚

Collapse
 
dvjoan profile image
Krammer

Good job bro , you have earned another start

Collapse
 
davdev profile image
David

Thanks so much for share this and for free!!
of course i give you a star ;)