1、在minimax平台上根据自然语言生成前端代码
https://agent.minimaxi.com/chat?id=3758586920273252
用户问题:现在有个收支数据,请给我生成对应的a2ui卡片,data={"time": "1月", "in": 1230, "out": 20},其中in为收入,out为支出,单位是元,请用a2ui协议生成代码,并提供文件下载

生成文件如下:
{
"type": "view",
"props": {
"className": "finance-card"
},
"style": {
"backgroundColor": "#FFFFFF",
"borderRadius": 12,
"padding": 16,
"flexDirection": "row",
"justifyContent": "space-between",
"alignItems": "center",
"boxShadow": "0 2px 8px rgba(0,0,0,0.05)",
"marginBottom": 12
},
"children": [
{
"type": "text",
"props": {
"text": "1月"
},
"style": {
"fontSize": 24,
"fontWeight": "bold",
"color": "#1F2937",
"flex": 1
}
},
{
"type": "view",
"style": {
"flexDirection": "column",
"alignItems": "flex-end"
},
"children": [
{
"type": "view",
"style": {
"flexDirection": "row",
"alignItems": "center",
"marginBottom": 4
},
"children": [
{
"type": "text",
"props": {
"text": "收入"
},
"style": {
"fontSize": 12,
"color": "#9CA3AF",
"marginRight": 8
}
},
{
"type": "text",
"props": {
"text": "+¥1,230"
},
"style": {
"fontSize": 16,
"fontWeight": "600",
"color": "#EF4444"
}
}
]
},
{
"type": "view",
"style": {
"flexDirection": "row",
"alignItems": "center"
},
"children": [
{
"type": "text",
"props": {
"text": "支出"
},
"style": {
"fontSize": 12,
"color": "#9CA3AF",
"marginRight": 8
}
},
{
"type": "text",
"props": {
"text": "-¥20"
},
"style": {
"fontSize": 16,
"fontWeight": "600",
"color": "#10B981"
}
}
]
}
]
}
]
}
给出的js渲染文件如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A2UI 收支卡片演示</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 400px;
}
.card-wrapper {
background: #ffffff;
border-radius: 20px;
padding: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.card-title {
text-align: center;
color: #333;
font-size: 18px;
font-weight: 600;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
/* A2UI Renderer Base Styles */
.a2ui-view {
display: flex;
flex-direction: column;
}
.a2ui-text {
display: block;
}
.a2ui-image {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
<div class="container">
<div class="card-wrapper">
<div class="card-title">A2UI 收支卡片</div>
<div id="app"></div>
</div>
</div>
<script>
// A2UI 轻量级渲染器
const A2UIRenderer = {
render: function(node, parent) {
if (!node) return;
let el;
const type = node.type || 'view';
// 根据类型创建元素
switch(type) {
case 'text':
el = document.createElement('span');
el.innerText = node.props?.text || '';
break;
case 'button':
el = document.createElement('button');
if (node.props?.text) el.innerText = node.props.text;
el.style.cssText = 'border: none; cursor: pointer; outline: none;';
break;
case 'image':
el = document.createElement('img');
el.src = node.props?.src || '';
el.alt = node.props?.alt || 'image';
break;
case 'view':
default:
el = document.createElement('div');
break;
}
// 应用样式
if (node.style) {
Object.entries(node.style).forEach(([key, value]) => {
// 需要添加px单位的属性
const pxProps = [
'width', 'height', 'fontSize', 'borderRadius',
'padding', 'margin', 'paddingTop', 'paddingBottom',
'paddingLeft', 'paddingRight', 'marginTop', 'marginBottom',
'marginRight', 'marginLeft', 'gap', 'lineHeight'
];
if (pxProps.includes(key) && typeof value === 'number') {
el.style[key] = value + 'px';
} else {
el.style[key] = value;
}
});
}
// 递归渲染子元素
if (node.children && Array.isArray(node.children)) {
node.children.forEach(child => {
this.render(child, el);
});
}
// 挂载到父元素
if (parent) {
parent.appendChild(el);
}
return el;
}
};
// 收支卡片数据
const financeCardData = {
"type": "view",
"style": {
"backgroundColor": "#FFFFFF",
"borderRadius": 12,
"padding": 16,
"flexDirection": "row",
"justifyContent": "space-between",
"alignItems": "center",
"boxShadow": "0 2px 8px rgba(0,0,0,0.05)"
},
"children": [
{
"type": "text",
"props": {
"text": "1月"
},
"style": {
"fontSize": 28,
"fontWeight": "bold",
"color": "#1F2937",
"flex": 1
}
},
{
"type": "view",
"style": {
"flexDirection": "column",
"alignItems": "flex-end"
},
"children": [
{
"type": "view",
"style": {
"flexDirection": "row",
"alignItems": "center",
"marginBottom": 8
},
"children": [
{
"type": "text",
"props": {
"text": "收入"
},
"style": {
"fontSize": 13,
"color": "#9CA3AF",
"marginRight": 12
}
},
{
"type": "text",
"props": {
"text": "+¥1,230"
},
"style": {
"fontSize": 18,
"fontWeight": "600",
"color": "#EF4444"
}
}
]
},
{
"type": "view",
"style": {
"flexDirection": "row",
"alignItems": "center"
},
"children": [
{
"type": "text",
"props": {
"text": "支出"
},
"style": {
"fontSize": 13,
"color": "#9CA3AF",
"marginRight": 12
}
},
{
"type": "text",
"props": {
"text": "-¥20"
},
"style": {
"fontSize": 18,
"fontWeight": "600",
"color": "#10B981"
}
}
]
}
]
}
]
};
// 页面加载完成后渲染卡片
window.addEventListener('DOMContentLoaded', function() {
const app = document.getElementById('app');
A2UIRenderer.render(financeCardData, app);
});
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.inland-security-footer {
position: fixed;
z-index: 10;
bottom: 0;
right: 0;
width: fit-content;
display: flex;
justify-content: flex-end;
}
.tooltip-container {
position: relative;
}
.trigger-content {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
padding: 8px;
background-color: transparent;
border-radius: 8px;
cursor: pointer;
}
.minimax-link {
display: flex;
align-items: center;
cursor: pointer;
font-weight: 300;
color: #adadad;
text-decoration: none;
}
.minimax-link:visited {
color: #adadad;
}
.minimax-link:hover {
color: #666666;
}
.tooltip-content {
position: absolute;
bottom: 100%;
right: 0;
display: none;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 8px;
padding-bottom: 16px; /* 8px 内容间距 + 8px 用于鼠标移动的透明区域 */
font-size: 12px;
line-height: 17px;
background-color: #fafafa;
border-radius: 12px;
white-space: nowrap;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
color: #171717;
}
/* 创建透明的连接区域,让鼠标可以平滑移动到 tooltip */
.tooltip-content::after {
content: '';
position: absolute;
bottom: -8px;
left: 0;
right: 0;
height: 16px;
}
.tooltip-container:hover .tooltip-content {
display: flex;
}
.info-section {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
}
.beian-link {
display: flex;
align-items: center;
text-decoration: none;
color: #171717;
}
.beian-icon {
width: 14px;
height: 14px;
margin-right: 2px;
}
.underline-text {
color: #171717;
text-decoration: underline;
}
.underline-text:hover {
color: #000000;
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
.trigger-content {
background-color: transparent;
}
.minimax-link,
.minimax-link:visited {
color: #666666;
}
.minimax-link:hover {
color: #adadad;
}
.tooltip-content {
background-color: #262626;
color: #ededed;
}
.beian-link,
.underline-text {
color: #ededed;
}
.underline-text:hover {
color: #ffffff;
}
}
</style>
<div class="inland-security-footer">
<div class="tooltip-container">
<div class="trigger-content">
<a href="https://www.minimaxi.com/" target="_blank" rel="noreferrer" class="minimax-link">
<span>© 2026 MiniMax</span>
</a>
</div>
<div class="tooltip-content">
<section class="info-section">
<span>上海稀宇科技有限公司</span>
</section>
<section class="info-section">
<a class="beian-link" href="https://www.beian.gov.cn/portal/registerSystemInfo?recordcode=31010402010179" target="_blank" rel="noreferrer">
<img alt="hailuo-beian-icon" src="https://cdn.hailuoai.com/hailuo-video-web/public_assets/223037d3-79fd-4db0-a456-749f38a6a14b.png" class="beian-icon">
<span class="underline-text">沪公网安备31010402010179号</span>
</a>
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noreferrer" class="underline-text">
沪ICP备2023003282号-38
</a>
</section>
<section class="info-section">
<span>模型名称:MiniMax</span>
<span>备案号:Shanghai-MiniMax-202505230046</span>
</section>
<section class="info-section">
<a href="https://agent.minimaxi.com/doc/zh/terms-of-service.html" target="_blank" rel="noreferrer" class="underline-text">
用户协议
</a>
<a href="https://agent.minimaxi.com/doc/zh/privacy-policy.html" target="_blank" rel="noreferrer" class="underline-text">
隐私政策
</a>
</section>
</div>
</div>
</div>
</body>
</html>
通用方案如下:
前端集成A2UI主要有以下几种方式,根据你的技术栈和需求选择:
一、使用CopilotKit(推荐方案)
CopilotKit是目前最成熟的前端A2UI集成方案,提供了完整的React组件和渲染器。
1. 安装依赖
# 使用npm
npm install @copilotkit/react-core @copilotkit/react-ui @a2ui/react
# 使用yarn
yarn add @copilotkit/react-core @copilotkit/react-ui @a2ui/react
# 使用pnpm
pnpm add @copilotkit/react-core @copilotkit/react-ui @a2ui/react
2. 基础集成代码
// app.jsx 或 main.jsx
import { CopilotKit } from '@copilotkit/react-core';
import { CopilotSidebar } from '@copilotkit/react-ui';
import { createA2UIMessageRenderer } from '@a2ui/react';
// 创建A2UI渲染器
const a2uiRenderer = createA2UIMessageRenderer({
theme: {
primaryColor: '#0070f3',
// 其他主题配置
}
});
function App() {
return (
<CopilotKit
runtimeUrl="/api/copilotkit" // 后端API路由
renderActivityMessages={[a2uiRenderer]} // 注册A2UI渲染器
>
<CopilotSidebar
instructions="你是一个餐厅助手,可以帮助用户查找和预订餐厅。"
defaultOpen={true}
/>
{/* 你的应用内容 */}
</CopilotKit>
);
}
3. 配置API路由(Next.js示例)
// app/api/copilotkit/route.js
import { CopilotBackend } from '@copilotkit/backend';
import { A2AClient } from '@a2ui/a2a-client';
export async function POST(request) {
const backend = new CopilotBackend({
actions: [
// 你的自定义操作
],
});
// 连接到A2A后端
const a2aClient = new A2AClient({
endpoint: 'http://localhost:10002', // A2A Agent地址
});
return backend.stream(request, {
onMessage: async (message) => {
// 将消息转发到A2A Agent
const response = await a2aClient.sendMessage(message);
return response;
},
});
}
二、使用官方渲染器
A2UI官方提供了多种前端框架的渲染器:
1. Lit渲染器(Web Components)
npm install @a2ui/lit
// 使用Lit渲染器
import { A2UIRenderer } from '@a2ui/lit';
class MyElement extends HTMLElement {
constructor() {
super();
this.renderer = new A2UIRenderer();
}
connectedCallback() {
// 连接到A2A流
const eventSource = new EventSource('/a2a-stream');
eventSource.onmessage = (event) => {
const a2uiMessage = JSON.parse(event.data);
this.renderer.render(a2uiMessage, this);
};
}
}
customElements.define('my-a2ui-element', MyElement);
2. Angular渲染器
npm install @a2ui/angular
// app.module.ts
import { A2UIModule } from '@a2ui/angular';
@NgModule({
imports: [
A2UIModule,
// 其他模块
]
})
export class AppModule { }
// 组件中使用
@Component({
selector: 'app-a2ui',
template: `
<a2ui-surface
[surfaceId]="'main'"
(action)="onAction($event)">
</a2ui-surface>
`
})
export class A2UIComponent {
onAction(event: any) {
console.log('A2UI Action:', event);
// 处理用户交互
}
}
3. Flutter渲染器(移动/桌面)
# pubspec.yaml
dependencies:
a2ui_flutter: ^0.1.0
import 'package:a2ui_flutter/a2ui_flutter.dart';
class A2UIPage extends StatefulWidget {
@override
_A2UIPageState createState() => _A2UIPageState();
}
class _A2UIPageState extends State<A2UIPage> {
final A2UIController _controller = A2UIController();
@override
void initState() {
super.initState();
_connectToAgent();
}
Future<void> _connectToAgent() async {
final stream = await connectToA2AAgent('ws://localhost:8080');
stream.listen((message) {
_controller.handleMessage(message);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: A2UIWidget(
controller: _controller,
onAction: (action) {
// 处理用户交互
print('Action: $action');
},
),
);
}
}
三、自定义集成方案
如果你需要更灵活的集成,可以手动处理A2UI协议:
1. 基本消息处理
// A2UI消息处理器
class A2UIHandler {
constructor() {
this.components = new Map();
this.dataModel = new Map();
}
// 处理A2UI消息流
async handleStream(stream) {
const reader = stream.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (line.trim()) {
try {
const message = JSON.parse(line);
this.processMessage(message);
} catch (error) {
console.error('解析A2UI消息失败:', error);
}
}
}
}
}
// 处理不同类型的A2UI消息
processMessage(message) {
if (message.beginRendering) {
this.beginRendering(message.beginRendering);
} else if (message.surfaceUpdate) {
this.surfaceUpdate(message.surfaceUpdate);
} else if (message.dataModelUpdate) {
this.dataModelUpdate(message.dataModelUpdate);
}
}
beginRendering(config) {
const { surfaceId, root, styles } = config;
// 初始化渲染表面
console.log(`开始渲染表面: ${surfaceId}, 根组件: ${root}`);
this.applyStyles(styles);
}
surfaceUpdate(update) {
const { surfaceId, components } = update;
components.forEach(component => {
const { id, component: componentDef } = component;
// 根据组件类型渲染
if (componentDef.Button) {
this.renderButton(id, componentDef.Button);
} else if (componentDef.Card) {
this.renderCard(id, componentDef.Card);
} else if (componentDef.List) {
this.renderList(id, componentDef.List);
}
// 处理其他组件类型...
});
}
dataModelUpdate(update) {
const { surfaceId, contents } = update;
contents.forEach(item => {
this.dataModel.set(item.key, item.value);
});
// 更新绑定数据的组件
this.updateDataBindings();
}
renderButton(id, config) {
const button = document.createElement('button');
button.id = id;
button.textContent = config.label || '按钮';
button.className = `btn btn-${config.variant || 'primary'}`;
if (config.onClick) {
button.addEventListener('click', () => {
this.handleAction(config.onClick);
});
}
document.getElementById('a2ui-container').appendChild(button);
this.components.set(id, button);
}
handleAction(action) {
// 发送用户交互回Agent
fetch('/api/a2ui-action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(action)
});
}
}
2. 连接到A2A Agent
// 连接到A2A Agent
async function connectToA2AAgent(endpoint) {
// 使用Server-Sent Events (SSE)
const eventSource = new EventSource(`${endpoint}/stream`);
const handler = new A2UIHandler();
eventSource.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
handler.processMessage(message);
} catch (error) {
console.error('处理消息失败:', error);
}
};
eventSource.onerror = (error) => {
console.error('SSE连接错误:', error);
// 重连逻辑
};
return {
close: () => eventSource.close(),
send: (action) => {
// 发送用户操作
return fetch(`${endpoint}/action`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(action)
});
}
};
}
// 使用WebSocket
function connectWithWebSocket(endpoint) {
const ws = new WebSocket(endpoint);
const handler = new A2UIHandler();
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
handler.processMessage(message);
};
ws.onopen = () => {
console.log('A2UI WebSocket连接已建立');
};
return ws;
}
四、组件库配置
1. 自定义组件Catalog
// catalog.json - 定义可用的组件类型
{
"components": {
"Button": {
"properties": {
"label": { "type": "string" },
"variant": {
"type": "string",
"enum": ["primary", "secondary", "outline"]
},
"onClick": { "type": "object" }
}
},
"Card": {
"properties": {
"title": { "type": "string" },
"content": { "type": "string" },
"imageUrl": { "type": "string" }
}
},
"List": {
"properties": {
"direction": {
"type": "string",
"enum": ["vertical", "horizontal"]
},
"items": { "type": "array" }
}
}
}
}
2. 主题配置
// theme.js
export const defaultTheme = {
colors: {
primary: '#0070f3',
secondary: '#7928ca',
background: '#ffffff',
text: '#000000'
},
spacing: {
small: '8px',
medium: '16px',
large: '24px'
},
typography: {
fontFamily: 'system-ui, sans-serif',
fontSize: {
small: '12px',
medium: '14px',
large: '16px'
}
}
};
// 在渲染器中使用
const renderer = createA2UIMessageRenderer({
theme: defaultTheme,
components: customComponents
});
五、完整集成示例
React + CopilotKit + A2UI完整示例
// App.jsx
import React, { useState } from 'react';
import { CopilotKit, useCopilotChat } from '@copilotkit/react-core';
import { CopilotPopup } from '@copilotkit/react-ui';
import { createA2UIMessageRenderer } from '@a2ui/react';
const a2uiRenderer = createA2UIMessageRenderer({
theme: {
colors: {
primary: '#0070f3',
background: '#f5f5f5'
}
},
onAction: (action) => {
console.log('A2UI Action:', action);
// 处理用户交互
if (action.type === 'submit_form') {
// 提交表单逻辑
}
}
});
function RestaurantFinder() {
const { messages, input, setInput, submitMessage } = useCopilotChat();
return (
<div className="app">
<CopilotPopup
instructions="你是一个餐厅助手,可以帮助用户查找、比较和预订餐厅。"
defaultOpen={true}
renderActivityMessages={[a2uiRenderer]}
/>
<div className="content">
<h1>餐厅查找助手</h1>
<div className="chat-container">
{messages.map((msg, idx) => (
<div key={idx} className={`message ${msg.role}`}>
{msg.content}
</div>
))}
</div>
<div className="input-area">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="输入你的需求,如:找5家北京的中餐厅"
onKeyPress={(e) => e.key === 'Enter' && submitMessage()}
/>
<button onClick={submitMessage}>发送</button>
</div>
</div>
</div>
);
}
function App() {
return (
<CopilotKit
runtimeUrl="/api/copilotkit"
publicApiKey={process.env.COPILOTKIT_API_KEY}
>
<RestaurantFinder />
</CopilotKit>
);
}
export default App;
六、最佳实践建议
-
渐进式集成:先从简单的组件开始,逐步增加复杂度
-
错误处理:确保A2UI消息解析失败时有降级方案
-
性能优化:使用虚拟列表处理大量数据,实现增量更新
-
样式隔离:使用Shadow DOM或CSS-in-JS避免样式冲突
-
状态管理:将A2UI状态与你的应用状态同步
-
测试策略:编写A2UI消息的单元测试和集成测试
七、调试工具
1. A2UI Composer(可视化设计工具)
访问:https://a2ui-editor.copilotkit.ai/
2. 浏览器开发者工具
// 在控制台调试
window.a2uiDebug = {
logMessages: true,
showWireframe: false,
validateSchema: true
};
3. 消息监控
// 监控A2UI消息流
const originalFetch = window.fetch;
window.fetch = function(...args) {
if (args[0].includes('/a2ui')) {
console.log('A2UI请求:', args);
return originalFetch.apply(this, args).then(response => {
console.log('A2UI响应:', response);
return response;
});
}
return originalFetch.apply(this, args);
};
八、常见问题解决
-
消息解析失败:检查JSON格式,确保符合A2UI Schema
-
组件未渲染:确认组件已在Catalog中注册
-
样式不生效:检查主题配置和CSS加载顺序
-
事件不触发:验证action配置和事件绑定
-
性能问题:使用增量更新,避免全量重新渲染
总结:前端集成A2UI的核心是选择合适的渲染器(推荐CopilotKit),正确处理A2UI消息流,并实现组件渲染和用户交互的闭环。根据你的技术栈选择对应的集成方案,从简单示例开始逐步完善。