TL;DR
In this article, you will learn how to build an AI-powered blogging platform that can search the web and research any topic for a blog article.
We will be covering:
- Next.js for the app framework 🖥️
- OpenAI for the LLM 🧠
- LangChain & Tavily for a web-searching AI agent 🤖
- Using CopilotKit to integrate the AI into your app 🪁
- Supabase for storing and retrieving the blogging platform article data.
CopilotKit: the open-source Copilot framework
CopilotKit is the open-source AI copilot framework & platform. We make it easy to integrate powerful AI into your react apps.
Build:
ChatBots💬: Context aware in-app chatbots that can take actions in-app
CopilotTextArea📝: AI-powered textFields with context-aware autocomplete & insertions
Co-Agents🤖: In-app AI agents that can interact with your app & users. Powered by LangChain.
Now back to the article.
Prerequisites
Before we start building the app, let us first see the dependencies or packages we need to build the app
-
copilotkit/react-core
: CopilotKit frontend package with react hooks for providing app-state and actions to the copilot (AI functionalities) -
copilotkit/react-ui:
CopilotKit frontend package for the chatbot sidebar UI -
copilotkit/react-textarea:
CopilotKit frontend package for AI-assisted text-editing in the presentation speaker notes. -
LangChainJS:
A framework for developing applications powered by language models. -
Tavily Search API:
An API for helping connect LLMs and AI applications to trusted and real-time knowledge.
Installing All The Project Packages and Dependencies
Before installing all the project packages and dependencies, let us first create a Nextjs project by running the following command on your terminal.
npx create-next-app@latest
Then you will be prompted to select some options. Feel free to mark them, as shown below.
After that, open the newly created Nextjs project using a text editor of your choice. Then run the command below on the command line to install all the project packages and dependencies.
npm i @copilotkit/backend @copilotkit/shared @langchain/langgraph @copilotkit/react-core @copilotkit/react-ui @copilotkit/react-textarea @supabase/ssr @supabase/auth-helpers-nextjs
Creating The Blogging Platform Frontend
In this section, I will walk you through the process of creating the frontend of the blogging platform with static content to define the platform’s user interface.
To get started, go to /[root]/src/app
and create a folder called components
. Inside the components folder, create a file named Article.tsx
.
After that, add the following code to the file that defines a functional component named Article
that will be used to render the article creation form.
"use client";
import { useRef, useState } from "react";
export function Article() {
// Define state variables for article outline, copilot text, and article title
const [articleOutline, setArticleOutline] = useState("");
const [copilotText, setCopilotText] = useState("");
const [articleTitle, setArticleTitle] = useState("");
return (
// Form element for article input
<form
action={""}
className="w-full h-full gap-10 flex flex-col items-center p-10">
{/* Input field for article title */}
<div className="flex w-full items-start gap-3">
<textarea
className="p-2 w-full h-12 rounded-lg flex-grow overflow-x-auto overflow-y-hidden whitespace-nowrap"
id="title"
name="title"
value={articleTitle}
placeholder="Article Title"
onChange={(event) => setArticleTitle(event.target.value)}
/>
</div>
{/* Textarea for article content */}
<textarea
className="p-4 w-full aspect-square font-bold text-xl bg-slate-800 text-white rounded-lg resize-none"
id="content"
name="content"
value={copilotText}
placeholder="Write your article content here"
onChange={(event) => setCopilotText(event.target.value)}
/>
{/* Publish button */}
<button
type="submit"
className="p-4 w-full !bg-slate-800 text-white rounded-lg">Publish</button>
</form>
);
}
Next, add another file to the components folder, and name it Header.tsx
. Then add the following code to the file that defines a functional component named Header
that will render the blogging platform’s navbar.
import Link from "next/link";
export default function Header() {
return (
<>
<header className="flex flex-wrap sm:justify-start sm:flex-nowrap z-50 w-full bg-white border-b border-gray-200 text-sm py-3 sm:py-0 ">
<nav
className="relative max-w-7xl w-full mx-auto px-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8"
aria-label="Global">
<div className="flex items-center justify-between">
<Link
className="flex-none text-xl font-semibold "
href="/"
aria-label="Brand">
AIBlogging
</Link>
</div>
<div id="navbar-collapse-with-animation" className="">
<div className="flex flex-col gap-y-4 gap-x-0 mt-5 sm:flex-row sm:items-center sm:justify-end sm:gap-y-0 sm:gap-x-7 sm:mt-0 sm:ps-7">
<Link
className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 "
href="/writearticle">
Create Post
</Link>
</div>
</div>
</nav>
</header>
</>
);
}
After that, go to /[root]/src/app
and create a folder called writearticle
. Inside the writearticle
folder, create a file called page.tsx
file. Then add the following code into the file that imports Article
and Header
components. The code then defines a functional component named WriteArticle
that will render the navbar and the article creation form.
import { Article } from "../components/Article";
import Header from "../components/Header";
export default function WriteArticle() {
return (
<>
<Header />
<Article />
</>
);
}
Next, go to /[root]/src/page.tsx
file, and add the following code that defines a functional component named Home
that renders the blogging platform homepage that will display list of published articles.
import Image from "next/image";
import Link from "next/link";
import Header from "./components/Header";
const Home = async () => {
return (
<>
<Header />
<div className="max-w-[85rem] h-full px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
<Link
key={""}
className="group flex flex-col h-full bg-white border border-gray-200 hover:border-transparent hover:shadow-lg transition-all duration-300 rounded-xl p-5 "
href={""}>
<div className="aspect-w-16 aspect-h-11">
<Image
className="object-cover h-48 w-96 rounded-xl"
src={`https://source.unsplash.com/featured/?${encodeURIComponent(
"hello world"
)}`}
width={500}
height={500}
alt="Image Description"
/>
</div>
<div className="my-6">
<h3 className="text-xl font-semibold text-gray-800 ">
Hello World
</h3>
</div>
</Link>
</div>
</div>
</>
);
};
export default Home;
After that, go to the next.config.js
file and add the following code that allows you to use images from Unsplash as cover images for the published articles.
module.exports = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "source.unsplash.com",
},
],
},
};
Finally, run the command npm run dev
on the command line and then navigate to http://localhost:3000/. Now you should view the blogging platform frontend on your browser, as shown below.
Integrating The Blogging Platform With The CopilotKit Backend
In this section, I will walk you through the process of integrating the blogging platform with CopilotKit backend that handles requests from frontend, provides function calling and various LLM backends such as GPT. Also, we will integrate an AI agent named Tavily that can research any topic on the web.
To get started, create a file called .env.local
in the root directory. Then add the environment variables below in the file that hold your ChatGPT
and Tavily
Search API keys.
OPENAI_API_KEY="Your ChatGPT API key"
TAVILY_API_KEY="Your Tavily Search API key"
To get the ChatGPT API key, navigate to https://platform.openai.com/api-keys.
To get the Tavily Search API key, navigate to https://app.tavily.com/home
After that, go to /[root]/src/app
and create a folder called api
. In the api
folder, create a folder called copilotkit
. In the copilotkit
folder, create a file called research.ts
. Then Navigate to this research.ts gist file, copy the code, and add it to the research.ts
file
Next, create a file called route.ts
in the /[root]/src/app/api/copilotkit
folder. The file will contain code that sets up a backend functionality to process POST requests. It conditionally includes a "research" action that performs research on a given topic.
Now import the following modules at the top of the file.
import { CopilotBackend, OpenAIAdapter } from "@copilotkit/backend"; // For backend functionality with CopilotKit.
import { researchWithLangGraph } from "./research"; // Import a custom function for conducting research.
import { AnnotatedFunction } from "@copilotkit/shared"; // For annotating functions with metadata.
Below the code above, define a runtime environment variable and a function named researchAction
that conducts research on a certain topic using the code below.
// Define a runtime environment variable, indicating the environment where the code is expected to run.
export const runtime = "edge";
// Define an annotated function for research. This object includes metadata and an implementation for the function.
const researchAction: AnnotatedFunction<any> = {
name: "research", // Function name.
description: "Call this function to conduct research on a certain topic. Respect other notes about when to call this function", // Function description.
argumentAnnotations: [ // Annotations for arguments that the function accepts.
{
name: "topic", // Argument name.
type: "string", // Argument type.
description: "The topic to research. 5 characters or longer.", // Argument description.
required: true, // Indicates that the argument is required.
},
],
implementation: async (topic) => { // The actual function implementation.
console.log("Researching topic: ", topic); // Log the research topic.
return await researchWithLangGraph(topic); // Call the research function and return its result.
},
};
Then add the code below under the code above to define an asynchronous function that handles POST requests.
// Define an asynchronous function that handles POST requests.
export async function POST(req: Request): Promise<Response> {
const actions: AnnotatedFunction<any>[] = []; // Initialize an array to hold actions.
// Check if a specific environment variable is set, indicating access to certain functionality.
if (process.env["TAVILY_API_KEY"]) {
actions.push(researchAction); // Add the research action to the actions array if the condition is true.
}
// Instantiate CopilotBackend with the actions defined above.
const copilotKit = new CopilotBackend({
actions: actions,
});
// Use the CopilotBackend instance to generate a response for the incoming request using an OpenAIAdapter.
return copilotKit.response(req, new OpenAIAdapter());
}
Integrating The Blogging Platform With The CopilotKit Frontend
In this section, I will walk you through the process of integrating the blogging platform with the CopilotKit frontend to facilitate blog article research and article outline generation. We will use a chatbot sidebar component, a copilot textarea component, a useMakeCopilotReadable hook for providing app-state & other information to the Copilot, and a useCopilotAction hook for providing actions the Copilot can call
To get started, import the useMakeCopilotReadable
, useCopilotAction
, CopilotTextarea
, and HTMLCopilotTextAreaElement
hooks at the top of the /[root]/src/app/components/Article.tsx
file.
import {
useMakeCopilotReadable,
useCopilotAction,
} from "@copilotkit/react-core";
import {
CopilotTextarea,
HTMLCopilotTextAreaElement,
} from "@copilotkit/react-textarea";
Inside the Article function, below the state variables, add the following code that uses the useMakeCopilotReadable
hook to add the article outline that will be generated as context for the in-app chatbot. The hook makes the article outline readable to the copilot.
useMakeCopilotReadable("Blog article outline: " + JSON.stringify(articleOutline));
Below the useMakeCopilotReadable
hook, use the code below to create a reference called copilotTextareaRef
to a textarea element called HTMLCopilotTextAreaElement
.
const copilotTextareaRef = useRef<HTMLCopilotTextAreaElement>(null);
Below the code above, add the following code that uses the useCopilotAction
hook to set up an action called researchBlogArticleTopic
which will enable research on a given topic for a blog article. The action takes in two parameters called articleTitle
and articleOutline
which enables generation of an article title and outline.
The action contains a handler function that generates an article title and outline based on a given topic. Inside the handler function, articleOutline
state is updated with the newly generated outline while articleTitle
state is updated with the newly generated title, as shown below.
useCopilotAction(
{
name: "researchBlogArticleTopic",
description: "Research a given topic for a blog article.",
parameters: [
{
name: "articleTitle",
type: "string",
description: "Title for a blog article.",
required: true,
},
{
name: "articleOutline",
type: "string",
description:"Outline for a blog article that shows what the article covers.",
required: true,
},
],
handler: async ({ articleOutline, articleTitle }) => {
setArticleOutline(articleOutline);
setArticleTitle(articleTitle);
},
},
[]
);
Below the code above, go to the form component and add the following CopilotTextarea
element that will enable you to add completions, insertions, and edits to your article content.
<CopilotTextarea
value={copilotText}
ref={copilotTextareaRef}
placeholder="Write your article content here"
onChange={(event) => setCopilotText(event.target.value)}
className="p-4 w-full aspect-square font-bold text-xl bg-slate-800 text-white rounded-lg resize-none"
placeholderStyle={{
color: "white",
opacity: 0.5,
}}
autosuggestionsConfig={{
textareaPurpose: articleTitle,
chatApiConfigs: {
suggestionsApiConfig: {
forwardedParams: {
max_tokens: 5,
stop: ["\n", ".", ","],
},
},
insertionApiConfig: {},
},
debounceTime: 250,
}}
/>
Then add the Tailwindcss hidden class to the Textarea for article content, as shown below. The textarea will hold the article's content and enable it to be inserted into a database once the article is published.
{/* Textarea for article content */}
<textarea
className="p-4 w-full aspect-square font-bold text-xl bg-slate-800 text-white rounded-lg resize-none hidden"
id="content"
name="content"
value={copilotText}
placeholder="Write your article content here"
onChange={(event) => setCopilotText(event.target.value)}
/>
After that, go to /[root]/src/app/writearticle/page.tsx
file and import CopilotKit frontend packages and styles at the top using the code below.
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
import "@copilotkit/react-textarea/styles.css";
Then use CopilotKit
and CopilotSidebar
to wrap the Article component, as shown below. The CopilotKit
component specifies the URL for CopilotKit's backend endpoint (/api/copilotkit/openai/
) while the CopilotSidebar
renders the in-app chatbot that you can give prompts to research any topic for an article.
export default function WriteArticle() {
return (
<>
<Header />
<CopilotKit url="/api/copilotkit">
<CopilotSidebar
instructions="Help the user research a blog article topic."
defaultOpen={true}
labels={{
title: "Blog Article Copilot",
initial:
"Hi you! 👋 I can help you research any topic for a blog article.",
}}
clickOutsideToClose={false}>
<Article />
</CopilotSidebar>
</CopilotKit>
</>
);
}
After that, run the development server and navigate to http://localhost:3000/writearticle. You should see that the in-app chatbot was integrated into the Blogging platform.
Give the chatbot on the right side a prompt like, “research a blog article topic on generative AI and give me the article outline.” The chatbot will start researching the topic and then generate a blog title.
When you start writing on the editor, you should see content autosuggestions, as shown below.
Integrating The Blogging Platform With Supabase Database
In this section, I will walk you through the process of integrating the blogging platform with Supabase database to insert and fetch blog article data.
To get started, navigate to supabase.com and click the Start your project button on the home page.
Then create a new project called AiBloggingPlatform, as shown below.
Once the project is created, add your Supabase URL and API key to environment variables in the env.local file, as shown below.
NEXT_PUBLIC_SUPABASE_URL=”Your Supabase URL”
NEXT_PUBLIC_SUPABASE_ANON_KEY=”Your Supabase API Key”
After that, go to your project’s dashboard on Supabase and open the SQL Editor section. Then add the following SQL code to the editor and click CTRL + Enter keys to create a table called articles. The articles table has id, title, and content rows.
create table if not exists
articles (
id bigint primary key generated always as identity,
title text,
content text
);
Once the table is created, you should get a success message, as shown below.
After that, go to /[root]/src/
folder and create a folder called utils
. Inside the utils
folder, create a file called supabase.ts
and add the following code that creates and returns a Supabase client.
// Importing necessary functions and types from the Supabase SSR package
import { createServerClient, type CookieOptions } from '@supabase/ssr'
// Define a function named 'supabase' that takes a 'CookieOptions' object as input
export const supabase = (cookies: CookieOptions) => {
// Retrieve cookies from the provided 'CookieOptions' object
const cookieStore = cookies()
// Create and return a Supabase client configured with environment variables and cookie handling
return createServerClient(
// Retrieve Supabase URL from environment variables
process.env.NEXT_PUBLIC_SUPABASE_URL!,
// Retrieve Supabase anonymous key from environment variables
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
// Define a custom 'get' function to retrieve cookies by name from the cookie store
get(name: string) {
return cookieStore.get(name)?.value
},
},
}
)
}
Then go to /[root]/src/app
folder and create a folder called serveractions
. In the serveractions
folder, create a file called AddArticle.ts
and add the following code that inserts blog article data into the Supabase database.
// Importing necessary functions and modules for server-side operations
"use server";
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
// Define an asynchronous function named 'addArticle' that takes form data as input
export async function addArticle(formData: any) {
// Extract title and content from the provided form data
const title = formData.get("title");
const content = formData.get("content");
// Retrieve cookies from the HTTP headers
const cookieStore = cookies();
// Create a Supabase client configured with the provided cookies
const supabase = createServerComponentClient({ cookies: () => cookieStore });
// Insert the article data into the 'articles' table on Supabase
const { data, error } = await supabase.from("articles").insert([
{
title,
content,
},
]);
// Check for errors during the insertion process
if (error) {
console.error("Error inserting data", error);
return;
}
// Redirect the user to the home page after successfully adding the article
redirect("/");
// Return a success message
return { message: "Success" };
}
After that, go to /[root]/src/app/components/Article.tsx
file and import the addArticle
function.
import { addArticle } from "../serveractions/AddArticle";
Then add the addArticle
function as the form action parameter, as shown below.
// Form element for article input
<form
action={addArticle}
className="w-full h-full gap-10 flex flex-col items-center p-10">
</form>
After that, navigate to http://localhost:3000/writearticle, research the topic of your choice, add article content, and then click the publish button at the bottom to publish the article.
Then go to your project’s dashboard on Supabase and navigate to the Table Editor section. You should see that your article data was inserted into the Supabase database, as shown below.
Next, go to /[root]/src/app/page.tsx
file and import cookies and supabase packages at the top.
import { cookies } from "next/headers";
import { supabase } from "@/utils/supabase";
Then inside the Home function, add the following code that fetches articles data from Supabase database.
const { data: articles, error } = await supabase(cookies).from('articles').select('*')
After that, update the elements code as shown below to render the published articles on the blogging platform homepage.
return (
<>
<Header />
<div className="max-w-[85rem] h-full px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
{articles?.map((post: any) => (
<Link
key={post.id}
className="group flex flex-col h-full bg-white border border-gray-200 hover:border-transparent hover:shadow-lg transition-all duration-300 rounded-xl p-5 "
href={`/posts/${post.id}`}>
<div className="aspect-w-16 aspect-h-11">
<Image
className="object-cover h-48 w-96 rounded-xl"
src={`https://source.unsplash.com/featured/?${encodeURIComponent(
post.title
)}`}
width={500}
height={500}
alt="Image Description"
/>
</div>
<div className="my-6">
<h3 className="text-xl font-semibold text-gray-800 ">
{post.title}
</h3>
</div>
</Link>
))}
</div>
</div>
</>
);
Then navigate to http://localhost:3000 and you should see the article you published, as shown below.
After that, go to /[root]/src/app
folder and create a folder called [id].
In the [id]
folder, create a file called page.tsx
and import the following packages and components at the top.
import { supabase } from '@/utils/supabase';
import { cookies } from "next/headers";
import Header from '@/app/components/Header';
Below the imports, define an asynchronous function named 'getArticles' that retrieves article data from supabase database based on id parameter, as shown below.
// Define an asynchronous function named 'getArticles' that retrieves article data based on the provided parameters
async function getArticles(params: any) {
// Extract the 'id' parameter from the provided 'params' object
const { id } = params
// Retrieve article data from Supabase database where the 'id' matches the provided value
const { data, error } = await supabase(cookies)
.from('articles')
.select('*')
.eq('id', id)
.single();
// Return the retrieved data
return data
}
Below the code above, define a function called 'Post' that takes 'params' as props, as shown below.
// Define a default asynchronous function named 'Post' that takes 'params' as props
export default async function Post({ params }: { params: any }) {
// Retrieve the post data asynchronously based on the provided 'params'
const post = await getArticles(params);
// Return JSX to render the post details
return (
<>
{/* Render the header component */}
<Header />
{/* Main content wrapper */}
<div className="max-w-3xl px-4 pt-6 lg:pt-10 pb-12 sm:px-6 lg:px-8 mx-auto">
<div className="max-w-2xl">
<div className="space-y-5 md:space-y-8">
<div className="space-y-3">
{/* Render the post title */}
<h2 className="text-2xl font-bold md:text-3xl dark:text-white">
{/* Render the post title only if 'post' is truthy */}
{post && post.title}
</h2>
{/* Render the post content */}
<p className="text-lg text-gray-800 dark:text-gray-200">
{/* Render the post content only if 'post' is truthy */}
{post && post.content}
</p>
</div>
</div>
</div>
</div>
</>
);
}
After that, navigate to http://localhost:3000 and click the article displayed on the blogging platform homepage.
You should then be redirected to the article’s content, as shown below.
Conclusion
In conclusion, you can use CopilotKit to build in-app AI chatbots that can see the current app state and take action inside your app. The AI chatbot can talk to your app frontend, backend, and third-party services.
For the full source-code: https://github.com/TheGreatBonnie/aipoweredblog
Top comments (35)
Hi! Need help)
I do not have next.config.js file in main folder. I have next.config.mjs file.
And I have error after "next dev" - ReferenceError: module is not defined in ES module scope
at file:///Users/kr/PJ/FE/next-ai-blog/next.config.mjs:6:1
Any advice?
Hey Kostiantyn,
Rename next.config.mjs to next.config.js
Sorry, but another error from me)
pls see pic
Not sure what is the issue, but you can check the project demo repo and see where your code doesn't look good.
github.com/TheGreatBonnie/aipowere...
Did fork and try to use it with my env file, but no luck...
Got this in console:
Dear Bonnie, looks like in my region there some restrictions from Open AI. I am in Ukraine.
I try to use Germany via VPN and looks like it works.
Hope for some response soon.
Happy to hear that.
Hope everything works fine now.
Yes, now it is ok!)
Oh! Thanks, it works now)
I am happy to hear that.
Feel free to reach out if you come across any other issues.
Of course when I initially read the title my first thought was to imagine a complete blogging site that uses ai to write everything ;). Maybe the comments generated by ai too! Too soon? Perhaps, thankfully, but one day artificial idiocy may reach the point that natural humans on the internet do become as rare as a bald eagle in the sky...
Part II idea?? @the_greatbonnie
Sounds great.
This is such a great project @the_greatbonnie. Wish you more successful projects. Please I have a question about copilotkit AI does it support gpt-3.5-turbo or it only supports gpt-4-turbo? Because I have tried to search online but no obvious answer coming through. I will be expecting your response about this. Thanks.
Thanks, Mampha.
All GPT models are supported.
Well explained and detailed! Keep up the good work Bonnie.
Thanks, Samuel.
You're welcome, Bonnie.
Another great Article Bonnie. Great job!
Thanks, Uli.
Super insightful 🌟
Thanks, Bap.
Great stuff!
Thanks, David.
Nice one
Thanks, Thomas.
This look like a fun build, but at a glance, where is the Langchain part?
The backend part.
You can learn more here.
docs.copilotkit.ai/getting-started...
Where is the blog though?
It is not deployed yet.
You can clone the GitHub repo, and deploy it.