diff --git a/apps/ui-library/registry/default/blocks/assistant/components/assistant-widget.tsx b/apps/ui-library/registry/default/blocks/assistant/components/assistant-widget.tsx
index da29574ba5d..89e4ebbe635 100644
--- a/apps/ui-library/registry/default/blocks/assistant/components/assistant-widget.tsx
+++ b/apps/ui-library/registry/default/blocks/assistant/components/assistant-widget.tsx
@@ -40,6 +40,7 @@ import { DefaultChatTransport } from 'ai'
import { CheckIcon, MessageCircle } from 'lucide-react'
import { useCallback, useState } from 'react'
import { toast } from 'sonner'
+import { Chart, type ChartProps } from './parts/chart'
import { Row, type RowItem } from './parts/row'
const CHAT_API_URL = process.env.NEXT_PUBLIC_CHAT_API_URL
@@ -97,6 +98,8 @@ type RenderRowToolInput = {
rows: RowItem[]
}
+type RenderChartToolInput = ChartProps
+
interface AssistantWidgetProps {
/**
* Override the trigger positioning if you need to embed the widget in a scoped container.
@@ -236,6 +239,14 @@ export const AssistantWidget = ({
/>
)
}
+ case 'tool-renderChart': {
+ const payload = (part.input ?? part.output) as
+ | RenderChartToolInput
+ | undefined
+ if (!payload) return null
+
+ return
+ }
default:
return null
}
diff --git a/apps/ui-library/registry/default/blocks/assistant/components/parts/chart.tsx b/apps/ui-library/registry/default/blocks/assistant/components/parts/chart.tsx
new file mode 100644
index 00000000000..d76ef7f9238
--- /dev/null
+++ b/apps/ui-library/registry/default/blocks/assistant/components/parts/chart.tsx
@@ -0,0 +1,78 @@
+'use client'
+
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/registry/default/components/ui/card'
+import {
+ type ChartConfig,
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+} from '@/registry/default/components/ui/chart'
+import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts'
+
+export type ChartDataPoint = Record
+
+export type ChartProps = {
+ primaryText: string
+ secondaryText?: string
+ tertiaryText?: string
+ data: ChartDataPoint[]
+ xAxis: string
+ yAxis: string
+}
+
+export function Chart({
+ primaryText,
+ secondaryText,
+ tertiaryText,
+ data,
+ xAxis,
+ yAxis,
+}: ChartProps) {
+ if (!data.length) return null
+
+ const chartConfig = {
+ [yAxis]: {
+ label: yAxis,
+ color: 'var(--chart-1)',
+ },
+ } satisfies ChartConfig
+
+ const fillColor = `var(--color-${yAxis}, var(--chart-1))`
+
+ return (
+
+
+ {primaryText}
+ {secondaryText ? {secondaryText} : null}
+
+
+
+
+
+ String(value).slice(0, 3)}
+ tickLine={false}
+ tickMargin={10}
+ />
+ } />
+
+
+
+
+ {(tertiaryText ?? null) ? (
+
+ {tertiaryText}
+
+ ) : null}
+
+ )
+}
diff --git a/apps/ui-library/registry/default/blocks/assistant/registry-item.json b/apps/ui-library/registry/default/blocks/assistant/registry-item.json
index cb22667e629..e4f2c9599f9 100644
--- a/apps/ui-library/registry/default/blocks/assistant/registry-item.json
+++ b/apps/ui-library/registry/default/blocks/assistant/registry-item.json
@@ -8,6 +8,7 @@
"input",
"popover",
"scroll-area",
+ "chart",
"@ai-elements/conversation",
"@ai-elements/message",
"@ai-elements/prompt-input",
@@ -26,7 +27,8 @@
"zod@latest",
"nanoid@latest",
"sonner@latest",
- "lucide-react@latest"
+ "lucide-react@latest",
+ "recharts@latest"
],
"files": [
{
@@ -37,6 +39,10 @@
"path": "registry/default/blocks/assistant/components/parts/row.tsx",
"type": "registry:component"
},
+ {
+ "path": "registry/default/blocks/assistant/components/parts/chart.tsx",
+ "type": "registry:component"
+ },
{
"path": "registry/default/blocks/assistant/lib/supabase/server.ts",
"type": "registry:file",
diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/index.ts b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/index.ts
index 5fa6768c736..56fa6a42c59 100644
--- a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/index.ts
+++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/index.ts
@@ -25,6 +25,32 @@ const rowSchema = z.object({
.describe('Optional list of quick actions the user can trigger for this row.'),
})
+const chartSchema = z.object({
+ primaryText: z.string().describe('Primary title shown at the top of the chart card.'),
+ secondaryText: z
+ .string()
+ .optional()
+ .describe('Optional short description shown under the title.'),
+ tertiaryText: z
+ .string()
+ .optional()
+ .describe('Optional supporting text shown beneath the chart.'),
+ data: z
+ .array(
+ z.record(z.union([z.string(), z.number()])).describe(
+ 'Data point object containing axis and value fields.'
+ )
+ )
+ .min(1)
+ .describe('Data points to plot on the chart.'),
+ xAxis: z
+ .string()
+ .describe('Key inside each data point to use for the X-axis labels (e.g., "month").'),
+ yAxis: z
+ .string()
+ .describe('Key inside each data point to use for the bar height values (e.g., "desktop").'),
+})
+
const renderRowTool = tool({
description:
'Render a task row to summarize Supabase records, including follow-up actions the user can take.',
@@ -39,8 +65,20 @@ const renderRowTool = tool({
},
})
+const renderChartTool = tool({
+ description: 'Render a bar chart summarizing Supabase metrics for the user.',
+ inputSchema: chartSchema,
+ execute: async () => {
+ return {
+ success: true,
+ message: 'Chart has been shown to the user',
+ }
+ },
+})
+
const localTools = {
renderRow: renderRowTool,
+ renderChart: renderChartTool,
}
type ChatRequestBody = {
diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/count-tasks.ts b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/count-tasks.ts
new file mode 100644
index 00000000000..8f68f669a0b
--- /dev/null
+++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/count-tasks.ts
@@ -0,0 +1,79 @@
+import { z } from 'zod'
+import type { ToolDefinition } from './types.ts'
+
+type CountTasksInput = {
+ createdAtStart?: string
+ createdAtEnd?: string
+ completedAtStart?: string
+ completedAtEnd?: string
+}
+
+export const countTasksTool: ToolDefinition = {
+ name: 'countTasks',
+ description:
+ 'Count tasks that were created and/or completed between the provided date ranges. Dates should be ISO strings (YYYY-MM-DD or full timestamp).',
+ authErrorMessage: 'Error: Authentication required to count tasks.',
+ inputSchema: z.object({
+ createdAtStart: z
+ .string()
+ .optional()
+ .describe('Lower bound for created_at (inclusive). Accepts ISO dates or timestamps.'),
+ createdAtEnd: z
+ .string()
+ .optional()
+ .describe('Upper bound for created_at (inclusive). Accepts ISO dates or timestamps.'),
+ completedAtStart: z
+ .string()
+ .optional()
+ .describe('Lower bound for completed_at (inclusive). Accepts ISO dates or timestamps.'),
+ completedAtEnd: z
+ .string()
+ .optional()
+ .describe('Upper bound for completed_at (inclusive). Accepts ISO dates or timestamps.'),
+ }),
+ run: async (supabase, { createdAtStart, createdAtEnd, completedAtStart, completedAtEnd }) => {
+ try {
+ const { data, error } = await supabase.rpc('count_tasks', {
+ created_at_start: createdAtStart ?? null,
+ created_at_end: createdAtEnd ?? null,
+ completed_at_start: completedAtStart ?? null,
+ completed_at_end: completedAtEnd ?? null,
+ })
+
+ if (error) {
+ return {
+ content: [
+ {
+ type: 'text',
+ text: `Error counting tasks: ${error.message}`,
+ },
+ ],
+ isError: true,
+ }
+ }
+
+ const total = Array.isArray(data)
+ ? (data[0]?.total as number | undefined) ?? 0
+ : (data as number | null) ?? 0
+
+ return {
+ content: [
+ {
+ type: 'text',
+ text: `Found ${total} task(s) matching the provided filters.`,
+ },
+ ],
+ }
+ } catch (err) {
+ return {
+ content: [
+ {
+ type: 'text',
+ text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
+ },
+ ],
+ isError: true,
+ }
+ }
+ },
+}
diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/registry.ts b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/registry.ts
index 81120ec968b..b9e512eab6a 100644
--- a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/registry.ts
+++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/registry.ts
@@ -1,6 +1,7 @@
import { createTaskTool } from './create-task.ts'
import { deleteTaskTool } from './delete-task.ts'
import { getUserTasksTool } from './get-user-tasks.ts'
+import { countTasksTool } from './count-tasks.ts'
import { updateTaskTool } from './update-task.ts'
import type { ToolDefinition } from './types.ts'
@@ -9,4 +10,5 @@ export const toolRegistry: ToolDefinition[] = [
createTaskTool,
updateTaskTool,
deleteTaskTool,
+ countTasksTool,
]