Response actions The control row ranked by what people actually do. Copy comes first because it is used most, then the thumbs pair, then retry, with the rest behind an overflow. A thumbs-down that collects a category is worth more than one that collects a bit. npx shadcn@latest add button dropdown-menu npm i lucide-react Tokens this needs: --card, --card-foreground, --muted, --muted-foreground, --border, --chart-1 ──────────────────────────────────────────────────────────────────────── // ResponseActions.tsx ──────────────────────────────────────────────────────────────────────── import { Copy, RotateCcw, ThumbsDown, ThumbsUp, MoreHorizontal } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; export type Rating = "up" | "down" | null; /** A thumbs-down is one bit. The follow-up turns it into a category, and the * set is closed so the numbers add up across turns. */ export type Reason = "wrong" | "too long" | "off topic"; export const REASONS: Reason[] = ["wrong", "too long", "off topic"]; export type OverflowItem = { label: string; onSelect: () => void }; type Props = { /** What copy takes. The caller decides markdown, rendered text or raw. */ copyText: string; rating: Rating; onRate: (rating: Rating) => void; onReason: (reason: Reason) => void; onRetry: () => void; /** Everything past the top four. Read aloud, share, report: they exist, * they are one click further away. */ overflow: OverflowItem[]; /** Where a rating goes once clicked. Required, because a control that * quietly feeds a pipeline is asking for trust it has not explained. */ feedbackUse: string; /** The newest turn keeps its row; older turns reveal it on hover or focus, * so the keyboard reaches it the same way the pointer does. */ pinned?: boolean; onCopy?: () => void; }; export function ResponseActions({ copyText, rating, onRate, onReason, onRetry, overflow, feedbackUse, pinned = false, onCopy, }: Props) { const copy = () => { navigator.clipboard?.writeText(copyText); onCopy?.(); }; const reveal = pinned ? "" : "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100"; return (
What went wrong?
{feedbackUse}
{ANSWER}