PraisonAIUI hosts A2UI v0.9 surfaces: agents push structured UI messages to a live canvas while users chat in the same window or on a full-page workspace.
For dashboard extension patterns (views, plugins, layout JSON), see Agent UI host.
Run the full example: examples/python/30-a2ui-canvas/app.py.
examples/python/30-a2ui-canvas/app.py
| Page id | Route | Purpose |
|---------|-------|---------|
| chat-canvas | /chat-canvas | Split view — chat left, live A2UI preview right |
chat-canvas
/chat-canvas
| chat | /chat | Vanilla chat (unchanged) |
chat
/chat
| canvas | /canvas | Full-page surface workspace |
canvas
/canvas
Enable the split preview with set_chat_preview. Settings appear in /ui-config.json as chat.preview: { enabled, surfaceId, width }.
set_chat_preview
/ui-config.json
chat.preview: { enabled, surfaceId, width }
Agents should call send_a2ui_messages (from praisonaiagents.tools.a2ui_tools) with A2UI message dicts:
send_a2ui_messages
praisonaiagents.tools.a2ui_tools
Wrap the tool with coerce_a2ui_tool_messages so common LLM argument shapes still work:
coerce_a2ui_tool_messages
coerce_a2ui_tool_messages accepts:
messages
components
updateComponents
createSurface
surface_id
Tool results are detected by a2ui_utils and ingested into the surface store; connected clients receive WebSocket a2ui_surface events.
a2ui_utils
a2ui_surface
| Method | Path | Behaviour |
|--------|------|-----------|
| GET | /api/surfaces | List surfaces and message counts |
GET
/api/surfaces
| GET | /api/surfaces/{id} | Surface state; 200 with { messages: [] } if not created yet (empty canvas is normal) |
/api/surfaces/{id}
{ messages: [] }
| POST | /api/surfaces/{id}/messages | Append or replace messages (replace: true in body) |
POST
/api/surfaces/{id}/messages
replace: true
| POST | /api/surfaces/{id}/actions | User action from a button/component |
/api/surfaces/{id}/actions
| DELETE | /api/surfaces/{id} | Clear surface |
DELETE
Realtime updates use the existing dashboard WebSocket (a2ui_surface payload).
Manage surfaces against a running server (--server / AIUI_SERVER, default http://127.0.0.1:8000):
--server
AIUI_SERVER
http://127.0.0.1:8000
messages.json must contain { "messages": [ ... ] }.
messages.json
{ "messages": [ ... ] }
Shared dashboard JS (lazy-loaded with chat-canvas):
| Module | Role |
|--------|------|
| surface-utils.js | Load surface, WebSocket sync, post actions |
surface-utils.js
| canvas-preview.js | Right-hand preview panel |
canvas-preview.js
| a2ui-mapper.js | Default DOM mapper (Text, Button, Column, Row, Card, …) |
a2ui-mapper.js
| plugins/views/chat-canvas.js | Composes chat + preview without modifying chat.js |
plugins/views/chat-canvas.js
chat.js
Override rendering: window.aiui.registerSurfaceRenderer("main", fn).
window.aiui.registerSurfaceRenderer("main", fn)
| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| Chat works but canvas stays empty | Agent did not call send_a2ui_messages | Strengthen instructions; wrap tool with coerce_a2ui_tool_messages |
| Tool error / invalid JSON | LLM passed wrong shape | Use coercion wrapper; include schema prompt from praisonaiagents.ui.a2ui.adapter |
praisonaiagents.ui.a2ui.adapter
| Preview shows raw JSON | Component type not in a2ui-mapper | Extend mapper or use supported components |
a2ui-mapper
| GET /api/surfaces/main empty | Normal before first push | Push via agent or aiui surface push |
GET /api/surfaces/main
aiui surface push
| Button click does nothing | No @aiui.surface_action handler | Register handler for that surface_id |
@aiui.surface_action
registerSurfaceRenderer