Agent 14: batch run
A batch run in one view: tiles for everything being processed (done, running or failed, with Retry), and the queue of what runs next, where waiting work can be reordered, paused or cancelled.
The normal view. Pick a state to see what the block shows while it loads, is empty or fails.
// Ballmac UI: Agent 14. https://ui.ballmac.com/blocks/agent-14
"use client"
import * as React from "react"
import { AgentQueue, type QueuePriority, type QueueTask } from "@/components/ballmac/agent-queue"
import { FanOutGrid, type FanItem } from "@/components/ballmac/fan-out-grid"
import { Skeleton } from "@/components/ballmac/skeleton"
import { frameProps, StateView, type StateProps } from "@/components/ballmac/state-view"
import { cn } from "@/lib/utils"
type Agent14Props = Omit<React.ComponentProps<"section">, "title" | "onPause" | "onCancel"> &
StateProps & {
title?: string
description?: string
/** The things being worked through, one tile each. */
items?: FanItem[]
/** What runs next. */
tasks?: QueueTask[]
slots?: number
/** Called to run the failed items again. Reject and the card says why. */
onRetryItems?: (ids: string[]) => void | Promise<void>
onMove?: (id: string, toIndex: number) => void | Promise<void>
onPause?: (id: string, paused: boolean) => void | Promise<void>
onCancel?: (id: string) => void | Promise<void>
onPriority?: (id: string, priority: QueuePriority) => void | Promise<void>
}
const failures: Record<number, string> = {
7: "The total did not match the line items: $412.00 against $421.00.",
19: "Unreadable scan. The second page is blank.",
33: "The supplier is not in the vendor list.",
}
const sampleItems: FanItem[] = Array.from({ length: 48 }, (_, i) => {
const n = i + 1
const label = `invoice-${String(n).padStart(3, "0")}.pdf`
if (failures[n]) return { id: String(n), label, status: "failed", detail: failures[n], duration: "0.9 s" }
if (n <= 30) return { id: String(n), label, status: "done", detail: `Matched to purchase order PO-${4100 + n}.`, duration: `${(0.8 + (n % 5) * 0.3).toFixed(1)} s` }
if (n <= 36) return { id: String(n), label, status: "running", detail: "Reading the line items." }
if (n === 44) return { id: String(n), label, status: "skipped", detail: "A duplicate of invoice-012.pdf." }
return { id: String(n), label, status: "queued" }
})
const sampleTasks: QueueTask[] = [
{ id: "r1", title: "Match invoices 31 to 36", state: "running", progress: 58, eta: "about 1 min", agent: "Finance agent" },
{ id: "r2", title: "Match invoices 37 to 42", state: "running", progress: 12, eta: "about 3 min", agent: "Finance agent" },
{ id: "a", title: "Match invoices 43 to 48", state: "queued", priority: "normal", eta: "starts when a slot frees" },
{ id: "b", title: "Post matched invoices to the ledger", state: "queued", priority: "high" },
{ id: "c", title: "Email the finance team a summary", state: "queued", priority: "low" },
]
function Skel() {
return (
<div aria-hidden="true" className="grid gap-6 lg:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]">
<Skeleton className="h-96 rounded-xl" />
<Skeleton className="h-96 rounded-xl" />
</div>
)
}
/**
* A batch run in one view: the tiles of everything being processed on the left (which are done, running or failed, with Retry for the failures), and the queue of what
* the agents will do next on the right, where waiting work can be reordered, paused or cancelled. It answers the two questions people have of a big job: which ones went
* wrong, and what happens next.
*/
function Agent14({ title = "Matching invoices to purchase orders", description = "48 invoices, worked through three at a time.", items = sampleItems, tasks = sampleTasks, slots = 3, onRetryItems, onMove, onPause, onCancel, onPriority, state = "ready", onRetry, emptyState, errorState, className, ...props }: Agent14Props) {
const id = React.useId()
const view = state === "ready" && items.length === 0 ? "empty" : state
return (
<section data-slot="agent-14" data-state={state} aria-labelledby={`${id}-title`} className={cn("mx-auto w-full max-w-6xl px-4 py-8 sm:px-6", className)} {...frameProps(props)}>
<header className="mb-6">
<h1 id={`${id}-title`} className="text-2xl font-semibold tracking-[-0.03em] text-balance">{title}</h1>
<p className="mt-1 text-sm text-muted-foreground text-pretty">{description}</p>
</header>
<StateView state={view} onRetry={onRetry} label="Loading the batch" loading={<Skel />} empty={{ title: "No batch running", description: "Start a batch and its items appear here as they are worked through.", ...emptyState }} error={{ title: "The batch could not be loaded", description: "Nothing was changed. Check your connection and try again.", ...errorState }}>
<div className="grid items-start gap-6 lg:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]">
<FanOutGrid items={items} title="Invoices" defaultSelected={items.find((i) => i.status === "failed")?.id} onRetry={onRetryItems} />
<AgentQueue tasks={tasks} slots={slots} title="What runs next" onMove={onMove} onPause={onPause} onCancel={onCancel} onPriority={onPriority} />
</div>
</StateView>
</section>
)
}
export { Agent14, type Agent14Props }Installation
$ pnpm dlx shadcn@latest add @ballmac/agent-14Add the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/skeleton @ballmac/state-view @ballmac/agent-queue @ballmac/fan-out-gridCopy the source into your project.
components/ballmac/blocks/agent-14/agent-14.tsx// Ballmac UI: Agent 14. https://ui.ballmac.com/blocks/agent-14 "use client" import * as React from "react" import { AgentQueue, type QueuePriority, type QueueTask } from "@/components/ballmac/agent-queue" import { FanOutGrid, type FanItem } from "@/components/ballmac/fan-out-grid" import { Skeleton } from "@/components/ballmac/skeleton" import { frameProps, StateView, type StateProps } from "@/components/ballmac/state-view" import { cn } from "@/lib/utils" type Agent14Props = Omit<React.ComponentProps<"section">, "title" | "onPause" | "onCancel"> & StateProps & { title?: string description?: string /** The things being worked through, one tile each. */ items?: FanItem[] /** What runs next. */ tasks?: QueueTask[] slots?: number /** Called to run the failed items again. Reject and the card says why. */ onRetryItems?: (ids: string[]) => void | Promise<void> onMove?: (id: string, toIndex: number) => void | Promise<void> onPause?: (id: string, paused: boolean) => void | Promise<void> onCancel?: (id: string) => void | Promise<void> onPriority?: (id: string, priority: QueuePriority) => void | Promise<void> } const failures: Record<number, string> = { 7: "The total did not match the line items: $412.00 against $421.00.", 19: "Unreadable scan. The second page is blank.", 33: "The supplier is not in the vendor list.", } const sampleItems: FanItem[] = Array.from({ length: 48 }, (_, i) => { const n = i + 1 const label = `invoice-${String(n).padStart(3, "0")}.pdf` if (failures[n]) return { id: String(n), label, status: "failed", detail: failures[n], duration: "0.9 s" } if (n <= 30) return { id: String(n), label, status: "done", detail: `Matched to purchase order PO-${4100 + n}.`, duration: `${(0.8 + (n % 5) * 0.3).toFixed(1)} s` } if (n <= 36) return { id: String(n), label, status: "running", detail: "Reading the line items." } if (n === 44) return { id: String(n), label, status: "skipped", detail: "A duplicate of invoice-012.pdf." } return { id: String(n), label, status: "queued" } }) const sampleTasks: QueueTask[] = [ { id: "r1", title: "Match invoices 31 to 36", state: "running", progress: 58, eta: "about 1 min", agent: "Finance agent" }, { id: "r2", title: "Match invoices 37 to 42", state: "running", progress: 12, eta: "about 3 min", agent: "Finance agent" }, { id: "a", title: "Match invoices 43 to 48", state: "queued", priority: "normal", eta: "starts when a slot frees" }, { id: "b", title: "Post matched invoices to the ledger", state: "queued", priority: "high" }, { id: "c", title: "Email the finance team a summary", state: "queued", priority: "low" }, ] function Skel() { return ( <div aria-hidden="true" className="grid gap-6 lg:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]"> <Skeleton className="h-96 rounded-xl" /> <Skeleton className="h-96 rounded-xl" /> </div> ) } /** * A batch run in one view: the tiles of everything being processed on the left (which are done, running or failed, with Retry for the failures), and the queue of what * the agents will do next on the right, where waiting work can be reordered, paused or cancelled. It answers the two questions people have of a big job: which ones went * wrong, and what happens next. */ function Agent14({ title = "Matching invoices to purchase orders", description = "48 invoices, worked through three at a time.", items = sampleItems, tasks = sampleTasks, slots = 3, onRetryItems, onMove, onPause, onCancel, onPriority, state = "ready", onRetry, emptyState, errorState, className, ...props }: Agent14Props) { const id = React.useId() const view = state === "ready" && items.length === 0 ? "empty" : state return ( <section data-slot="agent-14" data-state={state} aria-labelledby={`${id}-title`} className={cn("mx-auto w-full max-w-6xl px-4 py-8 sm:px-6", className)} {...frameProps(props)}> <header className="mb-6"> <h1 id={`${id}-title`} className="text-2xl font-semibold tracking-[-0.03em] text-balance">{title}</h1> <p className="mt-1 text-sm text-muted-foreground text-pretty">{description}</p> </header> <StateView state={view} onRetry={onRetry} label="Loading the batch" loading={<Skel />} empty={{ title: "No batch running", description: "Start a batch and its items appear here as they are worked through.", ...emptyState }} error={{ title: "The batch could not be loaded", description: "Nothing was changed. Check your connection and try again.", ...errorState }}> <div className="grid items-start gap-6 lg:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]"> <FanOutGrid items={items} title="Invoices" defaultSelected={items.find((i) => i.status === "failed")?.id} onRetry={onRetryItems} /> <AgentQueue tasks={tasks} slots={slots} title="What runs next" onMove={onMove} onPause={onPause} onCancel={onCancel} onPriority={onPriority} /> </div> </StateView> </section> ) } export { Agent14, type Agent14Props }Update the import paths to match your project setup.
Usage
"use client"
import { Agent14 } from "@/components/ballmac/blocks/agent-14/agent-14"
export default function Agent14Demo() {
return <Agent14 />
}States
Pass state and the block does the rest: skeletons that match its layout, a clear empty message, an error you can retry, and announcements for screen readers.
Loading
Skeleton grid and queue under the real heading, announced as loading.
state="loading"No batch running
Says no batch is running; emptyState sets the copy.
state="empty"Could not load
An alert saying nothing was changed, with Try again (onRetry). A retry or a queue change that fails says why and changes nothing.
state="error"
Dependencies
- npm
- None
Use with AI
Batch run. <Agent14 title description items={FanItem[]} tasks={QueueTask[]} slots onRetryItems={(ids) => Promise} onMove onPause onCancel onPriority />. The grid answers which items went wrong (it opens on the first failed one); the queue answers what happens next. Both report failures plainly and change nothing when a handler rejects. (onRetry is the usual retry for the error state.) With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Agent 14: batch run (@ballmac/agent-14) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Watching a bulk job where one agent task runs over many files or records
- Deciding what runs next and retrying only what failed
Not for
- One long run; use agent-11
- A team of different agents; use agent-12
Registry JSON: https://ui.ballmac.com/r/agent-14.json
Free to use in personal and commercial projects.
More ai agents blocks
Agent 1: live agent run
One AI agent run as it happens: the task and status, the plan beside it, every note, tool call and approval request in order, approvals that pause the run, the result with copy, and a follow-up box.
Agent 10: console with handoff
An agent console: the conversation with each tool call as a step you can open to see what was asked and what came back, Stop while it works, Enter to send, and a clear Take over and Hand back so a person is never mistaken for the agent.
Agent 11: run workbench
A run workbench: the conversation with tool calls as openable cards, a live plan, a context meter and a steering box to redirect the agent while it works, with Stop, optimistic instructions and honest failures.
Agent 12: mission control
Mission control for a team of agents: spend, tokens, running agents and decisions waiting at the top, the delegation tree below, and an inbox of requests with Approve and Deny that report back honestly.
Ballmac UI Pro
Need the whole screen, not one piece?
150 premium blocks (dashboards, settings, calendars, mail, shops, onboarding), a private shadcn registry and Figma tokens. Blocks that show data come with loading, empty and error states built in.
Not ready? Try three Pro blocks free
Leave your email and the blocks appear right here, ready to copy. Yours to use in any project.