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:
- widgetType - A unique name for the widget
- schema - A Zod schema defining the payload structure
- render() - React code to render the widget
- 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.