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:


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

{
  "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

{ "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 ids (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

{
  "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

{ "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

{
  "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

{
  "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

{
  "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

{
  "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[]. |