一、前端正在被"黑箱化"
说一个不太舒服但必须面对的事实:2026 年,AI 编码工具已经能独立完成完整功能模块的开发。重复的 CRUD 页面、标准的列表筛选、常规的表单增删改查——这些占日常开发量 60% 以上的工作,AI 做得比大多数初级工程师更快、更稳定。
前端岗位结构正在变成一个极端的金字塔:塔基的初级页面岗几乎消失,塔身的中级模块开发在和 AI 比价,只有塔尖的架构师、体验专家、领域专家需求暴增。
但我想说一个被广泛误解的判断:AI 替代的不是前端工程师,而是"把设计稿翻译成 HTML/CSS/组件"这个环节。 这个环节曾经是前端团队存在的理由之一,但从来不是全部。
前端的价值必须向上下游迁移——向上是产品体验设计和交互架构,向下是 Agent 产品化落地和工程化治理。而我认为,生成式 UI(Generative UI)正是这条迁移路径上最值得投入的方向。
二、什么是生成式 UI
先消除一个常见误解:生成式 UI ≠ AI 写前端代码。
传统模式下,开发者预先编写所有页面,用户在固定界面中操作。生成式 UI 的模式完全不同:
用户意图 → LLM 理解 → 输出 JSON Schema → 前端组件库渲染 → 交互式界面
↑ ↑
工具层(MCP) 组件白名单(Catalog)
调用后端 API 获取数据 保证品牌和工程规范
AI 不生成代码,而是输出结构化描述。前端从预定义的组件库中选取组件、填充数据、渲染界面。安全性、一致性、可维护性全部可控。
三种范式
生成式 UI 不是单一技术,而是一个光谱。理解这三种范式的差异,是做好选型的前提。
1. 静态生成式(Static)
AI 从预定义组件库中选择组件并填充 props,不创造新组件。视觉一致性满分,安全性满分,但灵活性受限——新需求意味着新组件,线性增长。
典型实现:Vercel AI SDK 的 streamUI + 自定义工具组件。
2. 声明式生成式(Declarative)
AI 输出结构化 JSON/YAML 规范(spec),前端用通用渲染器解释执行。灵活性和安全性的最佳平衡点。
典型实现:Vercel json-render(React/Vue/Solid)、Google A2UI 协议。
3. 开放式生成式(Open-Ended)
AI 直接生成完整 HTML/JSX 代码,前端在沙箱中渲染。理论上能生成任何界面,但视觉一致性差、XSS 风险高。
典型实现:OpenUI(W&B 开源)、MCP-UI。
我的选型建议
后管系统:声明式为主 + 静态兜底。
核心业务页面(审批、权限配置等高频操作)用静态模式保证稳定性;数据查询、报表展示、动态表单等长尾需求用声明式模式让 AI 灵活组合;开放式模式仅用于内部原型验证,不上生产。
理由很简单:后管系统对一致性和安全性的要求远高于创意自由度。你需要 AI 在可控的边界内发挥灵活性,而不是天马行空。
三、协议栈:MCP → A2A → AG-UI → A2UI
2026 年 Agent 生态已形成四层协议栈,生成式 UI 处于最上层。理解完整栈才能做对架构决策。
┌─────────────────────────────────────────────────────┐
│ A2UI │
│ 声明式 UI 描述规范(UI 长什么样) │
│ Google 2026 年开源,JSON Schema for UI │
├─────────────────────────────────────────────────────┤
│ AG-UI │
│ Agent ↔ 用户交互协议(UI 内容怎么流动) │
│ CopilotKit 发起,16 类标准事件 │
├─────────────────────────────────────────────────────┤
│ A2A │
│ Agent ↔ Agent 互操作(Agent 之间怎么协作) │
│ Google 发起,已入 Linux Foundation │
├─────────────────────────────────────────────────────┤
│ MCP │
│ Agent ↔ 工具/数据(Agent 怎么调用工具和获取上下文) │
│ Anthropic 发起,生态最成熟 │
└─────────────────────────────────────────────────────┘
简单说:MCP 解决"Agent 怎么拿数据",A2A 解决"Agent 之间怎么协作",AG-UI 解决"Agent 的输出怎么到达用户",A2UI 解决"UI 长什么样"。
AG-UI 协议——当前最关键的一层
AG-UI 是 Agent 和用户之间的交互协议,由 CopilotKit 发起,目前 LangGraph、CrewAI、Mastra、Microsoft Agent Framework、Google ADK、AWS Bedrock AgentCore 均已集成。它的核心抽象很简洁:一次 Agent 运行 = 一个 RunAgentInput 输入 → 一个事件流输出。
interface RunAgentInput {
threadId: string // 会话线程 ID
runId: string // 本次运行 ID
messages: Message[] // 完整对话历史
tools: Tool[] // 前端注册的工具定义
state: any // 共享状态(双向同步的关键)
}
16 类标准事件覆盖了所有交互场景:
| 类别 | 事件 | 作用 |
|---|---|---|
| 生命周期 | RUN_STARTED/FINISHED/ERROR |
运行起止 |
| 文本流 | TEXT_MESSAGE_START→CONTENT(×N)→END |
token 级流式文本 |
| 工具调用 | TOOL_CALL_START→ARGS(×N)→END + TOOL_CALL_RESULT |
工具调用过程可视化 |
| 状态管理 | STATE_SNAPSHOT/STATE_DELTA/MESSAGES_SNAPSHOT |
全量快照 + JSON Patch 增量 |
| 扩展 | THINKING_*、ACTIVITY_* |
推理过程、纯展示状态 |
几个设计亮点值得强调:
状态双向同步:Agent 用 STATE_SNAPSHOT 发全量状态,后续用 STATE_DELTA(JSON Patch RFC 6902)发增量,前端修改后随下一次 run 回传。大状态对象每步只耗几十字节。
ACTIVITY 事件:进度条、"正在检索第 12/40 个网页"等瞬态展示信息,只给用户看不进 LLM 上下文,避免污染共享状态。
三段式流:START→CONTENT→END,前端在 START 时创建 UI 占位,随 CONTENT 渐进填充,END 时落定。这种模式让流式渲染体验非常接近原生。
A2UI 与 AG-UI 的关系
这两个经常被混淆,实际是上下层关系:A2UI 定义"UI 长什么样"(声明式组件树规范),AG-UI 定义"UI 内容怎么在 Agent 和客户端之间流动"。实践中:Agent 用 A2UI 描述界面意图 → 通过 AG-UI 事件流推送到客户端 → 客户端渲染。
四、后管系统 6 大应用场景
讲了这么多架构,回到最核心的问题:生成式 UI 在后管系统里到底能做什么?以下是我认为是最高价值的 6 个场景。
场景 1:智能数据看板
痛点:运营、产品、管理层看同一份数据但关注点完全不同,传统做法是开发 N 个报表页面,每个角色一套视图。
生成式 UI 做法:运营说"帮我看看上个月各区域的客诉趋势,对比去年同期",AI 调用数据 API 获取结果,返回 JSON Schema → 前端渲染折线图组件 + 数据源标注 + 对比维度 + 下钻交互。
这里的关键不是"AI 画图",而是意图理解 → 数据获取 → 组件选择 → 交互绑定的完整链路。图表组件是你预定义的,数据是实时查询的,交互是你设计好的——AI 只是把"用户想要什么"翻译成了"用什么组件展示什么数据"。
场景 2:动态表单生成
痛点:后管系统表单最多也最烦——新增字段、改校验、调布局,每次变更都是开发排期。
生成式 UI 做法:管理员说"加一个患者随访记录表单,包含症状描述、严重程度、是否转诊、下次随访时间",AI 生成表单 Schema(字段类型 + 校验规则 + 联动逻辑),前端渲染完整表单。
组件白名单机制在这里发挥核心作用——AI 只能调用项目内规范组件,输出的表单天然符合设计系统和数据规范,不会出现不可维护的"AI 自由发挥"代码。
场景 3:自适应列表/表格
痛点:每个查询场景一个页面——订单列表、用户列表、工单列表……列不同、筛选不同、操作按钮不同,开发维护成本巨大。
生成式 UI 做法:用户说"导出本月所有未付款且金额超过 5000 的订单,带客户联系人信息",AI 返回表格组件 + 筛选条件 + 列配置 + 导出按钮,前端渲染完整交互表格。
一个通用表格渲染器替代了十几个定制列表页面——这不是理论,是目前json-render 方案已经能做到的事。
五、工程化落地:Vercel json-render 实战
理论讲完了,来看具体怎么落地。Vercel 的 json-render 是目前最成熟的声明式生成式 UI 开源实现,我选它作为示例。
整个流程分五步:定义 Catalog → 创建 Registry → 搭建 API 端点 → 配置 Providers → 渲染。
5.1 Catalog 定义——AI 和应用之间的契约
Catalog 告诉 AI"你有哪些组件可以用,每个组件接受什么参数"。AI 只能在这个边界内选择,不可能越界。
import { defineCatalog } from '@json-render/core';
import { z } from 'zod';
export const catalog = defineCatalog(schema, {
components: {
Card: {
props: z.object({
title: z.string(),
description: z.string().nullable(),
}),
slots: ["default"],
description: "Container card with optional title",
},
Button: {
props: z.object({
label: z.string(),
action: z.string().nullable(),
}),
description: "Clickable button that triggers an action",
},
DataTable: {
props: z.object({
columns: z.array(z.object({
field: z.string(),
title: z.string(),
})),
data: z.array(z.record(z.any())),
}),
description: "Data table with columns",
},
},
actions: {
submit: {
params: z.object({ formId: z.string() }),
description: "Submit a form",
},
navigate: {
params: z.object({ path: z.string() }),
description: "Navigate to a page",
},
},
});
几个关键点:
- 用 Zod Schema 严格定义每个组件的 props 类型,AI 输出的 JSON 必须通过 Schema 校验
catalog.prompt()自动生成系统提示词,告诉 LLM 有哪些组件可用catalog.validate(spec)验证 AI 输出是否合规catalog.jsonSchema()导出 JSON Schema 供 LLM structured output 使用
这就是"组件白名单"的工程化实现。 你的 Ant Design 组件库有多少个组件,Catalog 就定义多少个——不多不少。
5.2 Registry 实现——JSON 到 React 组件的映射
import { defineRegistry } from '@json-render/react';
import { catalog } from './catalog';
export const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) => (
<div className="p-4 border rounded-lg">
<h2 className="font-bold">{props.title}</h2>
{props.description && <p className="text-gray-600">{props.description}</p>}
{children}
</div>
),
Button: ({ props, emit }) => (
<button
className="px-4 py-2 bg-blue-500 text-white rounded"
onClick={() => emit("press")}
>
{props.label}
</button>
),
DataTable: ({ props }) => (
<table>
<thead>
<tr>{props.columns.map(col =>
<th key={col.field}>{col.title}</th>
)}</tr>
</thead>
<tbody>
{props.data.map((row, i) => (
<tr key={i}>
{props.columns.map(col =>
<td key={col.field}>{row[col.field]}</td>
)}
</tr>
))}
</tbody>
</table>
),
},
});
对于表单类组件,通过 useBoundProp hook 实现 AI ↔ 用户的双向状态同步:
Input: ({ props, bindings }) => {
const [value, setValue] = useBoundProp<string>(
props.value, bindings?.value
);
return (
<input
value={value ?? ""}
onChange={(e) => setValue(e.target.value)}
/>
);
}
不想手写 Registry 可以直接用 @json-render/shadcn,提供 36 个预定义组件,开箱即用。
5.3 流式 API 端点
import { streamText } from 'ai';
import { catalog } from '@/lib/catalog';
import { buildUserPrompt } from '@json-render/core';
export async function POST(req: Request) {
const { prompt, previousSpec } = await req.json();
// Catalog 自动生成系统提示词
const systemPrompt = catalog.prompt({
mode: "standalone",
customRules: ["Use Card as root element"],
});
// 构建用户提示词(支持多轮增量编辑)
const userPrompt = buildUserPrompt({
prompt,
currentSpec: previousSpec, // 传入已有 spec 实现增量编辑
});
const result = streamText({
model: 'anthropic/claude-sonnet-4-20250514',
system: systemPrompt,
prompt: userPrompt,
});
return result.toTextStreamResponse();
}
注意 previousSpec 参数——这意味着用户可以基于已有界面说"把表格换成卡片视图"或"加一列审批状态",AI 在已有 spec 基础上做增量修改,而不是从零生成。
整体架构
┌──────────────────────────────────────────────┐
│ 用户意图 │
└──────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ LLM(理解 + 规划) │
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ MCP 工具调用 │ │ JSON Schema 输出 │ │
│ │ (获取数据) │ │ (描述 UI 结构) │ │
│ └──────┬──────┘ └──────────┬──────────┘ │
└─────────┼──────────────────────┼─────────────┘
▼ ▼
┌──────────────────┐ ┌────────────────────────┐
│ 后端 API 层 │ │ 组件白名单(Catalog) │
│ 数据库/微服务 │ │ Table/Form/Chart/... │
└──────────────────┘ └───────────┬────────────┘
▼
┌──────────────────────────┐
│ 前端渲染层(React) │
│ 结构化 JSON → 交互界面 │
└──────────────────────────┘
六、生产环境六大失败模式
这部分是全文最有价值的内容。 Demo 里你看不到这些问题,但上线后每一个都会爆。以下来自 Metacto 等团队的生产实践总结。
失败模式 1:幻觉组件和 Props
模型渲染了一个不存在的 <ChartCompare> 组件,或给图表传了不支持的 groupBy prop。结果是空白区域、控制台报错,或更糟糕——用缓存值显示过时内容,用户看到的"正确数据"其实是三天前的。
解决方案:Schema 验证 + 白名单,没有例外。
const allowedComponents = ['Button', 'Input', 'Table', 'Card', 'Modal'];
function RenderUI({ description }: { description: UIDescription }) {
if (!allowedComponents.includes(description.name)) {
throw new Error(`不允许的组件: ${description.name}`);
}
// ...
}
失败模式 2:千篇一律的 AI 风格
所有 LLM 都训练过类似的 UI 数据,输出趋向同质化——方盒子卡片、相同配色、同一种图表布局。用户一眼就能看出"这是 AI 做的",而且会觉得粗糙。
解决方案:
- 紧凑的组件目录 + 强默认值,让模型选而非发明
- 设计系统在组件层面强制执行(Design Token 约束)
- 提供少量布局模板让模型选择
- 严禁模型输出内联样式或任意 HTML
失败模式 3:数据过时
模型用请求时刻的数据快照生成了漂亮的仪表盘,5 分钟后数据变了但 UI 不知道。用户看到的是"看起来很新但数字是旧的"仪表盘——这比不展示更糟糕,因为它摧毁信任。
解决方案:
- 必须显示时间戳:“截至 15:23”
- 提供刷新按钮
- 明确的 “as of” 标记
- 动态 UI 比静态 UI 更容易让人信任数据——所以更要标注时效性
失败模式 4:空壳按钮(Action Handler 未接线)
模型渲染了一个"审批"按钮,但没有 handler。点击:什么都没发生。更危险的场景是——模型在 props 里编造了一个 onClick,框架部分执行了它,触发了一个开发者从未测试过的动作。
解决方案:Props 白名单过滤 + 只允许已注册的 action。
const allowedProps: Record<string, Set<string>> = {
Button: new Set(['children', 'variant', 'size', 'onClick']),
Input: new Set(['value', 'placeholder', 'onChange', 'type']),
Table: new Set(['data', 'columns']),
};
function filterProps(
componentName: string,
props: Record<string, any>
) {
const allowed = allowedProps[componentName] || new Set();
return Object.fromEntries(
Object.entries(props).filter(([key]) => allowed.has(key))
);
}
核心原则:按钮只在系统已有 handler 时才存在。模型不发明按钮,只从已知的按钮中选择。
失败模式 5:无障碍退化
生成的 UI 很少能通过 WCAG 检测。模型在组合组件时不会考虑屏幕阅读器、对比度、键盘导航。
解决方案:无障碍性从组件继承,而非从组合继承。如果你的组件库本身是无障碍的,且模型不能输出内联样式或任意标记,无障碍性就能保持。这也是为什么"禁止模型自由输出 HTML"这条规则如此重要——它不仅关乎视觉一致性,还关乎可访问性。
失败模式 6:框架被淘汰
CopilotKit、Vercel AI SDK、Thesys 等框架在激烈竞争,有些活不过两年。你今天 all-in 的框架,明年可能停止维护。
解决方案:选择模式而非框架。
“组件白名单选择 + Schema 验证 + 服务端流式传输 + 确定性 handler + 已知安全回退 + 明确的 AI 生成标记”——这个模式在任何框架都能工作,也能在框架迁移时保留。
组件库通常几天就能移植;需要重写的是服务端流式层和客户端集成。所以把工程重心放在 Catalog 定义和 Registry 实现上,这两块是最稳定的资产。
七、结语:前端团队破局的关键
回到开头的问题。前端团队的机会在哪?
我的判断是:成为 Agent 产品化落地的核心力量。
理由很直接。最好的技术如果不被用户优雅地使用,就毫无价值。大模型再强,也需要有人把它的能力翻译成用户可感知、可操作、可信任的界面。而这件事需要的能力——组件库积累、交互设计判断、业务流程理解——恰好是前端团队多年沉淀的壁垒。
具体来说:
- 组件库是你的护城河。AI 只能在 Catalog 定义的边界内生成,组件库的质量直接决定了生成式 UI 的上限。
- 交互设计是你的差异化。何时流式渲染、何时弹确认、何时展示加载状态、何时降级——这些决策 AI 做不了。
- 业务理解是你的入场券。前端是最了解业务流程的工程师群体,订单/审批/权限逻辑都在前端体现。
Fortune 1000 中 78% 已部署 GenAI,但仅 28% 走出试点进入全面生产——大部分卡在哪?卡在 UI 层。谁解决了这个问题,谁就是不可替代的。
生成式 UI 不是前端的终局,但它是前端从"页面开发者"进化为"Agent 产品化工程师"的关键跳板。这个窗口期不会太长,现在入场正是时候。
评论 (0)
还没有评论,来说两句吧。