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