AkurAI Build
Menu

Public repository / 91bd2a37654909c5a3aef6ddd614991bd378ccb8

AkurAI-Framework / site/content/docs/ai-input.md

Latest change 63ae119f1bdd5ddffd3f9a2b3b3216aa9d21449b - feat(ai): Input component batch — option_list, parameter_slider, preferences_panel, question_flow by Ólafur Búi Ólafsson

# AI Input components

Four interactive **tool-UI input components** in the `Input` category. Each is a
vanilla ES module at `site/frontend/ai/components/input/<name>.js`. The runtime
matches a tool-call payload against `def.match`, renders `def.render(payload, ctx)`,
and the user's interaction flows back to the assistant as a receipt via
`ctx.sendReceipt(receipt)`. A `CustomEvent('toolui:input', { detail: receipt })`
is also emitted on the root element as a fallback for listeners that don't use `ctx`.

All components are:
- **Pure vanilla DOM** — no imports, no framework, no build step.
- **XSS-safe** — user-supplied strings are always written via `.textContent`.
- **Reusable** — they use only existing `styles.css` design-system classes.

---

## Option List

**File:** `site/frontend/ai/components/input/option_list.js`  
**`def.name`:** `option_list`

Presents a list of labelled options for the user to pick from. Supports both
single-select (radio) and multi-select (checkbox) modes. A **Confirm** button sends
the receipt.

### Payload

```json
{
  "prompt": "Which deployment target?",
  "options": [
    { "id": "bare",   "label": "Bare metal", "description": "Full control, no overhead" },
    { "id": "docker", "label": "Docker",     "description": "Containerised, portable" },
    { "id": "k8s",    "label": "Kubernetes", "description": "Orchestrated at scale" }
  ],
  "multi": false
}
```

| Field | Type | Required | Notes |
|---|---|---|---|
| `prompt` | `string` | no | Displayed as a heading above the list. |
| `options` | `{id, label, description?}[]` | yes | At least one option required (`match` gate). |
| `multi` | `boolean` | no | `false` → radio (single); `true` → checkboxes. Default `false`. |

### Receipt

```json
{ "component": "option_list", "selected": "docker" }
```

| Field | Value when `multi: false` | Value when `multi: true` |
|---|---|---|
| `selected` | `string \| null` — the chosen `id`, or `null` if none | `string[]` — all chosen `id`s (may be empty) |

---

## Parameter Slider

**File:** `site/frontend/ai/components/input/parameter_slider.js`  
**`def.name`:** `parameter_slider`

A labelled range slider with a live badge showing the current value. An **Apply**
button sends the receipt.

### Payload

```json
{
  "label": "Temperature",
  "min": 0,
  "max": 2,
  "step": 0.1,
  "value": 0.7,
  "unit": ""
}
```

| Field | Type | Required | Notes |
|---|---|---|---|
| `label` | `string` | no | Displayed to the left of the value badge. |
| `min` | `number` | yes | Slider minimum. |
| `max` | `number` | yes | Slider maximum. |
| `step` | `number` | no | Slider step size. Default `1`. |
| `value` | `number` | yes | Initial value (must be between `min` and `max`). |
| `unit` | `string` | no | Appended to the displayed value, e.g. `"ms"`, `"°C"`. Default `""`. |

### Receipt

```json
{ "component": "parameter_slider", "value": 1.2 }
```

| Field | Type | Notes |
|---|---|---|
| `value` | `number` | The slider's value at the moment the user clicked Apply. |

---

## Preferences Panel

**File:** `site/frontend/ai/components/input/preferences_panel.js`  
**`def.name`:** `preferences_panel`

A settings form grouped by section. Each field can be a **toggle** (switch),
**select** (dropdown), or **text** (free-text input). A **Save preferences** button
sends all field values at once.

### Payload

```json
{
  "groups": [
    {
      "label": "Appearance",
      "fields": [
        { "key": "dark_mode", "type": "toggle", "label": "Dark mode",      "value": true },
        { "key": "compact",   "type": "toggle", "label": "Compact layout", "value": false }
      ]
    },
    {
      "label": "Locale",
      "fields": [
        {
          "key": "language", "type": "select", "label": "Language", "value": "en",
          "options": [{ "id": "en", "label": "English" }, { "id": "is", "label": "Íslenska" }]
        },
        { "key": "display_name", "type": "text", "label": "Display name", "value": "" }
      ]
    }
  ]
}
```

| Field | Type | Required | Notes |
|---|---|---|---|
| `groups` | `Group[]` | yes | At least one group required (`match` gate). |
| `groups[].label` | `string` | no | Section heading. |
| `groups[].fields` | `Field[]` | yes | One or more fields per group. |
| `field.key` | `string` | yes | Receipt key; must be unique across all groups. |
| `field.type` | `'toggle' \| 'select' \| 'text'` | yes | Controls rendered widget. |
| `field.label` | `string` | no | Human-readable label. Falls back to `key`. |
| `field.value` | any | no | Initial value. Toggles default `false`; others default `""`. |
| `field.options` | `{id,label}[] \| string[]` | select only | Dropdown choices. |
| `field.placeholder` | `string` | no | Hint text for `type: 'text'` inputs. |

### Receipt

```json
{
  "component": "preferences_panel",
  "values": {
    "dark_mode": true,
    "compact": false,
    "language": "is",
    "display_name": "Ólafur"
  }
}
```

| Field | Type | Notes |
|---|---|---|
| `values` | `{ [key]: boolean \| string }` | Flat map of all field keys to their current values at save time. Toggles are `boolean`; select and text are `string`. |

---

## Question Flow

**File:** `site/frontend/ai/components/input/question_flow.js`  
**`def.name`:** `question_flow`

A step-by-step wizard. Renders one question at a time with **Back / Next** navigation
and a progress bar. The user steps through all questions; pressing **Finish** on the
last step sends the receipt.

### Payload

```json
{
  "questions": [
    {
      "id": "role",
      "prompt": "What is your primary role?",
      "type": "single",
      "options": [
        { "id": "dev",    "label": "Developer" },
        { "id": "design", "label": "Designer" },
        { "id": "pm",     "label": "Product manager" }
      ]
    },
    {
      "id": "tools",
      "prompt": "Which tools do you use regularly?",
      "type": "multi",
      "options": [
        { "id": "vscode", "label": "VS Code" },
        { "id": "figma",  "label": "Figma" },
        { "id": "linear", "label": "Linear" }
      ]
    },
    {
      "id": "goal",
      "prompt": "What are you trying to build?",
      "type": "text"
    }
  ]
}
```

| Field | Type | Required | Notes |
|---|---|---|---|
| `questions` | `Question[]` | yes | At least one question required (`match` gate). |
| `question.id` | `string` | yes | Used as the key in the receipt `answers` object. Must be unique. |
| `question.prompt` | `string` | yes | The question text displayed to the user. |
| `question.type` | `'single' \| 'multi' \| 'text'` | yes | `single` → radio buttons; `multi` → checkboxes; `text` → text input. |
| `question.options` | `{id,label}[] \| string[]` | single/multi only | The choices to display. |

Answers are preserved when navigating back, so the user can revise without starting
over.

### Receipt

```json
{
  "component": "question_flow",
  "answers": {
    "role": "dev",
    "tools": ["vscode", "figma"],
    "goal": "A self-hosted AI assistant for my team."
  }
}
```

| Field | Type | Notes |
|---|---|---|
| `answers` | `{ [id]: value }` | Keyed by `question.id`. `single` and `text` answers are `string \| null`; `multi` answers are `string[]`. |