Regenerate One turn holding three alternates behind a pager, with a directed retry that asks for a different model or a shorter answer. Continuing from a different alternate produces a different conversation, and the thread the reader is on is whichever one is selected. npx shadcn@latest add button Tokens this needs: --card, --card-foreground, --background, --accent, --accent-foreground, --muted-foreground, --border, --input, --chart-1 ──────────────────────────────────────────────────────────────────────── // Regenerate.tsx ──────────────────────────────────────────────────────────────────────── import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; /** A retry with a direction. A bare reroll gives the model nothing it did not * already have; "shorter" or "another model" turns the dice roll into a * steer. */ export type Retry = { shorter?: boolean; otherModel?: boolean }; type Props = { /** Every answer this turn has produced, oldest first. Kept, never replaced: * the viewer retried because the first one was partly right. */ alternates: string[]; onRetry: (direction: Retry) => void; /** Which alternate the conversation continues from. Once a turn holds * several answers the transcript is a tree, and the selection is the * branch. */ onSelect?: (index: number) => void; /** Which alternate shows first. Defaults to the newest. */ defaultIndex?: number; }; export function Regenerate({ alternates, onRetry, onSelect, defaultIndex }: Props) { const n = alternates.length; const [i, setI] = useState(defaultIndex ?? n - 1); const go = (next: number) => { setI(next); onSelect?.(next); }; // A retry lands on the new answer. The viewer asked for it; it is what they // want to see, and the earlier ones are one keypress back. const seen = useRef(n); useEffect(() => { if (n > seen.current) go(n - 1); seen.current = n; }, [n]); return (

{alternates[i]}

{n > 1 && (
{i + 1} of {n}
)}
{/* The tree is kept and hidden; the selection defines the visible thread. This line is what makes the branch point findable. */} {n > 1 && (

the thread below continues from answer {i + 1}

)}
); } ──────────────────────────────────────────────────────────────────────── // demo.tsx ──────────────────────────────────────────────────────────────────────── import { useState } from "react"; import { Regenerate, type Retry } from "./Regenerate"; const ANSWERS = [ "The lease is for five years from 1 March 2024. Rent is £42,000 a year and is reviewed at the third anniversary, upward only, to the open market figure or the passing rent, whichever is higher. The tenant may end the lease at the end of the second year by giving six months' written notice, on condition that all rent has been paid and the premises are returned with vacant possession. A deposit of three months' rent is held in a separate account and returned within thirty days of the end of the term, less any agreed deductions for dilapidations.", "Five-year term from 1 March 2024 at £42,000 a year, with an upward-only review at the start of year three. The tenant can break at the end of year two on six months' notice, provided the rent is paid up to date and the premises are handed back empty. The deposit is three months' rent, returned within 30 days of the end of the term.", "Five years from March 2024, £42,000 a year, upward-only review at year three. Tenant break at year two on six months' notice if rent is paid and the premises are empty. Deposit of three months' rent, back within 30 days.", ]; /** Three answers to one question. Retry appends a fourth in the asked-for direction. */ export default function Demo() { const [alternates, setAlternates] = useState(ANSWERS); const retry = (d: Retry) => { const next = d.shorter ? "Five years, £42,000 a year, reviewed upward at year three. Break at year two on six months' notice. Three months' deposit." : "The lease runs 1 March 2024 to 28 February 2029. Annual rent £42,000, upward-only review on 1 March 2027. A tenant-only break on 28 February 2026 needs six months' notice and rent paid up to date. Deposit is three months' rent, refunded within 30 days."; setAlternates([...alternates, next]); }; return (
); }