Elements

22 / 122 · Messages

Regenerate with

Fork the same turn to a different model instead of rolling the same dice.

Installation

npx shadcn@latest add "@assistant-ui/elements-regenerate-menu"

Usage

import { RegenerateMenu } from "@/components/elements/regenerate-menu";

<RegenerateMenu
  options={options}
  open={open}
  currentId="sonnet"
  onOpenChange={setOpen}
  onPick={regenerate}
/>

Props

options*RegenerateOption[]What the turn can be re-run with.
open*booleanWhether the menu is showing.
currentId*stringWhich option produced the answer on screen; it is labelled current instead of showing its detail.
onOpenChange(open: boolean) => voidCalled when the trigger is toggled.
onPick(id: string) => voidCalled with the chosen option.
classNamestringExtra classes merged onto the root.

Source

regenerate-menu.tsx
"use client";

import { RefreshCwIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { floating, ghostButton, mono } from "./surfaces";

export interface RegenerateOption {
  id: string;
  label: string;
  detail: string;
}

export function RegenerateMenu({
  options,
  open,
  currentId,
  onOpenChange,
  onPick,
  className,
}: {
  options: readonly RegenerateOption[];
  open: boolean;
  currentId: string;
  onOpenChange?: (open: boolean) => void;
  onPick?: (id: string) => void;
  className?: string;
}) {
  return (
    <div className={cn("flex w-full max-w-sm flex-col gap-2", className)}>
      <button
        type="button"
        aria-expanded={open}
        aria-label="Regenerate with a different model"
        onClick={() => onOpenChange?.(!open)}
        className={cn(
          ghostButton,
          "size-7 self-start",
          open && "bg-foreground/[0.06] text-foreground/90",
        )}
      >
        <RefreshCwIcon className="size-3.5" />
      </button>

      {open && (
        <div
          className={cn(
            floating,
            "fade-in zoom-in-95 slide-in-from-top-1 animate-in flex flex-col gap-0.5 rounded-2xl p-1.5 duration-200",
          )}
        >
          {options.map((option) => (
            <button
              key={option.id}
              type="button"
              onClick={() => onPick?.(option.id)}
              className="hover:bg-foreground/[0.05] flex items-baseline gap-2 rounded-xl px-2.5 py-1.5 text-start transition-colors"
            >
              <span className="min-w-0 flex-1 truncate text-[13px]">
                {option.label}
              </span>
              <span className={cn(mono, "text-foreground/30 shrink-0")}>
                {option.id === currentId ? "current" : option.detail}
              </span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}