Agent activity
The agent runs for the organization with their steps and the approvals that wait for a person. It loads its data on the server through the block's client.

Add the components
terminal npx shadcn add badge empty skeletonInstall the glue
Install the block with
npx better-supabase add agents. The glue items are copied into your app, so edit them as you need.None: the page calls the block's client on the server.
Write the component
React and shadcn
View in the exampleagent-activity.tsx import { ActivityIcon, InboxIcon } from "lucide-react"; import { getExtracted, getFormatter } from "next-intl/server"; import { Badge } from "@/components/ui/badge"; import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from "@/components/ui/empty"; import { Skeleton } from "@/components/ui/skeleton"; import { Link } from "@/i18n/navigation"; import { getActivity } from "../assistant-queries"; import { ApprovalButtons } from "./approval-buttons"; /** Render inside `<Suspense>`: the approval inbox, then the latest runs with their steps. */ export async function AgentActivity() { const [{ runs, approvals }, t, format] = await Promise.all([ getActivity(), getExtracted("assistant"), getFormatter(), ]); const when = (iso: string) => format.dateTime(new Date(iso), { dateStyle: "medium", timeStyle: "short" }); return ( <div className="flex flex-col gap-6"> <section className="flex flex-col gap-3" aria-labelledby="approvals"> <h2 id="approvals" className="text-sm font-medium"> {t("Waiting for approval")} </h2> {approvals.length === 0 ? ( <Empty className="border"> <EmptyHeader> <EmptyMedia variant="icon"> <InboxIcon /> </EmptyMedia> <EmptyTitle>{t("Nothing to approve")}</EmptyTitle> <EmptyDescription> {t("Tool calls that need your approval show up here.")} </EmptyDescription> </EmptyHeader> </Empty> ) : ( <ul className="divide-y rounded-xl border"> {approvals.map((approval) => ( <li key={approval.approvalId} className="flex flex-col gap-2 px-4 py-3 text-sm" > <p className="flex flex-wrap items-center gap-2"> <span className="font-medium">{approval.tool}</span> <Link href={`/assistant/${approval.chatId}`} className="text-muted-foreground underline" > {approval.chatTitle === "" ? t("Untitled chat") : approval.chatTitle} </Link> <span className="text-xs text-muted-foreground tabular-nums"> {when(approval.createdAt)} </span> </p> <pre className="overflow-x-auto rounded-md bg-muted p-2 text-xs whitespace-pre-wrap"> {approval.input} </pre> <ApprovalButtons chatId={approval.chatId} approvalId={approval.approvalId} /> </li> ))} </ul> )} </section> <section className="flex flex-col gap-3" aria-labelledby="runs"> <h2 id="runs" className="text-sm font-medium"> {t("Latest runs")} </h2> {runs.length === 0 ? ( <Empty className="border"> <EmptyHeader> <EmptyMedia variant="icon"> <ActivityIcon /> </EmptyMedia> <EmptyTitle>{t("No runs yet")}</EmptyTitle> <EmptyDescription> {t("Every answer the assistant writes is a run.")} </EmptyDescription> </EmptyHeader> </Empty> ) : ( <ul className="divide-y rounded-xl border"> {runs.map((run) => ( <li key={run.id} className="flex flex-col gap-2 px-4 py-3 text-sm" > <p className="flex flex-wrap items-center gap-2"> <Badge variant="outline">{run.status}</Badge> <Badge variant="secondary"> {run.engine === "workflow" ? t("Durable") : t("Standard")} </Badge> <Link href={`/assistant/${run.chatId}`} className="flex-1 truncate underline" > {run.model ?? t("Default model")} </Link> <span className="text-xs text-muted-foreground tabular-nums"> {when(run.startedAt)} </span> </p> {run.steps.length > 0 ? ( <ol className="flex flex-col gap-1 ps-4 text-xs text-muted-foreground"> {run.steps.map((step) => ( <li key={step.key} className="flex gap-2"> <span className="w-14 shrink-0">{step.status}</span> <span className="truncate">{step.label}</span> </li> ))} </ol> ) : null} </li> ))} </ul> )} </section> </div> ); } export function AgentActivitySkeleton() { return <Skeleton className="h-96 w-full rounded-xl" />; }