mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
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:
1 parent
a433c483c1
commit
5dd3cd81e4
5 files changed
+165
-24
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
@@ -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,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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
Reference in new issue
Block a user