《AI 前端实战》第 5/8 篇
下篇预告:Agent 前端状态机
前面四篇解决了:流式文本、工程化失败路径、工具调用。
下一步更激进一点:
让模型不只返回文字,还返回「可渲染的界面描述」。
这就是生成式 UI(Generative UI)。2026 年主流做法不是让模型直接吐 HTML/JS,而是:
JSON Schema 约束 → 白名单组件映射 → React 安全渲染。
你将学到
- 为什么用 JSON,而不是 HTML
- 一套最小 Schema 设计
- React Catalog(组件白名单)怎么写
- 流式增量渲染与校验
- 安全边界:防 XSS、防非法组件
一、为什么不是「模型直接写 React/HTML」
直接生成代码看起来爽,但生产上很难:
- XSS / 注入风险极高
- 样式与设计系统失控
- 无法稳定测试与权限控制
- 流式过程中半截代码不可执行
JSON 描述的好处:
- 可 Schema 校验
- 可映射到已审核组件
- 可版本化、可缓存、可 A/B
- 更适合流式「边到边渲染」
二、最小协议:UI Document
ts
type UINode =
| { type: "Card"; title: string; children: UINode[] }
| { type: "Text"; text: string }
| { type: "Button"; label: string; action: string; payload?: Record<string, unknown> }
| { type: "Table"; columns: string[]; rows: string[][] }
| { type: "Form"; fields: { name: string; label: string; kind: "text" | "number" }[]; submitAction: string };
对应 JSON Schema 思路:type 必须是枚举;未知 type 直接拒绝。
三、React Catalog:只渲染你允许的组件
tsx
const catalog = {
Card: ({ title, children }: any) => (
<section className="rounded-xl border p-4">
<h3 className="font-semibold mb-2">{title}</h3>
{children}
</section>
),
Text: ({ text }: any) => <p>{text}</p>,
Button: ({ label, action, payload, onAction }: any) => (
<button className="border rounded px-3 py-1" onClick={() => onAction?.(action, payload)}>
{label}
</button>
),
Table: ({ columns, rows }: any) => (
<table>
<thead>
<tr>{columns.map((c: string) => <th key={c}>{c}</th>)}</tr>
</thead>
<tbody>
{rows.map((r: string[], i: number) => (
<tr key={i}>{r.map((cell, j) => <td key={j}>{cell}</td>)}</tr>
))}
</tbody>
</table>
),
} as const;
function renderNode(node: any, onAction?: Function): React.ReactNode {
const Comp = catalog[node.type as keyof typeof catalog];
if (!Comp) return <div className="text-red-500">不支持的组件:{String(node.type)}</div>;
const children = (node.children || []).map((c: any, i: number) => (
<div key={i}>{renderNode(c, onAction)}</div>
));
return <Comp {...node} onAction={onAction}>{children}</Comp>;
}
原则:
- 没有 Catalog 的 type = 不渲染
- 组件 props 再做一次运行时校验(zod 等)
action只允许白名单事件名
四、和 Tool Calling 怎么配合
常见两种模式:
模式 A:模型直接输出 UI JSON
适合:表单、对比表、下一步按钮。
模式 B:工具返回数据,前端/模型再生成 UI
适合:查库结果 → Table;需要强权限的操作按钮。
推荐生产路径:
text
Tool 拿可信数据 → 模型生成 UI JSON(仅展示与次要交互) → 高风险动作仍走确认
五、流式生成式 UI:边到边渲染
模型可能一段段输出 JSON。前端策略:
- 缓冲文本,尝试
JSON.parse - 解析失败则等待更多 token
- 解析成功先
ajv/zod校验 - 校验通过再进入 Catalog 渲染
- 校验失败显示「界面协议错误」,回退纯文本
进阶:用 JSON Lines / 分片协议,避免等完整大 JSON。
六、安全清单(必须做)
- 禁止原始 HTML/JS 节点 (没有
DangerousHTML) - 禁止任意 URL 跳转(链接域名白名单)
- Button action 白名单 (不能让模型触发
deleteAll) - 文本做转义 (React 默认文本安全,仍避免
dangerouslySetInnerHTML) - 大小限制(节点数、深度、字符串长度)
- 权限二次校验(服务端)
生成式 UI 的攻击面,比普通 Chat 更大:把它当「远程组件协议」来防。
七、一个业务例子:退款助手
用户问:「我订单 123 能退吗?」
模型(在工具结果基础上)输出:
json
{
"type": "Card",
"title": "退款评估",
"children": [
{ "type": "Text", "text": "订单 123 仍在可退期内,预计 1-3 个工作日到账。" },
{
"type": "Button",
"label": "申请退款",
"action": "create_refund",
"payload": { "orderId": "123" }
}
]
}
前端:
- 渲染 Card/Text/Button
- 点击后走
create_refund确认框(第 4 篇权限模型) - 真正退款 API 在服务端鉴权执行
八、什么时候不要上生成式 UI
- 交互极简单,纯 Markdown 足够
- 设计系统不稳定,组件语义不清
- 团队还没有 Schema 校验与白名单机制
生成式 UI 是加速定制界面,不是让模型取代设计系统。
小结
生成式 UI 的可靠公式:
- Schema 约束输出
- Catalog 白名单渲染
- 流式校验再上屏
- 高风险动作仍人确认
这样,AI 才能「画界面」,而你的产品仍可控。
下篇预告 :《AI 前端实战》第 6/8 篇
Agent 前端状态机:多工具并发、Abort 与错误隔离。
系列导航: