affectUI › Components › Agents

Rate Limit Planner

Plan when your agents run so they stay under the API's rate limit – the day's load as a line chart, one row per agent run to drag; Spread out lowers the peak, the overflow is hatched in red.

Part of affectUI Pro. Install: npx shadcn@latest add https://www.affectui.com/r/rate-limit-planner.json · Import from @/components/ui/rate-limit-planner

When to use

  • Scheduling agent runs, cron jobs or batch work against one API rate limit (tokens per minute)
  • Showing why a run hit 429s – which runs overlap and when the day goes over
  • An ops or settings page where people move their own jobs and see the peak change as they drag

When not to use

  • Live monitoring of every request – use a metrics chart; this plans a day
  • Many separate limits at once (per model, per key) – one planner per limit
  • More than about ten runs a day – the rows get long; group runs per agent first

Usage

import { useState } from "react";
import { RateLimitPlanner, type PlannedRun } from "@/components/ui/rate-limit-planner";

const runs: PlannedRun[] = [
  { id: "triage", name: "Inbox triage", at: 8 * 60, peak: 46_000, rise: 45, halfLife: 70 },
  { id: "research", name: "Lead research", at: 11 * 60, peak: 82_000, rise: 60, halfLife: 90 },
  { id: "report", name: "Report writer", at: 15 * 60 + 30, peak: 74_000, rise: 55, halfLife: 100 },
];

export function Schedule() {
  const [plan, setPlan] = useState(runs);
  return (
    <RateLimitPlanner
      runs={plan}
      onRunsChange={setPlan}
      limit={120_000}
      checkpoint={{ at: 23 * 60, label: "Nightly batch" }}
    />
  );
}

Props

NameTypeDefaultDescription
runsPlannedRun[]The runs, controlled – each with id, name, start (minutes after midnight) and peak tokens per minute; pair with onRunsChange
defaultRunsPlannedRun[]Starting runs when uncontrolled
onRunsChange(runs: PlannedRun[]) => voidCalled on every drag, Spread out, edit, add and remove
limitnumberThe API's limit in tokens per minute
buffernumber0.8Share of the limit to stay under – above it the badge says Close to limit
startHournumber6Hour the 24-hour window starts at
checkpoint{ at: number; label: string }A fixed time to check against, e.g. a nightly batch – what's still running then is read out
nownumberThe time now, in minutes after midnight – runs that already started stay put, Spread out and Add run only use the time after it
actualnumber[]Measured tokens per minute from the window's start up to now – drawn solid, the plan ahead dashed, and compared in the footer
today"weekdays" | "weekend""weekdays"Runs with days set to the other kind are left out
selectedstringId of the run shown in its color in the chart – controlled; pair with onSelectedChange
onSelectedChange(id: string) => voidCalled when a row or bar is picked
titlestring"Rate limit"Heading of the card
classNamestringExtra class on the card
PlannedRun.risenumber30Minutes until a run reaches its peak
PlannedRun.halfLifenumber45Minutes until its load has halved again – the tail
PlannedRun.days"daily" | "weekdays" | "weekend""daily"Which days it runs on
PlannedRun.colorstringAny CSS color – by default each run takes the next color of the design system's palette

Keyboard

  • Tab: Moves through Spread out, each run's bar, its remove and edit buttons, Add run and the footer's Move to
  • ← / →: On a bar: moves the run 15 minutes earlier or later
  • Shift / ← / →: On a bar: moves it an hour (Page Up / Page Down too)
  • Home / End: On a bar: the earliest start (now) or the latest that still fits the day
  • Enter: On a bar or ⋯: opens the run's settings
  • Delete: On a bar: removes the run – focus moves on to the next one
  • Esc: Closes the settings and returns to ⋯; tabbing out closes them too

Accessibility

  • Every bar is a slider with the run's name, start and peak as its value text; runs that already started are marked as disabled
  • The footer sentence is a polite live region and describes each bar – screen readers hear what a move did
  • The settings window is a dialog; focus lands in the name field and returns to ⋯ when it closes
  • The chart is decoration – everything in it is also in the badge, the rows and the footer

Responsive

  • Fills its container; below 560px the chart and bars take the full width and each run's name moves onto its own line above its bar
  • On touch screens the picked row shows its remove and settings buttons – there's no hover to reveal them
  • Broken numbers from an API (NaN, negative, a start outside the day) fall back to safe values instead of breaking the chart
  • Touch works: drag a bar with a finger; the page still scrolls when you swipe elsewhere
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"
  • Every color, the font, radius and shadows come from the design system tokens (--aui-*), so it follows your theme

Requirements

  • React 19

Related

  • Agent Lanes – Where every agent is right now – a live timeline with a lane per agent, tasks growing into “now”, hand-offs drawn between agents and approvals right on the block
  • Scheduled Runs – Agent tasks sorted into a week of sessions – each session has a usage limit, small tasks fill the gaps, big ones are split across sessions, and the stack flies in one card at a time
  • Cost Waterfall – Where the money and tokens of an agent run go – every LLM call, tool call and sub-agent as a step in a waterfall, with model, tokens, cost and time, the most expensive step on top and a switch between cost and tokens
  • Usage Meter – Plan usage and AI credits – a stacked credits meter by category, daily sparkline, limits and a button that speaks up near the limit