Aaditya

Sampling Explainer

AI chats

About

Shows what temperature and top P do to a model's next token: real softmax over the logits you pass, a top P cutoff line, cut tokens struck out, and sample draws to see how often each one comes up.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Sampling Explainer to your project and fits it to your theme.

Code

How Sampling Explainer is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client"

import * as React from "react"
import { SamplingExplainer, SamplingSettings } from "@/components/ui/sampling-explainer"

const reducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches

/**
 * Settings along a slow loop: temperature swings from focused to wild while top P closes
 * and opens, so the next token's chances keep reshaping. Drag either slider and the tour waits.
 */
function tour(t: number): SamplingSettings {
  const phase = (t / 4200) * Math.PI * 2
  const temperature = 1.3 - 0.75 * Math.cos(phase)
  const topP = 0.82 + 0.17 * Math.cos(phase * 0.5 + 0.6)
  return {
    temperature: Math.round(temperature * 100) / 100,
    topP: Math.round(Math.min(1, Math.max(0.3, topP)) * 100) / 100,
  }
}

const FIT_CSS = "html,body{overflow:hidden}[data-fit-small] [data-fit-hide]{display:none!important}"

/**
 * 21st shows a demo in a small frame: about 674 by 421 on a component page, 358 by 224 on a
 * phone and 245 by 139 on a profile card. A demo taller than its frame gets scrollbars, which
 * render white on dark pages. FitFrame fills the frame exactly instead.
 *
 * - "scale": the content shrinks until it fits, centered. For visuals such as orbs.
 * - "crop": the content stays at full size from the top and fades out at the bottom. For text
 *   that streams in, where a changing scale would make the page breathe.
 *
 * `minWidth` lays out a wide demo (a table, a two column panel) at that width and scales it down
 * to the frame, instead of letting it collapse to a narrow layout. The frame centers it, so it
 * shrinks evenly on both sides.
 *
 * Anything marked `data-fit-hide` (state buttons, the chatcn.dev line) drops out in frames
 * shorter than `hideBelow`, so a profile card shows the visual alone.
 */
function FitFrame({
  children,
  mode = "scale",
  hideBelow = 360,
  minWidth,
}: {
  children: React.ReactNode
  mode?: "scale" | "crop"
  hideBelow?: number
  minWidth?: string
}) {
  const content = React.useRef<HTMLDivElement>(null)
  const [cropped, setCropped] = React.useState(false)

  React.useLayoutEffect(() => {
    const el = content.current
    if (!el) return
    const measure = () => {
      const vw = window.innerWidth
      const vh = window.innerHeight
      el.toggleAttribute("data-fit-small", vh < hideBelow)
      // Layout sizes ignore the transform, so measuring never feeds back.
      const w = Math.max(el.offsetWidth, el.scrollWidth)
      const h = Math.max(el.offsetHeight, el.scrollHeight)
      const scale = mode === "scale" ? Math.min(1, vw / w, vh / h) : 1
      el.style.transform = scale < 1 ? `scale(${scale})` : ""
      setCropped(mode === "crop" && h > vh + 1)
    }
    measure()
    const observer = new ResizeObserver(measure)
    observer.observe(el)
    // Content that changes inside a fixed width box (a headline that cycles words) never resizes
    // the box itself, so changes below it measure again on the next frame.
    let frame = 0
    const mutations = new MutationObserver(() => {
      cancelAnimationFrame(frame)
      frame = requestAnimationFrame(measure)
    })
    mutations.observe(el, { subtree: true, childList: true, characterData: true })
    window.addEventListener("resize", measure)
    return () => {
      observer.disconnect()
      mutations.disconnect()
      cancelAnimationFrame(frame)
      window.removeEventListener("resize", measure)
    }
  }, [mode, hideBelow])

  const fade = "linear-gradient(to bottom, #000 calc(100% - 40px), transparent)"
  return (
    <div
      className="flex w-full justify-center overflow-hidden"
      style={{
        height: "100dvh",
        alignItems: mode === "crop" ? "flex-start" : "center",
        maskImage: cropped ? fade : undefined,
        WebkitMaskImage: cropped ? fade : undefined,
      }}
    >
      <style>{FIT_CSS}</style>
      <div
        ref={content}
        className="w-full shrink-0"
        style={{ transformOrigin: mode === "crop" ? "top center" : "center", minWidth }}
      >
        {children}
      </div>
    </div>
  )
}

const CANDIDATES = [
  { token: " Canberra", logit: 8.2 },
  { token: " Sydney", logit: 6.1 },
  { token: " Melbourne", logit: 5.3 },
  { token: " located", logit: 4.4 },
  { token: " a", logit: 3.9 },
  { token: " the", logit: 3.6 },
  { token: " not", logit: 2.7 },
  { token: " Perth", logit: 2.1 },
]

/** How long a choice holds a tour before it carries on. */
const RESUME_MS = 5000

/** The line under each 21st demo that points back to the kit. */
function MoreOn({ slug, children }: { slug: string; children: React.ReactNode }) {
  return (
    <p data-fit-hide className="text-[0.75rem]/[1rem] text-(color:--chatcn-fg-faint) text-center text-balance">
      {children}{" "}
      <a
        href={`https://chatcn.dev/docs/components/${slug}`}
        target="_blank"
        rel="noopener"
        className="text-(color:--chatcn-fg-subtle) hover:text-(color:--chatcn-fg) underline underline-offset-2"
      >
        chatcn.dev
      </a>
    </p>
  )
}

export default function SamplingExplainerPreview() {
  const [value, setValue] = React.useState<SamplingSettings>({ temperature: 1.2, topP: 0.9 })
  // Dragging a slider holds the tour for a few seconds.
  const heldUntil = React.useRef(0)
  React.useEffect(() => {
    if (reducedMotion()) return
    const began = performance.now()
    const id = window.setInterval(() => {
      if (performance.now() > heldUntil.current) setValue(tour(performance.now() - began + 1050))
    }, 50)
    return () => window.clearInterval(id)
  }, [])
  return (
    <FitFrame minWidth="46rem">
      <div className="flex w-full flex-col items-center justify-center gap-6 p-6">
        <SamplingExplainer
          className="max-w-4xl"
          context="The capital of Australia is"
          candidates={CANDIDATES}
          value={value}
          onValueChange={(next) => {
            heldUntil.current = performance.now() + RESUME_MS
            setValue(next)
          }}
        />
        <MoreOn slug="sampling-explainer">
          Temperature and top P on real logits, with draws you can sample. On
        </MoreOn>
      </div>
    </FitFrame>
  )
}

Dependencies

More by Aaditya

View all
  • Pixel Orb

    Pixel Orb

    9
  • Dither Orb

    Dither Orb

    9
  • Dot Matrix Loader

    Dot Matrix Loader

    8
  • Halftone Loader

    Halftone Loader

    6
  • ASCII 3D

    ASCII 3D

    5
  • Wireframe Globe

    Wireframe Globe

    5
  • Audio Visualizer

    Audio Visualizer

    5
  • LED Matrix

    LED Matrix

    4

More in AI chats

View all
  • AI Actions

    AI Actions

    336
  • Aquarium Prompt

    Aquarium Prompt

    24
  • Attachments

    Attachments

    33
  • Message Thread

    Message Thread

    55
  • Agent Chat

    Agent Chat

    18
  • Color Picker

    Color Picker

    159
  • Chat Bubble

    Chat Bubble

    194
  • Commit

    Commit

    27

Explore more

Part of chatcn — browse the full library on 21st.dev.