Skip to content

Commit 1696568

Browse files
bloveclaude
andauthored
docs(cockpit): add ag-ui/json-render guide (backend shared-state generative UI) (#630)
json-render shipped without a docs/guide.md (docsAssetPaths was empty), so the cockpit Docs tab had nothing for it. Add a guide covering the distinctive AG-UI shared-state pattern — backend-authored render spec in message content plus dashboard data delivered as STATE_SNAPSHOT, resolved through the spec's $state bindings — matching the streaming/tool-views/a2ui guide format. Wire docsAssetPaths so the cockpit surfaces it. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent aa82a4c commit 1696568

2 files changed

Lines changed: 123 additions & 1 deletion

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Backend-Driven Generative UI with AG-UI Shared State
2+
3+
<Summary>
4+
Let the agent author a UI layout AND stream its data over AG-UI. The agent
5+
sends a json-render spec as the assistant message — the chat lib mounts it
6+
against your `views` registry — while the dashboard's numbers arrive as
7+
**agent shared state** (AG-UI `STATE_SNAPSHOT` / `STATE_DELTA`). The spec's
8+
`$state` bindings resolve against that state, so the same layout re-renders
9+
as the backend updates the data.
10+
</Summary>
11+
12+
<Prompt>
13+
Render a backend-authored dashboard with `@threadplane/chat` over the AG-UI adapter. Register your view components in the `views` map and pass it to `<chat>`. Have the agent emit a json-render spec (with `$state` bindings) as the assistant message content, and put the data the spec binds to in the LangGraph graph state so `ag-ui-langgraph` emits it as a `STATE_SNAPSHOT`. The chat composition resolves the bindings automatically.
14+
</Prompt>
15+
16+
<Steps>
17+
<Step title="Register the view components">
18+
19+
Build a `views` registry keyed by the component types your spec will reference, and pass it to `<chat>`:
20+
21+
```typescript
22+
// json-render.component.ts
23+
import { ChatComponent, views } from '@threadplane/chat';
24+
import { injectAgent } from '@threadplane/ag-ui';
25+
import { StatCardComponent } from './views/stat-card.component';
26+
import { DashboardGridComponent } from './views/dashboard-grid.component';
27+
// …line-chart, bar-chart, data-grid, container
28+
29+
const dashboardViews = views({
30+
stat_card: StatCardComponent,
31+
dashboard_grid: DashboardGridComponent,
32+
//
33+
});
34+
```
35+
36+
```html
37+
<chat main [agent]="agent" [views]="dashboardViews" />
38+
```
39+
40+
</Step>
41+
<Step title="Configure the AG-UI provider">
42+
43+
```typescript
44+
// app.config.ts
45+
import { provideAgent } from '@threadplane/ag-ui';
46+
import { provideChat } from '@threadplane/chat';
47+
48+
export const appConfig: ApplicationConfig = {
49+
providers: [provideAgent({ url: '/agent' }), provideChat({})],
50+
};
51+
```
52+
53+
</Step>
54+
<Step title="Emit the layout spec from the agent">
55+
56+
The agent authors the layout once and returns it as JSON. A post-process node
57+
moves that payload into the assistant message content, where the chat lib's
58+
content classifier detects the leading `{` and mounts the render surface. Each
59+
data prop uses a `$state` binding rather than a literal:
60+
61+
```json
62+
{
63+
"elements": {
64+
"on_time_card": {
65+
"type": "stat_card",
66+
"props": { "label": "On-time %", "value": { "$state": "/on_time/value" } }
67+
}
68+
},
69+
"root": "..."
70+
}
71+
```
72+
73+
</Step>
74+
<Step title="Deliver the data as agent shared state">
75+
76+
This is the AG-UI-native part. Instead of pushing data through a side channel,
77+
put it in the **graph state**`ag-ui-langgraph` emits the state object as a
78+
`STATE_SNAPSHOT`, the adapter writes it to the agent's `state` signal, and the
79+
chat composition syncs it into the render store where the `$state` bindings
80+
resolve:
81+
82+
```python
83+
# graph.py — emit_state returns the accumulated tool data into state
84+
async def emit_state(state: DashboardState) -> dict:
85+
updates: dict = {}
86+
for msg in reversed(state["messages"]):
87+
if getattr(msg, "type", None) == "tool" and msg.name == "query_airline_kpis":
88+
updates.update(json.loads(msg.content)) # {on_time: {value, delta}, …}
89+
# …other data tools
90+
return updates # becomes top-level state fields → STATE_SNAPSHOT
91+
```
92+
93+
The spec binding `/on_time/value` resolves to `state.on_time.value`. Run the
94+
backend with:
95+
96+
```bash
97+
uv run uvicorn src.server:app --port 5323
98+
```
99+
100+
<Warning>
101+
A field is only visible to the frontend if it is in the graph's **output
102+
schema**`ag-ui-langgraph` filters the snapshot to output-schema keys.
103+
Declare every bound field on `DashboardState` (a plain `StateGraph(State)` uses
104+
its state schema as the output schema). Also: `ag-ui-langgraph` requires a
105+
checkpointer — the graph uses `MemorySaver` for development.
106+
</Warning>
107+
108+
</Step>
109+
</Steps>
110+
111+
<Tip>
112+
The same `views` registry powers tool-driven rendering too — a component you
113+
register here is reusable for the tool-views pattern with no changes. The only
114+
difference is where the layout and data come from: a backend spec + shared
115+
state here, versus a tool call's args/result there.
116+
</Tip>
117+
118+
<Related>
119+
- [AG-UI Tool Views](/ag-ui/core-capabilities/tool-views/overview/python) — Frontend component keyed by tool name (no spec on the wire)
120+
- [AG-UI A2UI](/ag-ui/core-capabilities/a2ui/overview/python) — Backend-authored A2UI surfaces in message content
121+
- [AG-UI Streaming](/ag-ui/core-capabilities/streaming/overview/python) — Real-time token streaming with the AG-UI adapter
122+
</Related>

cockpit/ag-ui/json-render/python/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const agUiJsonRenderPythonModule: CockpitCapabilityModule = {
3737
'cockpit/ag-ui/json-render/python/src/graph.py',
3838
'cockpit/ag-ui/json-render/python/src/server.py',
3939
],
40-
docsAssetPaths: [],
40+
docsAssetPaths: ['cockpit/ag-ui/json-render/python/docs/guide.md'],
4141
runtimeUrl: 'ag-ui/json-render',
4242
devPort: 4323,
4343
};

0 commit comments

Comments
 (0)