Skip to main content

Installation

1

Install the block

npx shadcn@latest add @blocks/ai-05
This installs required dependencies (@tabler/icons-react, ai) and shadcn/ui components (badge, button).
2

Install AI Elements

npx ai-elements@latest add conversation message prompt-input
This component requires AI Elements for conversation and message components.

Usage

import Ai05 from "@/components/ai-05";

export default function Page() {
  return <Ai05 />;
}

Features

A complete chat interface built with AI Elements, featuring message history, streaming responses, and a polished UI:

AI Elements Integration

Production-Ready Components - Built with AI Elements library for robust conversation management, message rendering, and prompt input handling.
The component uses three main AI Elements modules:
import {
  Conversation,
  ConversationContent,
  ConversationScrollButton,
} from "@/components/ai-elements/conversation";

import {
  Message,
  MessageContent,
  MessageResponse,
} from "@/components/ai-elements/message";

import {
  PromptInput,
  PromptInputButton,
  PromptInputFooter,
  PromptInputSubmit,
  PromptInputTextarea,
  PromptInputTools,
} from "@/components/ai-elements/prompt-input";

Message System

Messages use a typed interface for type safety:
interface DemoMessage {
  id: string;
  role: "user" | "assistant";
  content: string;
}

Chat Status Management

The component tracks chat status using types from the ai package:
const [status, setStatus] = useState<ChatStatus>("ready");
Status transitions:
  • ready - Ready to receive input
  • submitted - Message submitted, awaiting response

Initial Messages

Pre-populated conversation to demonstrate capabilities:
const INITIAL_MESSAGES: DemoMessage[] = [
  {
    id: "intro",
    role: "assistant",
    content: "**Welcome back.** I can help you explore this chat block.\n\n- Draft UI copy\n- Summarize docs\n- Turn notes into tasks\n\nAsk me anything and I will respond with a demo reply.",
  },
  // ... more messages
];

Response Rotation

Demo responses cycle through predefined replies:
const RESPONSES = [
  "Here is a quick outline you can reuse:\n\n1. Swap the mock response with a real API call.\n2. Stream tokens into `MessageResponse`.\n3. Keep the layout exactly as-is for a consistent UI.",
  "If you want multi-model support, add a small model selector next to the status badge and pass the selection to your backend.",
  "You can also inject tools like file upload or voice input by adding buttons to the prompt footer.",
];

const pickResponse = (index: number) => RESPONSES[index % RESPONSES.length];

Toolbar Actions

Quick access buttons in the prompt footer:
  • Attach - Upload files
  • Quick Prompt - Access saved prompts
  • New Chat - Start fresh conversation

Component Props

This component doesn’t accept external props. It demonstrates a self-contained chat interface.

Customization

Connecting to Real API

Replace the demo response logic with an actual API call:
const handleSend = async (text: string) => {
  const trimmed = text.trim();
  if (!trimmed) return;

  const newMessage: DemoMessage = {
    id: `user-${Date.now()}`,
    role: "user",
    content: trimmed,
  };

  setMessages((prev) => [...prev, newMessage]);
  setInputValue("");
  setStatus("submitted");

  try {
    // Replace with your API call
    const response = await fetch("/api/chat", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ message: trimmed }),
    });

    const data = await response.json();
    
    const assistantMessage: DemoMessage = {
      id: `assistant-${Date.now()}`,
      role: "assistant",
      content: data.response,
    };

    setMessages((prev) => [...prev, assistantMessage]);
  } catch (error) {
    console.error("Failed to get response:", error);
  } finally {
    setStatus("ready");
  }
};

Styling the Header

Customize the chat header:
<header className="flex items-center justify-between gap-4 border-b border-border/80 px-4 py-3">
  <div className="flex items-center gap-3">
    <div className="space-y-1">
      <div className="flex items-center gap-2 text-balance text-sm font-semibold">
        Your Chat Title
      </div>
      <div className="flex items-center gap-2 text-pretty text-xs text-muted-foreground">
        <span className="inline-flex items-center gap-1">
          <span className="size-1.5 rounded-full bg-emerald-500" />
          Custom status
        </span>
      </div>
    </div>
  </div>
</header>

Adding Tools to Prompt Input

Extend the prompt footer with custom tools:
<PromptInputTools>
  <PromptInputButton aria-label="Attach">
    <IconPaperclip className="size-4" />
  </PromptInputButton>
  {/* Add custom tools */}
  <PromptInputButton aria-label="Custom Tool">
    <IconCustom className="size-4" />
  </PromptInputButton>
</PromptInputTools>

Message Rendering

The component uses MessageResponse for assistant messages to support markdown:
{message.role === "assistant" ? (
  <MessageResponse>{message.content}</MessageResponse>
) : (
  <p className="whitespace-pre-wrap text-pretty">
    {message.content}
  </p>
)}

Implementation Details

Message State Management

const [messages, setMessages] = useState<DemoMessage[]>(INITIAL_MESSAGES);
const [inputValue, setInputValue] = useState("");
const [status, setStatus] = useState<ChatStatus>("ready");

Timeout Cleanup

Proper cleanup of timeout references:
const replyTimeoutRef = useRef<number | null>(null);

useEffect(() => {
  return () => {
    if (replyTimeoutRef.current) {
      window.clearTimeout(replyTimeoutRef.current);
    }
  };
}, []);

Conversation Layout

The conversation uses a fixed height container with scrolling:
<div className="mx-auto flex h-96 w-full max-w-2xl flex-col overflow-hidden rounded-2xl border border-border bg-card shadow-lg sm:w-3/5">
  <Conversation className="bg-muted/30">
    <ConversationContent className="gap-6 pl-1">
      {/* Messages */}
    </ConversationContent>
    <ConversationScrollButton />
  </Conversation>
</div>

Build docs developers (and LLMs) love