The row under an assistant message: copy, regenerate, thumbs up or down with a reason, and a switcher between regenerated answers.
Set your access key from API keys as NEXTUI_TOKEN in .env.local and add the @nextui namespace below to components.json once. Then every item installs by name. Each install counts as a copy on a free account.
$pnpm dlx shadcn@latest add @nextui/message-actions
1{2"registries": {3"@nextui": {4"url": "https://nextui.dev/r/{name}.json",5"headers": { "Authorization": "Bearer ${NEXTUI_TOKEN}" }6}7}8}
1"use client";23import { useChat } from "@ai-sdk/react";4import { MessageActions } from "@/components/nextui/message-actions";56export function Messages() {7const { messages, regenerate, status } = useChat();89return messages.map((message, i) => {10const text = message.parts.map((p) => (p.type === "text" ? p.text : "")).join("");11if (message.role !== "assistant") return <p key={message.id}>{text}</p>;12const last = i === messages.length - 1;1314return (15<MessageActions16key={message.id}17text={text}18// AI SDK regenerate() replaces the last assistant message.19onRegenerate={last ? () => regenerate() : undefined}20regenerating={last && status === "streaming"}21onFeedback={(feedback, reason) =>22fetch("/api/feedback", { method: "POST", body: JSON.stringify({ id: message.id, feedback, reason }) })23}24>25<p>{text}</p>26</MessageActions>27);28});29}
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | (version: number) => ReactNode | – | The message. Hovering or focusing it reveals the actions. A function gets the version shown |
| text | string | – | What Copy puts on the clipboard |
| onCopy | () => void | – | Called after copying |
| onRegenerate | () => void | – | Shows Regenerate. Usually only on the last assistant message |
| regenerating | boolean | false | Spins the icon and dims the message while a new answer streams |
| feedback | "up" | "down" | null | – | Controlled rating |
| onFeedback | (feedback, reason?) => void | – | Called on each thumb, and again with the reason picked after thumbs down |
| reasons | string[] | ["Not accurate", "Not helpful", "Too long", "Didn't follow instructions"] | Reasons offered after thumbs down. Pass [] to skip |
| versionCount | number | – | Answers kept for this turn. Above 1 shows a ‹ 2/3 › switcher |
| version | number | – | Controlled version, from 1 |
| onVersionChange | (version: number) => void | – | Called by the switcher |
| alwaysVisible | boolean | false | Keep the row visible instead of revealing it on hover and focus |
| autoPlay | boolean | false | Loop a scripted copy, thumbs down with a reason and regenerate, for demos |
| className | string | – | Extra classes on the wrapper |
Sign in to see the code
Preview it freely. A free account copies code and prompts a few times a day.