Elements

Elements · Structured output

Activity graph

A half-year of runs as a calendar of cells, dense where the work was.

Agent runs1,743 in 6 months
TueThuSat
lessmore
fig. 01

Installation

npx shadcn@latest add "@assistant-ui/elements-activity-graph"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

Activity graph is a calendar of cells, tinted darker on days with more work, wrapping the heat-graph package's grid with a title, a total, and its own five-step tint scale. With a runtime the day counts come from a tool's result; standalone you pass the same data in directly.

Getting started

With a runtime, data is exactly the shape a tool would return: one count per day over some window. Register a tool that reports it and the graph needs nothing else.

Render the tool call

components/assistant-ui/elements/run-activity-tool-ui.tsx
"use client";

import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import type { DataPoint } from "heat-graph";
import { ActivityGraph } from "@/components/assistant-ui/elements/activity-graph";

type ActivityArgs = { days: number };
type ActivityResult = {
  title: string;
  total: string;
  start: string;
  end: string;
  data: DataPoint[];
};

export const RunActivityToolUI: ToolCallMessagePartComponent<
  ActivityArgs,
  ActivityResult
> = ({ result }) => {
  if (!result) return null;
  return (
    <ActivityGraph
      data={result.data}
      start={result.start}
      end={result.end}
      title={result.title}
      total={result.total}
    />
  );
};

Register the tool

app/toolkit.ts
import { defineToolkit } from "@assistant-ui/react";
import { z } from "zod";
import { RunActivityToolUI } from "@/components/assistant-ui/elements/run-activity-tool-ui";

export const toolkit = defineToolkit({
  get_run_activity: {
    type: "frontend",
    description: "Get daily run counts for the last N days as a heat map.",
    parameters: z.object({ days: z.number() }),
    execute: async ({ days }) => fetchRunActivity(days),
    render: RunActivityToolUI,
  },
});
app/MyRuntimeProvider.tsx
import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
import { toolkit } from "./toolkit";

export function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
  const runtime = useChatRuntime();
  const config = AuiConfig({ tools: Tools({ toolkit }) });
  return (
    <AssistantRuntimeProvider runtime={runtime} config={config}>
      {children}
    </AssistantRuntimeProvider>
  );
}

See Tool UI for backend-defined tools and approval gates.

Anatomy

<div data-slot="activity-graph">
  <div>{/* title, total */}</div>
  {/* HeatGraph.Root: day labels down the left, a grid of cells, a less-to-more legend */}
</div>

Each cell's shade comes from heat-graph's own level classification (0 through 4) for that DataPoint, mapped here onto a five-step blue tint; a level outside that range falls back to the lightest tint. Day labels only print on odd grid rows, one every other week, so the left edge does not crowd. The week always starts on Monday; that choice is fixed by this element and is not exposed as a prop.

Examples

Choosing the date range

start and end bound the grid; heat-graph fills in every day between them from data, including days with no matching point, which render at the lowest tint.

<ActivityGraph data={data} start={sixMonthsAgo} end={today} title="Agent runs" total={total} />

Restyle the graph

Both lanes take className on the root. The title reads plain text and the total reads the shared mono token from surfaces.tsx. The tint scale itself is a fixed array inside activity-graph.tsx, so recoloring the cells means editing that file directly.

<ActivityGraph className="max-w-none" /* ... */ />

API reference

Tool-call render props

PropTypeDescription
argsTArgsParsed arguments. Partial while the model is still streaming them.
argsTextstringRaw JSON argument text streamed by the model.
resultTResult | undefinedThe tool's return value once it completes. undefined while running.
statusToolCallMessagePartStatusstatus.type is "running", "requires-action", "complete", or "incomplete".
toolNamestringName of the tool the model called.
toolCallIdstringStable id for this invocation.
isErrorboolean | undefinedWhether result represents a tool execution error.

Register the renderer on a toolkit entry's render field and attach the toolkit with Tools({ toolkit }). See Tool UI for the full render-prop surface, including addResult, human tools, and approval gates.