Skip to content

Screens

Booking Assistant

An assistant that acts — compares options, picks a time, fills a form and confirms before it books.

2 variantsMIT

Preview

Full screen

Design team offsite

Ana Ribeiro

Plan a two-day offsite for our design team of 8 in Copenhagen, mid-March. Keep it under €5k.

files · / commands

Installation

Copies the screen into components/ and installs every component it uses, with their dependencies and theme tokens.

pnpm dlx shadcn@latest add https://tuffui.com/r/booking-assistant.json

Usage

Pass your own data and wire the callbacks to your agent runtime.

import { BookingAssistant } from "@/components/booking-assistant";
import { AgentQuestion } from "@/components/ui/agent-question";
import { ConfirmationCard } from "@/components/ui/confirmation-card";
import { SlotPicker } from "@/components/ui/slot-picker";

export function TripPlanner({ trip, run }: { trip: Trip; run: AgentRun }) {
  return (
    <BookingAssistant
      status={run.streaming ? "streaming" : "idle"}
      onSend={(text) => run.send(text)}
      onStop={() => run.stop()}
      data={{
        title: trip.name,
        model: run.model,
        user: { name: trip.owner.name },
        // Each answer can be an interactive component the agent asked for.
        messages: run.turns.map((turn) => ({
          id: turn.id,
          role: turn.role,
          at: turn.createdAt,
          content: renderTurn(turn), // AgentQuestion, SlotPicker, FormCard, ConfirmationCard…
        })),
        summary: {
          title: "Trip summary",
          estimate: trip.estimate,
          items: [
            { id: "venue", label: "Venue", value: trip.venue?.name },
            { id: "call", label: "Kickoff call", value: trip.call?.label },
            { id: "booking", label: "Booking", value: trip.bookingRef },
          ],
        },
      }}
    />
  );
}

Props

PropTypeDefaultDescription
data*BookingAssistantData—Title, model, user, thread messages (any React content) and the summary panel rows. A row with no value shows as not decided yet.
status"idle" | "streaming""idle"While streaming, the composer queues messages and shows Stop.
onSend(text: string) => void—Called with the composer text.
onStop() => void—Stops the running answer.
summaryOpenboolean—Controls the side panel (from 64rem wide). Uncontrolled and open by default; below 64rem the same button opens a drawer.
onSummaryOpenChange(open: boolean) => void—Called when the side panel is shown or hidden.
classNamestring—Extra classes for the root.
type BookingAssistantData = {
  title: string;
  model: string;
  messages: ThreadMessage[]; // { id, role, at, content: ReactNode }
  summary: { title: string; items: SummaryItem[]; estimate?: string };
  user: { name: string; avatar?: string };
};

type SummaryItem = {
  id: string;
  label: string;
  value?: string; // empty until decided
  icon?: Icon;
};