A flexible, touch-optimized UI system for HAL voice assistant that generates interfaces from JSON configuration.
This system allows you to define complete user interfaces in JSON without writing HTML/CSS/JavaScript. Perfect for kiosk applications and touch screens.
cd /home/hal/hal/voice-kiosk
python3 ui_demo.pyOpen browser to: http://localhost:8080/ui-demo.html
Edit ui-config.json to customize the interface.
Changes to ui-config.json take effect on page reload (F5).
{
"version": "1.0",
"initial_screen": "home",
"styles": { ... },
"screens": { ... }
}{
"type": "button",
"name": "Movies",
"style": "primary-button",
"action": {
"type": "navigate",
"target": "movies-screen"
}
}Properties:
name: Button textstyle: Named style or inline style objectaction: What happens when clicked
{
"type": "text",
"id": "status-text",
"content": "Ready",
"style": "heading"
}Properties:
content: Text to display (supports{item.name}interpolation)id: Optional ID for targeting with actionsstyle: Named style or inline style object
{
"type": "list",
"id": "movie-list",
"style": "grid-list",
"autoLoad": {
"type": "mcp",
"server": "media-server",
"tool": "list_movies",
"params": {}
},
"itemTemplate": {
"type": "card",
"components": [...]
},
"onSelect": {
"type": "popup",
"components": [...]
}
}Properties:
id: Required - used to populate liststyle: List container stylingautoLoad: Action to run on screen loaditemTemplate: How to render each itemonSelect: Action when item is clicked
{
"type": "image",
"src": "/icons/bulb.png",
"alt": "Light bulb",
"style": {
"width": "64px",
"height": "64px"
}
}{
"type": "container",
"layout": "horizontal",
"wrap": true,
"components": [
{...},
{...}
]
}Layouts:
horizontal: Flexbox rowvertical: Flexbox column
{
"type": "spacer",
"height": "20px"
}Switch to another screen:
{
"type": "navigate",
"target": "movies-screen"
}Call MCP tool and populate target:
{
"type": "mcp",
"server": "media-server",
"tool": "list_movies",
"params": {},
"target": "movie-list",
"onComplete": {
"type": "navigate",
"target": "home"
}
}Properties:
server: MCP server nametool: Tool to callparams: Parameters (supports interpolation)target: ID of element to populate with resultonComplete: Optional action to run after success
Show modal popup:
{
"type": "popup",
"title": "Confirm",
"components": [
{
"type": "text",
"content": "Are you sure?"
},
{
"type": "button",
"name": "Yes",
"action": {"type": "close_popup"}
}
]
}{
"type": "close_popup"
}Reload list data:
{
"type": "refresh_list",
"target": "device-list"
}Use {variable.property} to insert data:
{
"type": "text",
"content": "{item.name}"
}Context: {item} = current list item
{
"type": "button",
"name": "Play",
"action": {
"type": "mcp",
"server": "media-server",
"tool": "play_movie",
"params": {
"filename": "{selected.name}"
}
}
}Context: {selected} = clicked list item
Define in styles section:
{
"styles": {
"primary-button": {
"backgroundColor": "#667eea",
"fontSize": "24px",
"padding": "20px 40px"
}
}
}Use by name:
{
"type": "button",
"style": "primary-button"
}{
"type": "text",
"style": {
"fontSize": "18px",
"color": "#666"
}
}Note: Use camelCase for property names (e.g., backgroundColor, not background-color)
{
"minWidth": "88px",
"minHeight": "88px",
"touchAction": "manipulation"
}Apple recommends 44x44pt minimum (88px on retina).
touchAction: "manipulation"{
"transition": "all 0.2s"
}Renderer automatically adds scale transform on press.
{
"type": "container",
"layout": "vertical",
"components": [
{"type": "button", "name": "Option 1", "action": {...}},
{"type": "spacer", "height": "20px"},
{"type": "button", "name": "Option 2", "action": {...}}
]
}{
"type": "list",
"id": "items",
"itemTemplate": {
"components": [
{"type": "text", "content": "{item.name}"},
{
"type": "container",
"layout": "horizontal",
"components": [
{"type": "button", "name": "Edit", "action": {...}},
{"type": "button", "name": "Delete", "action": {...}}
]
}
]
}
}{
"type": "popup",
"title": "Confirm",
"components": [
{"type": "text", "content": "Delete {selected.name}?"},
{
"type": "container",
"layout": "horizontal",
"components": [
{
"type": "button",
"name": "Delete",
"action": {
"type": "mcp",
"server": "...",
"tool": "delete",
"params": {"id": "{selected.id}"},
"onComplete": {"type": "close_popup"}
}
},
{
"type": "button",
"name": "Cancel",
"action": {"type": "close_popup"}
}
]
}
]
}{
"type": "list",
"id": "items",
"autoLoad": {
"type": "mcp",
"server": "...",
"tool": "list_items"
}
}Shows "No items" until data loads.
{
"type": "mcp",
"server": "lights",
"tool": "scan_devices",
"target": "status",
"onComplete": {
"type": "mcp",
"server": "lights",
"tool": "list_devices",
"target": "device-list"
}
}First scans, then lists devices.
{
"itemTemplate": {
"type": "card",
"components": [
{"type": "image", "src": "{item.poster}"},
{"type": "text", "content": "{item.name}"},
{"type": "text", "content": "{item.year}"},
{
"type": "container",
"layout": "horizontal",
"components": [
{"type": "button", "name": "Play", "action": {...}},
{"type": "button", "name": "Info", "action": {...}}
]
}
]
}
}Check browser console (F12) for errors. Common issues:
- JSON syntax error (missing comma, quotes)
- File path incorrect
- Server not running
- Check MCP call works: look at Python console
- Check target ID matches list ID
- Check data format (should be array or parseable text)
- Named styles must be defined in
stylessection - Inline styles use camelCase
- Check browser console for CSS errors
- Ensure
touchAction: "manipulation"is set - Check button size meets minimum (88px)
- Test on actual touch device (mouse hover doesn't exist on touch)
To integrate into main voice assistant:
- Add
load_ui_config()andcall_mcp()functions - Change Eel start page to
ui-demo.html - UI will coexist with voice interface
Both systems can call MCP simultaneously.
voice-kiosk/
├── ui-config.json # UI definition
├── ui_demo.py # Standalone demo
├── web/
│ ├── ui-demo.html # HTML page
│ └── ui-renderer.js # JavaScript renderer
└── UI_CONFIG_GUIDE.md # This file
- Customize Styles: Edit colors, fonts, sizes in
stylessection - Add Screens: Create new screens for different functions
- Test on Touch: Deploy to Pi with touch screen
- Add Icons: Create
/web/icons/directory for images - Integrate Voice: Merge with
hal_voice_assistant.py
See ui-config.json for complete examples of:
- ✅ Multi-screen navigation
- ✅ MCP integration
- ✅ List selection with popups
- ✅ Action chaining
- ✅ Touch-optimized layouts
- ✅ Color pickers
- ✅ Playback controls
For questions or issues, check:
- Browser console (F12) for JavaScript errors
- Python console for MCP errors
- This guide for configuration reference