Dashboardblocks
BlocksAI

AI Assistant

A chat panel docked beside a page, a conversation with code and feedback, questions about a usage card with the cost of each answer, and a failed reply with retry.

The assistant works with any model and any API. Messages are plain objects with an id, a role of user or assistant, and content text, and status is ready, submitted, streaming or error: the same shapes as the AI SDK's useChat. Replies are formatted safely, with no HTML, for paragraphs, lists, bold, code and fenced code blocks. A new question moves to the top of the log and its reply grows below it, so it reads from the start; a saved conversation opens at its last question. When you're at the bottom the log follows the reply, until you scroll up or select text. Screen readers hear the whole reply once it's done, not every word. The examples stream canned replies; see Usage to connect your own.

It's built on shadcn's Message, Bubble, Marker, Attachment, Code Block and Message Scroller, which install with it, so it matches the rest of your chat in your style. Code Block adds the shiki package, which colours the code in a reply as it streams, and Message Scroller adds @shadcn/react. The typing indicator's shimmer comes from the shadcn package's Tailwind CSS: if it doesn't show, update shadcn and Tailwind CSS.

Side Panel

An assistant docked beside the page, with suggested prompts to start from, the page as context, and new chat and close buttons. On narrow screens it covers the page.

Conversation

A conversation with a list and a SQL query. Copy a reply or its code, rate it, regenerate the last one, or pick a follow-up question.

Analytics assistant
Ask questions about your product analytics
You:

Why did checkout conversion drop last week?

Assistant:

Checkout conversion fell from 3.8% to 3.1% between Sep 15 and Sep 21. Three things changed that week:

  • Safari 17 payment errors. 41% of failed checkouts were on Safari 17, starting with the Sep 15 release.
  • Longer shipping estimates. Standard delivery showed 5–7 days instead of 3–5 for EU addresses.
  • More paid social traffic. Sessions from paid social doubled, and they convert at 1.2%.

The Safari errors explain most of the drop. Fixing them should win back about half a point.

You:

Can you give me the SQL to check the Safari errors?

Assistant:

This counts started and failed checkouts by browser for that week, from the events table:

sql
select  browser,  count(*) filter (where name = 'checkout_started') as started,  count(*) filter (where name = 'checkout_failed') as failed,  round(100.0 * count(*) filter (where name = 'checkout_failed')    / nullif(count(*) filter (where name = 'checkout_started'), 0), 1) as failure_ratefrom eventswhere occurred_at >= '2026-09-15' and occurred_at < '2026-09-22'group by browserorder by failed desc;

If Safari's failure_rate is well above the other browsers, the release is the likely cause.

Ask About Data

Questions about an AI Usage card, with the tokens and cost of each answer.

Token usage
All models, this month
Tokens
305.5M
Cost
$998
Cache hits
30%
  • Input 197M
  • Cached 83M
  • Output 25.5M
You:

What drove the cost this month?

Assistant:

Output tokens: they're 8% of the tokens but 38% of the cost, at five times the input price. Most come from the support summaries feature, which writes about 900 tokens per ticket.

Capping summaries at 400 tokens would save roughly $150 a month.

2K in · 96 out · $0.0037

Press Enter to send and Shift+Enter for a new line.

Retry

A reply that failed, kept in the conversation with a button to try again.

Ops assistant
Answers from incidents, deploys and runbooks
You:

Is checkout healthy right now?

Assistant:

Yes. The checkout error rate is 0.3% over the last hour, under the 1% alert threshold, and p95 latency is 420 ms. The last deploy, web@4.18.2, went out at 09:12 UTC with no new errors.

You:

Summarise last night’s incidents

Usage

The examples run on useChatStream, a small hook in the primitives. Give it onSend, a function that streams the reply to the conversation so far as chunks of text, and it keeps the messages and status, and handles stop, regenerate and new chat. Stop aborts the signal.

async function* onSend(messages: ChatMessageData[], signal: AbortSignal) {
  const response = await fetch('/api/chat', {
    body: JSON.stringify({ messages }),
    method: 'POST',
    signal,
  })
  const reader = response.body!.pipeThrough(new TextDecoderStream()).getReader()
  while (true) {
    const { done, value } = await reader.read()
    if (done) return
    yield value
  }
}

const chat = useChatStream({ onSend })

With the AI SDK, use useChat instead and pass its values to the same primitives. Its messages keep text in parts, so join those into content:

const { messages, regenerate, sendMessage, status, stop } = useChat()

const chatMessages = messages.flatMap((message) =>
  message.role === 'system'
    ? []
    : [
        {
          content: message.parts.map((part) => (part.type === 'text' ? part.text : '')).join(''),
          id: message.id,
          role: message.role,
        },
      ],
)

<ChatMessages latest={chatMessages.at(-1)} status={status}>
  {chatMessages.map((message) => (
    <ChatMessage key={message.id} message={message} />
  ))}
</ChatMessages>
<ChatComposer status={status} onSubmit={(text) => sendMessage({ text })} onStop={stop} />

Render each ChatMessage inside ChatMessages, and pass ChatMessages the status: the log places each question at the top when the status turns submitted. Put the typing indicator, an error or follow-up suggestions inside it too, after the messages.

On this page