How I Upped My Frontend Game with Generative UI πŸ§‘β€πŸ’»πŸŒ 

uliyahoo

uliyahoo

Posted on August 14, 2024

How I Upped My Frontend Game with Generative UI πŸ§‘β€πŸ’»πŸŒ 

TL;DR

In this tutorial, you'll learn what Generative UI is and how to use it to provide a dynamic user experience within your apps.

You'll also learn how to build an interactive sales dashboard that allows you to add, update, and delete data using an AI copilot.

This practical guide will bring up to date to the cutting edge of frontend & AI-enabled application development. Your users will be grateful you honed these skills.

Image description


CopilotKit: The framework for building in-app AI copilots

CopilotKit is anΒ open-source AI copilot platform. 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 πŸ€–

Image description

Star CopilotKit ⭐️


What is Generative UI?

Generative UI refers to UI components that are dynamically generated and updated in real-time based on users' inputs. The AI-embedded software application listens to a user's prompt and generates a UI based on the given instruction. CopilotKit uses this feature to enable users to render React components in the copilot chat window.

Prerequisites

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

We'll also make use of the following tools:

  • CopilotKit - an open-source copilot framework for building custom AI chatbots, in-app AI agents, and text areas.

  • OpenAI API Key - provides an API key that enables us to carry out various tasks using ChatGPT models.

  • Shadcn/ui - a collection of customizable and reusable UI components.

  • Recharts - a chart library built specifically for React applications

Generative UI with CopilotKit


Project Set up and Package Installation

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



npx create-next-app generative-ui-with-copilotkit


Enter fullscreen mode Exit fullscreen mode

Install the CopilotKit packages. These packages enable the AI copilot to retrieve data from the React state and make decisions within the application.



npm install @copilotkit/react-ui @copilotkit/react-core @copilotkit/backend recharts


Enter fullscreen mode Exit fullscreen mode

Set up the shadcn/ui within the Next.js project by running the code snippet below:



npx shadcn-ui@latest init


Enter fullscreen mode Exit fullscreen mode

Configure the components.json file by answering the following installation questions:



Which style would you like to use? β€Ί Default
Which color would you like to use as base color? β€Ί Slate
Do you want to use CSS variables for colors? β€Ί > yes


Enter fullscreen mode Exit fullscreen mode

Congratulations! You should now have a components.json file within your Next.js project that outlines the configurations for Shadcn UI and components folder that will contain the various UI components.

Finally, add the following ShadCn components used within the application: Card, Chart, Checkbox, and Table components to the application.



npx shadcn-ui@latest add card
npx shadcn-ui@latest add chart
npx shadcn-ui@latest add checkbox
npx shadcn-ui@latest add table


Enter fullscreen mode Exit fullscreen mode

Building the Sales Dashboard with Next.js

In this section, you'll learn how to create a sales dashboard that visualizes data using the ShadCn UI components.

Sales Dashboard overview

First, create a types.d.ts file at the root of the Next.js project and copy the following code snippet into the file:



interface Todo {
    id: number;
    text: string;
    completed: boolean;
}
interface Invoice {
    id: number;
    status: "Paid" | "Pending" | "Overdue";
    amount: number;
    method: "Credit Card" | "Paypal" | "Bank Transfer";
}
interface Chart {
    month: string;
    sales: number;
    customers: number;
}


Enter fullscreen mode Exit fullscreen mode

The code snippet above defines the data structure of the various variables used within the application.

Add a components folder within the Next.js app folder and create App, Card, Chart, Checkbox, Nav, and Table components.



cd app
mkdir components && cd components
touch App.tsx Card.tsx Chart.tsx Checkbox.tsx Nav.tsx Table.tsx


Enter fullscreen mode Exit fullscreen mode

Update the App.tsx component to contain the necessary React states and function:



import { useState } from "react";
import ChartComponent from "@/app/components/Chart";
import CardComponent from "@/app/components/Card";
import TableComponent from "@/app/components/Table";
import CheckboxComponent from "@/app/components/Checkbox";
import NavComponent from "@/app/components/Nav";

export default function App() {
    //πŸ‘‡πŸ» a todo list
    const [todoList, setTodoList] = useState<Todo[]>([
        {
            id: 1,
            text: "Learn about CopilotKit implementation",
            completed: false,
        },
        {
            id: 2,
            text: "Remind Uli about the next project",
            completed: false,
        },
        {
            id: 3,
            text: "Send an invoice to CopilotKit team",
            completed: false,
        },
    ]);

    //πŸ‘‡πŸ» an invoice list
    const [invoiceList, setInvoiceList] = useState<Invoice[]>([
        {
            id: 1,
            status: "Pending",
            amount: 1000,
            method: "Credit Card",
        },
        {
            id: 2,
            status: "Paid",
            amount: 2000,
            method: "Paypal",
        },
        {
            id: 3,
            status: "Overdue",
            amount: 3000,
            method: "Bank Transfer",
        },
    ]);

  //πŸ‘‡πŸ» the chart data
    const [chartData, setChartData] = useState<Chart[]>([
        { month: "January", sales: 350, customers: 80 },
        { month: "February", sales: 200, customers: 30 },
        { month: "March", sales: 1500, customers: 120 },
        { month: "April", sales: 1050, customers: 190 },
        { month: "May", sales: 1200, customers: 130 },
        { month: "June", sales: 550, customers: 140 },
        { month: "July", sales: 1200, customers: 130 },
    ]);

    //πŸ‘‡πŸ» calculates the total sales and number of customers
    const calculateTotal = (key: keyof Chart): number => {
        if (key === "sales")
            return chartData.reduce((acc, item) => acc + item.sales, 0);
        return chartData.reduce((acc, item) => acc + item.customers, 0);
    };

    return (/**-- πŸ‘‰πŸ» UI components πŸ‘ˆπŸΌ ---*/)
}


Enter fullscreen mode Exit fullscreen mode

Render the following UI components from the App component, each designed to display the invoices, to-do tasks, and sales data.



export default function App() {
    //πŸ‘‰πŸ» the states and functions

    return (
        <main>
            <NavComponent />
            <div className='w-full flex items-center justify-between p-4 md:flex-row space-x-4'>
                <div className='lg:w-1/2 h-[300px] lg:mb-0 mb-4 w-full'>
                    <CardComponent
                        invoiceLength={invoiceList.length}
                        todoLength={todoList.length}
                        totalCustomers={calculateTotal("customers")}
                        totalSales={calculateTotal("sales")}
                    />
                </div>
                <div className='lg:w-1/2  h-[300px] w-full lg:mb-0 mb-4 '>
                    <ChartComponent chartData={chartData} />
                </div>
            </div>
            <div className='w-full flex flex-row items-center justify-between lg:space-x-4 p-4'>
                <div className='lg:w-1/2 w-full h-full lg:mb-0 mb-8'>
                    <TableComponent invoiceList={invoiceList} />
                </div>
                <div className='lg:w-1/2 w-full h-full lg:mb-0 mb-4'>
                    <CheckboxComponent todoList={todoList} setTodoList={setTodoList} />
                </div>
            </div>
        </main>
    );
}


Enter fullscreen mode Exit fullscreen mode

Finally, you can copy the various dashboard components from the GitHub repository.


How to Add CopilotKit to a Next.js application

In this section, you'll learn how to add CopilotKit to the application to enable users to add, delete, and update the data automatically using AI copilots.

Before we proceed, visit the OpenAI Developers' Platform and create a new secret key.

OpenAI API key

Create a .env.local file and copy the your newly created secret key into the file.



OPENAI_API_KEY=<YOUR_OPENAI_SECRET_KEY>
OPENAI_MODEL=gpt-4-1106-preview


Enter fullscreen mode Exit fullscreen mode

Next, you need to create an API endpoint for CopilotKit. Within the Next.js app folder, create an api/copilotkit folder containing a route.ts file.



cd app
mkdir api && cd api
mkdir copilotkit && cd copilotkit
touch route.ts


Enter fullscreen mode Exit fullscreen mode

Copy the code snippet below into the route.ts file. The CopilotKit backend accept users’ requests and make decisions using the OpenAI model.



import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/backend";

export const runtime = "edge";

export async function POST(req: Request): Promise<Response> {
    const copilotKit = new CopilotRuntime({});
    const openaiModel = process.env["OPENAI_MODEL"];
    return copilotKit.response(req, new OpenAIAdapter({ model: openaiModel }));
}


Enter fullscreen mode Exit fullscreen mode

To connect the application to the backend API route, copy the code snippet below into the app/page.tsx file.



"use client"
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
import "@copilotkit/react-textarea/styles.css";
import App from "./components/App";

export default function Home() {
    return (
        <CopilotKit runtimeUrl='/api/copilotkit/'>
            <App />
            <CopilotPopup
                instructions='Help the user update and manipulate data on the chart, table, todo, and card components.'
                defaultOpen={true}
                labels={{
                    title: "Data Visualization Copilot",
                    initial:
                        "Hello there! I can help you add, edit, and remove data from the various components on the page. You can update the chat, table, and todo list. Let's get started!",
                }}
                clickOutsideToClose={false}
            ></CopilotPopup>
        </CopilotKit>
    );
}


Enter fullscreen mode Exit fullscreen mode

The CopilotKit component wraps the entire application and accepts a runtimeUrl prop that contains a link to the API endpoint. The CopilotKitPopup component adds a chatbot sidebar panel to the application, enabling us to provide various instructions and perform various actions using the AI copilot.

Dashboard App Overview with CopilotKit


Leveraging Generative UI and CopilotKit for Interactive Actions

CopilotKit provides two hooks that enable us to handle user's request and plug into the application state: useCopilotAction and useCopilotReadable.

The useCopilotAction hook allows you to define actions to be carried out by CopilotKit. It accepts an object containing the following parameters:

  • name - the action's name.
  • description - the action's description.
  • parameters - an array containing the list of the required parameters.
  • render - a string or function that returns a UI component.
  • handler - the executable function that is triggered by the action.


useCopilotAction({
    name: "sayHello",
    description: "Say hello to someone.",
    parameters: [
        {
            name: "name",
            type: "string",
            description: "name of the person to say greet",
        },
    ],
    render: "Process greeting message...",
    handler: async ({ name }) => {
        alert(`Hello, ${name}!`);
    },
});


Enter fullscreen mode Exit fullscreen mode

The render attribute allows us to dynamically display React components based on user input and the current status of an action, facilitating the use of Generative UI with CopilotKit. You'll learn how to implement this feature shortly.

The useCopilotReadable hook provides the application state to CopilotKit.



import { useCopilotReadable } from "@copilotkit/react-core";

const myAppState = "...";
useCopilotReadable({
  description: "The current state of the app",
  value: myAppState
});


Enter fullscreen mode Exit fullscreen mode

Now, let’s plug the application states into CopilotKit.

Within the App.tsx component, pass the chartData, invoiceList, and todoList states into CopilotKit.



   //πŸ‘‡πŸ» pass Chart data to CopilotKit
    useCopilotReadable({
        description:
            "The chart data is a list of sales and customers data for each month. You can update the data for each month. It contains the month, sales, and customers data.",
        value: chartData,
    });

 //πŸ‘‡πŸ» pass invoice data to CopilotKit
    useCopilotReadable({
        description: "The invoice list is a list of invoices that need to be paid. You can add, edit, and remove invoices from the list and also update the status of the invoice. An invoice status can either be Paid, Pending, or Overdue. The acceptable payment methods are Credit Card, Paypal, and Bank Transfer.",
        value: invoiceList,
    });

 //πŸ‘‡πŸ» pass todolist data to CopilotKit
    useCopilotReadable({
        description: "The todo list is a list of tasks that need to be completed. You can add, edit, and remove tasks from the list.",
        value: todoList,
    });


Enter fullscreen mode Exit fullscreen mode

Automating Various Actions with CopilotKit

We need to allow users to create, update, and delete data from the application. Therefore, let's create actions that do the following using the useCopilotAction hook:

  • update the chart data,
  • create new invoices,
  • delete an invoice,
  • update a todo status,
  • create new todo,
  • delete a todo.

Add an action that updates the chartData to the App.tsx file:



   //πŸ‘‡πŸ» action to update chartData
    useCopilotAction({
        name: "updateChartData",
        description: "Update the chart data for the a particular month.",
        parameters: [
            {
                name: "month",
                type: "string",
                description: "The month to update the data for.",
                required: true,
            },
            {
                name: "sales",
                type: "number",
                description: "The sales data for the month.",
                required: true,
            },
            {
                name: "customers",
                type: "number",
                description: "The customers data for the month.",
                required: true,
            },
        ],
        render: ({ status, args }) => {
            const { month, sales, customers } = args;
            if (month === undefined || sales === undefined || customers === undefined) return "";
            if (typeof month !== "string" || typeof sales !== "number" || typeof customers !== "number") return "";

            const updateChart = () => {
                setChartData((prev)  => {
                    return prev.map((item) => {
                        if (item.month === month) {
                            return { month, sales, customers };
                        }
                        return item;
                    });
                });
            };
            return (
                <div className="w-full p-2">
                    <p className="text-sm text-blue-400 mb-2">Status: {status}</p>
                    <ChartComponent chartData={[{month, sales, customers}]} />
                    <button className="px-4 py-2 bg-blue-400 text-white shadow rounded-md" onClick={updateChart}>Update</button>
                </div>
            )
        },
        handler: async () => {
            // Do nothing
        },
    });


Enter fullscreen mode Exit fullscreen mode
  • From the code snippet above,
    • The action accepts an array of parameters that describes the attributes of the chartData React state.
    • The render property displays the result of the user's prompt and a button that allows the user to add the data to the page.

Generative UI with CopilotKit

Next, create the actions that create and delete invoices from the application.



//πŸ‘‡πŸ» action to add new invoice
    useCopilotAction({
        name: "addNewInvoice",
        description: "Add new invoices to the invoice list",
            parameters: [
                {
                    name: "status",
                    type: "string",
                    description: "The status of the invoice.",
                    required: true,
                },
                {
                    name: "amount",
                    type: "number",
                    description: "The amount of the invoice.",
                    required: true,
                },
                {
                    name: "method",
                    type: "string",
                    description: "The payment method of the invoice.",
                    required: true,
                },
        ],
        render: ({ status: fetchStatus, args }) => {
            const { amount, method, status } = args;
            if (method !== "Credit Card" && method !== "Paypal" && method !== "Bank Transfer") return "";
            if (status !== "Paid" && status !== "Pending" && status !== "Overdue") return "";
            if (amount === undefined) return "";

            const addInvoice = () => {
                 setInvoiceList((prev ) => {
                    return [...prev, { id: prev.length + 1, status, amount, method }];
                });
            };
            return (
                <div className="w-full p-2">
                    <p className="text-sm text-blue-400 mb-2">Status: {fetchStatus}</p>
                    {status && amount !== undefined && method && <TableComponent invoiceList={[{ id: invoiceList.length + 1, status, amount, method }]} />}
                    <button className="px-4 py-2 bg-blue-400 text-white shadow rounded-md" onClick={addInvoice}>Add to Page</button>
                </div>
            )
         },
        handler: async ({ status, amount, method }) => {
            //Do nothing
    })

    //πŸ‘‡πŸ» action to delete invoices
    useCopilotAction({
        name: "deleteInvoice",
        description: "Remove invoices to the invoice list",
            parameters: [
                {
                    name: "id",
                    type: "number",
                    description: "The id of the invoice to remove.",
                    required: true,
                },
        ],
        render: ({ status, args }) => {
            const { id } = args;
            if (id === undefined) return "";
            const getInvoice = invoiceList.find((item) => item.id === id);
            if (!getInvoice) return ""

            const deleteInvoice = () => { 
                setInvoiceList((prev) => {
                    return prev.filter((item) => item.id !== id);
                });
            }

            return (
                <div className="w-full p-2">
                    <p className="text-sm text-blue-400 mb-2">Status: {status}</p>
                    <TableComponent invoiceList={[getInvoice]} />
                    <button className="px-4 py-2 bg-blue-400 text-white shadow rounded-md" onClick={deleteInvoice}>Delete</button>
                </div>

            )
        },
        handler: async ({) => {
            // Do nothing
        }
    })


Enter fullscreen mode Exit fullscreen mode

The addNewInvoice and deleteInvoice actions render React components (Generative UI) after executing the user's requests, allowing the user to add the result to the page by clicking a button.

Add & Delete Invoices using Generative UI in CopilotKit

Finally, add the actions that create, update, and delete todos.



 //πŸ‘‡πŸ» action to update todo status
    useCopilotAction({
        name: "toggleTodo",
        description: "Toggle the completion status of a todo item.",
        parameters: [
            {
                name: "id",
                type: "number",
                description: "The id of the todo item to toggle.",
                required: true,
            },
        ],
        render: ({ status, args }) => {
            const { id } = args;
            if (id === undefined) return "";
            const getTodo = todoList.find((item) => item.id === id);
            if (!getTodo) return "";

            const toggleTodo = () => {
                setTodoList(
                    todoList.map((todo) => {
                        if (todo.id === id) {
                            return { ...todo, completed: !todo.completed };
                        }
                        return todo;
                    })
                );
            };
            return (
                <div className="w-full p-2">
                    <p className="text-sm text-blue-400 mb-2">Status: {status}</p>
                    <CheckboxComponent todoList={[getTodo]} setTodoList={setTodoList} />
                    <button className="px-4 py-2 bg-blue-400 text-white shadow rounded-md" onClick={toggleTodo}>Toggle Todo</button>
                </div>
            )

        },
        handler: async () => {
            // Do nothing
        },
    })

    //πŸ‘‡πŸ» action to add new todo
    useCopilotAction({
        name: "addNewTodo",
        description: "Add new todo to the todo list",
        parameters: [
            {
                name: "text",
                type: "string",
                description: "The text of the todo item.",
                required: true,
            },
        ],
        render: ({ status, args }) => {
            const { text } = args;
            if (text === undefined) return "";

            const addTodo = () => {
                setTodoList((prev) => {
                    return [...prev, { id: prev.length + 1, text, completed: false }];
                });
            };
            return (
                <div className="w-full p-2">
                    <p className="text-sm text-blue-400 mb-2">Status: {status}</p>
                    <CheckboxComponent todoList={[...todoList, { id: todoList.length + 1, text, completed: false }]} setTodoList={setTodoList} />
                    <button className="px-4 py-2 bg-blue-400 text-white shadow rounded-md" onClick={addTodo}>Add to Page</button>
                </div>
            )

        },
        handler: async () => {
            // Do nothing
        },
    });

    //πŸ‘‡πŸ» action to delete todo
    useCopilotAction({
        name: "deleteTodo",
        description: "Remove todo from the todo list",
        parameters: [
            {
                name: "id",
                type: "number",
                description: "The id of the todo item to remove.",
                required: true,
            },
        ],
        render: ({ status, args }) => { 
            const { id } = args;
            if (id === undefined) return "";
            const getTodo = todoList.find((item) => item.id === id);
            if (!getTodo) return "";

            const deleteTodo = () => {
                setTodoList((prev) => {
                    return prev.filter((item) => item.id !== id);
                });
            };
            return (
                <div className="w-full p-2">
                    <p className="text-sm text-blue-400 mb-2">Status: {status}</p>
                    <CheckboxComponent todoList={[getTodo]} setTodoList={setTodoList} />
                    <button className="px-4 py-2 bg-red-500 text-white shadow rounded-md" onClick={deleteTodo}>Delete</button>
                </div>
            )
        },
        handler: async ({ id }) => {
            // Do nothing
        },
    });


Enter fullscreen mode Exit fullscreen mode

The toggleTodo action toggles the status of a todo. The addNewTodo action creates a new todo, and the deleteTodo action deletes a todo via its ID.

Demo showing how CopilotKit Generative UI works

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

Here is a brief demo of the application:


Conclusion

CopilotKit 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/dha-stix/interactive-sales-dashboard-with-copilokit

Thank you for reading!

πŸ’– πŸ’ͺ πŸ™… 🚩
uliyahoo
uliyahoo

Posted on August 14, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related