Ensemble Docs

Custom Widgets

Render rich UI widgets in chat responses

The chat widget lets you define rich, interactive components to supplement the default markdown response.

Each widget requires:

  1. widgetType - A unique name for the widget
  2. schema - A Zod schema defining the payload structure
  3. render() - React code to render the widget
  4. enrich (optional) - Fetch additional data from Ensemble tools

Defining Custom Widgets

import { ChatWidget, createWidget } from '@ensembleapp/client-sdk';
import { z } from 'zod';

const widgets = [
  createWidget({
    widgetType: 'product-card',
    // Zod schema with describe() helps the agent generate correct data
    schema: z.object({
      name: z.string(),
      price: z.number(),
      imageUrl: z.string().optional(),
    }).describe('Display a product card'),
    render: (payload) => (
      <div className="border rounded p-4">
        {payload.imageUrl && <img src={payload.imageUrl} alt={payload.name} />}
        <h3>{payload.name}</h3>
        <p>${payload.price}</p>
      </div>
    ),
  }),
];

<ChatWidget
  api={{ baseUrl, token }}
  agentId="shopping-agent"
  threadId={threadId}
  widgets={widgets}
/>

Widget Enrichment

Sometimes you want to supplement the agent's response with additional data from your tools, without having the LLM echo back large datasets (which wastes tokens and can introduce errors).

Enrichment tools run server-side. The results are passed to your render() function alongside the agent's payload.

createWidget({
  widgetType: 'vendor-list',
  schema: z.object({
    // return just the list of IDs to save tokens/time
    vendorIds: z.array(z.string()),
  }),
  enrich: {
    // call the get-vendor-details tool with the list of vendorIds as input
    details: {
      toolId: 'get-vendor-details',
      inputs: {
        ids: "${vendorIds|join(',')}",  // template from payload
      },
    },
  },
  render: (payload, enriched) => (
    <VendorList
      ids={payload.vendorIds}
      details={enriched.details}
    />
  ),
})

The enrich config calls tools server-side. Results are passed to render() as the second argument.

On this page