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.
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:
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;
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
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:
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;
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:
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>
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." });
}
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");
};
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",
},
]
}
-
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 🚀🚀🚀🚀🚀
Top comments (9)
Wow!
Love it,
Thank you for making this
Thanks Henryjohn21! I truly appreciate it❤️
Looking forward to seeing your results!
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?
Hello Nevo, thank you so much!
This is a great idea, and I am actually working on it as well :D
Awesome, I can't wait 🤩
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! 😂
Good job bro , you have earned another start
Thanks so much for share this and for free!!
of course i give you a star ;)