AI-Powered Blog Pt. II (Nextjs, GPT4, Supabase & CopilotKit)
Tags: webdev, tutorial, javascript, programming
Published: 2024-05-20 14:11:44

The AI era is upon us. To get ahead as devs, it's great to have some AI-powered projects in your portfolio. 

Today, we will go through building an AI-powered blog platform with some awesome functionality such as research, autocomplete and a Copilot. 

I built an initial version of this project [here](https://dev.to/copilotkit/how-to-build-an-ai-powered-blogging-platform-nextjs-langchain-supabase-1hdp). A commenter had some really cool suggestions for taking it to the next level. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b2pbxvo6rff8xz8a0h3b.png)

So we decided to build it!

## **TL;DR**
We're building an AI-powered blogging platform Pt. II

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bi1j9iq1jkll1r21f1e8.gif)

---

# CopilotKit: The framework for building in-app AI copilots

CopilotKit is an [open-source AI copilot platform](https://github.com/CopilotKit/CopilotKit). We make it easy to integrate powerful AI into your React apps.

Build:

- ChatBot: 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 🤖



![Uploading image](...)

{% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %}

Now back to the article!


---
## Prerequisites

To fully understand this tutorial, you need to have a basic understanding of React or Next.js.

Here are the tools required to build the AI-powered blog:

- [Quill Rich Text Editor](https://quilljs.com/) - a text editor that enables you to easily format text, add images, add code, and create custom, interactive content in your web app.
- [Supabase](https://supabase.com/) - a PostgreSQL hosting service that provides you with all the backend features you need for your project.
- [Langchain](https://www.langchain.com/) - provides a framework that enables AI agents to search the web and research any topic.
- [OpenAI API](https://platform.openai.com/api-keys) - provides an API key that enables you to carry out various tasks using ChatGPT models.
- [Tavily AI](https://tavily.com/) - a search engine that enables AI agents to conduct research and access real-time knowledge within the application.
- [CopilotKit](https://github.com/CopilotKit) - an open-source copilot framework for building custom AI chatbots, in-app AI agents, and text areas.

## Project Set up and Package Installation

First, create a Next.js application by running the code snippet below in your terminal:

```tsx
npx create-next-app@latest aiblogapp
```

Select your preferred configuration settings. For this tutorial, we'll be using TypeScript and Next.js App Router.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pae912scgh9tjfa75l8t.png)

Next, install Quill rich text editor, Supabase, and Langchain packages and their dependencies.

```tsx
npm install quill react-quill @supabase/supabase-js @supabase/ssr @supabase/auth-helpers-nextjs @langchain/langgraph
```

Finally, install the CopilotKit packages. These packages enable us to retrieve data from the React state and add AI copilot to the application.

```tsx
npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend
```

Congratulations! You're now ready to build an AI-powered blog.

## **Building The Blog Frontend**

In this section, I will walk you through the process of creating the blog's frontend with static content to define the blog’s user interface.

The blog’s frontend will consist of four pages: the home page, post page, create post page, and login/sign up page.

To get started, go to `/[root]/src/app` in your code editor and create a folder called `components`. Inside the components folder, create five files named `Header.tsx`, `Posts.tsx`, `Post.tsx`, `Comment.tsx` and `QuillEditor.tsx`

In the `Header.tsx` file, add the following code that defines a functional component named `Header` that will render the blog’s navbar.

```tsx
"use client";

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-gray-800 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 text-white font-semibold "
              href="/"
              aria-label="Brand">
              AIBlog
            </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="/createpost">
                Create Post
              </Link>

              <form action={""}>
                <button
                  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 "
                  type="submit">
                  Logout
                </button>
              </form>

              <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="/login">
                Login
              </Link>
            </div>
          </div>
        </nav>
      </header>
    </>
  );
}
```

In the `Posts.tsx` file, add the following code that defines a functional component named `Posts` that renders the blogging platform homepage that will display a list of published articles.

```tsx
"use client";

import React, { useEffect, useState } from "react";
import Image from "next/image";
import Link from "next/link";

export default function Posts() {
  const [articles, setArticles] = useState<any[]>([]);

  return (
    <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-gray-800 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-white ">Hello World</h3>
          </div>
        </Link>
      </div>
    </div>
  );
}

```

In the `QuillEditor.tsx` file, add the following code that dynamically imports the QuillEditor component, defines modules configuration for the Quill editor toolbar, and defines text formats for the Quill editor.

```tsx
// Import the dynamic function from the "next/dynamic" package
import dynamic from "next/dynamic";
// Import the CSS styles for the Quill editor's "snow" theme
import "react-quill/dist/quill.snow.css";

// Export a dynamically imported QuillEditor component
export const QuillEditor = dynamic(() => import("react-quill"), { ssr: false });

// Define modules configuration for the Quill editor toolbar
export const quillModules = {
  toolbar: [
    // Specify headers with different levels
    [{ header: [1, 2, 3, false] }],
    // Specify formatting options like bold, italic, etc.
    ["bold", "italic", "underline", "strike", "blockquote"],
    // Specify list options: ordered and bullet
    [{ list: "ordered" }, { list: "bullet" }],
    // Specify options for links and images
    ["link", "image"],
    // Specify alignment options
    [{ align: [] }],
    // Specify color options
    [{ color: [] }],
    // Specify code block option
    ["code-block"],
    // Specify clean option for removing formatting
    ["clean"],
  ],
};

// Define supported formats for the Quill editor
export const quillFormats = [
  "header",
  "bold",
  "italic",
  "underline",
  "strike",
  "blockquote",
  "list",
  "bullet",
  "link",
  "image",
  "align",
  "color",
  "code-block",
];

```

In the `Post.tsx` file, add the following code that defines a functional component named `CreatePost` that will be used to render the article creation form.

```tsx
"use client";

// Importing React hooks and components
import { useRef, useState } from "react";
import { QuillEditor } from "./QuillEditor";
import { quillModules } from "./QuillEditor";
import { quillFormats } from "./QuillEditor";
import "react-quill/dist/quill.snow.css";

// Define the CreatePost component
export default function CreatePost() {
  // Initialize state variables for article outline, copilot text, and article title
  const [articleOutline, setArticleOutline] = useState("");
  const [copilotText, setCopilotText] = useState("");
  const [articleTitle, setArticleTitle] = useState("");

  // State variable to track if research task is running
  const [publishTaskRunning, setPublishTaskRunning] = useState(false);

  // Handle changes to the editor content
  const handleEditorChange = (newContent: any) => {
    setCopilotText(newContent);
  };

  return (
    <>
      {/* Main */}
      <div className="p-3 max-w-3xl mx-auto min-h-screen">
        <h1 className="text-center text-white text-3xl my-7 font-semibold">
          Create a post
        </h1>

        {/* Form for creating a post */}
        <form action={""} className="flex flex-col gap-4 mb-2 mt-2">
          <div className="flex flex-col gap-4 sm:flex-row justify-between mb-2">
            {/* Input field for article title */}
            <input
              type="text"
              id="title"
              name="title"
              placeholder="Title"
              value={articleTitle}
              onChange={(event) => setArticleTitle(event.target.value)}
              className="flex-1 block w-full rounded-lg border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500"
            />
          </div>
          
          {/* Hidden 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)}
          />

          {/* Quill editor component */}
          <QuillEditor
            onChange={handleEditorChange}
            modules={quillModules}
            formats={quillFormats}
            className="h-80 mb-12 text-white"
          />
          {/* Submit button for publishing the post */}
          <button
            type="submit"
            disabled={publishTaskRunning}
            className={`bg-blue-500 text-white font-bold py-2 px-4 rounded ${
              publishTaskRunning
                ? "opacity-50 cursor-not-allowed"
                : "hover:bg-blue-700"
            }`}
            onClick={async () => {
              try {
                setPublishTaskRunning(true);
              } finally {
                setPublishTaskRunning(false);
              }
            }}>
            {publishTaskRunning ? "Publishing..." : "Publish"}
          </button>
        </form>
      </div>
    </>
  );
}
```

In the `Comment.tsx` file, add the following code that defines a functional component called Comment that renders post comment form and post comments.

```tsx
// Client-side rendering
"use client";

// Importing React and Next.js components
import React, { useEffect, useRef, useState } from "react";
import Image from "next/image";

// Define the Comment component
export default function Comment() {
  // State variables for comment, comments, and article content
  const [comment, setComment] = useState("");
  const [comments, setComments] = useState<any[]>([]);
  const [articleContent, setArticleContent] = useState("");

  return (
    <div className="max-w-2xl mx-auto w-full p-3">
      {/* Form for submitting a comment */}
      <form action={""} className="border border-teal-500 rounded-md p-3 mb-4">
        {/* Textarea for entering a comment */}
        <textarea
          id="content"
          name="content"
          placeholder="Add a comment..."
          rows={3}
          onChange={(e) => setComment(e.target.value)}
          value={comment}
          className="hidden"
        />

        {/* Submit button */}
        <div className="flex justify-between items-center mt-5">
          <button
            type="submit"
            className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
            Submit
          </button>
        </div>
      </form>

      {/* Comments section */}
      <p className="text-white mb-2">Comments:</p>

      {/* Comment item (currently hardcoded) */}
      <div key={""} className="flex p-4 border-b dark:border-gray-600 text-sm">
        <div className="flex-shrink-0 mr-3">
          {/* Profile picture */}
          <Image
            className="w-10 h-10 rounded-full bg-gray-200"
            src={`(link unavailable){encodeURIComponent(
              "Silhouette"
            )}`}
            width={500}
            height={500}
            alt="Profile Picture"
          />
        </div>
        <div className="flex-1">
          <div className="flex items-center mb-1">
            {/* Username (currently hardcoded as "Anonymous") */}
            <span className="font-bold text-white mr-1 text-xs truncate">
              Anonymous
            </span>
          </div>
          {/* Comment text (currently hardcoded as "No Comments") */}
          <p className="text-gray-500 pb-2">No Comments</p>
        </div>
      </div>
    </div>
  );
}
```

Next, go to `/[root]/src/app` and create a folder called `[slug]`. Inside the [slug] folder, create a `page.tsx` file.

Then add the following code into the file that imports `Comment` and `Header` components and defines a functional component named `Post` that will render the navbar, post content, comment form, and post comments.

```tsx
import Header from "../components/Header";
import Comment from "../components/Comment";

export default async function Post() {
  return (
    <>
      <Header />
      <main className="p-3 flex flex-col max-w-6xl mx-auto min-h-screen">
        <h1 className="text-3xl text-white mt-10 p-3 text-center font-serif max-w-2xl mx-auto lg:text-4xl">
          Hello World
        </h1>
        <div className="flex justify-between text-white p-3 border-b border-slate-500 mx-auto w-full max-w-2xl text-xs">
          <span></span>
          <span className="italic">0 mins read</span>
        </div>
        <div className="p-3 max-w-2xl text-white mx-auto w-full post-content border-b border-slate-500 mb-2">
          No Post Content
        </div>
        <Comment />
      </main>
    </>
  );
}

```

After that, go to `/[root]/src/app` and create a folder called `createpost`. Inside the `createpost` folder, create a file called `page.tsx` file. 

Then add the following code into the file that imports `CreatePost` and `Header` components and defines a functional component named `WriteArticle` that will render the navbar and the post creation form.

```tsx
import CreatePost from "../components/Post";
import Header from "../components/Header";
import { redirect } from "next/navigation";

export default async function WriteArticle() {
  return (
    <>
      <Header />
      <CreatePost />
    </>
  );
}
```

Next, go to `/[root]/src/page.tsx` file, and add the following code that imports `Posts` and `Header` components and defines a functional component named `Home`.

```tsx
import Header from "./components/Header";
import Posts from "./components/Posts";

export default async function Home() {
  return (
    <>
      <Header />
      <Posts />
    </>
  );
}

```

After that, go to the `next.config.mjs` file and rename it to `next.config.js` . Then add the following code that allows you to use images from Unsplash as cover images for the published articles.

```tsx
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "source.unsplash.com",
      },
    ],
  },
};

```

Next, remove the CSS code in the globals.css file and add the following CSS code.

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  height: 100vh;
  background-color: rgb(16, 23, 42);
}

.ql-editor {
  font-size: 1.25rem;
}

.post-content p {
  margin-bottom: 0.5rem;
}

.post-content h1 {
  font-size: 1.5rem;
  font-weight: 600;
  font-family: sans-serif;
  margin: 1.5rem 0;
}

.post-content h2 {
  font-size: 1.4rem;
  font-family: sans-serif;
  margin: 1.5rem 0;
}

.post-content a {
  color: rgb(73, 149, 199);
  text-decoration: none;
}

.post-content a:hover {
  text-decoration: underline;
}
```

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.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz1qewlt9gkf2vbp5jqq.png)

## **Integrating AI Functionalities To The Blog Using CopilotKit**

In this section, you will learn how to add an AI copilot to the blog to perform blog topic research and content autosuggestions using CopilotKit.

CopilotKit offers both frontend and [backend](https://docs.copilotkit.ai/getting-started/quickstart-backend) packages. They enable you to plug into the React states and process application data on the backend using AI agents.

First, let's add the CopilotKit React components to the blog frontend.

### **Adding CopilotKit to the Blog Frontend**

Here, I will walk you through the process of integrating the blog with the CopilotKit frontend to facilitate blog article research and article outline generation. 

To get started, use the code snippet below to import `useMakeCopilotReadable`, `useCopilotAction`, `CopilotTextarea`, and `HTMLCopilotTextAreaElement` custom hooks at the top of the `/[root]/src/app/components/Post.tsx` file.

```tsx
import {
    useMakeCopilotReadable,
    useCopilotAction,
  } from "@copilotkit/react-core";
  import {
    CopilotTextarea,
    HTMLCopilotTextAreaElement,
  } from "@copilotkit/react-textarea";
```

Inside the CreatePost 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.

```tsx
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`.

```tsx
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.

```tsx
// Define a Copilot action
useCopilotAction(
  {
    // Action name and description
    name: "researchBlogArticleTopic",
    description: "Research a given topic for a blog article.",
    
    // Parameters for the action
    parameters: [
      {
        // Parameter 1: articleTitle
        name: "articleTitle",
        type: "string",
        description: "Title for a blog article.",
        required: true, // This parameter is required
      },
      {
        // Parameter 2: articleOutline
        name: "articleOutline",
        type: "string",
        description: "Outline for a blog article that shows what the article covers.",
        required: true, // This parameter is required
      },
    ],
    
    // Handler function for the action
    handler: async ({ articleOutline, articleTitle }) => {
      // Set the article outline and title using state setters
      setArticleOutline(articleOutline);
      setArticleTitle(articleTitle);
    },
  },
  [] // Dependencies (empty array means no dependencies)
);
```

Below the code above, go to the form component and add the following `CopilotTextarea` component that will enable you to add text completions, insertions, and edits to your article content.

```tsx
<CopilotTextarea
            className="p-4 h-72 w-full rounded-lg mb-2 border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500 resize-none"
            ref={copilotTextareaRef}
            placeholder="Start typing for content autosuggestion."
            value={articleOutline}
            rows={5}
            autosuggestionsConfig={{
              textareaPurpose: articleTitle,
              chatApiConfigs: {
                suggestionsApiConfig: {
                  forwardedParams: {
                    max_tokens: 5,
                    stop: ["\n", ".", ","],
                  },
                },
                insertionApiConfig: {},
              },
              debounceTime: 250,
            }}
          />
```

After that, go to `/[root]/src/app/createpost/page.tsx` file and import CopilotKit frontend packages and styles at the top using the code below.

```tsx
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
```

Then use `CopilotKit` to wrap the `CopilPopup` and  `CreatePost` components, as shown below. The `CopilotKit` component specifies the URL for CopilotKit's backend endpoint (`/api/copilotkit/`) while the `CopilotPopup` renders the in-app chatbot that you can give prompts to research any topic for an article.

```tsx
export default async function WriteArticle() {
  return (
    <>
      <Header />
      <CopilotKit url="/api/copilotkit">
        <CopilotPopup
          instructions="Help the user research a blog article topic."
          defaultOpen={true}
          labels={{
            title: "Blog Article Research AI Assistant",
            initial:
              "Hi! 👋 I can help you research any topic for a blog article.",
          }}
        />
        <CreatePost />
      </CopilotKit>
    </>
  );
}
```

After that, use the code snippet below to import `useMakeCopilotReadable`, `CopilotKit`, `CopilotTextarea`, and `HTMLCopilotTextAreaElement` custom hooks at the top of the `/[root]/src/app/components/Comment.tsx` file.

```tsx
import { useMakeCopilotReadable, CopilotKit } from "@copilotkit/react-core";
import {
  CopilotTextarea,
  HTMLCopilotTextAreaElement,
} from "@copilotkit/react-textarea";
```

Inside the Comment function, below the state variables, add the following code that uses the `useMakeCopilotReadable` hook to add the post content as context for the comment content autosuggestion.

```tsx
useMakeCopilotReadable(
    "Blog article content: " + JSON.stringify(articleContent)
  );
  
  const copilotTextareaRef = useRef<HTMLCopilotTextAreaElement>(null);
```

Then add the CopilotTextarea component to the comment form and use `CopilotKit` to wrap the form, as shown below.

```tsx
<CopilotKit url="/api/copilotkit">
        <form
          action={""}
          className="border border-teal-500 rounded-md p-3 mb-4">
          <textarea
            id="content"
            name="content"
            placeholder="Add a comment..."
            rows={3}
            onChange={(e) => setComment(e.target.value)}
            value={comment}
            className="hidden"
          />

          <CopilotTextarea
            className="p-4 w-full rounded-lg mb-2 border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500 resize-none"
            ref={copilotTextareaRef}
            placeholder="Start typing for content autosuggestion."
            onChange={(event) => setComment(event.target.value)}
            rows={5}
            autosuggestionsConfig={{
              textareaPurpose: articleContent,
              chatApiConfigs: {
                suggestionsApiConfig: {
                  forwardedParams: {
                    max_tokens: 5,
                    stop: ["\n", ".", ","],
                  },
                },
                insertionApiConfig: {},
              },
              debounceTime: 250,
            }}
          />

          <div className="flex justify-between items-center mt-5">
            <button
              type="submit"
              className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
              Submit
            </button>
          </div>
        </form>
      </CopilotKit>
```

After that, run the development server and navigate to http://localhost:3000/createpost. You should see that the popup in-app chatbot and CopilotTextarea were integrated into the Blog.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0j5ly5l5cf0ldaf3hwgx.png)

Congratulations! You've successfully added CopilotKit to the Blog Frontend**.**

### **Adding CopilotKit Backend to the Blog**

Here, I will walk you through the process of integrating the blog with the CopilotKit backend that handles requests from frontend, and 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.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ei04w6fadsmalbjaocun.png)

To get the Tavily Search API key, navigate to https://app.tavily.com/home


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqbqb5wvrnkv1vc8z7hf.png)

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](https://gist.github.com/TheGreatBonnie/58dc21ebbeeb8cbb08df665db762738c), 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.

```tsx
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 researches a certain topic using the code below.

```tsx
// 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.

```tsx
// 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());
}

```

Congratulations! You've successfully added the CopilotKit backend to the Blog**.**

## **Integrating Database to the Blog Using Supabase**

In this section, I will walk you through the process of integrating the blog with the Supabase database to insert and fetch blog article and comments data.

To get started, navigate to [supabase.com](http://supabase.com/) and click the Start your project button on the home page.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qu4h1xuhvkbtttqvry1o.png)

Then create a new project called AiBloggingPlatform, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01uq8s2l9yoa6cvpmocl.png)

Once the project is created, add your Supabase URL and API key to environment variables in the `env.local` file, as shown below.

```tsx
NEXT_PUBLIC_SUPABASE_URL=”Your Supabase URL”
NEXT_PUBLIC_SUPABASE_ANON_KEY=”Your Supabase API Key”
```

### **Setting up Supabase Authentication for  the blog**

Here, I will walk you through the process of setting up authentication for the blog that enables users to sign up, log in, or log out.

To get started, go to `/[root]/src/` and create a folder named `utils`. Inside the `utils` folder, create a folder named `supabase`. 

Then create two files named `client.ts` and `server.ts` inside the `supabase` folder. 

After that, navigate to [this supabase docs link](https://supabase.com/docs/guides/auth/server-side/nextjs#:~:text=Write%20utility%20functions%20to%20create%20Supabase%20clients), copy the code provided there, and paste it to the respective files you created in the `supabase` folder.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d99g065wf471fag7rbjs.png)

Next, create a file named `middleware.ts` at the root of your project and another file by the same name inside the `/[root]/src/utils/supabase` folder. 

After that, navigate to [this supabase docs link](https://supabase.com/docs/guides/auth/server-side/nextjs#:~:text=4-,Hook%20up%20middleware,-Create%20a%20middleware), copy the code provided there, and paste it to the respective `middleware.ts` files.



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qo21y007yjpw6w50xb9l.png)

Next, go to `/[root]/src/app/login` folder and create a file named `actions.ts`. After that, navigate to [this supabase docs link](https://supabase.com/docs/guides/auth/server-side/nextjs#:~:text=5-,Create%20a%20login%20page,-Create%20a%20login), copy the code provided there, and paste it to the `actions.ts`. 



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q1ql5mf0dhqyyli9dipm.png)

After that, change the email template to support the authentication flow. To do that, go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your Supabase dashboard. 

In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=signup` as shown below and then click the save button.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9n8n4wdycoqnx3l41rn.png)

After that, create a route handler for authentication confirmation using email**.** To do that**, go to** `/[root]/src/app/` and create a folder named `auth`. Then create a folder named `confirm` inside the `auth` folder. 

Inside the `confirm` folder, create a file named `route.ts` and navigate to [this supabase docs link](https://supabase.com/docs/guides/auth/server-side/nextjs#:~:text=Create%20a%20route%20handler%20for%20Auth%20confirmation), copy the code provided there, and paste it to the `route.ts` file.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mws1datksq4dml3a8z8n.png)

After that, go to `/[root]/src/app/login/page.tsx` file and use the code snippet below to import the Supabase `signup` and `login` functions.

```tsx
import { login, signup } from "./actions";
```

In the signup/login form, use formAction to call the Supabase `signup` and `login` functions when the login and signup buttons are clicked, as shown below.

```tsx
<button
	className="bg-blue-500 text-white font-bold py-2 px-4 rounded"
	formAction={login}>
	Log in
</button>
<button
	className="bg-blue-500 text-white font-bold py-2 px-4 rounded"
	formAction={signup}>
	Sign up
</button>
```

After that, run the development server and navigate to [http://localhost:3000/login](http://localhost:3000/createpost). Add an email and password as shown below and click the Sign Up button.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/37ee0ii966uhhpi8lqz8.png)

Then go to the inbox of the email you used to sign up and click the Confirm your email button, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekukm36uhh833uuhg1k3.png)

After that, go to the [Auth users](https://supabase.com/dashboard/project/_/auth/users) page in your Supabase dashboard and you should see the newly created user, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qmv02jptpsn9k06ytkri.png)

Next, set up logout functionality. To do that, go to `/[root]/src/app` and create a folder named `logout`. Then create a file named `route.ts` and add the following code into the file.

```tsx
// Server-side code
"use server";

// Importing Next.js functions
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

// Importing Supabase client creation function from utils
import { createClient } from "@/utils/supabase/server";

// Exporting the logout function
export async function logout() {
  // Creating a Supabase client instance
  const supabase = createClient();

  // Signing out of Supabase auth
  const { error } = await supabase.auth.signOut();

  // If there's an error, redirect to the error page
  if (error) {
    redirect("/error");
  }

  // Revalidate the "/" path for the "layout" cache
  revalidatePath("/", "layout");

  // Redirect to the homepage
  redirect("/");
}
```

After that, go to `/[root]/src/app/components/Header.tsx` file and import the Supabase logout function using the code snippet below.

```tsx
import { logout } from "../logout/actions";
```

Then add the logout function to form action parameter, as shown below.

```tsx
<form action={logout}>
	<button
		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 "
		type="submit">
		Logout
	</button>
</form>
```

If you click the Logout button, the logged-in user will be logged out.

### **Setting up user roles and permissions for the blog**

Here, I will walk you through the process of setting up user roles and permissions that control what different users can do on the blog.

To get started, go to `/[root]/src/app/components/Header.tsx` file and import the Supabase createClient function.

```tsx
import { createClient } from "@/utils/supabase/client";
```

Then import `useState` and `useEffect` hooks and define a type named user using the code snippet below.

```tsx
import { useEffect, useState } from "react";

type User = {};
```

Inside the Header functional component, add the following code that uses the `useState` hook to store the `user` and `admin` data, and the `useEffect` hook to fetch the user data from Supabase auth when the component mounts. The `getUser` function checks for errors and sets the user and admin states accordingly.

```tsx
// State variables for user and admin
const [user, setUser] = useState<User | null>(null);
const [admin, setAdmin] = useState<User | null>(null);

// useEffect hook to fetch user data on mount
useEffect(() => {
  // Define an async function to get the user
  async function getUser() {
    // Create a Supabase client instance
    const supabase = createClient();

    // Get the user data from Supabase auth
    const { data, error } = await supabase.auth.getUser();

    // If there's an error or no user data, log a message
    if (error || !data?.user) {
      console.log("No User");
    } 
    // If user data is available, set the user state
    else {
      setUser(data.user);
    }

    // Define the email of the signed-up user
    const userEmail = "email of signed-up user";

    // Check if the user is an admin (email matches)
    if (!data?.user || data.user?.email !== userEmail) {
      console.log("No Admin");
    } 
    // If the user is an admin, set the admin state
    else {
      setAdmin(data.user);
    }
  }
  // Call the getUser function
  getUser();
}, []); // Dependency array is empty, so the effect runs only once on mount
```

After that, update the navbar code as shown below. The updated code controls which buttons will be rendered depending on whether there is a logged-in user or the logged-in user is an admin.

```tsx
<div id="navbar-collapse-with-animation" className="">
  {/* Navbar content container */}
  <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">
    {/* Conditional rendering for admin link */}
    {admin ? (
      // If admin is true, show the "Create Post" link
      <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="/createpost">
        Create Post
      </Link>
    ) : (
      // If admin is false, render an empty div
      <div></div>
    )}

    {/* Conditional rendering for user link/logout button */}
    {user ? (
      // If user is true, show the logout button
      <form action={logout}>
        <button
          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 "
          type="submit">
          Logout
        </button>
      </form>
    ) : (
      // If user is false, show the "Login" link
      <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="/login">
        Login
      </Link>
    )}
  </div>
</div>
```

If you navigate to [http://localhost:3000](http://localhost:3000/) you should see that only the Create Post and Logout buttons are rendered because the user is logged in and set to admin.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdhh0x1ln8xl9y1tq7ua.png)

After that, go to `/[root]/src/app/createpost/page.tsx` file and import the Supabase createClient function.

```tsx
import { createClient } from "@/utils/supabase/client";
```

Inside the WriteArticle functional component, add the following code that fetches the logged in user using Supabase createClient function and verifies if the user’s email is the same with the email of the user set as admin.

```tsx
// Define the email of the user you want to make admin
const userEmail = "email of admin user";

// Create a Supabase client instance
const supabase = createClient();

// Get the user data from Supabase auth
const { data, error } = await supabase.auth.getUser();

// Check for errors or if the user data doesn't match the expected email
if (error || !data?.user || data?.user.email !== userEmail) {
  // If any of the conditions are true, redirect to the homepage
  redirect("/");
}
```

Now only a user set to admin can access the  http://localhost:3000/createpost page, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vrnvib7ikzsgt8webrz.png)

### **Setting up insert and fetch post data with Supabase functionality**

Here, I will walk you through the process of setting up insert and fetch data functionality to the blog using the Supabase database.

To get started, go to the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new) page in your Supabase dashboard. 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, slug, content, and created_at columns.

```sql
create table if not exists
  articles (
    id bigint primary key generated always as identity,
    title text,
    slug text,
    content text,
    created_at timestamp 
  );
```

Once the table is created, you should get a success message, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dd3xgg0o8ygztkab3cir.png)

After that, go to `/[root]/src/utils/supabase` folder and create a file called `AddArticle.ts`. Then add the following code that inserts blog article data into the Supabase database to the file.

```tsx
// Server-side code
"use server";

// Importing Supabase auth helpers and Next.js functions
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

// Exporting the addArticle function
export async function addArticle(formData: any) {
  // Extracting form data
  const title = formData.get("title");
  const content = formData.get("content");
  const slug = formData
    .get("title")
    .split(" ")
    .join("-")
    .toLowerCase()
    .replace(/[^a-zA-Z0-9-]/g, ""); // Generating a slug from the title
  const created_at = formData.get(new Date().toDateString()); // Getting the current date

  // Creating a cookie store
  const cookieStore = cookies();

  // Creating a Supabase client instance with cookie store
  const supabase = createServerComponentClient({ cookies: () => cookieStore });

  // Inserting data into the "articles" table
  const { data, error } = await supabase.from("articles").insert([
    {
      title,
      content,
      slug,
      created_at,
    },
  ]);

  // Handling errors
  if (error) {
    console.error("Error inserting data", error);
    return;
  }

  // Redirecting to the homepage
  redirect("/");

  // Returning a success message
  return { message: "Success" };
}
```

Next, go to `/[root]/src/app/components/Post.tsx` file and import the addArticle function.

```tsx

import { addArticle } from "@/utils/supabase/AddArticle";
```

Then add the `addArticle` function as the form action parameter, as shown below.

```tsx
<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/createpost and give the chatbot on the right side a prompt like, “research a blog article topic on JavaScript frameworks.”

The chatbot will start researching the topic and then generate a blog title and outline, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w3i5ndwct44094iczrpo.png)

When you start writing on the CopilotKitTextarea, you should see content autosuggestions, as shown below.

{% embed https://youtu.be/2oMNV1acKIs %}

If the content is up to your liking, copy and paste it to the Quill rich text editor. Then start editing it as shown below.

{% embed https://youtu.be/-r3woCeq4vs %}

Then click the publish button at the bottom to publish the article. Go to your project’s dashboard on Supabase and navigate to the Table Editor section. Click the articles table and you should see that your article data was inserted into the Supabase database, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4umd2qijpdt9z3evfkj.png)

Next, go to `/[root]/src/app/components/Posts.tsx` file and import the createClient function.

```tsx
import { createClient } from "@/utils/supabase/client";
```

Inside the Posts functional component, add the following code that uses the useState hook to store the articles data and the useEffect hook to fetch the articles from Supabase when the component mounts. The fetchArticles function creates a Supabase client instance, fetches the articles, and updates the state if data is available.

```tsx
// State variable for articles
const [articles, setArticles] = useState<any[]>([]);

// useEffect hook to fetch articles on mount
useEffect(() => {
  // Define an async function to fetch articles
  const fetchArticles = async () => {
    // Create a Supabase client instance
    const supabase = createClient();

    // Fetch articles from the "articles" table
    const { data, error } = await supabase.from("articles").select("*");

    // If data is available, update the articles state
    if (data) {
      setArticles(data);
    }
  };

  // Call the fetchArticles function
  fetchArticles();
}, []); // Dependency array is empty, so the effect runs only once on mount
```

After that, update the elements code as shown below to render the published articles on the blog’s homepage.

```tsx
// Return a div element with a max width, full height, padding, and horizontal margin
return (
  <div className="max-w-[85rem] h-full  px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
    // Create a grid container with dynamic number of columns based on screen size
    <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
      // Map over the articles array and render a Link component for each item
      {articles?.map((post) => (
        <Link
          // Assign a unique key prop to each Link component
          key={post.id}
          // Apply styles for the Link component
          className="group flex flex-col h-full bg-gray-800 border border-gray-200 hover:border-transparent hover:shadow-lg transition-all duration-300 rounded-xl p-5 "
          // Set the href prop to the post slug
          href={`/${post.slug}`}>
          // Create a container for the image
          <div className="aspect-w-16 aspect-h-11">
            // Render an Image component with a dynamic src based on the post title
            <Image
              className="object-cover h-48 w-96 rounded-xl"
              src={`(link unavailable){encodeURIComponent(
                post.title
              )}`}
              // Set the width and height props for the Image component
              width={500}
              height={500}
              // Set the alt prop for accessibility
              alt="Image Description"
            />
          </div>
          // Create a container for the post title
          <div className="my-6">
            // Render an h3 element with the post title
            <h3 className="text-xl font-semibold text-white ">
              {post.title}
            </h3>
          </div>
        </Link>
      ))}
    </div>
  </div>
);
```

Then navigate to  [http://localhost:3000](http://localhost:3000) and you should see the article you published, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpwvh0b3cm7dfrgwixtv.png)

Next, go to `/[root]/src/app/[slug]/page.tsx` file and import the createClient function.

```tsx
import { createClient } from "@/utils/supabase/client";
```

Below the imports, define an asynchronous function named 'getArticleContent' that retrieves article data from supabase database based on slug parameter, as shown below.

```tsx
// Define an asynchronous function to retrieve article content
async function getArticleContent(params: any) {
  // Extract the slug parameter from the input params object
  const { slug } = params;
  
  // Create a Supabase client instance
  const supabase = createClient();
  
  // Query the "articles" table in Supabase
  // Select all columns (*)
  // Filter by the slug column matching the input slug
  // Retrieve a single record (not an array)
  const { data, error } = await supabase
    .from("articles")
    .select("*")
    .eq("slug", slug)
    .single();
  
  // Return the retrieved article data
  return data;
}
```

After that, update the functional component Post as shown below to render the article content.

```tsx
export default async function Post({ params }: { params: any }) {
  // Fetch the post content using the getArticleContent function
  const post = await getArticleContent(params);

  // Return the post component
  return (
    // Fragment component to wrap multiple elements
    <>
      // Header component
      <Header />
      // Main container with max width and height
      <main className="p-3 flex flex-col max-w-6xl mx-auto min-h-screen">
        // Post title
        <h1 className="text-3xl text-white mt-10 p-3 text-center font-serif max-w-2xl mx-auto lg:text-4xl">
          {post && post.title} // Display post title if available
        </h1>
        // Post metadata (author, date, etc.)
        <div className="flex justify-between text-white p-3 border-b border-slate-500 mx-auto w-full max-w-2xl text-xs">
          <span></span>
          // Estimated reading time
          <span className="italic">
            {post && (post.content.length / 1000).toFixed(0)} mins read
          </span>
        </div>
        // Post content
        <div
          className="p-3 max-w-2xl text-white mx-auto w-full post-content border-b border-slate-500 mb-2"
          // Use dangerouslySetInnerHTML to render HTML content
          dangerouslySetInnerHTML={{ __html: post && post.content }}></div>
        // Comment component
        <Comment />
      </main>
    </>
  );
}
```

Navigate to  [http://localhost:3000](http://localhost:3000) and click an article displayed on the blog homepage. You should then be redirected to the article’s content, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/byfdvwr7anceq09gto1g.png)

### **Setting up insert and fetch comment data with Supabase functionality**

Here, I will walk you through the process of setting up insert and fetch data functionality for the blog’s content comments using the Supabase database.

To get started, go to the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new) page in your Supabase dashboard. Then add the following SQL code to the editor and click CTRL + Enter keys to create a table called comments. The comments table has id, content, and postId columns.

```sql
create table if not exists
  comments (
    id bigint primary key generated always as identity,
    postId text,
    content text,
  );
```

Once the table is created, you should get a success message, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5h05jkk1g33ewtupsb5a.png)

After that, go to `/[root]/src/utils/supabase` folder and create a file called `AddComment.ts` . Then add the following code that inserts blog article comment data into the Supabase database to the file.

```tsx
// 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 'addComment' that takes form data as input
export async function addComment(formData: any) {
  // Extract postId and content from the provided form data
  const postId = formData.get("postId");
  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 'comments' table on Supabase
  const { data, error } = await supabase.from("comments").insert([
    {
      postId,
      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" };
}
```

Next, go to `/[root]/src/app/components/Comment.tsx` file, import the addArticle createClient functions.

```tsx

import { addComment } from "@/utils/supabase/AddComment";

import { createClient } from "@/utils/supabase/client";
```

Then add postId as prop parameter to Comment functional component.

```tsx
export default function Comment({ postId }: { postId: any }) {}
```

Inside the function, add the following code that uses uses the `useEffect` hook to fetch comments and article content from Supabase when the component mounts or when the `postId` changes. The `fetchComments` function fetches all comments, while the `fetchArticleContent` function fetches the content of the article with the current `postId`.

```tsx
useEffect(() => {
  // Define an async function to fetch comments
  const fetchComments = async () => {
    // Create a Supabase client instance
    const supabase = createClient();
    // Fetch comments from the "comments" table
    const { data, error } = await supabase.from("comments").select("*");
    // If data is available, update the comments state
    if (data) {
      setComments(data);
    }
  };

  // Define an async function to fetch article content
  const fetchArticleContent = async () => {
    // Create a Supabase client instance
    const supabase = createClient();
    // Fetch article content from the "articles" table
    // Filter by the current postId
    const { data, error } = await supabase
      .from("articles")
      .select("*")
      .eq("id", postId)
      .single();
    // If the fetched article ID matches the current postId
    if (data?.id == postId) {
      // Update the article content state
      setArticleContent(data.content);
    }
  };

  // Call the fetch functions
  fetchArticleContent();
  fetchComments();
}, [postId]); // Dependency array includes postId, so the effect runs when postId changes
```

Then add the `addComment` function as the form action parameter, as shown below.

```tsx
<form
          action={addComment}
          className="border border-teal-500 rounded-md p-3 mb-4">
          <textarea
            id="content"
            name="content"
            placeholder="Add a comment..."
            rows={3}
            onChange={(e) => setComment(e.target.value)}
            value={comment}
            className="hidden"
          />

          <CopilotTextarea
            className="p-4 w-full rounded-lg mb-2 border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500 resize-none"
            ref={copilotTextareaRef}
            placeholder="Start typing for content autosuggestion."
            onChange={(event) => setComment(event.target.value)}
            rows={5}
            autosuggestionsConfig={{
              textareaPurpose: articleContent,
              chatApiConfigs: {
                suggestionsApiConfig: {
                  forwardedParams: {
                    max_tokens: 5,
                    stop: ["\n", ".", ","],
                  },
                },
                insertionApiConfig: {},
              },
              debounceTime: 250,
            }}
          />
          <input
            type="text"
            id="postId"
            name="postId"
            value={postId}
            className="hidden"
          />
          <div className="flex justify-between items-center mt-5">
            <button
              type="submit"
              className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
              Submit
            </button>
          </div>
        </form>
```

Below the form element, add the following code that renders post comments.

```tsx
{comments?.map(
          (postComment: any) =>
            postComment.postId == postId && (
              <div
                key={postComment.id}
                className="flex p-4 border-b dark:border-gray-600 text-sm">
                <div className="flex-shrink-0 mr-3">
                  <Image
                    className="w-10 h-10 rounded-full bg-gray-200"
                    src={`https://source.unsplash.com/featured/?${encodeURIComponent(
                      "Silhouette"
                    )}`}
                    width={500}
                    height={500}
                    alt="Profile Picture"
                  />
                </div>
                <div className="flex-1">
                  <div className="flex items-center mb-1">
                    <span className="font-bold text-white mr-1 text-xs truncate">
                      Anonymous
                    </span>
                  </div>
                  <p className="text-gray-500 pb-2">{postComment.content}</p>
                </div>
              </div>
            )
        )}
```

Next, go to `/[root]/src/app/[slug]/page.tsx` file and add postId as a prop to the Comment component.

```tsx
<Comment postId={post && [post.id](http://post.id/)} />
```

Go to the published article content page and start typing a comment in the textarea. You should get content autosuggestion as you type.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m6br70wf3mhxzovgfet.png)

Then click the submit button to add your comment. Go to your project’s dashboard on Supabase and navigate to the Table Editor section. Click the comments table and you should see that your comment data was inserted into the Supabase database, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3j9c6cy82cw9fcao2pe9.png)

Go back to the published article content page you commented and you should see your comment, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n98o9kxqnyzm90tzvw40.png)

Congratulations! You’ve completed the project for this tutorial.

## Conclusion

[CopilotKit](https://copilotkit.ai/) is an incredible tool that allows you to add AI Copilots to your products within minutes. Whether you're interested in AI chatbots and assistants or automating complex tasks, CopilotKit makes it easy.

If you need to build an AI product or integrate an AI tool into your software applications, you should consider CopilotKit.

You can find the source code for this tutorial on GitHub: https://github.com/TheGreatBonnie/aiblogapp