# Agent Builder

> A complete screen built from Button, ChatMessage, Combobox, Composer and 7 more components.

Pattern · Pro kit · [Live page](https://ui.twentyfirst.media/pro/patterns/AgentBuilderScreen/)

## Code

```tsx
import * as React from "react";
import {
  Button,
  ChatMessage,
  Combobox,
  Composer,
  PageHeader,
  Panel,
  RadioGroup,
  Segmented,
  Slider,
} from "@twentyfirstmedia/pro";
import { Shell } from "./_workspace";

function App() {
  return (
    <Shell
      area="Agents"
      current="Builder"
      action={{
        label: "New agent",
        icon: "plus",
      }}
      pages={[
        {
          id: "Fleet",
          label: "Fleet",
          icon: "bot",
        },
        {
          id: "Chat",
          label: "Chat",
          icon: "mail",
        },
        {
          id: "Builder",
          label: "Builder",
          icon: "settings",
        },
        {
          id: "Runs",
          label: "Runs",
          icon: "refresh",
        },
      ]}
    >
      <PageHeader
        crumbs={["Agents", "Atlas"]}
        title="Configure Atlas"
        subtitle="Support triage · changes apply to new runs"
        actions={[
          <Button key="s" icon="check">
            Save changes
          </Button>,
        ]}
      />
      <div
        className="tf-grid"
        style={
          {
            "--cols": "minmax(0, 1fr) 340px",
            gap: 16,
            alignItems: "start",
          } as React.CSSProperties
        }
      >
        <div
          className="tf-col"
          style={{
            gap: 16,
          }}
        >
          <Panel title="Instructions" subtitle="What it does, how it talks, what it must never do">
            <textarea
              className="tf-input"
              aria-label="Instructions"
              rows={5}
              style={{
                fontFamily: "var(--font-mono)",
                fontSize: 13,
              }}
              defaultValue={
                "You are Atlas, the support triage agent for Halo.\n- Tag new mail and draft replies.\n- Never promise refunds; hand those to a person."
              }
            />
          </Panel>
          <Panel title="Model">
            <RadioGroup
              cards
              defaultValue="Sonnet"
              options={[
                {
                  value: "Opus",
                  label: "Opus 5.5",
                  hint: "Deepest · €15 / M",
                },
                {
                  value: "Sonnet",
                  label: "Sonnet 5.5",
                  hint: "Balanced · €3 / M",
                },
                {
                  value: "Haiku",
                  label: "Haiku 5.5",
                  hint: "Fast · €1 / M",
                },
              ]}
            />
          </Panel>
          <Panel title="Guardrails">
            <Segmented
              defaultValue="Drafts only"
              options={["Drafts only", "Acts, then reports", "Fully autonomous"]}
            />
            <Combobox
              label="Always ask before"
              multiple
              defaultValue={["Sending email", "Spending money"]}
              options={["Sending email", "Spending money", "Deleting anything"]}
            />
            <Slider
              label="Monthly budget"
              min={0}
              max={500}
              defaultValue={120}
              format={(v) => "€" + v}
            />
          </Panel>
        </div>
        <div
          className="tf-panel"
          style={{
            gap: 16,
          }}
        >
          <div>
            <h3 className="tf-panel-title">Test</h3>
            <p className="tf-panel-sub">Uses your unsaved setup</p>
          </div>
          <ChatMessage role="user" text="Can I get a refund?" />
          <ChatMessage
            role="assistant"
            name="Atlas"
            tint="sky"
            actions={false}
            text="I’ll pass this to a person, since refunds need a human. You’ll hear back today."
          />
          <Composer placeholder="Test a message…" hint="" />
        </div>
      </div>
    </Shell>
  );
}
export default App;
```

## Props

### Button

| Prop | Type | Notes |
| --- | --- | --- |
| `variant?` | `"primary" \| "secondary" \| "soft" \| "ghost" \| "danger" \| "danger-solid"` |  |
| `size?` | `"md" \| "sm"` |  |
| `icon?` | `IconName` |  |
| `iconRight?` | `IconName` |  |
| `disabled?` | `boolean` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `children?` | `ReactNode` |  |
| `asChild?` | `boolean` | Render the single child element (a link, a router Link) with the button's look instead of a <button>. |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### ChatMessage

| Prop | Type | Notes |
| --- | --- | --- |
| `role` | `"user" \| "assistant" \| "system"` | Required |
| `name?` | `string` |  |
| `time?` | `string` |  |
| `model?` | `string` |  |
| `text?` | `string` |  |
| `streaming?` | `boolean` |  |
| `tint?` | `Tint` |  |
| `sources?` | `Source[]` |  |
| `attachments?` | `{ name: string; size?: string; kind?: string }[]` |  |
| `usage?` | `string` |  |
| `onRetry?` | `() => void` |  |
| `onFeedback?` | `(v: "up" \| "down") => void` |  |
| `actions?` | `boolean` |  |
| `before?` | `ReactNode` |  |
| `after?` | `ReactNode` |  |
| `children?` | `ReactNode` |  |
| `feedback?` | `"up" \| "down" \| null` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Combobox

| Prop | Type | Notes |
| --- | --- | --- |
| `label?` | `string` |  |
| `options` | `(string \| Option)[]` | Required |
| `value?` | `string \| string[] \| null` |  |
| `defaultValue?` | `string \| string[]` |  |
| `onChange?` | `(v: string \| string[] \| null) => void` |  |
| `multiple?` | `boolean` |  |
| `creatable?` | `boolean` |  |
| `placeholder?` | `string` |  |
| `hint?` | `string` |  |
| `error?` | `string` |  |
| `required?` | `boolean` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Composer

| Prop | Type | Notes |
| --- | --- | --- |
| `value?` | `string` |  |
| `onChange?` | `(v: string) => void` |  |
| `onSend?` | `(text: string, files: (File \| { name: string })[]) => void` |  |
| `busy?` | `boolean` |  |
| `onStop?` | `() => void` |  |
| `placeholder?` | `string` |  |
| `label?` | `string` |  |
| `models?` | `ModelOption[]` |  |
| `model?` | `string` |  |
| `onModel?` | `(id: string) => void` |  |
| `commands?` | `ComposerCommand[]` |  |
| `onCommand?` | `(c: ComposerCommand) => void` |  |
| `attachments?` | `(File \| { name: string })[]` |  |
| `onAttach?` | `(files: FileList) => void` |  |
| `onRemoveAttachment?` | `(i: number) => void` |  |
| `hint?` | `string` |  |
| `left?` | `ReactNode` |  |
| `disabled?` | `boolean` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Icon

| Prop | Type | Notes |
| --- | --- | --- |
| `name` | `IconName` | Required |
| `size?` | `number` |  |
| `label?` | `string` |  |
| `className?` | `string` |  |
| `style?` | `CSSProperties` | Inline style on the root element. |

### PageHeader

| Prop | Type | Notes |
| --- | --- | --- |
| `crumbs?` | `(string \| Crumb)[]` | A breadcrumb above the title (see `Breadcrumb`): labels, or items with `href`/`onClick`. |
| `crumbLinkAs?` | `ElementType` | The element crumb links render as, e.g. Next.js `Link`. |
| `title` | `string` | Required |
| `subtitle?` | `string` |  |
| `actions?` | `ReactNode[]` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Panel

| Prop | Type | Notes |
| --- | --- | --- |
| `side?` | `boolean` |  |
| `title?` | `string` |  |
| `subtitle?` | `string` |  |
| `action?` | `ReactNode` |  |
| `children?` | `ReactNode` |  |
| `surface?` | `Surface` | `"glass"`: translucent glass over what is behind (maps, 3D, video, photos); `"solid"` opts out inside a glass `UIProvider`. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### RadioGroup

| Prop | Type | Notes |
| --- | --- | --- |
| `label?` | `string` |  |
| `options` | `(string \| Option)[]` | Required |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `onChange?` | `(v: string) => void` |  |
| `cards?` | `boolean` |  |
| `hint?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Segmented

| Prop | Type | Notes |
| --- | --- | --- |
| `options` | `(string \| Option)[]` | Required |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `onChange?` | `(v: string) => void` |  |
| `size?` | `"sm"` |  |
| `block?` | `boolean` |  |
| `label?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Slider

| Prop | Type | Notes |
| --- | --- | --- |
| `label?` | `string` |  |
| `min?` | `number` |  |
| `max?` | `number` |  |
| `step?` | `number` |  |
| `value?` | `number` |  |
| `defaultValue?` | `number` |  |
| `onChange?` | `(n: number) => void` |  |
| `format?` | `(n: number) => string` |  |
| `marks?` | `boolean` |  |
| `hint?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### SubNav

| Prop | Type | Notes |
| --- | --- | --- |
| `eyebrow?` | `string` |  |
| `action?` | `{ label: string; icon?: IconName; onClick?: () => void }` |  |
| `items` | `SubNavItem[]` | Required |
| `current?` | `string` |  |
| `onPick?` | `(id: string) => void` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### TopBar

| Prop | Type | Notes |
| --- | --- | --- |
| `brand?` | `ReactNode` | The product name, or a node (a logo mark with the name). |
| `tag?` | `string` |  |
| `items` | `NavItem[]` | Required |
| `active?` | `string` |  |
| `user?` | `{ name: string; email?: string }` |  |
| `onNavigate?` | `(label: string) => void` |  |
| `extra?` | `ReactNode` |  |
| `userOpen?` | `boolean` |  |
| `onUser?` | `() => void` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
