conversational-assistant/config/ui/components.tsx (226 lines of code) (raw):
// Define components that will be used by the generate_ui tool
// Updates the componentsMap object to map React components to the components defined in config/components-definition.ts
import React from 'react'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '@/components/ui/table'
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { ChartConfig, ChartContainer } from '@/components/ui/chart'
import { getComponent } from '@/lib/components-mapping'
import { addToCart, selectOrder } from '@/config/user-actions'
import { Button } from '@/components/ui/button'
const formatKey = (key: string) =>
key.toLowerCase().replace(/[^a-zA-Z0-9]/g, '_')
const getChartConfig = (columns: { label: string; value: number }[]) => {
const config: ChartConfig = {}
columns.forEach((item: { label: string; value: number }) => {
config[formatKey(item.label)] = {
label: item.label,
color: '#ffffff'
}
})
return config
}
const getChartData = (columns: { label?: string; value?: string }[]) => {
return columns
.filter(item => !!item.label)
.map((item: { label?: string; value?: string }, index: number) => {
if (!item.label) {
throw new Error('Label is required')
}
return {
id: index,
label: item.label,
value: item.value !== undefined ? parseFloat(item.value) : 0,
fill: '#000000'
}
})
}
export const HeaderComponent = ({ content }: { content?: string }) => {
return (
<div>
<h1 className="text-sm text-stone-900 font-medium">{content}</h1>
</div>
)
}
export const BarChartComponent = ({
columns
}: {
columns?: { label?: string; value?: string }[]
}) => {
if (!columns) return null
const chartData = getChartData(columns)
const chartConfig = getChartConfig(chartData)
return (
<ChartContainer config={chartConfig} className="">
<BarChart accessibilityLayer data={chartData}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="label"
tickLine={false}
tickMargin={10}
axisLine={false}
padding={{ left: 10, right: 10 }}
/>
<YAxis orientation="left" width={24} />
<Bar dataKey="value" fill="#1A535C" radius={6} barSize={30} />
</BarChart>
</ChartContainer>
)
}
export const TableComponent = ({
columns,
rows
}: {
columns?: { key?: string; title?: string }[]
rows?: any[]
}) => (
<Table>
<TableHeader>
<TableRow>
{columns?.map((column, index) => (
<TableHead key={index}>{column.title}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{rows?.map((row, index) => (
<TableRow key={index}>
{row.values?.map((value: string, index: number) => (
<TableCell key={index}>{value}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
)
export const ItemComponent = ({
id,
item_name,
primary_image,
description,
price
}: any) => (
<div className="flex flex-col mb-3 gap-2 justify-between border border-gray-200 bg-gray-50 p-4 rounded-lg flex-shrink-0 w-52 h-96 overflow-x-auto">
<div className="flex flex-col">
<div className="aspect-h-1 aspect-w-1 rounded-lg overflow-hidden text-center h-48">
{primary_image && primary_image.match(/\.(jpeg|jpg|gif|png|webp)$/) ? (
<img
src={`/imgs/${primary_image}`}
alt={item_name || 'Product Image'}
className="w-full h-auto object-cover object-center rounded-lg"
/>
) : (
<div className="animate-pulse bg-gray-200 h-full w-full rounded-lg"></div>
)}
</div>
<div className="flex flex-col gap-1 justify-start">
<h3 className="text-sm font-semibold text-gray-700 line-clamp-2">
{item_name ?? ''}
</h3>
<p className="text-xs text-gray-500 line-clamp-3">
{description ?? ''}
</p>
</div>
</div>
<div className="flex justify-start">
{typeof price === 'number' && !isNaN(price) ? (
<span className="font-medium text-gray-900">${price.toFixed(2)}</span>
) : null}
</div>
<Button size="sm" onClick={() => addToCart(id)}>
Add to cart
</Button>
</div>
)
export const OrderComponent = ({ id, total, date, status, products }: any) => (
<div className="flex flex-col gap-2 mb-3">
<div className="flex flex-col justify-between rounded-lg border bg-white p-4 w-96 h-72 flex-shrink-0">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between text-gray-800">
<div className="flex items-center gap-2">
Order <span className="font-semibold"> #{id ?? ''} </span>
</div>
<div className="text-xs border border-gray-500 rounded-md px-1.5 py-0.5 text-gray-500">
{status ?? ''}
</div>
</div>
<div className="flex items-center gap-2">
<div className="text-xs text-gray-500">{date ?? ''}</div>
</div>
<div className="flex flex-col gap-2 mt-2">
{products?.map((product: any, index: number) => (
<div className="flex items-center gap-2" key={index}>
<div className="aspect-h-1 aspect-w-1 w-16 h-16 overflow-hidden rounded-lg bg-gray-100 border border-gray-200 xl:aspect-h-8 xl:aspect-w-7">
{product.item?.primary_image &&
product.item.primary_image.match(
/\.(jpeg|jpg|gif|png|webp)$/
) ? (
<img
src={`/imgs/${product.item.primary_image}`}
alt={product.item.item_name}
className="h-full w-full object-cover object-center"
/>
) : (
<div className="animate-pulse bg-gray-200 h-full w-full"></div>
)}
</div>
<div className="text-xs text-gray-600 flex-1 text-ellipsis text-nowrap overflow-hidden">
<span className="text-ellipsis">
{product.item?.item_name ?? ''}
</span>
<span className="font-semibold ml-1">
x {product.quantity ?? ''}
</span>
</div>
<div className="text-xs font-semibold text-gray-800">
$ {product.item?.price ?? ''}
</div>
</div>
))}
</div>
</div>
<div className="flex items-center justify-between">
<div className="text-gray-500 font-semibold">Total</div>
<div className="font-medium text-gray-900 ">$ {total}</div>
</div>
</div>
<div className="flex justify-start">
<Button size="sm" onClick={() => selectOrder(id)}>
Select order
</Button>
</div>
</div>
)
export const CardComponent = ({ children }: { children?: any[] }) => (
<div className="flex flex-col w-full bg-white rounded-lg p-4 shadow-md mt-2">
{children ? (
<div className="flex flex-col gap-4">
{children.map((child: any, index: number) => (
<React.Fragment key={index}>{getComponent(child)}</React.Fragment>
))}
</div>
) : null}
</div>
)
export const CarouselComponent = ({ children }: { children?: any[] }) => (
<div className="flex space-x-2 overflow-x-scroll w-full">
{children
? children.map((child: any, index: number) => (
<React.Fragment key={index}>{getComponent(child)}</React.Fragment>
))
: null}
</div>
)
export const componentsMap = {
card: CardComponent,
carousel: CarouselComponent,
bar_chart: BarChartComponent,
header: HeaderComponent,
table: TableComponent,
item: ItemComponent,
order: OrderComponent
// update componentsMap to match components passed to generate_ui
}