Skip to main content

Overview

This guide demonstrates how to build an analytics dashboard where users can interact with data through natural language. The AI generates appropriate visualizations and provides insights based on user queries.

Architecture

An analytics dashboard with Tambo consists of:
  1. Dashboard Layout - Grid or flex layout for organizing visualizations
  2. Graph Components - Charts registered with the AI for data visualization
  3. Data Context - Real-time data passed to the AI for analysis
  4. Chat Interface - Natural language queries for data exploration
  5. Control Panel - Filters and settings for data customization

Basic Dashboard Setup

Start with a simple dashboard layout:
import { TamboProvider } from "@tambo-ai/react";
import { Graph, graphSchema } from "@tambo-ai/ui-registry/components/graph";
import type { TamboComponent } from "@tambo-ai/react";

const components: TamboComponent[] = [
  {
    name: "Graph",
    description: `Displays data as bar charts, line charts, or pie charts. 
      Use for sales data, analytics, trends, and comparative data visualization.`,
    component: Graph,
    propsSchema: graphSchema,
  },
];

function AnalyticsDashboard() {
  return (
    <TamboProvider
      apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
      userKey="user-123"
      components={components}
    >
      <div className="h-screen flex flex-col">
        <header className="border-b p-4">
          <h1 className="text-2xl font-semibold">Analytics Dashboard</h1>
        </header>
        <main className="flex-1 p-6">
          <DashboardContent />
        </main>
      </div>
    </TamboProvider>
  );
}

export default AnalyticsDashboard;

Adding Data Context

Provide real-time data context to help the AI generate accurate visualizations:
import { TamboProvider } from "@tambo-ai/react";
import { useState, useEffect } from "react";

// Mock data fetching
const fetchAnalyticsData = async () => {
  return {
    revenue: { current: 125000, previous: 110000 },
    users: { current: 5420, previous: 4890 },
    conversion: { current: 3.2, previous: 2.8 },
    topProducts: [
      { name: "Product A", sales: 45000 },
      { name: "Product B", sales: 38000 },
      { name: "Product C", sales: 25000 },
    ],
  };
};

function DashboardWithContext() {
  const [analyticsData, setAnalyticsData] = useState(null);

  useEffect(() => {
    fetchAnalyticsData().then(setAnalyticsData);
  }, []);

  return (
    <TamboProvider
      apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
      userKey="user-123"
      components={components}
      contextHelpers={{
        currentMetrics: () => ({
          key: "metrics",
          value: analyticsData ? JSON.stringify(analyticsData) : "Loading...",
        }),
        dateRange: () => ({
          key: "dateRange",
          value: "Last 30 days",
        }),
      }}
    >
      <DashboardLayout />
    </TamboProvider>
  );
}

Dashboard Layout with Charts

Create a responsive grid layout for visualizations:
import { useTambo } from "@tambo-ai/react";
import { MessageThreadPanel } from "@tambo-ai/ui-registry/components/message-thread-panel";
import { Graph } from "@tambo-ai/ui-registry/components/graph";

function DashboardLayout() {
  return (
    <div className="h-full grid grid-cols-1 lg:grid-cols-3 gap-6">
      {/* Main Chart Area */}
      <div className="lg:col-span-2 space-y-6">
        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
          <MetricCard
            title="Revenue"
            value="$125K"
            change="+13.6%"
            trend="up"
          />
          <MetricCard
            title="Active Users"
            value="5,420"
            change="+10.8%"
            trend="up"
          />
          <MetricCard
            title="Conversion Rate"
            value="3.2%"
            change="+14.3%"
            trend="up"
          />
          <MetricCard
            title="Avg. Order Value"
            value="$87"
            change="-2.1%"
            trend="down"
          />
        </div>

        {/* AI-Generated Charts Appear Here */}
        <div className="border rounded-lg p-6 min-h-96">
          <h2 className="text-xl font-semibold mb-4">Visualizations</h2>
          {/* Charts generated by AI will render here */}
        </div>
      </div>

      {/* AI Chat Panel */}
      <div className="lg:col-span-1">
        <MessageThreadPanel className="h-full" />
      </div>
    </div>
  );
}

function MetricCard({ title, value, change, trend }: {
  title: string;
  value: string;
  change: string;
  trend: "up" | "down";
}) {
  return (
    <div className="border rounded-lg p-6">
      <p className="text-sm text-muted-foreground mb-2">{title}</p>
      <p className="text-3xl font-semibold mb-2">{value}</p>
      <p
        className={`text-sm ${
          trend === "up" ? "text-green-600" : "text-red-600"
        }`}
      >
        {change}
      </p>
    </div>
  );
}

Interactive Chart Generation

Register multiple visualization types for different data scenarios:
import { useEffect } from "react";
import { useTambo } from "@tambo-ai/react";
import { Graph, graphSchema } from "@tambo-ai/ui-registry/components/graph";

function TimeSeriesDashboard() {
  const { registerComponent, currentThreadId } = useTambo();

  useEffect(() => {
    registerComponent({
      name: "Graph",
      description: `Line chart for time-series data. Shows trends over time.
        Perfect for revenue trends, user growth, performance metrics over days/weeks/months.
        Use when users ask about trends, changes over time, or historical data.`,
      component: Graph,
      propsSchema: graphSchema,
    });
  }, [registerComponent, currentThreadId]);

  return <MessageThreadPanel />;
}

Adding Filters and Controls

Implement dynamic filters that update the AI context:
import { useState } from "react";
import { TamboProvider } from "@tambo-ai/react";

function DashboardWithFilters() {
  const [dateRange, setDateRange] = useState("30d");
  const [region, setRegion] = useState("all");
  const [metric, setMetric] = useState("revenue");

  return (
    <TamboProvider
      apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
      userKey="user-123"
      components={components}
      contextHelpers={{
        filters: () => ({
          key: "activeFilters",
          value: JSON.stringify({ dateRange, region, metric }),
        }),
      }}
    >
      <div className="space-y-6">
        {/* Filter Controls */}
        <div className="flex gap-4 border-b pb-4">
          <select
            value={dateRange}
            onChange={(e) => setDateRange(e.target.value)}
            className="border rounded px-3 py-2"
          >
            <option value="7d">Last 7 days</option>
            <option value="30d">Last 30 days</option>
            <option value="90d">Last 90 days</option>
            <option value="1y">Last year</option>
          </select>

          <select
            value={region}
            onChange={(e) => setRegion(e.target.value)}
            className="border rounded px-3 py-2"
          >
            <option value="all">All Regions</option>
            <option value="us">United States</option>
            <option value="eu">Europe</option>
            <option value="apac">Asia Pacific</option>
          </select>

          <select
            value={metric}
            onChange={(e) => setMetric(e.target.value)}
            className="border rounded px-3 py-2"
          >
            <option value="revenue">Revenue</option>
            <option value="users">Active Users</option>
            <option value="conversion">Conversion Rate</option>
          </select>
        </div>

        <DashboardLayout />
      </div>
    </TamboProvider>
  );
}

Real-time Data Updates

Update visualizations when data changes:
import { useState, useEffect } from "react";
import { useTambo } from "@tambo-ai/react";

function RealtimeDashboard() {
  const [data, setData] = useState(null);
  const { updateContext } = useTambo();

  useEffect(() => {
    const interval = setInterval(async () => {
      const newData = await fetchAnalyticsData();
      setData(newData);
      
      // Update context so AI has latest data
      updateContext({
        key: "liveMetrics",
        value: JSON.stringify(newData),
      });
    }, 30000); // Update every 30 seconds

    return () => clearInterval(interval);
  }, [updateContext]);

  return (
    <div className="space-y-6">
      <div className="flex items-center gap-2">
        <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
        <span className="text-sm text-muted-foreground">Live</span>
      </div>
      <DashboardLayout />
    </div>
  );
}

Complete Dashboard Example

Here’s a production-ready analytics dashboard:
import {
  TamboProvider,
  useTambo,
  type TamboComponent,
} from "@tambo-ai/react";
import { MessageThreadPanel } from "@tambo-ai/ui-registry/components/message-thread-panel";
import { Graph, graphSchema } from "@tambo-ai/ui-registry/components/graph";
import { useState, useEffect } from "react";
import { z } from "zod";

const components: TamboComponent[] = [
  {
    name: "Graph",
    description: `Versatile data visualization for analytics dashboards.
      Supports bar charts (comparisons), line charts (trends), and pie charts (distributions).
      Automatically formats data, adds legends, and handles responsive sizing.
      Use when users ask to visualize, chart, graph, or show data.`,
    component: Graph,
    propsSchema: graphSchema,
  },
];

function Dashboard() {
  const [filters, setFilters] = useState({
    dateRange: "30d",
    region: "all",
  });
  const [metrics, setMetrics] = useState(null);

  useEffect(() => {
    fetchAnalyticsData(filters).then(setMetrics);
  }, [filters]);

  return (
    <TamboProvider
      apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
      userKey="user-123"
      components={components}
      contextHelpers={{
        currentFilters: () => ({
          key: "filters",
          value: JSON.stringify(filters),
        }),
        metrics: () => ({
          key: "metrics",
          value: metrics ? JSON.stringify(metrics) : "Loading...",
        }),
      }}
    >
      <div className="h-screen flex flex-col">
        <header className="border-b p-4">
          <h1 className="text-2xl font-semibold">Analytics Dashboard</h1>
        </header>

        <main className="flex-1 overflow-hidden">
          <div className="h-full grid grid-cols-1 lg:grid-cols-3 gap-6 p-6">
            {/* Main Content */}
            <div className="lg:col-span-2 space-y-6 overflow-y-auto">
              {/* Filters */}
              <div className="flex gap-4">
                <select
                  value={filters.dateRange}
                  onChange={(e) =>
                    setFilters({ ...filters, dateRange: e.target.value })
                  }
                  className="border rounded px-3 py-2"
                >
                  <option value="7d">Last 7 days</option>
                  <option value="30d">Last 30 days</option>
                  <option value="90d">Last 90 days</option>
                </select>
                <select
                  value={filters.region}
                  onChange={(e) =>
                    setFilters({ ...filters, region: e.target.value })
                  }
                  className="border rounded px-3 py-2"
                >
                  <option value="all">All Regions</option>
                  <option value="us">United States</option>
                  <option value="eu">Europe</option>
                </select>
              </div>

              {/* Metric Cards */}
              {metrics && (
                <div className="grid grid-cols-2 gap-4">
                  <MetricCard
                    title="Revenue"
                    value={`$${metrics.revenue.toLocaleString()}`}
                    change="+13.6%"
                  />
                  <MetricCard
                    title="Users"
                    value={metrics.users.toLocaleString()}
                    change="+10.8%"
                  />
                </div>
              )}
            </div>

            {/* AI Chat Panel */}
            <div className="lg:col-span-1">
              <MessageThreadPanel className="h-full" />
            </div>
          </div>
        </main>
      </div>
    </TamboProvider>
  );
}

function MetricCard({ title, value, change }: {
  title: string;
  value: string;
  change: string;
}) {
  return (
    <div className="border rounded-lg p-6">
      <p className="text-sm text-muted-foreground mb-2">{title}</p>
      <p className="text-3xl font-semibold mb-2">{value}</p>
      <p className="text-sm text-green-600">{change}</p>
    </div>
  );
}

export default Dashboard;

Best Practices

  • Keep context data concise and relevant
  • Update context when filters change
  • Include data summaries rather than raw datasets
  • Format numbers for readability in context
  • Provide clear component descriptions mentioning use cases
  • Use appropriate chart types for different data patterns
  • Include legends and labels for clarity
  • Support responsive sizing for mobile devices
  • Cache analytics data when possible
  • Debounce filter changes to reduce API calls
  • Use skeleton loaders during data fetching
  • Implement virtual scrolling for large datasets
  • Provide example queries to guide users
  • Show loading states during data updates
  • Validate date ranges and filter combinations
  • Export visualizations as images or PDFs

Next Steps

Build docs developers (and LLMs) love