> ## Documentation Index
> Fetch the complete documentation index at: https://docs.voiceblox.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversation Nodes

> Step nodes that define the conversation flow: Start, Timer, Burst, IfElse, End, and more.

export const ConvNode = ({label, icon, hasInputLeft = true, hasOutputRight = true, outputs = null, inputs = null, subtitle = null, children}) => {
  const RED = "hsl(5,82%,55%)";
  const Wand = () => <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
    opacity: 0.3
  }}>
      <path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.21 1.21 0 0 0 1.72 0L21.64 5.36a1.21 1.21 0 0 0 0-1.72Z" />
      <path d="m14 7 3 3" /><path d="M5 6v4" /><path d="M19 14v4" /><path d="M10 2v2" /><path d="M7 8H3" /><path d="M21 16h-4" /><path d="M11 3H9" />
    </svg>;
  const Handle = ({side, color}) => <div style={{
    position: "absolute",
    [side]: "-6px",
    top: "18px",
    width: "12px",
    height: "12px",
    borderRadius: "50%",
    background: color || RED,
    border: "2px solid white",
    zIndex: 10,
    boxShadow: "0 1px 3px rgba(0,0,0,0.2)"
  }} />;
  return <div style={{
    display: "flex",
    justifyContent: "center",
    padding: "28px 16px",
    background: "rgba(128,128,128,0.06)",
    borderRadius: "12px",
    margin: "16px 0"
  }}>
      <div style={{
    position: "relative",
    width: "240px",
    borderRadius: "12px",
    border: "1px solid hsla(5,82%,55%,0.3)",
    background: "hsla(5,82%,55%,0.1)",
    boxShadow: "0 4px 24px -4px rgba(0,0,0,0.08),0 1px 2px rgba(0,0,0,0.04)",
    fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif"
  }}>
        {hasInputLeft && !inputs && <Handle side="left" color={RED} />}
        {hasOutputRight && !outputs && <Handle side="right" color={RED} />}
        <div style={{
    display: "flex",
    alignItems: "center",
    gap: "8px",
    padding: "8px 12px",
    borderBottom: "1px solid rgba(128,128,128,0.12)"
  }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={RED} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">{icon}</svg>
          <span style={{
    fontSize: "14px",
    fontWeight: 600,
    flex: 1
  }}>{label}</span>
          <Wand />
        </div>
        <div style={{
    padding: "10px 12px",
    display: "flex",
    flexDirection: "column",
    gap: "8px"
  }}>
          {subtitle && <p style={{
    fontSize: "10px",
    color: "#888",
    margin: 0,
    lineHeight: 1.4
  }}>{subtitle}</p>}
          {children}
        </div>
        {inputs && <div style={{
    borderTop: "1px solid rgba(128,128,128,0.1)",
    padding: "8px 12px",
    display: "flex",
    flexDirection: "column",
    gap: "4px"
  }}>
            {inputs.map(inp => <div key={inp.id} style={{
    position: "relative",
    display: "flex",
    alignItems: "center",
    gap: "8px",
    padding: "6px 8px",
    borderRadius: "4px",
    background: "rgba(128,128,128,0.1)"
  }}>
                <div style={{
    position: "absolute",
    left: "-18px",
    top: "50%",
    transform: "translateY(-50%)",
    width: "12px",
    height: "12px",
    borderRadius: "50%",
    background: inp.color,
    border: "2px solid white",
    boxShadow: "0 1px 3px rgba(0,0,0,0.2)"
  }} />
                <span style={{
    fontSize: "12px",
    fontWeight: 500,
    color: inp.color
  }}>{inp.label}</span>
              </div>)}
          </div>}
        {outputs && <div style={{
    borderTop: "1px solid rgba(128,128,128,0.1)",
    padding: "8px 12px"
  }}>
            <p style={{
    fontSize: "10px",
    color: "#888",
    fontWeight: 500,
    margin: "0 0 4px 0"
  }}>Outputs</p>
            <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "4px"
  }}>
              {outputs.map(o => <div key={o.id} style={{
    position: "relative",
    display: "flex",
    alignItems: "center",
    justifyContent: "flex-end",
    padding: "6px 8px",
    borderRadius: "4px",
    background: "rgba(128,128,128,0.1)",
    fontSize: "10px"
  }}>
                  <span style={{
    color: "#888"
  }}>{o.label}</span>
                  <div style={{
    position: "absolute",
    right: "-18px",
    top: "50%",
    transform: "translateY(-50%)",
    width: "12px",
    height: "12px",
    borderRadius: "50%",
    background: RED,
    border: "2px solid white",
    boxShadow: "0 1px 3px rgba(0,0,0,0.2)"
  }} />
                </div>)}
            </div>
          </div>}
      </div>
    </div>;
};

export const ConvTextarea = ({label = "Instruction", placeholder, rows = 2}) => {
  const [val, setVal] = useState("");
  return <div>
      <label style={{
    fontSize: "10px",
    color: "#888",
    display: "block",
    marginBottom: "3px"
  }}>{label}</label>
      <textarea placeholder={placeholder} value={val} onChange={e => setVal(e.target.value)} rows={rows} style={{
    width: "100%",
    padding: "4px 8px",
    border: "1px solid rgba(128,128,128,0.2)",
    borderRadius: "6px",
    fontSize: "12px",
    background: "rgba(128,128,128,0.06)",
    outline: "none",
    resize: "none",
    boxSizing: "border-box",
    fontFamily: "inherit"
  }} />
    </div>;
};

export const ConvBool = ({label, sublabel = null, defaultVal = false}) => {
  const PURPLE = "hsl(262,83%,58%)";
  const [val, setVal] = useState(defaultVal);
  return <label style={{
    display: "flex",
    alignItems: "center",
    gap: "8px",
    padding: "6px 8px",
    borderRadius: "6px",
    border: `1px solid ${val ? "rgba(130,111,252,0.4)" : "rgba(128,128,128,0.2)"}`,
    background: val ? "rgba(130,111,252,0.1)" : "rgba(128,128,128,0.05)",
    cursor: "pointer"
  }}>
      <input type="checkbox" checked={val} onChange={e => setVal(e.target.checked)} style={{
    accentColor: PURPLE
  }} />
      <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "1px"
  }}>
        <span style={{
    fontSize: "12px",
    fontWeight: 500,
    color: val ? PURPLE : "inherit"
  }}>{label}</span>
        {sublabel && <span style={{
    fontSize: "9px",
    color: "#aaa",
    lineHeight: 1.2
  }}>{sublabel}</span>}
      </div>
    </label>;
};

export const ConvStepper = ({label, min, max, step, defaultVal, unit = ""}) => {
  const [val, setVal] = useState(defaultVal);
  const fmt = v => {
    if (unit === "s" && v >= 60) {
      const m = Math.floor(v / 60), s = v % 60;
      return s > 0 ? `${m}m ${s}s` : `${m}m`;
    }
    return `${v}${unit}`;
  };
  return <div>
      <label style={{
    fontSize: "10px",
    color: "#888",
    display: "block",
    marginBottom: "3px"
  }}>{label}</label>
      <div style={{
    display: "flex",
    alignItems: "center",
    gap: "4px"
  }}>
        <button onClick={() => setVal(v => Math.max(min, v - step))} style={{
    width: "28px",
    height: "28px",
    borderRadius: "6px",
    border: "1px solid rgba(128,128,128,0.2)",
    background: "rgba(128,128,128,0.06)",
    cursor: "pointer",
    fontSize: "14px",
    fontWeight: 500,
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  }}>−</button>
        <div style={{
    flex: 1,
    textAlign: "center",
    fontSize: "12px",
    fontWeight: 500,
    border: "1px solid rgba(128,128,128,0.2)",
    borderRadius: "6px",
    padding: "5px 4px",
    background: "rgba(128,128,128,0.04)"
  }}>{fmt(val)}</div>
        <button onClick={() => setVal(v => Math.min(max, v + step))} style={{
    width: "28px",
    height: "28px",
    borderRadius: "6px",
    border: "1px solid rgba(128,128,128,0.2)",
    background: "rgba(128,128,128,0.06)",
    cursor: "pointer",
    fontSize: "14px",
    fontWeight: 500,
    display: "flex",
    alignItems: "center",
    justifyContent: "center"
  }}>+</button>
      </div>
    </div>;
};

export const CategorizeNodePreview = () => {
  const RED = "hsl(5,82%,55%)";
  const LLM = "hsl(142,71%,45%)";
  const [cats, setCats] = useState(["Sales", "Support"]);
  const [cmd, setCmd] = useState("");
  const Wand = () => <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
    opacity: 0.3
  }}>
      <path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.21 1.21 0 0 0 1.72 0L21.64 5.36a1.21 1.21 0 0 0 0-1.72Z" />
      <path d="m14 7 3 3" /><path d="M5 6v4" /><path d="M19 14v4" /><path d="M10 2v2" /><path d="M7 8H3" /><path d="M21 16h-4" /><path d="M11 3H9" />
    </svg>;
  return <div style={{
    display: "flex",
    justifyContent: "center",
    padding: "28px 16px",
    background: "rgba(128,128,128,0.06)",
    borderRadius: "12px",
    margin: "16px 0"
  }}>
      <div style={{
    position: "relative",
    width: "240px",
    borderRadius: "12px",
    border: "1px solid hsla(5,82%,55%,0.3)",
    background: "hsla(5,82%,55%,0.1)",
    boxShadow: "0 4px 24px -4px rgba(0,0,0,0.08),0 1px 2px rgba(0,0,0,0.04)",
    fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif"
  }}>
        <div style={{
    display: "flex",
    alignItems: "center",
    gap: "8px",
    padding: "8px 12px",
    borderBottom: "1px solid rgba(128,128,128,0.12)"
  }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={RED} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z" />
            <path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65" />
            <path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65" />
          </svg>
          <span style={{
    fontSize: "14px",
    fontWeight: 600,
    flex: 1
  }}>Categorize</span>
          <Wand />
        </div>
        <div style={{
    padding: "10px 12px",
    display: "flex",
    flexDirection: "column",
    gap: "8px"
  }}>
          <p style={{
    fontSize: "10px",
    color: "#888",
    margin: 0,
    lineHeight: 1.4
  }}>Classifies the user's request into categories and routes to different paths</p>
          <div>
            <label style={{
    fontSize: "10px",
    color: "#888",
    display: "block",
    marginBottom: "3px"
  }}>Instruction</label>
            <textarea placeholder="Classify the user's inquiry..." value={cmd} onChange={e => setCmd(e.target.value)} rows={2} style={{
    width: "100%",
    padding: "4px 8px",
    border: "1px solid rgba(128,128,128,0.2)",
    borderRadius: "6px",
    fontSize: "12px",
    background: "rgba(128,128,128,0.06)",
    outline: "none",
    resize: "none",
    boxSizing: "border-box",
    fontFamily: "inherit"
  }} />
          </div>
          <div>
            <label style={{
    fontSize: "10px",
    color: "#888",
    display: "block",
    marginBottom: "3px"
  }}>Categories</label>
            {cats.map((c, i) => <div key={i} style={{
    display: "flex",
    gap: "4px",
    marginBottom: "4px"
  }}>
                <input value={c} onChange={e => {
    const n = [...cats];
    n[i] = e.target.value;
    setCats(n);
  }} style={{
    flex: 1,
    padding: "4px 8px",
    border: "1px solid rgba(128,128,128,0.2)",
    borderRadius: "6px",
    fontSize: "12px",
    background: "rgba(128,128,128,0.06)",
    outline: "none"
  }} />
                <button onClick={() => cats.length > 1 && setCats(cats.filter((_, j) => j !== i))} style={{
    padding: "4px 8px",
    border: "1px solid rgba(128,128,128,0.2)",
    borderRadius: "6px",
    fontSize: "11px",
    background: "rgba(128,128,128,0.06)",
    cursor: "pointer",
    color: "#888"
  }}>✕</button>
              </div>)}
            <button onClick={() => setCats([...cats, ""])} style={{
    width: "100%",
    padding: "4px 8px",
    border: "1px dashed rgba(128,128,128,0.3)",
    borderRadius: "6px",
    fontSize: "10px",
    background: "transparent",
    cursor: "pointer",
    color: "#888"
  }}>+ Add category</button>
          </div>
        </div>
        <div style={{
    borderTop: "1px solid rgba(128,128,128,0.1)",
    padding: "8px 12px",
    display: "flex",
    flexDirection: "column",
    gap: "4px"
  }}>
          {[{
    id: "in",
    label: "Previous Step",
    color: RED
  }, {
    id: "llm_in",
    label: "LLM",
    color: LLM
  }].map(inp => <div key={inp.id} style={{
    position: "relative",
    display: "flex",
    alignItems: "center",
    gap: "8px",
    padding: "6px 8px",
    borderRadius: "4px",
    background: "rgba(128,128,128,0.1)"
  }}>
              <div style={{
    position: "absolute",
    left: "-18px",
    top: "50%",
    transform: "translateY(-50%)",
    width: "12px",
    height: "12px",
    borderRadius: "50%",
    background: inp.color,
    border: "2px solid white",
    boxShadow: "0 1px 3px rgba(0,0,0,0.2)"
  }} />
              <span style={{
    fontSize: "12px",
    fontWeight: 500,
    color: inp.color
  }}>{inp.label}</span>
            </div>)}
        </div>
        <div style={{
    borderTop: "1px solid rgba(128,128,128,0.1)",
    padding: "8px 12px"
  }}>
          <p style={{
    fontSize: "10px",
    color: "#888",
    fontWeight: 500,
    margin: "0 0 4px 0"
  }}>Outputs</p>
          <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "4px"
  }}>
            {cats.map((c, i) => <div key={i} style={{
    position: "relative",
    display: "flex",
    alignItems: "center",
    justifyContent: "flex-end",
    padding: "6px 8px",
    borderRadius: "4px",
    background: "rgba(128,128,128,0.1)",
    fontSize: "10px"
  }}>
                <span style={{
    color: "#888"
  }}>{c || `Category ${i + 1}`}</span>
                <div style={{
    position: "absolute",
    right: "-18px",
    top: "50%",
    transform: "translateY(-50%)",
    width: "12px",
    height: "12px",
    borderRadius: "50%",
    background: RED,
    border: "2px solid white",
    boxShadow: "0 1px 3px rgba(0,0,0,0.2)"
  }} />
              </div>)}
          </div>
        </div>
      </div>
    </div>;
};

# Conversation Nodes

Conversation nodes form the **step chain** — the ordered sequence of actions your agent takes during a call. They connect left-to-right via `control` handles.

## Start

The entry point of every conversation. The Agent node connects to Start, and Start connects to the first step.

<ConvNode label="Start" hasInputLeft={true} hasOutputRight={true} subtitle="Where the conversation begins — connect an Agent here first" icon={<polygon points="6 3 20 12 6 21 6 3"/>}>
  <ConvTextarea placeholder="Initial greeting or instruction..." rows={2} />

  <ConvBool label="Literal" sublabel="Say word-for-word" />
</ConvNode>

| Parameter   | Type    | Description                                                                |
| ----------- | ------- | -------------------------------------------------------------------------- |
| `command`   | text    | The agent's opening message                                                |
| `isLiteral` | boolean | If true, speaks the command verbatim instead of using it as an instruction |

**Connections:** Agent `output` → Start → next step `in`

***

## Burst

A fixed number of back-and-forth exchanges on a topic. Use it for structured Q\&A, data collection, or focused discussions.

<ConvNode label="Burst" subtitle="Bot leads a focused topic for a set number of rounds, then moves on to the next step" icon={<><path d="m2 9 3-3 3 3"/><path d="M13 18H7a2 2 0 0 1-2-2V6"/><path d="m22 15-3 3-3-3"/><path d="M11 6h6a2 2 0 0 1 2 2v10"/></>}>
  <ConvStepper label="Rounds" min={1} max={10} step={1} defaultVal={1} unit="x" />

  <ConvTextarea placeholder="Cover this topic..." rows={2} />
</ConvNode>

| Parameter | Type           | Description                                  |
| --------- | -------------- | -------------------------------------------- |
| `rounds`  | stepper (1–10) | Number of user/agent exchanges. Default: `1` |
| `command` | text           | Topic instruction for the LLM                |

***

## Open Talk

Unlimited free-form conversation on a topic. Use it when you don't know how many turns the user needs.

<ConvNode label="Open Talk" subtitle="Bot and user talk back and forth on a topic with no time limit" icon={<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/>}>
  <ConvTextarea placeholder="Talk freely about this topic..." rows={3} />
</ConvNode>

| Parameter | Type | Description                   |
| --------- | ---- | ----------------------------- |
| `command` | text | Topic instruction for the LLM |

<Note>
  Open Talk continues until the StepWatcher decides to advance — typically after the user signals they are done. Connect an If/Else or Categorize node after Open Talk to decide where to go next.
</Note>

***

## Timer

Conversation on a topic for a fixed duration. Advances automatically when the timer expires.

<ConvNode label="Timer" subtitle="Bot and user talk back and forth on a topic for a set time, then move on" icon={<><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></>}>
  <ConvStepper label="Duration" min={30} max={1800} step={30} defaultVal={30} unit="s" />

  <ConvTextarea placeholder="Talk about this topic..." rows={2} />
</ConvNode>

| Parameter  | Type               | Description                        |
| ---------- | ------------------ | ---------------------------------- |
| `duration` | stepper (30–1800s) | Duration in seconds. Default: `30` |
| `command`  | text               | Topic instruction for the LLM      |

***

## If/Else

Binary branch based on an LLM-evaluated condition. Requires its own LLM node connected to `llm_in`.

<ConvNode label="If/Else" subtitle="Evaluates a condition and branches the flow into True or False paths" icon={<><path d="M8 19H5c-1 0-2-1-2-2V7c0-1 1-2 2-2h3"/><path d="M16 5h3c1 0 2 1 2 2v10c0 1-1 2-2 2h-3"/><line x1="12" x2="12" y1="19" y2="5"/></>} inputs={[{id:"in",label:"Previous Step",color:"hsl(5,82%,55%)"},{id:"llm_in",label:"LLM",color:"hsl(142,71%,45%)"}]} outputs={[{id:"true",label:"True"},{id:"false",label:"False"}]}>
  <ConvTextarea label="Condition" placeholder="e.g. user confirmed their name" rows={2} />
</ConvNode>

| Parameter       | Type | Description                                                          |
| --------------- | ---- | -------------------------------------------------------------------- |
| `conditionText` | text | Condition to evaluate (e.g., "Is the user interested in upgrading?") |

**Outputs:** `true` handle and `false` handle — connect each to a different next step.

**Allowed sources:** Timer, Burst, Open Talk only (not Start).

***

## Categorize

Multi-way branch by semantic classification. Requires its own LLM node connected to `llm_in`. Dynamically generates one output handle per category.

<CategorizeNodePreview />

| Parameter    | Type             | Description                                                                               |
| ------------ | ---------------- | ----------------------------------------------------------------------------------------- |
| `command`    | text             | Classification instruction                                                                |
| `categories` | categories array | Category names (e.g., `["Sales", "Support", "Billing"]`). Default: `["Sales", "Support"]` |

**Outputs:** One `control` handle per category — connect each to a different next step.

***

## End

Terminates the conversation. The agent speaks its closing message and disconnects.

<ConvNode label="End" subtitle="Ends the conversation and hangs up the call" icon={<><circle cx="12" cy="12" r="10"/><rect width="6" height="6" x="9" y="9"/></>}>
  <ConvTextarea placeholder="Thank you for calling..." rows={2} />

  <ConvBool label="Literal" sublabel="Say word-for-word" />
</ConvNode>

| Parameter   | Type    | Description                          |
| ----------- | ------- | ------------------------------------ |
| `command`   | text    | Closing message                      |
| `isLiteral` | boolean | If true, speaks the command verbatim |

**Note:** End nodes can also connect to Webhook nodes to fire an HTTP POST before disconnecting.

***

## Transfer

Hands off to another agent or phone number. Connects to `transfer_in` on the Agent node.

| Parameter   | Type    | Description                          |
| ----------- | ------- | ------------------------------------ |
| `command`   | text    | Spoken message before the transfer   |
| `isLiteral` | boolean | If true, speaks the command verbatim |

***

## Typical flow patterns

**Simple linear:**

```
Start → Burst → End
```

**With branching:**

```
Start → Open Talk → If/Else → [true] Burst → End
                             → [false] End
```

**With triage:**

```
Start → Burst → Categorize → [Sales] Burst → End
                            → [Support] Open Talk → End
                            → [Billing] Transfer
```
