How to build: an AI-powered blogging platform (Next.js, Langchain, & Supabase)

How to build: an AI-powered blogging platform (Next.js, Langchain, & Supabase)

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.

Star CopilotKit ⭐️

(forgive the AI’s spelling mistake & give a star 🙂

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 createnextapp@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/reactcore @copilotkit/react-ui @copilotkit/reacttextarea @supabase/ssr @supabase/authhelpersnextjs

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

Leave a Reply

Your email address will not be published. Required fields are marked *