ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

生成式界面交付前,先把可编辑边界写进组件

生成式界面交付前,先把可编辑边界写进组件 生成式界面交付前先把可编辑边界写进组件生成式 UI 的需求评审通常会先讨论“模型能生成什么”却容易遗漏“前端允许渲染什么”。模型输出字段或组件类型一旦偏离约定页面不该靠临时兼容代码猜测含义。更稳妥的做法是把 JSON Schema 作为产品、算法和前端之间的契约模型生成结构化描述前端只渲染通过校验且在组件白名单内的内容。校验失败时展示受控的降级界面并保留必要的诊断信息。生成式 UI 的渲染管线与防污染隔离生成式 UI 的本质是把 LLM 充当“动态布局与配置生成器”而前端提供“确定性受控组件库”。让模型直接输出 HTML 或 JSX 会扩大 XSS 和样式失控的风险。较常见的边界是模型只输出 JSON 描述树前端用 Zod 校验、组件注册表映射并为每个组件的 Props 设置更细的 schema。Schema 校验不是全部安全措施服务端仍应对输入、权限和数据访问做校验。在这个管线中算法/产品责任确保 LLM 调用的 Prompt 输出符合预定义的 JSON Schema 规范。前端工程责任维护 Schema 校验器、组件注册表Component Registry与 Fail-safe 错误边界。生产级 React Zod 生成式 UI 渲染器实现下面是一套在生产环境验证过的生成式 UI 动态渲染引擎代码。使用 React 18 与 Zod 编写具备完全的类型推导、未知组件隔离与 Error Boundary 兜底能力。import React, { Component, ErrorInfo, ReactNode } from react; import { z } from zod; // 1. 严格定义前端允许渲染的组件 Schema 契约 export const BaseComponentSchema z.object({ id: z.string(), type: z.enum([Input, Select, DatePicker, MetricCard]), label: z.string(), props: z.record(z.any()).default({}), }); export const GenerativeUISchema z.object({ version: z.literal(1.0.0), layout: z.enum([grid, flex_column, flex_row]), components: z.array(BaseComponentSchema), }); export type GenerativeUIConfig z.infertypeof GenerativeUISchema; // 2. 确定性的前端受控 UI 组件映射表 const UI_REGISTRY: RecordGenerativeUIConfig[components][number][type], React.FCRecordstring, unknown { Input: ({ label, placeholder }) ( div classNamegen-ui-field label classNametext-sm font-medium text-gray-700{label}/label input typetext placeholder{placeholder} classNamemt-1 block w-full rounded-md border-gray-300 p-2 border / /div ), Select: ({ label, options }) ( div classNamegen-ui-field label classNametext-sm font-medium text-gray-700{label}/label select classNamemt-1 block w-full rounded-md border-gray-300 p-2 border {options?.map((opt: string) ( option key{opt} value{opt}{opt}/option ))} /select /div ), DatePicker: ({ label }) ( div classNamegen-ui-field label classNametext-sm font-medium text-gray-700{label}/label input typedate classNamemt-1 block w-full rounded-md border-gray-300 p-2 border / /div ), MetricCard: ({ label, value, trend }) ( div classNamep-4 bg-white rounded-lg shadow-sm border border-gray-100 div classNametext-xs text-gray-500{label}/div div classNametext-2xl font-bold text-gray-900 mt-1{value}/div {trend span classNametext-xs text-green-600{trend}/span} /div ), }; // 3. Error Boundary 降级兜底组件 interface ErrorBoundaryProps { fallback: ReactNode; children: ReactNode; } interface ErrorBoundaryState { hasError: boolean; } class GenerativeUIErrorBoundary extends ComponentErrorBoundaryProps, ErrorBoundaryState { public state: ErrorBoundaryState { hasError: false }; public static getDerivedStateFromError(_: Error): ErrorBoundaryState { return { hasError: true }; } public componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error([GenerativeUI Error] 组件渲染崩溃:, error, errorInfo); } public render() { if (this.state.hasError) { return this.props.fallback; } return this.props.children; } } // 4. 核心渲染器组件 export const GenerativeUIRenderer: React.FC{ rawJson: unknown } ({ rawJson }) { // 步骤 A: 校验 JSON Schema 契约 const parseResult GenerativeUISchema.safeParse(rawJson); if (!parseResult.success) { console.warn([GenerativeUI] Schema 契约校验失败:, parseResult.error.format()); return ( div classNamep-4 bg-red-50 text-red-600 border border-red-200 rounded-md text-sm ⚠️ 生成式 UI 数据格式不符合契约Schema Mismatch /div ); } const { layout, components } parseResult.data; const layoutClass layout grid ? grid grid-cols-2 gap-4 : flex flex-col space-y-3; return ( GenerativeUIErrorBoundary fallback{ div classNamep-4 bg-yellow-50 text-yellow-700 border border-yellow-200 rounded-md text-sm ⚠️ 动态 UI 组件渲染遇到运行时异常已触发现场隔离降级。 /div } div className{generative-ui-container ${layoutClass}} {components.map((comp) { const TargetComponent UI_REGISTRY[comp.type]; if (!TargetComponent) { return ( div key{comp.id} classNamep-2 text-xs bg-gray-100 text-gray-400 rounded [未知组件类型: {comp.type}] /div ); } return TargetComponent key{comp.id} label{comp.label} {...comp.props} /; })} /div /GenerativeUIErrorBoundary ); };契约发生偏差时怎么处理例如Select的options约定为字符串数组而模型可能返回对象数组。不要把这种值直接传给组件再由组件猜测数据形状应在解析阶段拒绝该 payload展示降级内容并记录 schema 版本、错误路径和请求标识。包含用户数据的原始 payload 应按脱敏和留存规则处理。前端不负责推断未知格式。若确实需要兼容版本演进应明确写成带版本号的迁移逻辑并规定下线日期。三条责任边界产品、算法和前端共同维护 Schema 及其版本对未知字段采用拒绝还是忽略应写入契约并保持一致。AI 可以选择白名单中的组件和受限 Props不应直接控制任意 DOM、样式或事件处理器。Error Boundary 应尽量靠近动态节点避免单个组件失败影响整页。
返回列表