Initial commit
This commit is contained in:
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>',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
Reference in New Issue
Block a user