Venue Map
Pick seats at a stadium, arena or concert hall. Choose a section on an overview map shaded by price or seats left, then pick seats in it, or ask the agent for the best available.
Experience · Shares patterns with Seat Map, Region Map
≈ 6,567 tokens per use (tool 1,320 · guidance 325 · typical payload 4,922)How token costs are estimated
Example: Venue Map
Two tickets for Saturday's match behind the goal.
Northgate FC v Harbour City
Northgate Park · Sat, Oct 10, 3:00 PM
Sections in order: each tier clockwise from the top. Arrow keys move between sections; Enter opens one.
- Price from, light to strong: €20–€40, €40–€60, €60–€80, €80–€100
- Sold out
- Accessible
When to use
- The user is buying tickets for a stadium, arena or concert hall and where they sit matters: "behind the goal", "not too far back", "two together".
- The agent fetched the venue's sections, prices and live availability from the ticketing system.
When not to use
- General admission or a single price zone: there's nothing to choose. Ask how many tickets and book.
- A small room with one seating block, such as a cinema or a theater's stalls. Use the Seat Map.
- The agent doesn't have real availability. Fetch it first; never invent sections or prices.
How the agent should use it
- Every section, price and seat count comes from the ticketing system, with the system's real ids.
- Lay ring sections out with
angleandspanaround the focal point: 0° is the top, angles go clockwise, and ring 1 is nearest. A section may cross the top (angle: 348.75, span: 22.5). Floor and standing sections usefloor: { x, y, w, h }in 0–1 units of the floor area, front first. - Send
rowsonly for sections you fetched seat by seat. When the user opens a section without rows, the result isopen-section: fetch that section and call again with itsrowsandinitialSectionset to it. - Set
count,togetherandmaxPricefrom what the user said. The user can change the count and the price limit on the card. - On
best-available, pick the seats yourself within the count, price limit and accessibility the user set, and say which ones you chose. - This only picks seats. Reserve them after a
selectresult and confirm the total before taking payment. - Lifecycle: availability is live, so set
fetchedAtandstaleAfterMs(about 2 minutes) on the call; the Renderer then shows "may have changed" with a refresh. Once seats are held for the user, setexpiresAtto the end of the hold. When the agent sends fresh availability or a section's rows in a new call, mark the old call superseded (supersededBy).
Interaction
| Input | Action |
|---|---|
| Click a section, or Enter / Space in the section list | Opens it: its seats, or a loading state while the agent fetches them |
| ↑ ↓ ← → in the section list | Move between sections; the focused one is outlined on the map with its details |
| Home End in the section list | First or last section |
| Tickets stepper, Max price slider, Accessible only | Change the count and dim sections that don't fit |
| Price / Seats left (in the legend row) | Switch what the shading shows |
| Best available (footer, on the overview) | Hands the choice to the agent |
| Arrow keys, Home End in the seat grid | Move between available seats, as in the Seat Map |
| Back arrow beside the section name | Returns to the overview; picks in another section are cleared when you open it |
The map itself is hidden from assistive tech. Keyboard and screen reader users get a listbox of every section in reading order (floor first, then each tier clockwise from the top), grouped by tier, with the same facts as the tooltip: "Section 112, lower tier, behind the goal, from €55, 97 left". Sold-out sections are muted and say "Sold out"; sections above the max price or without accessible seating are dimmed and say why, so nothing relies on color alone. The shading uses one hue from light to strong, explained in the legend by a single ramp with its two ends; screen readers hear every step's range.
In the seat step, the grid is the Seat Map's ARIA grid with a roving tabindex. Picking more than the count drops the oldest pick and each pick is announced. The heading above the grid names the section with its view and price per seat (the tier is read to screen readers). The footer shows the chosen seats large ("Row F · 7, 8") with the total beside them, and one quiet line for progress and whether the seats are together; when a seat's price is unknown, the total is left out and the line says "Total at checkout". The legend under the grid lists only taken, unavailable and wheelchair seats, and a hovered or focused seat's details take its place.
Installation
npx shadcn@latest add https://your-site.com/r/venue-map.json
Usage
import { tool } from "ai"
import { venueMapTool } from "@/components/genui/venue-map.schema"
const tools = {
// No execute(): the call is completed by the user's choice in the UI.
[venueMapTool.name]: tool({
description: venueMapTool.description,
inputSchema: venueMapTool.inputSchema,
}),
}import { VenueMap } from "@/components/genui/venue-map"
function ToolPart({ part, addToolResult }) {
if (part.type !== "tool-pick_venue_seats") return null
return (
<VenueMap
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({
tool: "pick_venue_seats",
toolCallId: part.toolCallId,
output: result,
})
}
/>
)
}The agent receives one of:
{ action: "open-section", sectionId: "nwp-112" }
{ action: "best-available", count: 2, together: true, maxPrice: 60 }
{ action: "select", sectionId: "nwp-112", seats: [{ seatId: "112-F-7", price: 55 }, { seatId: "112-F-8", price: 55 }] }
{ action: "dismiss" }open-section is a request, not a decision. With the AI SDK it completes the call, and the card shows "Asked for seats in section 112" until the agent's next call (with the section's rows and initialSection) replaces it. A host that keeps the same card open can instead update its props in place: the card shows "Loading seats for section 112…" and switches to the seat grid when the rows arrive. best-available includes maxPrice only when the user lowered the slider, and accessible: true only when "Accessible only" is on. In select, seats come in map order with price when it's known.
With LangGraph
Push it from a graph node with ui.push({ name: "pick_venue_seats", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
Pass the stored result back as result. A select shows the chosen seats in their section, read-only; best-available, open-section and dismiss show what the user asked for. It never calls onResult again.
<VenueMap
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
venue | { name } | Data-bound |
event | { title, start }? | Data-bound. start is ISO 8601, shown in the venue's local time |
focal | { kind, label? } | pitch, court, rink (drawn in the middle) or stage (drawn at the top) |
sections | Section[] | Data-bound. Every section on sale. Up to 120 |
currency | string | ISO 4217 code |
count | number | Tickets wanted, 1–10. Default 2 |
together | boolean | The user wants seats side by side. Default true |
maxPrice | number? | Price per ticket. Sections whose lowest price is above it are dimmed |
colorBy | "price" | "availability" | What the shading shows first. Default "price" |
initialSection | string? | Section id to open first, after an open-section request |
Section is { id, label, tier?, position, price?, available, accessible?, view?, rows? }. tier is floor, lower, middle or upper; it groups the screen reader list and defaults from the ring (floor for floor rectangles). position is { angle, span, ring } (ring 1–3) or { floor: { x, y, w, h } }. price is { from, to? }: set only from when every seat costs the same. available is the number of seats left; 0 shows the section as sold out. view is a short phrase such as "Behind the goal".
rows are { label, seats }[], front row first. Each entry in seats is a seat { id, label?, state, price?, features? } or a stairway { gap: true }. state is available, taken or blocked; features are aisle, accessible, companion, restricted-view and extra-legroom. A seat without price uses the section's single price.
Host props: status (streaming, ready, submitting, selected, best-available, section-requested, dismissed), onResult, result and className.