Files
temetro/.agents/skills/ai-elements/references/message.md
T
Khalid Abdi 929bec8f31 feat: AI-added records save with placeholders + "Added by AI" provenance
Stop blocking AI imports/proposals on missing non-critical fields. Records
the chat agent drafts now save with safe placeholders, auto-generated file
numbers, and a source="ai" marker that surfaces an "Added by AI" badge so a
clinician can review/edit them later.

Backend:
- add `source` (manual|ai) column to patients/appointments/prescriptions
  (migration 0014) + canonical types, services, validation schemas
- relax patient/appointment validation: empty file number allowed, demographic
  + type/provider/initials fall back to placeholders (initials derived from name)
- patients.generateFileNumber() auto-assigns an MRN when one is missing
- proposeAppointment accepts a name when no file number resolves; AI commits +
  /api/ai/import stamp source="ai"

Frontend:
- `source` on Appointment/Patient/Prescription types; AI commits send source="ai"
- reusable <AiBadge> shown on the Patients table/detail and prescriptions list

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:27:17 +03:00

9.4 KiB

Message

A comprehensive suite of components for displaying chat messages, including message rendering, branching, actions, and markdown responses.

The Message component suite provides a complete set of tools for building chat interfaces. It includes components for displaying messages from users and AI assistants, managing multiple response branches, adding action buttons, and rendering markdown content.

See scripts/message.tsx for this example.

Installation

npx ai-elements@latest add message

Features

  • Displays messages from both user and AI assistant with distinct styling and automatic alignment
  • Minimalist flat design with user messages in secondary background and assistant messages full-width
  • Response branching with navigation controls to switch between multiple AI response versions
  • Markdown rendering with GFM support (tables, task lists, strikethrough), math equations, and smart streaming
  • Action buttons for common operations (retry, like, dislike, copy, share) with tooltips and state management
  • File attachments display with support for images and generic files with preview and remove functionality
  • Code blocks with syntax highlighting and copy-to-clipboard functionality
  • Keyboard accessible with proper ARIA labels
  • Responsive design that adapts to different screen sizes
  • Seamless light/dark theme integration

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:

"use client";

import { useState } from "react";
import {
  MessageActions,
  MessageAction,
} from "@/components/ai-elements/message";
import { Message, MessageContent } from "@/components/ai-elements/message";
import {
  Conversation,
  ConversationContent,
  ConversationScrollButton,
} from "@/components/ai-elements/conversation";
import {
  PromptInput,
  type PromptInputMessage,
  PromptInputTextarea,
  PromptInputSubmit,
} from "@/components/ai-elements/prompt-input";
import { MessageResponse } from "@/components/ai-elements/message";
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 = (message: PromptInputMessage) => {
    if (message.text.trim()) {
      sendMessage({ text: message.text });
      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>
                              <MessageResponse>{part.text}</MessageResponse>
                            </MessageContent>
                          </Message>
                          {message.role === "assistant" && isLastMessage && (
                            <MessageActions>
                              <MessageAction
                                onClick={() => regenerate()}
                                label="Retry"
                              >
                                <RefreshCcwIcon className="size-3" />
                              </MessageAction>
                              <MessageAction
                                onClick={() =>
                                  navigator.clipboard.writeText(part.text)
                                }
                                label="Copy"
                              >
                                <CopyIcon className="size-3" />
                              </MessageAction>
                            </MessageActions>
                          )}
                        </Fragment>
                      );
                    default:
                      return null;
                  }
                })}
              </Fragment>
            ))}
          </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 ActionsDemo;

Props

<Message />

Prop Type Default Description
from UIMessage[ - The role of the message sender (
...props React.HTMLAttributes<HTMLDivElement> - Any other props are spread to the root div.

<MessageContent />

Prop Type Default Description
...props React.HTMLAttributes<HTMLDivElement> - Any other props are spread to the content div.

<MessageResponse />

Prop Type Default Description
children string - The markdown content to render.
parseIncompleteMarkdown boolean true Whether to parse and fix incomplete markdown syntax (e.g., unclosed code blocks or lists).
className string - CSS class names to apply to the wrapper div element.
components object - Custom React components to use for rendering markdown elements (e.g., custom heading, paragraph, code block components).
allowedImagePrefixes string[] [ Array of allowed URL prefixes for images. Use [
allowedLinkPrefixes string[] [ Array of allowed URL prefixes for links. Use [
defaultOrigin string - Default origin to use for relative URLs in links and images.
rehypePlugins array [rehypeKatex] Array of rehype plugins to use for processing HTML. Includes KaTeX for math rendering by default.
remarkPlugins array [remarkGfm, remarkMath] Array of remark plugins to use for processing markdown. Includes GitHub Flavored Markdown and math support by default.
...props React.HTMLAttributes<HTMLDivElement> - Any other props are spread to the root div.

<MessageActions />

Prop Type Default Description
...props React.HTMLAttributes<HTMLDivElement> - HTML attributes to spread to the root div.

<MessageAction />

Prop Type Default Description
tooltip string - Optional tooltip text shown on hover.
label string - Accessible label for screen readers. Also used as fallback if tooltip is not provided.
...props React.ComponentProps<typeof Button> - Any other props are spread to the underlying shadcn/ui Button component.

<MessageBranch />

Prop Type Default Description
defaultBranch number 0 The index of the branch to show by default.
onBranchChange (branchIndex: number) => void - Callback fired when the branch changes.
...props React.HTMLAttributes<HTMLDivElement> - Any other props are spread to the root div.

<MessageBranchContent />

Prop Type Default Description
...props React.HTMLAttributes<HTMLDivElement> - Any other props are spread to the root div.

<MessageBranchSelector />

Prop Type Default Description
...props React.ComponentProps<typeof ButtonGroup> - Any other props are spread to the underlying ButtonGroup component.

<MessageBranchPrevious />

Prop Type Default Description
...props React.ComponentProps<typeof Button> - Any other props are spread to the underlying shadcn/ui Button component.

<MessageBranchNext />

Prop Type Default Description
...props React.ComponentProps<typeof Button> - Any other props are spread to the underlying shadcn/ui Button component.

<MessageBranchPage />

Prop Type Default Description
...props React.HTMLAttributes<HTMLSpanElement> - Any other props are spread to the underlying span element.

<MessageToolbar />

A container for placing actions and branch selectors below a message. Lays out children in a horizontal row with space-between alignment.

Prop Type Default Description
...props React.ComponentProps< - Any other props are spread to the root div.