docs: add A2UI canvas guide and surface API references

Document chat-canvas split preview, surface CLI, coerce_a2ui_tool_messages, and empty-canvas API behaviour; fix stale 29-a2ui-canvas paths.
This commit is contained in:
MervinPraison committed 2026-06-15 18:56:15 +01:00
1 parent a433c483c1
commit 5dd3cd81e4
5 files changed
+165 -24

No files matched your search

+32
View File
@@ -227,6 +227,25 @@ aiui.set_dashboard(page_header=False) # Hide page titles
---
### `aiui.set_chat_preview(enabled, *, surface_id, width)`
Configure the **chat + canvas** split preview (`chat-canvas` page). The vanilla `chat` page is unchanged.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `enabled` | `bool` | `True` | Expose preview settings in `/ui-config.json` |
| `surface_id` | `str` | `"main"` | Default A2UI surface id for the preview panel |
| `width` | `str` | `"40%"` | CSS width of the preview column (e.g. `"38%"`, `"420px"`) |
```python
aiui.set_chat_preview(enabled=True, surface_id="main", width="38%")
aiui.set_pages(["chat-canvas", "chat", "canvas"])
```
See [A2UI canvas](../features/a2ui-canvas.md).
---
### `aiui.register_theme(name, variables)`
Register a custom theme preset. The theme becomes available in the theme picker UI and via `/api/theme`.
@@ -559,6 +578,19 @@ async def on_resume(session_id):
---
### `@aiui.surface_action(surface_id)`
Handle user actions from an A2UI surface (button clicks, form submits).
```python
@aiui.surface_action("main")
async def on_surface_action(action: dict):
# action includes component_id, action name, data, session_id
return {"status": "ok"}
```
---
## Message Functions
Async functions for sending messages to the user. Call inside callback handlers.
+128
View File
@@ -0,0 +1,128 @@
# A2UI canvas — agent-driven UI
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](agent-ui-host.md).
## Quick start
```python
import praisonaiui as aiui
aiui.set_style("dashboard")
aiui.set_chat_preview(enabled=True, surface_id="main", width="38%")
aiui.set_pages(["chat-canvas", "chat", "canvas"])
@aiui.page("agent-canvas", title="Agent Canvas", icon="🖼️")
async def agent_canvas():
return {"_surface": {"id": "main", "messages": []}}
@aiui.surface_action("main")
async def on_surface_action(action: dict):
return {"received": action}
```
Run the full example: [`examples/python/30-a2ui-canvas/app.py`](../../examples/python/30-a2ui-canvas/app.py).
## Dashboard pages
| Page id | Route | Purpose |
|---------|-------|---------|
| `chat-canvas` | `/chat-canvas` | Split view — chat left, live A2UI preview right |
| `chat` | `/chat` | Vanilla chat (unchanged) |
| `canvas` | `/canvas` | Full-page surface workspace |
Enable the split preview with [`set_chat_preview`](../api/python.md#set_chat_previewenabled-surface_id-width). Settings appear in `/ui-config.json` as `chat.preview: { enabled, surfaceId, width }`.
## Agent integration
Agents should call **`send_a2ui_messages`** (from `praisonaiagents.tools.a2ui_tools`) with A2UI message dicts:
```python
send_a2ui_messages(messages=[
{"createSurface": {"surfaceId": "main", "catalogId": "basic"}},
{"updateComponents": {"components": [
{"component": "Text", "text": {"literal": "Hello"}},
{"component": "Button", "text": {"literal": "Submit"},
"action": {"name": "handleSubmit"}},
]}},
])
```
Wrap the tool with **`coerce_a2ui_tool_messages`** so common LLM argument shapes still work:
```python
from praisonaiui.a2ui_utils import coerce_a2ui_tool_messages
@tool
def send_a2ui_messages(messages):
coerced = coerce_a2ui_tool_messages(messages, surface_id="main")
return sdk_send_a2ui(messages=coerced)
```
`coerce_a2ui_tool_messages` accepts:
- A JSON string or list of message dicts
- A dict with a `messages` key
- A bare `components` list (wrapped in `updateComponents`)
- A single component dict (wrapped in `updateComponents`)
- Missing `createSurface` (inserted automatically for the given `surface_id`)
Tool results are detected by [`a2ui_utils`](../../src/praisonaiui/a2ui_utils.py) and ingested into the surface store; connected clients receive WebSocket `a2ui_surface` events.
## HTTP API
| Method | Path | Behaviour |
|--------|------|-----------|
| `GET` | `/api/surfaces` | List surfaces and message counts |
| `GET` | `/api/surfaces/{id}` | Surface state; **200 with `{ messages: [] }`** if not created yet (empty canvas is normal) |
| `POST` | `/api/surfaces/{id}/messages` | Append or replace messages (`replace: true` in body) |
| `POST` | `/api/surfaces/{id}/actions` | User action from a button/component |
| `DELETE` | `/api/surfaces/{id}` | Clear surface |
Realtime updates use the existing dashboard WebSocket (`a2ui_surface` payload).
## CLI
Manage surfaces against a running server (`--server` / `AIUI_SERVER`, default `http://127.0.0.1:8000`):
```bash
aiui surface list
aiui surface get main
aiui surface get main --json
aiui surface push main --file messages.json
aiui surface push main --file messages.json --replace
aiui surface clear main
aiui surface status
```
`messages.json` must contain `{ "messages": [ ... ] }`.
## Frontend modules
Shared dashboard JS (lazy-loaded with `chat-canvas`):
| Module | Role |
|--------|------|
| `surface-utils.js` | Load surface, WebSocket sync, post actions |
| `canvas-preview.js` | Right-hand preview panel |
| `a2ui-mapper.js` | Default DOM mapper (Text, Button, Column, Row, Card, …) |
| `plugins/views/chat-canvas.js` | Composes chat + preview without modifying `chat.js` |
Override rendering: `window.aiui.registerSurfaceRenderer("main", fn)`.
## Troubleshooting
| 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` |
| Preview shows raw JSON | Component type not in `a2ui-mapper` | Extend mapper or use supported components |
| `GET /api/surfaces/main` empty | Normal before first push | Push via agent or `aiui surface push` |
| Button click does nothing | No `@aiui.surface_action` handler | Register handler for that `surface_id` |
## Related
- [Agent UI host](agent-ui-host.md) — `registerSurfaceRenderer`, layout tiers
- [Python API — `set_chat_preview`](../api/python.md#set_chat_previewenabled-surface_id-width)
- [Python API — `@aiui.surface_action`](../api/python.md#aiuisurface_actionsurface_id)
+2 -21
View File
@@ -98,28 +98,9 @@ async def agent_canvas():
return {"_surface": {"id": "main", "messages": []}}
```
Or use `@aiui.surface_action("main")` for button callbacks. See [`examples/python/29-a2ui-canvas/`](../../examples/python/29-a2ui-canvas/app.py).
Or use `@aiui.surface_action("main")` for button callbacks.
### Chat + Canvas same-window preview
Three dashboard modes:
| Page | Route | Purpose |
|------|-------|---------|
| `chat-canvas` | `/chat-canvas` | **Split view** — chat left, live A2UI preview right |
| `chat` | `/chat` | Vanilla chat only (unchanged) |
| `canvas` | `/canvas` | Full-page surface workspace |
Enable preview settings and list the combined page:
```python
aiui.set_chat_preview(enabled=True, surface_id="main", width="38%")
aiui.set_pages(["chat-canvas", "chat", "canvas"])
```
`/ui-config.json` exposes `chat.preview: { enabled, surfaceId, width }` for the preview panel.
Shared modules: `surface-utils.js` (render, load, WebSocket, actions), `canvas-preview.js` (preview panel), `a2ui-mapper.js` (default DOM mapper). Override rendering with `registerSurfaceRenderer("main", fn)`.
**Full guide:** [A2UI canvas](a2ui-canvas.md) — chat-canvas split view, `send_a2ui_messages`, HTTP/CLI, `coerce_a2ui_tool_messages`, troubleshooting. Example: [`examples/python/30-a2ui-canvas/`](../../examples/python/30-a2ui-canvas/app.py).
## Optional dashboard modules
+1 -1
View File
@@ -1,4 +1,4 @@
"""Example 29 — A2UI canvas and send_a2ui_messages integration.
"""Example 30 — A2UI canvas and send_a2ui_messages integration.
Run:
python app.py
+2 -2
View File
@@ -176,11 +176,11 @@ def test_send_a2ui_messages_real_tool_extra():
def test_example_app_wiring():
"""Smoke: example 29 registers agent, pages, and preview config."""
"""Smoke: example 30 registers agent, pages, and preview config."""
import sys
from pathlib import Path
example_dir = Path(__file__).resolve().parents[2] / "examples" / "python" / "29-a2ui-canvas"
example_dir = Path(__file__).resolve().parents[2] / "examples" / "python" / "30-a2ui-canvas"
if not example_dir.is_dir():
pytest.skip("example app not present")