A whole assistant screen: starter cards, reasoning, tool calls, an approval before it emails anyone, and a streamed answer, with past conversations beside it.
$ pnpm dlx shadcn@latest add @dashral/ai-assistantWhen to use
- A whole assistant screen inside your product: past conversations, starter prompts, reasoning, tool calls and a streamed answer.
- Copilots and support assistants that act on the user's data and ask before they do anything that can't be undone.
When not to use
- A quick question beside other work: Prompt Composer and Conversation in a side panel are lighter than a whole screen.
- Answers that only look things up: a search or the Help center finds them faster than a chat.
Usage
AI assistant with your own data: every prop has the sample as its default, so pass only what you have.
import { AiAssistant } from "@/components/dashral/blocks/ai-assistant"
<AiAssistant
title="Ask Acme"
history={conversations}
suggestions={starters}
onAsk={async (prompt, run) => {
for await (const part of model.stream(prompt, { signal: run.signal })) {
if (part.type === "text") run.write(part.text)
}
}}
onFeedback={(answer, feedback) => api.feedback.save(answer, feedback)}
/>API reference
AiAssistant
| Prop | Type | Default |
|---|---|---|
title | string | "Assistant" |
description | string | sample |
empty | { title: string; description: string }The empty conversation's words, and the cards that start one. | sample |
suggestions | AssistantSuggestion[] | sample |
history | { title: string; prompt: string }[]Past conversations beside it on wide screens; none hides the column. | sample |
models | { value: string; label: string; description?: string }[]The models to pick from, in the header; none hides the picker. | sample |
webSearch | booleanThe Search toggle in the composer. | true |
placeholder | string | sample |
onAsk | (prompt: string, run: AssistantRun, options: { model?: string; web: boolean; files: File[] }) => Promise<void> | voidYour model: fill the turn as its stream comes in (see AssistantRun). Throw to show an error on the answer. Without it, scripted sample answers. | — |
onFeedback | (answer: { prompt: string; text: string }, feedback: "up" | "down" | null) => voidGood or bad response on an answer. | — |
type ApprovalRequest
What an action needs approved before it runs, on the approval card.
| Prop | Type | Default |
|---|---|---|
title | string | — |
description | string | — |
details | { label: string; value: string }[] | — |
approveLabel | string | — |
result | string | — |
type AssistantRun
One answer in the making, for your model's stream to fill in.
| Prop | Type | Default |
|---|---|---|
think | (text: string) => voidAdds to the reasoning shown above the answer (folded away once the answer starts). | — |
tool | (name: string, input?: unknown) => { done: (output?: unknown, description?: string) => void; fail: (description?: string) => void }Shows a tool call running; call done or fail on what it returns when it finishes. | — |
approve | (request: ApprovalRequest) => Promise<boolean>Shows the approval card and waits: true once approved, false when denied. | — |
write | (text: string) => voidAdds to the answer, as it streams in. | — |
signal | AbortSignalAborted when Stop is pressed or a new conversation starts: pass it to your model's call. | — |
type AssistantSuggestion
| Prop | Type | Default |
|---|---|---|
icon | React.ReactNode | — |
label | string | — |
description | string | — |
prompt | string | — |