Initial commit
This commit is contained in:
195
docs/components/actions.md
Normal file
195
docs/components/actions.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Actions
|
||||
|
||||
URL: /components/actions
|
||||
|
||||
---
|
||||
|
||||
title: Actions
|
||||
description: A row of composable action buttons for AI responses, including retry, like, dislike, copy, share, and custom actions.
|
||||
path: elements/components/actions
|
||||
|
||||
---
|
||||
|
||||
The `Actions` component provides a flexible row of action buttons for AI responses with common actions like retry, like, dislike, copy, and share.
|
||||
|
||||
<Preview path="actions" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="actions" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Actions, Action } from "@/components/ai-elements/actions";
|
||||
import { ThumbsUpIcon } from "lucide-react";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Actions className="mt-2">
|
||||
<Action label="Like">
|
||||
<ThumbsUpIcon className="size-4" />
|
||||
</Action>
|
||||
</Actions>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a simple chat UI where the user can copy or regenerate the most recent message.
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Actions, Action } from "@/components/ai-elements/actions";
|
||||
import { Message, MessageContent } from "@/components/ai-elements/message";
|
||||
import { Conversation, ConversationContent, ConversationScrollButton } from "@/components/ai-elements/conversation";
|
||||
import { Input, PromptInputTextarea, PromptInputSubmit } from "@/components/ai-elements/prompt-input";
|
||||
import { Response } from "@/components/ai-elements/response";
|
||||
import { RefreshCcwIcon, CopyIcon } from "lucide-react";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
const ActionsDemo = () => {
|
||||
const [input, setInput] = useState("");
|
||||
const { messages, sendMessage, status, regenerate } = useChat();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (input.trim()) {
|
||||
sendMessage({ text: input });
|
||||
setInput("");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<Conversation>
|
||||
<ConversationContent>
|
||||
{messages.map((message, messageIndex) => (
|
||||
<Fragment key={message.id}>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
const isLastMessage = messageIndex === messages.length - 1;
|
||||
|
||||
return (
|
||||
<Fragment key={`${message.id}-${i}`}>
|
||||
<Message from={message.role}>
|
||||
<MessageContent>
|
||||
<Response>{part.text}</Response>
|
||||
</MessageContent>
|
||||
</Message>
|
||||
{message.role === "assistant" && isLastMessage && (
|
||||
<Actions>
|
||||
<Action onClick={() => regenerate()} label="Retry">
|
||||
<RefreshCcwIcon className="size-3" />
|
||||
</Action>
|
||||
<Action onClick={() => navigator.clipboard.writeText(part.text)} label="Copy">
|
||||
<CopyIcon className="size-3" />
|
||||
</Action>
|
||||
</Actions>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Fragment>
|
||||
))}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
|
||||
<Input onSubmit={handleSubmit} className="mt-4 w-full max-w-2xl mx-auto relative">
|
||||
<PromptInputTextarea
|
||||
value={input}
|
||||
placeholder="Say something..."
|
||||
onChange={(e) => setInput(e.currentTarget.value)}
|
||||
className="pr-12"
|
||||
/>
|
||||
<PromptInputSubmit
|
||||
status={status === "streaming" ? "streaming" : "ready"}
|
||||
disabled={!input.trim()}
|
||||
className="absolute bottom-1 right-1"
|
||||
/>
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionsDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```tsx title="api/chat/route.ts"
|
||||
import { streamText, UIMessage, convertToModelMessages } from "ai";
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { model, messages }: { messages: UIMessage[]; model: string } = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: "openai/gpt-4o",
|
||||
messages: convertToModelMessages(messages),
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Row of composable action buttons with consistent styling
|
||||
- Support for custom actions with tooltips
|
||||
- State management for toggle actions (like, dislike, favorite)
|
||||
- Keyboard accessible with proper ARIA labels
|
||||
- Clipboard and Web Share API integration
|
||||
- TypeScript support with proper type definitions
|
||||
- Consistent with design system styling
|
||||
|
||||
## Examples
|
||||
|
||||
<Preview path="actions-hover" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Actions />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'HTML attributes to spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<Action />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
tooltip: {
|
||||
description: 'Optional tooltip text shown on hover.',
|
||||
type: 'string',
|
||||
},
|
||||
label: {
|
||||
description:
|
||||
'Accessible label for screen readers. Also used as fallback if tooltip is not provided.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description:
|
||||
'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
168
docs/components/artifact.md
Normal file
168
docs/components/artifact.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Artifact
|
||||
|
||||
URL: /components/artifact
|
||||
|
||||
---
|
||||
|
||||
title: Artifact
|
||||
description: A container component for displaying generated content like code, documents, or other outputs with built-in actions.
|
||||
path: elements/components/artifact
|
||||
|
||||
---
|
||||
|
||||
The `Artifact` component provides a structured container for displaying generated content like code, documents, or other outputs with built-in header actions.
|
||||
|
||||
<Preview path="artifact" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="artifact" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Artifact,
|
||||
ArtifactAction,
|
||||
ArtifactActions,
|
||||
ArtifactContent,
|
||||
ArtifactDescription,
|
||||
ArtifactHeader,
|
||||
ArtifactTitle,
|
||||
} from "@/components/ai-elements/artifact";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Artifact>
|
||||
<ArtifactHeader>
|
||||
<div>
|
||||
<ArtifactTitle>Dijkstra's Algorithm Implementation</ArtifactTitle>
|
||||
<ArtifactDescription>Updated 1 minute ago</ArtifactDescription>
|
||||
</div>
|
||||
<ArtifactActions>
|
||||
<ArtifactAction icon={CopyIcon} label="Copy" tooltip="Copy to clipboard" />
|
||||
</ArtifactActions>
|
||||
</ArtifactHeader>
|
||||
<ArtifactContent>{/* Your content here */}</ArtifactContent>
|
||||
</Artifact>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Structured container with header and content areas
|
||||
- Built-in header with title and description support
|
||||
- Flexible action buttons with tooltips
|
||||
- Customizable styling for all subcomponents
|
||||
- Support for close buttons and action groups
|
||||
- Clean, modern design with border and shadow
|
||||
- Responsive layout that adapts to content
|
||||
- TypeScript support with proper type definitions
|
||||
- Composable architecture for maximum flexibility
|
||||
|
||||
## Examples
|
||||
|
||||
### With Code Display
|
||||
|
||||
<Preview path="artifact" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Artifact />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div element.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ArtifactHeader />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div element.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ArtifactTitle />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying paragraph element.',
|
||||
type: 'React.HTMLAttributes<HTMLParagraphElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ArtifactDescription />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying paragraph element.',
|
||||
type: 'React.HTMLAttributes<HTMLParagraphElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ArtifactActions />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div element.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ArtifactAction />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
tooltip: {
|
||||
description: 'Tooltip text to display on hover.',
|
||||
type: 'string',
|
||||
},
|
||||
label: {
|
||||
description: 'Screen reader label for the action button.',
|
||||
type: 'string',
|
||||
},
|
||||
icon: {
|
||||
description: 'Lucide icon component to display in the button.',
|
||||
type: 'LucideIcon',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ArtifactClose />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ArtifactContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div element.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
146
docs/components/branch.md
Normal file
146
docs/components/branch.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Branch
|
||||
|
||||
URL: /components/branch
|
||||
|
||||
---
|
||||
|
||||
title: Branch
|
||||
description: Manages multiple versions of AI messages, allowing users to navigate between different response branches.
|
||||
path: elements/components/branch
|
||||
|
||||
---
|
||||
|
||||
The `Branch` component manages multiple versions of AI messages, allowing users to navigate between different response branches. It provides a clean, modern interface with customizable themes and keyboard-accessible navigation buttons.
|
||||
|
||||
<Preview path="branch" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="branch" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Branch, BranchMessages, BranchNext, BranchPage, BranchPrevious, BranchSelector } from "@/components/ai-elements/branch";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Branch defaultBranch={0}>
|
||||
<BranchMessages>
|
||||
<Message from="user">
|
||||
<MessageContent>Hello</MessageContent>
|
||||
</Message>
|
||||
<Message from="user">
|
||||
<MessageContent>Hi!</MessageContent>
|
||||
</Message>
|
||||
</BranchMessages>
|
||||
<BranchSelector from="user">
|
||||
<BranchPrevious />
|
||||
<BranchPage />
|
||||
<BranchNext />
|
||||
</BranchSelector>
|
||||
</Branch>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
<Callout>
|
||||
Branching is an advanced use case that you can implement yourself to suit your
|
||||
application's needs. While the AI SDK does not provide built-in support for
|
||||
branching, you have full flexibility to design and manage multiple response
|
||||
paths as required.
|
||||
</Callout>
|
||||
|
||||
## Features
|
||||
|
||||
- Context-based state management for multiple message branches
|
||||
- Navigation controls for moving between branches (previous/next)
|
||||
- Uses CSS to prevent re-rendering of branches when switching
|
||||
- Branch counter showing current position (e.g., "1 of 3")
|
||||
- Automatic branch tracking and synchronization
|
||||
- Callbacks for branch change and navigation using `onBranchChange`
|
||||
- Support for custom branch change callbacks
|
||||
- Responsive design with mobile-friendly controls
|
||||
- Clean, modern styling with customizable themes
|
||||
- Keyboard-accessible navigation buttons
|
||||
|
||||
## Props
|
||||
|
||||
### `<Branch />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
defaultBranch: {
|
||||
description: 'The index of the branch to show by default.',
|
||||
type: 'number',
|
||||
default: '0',
|
||||
},
|
||||
onBranchChange: {
|
||||
description: 'Callback fired when the branch changes.',
|
||||
type: '(branchIndex: number) => void',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<BranchMessages />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<BranchSelector />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
from: {
|
||||
description: 'Aligns the selector for user, assistant or system messages.',
|
||||
type: 'UIMessage["role"]',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the selector container.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<BranchPrevious />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<BranchNext />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<BranchPage />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying span element.',
|
||||
type: 'React.HTMLAttributes<HTMLSpanElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
60
docs/components/canvas.md
Normal file
60
docs/components/canvas.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Canvas
|
||||
|
||||
URL: /components/canvas
|
||||
|
||||
---
|
||||
|
||||
title: Canvas
|
||||
description: A React Flow-based canvas component for building interactive node-based interfaces.
|
||||
path: elements/components/canvas
|
||||
|
||||
---
|
||||
|
||||
The `Canvas` component provides a React Flow-based canvas for building interactive node-based interfaces. It comes pre-configured with sensible defaults for AI applications, including panning, zooming, and selection behaviors.
|
||||
|
||||
<Callout>
|
||||
The Canvas component is designed to be used with the [Node](/elements/components/node) and [Edge](/elements/components/edge) components. See the [Workflow](/elements/examples/workflow) demo for a full example.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="canvas" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Canvas } from "@/components/ai-elements/canvas";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Canvas nodes={nodes} edges={edges} nodeTypes={nodeTypes} edgeTypes={edgeTypes} />
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Pre-configured React Flow canvas with AI-optimized defaults
|
||||
- Pan on scroll enabled for intuitive navigation
|
||||
- Selection on drag for multi-node operations
|
||||
- Customizable background color using CSS variables
|
||||
- Delete key support (Backspace and Delete keys)
|
||||
- Auto-fit view to show all nodes
|
||||
- Disabled double-click zoom for better UX
|
||||
- Disabled pan on drag to prevent accidental canvas movement
|
||||
- Fully compatible with React Flow props and API
|
||||
|
||||
## Props
|
||||
|
||||
### `<Canvas />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Child components like Background, Controls, or MiniMap.',
|
||||
type: 'ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other React Flow props like nodes, edges, nodeTypes, edgeTypes, onNodesChange, etc.',
|
||||
type: 'ReactFlowProps',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
180
docs/components/chain-of-thought.md
Normal file
180
docs/components/chain-of-thought.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Chain of Thought
|
||||
|
||||
URL: /components/chain-of-thought
|
||||
|
||||
---
|
||||
|
||||
title: Chain of Thought
|
||||
description: A collapsible component that visualizes AI reasoning steps with support for search results, images, and step-by-step progress indicators.
|
||||
path: elements/components/chain-of-thought
|
||||
|
||||
---
|
||||
|
||||
The `ChainOfThought` component provides a visual representation of an AI's reasoning process, showing step-by-step thinking with support for search results, images, and progress indicators. It helps users understand how AI arrives at conclusions.
|
||||
|
||||
<Preview path="chain-of-thought" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="chain-of-thought" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
ChainOfThought,
|
||||
ChainOfThoughtContent,
|
||||
ChainOfThoughtHeader,
|
||||
ChainOfThoughtImage,
|
||||
ChainOfThoughtSearchResult,
|
||||
ChainOfThoughtSearchResults,
|
||||
ChainOfThoughtStep,
|
||||
} from "@/components/ai-elements/chain-of-thought";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<ChainOfThought defaultOpen>
|
||||
<ChainOfThoughtHeader />
|
||||
<ChainOfThoughtContent>
|
||||
<ChainOfThoughtStep icon={SearchIcon} label="Searching for information" status="complete">
|
||||
<ChainOfThoughtSearchResults>
|
||||
<ChainOfThoughtSearchResult>Result 1</ChainOfThoughtSearchResult>
|
||||
</ChainOfThoughtSearchResults>
|
||||
</ChainOfThoughtStep>
|
||||
</ChainOfThoughtContent>
|
||||
</ChainOfThought>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Collapsible interface with smooth animations powered by Radix UI
|
||||
- Step-by-step visualization of AI reasoning process
|
||||
- Support for different step statuses (complete, active, pending)
|
||||
- Built-in search results display with badge styling
|
||||
- Image support with captions for visual content
|
||||
- Custom icons for different step types
|
||||
- Context-aware components using React Context API
|
||||
- Fully typed with TypeScript
|
||||
- Accessible with keyboard navigation support
|
||||
- Responsive design that adapts to different screen sizes
|
||||
- Smooth fade and slide animations for content transitions
|
||||
- Composable architecture for flexible customization
|
||||
|
||||
## Props
|
||||
|
||||
### `<ChainOfThought />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
open: {
|
||||
description: 'Controlled open state of the collapsible.',
|
||||
type: 'boolean',
|
||||
},
|
||||
defaultOpen: {
|
||||
description: 'Default open state when uncontrolled.',
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
onOpenChange: {
|
||||
description: 'Callback when the open state changes.',
|
||||
type: '(open: boolean) => void',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div element.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ChainOfThoughtHeader />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Custom header text.',
|
||||
type: 'React.ReactNode',
|
||||
default: '"Chain of Thought"',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CollapsibleTrigger component.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleTrigger>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ChainOfThoughtStep />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
icon: {
|
||||
description: 'Icon to display for the step.',
|
||||
type: 'LucideIcon',
|
||||
default: 'DotIcon',
|
||||
},
|
||||
label: {
|
||||
description: 'The main text label for the step.',
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
description: 'Optional description text shown below the label.',
|
||||
type: 'string',
|
||||
},
|
||||
status: {
|
||||
description: 'Visual status of the step.',
|
||||
type: '"complete" | "active" | "pending"',
|
||||
default: '"complete"',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div element.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ChainOfThoughtSearchResults />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any props are spread to the container div element.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ChainOfThoughtSearchResult />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any props are spread to the Badge component.',
|
||||
type: 'React.ComponentProps<typeof Badge>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ChainOfThoughtContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any props are spread to the CollapsibleContent component.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ChainOfThoughtImage />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
caption: {
|
||||
description: 'Optional caption text displayed below the image.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the container div element.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
196
docs/components/code-block.md
Normal file
196
docs/components/code-block.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# Code Block
|
||||
|
||||
URL: /components/code-block
|
||||
|
||||
---
|
||||
|
||||
title: Code Block
|
||||
description: Provides syntax highlighting, line numbers, and copy to clipboard functionality for code blocks.
|
||||
path: elements/components/code-block
|
||||
|
||||
---
|
||||
|
||||
The `CodeBlock` component provides syntax highlighting, line numbers, and copy to clipboard functionality for code blocks.
|
||||
|
||||
<Preview path="code-block" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="code-block" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { CodeBlock, CodeBlockCopyButton } from "@/components/ai-elements/code-block";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<CodeBlock data={"console.log('hello world')"} language="jsx">
|
||||
<CodeBlockCopyButton onCopy={() => console.log("Copied code to clipboard")} onError={() => console.error("Failed to copy code to clipboard")} />
|
||||
</CodeBlock>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a simple code generation tool using the [`experimental_useObject`](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-object) hook.
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { experimental_useObject as useObject } from "@ai-sdk/react";
|
||||
import { codeBlockSchema } from "@/app/api/codegen/route";
|
||||
import { Input, PromptInputTextarea, PromptInputSubmit } from "@/components/ai-elements/prompt-input";
|
||||
import { CodeBlock, CodeBlockCopyButton } from "@/components/ai-elements/code-block";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Page() {
|
||||
const [input, setInput] = useState("");
|
||||
const { object, submit, isLoading } = useObject({
|
||||
api: "/api/codegen",
|
||||
schema: codeBlockSchema,
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (input.trim()) {
|
||||
submit(input);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1 overflow-auto mb-4">
|
||||
{object?.code && object?.language && (
|
||||
<CodeBlock code={object.code} language={object.language} showLineNumbers={true}>
|
||||
<CodeBlockCopyButton />
|
||||
</CodeBlock>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Input onSubmit={handleSubmit} className="mt-4 w-full max-w-2xl mx-auto relative">
|
||||
<PromptInputTextarea
|
||||
value={input}
|
||||
placeholder="Generate a React todolist component"
|
||||
onChange={(e) => setInput(e.currentTarget.value)}
|
||||
className="pr-12"
|
||||
/>
|
||||
<PromptInputSubmit status={isLoading ? "streaming" : "ready"} disabled={!input.trim()} className="absolute bottom-1 right-1" />
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```tsx title="api/codegen/route.ts"
|
||||
import { streamObject } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
export const codeBlockSchema = z.object({
|
||||
language: z.string(),
|
||||
filename: z.string(),
|
||||
code: z.string(),
|
||||
});
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const context = await req.json();
|
||||
|
||||
const result = streamObject({
|
||||
model: "openai/gpt-4o",
|
||||
schema: codeBlockSchema,
|
||||
prompt: `You are a helpful coding assitant. Only generate code, no markdown formatting or backticks, or text.` + context,
|
||||
});
|
||||
|
||||
return result.toTextStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Syntax highlighting with react-syntax-highlighter
|
||||
- Line numbers (optional)
|
||||
- Copy to clipboard functionality
|
||||
- Automatic light/dark theme switching
|
||||
- Customizable styles
|
||||
- Accessible design
|
||||
|
||||
## Examples
|
||||
|
||||
### Dark Mode
|
||||
|
||||
To use the `CodeBlock` component in dark mode, you can wrap it in a `div` with the `dark` class.
|
||||
|
||||
<Preview path="code-block-dark" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<CodeBlock />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
code: {
|
||||
description: 'The code content to display.',
|
||||
type: 'string',
|
||||
},
|
||||
language: {
|
||||
description: 'The programming language for syntax highlighting.',
|
||||
type: 'string',
|
||||
},
|
||||
showLineNumbers: {
|
||||
description: 'Whether to show line numbers.',
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
children: {
|
||||
description: 'Child elements (like CodeBlockCopyButton) positioned in the top-right corner.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the root container.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<CodeBlockCopyButton />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
onCopy: {
|
||||
description: 'Callback fired after a successful copy.',
|
||||
type: '() => void',
|
||||
},
|
||||
onError: {
|
||||
description: 'Callback fired if copying fails.',
|
||||
type: '(error: Error) => void',
|
||||
},
|
||||
timeout: {
|
||||
description: 'How long to show the copied state (ms).',
|
||||
type: 'number',
|
||||
default: '2000',
|
||||
},
|
||||
children: {
|
||||
description: 'Custom content for the button. Defaults to copy/check icons.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the button.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
347
docs/components/confirmation.md
Normal file
347
docs/components/confirmation.md
Normal file
@@ -0,0 +1,347 @@
|
||||
# Confirmation
|
||||
|
||||
URL: /components/confirmation
|
||||
|
||||
---
|
||||
|
||||
title: Confirmation
|
||||
description: An alert-based component for managing tool execution approval workflows with request, accept, and reject states.
|
||||
path: elements/components/confirmation
|
||||
|
||||
---
|
||||
|
||||
The `Confirmation` component provides a flexible system for displaying tool approval requests and their outcomes. Perfect for showing users when AI tools require approval before execution, and displaying the approval status afterward.
|
||||
|
||||
<Preview path="confirmation" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="confirmation" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Confirmation,
|
||||
ConfirmationContent,
|
||||
ConfirmationRequest,
|
||||
ConfirmationAccepted,
|
||||
ConfirmationRejected,
|
||||
ConfirmationActions,
|
||||
ConfirmationAction,
|
||||
} from "@/components/ai-elements/confirmation";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Confirmation approval={{ id: "tool-1" }} state="approval-requested">
|
||||
<ConfirmationContent>
|
||||
<ConfirmationRequest>This tool wants to access your file system. Do you approve?</ConfirmationRequest>
|
||||
<ConfirmationAccepted>
|
||||
<CheckIcon className="size-4" />
|
||||
<span>Approved</span>
|
||||
</ConfirmationAccepted>
|
||||
<ConfirmationRejected>
|
||||
<XIcon className="size-4" />
|
||||
<span>Rejected</span>
|
||||
</ConfirmationRejected>
|
||||
</ConfirmationContent>
|
||||
<ConfirmationActions>
|
||||
<ConfirmationAction variant="outline" onClick={handleReject}>
|
||||
Reject
|
||||
</ConfirmationAction>
|
||||
<ConfirmationAction variant="default" onClick={handleApprove}>
|
||||
Approve
|
||||
</ConfirmationAction>
|
||||
</ConfirmationActions>
|
||||
</Confirmation>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a chat UI with tool approval workflow where dangerous tools require user confirmation before execution.
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { DefaultChatTransport, type ToolUIPart } from "ai";
|
||||
import { useState } from "react";
|
||||
import { CheckIcon, XIcon } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Confirmation,
|
||||
ConfirmationContent,
|
||||
ConfirmationRequest,
|
||||
ConfirmationAccepted,
|
||||
ConfirmationRejected,
|
||||
ConfirmationActions,
|
||||
ConfirmationAction,
|
||||
} from "@/components/ai-elements/confirmation";
|
||||
import { Response } from "@/components/ai-elements/response";
|
||||
|
||||
type DeleteFileInput = {
|
||||
filePath: string;
|
||||
confirm: boolean;
|
||||
};
|
||||
|
||||
type DeleteFileToolUIPart = ToolUIPart<{
|
||||
delete_file: {
|
||||
input: DeleteFileInput;
|
||||
output: { success: boolean; message: string };
|
||||
};
|
||||
}>;
|
||||
|
||||
const Example = () => {
|
||||
const { messages, sendMessage, status, respondToConfirmationRequest } = useChat({
|
||||
transport: new DefaultChatTransport({
|
||||
api: "/api/chat",
|
||||
}),
|
||||
});
|
||||
|
||||
const handleDeleteFile = () => {
|
||||
sendMessage({ text: "Delete the file at /tmp/example.txt" });
|
||||
};
|
||||
|
||||
const latestMessage = messages[messages.length - 1];
|
||||
const deleteTool = latestMessage?.parts?.find((part) => part.type === "tool-delete_file") as DeleteFileToolUIPart | undefined;
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full space-y-4">
|
||||
<Button onClick={handleDeleteFile} disabled={status !== "ready"}>
|
||||
Delete Example File
|
||||
</Button>
|
||||
|
||||
{deleteTool?.approval && (
|
||||
<Confirmation approval={deleteTool.approval} state={deleteTool.state}>
|
||||
<ConfirmationContent>
|
||||
<ConfirmationRequest>
|
||||
This tool wants to delete: <code>{deleteTool.input?.filePath}</code>
|
||||
<br />
|
||||
Do you approve this action?
|
||||
</ConfirmationRequest>
|
||||
<ConfirmationAccepted>
|
||||
<CheckIcon className="size-4" />
|
||||
<span>You approved this tool execution</span>
|
||||
</ConfirmationAccepted>
|
||||
<ConfirmationRejected>
|
||||
<XIcon className="size-4" />
|
||||
<span>You rejected this tool execution</span>
|
||||
</ConfirmationRejected>
|
||||
</ConfirmationContent>
|
||||
<ConfirmationActions>
|
||||
<ConfirmationAction
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
respondToConfirmationRequest({
|
||||
approvalId: deleteTool.approval!.id,
|
||||
approved: false,
|
||||
})
|
||||
}
|
||||
>
|
||||
Reject
|
||||
</ConfirmationAction>
|
||||
<ConfirmationAction
|
||||
variant="default"
|
||||
onClick={() =>
|
||||
respondToConfirmationRequest({
|
||||
approvalId: deleteTool.approval!.id,
|
||||
approved: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
Approve
|
||||
</ConfirmationAction>
|
||||
</ConfirmationActions>
|
||||
</Confirmation>
|
||||
)}
|
||||
|
||||
{deleteTool?.output && (
|
||||
<Response>{deleteTool.output.success ? deleteTool.output.message : `Error: ${deleteTool.output.message}`}</Response>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Example;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```ts title="app/api/chat/route.tsx"
|
||||
import { streamText, UIMessage, convertToModelMessages } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages }: { messages: UIMessage[] } = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: "openai/gpt-4o",
|
||||
messages: convertToModelMessages(messages),
|
||||
tools: {
|
||||
delete_file: {
|
||||
description: "Delete a file from the file system",
|
||||
parameters: z.object({
|
||||
filePath: z.string().describe("The path to the file to delete"),
|
||||
confirm: z.boolean().default(false).describe("Confirmation that the user wants to delete the file"),
|
||||
}),
|
||||
requireApproval: true, // Enable approval workflow
|
||||
execute: async ({ filePath, confirm }) => {
|
||||
if (!confirm) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Deletion not confirmed",
|
||||
};
|
||||
}
|
||||
|
||||
// Simulate file deletion
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully deleted ${filePath}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Context-based state management for approval workflow
|
||||
- Conditional rendering based on approval state
|
||||
- Support for approval-requested, approval-responded, output-denied, and output-available states
|
||||
- Built on shadcn/ui Alert and Button components
|
||||
- TypeScript support with comprehensive type definitions
|
||||
- Customizable styling with Tailwind CSS
|
||||
- Keyboard navigation and accessibility support
|
||||
- Theme-aware with automatic dark mode support
|
||||
|
||||
## Examples
|
||||
|
||||
### Approval Request State
|
||||
|
||||
Shows the approval request with action buttons when state is `approval-requested`.
|
||||
|
||||
<Preview path="confirmation-request" />
|
||||
|
||||
### Approved State
|
||||
|
||||
Shows the accepted status when user approves and state is `approval-responded` or `output-available`.
|
||||
|
||||
<Preview path="confirmation-accepted" />
|
||||
|
||||
### Rejected State
|
||||
|
||||
Shows the rejected status when user rejects and state is `output-denied`.
|
||||
|
||||
<Preview path="confirmation-rejected" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Confirmation />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
approval: {
|
||||
description: 'The approval object containing the approval ID and status. If not provided or undefined, the component will not render.',
|
||||
type: 'ToolUIPart["approval"]',
|
||||
},
|
||||
state: {
|
||||
description: 'The current state of the tool (input-streaming, input-available, approval-requested, approval-responded, output-denied, or output-available). Will not render for input-streaming or input-available states.',
|
||||
type: 'ToolUIPart["state"]',
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the Alert component.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the Alert component.',
|
||||
type: 'React.ComponentProps<typeof Alert>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ConfirmationContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the AlertDescription component.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the AlertDescription component.',
|
||||
type: 'React.ComponentProps<typeof AlertDescription>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ConfirmationRequest />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'The content to display when approval is requested. Only renders when state is "approval-requested".',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ConfirmationAccepted />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'The content to display when approval is accepted. Only renders when approval.approved is true and state is "approval-responded", "output-denied", or "output-available".',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ConfirmationRejected />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'The content to display when approval is rejected. Only renders when approval.approved is false and state is "approval-responded", "output-denied", or "output-available".',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ConfirmationActions />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the actions container.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the div element. Only renders when state is "approval-requested".',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ConfirmationAction />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the Button component. Styled with h-8 px-3 text-sm classes by default.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
66
docs/components/connection.md
Normal file
66
docs/components/connection.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Connection
|
||||
|
||||
URL: /components/connection
|
||||
|
||||
---
|
||||
|
||||
title: Connection
|
||||
description: A custom connection line component for React Flow-based canvases with animated bezier curve styling.
|
||||
path: elements/components/connection
|
||||
|
||||
---
|
||||
|
||||
The `Connection` component provides a styled connection line for React Flow canvases. It renders an animated bezier curve with a circle indicator at the target end, using consistent theming through CSS variables.
|
||||
|
||||
<Callout>
|
||||
The Connection component is designed to be used with the [Canvas](/elements/components/canvas) component. See the [Workflow](/elements/examples/workflow) demo for a full example.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="connection" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Connection } from "@/components/ai-elements/connection";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<ReactFlow connectionLineComponent={Connection} />
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Smooth bezier curve animation for connection lines
|
||||
- Visual indicator circle at the target position
|
||||
- Theme-aware styling using CSS variables
|
||||
- Cubic bezier curve calculation for natural flow
|
||||
- Lightweight implementation with minimal props
|
||||
- Full TypeScript support with React Flow types
|
||||
- Compatible with React Flow's connection system
|
||||
|
||||
## Props
|
||||
|
||||
### `<Connection />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
fromX: {
|
||||
description: 'The x-coordinate of the connection start point.',
|
||||
type: 'number',
|
||||
},
|
||||
fromY: {
|
||||
description: 'The y-coordinate of the connection start point.',
|
||||
type: 'number',
|
||||
},
|
||||
toX: {
|
||||
description: 'The x-coordinate of the connection end point.',
|
||||
type: 'number',
|
||||
},
|
||||
toY: {
|
||||
description: 'The y-coordinate of the connection end point.',
|
||||
type: 'number',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
244
docs/components/context.md
Normal file
244
docs/components/context.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# Context
|
||||
|
||||
URL: /components/context
|
||||
|
||||
---
|
||||
|
||||
title: Context
|
||||
description: A compound component system for displaying AI model context window usage, token consumption, and cost estimation.
|
||||
path: elements/components/context
|
||||
|
||||
---
|
||||
|
||||
The `Context` component provides a comprehensive view of AI model usage through a compound component system. It displays context window utilization, token consumption breakdown (input, output, reasoning, cache), and cost estimation in an interactive hover card interface.
|
||||
|
||||
<Preview path="context" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="context" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Context,
|
||||
ContextTrigger,
|
||||
ContextContent,
|
||||
ContextContentHeader,
|
||||
ContextContentBody,
|
||||
ContextContentFooter,
|
||||
ContextInputUsage,
|
||||
ContextOutputUsage,
|
||||
ContextReasoningUsage,
|
||||
ContextCacheUsage,
|
||||
} from "@/components/ai-elements/context";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Context
|
||||
maxTokens={128000}
|
||||
usedTokens={40000}
|
||||
usage={{
|
||||
inputTokens: 32000,
|
||||
outputTokens: 8000,
|
||||
totalTokens: 40000,
|
||||
cachedInputTokens: 0,
|
||||
reasoningTokens: 0,
|
||||
}}
|
||||
modelId="openai:gpt-4"
|
||||
>
|
||||
<ContextTrigger />
|
||||
<ContextContent>
|
||||
<ContextContentHeader />
|
||||
<ContextContentBody>
|
||||
<ContextInputUsage />
|
||||
<ContextOutputUsage />
|
||||
<ContextReasoningUsage />
|
||||
<ContextCacheUsage />
|
||||
</ContextContentBody>
|
||||
<ContextContentFooter />
|
||||
</ContextContent>
|
||||
</Context>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Compound Component Architecture**: Flexible composition of context display elements
|
||||
- **Visual Progress Indicator**: Circular SVG progress ring showing context usage percentage
|
||||
- **Token Breakdown**: Detailed view of input, output, reasoning, and cached tokens
|
||||
- **Cost Estimation**: Real-time cost calculation using the `tokenlens` library
|
||||
- **Intelligent Formatting**: Automatic token count formatting (K, M, B suffixes)
|
||||
- **Interactive Hover Card**: Detailed information revealed on hover
|
||||
- **Context Provider Pattern**: Clean data flow through React Context API
|
||||
- **TypeScript Support**: Full type definitions for all components
|
||||
- **Accessible Design**: Proper ARIA labels and semantic HTML
|
||||
- **Theme Integration**: Uses currentColor for automatic theme adaptation
|
||||
|
||||
## Props
|
||||
|
||||
### `<Context />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
maxTokens: {
|
||||
description: 'The total context window size in tokens.',
|
||||
type: 'number',
|
||||
},
|
||||
usedTokens: {
|
||||
description: 'The number of tokens currently used.',
|
||||
type: 'number',
|
||||
},
|
||||
usage: {
|
||||
description: 'Detailed token usage breakdown from the AI SDK (input, output, reasoning, cached tokens).',
|
||||
type: 'LanguageModelUsage',
|
||||
},
|
||||
modelId: {
|
||||
description: 'Model identifier for cost calculation (e.g., "openai:gpt-4", "anthropic:claude-3-opus").',
|
||||
type: 'ModelId',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the HoverCard component.',
|
||||
type: 'ComponentProps<HoverCard>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ContextTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Custom trigger element. If not provided, renders a default button with percentage and icon.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Props spread to the default button element.',
|
||||
type: 'ComponentProps<Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ContextContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
className: {
|
||||
description: 'Additional CSS classes for the hover card content.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Props spread to the HoverCardContent component.',
|
||||
type: 'ComponentProps<HoverCardContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ContextContentHeader />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Custom header content. If not provided, renders percentage and token count with progress bar.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Props spread to the header div element.',
|
||||
type: 'ComponentProps<div>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ContextContentBody />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Body content, typically containing usage breakdown components.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Props spread to the body div element.',
|
||||
type: 'ComponentProps<div>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ContextContentFooter />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Custom footer content. If not provided, renders total cost when modelId is provided.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Props spread to the footer div element.',
|
||||
type: 'ComponentProps<div>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### Usage Components
|
||||
|
||||
All usage components (`ContextInputUsage`, `ContextOutputUsage`, `ContextReasoningUsage`, `ContextCacheUsage`) share the same props:
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Custom content. If not provided, renders token count and cost for the respective usage type.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Props spread to the div element.',
|
||||
type: 'ComponentProps<div>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
## Component Architecture
|
||||
|
||||
The Context component uses a compound component pattern with React Context for data sharing:
|
||||
|
||||
1. **`<Context>`** - Root provider component that holds all context data
|
||||
2. **`<ContextTrigger>`** - Interactive trigger element (default: button with percentage)
|
||||
3. **`<ContextContent>`** - Hover card content container
|
||||
4. **`<ContextContentHeader>`** - Header section with progress visualization
|
||||
5. **`<ContextContentBody>`** - Body section for usage breakdowns
|
||||
6. **`<ContextContentFooter>`** - Footer section for total cost
|
||||
7. **Usage Components** - Individual token usage displays (Input, Output, Reasoning, Cache)
|
||||
|
||||
## Token Formatting
|
||||
|
||||
The component uses `Intl.NumberFormat` with compact notation for automatic formatting:
|
||||
|
||||
- Under 1,000: Shows exact count (e.g., "842")
|
||||
- 1,000+: Shows with K suffix (e.g., "32K")
|
||||
- 1,000,000+: Shows with M suffix (e.g., "1.5M")
|
||||
- 1,000,000,000+: Shows with B suffix (e.g., "2.1B")
|
||||
|
||||
## Cost Calculation
|
||||
|
||||
When a `modelId` is provided, the component automatically calculates costs using the `tokenlens` library:
|
||||
|
||||
- **Input tokens**: Cost based on model's input pricing
|
||||
- **Output tokens**: Cost based on model's output pricing
|
||||
- **Reasoning tokens**: Special pricing for reasoning-capable models
|
||||
- **Cached tokens**: Reduced pricing for cached input tokens
|
||||
- **Total cost**: Sum of all token type costs
|
||||
|
||||
Costs are formatted using `Intl.NumberFormat` with USD currency.
|
||||
|
||||
## Styling
|
||||
|
||||
The component uses Tailwind CSS classes and follows your design system:
|
||||
|
||||
- Progress indicator uses `currentColor` for theme adaptation
|
||||
- Hover card has customizable width and padding
|
||||
- Footer has a secondary background for visual separation
|
||||
- All text sizes use the `text-xs` class for consistency
|
||||
- Muted foreground colors for secondary information
|
||||
60
docs/components/controls.md
Normal file
60
docs/components/controls.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Controls
|
||||
|
||||
URL: /components/controls
|
||||
|
||||
---
|
||||
|
||||
title: Controls
|
||||
description: A styled controls component for React Flow-based canvases with zoom and fit view functionality.
|
||||
path: elements/components/controls
|
||||
|
||||
---
|
||||
|
||||
The `Controls` component provides interactive zoom and fit view controls for React Flow canvases. It includes a modern, themed design with backdrop blur and card styling.
|
||||
|
||||
<Callout>
|
||||
The Controls component is designed to be used with the [Canvas](/elements/components/canvas) component. See the [Workflow](/elements/examples/workflow) demo for a full example.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="controls" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Controls } from "@/components/ai-elements/controls";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<ReactFlow>
|
||||
<Controls />
|
||||
</ReactFlow>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Zoom in/out controls
|
||||
- Fit view button to center and scale content
|
||||
- Rounded pill design with backdrop blur
|
||||
- Theme-aware card background
|
||||
- Subtle drop shadow for depth
|
||||
- Full TypeScript support
|
||||
- Compatible with all React Flow control features
|
||||
|
||||
## Props
|
||||
|
||||
### `<Controls />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the controls.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props from @xyflow/react Controls component (showZoom, showFitView, showInteractive, position, etc.).',
|
||||
type: 'ComponentProps<typeof Controls>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
238
docs/components/conversation.md
Normal file
238
docs/components/conversation.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# Conversation
|
||||
|
||||
URL: /components/conversation
|
||||
|
||||
---
|
||||
|
||||
title: Conversation
|
||||
description: Wraps messages and automatically scrolls to the bottom. Also includes a scroll button that appears when not at the bottom.
|
||||
path: elements/components/conversation
|
||||
|
||||
---
|
||||
|
||||
The `Conversation` component wraps messages and automatically scrolls to the bottom. Also includes a scroll button that appears when not at the bottom.
|
||||
|
||||
<Preview path="conversation" className="p-0" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="conversation" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton } from "@/components/ai-elements/conversation";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Conversation className="relative w-full" style={{ height: "500px" }}>
|
||||
<ConversationContent>
|
||||
{messages.length === 0 ? (
|
||||
<ConversationEmptyState
|
||||
icon={<MessageSquare className="size-12" />}
|
||||
title="No messages yet"
|
||||
description="Start a conversation to see messages here"
|
||||
/>
|
||||
) : (
|
||||
messages.map((message) => (
|
||||
<Message from={message.from} key={message.id}>
|
||||
<MessageContent>{message.content}</MessageContent>
|
||||
</Message>
|
||||
))
|
||||
)}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a simple conversational UI with `Conversation` and [`PromptInput`](/elements/components/prompt-input):
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton } from "@/components/ai-elements/conversation";
|
||||
import { Message, MessageContent } from "@/components/ai-elements/message";
|
||||
import { Input, PromptInputTextarea, PromptInputSubmit } from "@/components/ai-elements/prompt-input";
|
||||
import { MessageSquare } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { Response } from "@/components/ai-elements/response";
|
||||
|
||||
const ConversationDemo = () => {
|
||||
const [input, setInput] = useState("");
|
||||
const { messages, sendMessage, status } = useChat();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (input.trim()) {
|
||||
sendMessage({ text: input });
|
||||
setInput("");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<Conversation>
|
||||
<ConversationContent>
|
||||
{messages.length === 0 ? (
|
||||
<ConversationEmptyState
|
||||
icon={<MessageSquare className="size-12" />}
|
||||
title="Start a conversation"
|
||||
description="Type a message below to begin chatting"
|
||||
/>
|
||||
) : (
|
||||
messages.map((message) => (
|
||||
<Message from={message.role} key={message.id}>
|
||||
<MessageContent>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "text": // we don't use any reasoning or tool calls in this example
|
||||
return <Response key={`${message.id}-${i}`}>{part.text}</Response>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
))
|
||||
)}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
|
||||
<Input onSubmit={handleSubmit} className="mt-4 w-full max-w-2xl mx-auto relative">
|
||||
<PromptInputTextarea
|
||||
value={input}
|
||||
placeholder="Say something..."
|
||||
onChange={(e) => setInput(e.currentTarget.value)}
|
||||
className="pr-12"
|
||||
/>
|
||||
<PromptInputSubmit
|
||||
status={status === "streaming" ? "streaming" : "ready"}
|
||||
disabled={!input.trim()}
|
||||
className="absolute bottom-1 right-1"
|
||||
/>
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConversationDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```tsx title="api/chat/route.ts"
|
||||
import { streamText, UIMessage, convertToModelMessages } from "ai";
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages }: { messages: UIMessage[] } = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: "openai/gpt-4o",
|
||||
messages: convertToModelMessages(messages),
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Automatic scrolling to the bottom when new messages are added
|
||||
- Smooth scrolling behavior with configurable animation
|
||||
- Scroll button that appears when not at the bottom
|
||||
- Responsive design with customizable padding and spacing
|
||||
- Flexible content layout with consistent message spacing
|
||||
- Accessible with proper ARIA roles for screen readers
|
||||
- Customizable styling through className prop
|
||||
- Support for any number of child message components
|
||||
|
||||
## Props
|
||||
|
||||
### `<Conversation />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
contextRef: {
|
||||
description: 'Optional ref to access the StickToBottom context object.',
|
||||
type: 'React.Ref<StickToBottomContext>',
|
||||
},
|
||||
instance: {
|
||||
description: 'Optional instance for controlling the StickToBottom component.',
|
||||
type: 'StickToBottomInstance',
|
||||
},
|
||||
children: {
|
||||
description: 'Render prop or ReactNode for custom rendering with context.',
|
||||
type: '((context: StickToBottomContext) => ReactNode) | ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'Omit<React.HTMLAttributes<HTMLDivElement>, "children">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ConversationContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Render prop or ReactNode for custom rendering with context.',
|
||||
type: '((context: StickToBottomContext) => ReactNode) | ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'Omit<React.HTMLAttributes<HTMLDivElement>, "children">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ConversationEmptyState />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
title: {
|
||||
description: 'The title text to display.',
|
||||
type: 'string',
|
||||
default: '"No messages yet"',
|
||||
},
|
||||
description: {
|
||||
description: 'The description text to display.',
|
||||
type: 'string',
|
||||
default: '"Start a conversation to see messages here"',
|
||||
},
|
||||
icon: {
|
||||
description: 'Optional icon to display above the text.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
children: {
|
||||
description: 'Optional additional content to render below the text.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ConversationScrollButton />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
110
docs/components/edge.md
Normal file
110
docs/components/edge.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Edge
|
||||
|
||||
URL: /components/edge
|
||||
|
||||
---
|
||||
|
||||
title: Edge
|
||||
description: Customizable edge components for React Flow canvases with animated and temporary states.
|
||||
path: elements/components/edge
|
||||
|
||||
---
|
||||
|
||||
The `Edge` component provides two pre-styled edge types for React Flow canvases: `Temporary` for dashed temporary connections and `Animated` for connections with animated indicators.
|
||||
|
||||
<Callout>
|
||||
The Edge component is designed to be used with the [Canvas](/elements/components/canvas) component. See the [Workflow](/elements/examples/workflow) demo for a full example.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="edge" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Edge } from "@/components/ai-elements/edge";
|
||||
```
|
||||
|
||||
```tsx
|
||||
const edgeTypes = {
|
||||
temporary: Edge.Temporary,
|
||||
animated: Edge.Animated,
|
||||
};
|
||||
|
||||
<Canvas nodes={nodes} edges={edges} edgeTypes={edgeTypes} />;
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Two distinct edge types: Temporary and Animated
|
||||
- Temporary edges use dashed lines with ring color
|
||||
- Animated edges include a moving circle indicator
|
||||
- Automatic handle position calculation
|
||||
- Smart offset calculation based on handle type and position
|
||||
- Uses Bezier curves for smooth, natural-looking connections
|
||||
- Fully compatible with React Flow's edge system
|
||||
- Type-safe implementation with TypeScript
|
||||
|
||||
## Edge Types
|
||||
|
||||
### `Edge.Temporary`
|
||||
|
||||
A dashed edge style for temporary or preview connections. Uses a simple Bezier path with a dashed stroke pattern.
|
||||
|
||||
### `Edge.Animated`
|
||||
|
||||
A solid edge with an animated circle that moves along the path. The animation repeats indefinitely with a 2-second duration, providing visual feedback for active connections.
|
||||
|
||||
## Props
|
||||
|
||||
Both edge types accept standard React Flow `EdgeProps`:
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
id: {
|
||||
description: 'Unique identifier for the edge.',
|
||||
type: 'string',
|
||||
},
|
||||
source: {
|
||||
description: 'ID of the source node.',
|
||||
type: 'string',
|
||||
},
|
||||
target: {
|
||||
description: 'ID of the target node.',
|
||||
type: 'string',
|
||||
},
|
||||
sourceX: {
|
||||
description: 'X coordinate of the source handle (Temporary only).',
|
||||
type: 'number',
|
||||
},
|
||||
sourceY: {
|
||||
description: 'Y coordinate of the source handle (Temporary only).',
|
||||
type: 'number',
|
||||
},
|
||||
targetX: {
|
||||
description: 'X coordinate of the target handle (Temporary only).',
|
||||
type: 'number',
|
||||
},
|
||||
targetY: {
|
||||
description: 'Y coordinate of the target handle (Temporary only).',
|
||||
type: 'number',
|
||||
},
|
||||
sourcePosition: {
|
||||
description: 'Position of the source handle (Left, Right, Top, Bottom).',
|
||||
type: 'Position',
|
||||
},
|
||||
targetPosition: {
|
||||
description: 'Position of the target handle (Left, Right, Top, Bottom).',
|
||||
type: 'Position',
|
||||
},
|
||||
markerEnd: {
|
||||
description: 'SVG marker ID for the edge end (Animated only).',
|
||||
type: 'string',
|
||||
},
|
||||
style: {
|
||||
description: 'Custom styles for the edge (Animated only).',
|
||||
type: 'React.CSSProperties',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
164
docs/components/image.md
Normal file
164
docs/components/image.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Image
|
||||
|
||||
URL: /components/image
|
||||
|
||||
---
|
||||
|
||||
title: Image
|
||||
description: Displays AI-generated images from the AI SDK.
|
||||
path: elements/components/image
|
||||
|
||||
---
|
||||
|
||||
The `Image` component displays AI-generated images from the AI SDK. It accepts a [`Experimental_GeneratedImage`](/docs/reference/ai-sdk-core/generate-image) object from the AI SDK's `generateImage` function and automatically renders it as an image.
|
||||
|
||||
<Preview path="image" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="image" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Image } from "@/components/ai-elements/image";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Image
|
||||
base64="valid base64 string"
|
||||
mediaType: 'image/jpeg',
|
||||
uint8Array: new Uint8Array([]),
|
||||
alt="Example generated image"
|
||||
className="h-[150px] aspect-square border"
|
||||
/>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a simple app allowing a user to generate an image given a prompt.
|
||||
|
||||
Install the `@ai-sdk/openai` package:
|
||||
|
||||
```package-install
|
||||
npm i @ai-sdk/openai
|
||||
```
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { Image } from "@/components/ai-elements/image";
|
||||
import { Input, PromptInputTextarea, PromptInputSubmit } from "@/components/ai-elements/prompt-input";
|
||||
import { useState } from "react";
|
||||
import { Loader } from "@/components/ai-elements/loader";
|
||||
|
||||
const ImageDemo = () => {
|
||||
const [prompt, setPrompt] = useState("A futuristic cityscape at sunset");
|
||||
const [imageData, setImageData] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!prompt.trim()) return;
|
||||
setPrompt("");
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch("/api/image", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ prompt: prompt.trim() }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
setImageData(data);
|
||||
} catch (error) {
|
||||
console.error("Error generating image:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{imageData && (
|
||||
<div className="flex justify-center">
|
||||
<Image {...imageData} alt="Generated image" className="h-[300px] aspect-square border rounded-lg" />
|
||||
</div>
|
||||
)}
|
||||
{isLoading && <Loader />}
|
||||
</div>
|
||||
|
||||
<Input onSubmit={handleSubmit} className="mt-4 w-full max-w-2xl mx-auto relative">
|
||||
<PromptInputTextarea
|
||||
value={prompt}
|
||||
placeholder="Describe the image you want to generate..."
|
||||
onChange={(e) => setPrompt(e.currentTarget.value)}
|
||||
className="pr-12"
|
||||
/>
|
||||
<PromptInputSubmit status={isLoading ? "submitted" : "ready"} disabled={!prompt.trim()} className="absolute bottom-1 right-1" />
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```ts title="app/api/image/route.ts"
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { experimental_generateImage } from "ai";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { prompt }: { prompt: string } = await req.json();
|
||||
|
||||
const { image } = await experimental_generateImage({
|
||||
model: openai.image("dall-e-3"),
|
||||
prompt: prompt,
|
||||
size: "1024x1024",
|
||||
});
|
||||
|
||||
return Response.json({
|
||||
base64: image.base64,
|
||||
uint8Array: image.uint8Array,
|
||||
mediaType: image.mediaType,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Accepts `Experimental_GeneratedImage` objects directly from the AI SDK
|
||||
- Automatically creates proper data URLs from base64-encoded image data
|
||||
- Supports all standard HTML image attributes
|
||||
- Responsive by default with `max-w-full h-auto` styling
|
||||
- Customizable with additional CSS classes
|
||||
- Includes proper TypeScript types for AI SDK compatibility
|
||||
|
||||
## Props
|
||||
|
||||
### `<Image />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
alt: {
|
||||
description: 'Alternative text for the image.',
|
||||
type: 'string',
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the image.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'The image data to display, as returned by the AI SDK.',
|
||||
type: 'Experimental_GeneratedImage',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
388
docs/components/inline-citation.md
Normal file
388
docs/components/inline-citation.md
Normal file
@@ -0,0 +1,388 @@
|
||||
# Inline Citation
|
||||
|
||||
URL: /components/inline-citation
|
||||
|
||||
---
|
||||
|
||||
title: Inline Citation
|
||||
description: A hoverable citation component that displays source information and quotes inline with text, perfect for AI-generated content with references.
|
||||
path: elements/components/inline-citation
|
||||
|
||||
---
|
||||
|
||||
The `InlineCitation` component provides a way to display citations inline with text content, similar to academic papers or research documents. It consists of a citation pill that shows detailed source information on hover, making it perfect for AI-generated content that needs to reference sources.
|
||||
|
||||
<Preview path="inline-citation" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="inline-citation" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
InlineCitation,
|
||||
InlineCitationCard,
|
||||
InlineCitationCardBody,
|
||||
InlineCitationCardTrigger,
|
||||
InlineCitationCarousel,
|
||||
InlineCitationCarouselContent,
|
||||
InlineCitationCarouselItem,
|
||||
InlineCitationCarouselHeader,
|
||||
InlineCitationCarouselIndex,
|
||||
InlineCitationSource,
|
||||
InlineCitationText,
|
||||
} from "@/components/ai-elements/inline-citation";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<InlineCitation>
|
||||
<InlineCitationText>{citation.text}</InlineCitationText>
|
||||
<InlineCitationCard>
|
||||
<InlineCitationCardTrigger sources={citation.sources.map((source) => source.url)} />
|
||||
<InlineCitationCardBody>
|
||||
<InlineCitationCarousel>
|
||||
<InlineCitationCarouselHeader>
|
||||
<InlineCitationCarouselIndex />
|
||||
</InlineCitationCarouselHeader>
|
||||
<InlineCitationCarouselContent>
|
||||
<InlineCitationCarouselItem>
|
||||
<InlineCitationSource title="AI SDK" url="https://ai-sdk.dev" description="The AI Toolkit for TypeScript" />
|
||||
</InlineCitationCarouselItem>
|
||||
</InlineCitationCarouselContent>
|
||||
</InlineCitationCarousel>
|
||||
</InlineCitationCardBody>
|
||||
</InlineCitationCard>
|
||||
</InlineCitation>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build citations for AI-generated content using [`experimental_generateObject`](/docs/reference/ai-sdk-ui/use-object).
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { experimental_useObject as useObject } from "@ai-sdk/react";
|
||||
import {
|
||||
InlineCitation,
|
||||
InlineCitationText,
|
||||
InlineCitationCard,
|
||||
InlineCitationCardTrigger,
|
||||
InlineCitationCardBody,
|
||||
InlineCitationCarousel,
|
||||
InlineCitationCarouselContent,
|
||||
InlineCitationCarouselItem,
|
||||
InlineCitationCarouselHeader,
|
||||
InlineCitationCarouselIndex,
|
||||
InlineCitationCarouselPrev,
|
||||
InlineCitationCarouselNext,
|
||||
InlineCitationSource,
|
||||
InlineCitationQuote,
|
||||
} from "@/components/ai-elements/inline-citation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { citationSchema } from "@/app/api/citation/route";
|
||||
|
||||
const CitationDemo = () => {
|
||||
const { object, submit, isLoading } = useObject({
|
||||
api: "/api/citation",
|
||||
schema: citationSchema,
|
||||
});
|
||||
|
||||
const handleSubmit = (topic: string) => {
|
||||
submit({ prompt: topic });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 space-y-6">
|
||||
<div className="flex gap-2 mb-6">
|
||||
<Button onClick={() => handleSubmit("artificial intelligence")} disabled={isLoading} variant="outline">
|
||||
Generate AI Content
|
||||
</Button>
|
||||
<Button onClick={() => handleSubmit("climate change")} disabled={isLoading} variant="outline">
|
||||
Generate Climate Content
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isLoading && !object && <div className="text-muted-foreground">Generating content with citations...</div>}
|
||||
|
||||
{object?.content && (
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<p className="leading-relaxed">
|
||||
{object.content.split(/(\[\d+\])/).map((part, index) => {
|
||||
const citationMatch = part.match(/\[(\d+)\]/);
|
||||
if (citationMatch) {
|
||||
const citationNumber = citationMatch[1];
|
||||
const citation = object.citations?.find((c: any) => c.number === citationNumber);
|
||||
|
||||
if (citation) {
|
||||
return (
|
||||
<InlineCitation key={index}>
|
||||
<InlineCitationCard>
|
||||
<InlineCitationCardTrigger sources={[citation.url]} />
|
||||
<InlineCitationCardBody>
|
||||
<InlineCitationCarousel>
|
||||
<InlineCitationCarouselHeader>
|
||||
<InlineCitationCarouselPrev />
|
||||
<InlineCitationCarouselNext />
|
||||
<InlineCitationCarouselIndex />
|
||||
</InlineCitationCarouselHeader>
|
||||
<InlineCitationCarouselContent>
|
||||
<InlineCitationCarouselItem>
|
||||
<InlineCitationSource
|
||||
title={citation.title}
|
||||
url={citation.url}
|
||||
description={citation.description}
|
||||
/>
|
||||
{citation.quote && <InlineCitationQuote>{citation.quote}</InlineCitationQuote>}
|
||||
</InlineCitationCarouselItem>
|
||||
</InlineCitationCarouselContent>
|
||||
</InlineCitationCarousel>
|
||||
</InlineCitationCardBody>
|
||||
</InlineCitationCard>
|
||||
</InlineCitation>
|
||||
);
|
||||
}
|
||||
}
|
||||
return part;
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CitationDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```ts title="app/api/citation/route.ts"
|
||||
import { streamObject } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
export const citationSchema = z.object({
|
||||
content: z.string(),
|
||||
citations: z.array(
|
||||
z.object({
|
||||
number: z.string(),
|
||||
title: z.string(),
|
||||
url: z.string(),
|
||||
description: z.string().optional(),
|
||||
quote: z.string().optional(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { prompt } = await req.json();
|
||||
|
||||
const result = streamObject({
|
||||
model: "openai/gpt-4o",
|
||||
schema: citationSchema,
|
||||
prompt: `Generate a well-researched paragraph about ${prompt} with proper citations.
|
||||
|
||||
Include:
|
||||
- A comprehensive paragraph with inline citations marked as [1], [2], etc.
|
||||
- 2-3 citations with realistic source information
|
||||
- Each citation should have a title, URL, and optional description/quote
|
||||
- Make the content informative and the sources credible
|
||||
|
||||
Format citations as numbered references within the text.`,
|
||||
});
|
||||
|
||||
return result.toTextStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Hover interaction to reveal detailed citation information
|
||||
- **Carousel navigation** for multiple citations with prev/next controls
|
||||
- **Live index tracking** showing current slide position (e.g., "1/5")
|
||||
- Support for source titles, URLs, and descriptions
|
||||
- Optional quote blocks for relevant excerpts
|
||||
- Composable architecture for flexible citation formats
|
||||
- Accessible design with proper keyboard navigation
|
||||
- Seamless integration with AI-generated content
|
||||
- Clean visual design that doesn't disrupt reading flow
|
||||
- Smart badge display showing source hostname and count
|
||||
|
||||
## Props
|
||||
|
||||
### `<InlineCitation />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root span element.',
|
||||
type: 'React.ComponentProps<"span">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationText />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying span element.',
|
||||
type: 'React.ComponentProps<"span">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCard />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the HoverCard component.',
|
||||
type: 'React.ComponentProps<"span">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCardTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
sources: {
|
||||
description: 'Array of source URLs. The length determines the number displayed in the badge.',
|
||||
type: 'string[]',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying button element.',
|
||||
type: 'React.ComponentProps<"button">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCardBody />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCarousel />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying Carousel component.',
|
||||
type: 'React.ComponentProps<typeof Carousel>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCarouselContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CarouselContent component.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCarouselItem />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCarouselHeader />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCarouselIndex />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div. Children will override the default index display.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCarouselPrev />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CarouselPrevious component.',
|
||||
type: 'React.ComponentProps<typeof CarouselPrevious>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationCarouselNext />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CarouselNext component.',
|
||||
type: 'React.ComponentProps<typeof CarouselNext>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationSource />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
title: {
|
||||
description: 'The title of the source.',
|
||||
type: 'string',
|
||||
},
|
||||
url: {
|
||||
description: 'The URL of the source.',
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
description: 'A brief description of the source.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<InlineCitationQuote />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying blockquote element.',
|
||||
type: 'React.ComponentProps<"blockquote">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
163
docs/components/loader.md
Normal file
163
docs/components/loader.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Loader
|
||||
|
||||
URL: /components/loader
|
||||
|
||||
---
|
||||
|
||||
title: Loader
|
||||
description: A spinning loader component for indicating loading states in AI applications.
|
||||
path: elements/components/loader
|
||||
|
||||
---
|
||||
|
||||
The `Loader` component provides a spinning animation to indicate loading states in your AI applications. It includes both a customizable wrapper component and the underlying icon for flexible usage.
|
||||
|
||||
<Preview path="loader" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="loader" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Loader } from "@/components/ai-elements/loader";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Loader />
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a simple chat app that displays a loader before the response starts streaming by using `status === "submitted"`.
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { Conversation, ConversationContent, ConversationScrollButton } from "@/components/ai-elements/conversation";
|
||||
import { Message, MessageContent } from "@/components/ai-elements/message";
|
||||
import { Input, PromptInputTextarea, PromptInputSubmit } from "@/components/ai-elements/prompt-input";
|
||||
import { Loader } from "@/components/ai-elements/loader";
|
||||
import { useState } from "react";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
|
||||
const LoaderDemo = () => {
|
||||
const [input, setInput] = useState("");
|
||||
const { messages, sendMessage, status } = useChat();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (input.trim()) {
|
||||
sendMessage({ text: input });
|
||||
setInput("");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<Conversation>
|
||||
<ConversationContent>
|
||||
{messages.map((message) => (
|
||||
<Message from={message.role} key={message.id}>
|
||||
<MessageContent>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return <div key={`${message.id}-${i}`}>{part.text}</div>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
))}
|
||||
{status === "submitted" && <Loader />}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
|
||||
<Input onSubmit={handleSubmit} className="mt-4 w-full max-w-2xl mx-auto relative">
|
||||
<PromptInputTextarea
|
||||
value={input}
|
||||
placeholder="Say something..."
|
||||
onChange={(e) => setInput(e.currentTarget.value)}
|
||||
className="pr-12"
|
||||
/>
|
||||
<PromptInputSubmit
|
||||
status={status === "streaming" ? "streaming" : "ready"}
|
||||
disabled={!input.trim()}
|
||||
className="absolute bottom-1 right-1"
|
||||
/>
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoaderDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```ts title="app/api/chat/route.ts"
|
||||
import { streamText, UIMessage, convertToModelMessages } from "ai";
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { model, messages }: { messages: UIMessage[]; model: string } = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: "openai/gpt-4o",
|
||||
messages: convertToModelMessages(messages),
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Clean, modern spinning animation using CSS animations
|
||||
- Configurable size with the `size` prop
|
||||
- Customizable styling with CSS classes
|
||||
- Built-in `animate-spin` animation with proper centering
|
||||
- Exports both `AILoader` wrapper and `LoaderIcon` for flexible usage
|
||||
- Supports all standard HTML div attributes
|
||||
- TypeScript support with proper type definitions
|
||||
- Optimized SVG icon with multiple opacity levels for smooth animation
|
||||
- Uses `currentColor` for proper theme integration
|
||||
- Responsive and accessible design
|
||||
|
||||
## Examples
|
||||
|
||||
### Different Sizes
|
||||
|
||||
<Preview path="loader-sizes" />
|
||||
|
||||
### Custom Styling
|
||||
|
||||
<Preview path="loader-custom" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Loader />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
size: {
|
||||
description: 'The size (width and height) of the loader in pixels.',
|
||||
type: 'number',
|
||||
default: '16',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
179
docs/components/message.md
Normal file
179
docs/components/message.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Message
|
||||
|
||||
URL: /components/message
|
||||
|
||||
---
|
||||
|
||||
title: Message
|
||||
description: Displays a chat interface message from either a user or an AI.
|
||||
path: elements/components/message
|
||||
|
||||
---
|
||||
|
||||
The `Message` component displays a chat interface message from either a user or an AI. It includes an avatar, a name, and a message content.
|
||||
|
||||
<Preview path="message" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="message" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Message, MessageContent } from "@/components/ai-elements/message";
|
||||
```
|
||||
|
||||
```tsx
|
||||
// Default contained variant
|
||||
<Message from="user">
|
||||
<MessageContent>Hi there!</MessageContent>
|
||||
</Message>
|
||||
|
||||
// Flat variant for a minimalist look
|
||||
<Message from="assistant">
|
||||
<MessageContent variant="flat">Hello! How can I help you today?</MessageContent>
|
||||
</Message>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Render messages in a list with `useChat`.
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { Message, MessageContent } from "@/components/ai-elements/message";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { Response } from "@/components/ai-elements/response";
|
||||
|
||||
const MessageDemo = () => {
|
||||
const { messages } = useChat();
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
{messages.map((message) => (
|
||||
<Message from={message.role} key={message.id}>
|
||||
<MessageContent>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "text": // we don't use any reasoning or tool calls in this example
|
||||
return <Response key={`${message.id}-${i}`}>{part.text}</Response>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageDemo;
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Displays messages from both the user and AI assistant with distinct styling.
|
||||
- Two visual variants: **contained** (default) and **flat** for different design preferences.
|
||||
- Includes avatar images for message senders with fallback initials.
|
||||
- Shows the sender's name through avatar fallbacks.
|
||||
- Automatically aligns user and assistant messages on opposite sides.
|
||||
- Uses different background colors for user and assistant messages.
|
||||
- Accepts any React node as message content.
|
||||
|
||||
## Variants
|
||||
|
||||
### Contained (default)
|
||||
|
||||
The **contained** variant provides distinct visual separation with colored backgrounds:
|
||||
|
||||
- User messages appear with primary background color and are right-aligned
|
||||
- Assistant messages have secondary background color and are left-aligned
|
||||
- Both message types have padding and rounded corners
|
||||
|
||||
### Flat
|
||||
|
||||
The **flat** variant offers a minimalist design that matches modern AI interfaces like ChatGPT and Gemini:
|
||||
|
||||
- User messages use softer secondary colors with subtle borders
|
||||
- Assistant messages display full-width without background or padding
|
||||
- Creates a cleaner, more streamlined conversation appearance
|
||||
|
||||
## Notes
|
||||
|
||||
Always render the `AIMessageContent` first, then the `AIMessageAvatar`. The `AIMessage` component is a wrapper that determines the alignment of the message.
|
||||
|
||||
## Examples
|
||||
|
||||
### Render Markdown
|
||||
|
||||
We can use the [`Response`](/elements/components/response) component to render markdown content.
|
||||
|
||||
<Preview path="message-markdown" />
|
||||
|
||||
### Flat Variant
|
||||
|
||||
The flat variant provides a minimalist design that matches modern AI interfaces.
|
||||
|
||||
<Preview path="message-flat" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Message />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
from: {
|
||||
description:
|
||||
'The role of the message sender ("user", "assistant", or "system").',
|
||||
type: 'UIMessage["role"]',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<MessageContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
variant: {
|
||||
description: 'Visual style variant. "contained" (default) shows colored backgrounds, "flat" provides a minimalist design.',
|
||||
type: '"contained" | "flat"',
|
||||
default: '"contained"',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the content div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<MessageAvatar />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
src: {
|
||||
description: 'The URL of the avatar image.',
|
||||
type: 'string',
|
||||
},
|
||||
name: {
|
||||
description:
|
||||
'The name to use for the avatar fallback (first 2 letters shown if image is missing).',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description:
|
||||
'Any other props are spread to the underlying Avatar component.',
|
||||
type: 'React.ComponentProps<typeof Avatar>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
151
docs/components/node.md
Normal file
151
docs/components/node.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Node
|
||||
|
||||
URL: /components/node
|
||||
|
||||
---
|
||||
|
||||
title: Node
|
||||
description: A composable node component for React Flow-based canvases with Card-based styling.
|
||||
path: elements/components/node
|
||||
|
||||
---
|
||||
|
||||
The `Node` component provides a composable, Card-based node for React Flow canvases. It includes support for connection handles, structured layouts, and consistent styling using shadcn/ui components.
|
||||
|
||||
<Callout>
|
||||
The Node component is designed to be used with the [Canvas](/elements/components/canvas) component. See the [Workflow](/elements/examples/workflow) demo for a full example.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="node" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Node, NodeHeader, NodeTitle, NodeDescription, NodeAction, NodeContent, NodeFooter } from "@/components/ai-elements/node";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Node handles={{ target: true, source: true }}>
|
||||
<NodeHeader>
|
||||
<NodeTitle>Node Title</NodeTitle>
|
||||
<NodeDescription>Optional description</NodeDescription>
|
||||
<NodeAction>
|
||||
<Button>Action</Button>
|
||||
</NodeAction>
|
||||
</NodeHeader>
|
||||
<NodeContent>Main content goes here</NodeContent>
|
||||
<NodeFooter>Footer content</NodeFooter>
|
||||
</Node>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Built on shadcn/ui Card components for consistent styling
|
||||
- Automatic handle placement (left for target, right for source)
|
||||
- Composable sub-components (Header, Title, Description, Action, Content, Footer)
|
||||
- Semantic structure for organizing node information
|
||||
- Pre-styled sections with borders and backgrounds
|
||||
- Responsive sizing with fixed small width
|
||||
- Full TypeScript support with proper type definitions
|
||||
- Compatible with React Flow's node system
|
||||
|
||||
## Props
|
||||
|
||||
### `<Node />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
handles: {
|
||||
description: 'Configuration for connection handles. Target renders on the left, source on the right.',
|
||||
type: '{ target: boolean; source: boolean; }',
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the node.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying Card component.',
|
||||
type: 'ComponentProps<typeof Card>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<NodeHeader />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the header.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CardHeader component.',
|
||||
type: 'ComponentProps<typeof CardHeader>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<NodeTitle />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CardTitle component.',
|
||||
type: 'ComponentProps<typeof CardTitle>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<NodeDescription />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CardDescription component.',
|
||||
type: 'ComponentProps<typeof CardDescription>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<NodeAction />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CardAction component.',
|
||||
type: 'ComponentProps<typeof CardAction>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<NodeContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the content.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CardContent component.',
|
||||
type: 'ComponentProps<typeof CardContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<NodeFooter />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the footer.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CardFooter component.',
|
||||
type: 'ComponentProps<typeof CardFooter>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
133
docs/components/open-in-chat.md
Normal file
133
docs/components/open-in-chat.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Open In Chat
|
||||
|
||||
URL: /components/open-in-chat
|
||||
|
||||
---
|
||||
|
||||
title: Open In Chat
|
||||
description: A dropdown menu for opening queries in various AI chat platforms including ChatGPT, Claude, T3, Scira, and v0.
|
||||
path: elements/components/open-in-chat
|
||||
|
||||
---
|
||||
|
||||
The `OpenIn` component provides a dropdown menu that allows users to open queries in different AI chat platforms with a single click.
|
||||
|
||||
<Preview path="open-in-chat" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="open-in-chat" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
OpenIn,
|
||||
OpenInChatGPT,
|
||||
OpenInClaude,
|
||||
OpenInContent,
|
||||
OpenInCursor,
|
||||
OpenInScira,
|
||||
OpenInT3,
|
||||
OpenInTrigger,
|
||||
OpenInv0,
|
||||
} from "@/components/ai-elements/open-in-chat";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<OpenIn query="How can I implement authentication in Next.js?">
|
||||
<OpenInTrigger />
|
||||
<OpenInContent>
|
||||
<OpenInChatGPT />
|
||||
<OpenInClaude />
|
||||
<OpenInT3 />
|
||||
<OpenInScira />
|
||||
<OpenInv0 />
|
||||
<OpenInCursor />
|
||||
</OpenInContent>
|
||||
</OpenIn>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Pre-configured links to popular AI chat platforms
|
||||
- Context-based query passing for cleaner API
|
||||
- Customizable dropdown trigger button
|
||||
- Automatic URL parameter encoding for queries
|
||||
- Support for ChatGPT, Claude, T3 Chat, Scira AI, v0, and Cursor
|
||||
- Branded icons for each platform
|
||||
- TypeScript support with proper type definitions
|
||||
- Accessible dropdown menu with keyboard navigation
|
||||
- External link indicators for clarity
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
- **ChatGPT** - Opens query in OpenAI's ChatGPT with search hints
|
||||
- **Claude** - Opens query in Anthropic's Claude AI
|
||||
- **T3 Chat** - Opens query in T3 Chat platform
|
||||
- **Scira AI** - Opens query in Scira's AI assistant
|
||||
- **v0** - Opens query in Vercel's v0 platform
|
||||
- **Cursor** - Opens query in Cursor AI editor
|
||||
|
||||
## Props
|
||||
|
||||
### `<OpenIn />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
query: {
|
||||
description: 'The query text to be sent to all AI platforms.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Props to spread to the underlying radix-ui DropdownMenu component.',
|
||||
type: 'React.ComponentProps<typeof DropdownMenu>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<OpenInTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Custom trigger button.',
|
||||
type: 'React.ReactNode',
|
||||
default: '"Open in chat" button with chevron icon',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Props to spread to the underlying DropdownMenuTrigger component.',
|
||||
type: 'React.ComponentProps<typeof DropdownMenuTrigger>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<OpenInContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the dropdown content.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Props to spread to the underlying DropdownMenuContent component.',
|
||||
type: 'React.ComponentProps<typeof DropdownMenuContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<OpenInChatGPT />`, `<OpenInClaude />`, `<OpenInT3 />`, `<OpenInScira />`, `<OpenInv0 />`, `<OpenInCursor />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Props to spread to the underlying DropdownMenuItem component. The query is automatically provided via context from the parent OpenIn component.',
|
||||
type: 'React.ComponentProps<typeof DropdownMenuItem>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<OpenInItem />`, `<OpenInLabel />`, `<OpenInSeparator />`
|
||||
|
||||
Additional composable components for custom dropdown menu items, labels, and separators that follow the same props pattern as their underlying radix-ui counterparts.
|
||||
66
docs/components/panel.md
Normal file
66
docs/components/panel.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Panel
|
||||
|
||||
URL: /components/panel
|
||||
|
||||
---
|
||||
|
||||
title: Panel
|
||||
description: A styled panel component for React Flow-based canvases to position custom UI elements.
|
||||
path: elements/components/panel
|
||||
|
||||
---
|
||||
|
||||
The `Panel` component provides a positioned container for custom UI elements on React Flow canvases. It includes modern card styling with backdrop blur and flexible positioning options.
|
||||
|
||||
<Callout>
|
||||
The Panel component is designed to be used with the [Canvas](/elements/components/canvas) component. See the [Workflow](/elements/examples/workflow) demo for a full example.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="panel" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Panel } from "@/components/ai-elements/panel";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<ReactFlow>
|
||||
<Panel position="top-left">
|
||||
<Button>Custom Action</Button>
|
||||
</Panel>
|
||||
</ReactFlow>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Flexible positioning (top-left, top-right, bottom-left, bottom-right, top-center, bottom-center)
|
||||
- Rounded pill design with backdrop blur
|
||||
- Theme-aware card background
|
||||
- Flexbox layout for easy content alignment
|
||||
- Subtle drop shadow for depth
|
||||
- Full TypeScript support
|
||||
- Compatible with React Flow's panel system
|
||||
|
||||
## Props
|
||||
|
||||
### `<Panel />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
position: {
|
||||
description: 'Position of the panel on the canvas.',
|
||||
type: "'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'",
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the panel.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props from @xyflow/react Panel component.',
|
||||
type: 'ComponentProps<typeof Panel>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
167
docs/components/plan.md
Normal file
167
docs/components/plan.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Plan
|
||||
|
||||
URL: /components/plan
|
||||
|
||||
---
|
||||
|
||||
title: Plan
|
||||
description: A collapsible plan component for displaying AI-generated execution plans with streaming support and shimmer animations.
|
||||
path: elements/components/plan
|
||||
|
||||
---
|
||||
|
||||
The `Plan` component provides a flexible system for displaying AI-generated execution plans with collapsible content. Perfect for showing multi-step workflows, task breakdowns, and implementation strategies with support for streaming content and loading states.
|
||||
|
||||
<Preview path="plan" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="plan" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Plan, PlanAction, PlanContent, PlanDescription, PlanFooter, PlanHeader, PlanTitle, PlanTrigger } from "@/components/ai-elements/plan";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Plan defaultOpen={false}>
|
||||
<PlanHeader>
|
||||
<div>
|
||||
<PlanTitle>Implement new feature</PlanTitle>
|
||||
<PlanDescription>Add authentication system with JWT tokens and refresh logic.</PlanDescription>
|
||||
</div>
|
||||
<PlanTrigger />
|
||||
</PlanHeader>
|
||||
<PlanContent>
|
||||
<div className="space-y-4 text-sm">
|
||||
<div>
|
||||
<h3 className="mb-2 font-semibold">Overview</h3>
|
||||
<p>This plan outlines the implementation strategy...</p>
|
||||
</div>
|
||||
</div>
|
||||
</PlanContent>
|
||||
<PlanFooter>
|
||||
<Button>Execute Plan</Button>
|
||||
</PlanFooter>
|
||||
</Plan>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Collapsible content with smooth animations
|
||||
- Streaming support with shimmer loading states
|
||||
- Built on shadcn/ui Card and Collapsible components
|
||||
- TypeScript support with comprehensive type definitions
|
||||
- Customizable styling with Tailwind CSS
|
||||
- Responsive design with mobile-friendly interactions
|
||||
- Keyboard navigation and accessibility support
|
||||
- Theme-aware with automatic dark mode support
|
||||
- Context-based state management for streaming
|
||||
|
||||
## Props
|
||||
|
||||
### `<Plan />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
isStreaming: {
|
||||
description: 'Whether content is currently streaming. Enables shimmer animations on title and description.',
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
defaultOpen: {
|
||||
description: 'Whether the plan is expanded by default.',
|
||||
type: 'boolean',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the Collapsible component.',
|
||||
type: 'React.ComponentProps<typeof Collapsible>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PlanHeader />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CardHeader component.',
|
||||
type: 'React.ComponentProps<typeof CardHeader>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PlanTitle />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'The title text. Displays with shimmer animation when isStreaming is true.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props (except children) are spread to the CardTitle component.',
|
||||
type: 'Omit<React.ComponentProps<typeof CardTitle>, "children">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PlanDescription />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'The description text. Displays with shimmer animation when isStreaming is true.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props (except children) are spread to the CardDescription component.',
|
||||
type: 'Omit<React.ComponentProps<typeof CardDescription>, "children">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PlanTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CollapsibleTrigger component. Renders as a Button with chevron icon.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleTrigger>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PlanContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CardContent component.',
|
||||
type: 'React.ComponentProps<typeof CardContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PlanFooter />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the div element.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PlanAction />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CardAction component.',
|
||||
type: 'React.ComponentProps<typeof CardAction>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
811
docs/components/prompt-input.md
Normal file
811
docs/components/prompt-input.md
Normal file
@@ -0,0 +1,811 @@
|
||||
# Prompt Input
|
||||
|
||||
URL: /components/prompt-input
|
||||
|
||||
---
|
||||
|
||||
title: Prompt Input
|
||||
description: Allows a user to send a message with file attachments to a large language model. It includes a textarea, file upload capabilities, a submit button, and a dropdown for selecting the model.
|
||||
path: elements/components/prompt-input
|
||||
|
||||
---
|
||||
|
||||
The `PromptInput` component allows a user to send a message with file attachments to a large language model. It includes a textarea, file upload capabilities, a submit button, and a dropdown for selecting the model.
|
||||
|
||||
<Preview path="prompt-input" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="prompt-input" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
PromptInput,
|
||||
PromptInputActionAddAttachments,
|
||||
PromptInputActionMenu,
|
||||
PromptInputActionMenuContent,
|
||||
PromptInputActionMenuItem,
|
||||
PromptInputActionMenuTrigger,
|
||||
PromptInputAttachment,
|
||||
PromptInputAttachments,
|
||||
PromptInputBody,
|
||||
PromptInputButton,
|
||||
PromptInputProvider,
|
||||
PromptInputSpeechButton,
|
||||
PromptInputSubmit,
|
||||
PromptInputTextarea,
|
||||
PromptInputFooter,
|
||||
PromptInputTools,
|
||||
usePromptInputAttachments,
|
||||
} from "@/components/ai-elements/prompt-input";
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { GlobeIcon } from "lucide-react";
|
||||
|
||||
<PromptInput onSubmit={() => {}} className="mt-4 relative">
|
||||
<PromptInputHeader>
|
||||
<PromptInputAttachments>{(attachment) => <PromptInputAttachment data={attachment} />}</PromptInputAttachments>
|
||||
</PromptInputHeader>
|
||||
<PromptInputBody>
|
||||
<PromptInputTextarea onChange={(e) => {}} value={""} />
|
||||
</PromptInputBody>
|
||||
<PromptInputFooter>
|
||||
<PromptInputTools>
|
||||
<PromptInputActionMenu>
|
||||
<PromptInputActionMenuTrigger />
|
||||
<PromptInputActionMenuContent>
|
||||
<PromptInputActionAddAttachments />
|
||||
</PromptInputActionMenuContent>
|
||||
</PromptInputActionMenu>
|
||||
<PromptInputSpeechButton />
|
||||
<PromptInputButton>
|
||||
<GlobeIcon size={16} />
|
||||
<span>Search</span>
|
||||
</PromptInputButton>
|
||||
<PromptInputModelSelect onValueChange={(value) => {}} value="gpt-4o">
|
||||
<PromptInputModelSelectTrigger>
|
||||
<PromptInputModelSelectValue />
|
||||
</PromptInputModelSelectTrigger>
|
||||
<PromptInputModelSelectContent>
|
||||
<PromptInputModelSelectItem value="gpt-4o">GPT-4o</PromptInputModelSelectItem>
|
||||
<PromptInputModelSelectItem value="claude-opus-4-20250514">Claude 4 Opus</PromptInputModelSelectItem>
|
||||
</PromptInputModelSelectContent>
|
||||
</PromptInputModelSelect>
|
||||
</PromptInputTools>
|
||||
<PromptInputSubmit disabled={false} status={"ready"} />
|
||||
</PromptInputFooter>
|
||||
</PromptInput>;
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a fully functional chat app using `PromptInput`, [`Conversation`](/elements/components/conversation) with a model picker:
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import {
|
||||
PromptInput,
|
||||
PromptInputActionAddAttachments,
|
||||
PromptInputActionMenu,
|
||||
PromptInputActionMenuContent,
|
||||
PromptInputActionMenuTrigger,
|
||||
PromptInputAttachment,
|
||||
PromptInputAttachments,
|
||||
PromptInputBody,
|
||||
PromptInputButton,
|
||||
type PromptInputMessage,
|
||||
PromptInputModelSelect,
|
||||
PromptInputModelSelectContent,
|
||||
PromptInputModelSelectItem,
|
||||
PromptInputModelSelectTrigger,
|
||||
PromptInputModelSelectValue,
|
||||
PromptInputSpeechButton,
|
||||
PromptInputSubmit,
|
||||
PromptInputTextarea,
|
||||
PromptInputFooter,
|
||||
PromptInputTools,
|
||||
} from "@/components/ai-elements/prompt-input";
|
||||
import { GlobeIcon } from "lucide-react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { Conversation, ConversationContent, ConversationScrollButton } from "@/components/ai-elements/conversation";
|
||||
import { Message, MessageContent } from "@/components/ai-elements/message";
|
||||
import { Response } from "@/components/ai-elements/response";
|
||||
|
||||
const models = [
|
||||
{ id: "gpt-4o", name: "GPT-4o" },
|
||||
{ id: "claude-opus-4-20250514", name: "Claude 4 Opus" },
|
||||
];
|
||||
|
||||
const InputDemo = () => {
|
||||
const [text, setText] = useState<string>("");
|
||||
const [model, setModel] = useState<string>(models[0].id);
|
||||
const [useWebSearch, setUseWebSearch] = useState<boolean>(false);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const { messages, status, sendMessage } = useChat();
|
||||
|
||||
const handleSubmit = (message: PromptInputMessage) => {
|
||||
const hasText = Boolean(message.text);
|
||||
const hasAttachments = Boolean(message.files?.length);
|
||||
|
||||
if (!(hasText || hasAttachments)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessage(
|
||||
{
|
||||
text: message.text || "Sent with attachments",
|
||||
files: message.files,
|
||||
},
|
||||
{
|
||||
body: {
|
||||
model: model,
|
||||
webSearch: useWebSearch,
|
||||
},
|
||||
}
|
||||
);
|
||||
setText("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<Conversation>
|
||||
<ConversationContent>
|
||||
{messages.map((message) => (
|
||||
<Message from={message.role} key={message.id}>
|
||||
<MessageContent>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return <Response key={`${message.id}-${i}`}>{part.text}</Response>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
))}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
|
||||
<PromptInput onSubmit={handleSubmit} className="mt-4" globalDrop multiple>
|
||||
<PromptInputHeader>
|
||||
<PromptInputAttachments>{(attachment) => <PromptInputAttachment data={attachment} />}</PromptInputAttachments>
|
||||
</PromptInputHeader>
|
||||
<PromptInputBody>
|
||||
<PromptInputTextarea onChange={(e) => setText(e.target.value)} ref={textareaRef} value={text} />
|
||||
</PromptInputBody>
|
||||
<PromptInputFooter>
|
||||
<PromptInputTools>
|
||||
<PromptInputActionMenu>
|
||||
<PromptInputActionMenuTrigger />
|
||||
<PromptInputActionMenuContent>
|
||||
<PromptInputActionAddAttachments />
|
||||
</PromptInputActionMenuContent>
|
||||
</PromptInputActionMenu>
|
||||
<PromptInputSpeechButton onTranscriptionChange={setText} textareaRef={textareaRef} />
|
||||
<PromptInputButton onClick={() => setUseWebSearch(!useWebSearch)} variant={useWebSearch ? "default" : "ghost"}>
|
||||
<GlobeIcon size={16} />
|
||||
<span>Search</span>
|
||||
</PromptInputButton>
|
||||
<PromptInputModelSelect
|
||||
onValueChange={(value) => {
|
||||
setModel(value);
|
||||
}}
|
||||
value={model}
|
||||
>
|
||||
<PromptInputModelSelectTrigger>
|
||||
<PromptInputModelSelectValue />
|
||||
</PromptInputModelSelectTrigger>
|
||||
<PromptInputModelSelectContent>
|
||||
{models.map((model) => (
|
||||
<PromptInputModelSelectItem key={model.id} value={model.id}>
|
||||
{model.name}
|
||||
</PromptInputModelSelectItem>
|
||||
))}
|
||||
</PromptInputModelSelectContent>
|
||||
</PromptInputModelSelect>
|
||||
</PromptInputTools>
|
||||
<PromptInputSubmit disabled={!text && !status} status={status} />
|
||||
</PromptInputFooter>
|
||||
</PromptInput>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```ts title="app/api/chat/route.ts"
|
||||
import { streamText, UIMessage, convertToModelMessages } from "ai";
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const {
|
||||
model,
|
||||
messages,
|
||||
webSearch,
|
||||
}: {
|
||||
messages: UIMessage[];
|
||||
model: string;
|
||||
webSearch?: boolean;
|
||||
} = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: webSearch ? "perplexity/sonar" : model,
|
||||
messages: convertToModelMessages(messages),
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Auto-resizing textarea that adjusts height based on content
|
||||
- File attachment support with drag-and-drop
|
||||
- Image preview for image attachments
|
||||
- Configurable file constraints (max files, max size, accepted types)
|
||||
- Automatic submit button icons based on status
|
||||
- Support for keyboard shortcuts (Enter to submit, Shift+Enter for new line)
|
||||
- Customizable min/max height for the textarea
|
||||
- Flexible toolbar with support for custom actions and tools
|
||||
- Built-in model selection dropdown
|
||||
- Built-in native speech recognition button (Web Speech API)
|
||||
- Optional provider for lifted state management
|
||||
- Form automatically resets on submit
|
||||
- Responsive design with mobile-friendly controls
|
||||
- Clean, modern styling with customizable themes
|
||||
- Form-based submission handling
|
||||
- Hidden file input sync for native form posts
|
||||
- Global document drop support (opt-in)
|
||||
|
||||
## Examples
|
||||
|
||||
### Cursor style
|
||||
|
||||
<Preview path="prompt-input-cursor" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<PromptInput />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
onSubmit: {
|
||||
description: 'Handler called when the form is submitted with message text and files.',
|
||||
type: '(message: PromptInputMessage, event: FormEvent) => void',
|
||||
},
|
||||
accept: {
|
||||
description: 'File types to accept (e.g., "image/*"). Leave undefined for any.',
|
||||
type: 'string',
|
||||
},
|
||||
multiple: {
|
||||
description: 'Whether to allow multiple file selection.',
|
||||
type: 'boolean',
|
||||
},
|
||||
globalDrop: {
|
||||
description: 'When true, accepts file drops anywhere on the document.',
|
||||
type: 'boolean',
|
||||
},
|
||||
syncHiddenInput: {
|
||||
description: 'Render a hidden input with given name for native form posts.',
|
||||
type: 'boolean',
|
||||
},
|
||||
maxFiles: {
|
||||
description: 'Maximum number of files allowed.',
|
||||
type: 'number',
|
||||
},
|
||||
maxFileSize: {
|
||||
description: 'Maximum file size in bytes.',
|
||||
type: 'number',
|
||||
},
|
||||
onError: {
|
||||
description: 'Handler for file validation errors.',
|
||||
type: '(err: { code: "max_files" | "max_file_size" | "accept", message: string }) => void',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root form element.',
|
||||
type: 'React.HTMLAttributes<HTMLFormElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputTextarea />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying Textarea component.',
|
||||
type: 'React.ComponentProps<typeof Textarea>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputFooter />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the toolbar div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputTools />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the tools div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputButton />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputSubmit />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
status: {
|
||||
description: 'Current chat status to determine button icon (submitted, streaming, error).',
|
||||
type: 'ChatStatus',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputModelSelect />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying Select component.',
|
||||
type: 'React.ComponentProps<typeof Select>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputModelSelectTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying SelectTrigger component.',
|
||||
type: 'React.ComponentProps<typeof SelectTrigger>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputModelSelectContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying SelectContent component.',
|
||||
type: 'React.ComponentProps<typeof SelectContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputModelSelectItem />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying SelectItem component.',
|
||||
type: 'React.ComponentProps<typeof SelectItem>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputModelSelectValue />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying SelectValue component.',
|
||||
type: 'React.ComponentProps<typeof SelectValue>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputBody />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the body div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputAttachments />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'Render function for each attachment.',
|
||||
type: '(attachment: FileUIPart & { id: string }) => React.ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the attachments container.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputAttachment />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
data: {
|
||||
description: 'The attachment data to display.',
|
||||
type: 'FileUIPart & { id: string }',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the attachment div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputActionMenu />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying DropdownMenu component.',
|
||||
type: 'React.ComponentProps<typeof DropdownMenu>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputActionMenuTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputActionMenuContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying DropdownMenuContent component.',
|
||||
type: 'React.ComponentProps<typeof DropdownMenuContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputActionMenuItem />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying DropdownMenuItem component.',
|
||||
type: 'React.ComponentProps<typeof DropdownMenuItem>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputActionAddAttachments />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
label: {
|
||||
description: 'Label for the menu item.',
|
||||
type: 'string',
|
||||
default: '"Add photos or files"',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying DropdownMenuItem component.',
|
||||
type: 'React.ComponentProps<typeof DropdownMenuItem>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputProvider />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
initialInput: {
|
||||
description: 'Initial text input value.',
|
||||
type: 'string',
|
||||
},
|
||||
children: {
|
||||
description: 'Child components that will have access to the provider context.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
Optional global provider that lifts PromptInput state outside of PromptInput. When used, it allows you to access and control the input state from anywhere within the provider tree. If not used, PromptInput stays fully self-managed.
|
||||
|
||||
### `<PromptInputSpeechButton />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
textareaRef: {
|
||||
description: 'Reference to the textarea element to insert transcribed text.',
|
||||
type: 'RefObject<HTMLTextAreaElement | null>',
|
||||
},
|
||||
onTranscriptionChange: {
|
||||
description: 'Callback fired when transcription text changes.',
|
||||
type: '(text: string) => void',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying PromptInputButton component.',
|
||||
type: 'React.ComponentProps<typeof PromptInputButton>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
Built-in button component that provides native speech recognition using the Web Speech API. The button will be disabled if speech recognition is not supported in the browser. Displays a microphone icon and pulses while actively listening.
|
||||
|
||||
## Hooks
|
||||
|
||||
### `usePromptInputAttachments`
|
||||
|
||||
Access and manage file attachments within a PromptInput context.
|
||||
|
||||
```tsx
|
||||
const attachments = usePromptInputAttachments();
|
||||
|
||||
// Available methods:
|
||||
attachments.files; // Array of current attachments
|
||||
attachments.add(files); // Add new files
|
||||
attachments.remove(id); // Remove an attachment by ID
|
||||
attachments.clear(); // Clear all attachments
|
||||
attachments.openFileDialog(); // Open file selection dialog
|
||||
```
|
||||
|
||||
### `usePromptInputController`
|
||||
|
||||
Access the full PromptInput controller from a PromptInputProvider. Only available when using the provider.
|
||||
|
||||
```tsx
|
||||
const controller = usePromptInputController();
|
||||
|
||||
// Available methods:
|
||||
controller.textInput.value; // Current text input value
|
||||
controller.textInput.setInput(value); // Set text input value
|
||||
controller.textInput.clear(); // Clear text input
|
||||
controller.attachments; // Same as usePromptInputAttachments
|
||||
```
|
||||
|
||||
### `useProviderAttachments`
|
||||
|
||||
Access attachments context from a PromptInputProvider. Only available when using the provider.
|
||||
|
||||
```tsx
|
||||
const attachments = useProviderAttachments();
|
||||
|
||||
// Same interface as usePromptInputAttachments
|
||||
```
|
||||
|
||||
### `<PromptInputHeader />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props (except align) are spread to the InputGroupAddon component.',
|
||||
type: 'Omit<React.ComponentProps<typeof InputGroupAddon>, "align">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputHoverCard />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
openDelay: {
|
||||
description: 'Delay in milliseconds before opening.',
|
||||
type: 'number',
|
||||
default: '0',
|
||||
},
|
||||
closeDelay: {
|
||||
description: 'Delay in milliseconds before closing.',
|
||||
type: 'number',
|
||||
default: '0',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the HoverCard component.',
|
||||
type: 'React.ComponentProps<typeof HoverCard>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputHoverCardTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the HoverCardTrigger component.',
|
||||
type: 'React.ComponentProps<typeof HoverCardTrigger>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputHoverCardContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
align: {
|
||||
description: 'Alignment of the hover card content.',
|
||||
type: '"start" | "center" | "end"',
|
||||
default: '"start"',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the HoverCardContent component.',
|
||||
type: 'React.ComponentProps<typeof HoverCardContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputTabsList />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the div element.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputTab />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the div element.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputTabLabel />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the h3 element.',
|
||||
type: 'React.HTMLAttributes<HTMLHeadingElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputTabBody />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the div element.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputTabItem />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the div element.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputCommand />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the Command component.',
|
||||
type: 'React.ComponentProps<typeof Command>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputCommandInput />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CommandInput component.',
|
||||
type: 'React.ComponentProps<typeof CommandInput>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputCommandList />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CommandList component.',
|
||||
type: 'React.ComponentProps<typeof CommandList>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputCommandEmpty />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CommandEmpty component.',
|
||||
type: 'React.ComponentProps<typeof CommandEmpty>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputCommandGroup />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CommandGroup component.',
|
||||
type: 'React.ComponentProps<typeof CommandGroup>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputCommandItem />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CommandItem component.',
|
||||
type: 'React.ComponentProps<typeof CommandItem>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<PromptInputCommandSeparator />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CommandSeparator component.',
|
||||
type: 'React.ComponentProps<typeof CommandSeparator>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
272
docs/components/queue.md
Normal file
272
docs/components/queue.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# Queue
|
||||
|
||||
URL: /components/queue
|
||||
|
||||
---
|
||||
|
||||
title: Queue
|
||||
description: A comprehensive queue component system for displaying message lists, todos, and collapsible task sections in AI applications.
|
||||
path: elements/components/queue
|
||||
|
||||
---
|
||||
|
||||
The `Queue` component provides a flexible system for displaying lists of messages, todos, attachments, and collapsible sections. Perfect for showing AI workflow progress, pending tasks, message history, or any structured list of items in your application.
|
||||
|
||||
<Preview path="queue" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="queue" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Queue,
|
||||
QueueSection,
|
||||
QueueSectionTrigger,
|
||||
QueueSectionLabel,
|
||||
QueueSectionContent,
|
||||
QueueList,
|
||||
QueueItem,
|
||||
QueueItemIndicator,
|
||||
QueueItemContent,
|
||||
} from "@/components/ai-elements/queue";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Queue>
|
||||
<QueueSection>
|
||||
<QueueSectionTrigger>
|
||||
<QueueSectionLabel count={3} label="Tasks" />
|
||||
</QueueSectionTrigger>
|
||||
<QueueSectionContent>
|
||||
<QueueList>
|
||||
<QueueItem>
|
||||
<QueueItemIndicator />
|
||||
<QueueItemContent>Analyze user requirements</QueueItemContent>
|
||||
</QueueItem>
|
||||
</QueueList>
|
||||
</QueueSectionContent>
|
||||
</QueueSection>
|
||||
</Queue>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Flexible component system with composable parts
|
||||
- Collapsible sections with smooth animations
|
||||
- Support for completed/pending state indicators
|
||||
- Built-in scroll area for long lists
|
||||
- Attachment display with images and file indicators
|
||||
- Hover-revealed action buttons for queue items
|
||||
- TypeScript support with comprehensive type definitions
|
||||
- Customizable styling with Tailwind CSS
|
||||
- Responsive design with mobile-friendly interactions
|
||||
- Keyboard navigation and accessibility support
|
||||
- Theme-aware with automatic dark mode support
|
||||
|
||||
## Examples
|
||||
|
||||
### With PromptInput
|
||||
|
||||
<Preview path="queue-prompt-input" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Queue />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueSection />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
defaultOpen: {
|
||||
description: 'Whether the section is open by default.',
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the Collapsible component.',
|
||||
type: 'React.ComponentProps<typeof Collapsible>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueSectionTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the button element.',
|
||||
type: 'React.ComponentProps<"button">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueSectionLabel />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
label: {
|
||||
description: 'The label text to display.',
|
||||
type: 'string',
|
||||
},
|
||||
count: {
|
||||
description: 'The count to display before the label.',
|
||||
type: 'number',
|
||||
},
|
||||
icon: {
|
||||
description: 'An optional icon to display before the count.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the span element.',
|
||||
type: 'React.ComponentProps<"span">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueSectionContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CollapsibleContent component.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueList />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the ScrollArea component.',
|
||||
type: 'React.ComponentProps<typeof ScrollArea>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueItem />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the li element.',
|
||||
type: 'React.ComponentProps<"li">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueItemIndicator />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
completed: {
|
||||
description: 'Whether the item is completed. Affects the indicator styling.',
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the span element.',
|
||||
type: 'React.ComponentProps<"span">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueItemContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
completed: {
|
||||
description: 'Whether the item is completed. Affects text styling with strikethrough and opacity.',
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the span element.',
|
||||
type: 'React.ComponentProps<"span">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueItemDescription />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
completed: {
|
||||
description: 'Whether the item is completed. Affects text styling.',
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the div element.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueItemActions />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the div element.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueItemAction />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props (except variant and size) are spread to the Button component.',
|
||||
type: 'Omit<React.ComponentProps<typeof Button>, "variant" | "size">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueItemAttachment />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the div element.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueItemImage />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the img element.',
|
||||
type: 'React.ComponentProps<"img">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<QueueItemFile />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the span element.',
|
||||
type: 'React.ComponentProps<"span">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
213
docs/components/reasoning.md
Normal file
213
docs/components/reasoning.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Reasoning
|
||||
|
||||
URL: /components/reasoning
|
||||
|
||||
---
|
||||
|
||||
title: Reasoning
|
||||
description: A collapsible component that displays AI reasoning content, automatically opening during streaming and closing when finished.
|
||||
path: elements/components/reasoning
|
||||
|
||||
---
|
||||
|
||||
The `Reasoning` component displays AI reasoning content, automatically opening during streaming and closing when finished.
|
||||
|
||||
<Preview path="reasoning" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="reasoning" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Reasoning className="w-full" isStreaming={false}>
|
||||
<ReasoningTrigger />
|
||||
<ReasoningContent>I need to computer the square of 2.</ReasoningContent>
|
||||
</Reasoning>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a chatbot with reasoning using Deepseek R1.
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Reasoning,
|
||||
ReasoningContent,
|
||||
ReasoningTrigger,
|
||||
} from '@/components/ai-elements/reasoning';
|
||||
import {
|
||||
Conversation,
|
||||
ConversationContent,
|
||||
ConversationScrollButton,
|
||||
} from '@/components/ai-elements/conversation';
|
||||
import {
|
||||
PromptInput,
|
||||
PromptInputTextarea,
|
||||
PromptInputSubmit,
|
||||
} from '@/components/ai-elements/prompt-input';
|
||||
import { Loader } from '@/components/ai-elements/loader';
|
||||
import { Message, MessageContent } from '@/components/ai-elements/message';
|
||||
import { useState } from 'react';
|
||||
import { useChat } from '@ai-sdk/react';
|
||||
import { Response } from @/components/ai-elements/response';
|
||||
|
||||
const ReasoningDemo = () => {
|
||||
const [input, setInput] = useState('');
|
||||
|
||||
const { messages, sendMessage, status } = useChat();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
sendMessage({ text: input });
|
||||
setInput('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<Conversation>
|
||||
<ConversationContent>
|
||||
{messages.map((message) => (
|
||||
<Message from={message.role} key={message.id}>
|
||||
<MessageContent>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case 'text':
|
||||
return (
|
||||
<Response key={`${message.id}-${i}`}>
|
||||
{part.text}
|
||||
</Response>
|
||||
);
|
||||
case 'reasoning':
|
||||
return (
|
||||
<Reasoning
|
||||
key={`${message.id}-${i}`}
|
||||
className="w-full"
|
||||
isStreaming={status === 'streaming' && i === message.parts.length - 1 && message.id === messages.at(-1)?.id}
|
||||
>
|
||||
<ReasoningTrigger />
|
||||
<ReasoningContent>{part.text}</ReasoningContent>
|
||||
</Reasoning>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
))}
|
||||
{status === 'submitted' && <Loader />}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
|
||||
<PromptInput
|
||||
onSubmit={handleSubmit}
|
||||
className="mt-4 w-full max-w-2xl mx-auto relative"
|
||||
>
|
||||
<PromptInputTextarea
|
||||
value={input}
|
||||
placeholder="Say something..."
|
||||
onChange={(e) => setInput(e.currentTarget.value)}
|
||||
className="pr-12"
|
||||
/>
|
||||
<PromptInputSubmit
|
||||
status={status === 'streaming' ? 'streaming' : 'ready'}
|
||||
disabled={!input.trim()}
|
||||
className="absolute bottom-1 right-1"
|
||||
/>
|
||||
</PromptInput>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReasoningDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```ts title="app/api/chat/route.ts"
|
||||
import { streamText, UIMessage, convertToModelMessages } from "ai";
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { model, messages }: { messages: UIMessage[]; model: string } = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: "deepseek/deepseek-r1",
|
||||
messages: convertToModelMessages(messages),
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse({
|
||||
sendReasoning: true,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Automatically opens when streaming content and closes when finished
|
||||
- Manual toggle control for user interaction
|
||||
- Smooth animations and transitions powered by Radix UI
|
||||
- Visual streaming indicator with pulsing animation
|
||||
- Composable architecture with separate trigger and content components
|
||||
- Built with accessibility in mind including keyboard navigation
|
||||
- Responsive design that works across different screen sizes
|
||||
- Seamlessly integrates with both light and dark themes
|
||||
- Built on top of shadcn/ui Collapsible primitives
|
||||
- TypeScript support with proper type definitions
|
||||
|
||||
## Props
|
||||
|
||||
### `<Reasoning />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
isStreaming: {
|
||||
description: 'Whether the reasoning is currently streaming (auto-opens and closes the panel).',
|
||||
type: 'boolean',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying Collapsible component.',
|
||||
type: 'React.ComponentProps<typeof Collapsible>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ReasoningTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
title: {
|
||||
description: 'Optional title to display in the trigger.',
|
||||
type: 'string',
|
||||
default: '"Reasoning"',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CollapsibleTrigger component.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleTrigger>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ReasoningContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying CollapsibleContent component.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
153
docs/components/response.md
Normal file
153
docs/components/response.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Response
|
||||
|
||||
URL: /components/response
|
||||
|
||||
---
|
||||
|
||||
title: Response
|
||||
description: A component that renders a Markdown response from a large language model.
|
||||
path: elements/components/response
|
||||
|
||||
---
|
||||
|
||||
The `Response` component renders a Markdown response from a large language model. It uses [Streamdown](https://streamdown.ai/) under the hood to render the markdown.
|
||||
|
||||
<Preview path="response" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="response" />
|
||||
|
||||
<Callout label={false} type="warning">
|
||||
**Important:** After adding the component, you'll need to add the following to your `globals.css` file:
|
||||
|
||||
```css
|
||||
@source "../node_modules/streamdown/dist/index.js";
|
||||
```
|
||||
|
||||
This is **required** for the Response component to work properly. Without this import, the Streamdown styles will not be applied to your project. See [Streamdown's documentation](https://streamdown.ai/) for more details.
|
||||
</Callout>
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Response } from "@/components/ai-elements/response";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Response>**Hi there.** I am an AI model designed to help you.</Response>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Populate a markdown response with messages from [`useChat`](/docs/reference/ai-sdk-ui/use-chat).
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { Conversation, ConversationContent, ConversationScrollButton } from "@/components/ai-elements/conversation";
|
||||
import { Message, MessageContent } from "@/components/ai-elements/message";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { Response } from "@/components/ai-elements/response";
|
||||
|
||||
const ResponseDemo = () => {
|
||||
const { messages } = useChat();
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<Conversation>
|
||||
<ConversationContent>
|
||||
{messages.map((message) => (
|
||||
<Message from={message.role} key={message.id}>
|
||||
<MessageContent>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "text": // we don't use any reasoning or tool calls in this example
|
||||
return <Response key={`${message.id}-${i}`}>{part.text}</Response>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
))}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResponseDemo;
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Renders markdown content with support for paragraphs, links, and code blocks
|
||||
- Supports GFM features like tables, task lists, and strikethrough text via remark-gfm
|
||||
- Supports rendering Math Equations via rehype-katex
|
||||
- **Smart streaming support** - automatically completes incomplete formatting during real-time text streaming
|
||||
- Code blocks are rendered with syntax highlighting for various programming languages
|
||||
- Code blocks include a button to easily copy code to clipboard
|
||||
- Adapts to different screen sizes while maintaining readability
|
||||
- Seamlessly integrates with both light and dark themes
|
||||
- Customizable appearance through className props and Tailwind CSS utilities
|
||||
- Built with accessibility in mind for all users
|
||||
|
||||
## Props
|
||||
|
||||
### `<Response />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'The markdown content to render.',
|
||||
type: 'string',
|
||||
},
|
||||
parseIncompleteMarkdown: {
|
||||
description: 'Whether to parse and fix incomplete markdown syntax (e.g., unclosed code blocks or lists).',
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
},
|
||||
className: {
|
||||
description: 'CSS class names to apply to the wrapper div element.',
|
||||
type: 'string',
|
||||
},
|
||||
components: {
|
||||
description: 'Custom React components to use for rendering markdown elements (e.g., custom heading, paragraph, code block components).',
|
||||
type: 'object',
|
||||
},
|
||||
allowedImagePrefixes: {
|
||||
description: 'Array of allowed URL prefixes for images. Use ["*"] to allow all images.',
|
||||
type: 'string[]',
|
||||
default: '["*"]',
|
||||
},
|
||||
allowedLinkPrefixes: {
|
||||
description: 'Array of allowed URL prefixes for links. Use ["*"] to allow all links.',
|
||||
type: 'string[]',
|
||||
default: '["*"]',
|
||||
},
|
||||
defaultOrigin: {
|
||||
description: 'Default origin to use for relative URLs in links and images.',
|
||||
type: 'string',
|
||||
},
|
||||
rehypePlugins: {
|
||||
description: 'Array of rehype plugins to use for processing HTML. Includes KaTeX for math rendering by default.',
|
||||
type: 'array',
|
||||
default: '[rehypeKatex]',
|
||||
},
|
||||
remarkPlugins: {
|
||||
description: 'Array of remark plugins to use for processing markdown. Includes GitHub Flavored Markdown and math support by default.',
|
||||
type: 'array',
|
||||
default: '[remarkGfm, remarkMath]',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
84
docs/components/shimmer.md
Normal file
84
docs/components/shimmer.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Shimmer
|
||||
|
||||
URL: /components/shimmer
|
||||
|
||||
---
|
||||
|
||||
title: Shimmer
|
||||
description: An animated text shimmer component for creating eye-catching loading states and progressive reveal effects.
|
||||
path: elements/components/shimmer
|
||||
|
||||
---
|
||||
|
||||
The `Shimmer` component provides an animated shimmer effect that sweeps across text, perfect for indicating loading states, progressive reveals, or drawing attention to dynamic content in AI applications.
|
||||
|
||||
<Preview path="shimmer" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="shimmer" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Shimmer } from "@/components/ai-elements/shimmer";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Shimmer>Loading your response...</Shimmer>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Smooth animated shimmer effect using CSS gradients and Framer Motion
|
||||
- Customizable animation duration and spread
|
||||
- Polymorphic component - render as any HTML element via the `as` prop
|
||||
- Automatic spread calculation based on text length
|
||||
- Theme-aware styling using CSS custom properties
|
||||
- Infinite looping animation with linear easing
|
||||
- TypeScript support with proper type definitions
|
||||
- Memoized for optimal performance
|
||||
- Responsive and accessible design
|
||||
- Uses `text-transparent` with background-clip for crisp text rendering
|
||||
|
||||
## Examples
|
||||
|
||||
### Different Durations
|
||||
|
||||
<Preview path="shimmer-duration" />
|
||||
|
||||
### Custom Elements
|
||||
|
||||
<Preview path="shimmer-elements" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Shimmer />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
children: {
|
||||
description: 'The text content to apply the shimmer effect to.',
|
||||
type: 'string',
|
||||
},
|
||||
as: {
|
||||
description: 'The HTML element or React component to render.',
|
||||
type: 'ElementType',
|
||||
default: '"p"',
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the component.',
|
||||
type: 'string',
|
||||
},
|
||||
duration: {
|
||||
description: 'The duration of the shimmer animation in seconds.',
|
||||
type: 'number',
|
||||
default: '2',
|
||||
},
|
||||
spread: {
|
||||
description: 'The spread multiplier for the shimmer gradient, multiplied by text length.',
|
||||
type: 'number',
|
||||
default: '2',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
219
docs/components/sources.md
Normal file
219
docs/components/sources.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Sources
|
||||
|
||||
URL: /components/sources
|
||||
|
||||
---
|
||||
|
||||
title: Sources
|
||||
description: A component that allows a user to view the sources or citations used to generate a response.
|
||||
path: elements/components/sources
|
||||
|
||||
---
|
||||
|
||||
The `Sources` component allows a user to view the sources or citations used to generate a response.
|
||||
|
||||
<Preview path="sources" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="sources" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Source, Sources, SourcesContent, SourcesTrigger } from "@/components/ai-elements/sources";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Sources>
|
||||
<SourcesTrigger count={1} />
|
||||
<SourcesContent>
|
||||
<Source href="https://ai-sdk.dev" title="AI SDK" />
|
||||
</SourcesContent>
|
||||
</Sources>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a simple web search agent with Perplexity Sonar.
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { Source, Sources, SourcesContent, SourcesTrigger } from "@/components/ai-elements/sources";
|
||||
import { Input, PromptInputTextarea, PromptInputSubmit } from "@/components/ai-elements/prompt-input";
|
||||
import { Conversation, ConversationContent, ConversationScrollButton } from "@/components/ai-elements/conversation";
|
||||
import { Message, MessageContent } from "@/components/ai-elements/message";
|
||||
import { Response } from "@/components/ai-elements/response";
|
||||
import { useState } from "react";
|
||||
import { DefaultChatTransport } from "ai";
|
||||
|
||||
const SourceDemo = () => {
|
||||
const [input, setInput] = useState("");
|
||||
const { messages, sendMessage, status } = useChat({
|
||||
transport: new DefaultChatTransport({
|
||||
api: "/api/sources",
|
||||
}),
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (input.trim()) {
|
||||
sendMessage({ text: input });
|
||||
setInput("");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1 overflow-auto mb-4">
|
||||
<Conversation>
|
||||
<ConversationContent>
|
||||
{messages.map((message) => (
|
||||
<div key={message.id}>
|
||||
{message.role === "assistant" && (
|
||||
<Sources>
|
||||
<SourcesTrigger count={message.parts.filter((part) => part.type === "source-url").length} />
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "source-url":
|
||||
return (
|
||||
<SourcesContent key={`${message.id}-${i}`}>
|
||||
<Source key={`${message.id}-${i}`} href={part.url} title={part.url} />
|
||||
</SourcesContent>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Sources>
|
||||
)}
|
||||
<Message from={message.role} key={message.id}>
|
||||
<MessageContent>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return <Response key={`${message.id}-${i}`}>{part.text}</Response>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
</div>
|
||||
))}
|
||||
</ConversationContent>
|
||||
<ConversationScrollButton />
|
||||
</Conversation>
|
||||
</div>
|
||||
|
||||
<Input onSubmit={handleSubmit} className="mt-4 w-full max-w-2xl mx-auto relative">
|
||||
<PromptInputTextarea
|
||||
value={input}
|
||||
placeholder="Ask a question and search the..."
|
||||
onChange={(e) => setInput(e.currentTarget.value)}
|
||||
className="pr-12"
|
||||
/>
|
||||
<PromptInputSubmit
|
||||
status={status === "streaming" ? "streaming" : "ready"}
|
||||
disabled={!input.trim()}
|
||||
className="absolute bottom-1 right-1"
|
||||
/>
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SourceDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```tsx title="api/chat/route.ts"
|
||||
import { convertToModelMessages, streamText, UIMessage } from "ai";
|
||||
import { perplexity } from "@ai-sdk/perplexity";
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages }: { messages: UIMessage[] } = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: "perplexity/sonar",
|
||||
system: "You are a helpful assistant. Keep your responses short (< 100 words) unless you are asked for more details. ALWAYS USE SEARCH.",
|
||||
messages: convertToModelMessages(messages),
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse({
|
||||
sendSources: true,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Collapsible component that allows a user to view the sources or citations used to generate a response
|
||||
- Customizable trigger and content components
|
||||
- Support for custom sources or citations
|
||||
- Responsive design with mobile-friendly controls
|
||||
- Clean, modern styling with customizable themes
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom rendering
|
||||
|
||||
<Preview path="sources-custom" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Sources />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<SourcesTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
count: {
|
||||
description: 'The number of sources to display in the trigger.',
|
||||
type: 'number',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the trigger button.',
|
||||
type: 'React.ButtonHTMLAttributes<HTMLButtonElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<SourcesContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the content container.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<Source />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the anchor element.',
|
||||
type: 'React.AnchorHTMLAttributes<HTMLAnchorElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
143
docs/components/suggestion.md
Normal file
143
docs/components/suggestion.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Suggestion
|
||||
|
||||
URL: /components/suggestion
|
||||
|
||||
---
|
||||
|
||||
title: Suggestion
|
||||
description: A suggestion component that displays a horizontal row of clickable suggestions for user interaction.
|
||||
path: elements/components/suggestion
|
||||
|
||||
---
|
||||
|
||||
The `Suggestion` component displays a horizontal row of clickable suggestions for user interaction.
|
||||
|
||||
<Preview path="suggestion" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="suggestion" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Suggestion, Suggestions } from "@/components/ai-elements/suggestion";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Suggestions>
|
||||
<Suggestion suggestion="What are the latest trends in AI?" />
|
||||
</Suggestions>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a simple input with suggestions users can click to send a message to the LLM.
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { Input, PromptInputTextarea, PromptInputSubmit } from "@/components/ai-elements/prompt-input";
|
||||
import { Suggestion, Suggestions } from "@/components/ai-elements/suggestion";
|
||||
import { useState } from "react";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
|
||||
const suggestions = ["Can you explain how to play tennis?", "What is the weather in Tokyo?", "How do I make a really good fish taco?"];
|
||||
|
||||
const SuggestionDemo = () => {
|
||||
const [input, setInput] = useState("");
|
||||
const { sendMessage, status } = useChat();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (input.trim()) {
|
||||
sendMessage({ text: input });
|
||||
setInput("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSuggestionClick = (suggestion: string) => {
|
||||
sendMessage({ text: suggestion });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Suggestions>
|
||||
{suggestions.map((suggestion) => (
|
||||
<Suggestion key={suggestion} onClick={handleSuggestionClick} suggestion={suggestion} />
|
||||
))}
|
||||
</Suggestions>
|
||||
<Input onSubmit={handleSubmit} className="mt-4 w-full max-w-2xl mx-auto relative">
|
||||
<PromptInputTextarea
|
||||
value={input}
|
||||
placeholder="Say something..."
|
||||
onChange={(e) => setInput(e.currentTarget.value)}
|
||||
className="pr-12"
|
||||
/>
|
||||
<PromptInputSubmit
|
||||
status={status === "streaming" ? "streaming" : "ready"}
|
||||
disabled={!input.trim()}
|
||||
className="absolute bottom-1 right-1"
|
||||
/>
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SuggestionDemo;
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Horizontal row of clickable suggestion buttons
|
||||
- Customizable styling with variant and size options
|
||||
- Flexible layout that wraps suggestions on smaller screens
|
||||
- onClick callback that emits the selected suggestion string
|
||||
- Support for both individual suggestions and suggestion lists
|
||||
- Clean, modern styling with hover effects
|
||||
- Responsive design with mobile-friendly touch targets
|
||||
- TypeScript support with proper type definitions
|
||||
|
||||
## Examples
|
||||
|
||||
### Usage with AI Input
|
||||
|
||||
<Preview path="suggestion-input" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Suggestions />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying ScrollArea component.',
|
||||
type: 'React.ComponentProps<typeof ScrollArea>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<Suggestion />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
suggestion: {
|
||||
description: 'The suggestion string to display and emit on click.',
|
||||
type: 'string',
|
||||
},
|
||||
onClick: {
|
||||
description: 'Callback fired when the suggestion is clicked.',
|
||||
type: '(suggestion: string) => void',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'Omit<React.ComponentProps<typeof Button>, "onClick">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
247
docs/components/task.md
Normal file
247
docs/components/task.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# Task
|
||||
|
||||
URL: /components/task
|
||||
|
||||
---
|
||||
|
||||
title: Task
|
||||
description: A collapsible task list component for displaying AI workflow progress, with status indicators and optional descriptions.
|
||||
path: elements/components/task
|
||||
|
||||
---
|
||||
|
||||
The `Task` component provides a structured way to display task lists or workflow progress with collapsible details, status indicators, and progress tracking. It consists of a main `Task` container with `TaskTrigger` for the clickable header and `TaskContent` for the collapsible content area.
|
||||
|
||||
<Preview path="task" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="task" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Task, TaskContent, TaskItem, TaskItemFile, TaskTrigger } from "@/components/ai-elements/task";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Task className="w-full">
|
||||
<TaskTrigger title="Found project files" />
|
||||
<TaskContent>
|
||||
<TaskItem>
|
||||
Read <TaskItemFile>index.md</TaskItemFile>
|
||||
</TaskItem>
|
||||
</TaskContent>
|
||||
</Task>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a mock async programming agent using [`experimental_generateObject`](/docs/reference/ai-sdk-ui/use-object).
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { experimental_useObject as useObject } from "@ai-sdk/react";
|
||||
import { Task, TaskItem, TaskItemFile, TaskTrigger, TaskContent } from "@/components/ai-elements/task";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { tasksSchema } from "@/app/api/task/route";
|
||||
import { SiReact, SiTypescript, SiJavascript, SiCss, SiHtml5, SiJson, SiMarkdown } from "@icons-pack/react-simple-icons";
|
||||
|
||||
const iconMap = {
|
||||
react: { component: SiReact, color: "#149ECA" },
|
||||
typescript: { component: SiTypescript, color: "#3178C6" },
|
||||
javascript: { component: SiJavascript, color: "#F7DF1E" },
|
||||
css: { component: SiCss, color: "#1572B6" },
|
||||
html: { component: SiHtml5, color: "#E34F26" },
|
||||
json: { component: SiJson, color: "#000000" },
|
||||
markdown: { component: SiMarkdown, color: "#000000" },
|
||||
};
|
||||
|
||||
const TaskDemo = () => {
|
||||
const { object, submit, isLoading } = useObject({
|
||||
api: "/api/agent",
|
||||
schema: tasksSchema,
|
||||
});
|
||||
|
||||
const handleSubmit = (taskType: string) => {
|
||||
submit({ prompt: taskType });
|
||||
};
|
||||
|
||||
const renderTaskItem = (item: any, index: number) => {
|
||||
if (item?.type === "file" && item.file) {
|
||||
const iconInfo = iconMap[item.file.icon as keyof typeof iconMap];
|
||||
if (iconInfo) {
|
||||
const IconComponent = iconInfo.component;
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1" key={index}>
|
||||
{item.text}
|
||||
<TaskItemFile>
|
||||
<IconComponent color={item.file.color || iconInfo.color} className="size-4" />
|
||||
<span>{item.file.name}</span>
|
||||
</TaskItemFile>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
return item?.text || "";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex gap-2 mb-6 flex-wrap">
|
||||
<Button onClick={() => handleSubmit("React component development")} disabled={isLoading} variant="outline">
|
||||
React Development
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto space-y-4">
|
||||
{isLoading && !object && <div className="text-muted-foreground">Generating tasks...</div>}
|
||||
|
||||
{object?.tasks?.map((task: any, taskIndex: number) => (
|
||||
<Task key={taskIndex} defaultOpen={taskIndex === 0}>
|
||||
<TaskTrigger title={task.title || "Loading..."} />
|
||||
<TaskContent>
|
||||
{task.items?.map((item: any, itemIndex: number) => (
|
||||
<TaskItem key={itemIndex}>{renderTaskItem(item, itemIndex)}</TaskItem>
|
||||
))}
|
||||
</TaskContent>
|
||||
</Task>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```ts title="app/api/agent.ts"
|
||||
import { streamObject } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
export const taskItemSchema = z.object({
|
||||
type: z.enum(["text", "file"]),
|
||||
text: z.string(),
|
||||
file: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
color: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const taskSchema = z.object({
|
||||
title: z.string(),
|
||||
items: z.array(taskItemSchema),
|
||||
status: z.enum(["pending", "in_progress", "completed"]),
|
||||
});
|
||||
|
||||
export const tasksSchema = z.object({
|
||||
tasks: z.array(taskSchema),
|
||||
});
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { prompt } = await req.json();
|
||||
|
||||
const result = streamObject({
|
||||
model: "openai/gpt-4o",
|
||||
schema: tasksSchema,
|
||||
prompt: `You are an AI assistant that generates realistic development task workflows. Generate a set of tasks that would occur during ${prompt}.
|
||||
|
||||
Each task should have:
|
||||
- A descriptive title
|
||||
- Multiple task items showing the progression
|
||||
- Some items should be plain text, others should reference files
|
||||
- Use realistic file names and appropriate file types
|
||||
- Status should progress from pending to in_progress to completed
|
||||
|
||||
For file items, use these icon types: 'react', 'typescript', 'javascript', 'css', 'html', 'json', 'markdown'
|
||||
|
||||
Generate 3-4 tasks total, with 4-6 items each.`,
|
||||
});
|
||||
|
||||
return result.toTextStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Visual icons for pending, in-progress, completed, and error states
|
||||
- Expandable content for task descriptions and additional information
|
||||
- Built-in progress counter showing completed vs total tasks
|
||||
- Optional progressive reveal of tasks with customizable timing
|
||||
- Support for custom content within task items
|
||||
- Full type safety with proper TypeScript definitions
|
||||
- Keyboard navigation and screen reader support
|
||||
|
||||
## Props
|
||||
|
||||
### `<Task />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root Collapsible component.',
|
||||
type: 'React.ComponentProps<typeof Collapsible>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<TaskTrigger />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
title: {
|
||||
description: 'The title of the task that will be displayed in the trigger.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CollapsibleTrigger component.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleTrigger>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<TaskContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CollapsibleContent component.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<TaskItem />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<TaskItemFile />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
292
docs/components/tool.md
Normal file
292
docs/components/tool.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# Tool
|
||||
|
||||
URL: /components/tool
|
||||
|
||||
---
|
||||
|
||||
title: Tool
|
||||
description: A collapsible component for displaying tool invocation details in AI chatbot interfaces.
|
||||
path: elements/components/tool
|
||||
|
||||
---
|
||||
|
||||
The `Tool` component displays a collapsible interface for showing/hiding tool details. It is designed to take the `ToolUIPart` type from the AI SDK and display it in a collapsible interface.
|
||||
|
||||
<Preview path="tool" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="tool" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Tool, ToolContent, ToolHeader, ToolOutput, ToolInput } from "@/components/ai-elements/tool";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Tool>
|
||||
<ToolHeader type="tool-call" state={"output-available" as const} />
|
||||
<ToolContent>
|
||||
<ToolInput input="Input to tool call" />
|
||||
<ToolOutput errorText="Error" output="Output from tool call" />
|
||||
</ToolContent>
|
||||
</Tool>
|
||||
```
|
||||
|
||||
## Usage in AI SDK
|
||||
|
||||
Build a simple stateful weather app that renders the last message in a tool using [`useChat`](/docs/reference/ai-sdk-ui/use-chat).
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { DefaultChatTransport, type ToolUIPart } from "ai";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Response } from "@/components/ai-elements/response";
|
||||
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from "@/components/ai-elements/tool";
|
||||
|
||||
type WeatherToolInput = {
|
||||
location: string;
|
||||
units: "celsius" | "fahrenheit";
|
||||
};
|
||||
|
||||
type WeatherToolOutput = {
|
||||
location: string;
|
||||
temperature: string;
|
||||
conditions: string;
|
||||
humidity: string;
|
||||
windSpeed: string;
|
||||
lastUpdated: string;
|
||||
};
|
||||
|
||||
type WeatherToolUIPart = ToolUIPart<{
|
||||
fetch_weather_data: {
|
||||
input: WeatherToolInput;
|
||||
output: WeatherToolOutput;
|
||||
};
|
||||
}>;
|
||||
|
||||
const Example = () => {
|
||||
const { messages, sendMessage, status } = useChat({
|
||||
transport: new DefaultChatTransport({
|
||||
api: "/api/weather",
|
||||
}),
|
||||
});
|
||||
|
||||
const handleWeatherClick = () => {
|
||||
sendMessage({ text: "Get weather data for San Francisco in fahrenheit" });
|
||||
};
|
||||
|
||||
const latestMessage = messages[messages.length - 1];
|
||||
const weatherTool = latestMessage?.parts?.find((part) => part.type === "tool-fetch_weather_data") as WeatherToolUIPart | undefined;
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="space-y-4">
|
||||
<Button onClick={handleWeatherClick} disabled={status !== "ready"}>
|
||||
Get Weather for San Francisco
|
||||
</Button>
|
||||
|
||||
{weatherTool && (
|
||||
<Tool defaultOpen={true}>
|
||||
<ToolHeader type="tool-fetch_weather_data" state={weatherTool.state} />
|
||||
<ToolContent>
|
||||
<ToolInput input={weatherTool.input} />
|
||||
<ToolOutput
|
||||
output={<Response>{formatWeatherResult(weatherTool.output)}</Response>}
|
||||
errorText={weatherTool.errorText}
|
||||
/>
|
||||
</ToolContent>
|
||||
</Tool>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function formatWeatherResult(result: WeatherToolOutput): string {
|
||||
return `**Weather for ${result.location}**
|
||||
|
||||
**Temperature:** ${result.temperature}
|
||||
**Conditions:** ${result.conditions}
|
||||
**Humidity:** ${result.humidity}
|
||||
**Wind Speed:** ${result.windSpeed}
|
||||
|
||||
*Last updated: ${result.lastUpdated}*`;
|
||||
}
|
||||
|
||||
export default Example;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```ts title="app/api/weather/route.tsx"
|
||||
import { streamText, UIMessage, convertToModelMessages } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
// Allow streaming responses up to 30 seconds
|
||||
export const maxDuration = 30;
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages }: { messages: UIMessage[] } = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: "openai/gpt-4o",
|
||||
messages: convertToModelMessages(messages),
|
||||
tools: {
|
||||
fetch_weather_data: {
|
||||
description: "Fetch weather information for a specific location",
|
||||
parameters: z.object({
|
||||
location: z.string().describe("The city or location to get weather for"),
|
||||
units: z.enum(["celsius", "fahrenheit"]).default("celsius").describe("Temperature units"),
|
||||
}),
|
||||
inputSchema: z.object({
|
||||
location: z.string(),
|
||||
units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
|
||||
}),
|
||||
execute: async ({ location, units }) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
const temp = units === "celsius" ? Math.floor(Math.random() * 35) + 5 : Math.floor(Math.random() * 63) + 41;
|
||||
|
||||
return {
|
||||
location,
|
||||
temperature: `${temp}°${units === "celsius" ? "C" : "F"}`,
|
||||
conditions: "Sunny",
|
||||
humidity: `12%`,
|
||||
windSpeed: `35 ${units === "celsius" ? "km/h" : "mph"}`,
|
||||
lastUpdated: new Date().toLocaleString(),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse();
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Collapsible interface for showing/hiding tool details
|
||||
- Visual status indicators with icons and badges
|
||||
- Support for multiple tool execution states (pending, running, completed, error)
|
||||
- Formatted parameter display with JSON syntax highlighting
|
||||
- Result and error handling with appropriate styling
|
||||
- Composable structure for flexible layouts
|
||||
- Accessible keyboard navigation and screen reader support
|
||||
- Consistent styling that matches your design system
|
||||
- Auto-opens completed tools by default for better UX
|
||||
|
||||
## Examples
|
||||
|
||||
### Input Streaming (Pending)
|
||||
|
||||
Shows a tool in its initial state while parameters are being processed.
|
||||
|
||||
<Preview path="tool-input-streaming" />
|
||||
|
||||
### Input Available (Running)
|
||||
|
||||
Shows a tool that's actively executing with its parameters.
|
||||
|
||||
<Preview path="tool-input-available" />
|
||||
|
||||
### Output Available (Completed)
|
||||
|
||||
Shows a completed tool with successful results. Opens by default to show the results. In this instance, the output is a JSON object, so we can use the `CodeBlock` component to display it.
|
||||
|
||||
<Preview path="tool-output-available" />
|
||||
|
||||
### Output Error
|
||||
|
||||
Shows a tool that encountered an error during execution. Opens by default to display the error.
|
||||
|
||||
<Preview path="tool-output-error" />
|
||||
|
||||
## Props
|
||||
|
||||
### `<Tool />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root Collapsible component.',
|
||||
type: 'React.ComponentProps<typeof Collapsible>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ToolHeader />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
type: {
|
||||
description: 'The type/name of the tool.',
|
||||
type: 'ToolUIPart["type"]',
|
||||
},
|
||||
state: {
|
||||
description: 'The current state of the tool (input-streaming, input-available, output-available, or output-error).',
|
||||
type: 'ToolUIPart["state"]',
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the header.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CollapsibleTrigger.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleTrigger>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ToolContent />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the CollapsibleContent.',
|
||||
type: 'React.ComponentProps<typeof CollapsibleContent>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ToolInput />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
input: {
|
||||
description: 'The input parameters passed to the tool, displayed as formatted JSON.',
|
||||
type: 'ToolUIPart["input"]',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<ToolOutput />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
output: {
|
||||
description: 'The output/result of the tool execution.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
errorText: {
|
||||
description: 'An error message if the tool execution failed.',
|
||||
type: 'ToolUIPart["errorText"]',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying div.',
|
||||
type: 'React.ComponentProps<"div">',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
73
docs/components/toolbar.md
Normal file
73
docs/components/toolbar.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Toolbar
|
||||
|
||||
URL: /components/toolbar
|
||||
|
||||
---
|
||||
|
||||
title: Toolbar
|
||||
description: A styled toolbar component for React Flow nodes with flexible positioning and custom actions.
|
||||
path: elements/components/toolbar
|
||||
|
||||
---
|
||||
|
||||
The `Toolbar` component provides a positioned toolbar that attaches to nodes in React Flow canvases. It features modern card styling with backdrop blur and flexbox layout for action buttons and controls.
|
||||
|
||||
<Callout>
|
||||
The Toolbar component is designed to be used with the [Node](/elements/components/node) component. See the [Workflow](/elements/examples/workflow) demo for a full example.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="toolbar" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { Toolbar } from "@/components/ai-elements/toolbar";
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { Toolbar } from "@/components/ai-elements/toolbar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const CustomNode = () => (
|
||||
<Node>
|
||||
<NodeContent>...</NodeContent>
|
||||
<Toolbar>
|
||||
<Button size="sm" variant="ghost">
|
||||
Edit
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost">
|
||||
Delete
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</Node>
|
||||
);
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Attaches to any React Flow node
|
||||
- Bottom positioning by default
|
||||
- Rounded card design with border
|
||||
- Theme-aware background styling
|
||||
- Flexbox layout with gap spacing
|
||||
- Full TypeScript support
|
||||
- Compatible with all React Flow NodeToolbar features
|
||||
|
||||
## Props
|
||||
|
||||
### `<Toolbar />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
className: {
|
||||
description: 'Additional CSS classes to apply to the toolbar.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props from @xyflow/react NodeToolbar component (position, offset, isVisible, etc.).',
|
||||
type: 'ComponentProps<typeof NodeToolbar>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
251
docs/components/web-preview.md
Normal file
251
docs/components/web-preview.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# Web Preview
|
||||
|
||||
URL: /components/web-preview
|
||||
|
||||
---
|
||||
|
||||
title: Web Preview
|
||||
description: A composable component for previewing the result of a generated UI, with support for live examples and code display.
|
||||
path: elements/components/web-preview
|
||||
|
||||
---
|
||||
|
||||
The `WebPreview` component provides a flexible way to showcase the result of a generated UI component, along with its source code. It is designed for documentation and demo purposes, allowing users to interact with live examples and view the underlying implementation.
|
||||
|
||||
<Preview path="web-preview" />
|
||||
|
||||
## Installation
|
||||
|
||||
<ElementsInstaller path="web-preview" />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { WebPreview, WebPreviewNavigation, WebPreviewUrl, WebPreviewBody } from "@/components/ai-elements/web-preview";
|
||||
```
|
||||
|
||||
```tsx
|
||||
<WebPreview defaultUrl="https://ai-sdk.dev" style={{ height: "400px" }}>
|
||||
<WebPreviewNavigation>
|
||||
<WebPreviewUrl src="https://ai-sdk.dev" />
|
||||
</WebPreviewNavigation>
|
||||
<WebPreviewBody src="https://ai-sdk.dev" />
|
||||
</WebPreview>
|
||||
```
|
||||
|
||||
## Usage with AI SDK
|
||||
|
||||
Build a simple v0 clone using the [v0 Platform API](https://v0.dev/docs/api/platform).
|
||||
|
||||
Install the `v0-sdk` package:
|
||||
|
||||
```package-install
|
||||
npm i v0-sdk
|
||||
```
|
||||
|
||||
Add the following component to your frontend:
|
||||
|
||||
```tsx title="app/page.tsx"
|
||||
"use client";
|
||||
|
||||
import { WebPreview, WebPreviewBody, WebPreviewNavigation, WebPreviewUrl } from "@/components/ai-elements/web-preview";
|
||||
import { useState } from "react";
|
||||
import { Input, PromptInputTextarea, PromptInputSubmit } from "@/components/ai-elements/prompt-input";
|
||||
import { Loader } from "../ai-elements/loader";
|
||||
|
||||
const WebPreviewDemo = () => {
|
||||
const [previewUrl, setPreviewUrl] = useState("");
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!prompt.trim()) return;
|
||||
setPrompt("");
|
||||
|
||||
setIsGenerating(true);
|
||||
try {
|
||||
const response = await fetch("/api/v0", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ prompt }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
setPreviewUrl(data.demo || "/");
|
||||
console.log("Generation finished:", data);
|
||||
} catch (error) {
|
||||
console.error("Generation failed:", error);
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-1 mb-4">
|
||||
{isGenerating ? (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<Loader />
|
||||
<p className="mt-4 text-muted-foreground">Generating app, this may take a few seconds...</p>
|
||||
</div>
|
||||
) : previewUrl ? (
|
||||
<WebPreview defaultUrl={previewUrl}>
|
||||
<WebPreviewNavigation>
|
||||
<WebPreviewUrl />
|
||||
</WebPreviewNavigation>
|
||||
<WebPreviewBody src={previewUrl} />
|
||||
</WebPreview>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">Your generated app will appear here</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Input onSubmit={handleSubmit} className="w-full max-w-2xl mx-auto relative">
|
||||
<PromptInputTextarea
|
||||
value={prompt}
|
||||
placeholder="Describe the app you want to build..."
|
||||
onChange={(e) => setPrompt(e.currentTarget.value)}
|
||||
className="pr-12 min-h-[60px]"
|
||||
/>
|
||||
<PromptInputSubmit
|
||||
status={isGenerating ? "streaming" : "ready"}
|
||||
disabled={!prompt.trim()}
|
||||
className="absolute bottom-1 right-1"
|
||||
/>
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebPreviewDemo;
|
||||
```
|
||||
|
||||
Add the following route to your backend:
|
||||
|
||||
```ts title="app/api/v0/route.ts"
|
||||
import { v0 } from "v0-sdk";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { prompt }: { prompt: string } = await req.json();
|
||||
|
||||
const result = await v0.chats.create({
|
||||
system: "You are an expert coder",
|
||||
message: prompt,
|
||||
modelConfiguration: {
|
||||
modelId: "v0-1.5-sm",
|
||||
imageGenerations: false,
|
||||
thinking: false,
|
||||
},
|
||||
});
|
||||
|
||||
return Response.json({
|
||||
demo: result.demo,
|
||||
webUrl: result.webUrl,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Live preview of UI components
|
||||
- Composable architecture with dedicated sub-components
|
||||
- Responsive design modes (Desktop, Tablet, Mobile)
|
||||
- Navigation controls with back/forward functionality
|
||||
- URL input and example selector
|
||||
- Full screen mode support
|
||||
- Console logging with timestamps
|
||||
- Context-based state management
|
||||
- Consistent styling with the design system
|
||||
- Easy integration into documentation pages
|
||||
|
||||
## Props
|
||||
|
||||
### `<WebPreview />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
defaultUrl: {
|
||||
description: 'The initial URL to load in the preview.',
|
||||
type: 'string',
|
||||
default: '""',
|
||||
},
|
||||
onUrlChange: {
|
||||
description: 'Callback fired when the URL changes.',
|
||||
type: '(url: string) => void',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<WebPreviewNavigation />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the navigation container.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<WebPreviewNavigationButton />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
tooltip: {
|
||||
description: 'Tooltip text to display on hover.',
|
||||
type: 'string',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
|
||||
type: 'React.ComponentProps<typeof Button>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<WebPreviewUrl />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying shadcn/ui Input component.',
|
||||
type: 'React.ComponentProps<typeof Input>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<WebPreviewBody />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
loading: {
|
||||
description: 'Optional loading indicator to display over the preview.',
|
||||
type: 'React.ReactNode',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the underlying iframe.',
|
||||
type: 'React.IframeHTMLAttributes<HTMLIFrameElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### `<WebPreviewConsole />`
|
||||
|
||||
<TypeTable
|
||||
type={{
|
||||
logs: {
|
||||
description: 'Console log entries to display in the console panel.',
|
||||
type: 'Array<{ level: "log" | "warn" | "error"; message: string; timestamp: Date }>',
|
||||
},
|
||||
'...props': {
|
||||
description: 'Any other props are spread to the root div.',
|
||||
type: 'React.HTMLAttributes<HTMLDivElement>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
Reference in New Issue
Block a user