Files
gh-nathanonn-claude-skills-…/docs/components/confirmation.md
2025-11-30 08:41:51 +08:00

11 KiB

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.

Installation

Usage

import {
    Confirmation,
    ConfirmationContent,
    ConfirmationRequest,
    ConfirmationAccepted,
    ConfirmationRejected,
    ConfirmationActions,
    ConfirmationAction,
} from "@/components/ai-elements/confirmation";
<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:

"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:

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.

Approved State

Shows the accepted status when user approves and state is approval-responded or output-available.

Rejected State

Shows the rejected status when user rejects and state is output-denied.

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', }, }} />

<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', }, }} />

<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', }, }} />