A2UI 完整学习指南(含 Java 后端 + 前端实战示例)
这是可直接运行的完整版教程,包含:
- A2UI 核心知识(精简版)
- Java 后端:生成 A2UI 流式 JSON 接口(Spring Boot)
- 前端:原生 JS / Vue / React 三选一渲染 A2UI
- 完整交互流程(用户请求→Java生成UI→前端渲染→交互回传)
一、A2UI 核心速览(必看)
1. 是什么
- Agent-to-UI :AI 智能体用 JSON 描述界面 ,前端原生渲染
- 安全:不执行代码,只渲染可信组件
- 跨平台:Java 后端生成一份 JSON,Web/安卓/iOS/桌面都能渲染
2. 核心结构
beginRendering:开始渲染界面surfaceUpdate:更新 UI 组件(按钮、输入框、卡片等)dataModelUpdate:更新数据(自动同步到界面)- 传输格式:JSONL(每行一个 JSON,流式输出)
二、Java 后端实战(Spring Boot + A2UI 流式接口)
功能
Java 接口直接输出 A2UI 流式 JSON,前端 SSE 接收并渲染界面。
1. pom.xml 依赖
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
2. Java 控制器(生成 A2UI 流)
java
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.time.Duration;
@RestController
public class A2uiController {
// 流式返回 A2UI JSONL
@GetMapping(value = "/a2ui/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> a2uiStream() {
return Flux.interval(Duration.ofMillis(300))
.take(3)
.map(index -> switch (index.intValue()) {
// 1. 开始渲染
case 0 -> """
{"beginRendering":{"surfaceId":"booking"}}
""";
// 2. 渲染界面组件
case 1 -> """
{"surfaceUpdate":{"surfaceId":"booking","components":[{"id":"title","component":{"Text":{"text":"餐厅预订","style":{"fontSize":20,"fontWeight":"bold"}}}},{"id":"row","component":{"Row":{"children":["label","input"],"gap":12}}},{"id":"label","component":{"Text":{"text":"人数:"}}},{"id":"input","component":{"TextField":{"placeholder":"请输入人数","bind":"/people"}}},{"id":"btn","component":{"Button":{"text":"确认预订","action":{"type":"submit","value":"booking"}}}}]}}
""";
// 3. 初始化数据
case 2 -> """
{"dataModelUpdate":{"data":{"people":2}}}
""";
default -> "";
})
.filter(s -> !s.isEmpty());
}
// 接收前端交互事件
@GetMapping("/a2ui/action")
public String receiveAction(String data) {
System.out.println("前端用户操作:" + data);
return "success";
}
}
3. 运行
启动 Spring Boot,访问:
http://localhost:8080/a2ui/stream
会看到 A2UI 流式 JSON 输出,前端可直接接收渲染。
三、前端渲染 A2UI(3 种任选)
方案 1:原生 JS + SSE(最简单,无框架)
直接复制到 HTML 即可运行
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>A2UI 渲染</title>
<style>
body{padding:20px;max-width:400px;margin:0 auto}
.row{display:flex;gap:12px;align-items:center;margin:10px 0}
button{padding:8px 16px;background:#007bff;color:white;border:none;border-radius:4px}
input{padding:6px;width:100px}
</style>
</head>
<body>
<div id="root"></div>
<script>
const root = document.getElementById('root');
const dataModel = { people: 2 };
// 1. 连接 SSE 接收 A2UI 流
const es = new EventSource('http://localhost:8080/a2ui/stream');
es.onmessage = e => {
const msg = JSON.parse(e.data);
handleA2UIMessage(msg);
};
// 2. 处理 A2UI 消息
function handleA2UIMessage(msg) {
if (msg.beginRendering) {
root.innerHTML = `<div id="${msg.beginRendering.surfaceId}"></div>`;
}
if (msg.surfaceUpdate) {
renderComponents(msg.surfaceUpdate.components);
}
if (msg.dataModelUpdate) {
Object.assign(dataModel, msg.dataModelUpdate.data);
renderComponents(); // 数据更新重渲染
}
}
// 3. 渲染 A2UI 组件
function renderComponents(components = []) {
const surface = document.getElementById('booking');
if(!surface) return;
surface.innerHTML = '';
components.forEach(comp => {
const key = Object.keys(comp.component)[0];
const props = comp.component[key];
if(key === 'Text'){
const el = document.createElement('div');
el.innerText = props.text;
el.style.fontSize = props.style?.fontSize + 'px';
el.style.fontWeight = props.style?.fontWeight;
surface.appendChild(el);
}
if(key === 'Row'){
const el = document.createElement('div');
el.className = 'row';
surface.appendChild(el);
}
if(key === 'TextField'){
const el = document.createElement('input');
el.placeholder = props.placeholder;
el.value = dataModel[props.bind.replace('/','')];
el.oninput = e => {
dataModel.people = e.target.value;
};
surface.appendChild(el);
}
if(key === 'Button'){
const el = document.createElement('button');
el.innerText = props.text;
el.onclick = () => {
fetch(`http://localhost:8080/a2ui/action?data=${JSON.stringify(dataModel)}`);
alert('提交成功:' + JSON.stringify(dataModel));
};
surface.appendChild(el);
}
});
}
</script>
</body>
</html>
方案 2:Vue3 渲染 A2UI(推荐企业级)
vue
<template>
<div id="booking" style="padding:20px;max-width:400px">
<div v-for="comp in components" :key="comp.id">
<div v-if="comp.component.Text" :style="comp.component.Text.style">
{{ comp.component.Text.text }}
</div>
<div class="row" v-if="comp.component.Row" style="display:flex;gap:12px;margin:10px 0">
<component :is="renderChild(id)" v-for="id in comp.component.Row.children" :key="id" />
</div>
<input v-if="comp.component.TextField" v-model="dataModel.people" placeholder="输入人数" />
<button v-if="comp.component.Button" @click="submit">
{{ comp.component.Button.text }}
</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const components = ref([]);
const dataModel = ref({ people: 2 });
onMounted(() => {
const es = new EventSource('http://localhost:8080/a2ui/stream');
es.onmessage = e => {
const msg = JSON.parse(e.data);
if(msg.surfaceUpdate) components.value = msg.surfaceUpdate.components;
if(msg.dataModelUpdate) dataModel.value = msg.dataModelUpdate.data;
};
});
const submit = () => {
fetch('http://localhost:8080/a2ui/action?data=' + JSON.stringify(dataModel.value));
alert('提交:' + JSON.stringify(dataModel.value));
};
</script>
方案 3:React 渲染 A2UI
jsx
import { useEffect, useState } from 'react';
function A2uiRender() {
const [components, setComponents] = useState([]);
const [data, setData] = useState({ people: 2 });
useEffect(() => {
const es = new EventSource('http://localhost:8080/a2ui/stream');
es.onmessage = e => {
const msg = JSON.parse(e.data);
if (msg.surfaceUpdate) setComponents(msg.surfaceUpdate.components);
if (msg.dataModelUpdate) setData(msg.dataModelUpdate.data);
};
}, []);
const submit = () => {
fetch('http://localhost:8080/a2ui/action?data=' + JSON.stringify(data));
alert('提交:' + JSON.stringify(data));
};
return (
<div style={{ padding: 20, maxWidth: 400 }}>
{components.map(comp => (
<div key={comp.id}>
{comp.component.Text && <div style={comp.component.Text.style}>{comp.component.Text.text}</div>}
{comp.component.Row && <div style={{ display: 'flex', gap: 12, margin: '10px 0' }}></div>}
{comp.component.TextField && <input value={data.people} onChange={e => setData({...data, people:e.target.value})} />}
{comp.component.Button && <button onClick={submit}>{comp.component.Button.text}</button>}
</div>
))}
</div>
);
}
export default A2uiRender;
四、完整运行流程
- 启动 Java Spring Boot 后端
- 打开前端 HTML / Vue / React 页面
- 前端自动接收 A2UI 流
- 界面自动渲染:
- 标题
- 人数输入框(默认 2 人)
- 提交按钮
- 点击按钮 → 数据回传给 Java 后端
五、A2UI 常用组件(Java 可直接生成)
json
// 文本
{"Text":{"text":"你好"}}
// 按钮
{"Button":{"text":"确认","action":{"type":"submit"}}}
// 输入框
{"TextField":{"bind":"/name"}}
// 布局
{"Row":{"children":["id1","id2"]}}
{"Column":{"children":["id1","id2"]}}
// 卡片
{"Card":{"children":["title","content"]}}
六、学习总结
- A2UI = 界面协议,不是框架
- Java 后端:生成 JSONL 流
- 前端:解析 JSON → 渲染原生界面
- 优势:安全、跨平台、AI 易生成、体验原生