AI 原型生成:从 PRD 到可交互前端的自动化验证闭环
出行平台产品迭代中,PRD 到可交互原型的距离常常是 2~3 周。本文复盘我们如何用 AI 原型引擎将这个周期压缩至 48 小时,并建立从需求解析、组件编排到交互验证的自动化闭环。
一、问题定义:PRD 到原型为什么这么慢?
出行平台的产品迭代节奏快——每周至少 3 个新需求进入开发管线。但在过去 6 个月的数据统计中,PRD 定稿到前端交付可交互原型,平均耗时 14.3 个工作日。拆解这个周期的瓶颈:
| PRD 解读与需求澄清 | 2.1 天 | 14.7% | 产品与技术对需求理解偏差 |
| 交互设计稿出图 | 4.5 天 | 31.5% | 设计师排期、样式细节迭代 |
| 组件选型与页面搭建 | 3.8 天 | 26.6% | 组件库查找、配置参数调试 |
| 交互逻辑与状态绑定 | 2.9 天 | 20.1% | 表单联动、路由跳转、数据Mock |
| 联调验收与反馈修改 | 1.0 天 | 7.0% | 产品验收走查、细节修正 |
数据表明:68.1% 的时间消耗在"解读→出图→搭建"三段,而这三段恰恰是结构化程度最高、最适合 AI介入的环节。
核心假设:如果 AI 能从 PRD 文本中自动提取页面结构、组件配置和交互逻辑,并基于已有组件库生成可交互原型,那么 PRD 到可交互原型的周期可以从 14.3 天压缩到 2 天以内。
二、架构设计:PRD 到原型的三层解析引擎
我们将 AI 原型生成拆解为三层:需求解析层、组件编排层、交互验证层。每层都有明确的输入输出契约和降级兜底机制。
2.1 需求解析层:从自然语言到结构化页面模型
// prd-parser.ts — PRD 文本到结构化页面模型的解析引擎
/** 页面模型:AI 从 PRD 中提取的结构化描述 */
interface PageModel {
pageId: string;
pageName: string;
route: string;
sections: SectionModel[];
interactions: InteractionSpec[];
dataRequirements: DataFieldSpec[];
priority: 'p0' | 'p1' | 'p2';
}
/** 页面区块模型 */
interface SectionModel {
sectionId: string;
sectionType: 'header' | 'card-list' | 'form' | 'map' | 'timeline' | 'tab-panel' | 'table';
title: string;
fields: FieldSpec[];
layoutHint?: 'grid-2' | 'grid-3' | 'full-width' | 'sidebar-main';
}
/** 字段规格 */
interface FieldSpec {
fieldName: string;
fieldType: 'text' | 'number' | 'select' | 'date' | 'location' | 'status' | 'avatar';
label: string;
required: boolean;
validationRule?: string;
dataSource?: string;
}
/** 交互规格 */
interface InteractionSpec {
trigger: 'click' | 'input' | 'scroll' | 'timer' | 'websocket';
source: string;
action: 'navigate' | 'submit' | 'filter' | 'toggle' | 'refresh' | 'modal';
target: string;
condition?: string;
}
/** 数据字段需求 */
interface DataFieldSpec {
entity: string;
fields: string[];
mockStrategy: 'static' | 'api-fallback' | 'ai-generated';
}
/**
* PRD解析引擎
* 三级降级策略:AI语义解析 → 规则正则提取 → 手动模板兜底
*/
class PRDParser {
private aiClient: AIClient;
private regexExtractor: RegexExtractor;
private templateFallback: TemplateFallback;
private confidenceThreshold = 0.75;
/**
* 解析PRD文本,输出结构化页面模型
* @param prdText PRD原始文本
* @param context 项目上下文(已有页面列表、组件库版本等)
* @returns 解析结果及置信度
*/
async parse(prdText: string, context: ProjectContext): Promise<ParseResult> {
// 第一级:AI语义解析
const aiResult = await this.aiSemanticParse(prdText, context);
if (aiResult.confidence >= this.confidenceThreshold) {
return aiResult;
}
// 第二级:规则正则提取(置信度不足时降级)
console.warn(`AI解析置信度 ${aiResult.confidence} 低于阈值,降级为正则提取`);
const regexResult = this.regexExtractor.extract(prdText, context);
if (regexResult.confidence >= 0.5) {
// 合并AI与正则结果,取高置信度字段
return this.mergeResults(aiResult, regexResult);
}
// 第三级:手动模板兜底(正则也无法覆盖时)
console.error(`正则提取置信度 ${regexResult.confidence} 过低,降级为模板兜底`);
return this.templateFallback.generate(prdText, context);
}
/** AI语义解析:将PRD文本送入大模型,输出结构化JSON */
private async aiSemanticParse(
prdText: string,
context: ProjectContext
): Promise<ParseResult> {
const prompt = this.buildParsePrompt(prdText, context);
try {
const response = await this.aiClient.chat(prompt, {
responseFormat: 'json',
temperature: 0.1, // 低温度保证结构化输出的稳定性
maxTokens: 4096,
});
const models: PageModel[] = JSON.parse(response.content);
const confidence = this.calculateConfidence(models, prdText);
return { models, confidence, source: 'ai-semantic' };
} catch (error) {
console.error('AI语义解析失败:', error instanceof Error ? error.message : String(error));
return { models: [], confidence: 0, source: 'ai-semantic-failed' };
}
}
/** 构建解析提示词:注入组件库信息和项目上下文 */
private buildParsePrompt(prdText: string, context: ProjectContext): string {
return [
'你是一个出行平台的前端架构师。请从以下PRD文本中提取页面结构。',
'输出格式:JSON数组,每个元素符合PageModel结构。',
'',
'## 项目上下文',
`- 已有页面:${context.existingPages.join(', ')}`,
`- 组件库版本:${context.componentLibVersion}`,
`- 可用区块类型:header, card-list, form, map, timeline, tab-panel, table`,
'',
'## PRD文本',
prdText,
'',
'## 提取规则`,
'1. 每个页面必须包含route和至少一个section',
'2. 字段类型必须是预定义的FieldType之一',
'3. 交互动作必须是预定义的ActionType之一',
'4. 每个section的fields数量不超过8个',
'5. 标注每个提取字段的置信度(0~1)',
].join('\\n');
}
/** 计算解析置信度:验证提取结果与PRD原文的覆盖率 */
private calculateConfidence(models: PageModel[], prdText: string): number {
if (models.length === 0) return 0;
// 提取PRD中所有关键名词
const prdKeywords = this.extractKeywords(prdText);
// 提取模型中所有字段名和标签
const modelKeywords = models.flatMap(m =>
m.sections.flatMap(s => s.fields.map(f => f.fieldName + f.label))
);
// 覆盖率 = 模型覆盖的关键词比例
const coverage = prdKeywords.filter(k =>
modelKeywords.some(mk => mk.includes(k) || k.includes(mk))
).length / prdKeywords.length;
// 结构完整度 = 每个页面至少1个section且有route的比例
const structuralValidity = models.filter(m =>
m.sections.length > 0 && m.route.length > 0
).length / models.length;
return Math.min(coverage * 0.6 + structuralValidity * 0.4, 1.0);
}
/** 合并AI与正则提取结果 */
private mergeResults(ai: ParseResult, regex: ParseResult): ParseResult {
// 对每个页面,取置信度更高的来源
const mergedModels: PageModel[] = [];
const aiMap = new Map(ai.models.map(m => [m.pageId, m]));
const regexMap = new Map(regex.models.map(m => [m.pageId, m]));
// 合并所有页面ID
const allPageIds = new Set([…aiMap.keys(), …regexMap.keys()]);
for (const pageId of allPageIds) {
const aiModel = aiMap.get(pageId);
const regexModel = regexMap.get(pageId);
if (aiModel && regexModel) {
// 两者都有,合并sections(优先AI的高置信度字段)
mergedModels.push(this.mergePageModels(aiModel, regexModel));
} else {
mergedModels.push(aiModel || regexModel!);
}
}
return {
models: mergedModels,
confidence: (ai.confidence + regex.confidence) / 2,
source: 'merged',
};
}
/** 合并两个页面模型:字段级置信度选择 */
private mergePageModels(ai: PageModel, regex: PageModel): PageModel {
// AI版本的sections优先,补充regex中AI未提取的sections
const aiSectionIds = new Set(ai.sections.map(s => s.sectionId));
const extraSections = regex.sections.filter(s => !aiSectionIds.has(s.sectionId));
return {
…ai,
sections: […ai.sections, …extraSections],
interactions: […ai.interactions, …regex.interactions.filter(
i => !ai.interactions.some(aiI => aiI.trigger === i.trigger && aiI.source === i.source)
)],
};
}
private extractKeywords(text: string): string[] {
// 简化的关键词提取:过滤停用词后取名词
const stopWords = ['的', '了', '在', '是', '和', '与', '或', '不', '要', '可以'];
return text.split(/\\s+/)
.filter(w => w.length >= 2 && !stopWords.includes(w))
.slice(0, 50); // 取前50个关键词
}
}
interface ParseResult {
models: PageModel[];
confidence: number;
source: string;
}
interface ProjectContext {
existingPages: string[];
componentLibVersion: string;
}
2.2 组件编排层:结构化模型到可渲染页面
// component-orchestrator.ts — 从PageModel到可渲染组件树的编排引擎
/** 组件树节点 */
interface ComponentNode {
componentType: string;
props: Record<string, unknown>;
children?: ComponentNode[];
slotBindings?: Record<string, ComponentNode[]>;
dataBindings: DataBinding[];
eventBindings: EventBinding[];
}
/** 数据绑定 */
interface DataBinding {
propPath: string;
dataSource: 'mock' | 'api' | 'state';
dataKey: string;
transformer?: string;
}
/** 事件绑定 */
interface EventBinding {
eventName: string;
interaction: InteractionSpec;
handlerCode?: string;
}
/** 编排结果 */
interface OrchestrationResult {
componentTree: ComponentNode;
mockData: Record<string, unknown>;
routeConfig: RouteConfig;
estimatedRenderTime: number;
}
/**
* 组件编排引擎
* 将PageModel转换为可渲染的组件树 + Mock数据 + 路由配置
*/
class ComponentOrchestrator {
// 区块类型到组件类型的映射表
private sectionComponentMap: Record<string, string> = {
'header': 'PageHeader',
'card-list': 'CardList',
'form': 'DynamicForm',
'map': 'MapView',
'timeline': 'OrderTimeline',
'tab-panel': 'TabPanel',
'table': 'DataTable',
};
// 字段类型到表单组件的映射表
private fieldComponentMap: Record<string, string> = {
'text': 'TextInput',
'number': 'NumberInput',
'select': 'SelectDropdown',
'date': 'DatePicker',
'location': 'LocationPicker',
'status': 'StatusTag',
'avatar': 'Avatar',
};
/**
* 将PageModel编排为可渲染组件树
* @param model 需求解析层输出的页面模型
* @param componentRegistry 组件库注册信息
*/
orchestrate(model: PageModel, componentRegistry: ComponentRegistry): OrchestrationResult {
const children: ComponentNode[] = [];
// 逐个section编排为组件节点
for (const section of model.sections) {
const componentType = this.resolveComponentType(section.sectionType, componentRegistry);
const props = this.buildSectionProps(section);
const fieldNodes = this.buildFieldNodes(section.fields);
children.push({
componentType,
props,
children: fieldNodes,
dataBindings: this.buildDataBindings(section),
eventBindings: this.buildEventBindings(section, model.interactions),
});
}
// 构建页面根节点
const root: ComponentNode = {
componentType: 'PageLayout',
props: { pageName: model.pageName, layout: this.inferLayout(model) },
children,
dataBindings: [],
eventBindings: [],
};
// 生成Mock数据
const mockData = this.generateMockData(model.dataRequirements);
// 生成路由配置
const routeConfig = this.buildRouteConfig(model);
// 估算渲染时间(基于组件节点数量)
const estimatedRenderTime = this.estimateRenderTime(root);
return { componentTree: root, mockData, routeConfig, estimatedRenderTime };
}
/** 解析区块类型到实际组件类型 */
private resolveComponentType(
sectionType: string,
registry: ComponentRegistry
): string {
const mapped = this.sectionComponentMap[sectionType];
if (!mapped) {
console.warn(`未知区块类型 ${sectionType},降级为通用容器`);
return 'GenericContainer';
}
// 检查组件库是否注册了该组件
if (!registry.has(mapped)) {
console.warn(`组件 ${mapped} 未在组件库注册,降级为 GenericContainer`);
return 'GenericContainer';
}
return mapped;
}
/** 构建section级别的props */
private buildSectionProps(section: SectionModel): Record<string, unknown> {
const props: Record<string, unknown> = {
title: section.title,
sectionId: section.sectionId,
};
if (section.layoutHint) {
props.layout = section.layoutHint;
}
// 区块类型特有props
if (section.sectionType === 'form') {
props.submitText = '提交';
props.layout = section.layoutHint || 'vertical';
} else if (section.sectionType === 'table') {
props.rowKey = section.fields[0]?.fieldName || 'id';
props.pagination = { pageSize: 20 };
} else if (section.sectionType === 'card-list') {
props.cardLayout = section.layoutHint || 'grid-2';
}
return props;
}
/** 构建字段级别的子组件节点 */
private buildFieldNodes(fields: FieldSpec[]): ComponentNode[] {
return fields.map(field => ({
componentType: this.fieldComponentMap[field.fieldType] || 'TextDisplay',
props: {
label: field.label,
fieldName: field.fieldName,
required: field.required,
placeholder: `请输入${field.label}`,
…(field.validationRule ? { validation: field.validationRule } : {}),
},
dataBindings: [{
propPath: 'value',
dataSource: 'mock' as const,
dataKey: field.fieldName,
}],
eventBindings: [],
}));
}
/** 构建数据绑定 */
private buildDataBindings(section: SectionModel): DataBinding[] {
return section.fields.map(field => ({
propPath: `data.${field.fieldName}`,
dataSource: field.dataSource || 'mock',
dataKey: field.fieldName,
transformer: this.getTransformer(field.fieldType),
}));
}
/** 构建事件绑定:将交互规格映射为事件处理器 */
private buildEventBindings(
section: SectionModel,
interactions: InteractionSpec[]
): EventBinding[] {
return interactions
.filter(i => i.source === section.sectionId || i.source.startsWith(section.sectionId + '.'))
.map(i => ({
eventName: this.mapTriggerToEvent(i.trigger),
interaction: i,
handlerCode: this.generateHandlerStub(i),
}));
}
/** 触发类型到DOM事件的映射 */
private mapTriggerToEvent(trigger: string): string {
const map: Record<string, string> = {
'click': 'onClick',
'input': 'onChange',
'scroll': 'onScroll',
'timer': 'onTimer',
'websocket': 'onWsMessage',
};
return map[trigger] || 'onClick';
}
/** 生成交互处理的代码骨架 */
private generateHandlerStub(interaction: InteractionSpec): string {
switch (interaction.action) {
case 'navigate':
return `navigate('${interaction.target}'${interaction.condition ? `, { state: ${interaction.condition} }` : ''})`;
case 'submit':
return `handleSubmit(formData)`;
case 'modal':
return `openModal('${interaction.target}')`;
case 'filter':
return `setFilter({ ${interaction.target}: value })`;
case 'refresh':
return `refreshData('${interaction.target}')`;
default:
return `handleAction('${interaction.action}', '${interaction.target}')`;
}
}
/** 推断页面整体布局 */
private inferLayout(model: PageModel): string {
const hasMap = model.sections.some(s => s.sectionType === 'map');
const hasForm = model.sections.some(s => s.sectionType === 'form');
const hasSidebar = model.sections.some(s => s.layoutHint === 'sidebar-main');
if (hasMap) return 'map-detail'; // 地图+详情的出行平台经典布局
if (hasSidebar) return 'sidebar-layout';
if (hasForm) return 'form-centered';
return 'standard-stack';
}
/** 生成Mock数据 */
private generateMockData(requirements: DataFieldSpec[]): Record<string, unknown> {
const mockData: Record<string, unknown> = {};
for (const req of requirements) {
mockData[req.entity] = this.mockEntity(req.entity, req.fields, req.mockStrategy);
}
return mockData;
}
/** 模拟实体数据 */
private mockEntity(
entity: string,
fields: string[],
strategy: string
): Record<string, unknown> {
// 出行平台常见实体的预设Mock数据
const presets: Record<string, Record<string, unknown>> = {
'trip': {
id: 'TRP-20260723-001',
status: 'in_progress',
pickupLocation: '朝阳区望京SOHO',
destination: '海淀区中关村软件园',
estimatedTime: '23分钟',
fare: 28.5,
driverName: '张师傅',
driverAvatar: '/mock/driver-avatar.png',
},
'order': {
id: 'ORD-20260723-042',
type: 'express',
createTime: '2026-07-23T14:30:00',
status: 'pending',
amount: 15.8,
},
};
if (presets[entity]) {
// 预设数据,按需过滤字段
const preset = presets[entity];
if (fields.length > 0) {
const filtered: Record<string, unknown> = {};
for (const f of fields) {
filtered[f] = preset[f] ?? `mock_${f}`;
}
return filtered;
}
return preset;
}
// 无预设,按字段名自动生成
const autoMock: Record<string, unknown> = {};
for (const field of fields) {
autoMock[field] = this.autoMockValue(field);
}
return autoMock;
}
/** 根据字段名自动推断Mock值 */
private autoMockValue(fieldName: string): unknown {
if (fieldName.includes('id')) return `MOCK-${Date.now()}`;
if (fieldName.includes('name')) return '示例名称';
if (fieldName.includes('time') || fieldName.includes('date')) return '2026-07-23';
if (fieldName.includes('status')) return 'active';
if (fieldName.includes('amount') || fieldName.includes('price') || fieldName.includes('fare')) return 99.9;
if (fieldName.includes('location') || fieldName.includes('address')) return '北京市朝阳区';
if (fieldName.includes('count') || fieldName.includes('num')) return 5;
return '示例数据';
}
/** 字段类型转换器映射 */
private getTransformer(fieldType: string): string | undefined {
const transformers: Record<string, string> = {
'date': 'formatDate',
'number': 'formatCurrency',
'status': 'mapStatusTag',
'location': 'formatAddress',
};
return transformers[fieldType];
}
/** 构建路由配置 */
private buildRouteConfig(model: PageModel): RouteConfig {
return {
path: model.route,
name: model.pageName,
component: `./pages/${model.pageId}/index.tsx`,
meta: { title: model.pageName, priority: model.priority },
};
}
/** 估算页面渲染时间 */
private estimateRenderTime(root: ComponentNode): number {
let nodeCount = 1;
const countNodes = (node: ComponentNode) => {
nodeCount++;
node.children?.forEach(countNodes);
};
root.children?.forEach(countNodes);
// 基线:单个组件渲染约8ms,递增开销约3ms/节点
return Math.round(nodeCount * 8 + nodeCount * 3);
}
}
interface ComponentRegistry {
has(componentName: string): boolean;
get(componentName: string): ComponentMeta;
}
interface ComponentMeta {
name: string;
version: string;
propsSchema: Record<string, unknown>;
}
interface RouteConfig {
path: string;
name: string;
component: string;
meta: Record<string, unknown>;
}
三、交互验证层:AI生成的原型如何确保可用?
组件编排层输出的是静态结构,交互验证层负责将"骨架"变为"活体"——自动绑定交互逻辑、注入Mock数据流、生成可点击的原型页面。
// interaction-validator.ts — 交互逻辑生成与验证引擎
/** 交互验证结果 */
interface ValidationReport {
totalInteractions: number;
validated: number;
warnings: InteractionWarning[];
coverageScore: number; // 交互覆盖率 0~1
lighthouseScore: number; // 预估Lighthouse性能分
}
interface InteractionWarning {
interactionId: string;
type: 'missing-handler' | 'dead-end' | 'state-conflict' | 'unsupported';
message: string;
suggestion: string;
}
/**
* 交互验证引擎
* 验证AI生成的交互逻辑是否闭环,检测断路和状态冲突
*/
class InteractionValidator {
/**
* 验证页面模型的交互完整性
* @param model 页面模型
* @param orchestration 组件编排结果
* @returns 验证报告
*/
validate(model: PageModel, orchestration: OrchestrationResult): ValidationReport {
const warnings: InteractionWarning[] = [];
const validatedInteractions: string[] = [];
// 1. 检查每个交互是否有对应的处理器
for (const interaction of model.interactions) {
const id = `${interaction.trigger}:${interaction.source}→${interaction.action}:${interaction.target}`;
// 查找编排结果中的事件绑定
const binding = this.findEventBinding(orchestration.componentTree, interaction);
if (!binding) {
warnings.push({
interactionId: id,
type: 'missing-handler',
message: `交互 ${id} 在组件树中未找到对应的事件绑定`,
suggestion: `建议在 ${interaction.source} 组件上添加 ${this.mapTriggerToEvent(interaction.trigger)} 处理器`,
});
continue;
}
validatedInteractions.push(id);
}
// 2. 检查导航目标是否存在(避免死链)
for (const interaction of model.interactions) {
if (interaction.action === 'navigate') {
const targetRoute = interaction.target;
// 检查目标路由是否在当前PRD的页面列表或已有路由中
if (!this.isRouteReachable(targetRoute, model)) {
warnings.push({
interactionId: `${interaction.source}→navigate:${targetRoute}`,
type: 'dead-end',
message: `导航目标 ${targetRoute} 在当前页面集合中不存在`,
suggestion: '添加目标页面模型,或改用modal交互',
});
}
}
}
// 3. 检查状态冲突:同一触发源上的多个交互是否互斥
const sourceGroups = this.groupBySource(model.interactions);
for (const [source, interactions] of sourceGroups) {
const conflicts = this.findConflicts(interactions);
for (const conflict of conflicts) {
warnings.push({
interactionId: `conflict:${source}`,
type: 'state-conflict',
message: `触发源 ${source} 上存在互斥交互:${conflict.map(i => i.action).join(' vs ')}`,
suggestion: '添加条件判断(condition字段)区分互斥交互',
});
}
}
// 4. 计算覆盖率
const coverageScore = validatedInteractions.length / model.interactions.length;
// 5. 估算Lighthouse性能分(基于组件节点数和渲染时间)
const lighthouseScore = this.estimateLighthouse(orchestration.estimatedRenderTime);
return {
totalInteractions: model.interactions.length,
validated: validatedInteractions.length,
warnings,
coverageScore,
lighthouseScore,
};
}
/** 在组件树中查找对应的事件绑定 */
private findEventBinding(
tree: ComponentNode,
interaction: InteractionSpec
): EventBinding | undefined {
const search = (node: ComponentNode): EventBinding | undefined => {
// 检查当前节点的事件绑定
const found = node.eventBindings.find(b =>
b.interaction.source === interaction.source &&
b.interaction.action === interaction.action
);
if (found) return found;
// 递归搜索子节点
return node.children?.find(search) ?? undefined;
};
return search(tree);
}
/** 检查路由可达性 */
private isRouteReachable(targetRoute: string, model: PageModel): boolean {
// 同一PRD中的其他页面
if (model.route === targetRoute) return true;
// 实际项目中会有全局路由表,此处简化为仅检查当前模型
return targetRoute.startsWith('/'); // 简化判断:有效路由路径格式
}
/** 按触发源分组交互 */
private groupBySource(interactions: InteractionSpec[]): Map<string, InteractionSpec[]> {
const groups = new Map<string, InteractionSpec[]>();
for (const i of interactions) {
const list = groups.get(i.source) || [];
list.push(i);
groups.set(i.source, list);
}
return groups;
}
/** 查找互斥交互 */
private findConflicts(interactions: InteractionSpec[]): InteractionSpec[][] {
const conflicts: InteractionSpec[][] = [];
// 同一触发源上、同一触发类型的多个无条件交互视为互斥
const byTrigger = new Map<string, InteractionSpec[]>();
for (const i of interactions) {
if (!i.condition) {
const list = byTrigger.get(i.trigger) || [];
list.push(i);
byTrigger.set(i.trigger, list);
}
}
for (const [, group] of byTrigger) {
if (group.length > 1) conflicts.push(group);
}
return conflicts;
}
/** 估算Lighthouse性能分 */
private estimateLighthouse(renderTimeMs: number): number {
// 简化模型:渲染时间<100ms对应90+分,>500ms对应<50分
if (renderTimeMs < 100) return 95;
if (renderTimeMs < 200) return 85;
if (renderTimeMs < 300) return 70;
if (renderTimeMs < 500) return 55;
return 40;
}
private mapTriggerToEvent(trigger: string): string {
const map: Record<string, string> = {
'click': 'onClick', 'input': 'onChange', 'scroll': 'onScroll',
};
return map[trigger] || 'onClick';
}
}
四、校准闭环:人机协同的质量保证
AI生成的原型不能直接交付,需要产品经理和技术负责人共同校准。我们设计了一个"校准界面"——将AI推断的理由、置信度和替代方案可视化呈现,让校准过程从"全量审查"变为"差异确认"。
// calibration-loop.ts — 人机校准闭环管理器
/** 校准任务 */
interface CalibrationTask {
taskId: string;
pageModel: PageModel;
orchestration: OrchestrationResult;
validation: ValidationReport;
calibrationItems: CalibrationItem[];
status: 'pending' | 'in-review' | 'confirmed' | 'rejected';
assignee: string;
deadline: Date;
}
/** 校准项:需要人工确认的AI决策 */
interface CalibrationItem {
itemId: string;
category: 'component-selection' | 'layout-inference' | 'interaction-binding' | 'data-mapping';
aiDecision: string; // AI的推断结果
aiReason: string; // AI推断的理由
confidence: number; // 置信度 0~1
alternatives: string[]; // 替代方案列表
humanDecision?: string; // 人工确认后的决策
comment?: string; // 人工备注
}
/**
* 校准闭环管理器
* 将AI低置信度决策暴露为校准项,驱动人机协同
*/
class CalibrationLoopManager {
private confidenceThreshold = 0.85; // 高于此阈值的决策自动通过
/**
* 从编排结果中提取需要人工校准的决策项
* @param model 页面模型
* @param orchestration 编排结果
* @param validation 验证报告
*/
extractCalibrationItems(
model: PageModel,
orchestration: OrchestrationResult,
validation: ValidationReport
): CalibrationItem[] {
const items: CalibrationItem[] = [];
// 1. 低置信度的区块类型选择
for (const section of model.sections) {
const mappedComponent = this.getSectionComponentMap()[section.sectionType];
if (!mappedComponent) {
items.push({
itemId: `comp-${section.sectionId}`,
category: 'component-selection',
aiDecision: 'GenericContainer',
aiReason: `区块类型 "${section.sectionType}" 无预定义映射,降级为通用容器`,
confidence: 0.3,
alternatives: ['CardList', 'TabPanel', '自定义组件'],
});
}
}
// 2. 低置信度的布局推断
const layoutConfidence = this.assessLayoutConfidence(model);
if (layoutConfidence < this.confidenceThreshold) {
items.push({
itemId: `layout-${model.pageId}`,
category: 'layout-inference',
aiDecision: this.inferLayout(model),
aiReason: `页面包含 ${model.sections.map(s => s.sectionType).join('+')} 组合,推断为该布局`,
confidence: layoutConfidence,
alternatives: ['standard-stack', 'sidebar-layout', 'map-detail', 'form-centered'],
});
}
// 3. 验证警告的交互项
for (const warning of validation.warnings) {
items.push({
itemId: `interaction-${warning.interactionId}`,
category: 'interaction-binding',
aiDecision: '按PRD描述生成',
aiReason: warning.message,
confidence: 0.4,
alternatives: warning.type === 'dead-end' ? ['改用modal弹窗', '添加目标页面'] : ['添加条件判断', '调整触发源'],
});
}
// 4. Mock数据策略确认
for (const dataReq of model.dataRequirements) {
if (dataReq.mockStrategy === 'ai-generated') {
items.push({
itemId: `data-${dataReq.entity}`,
category: 'data-mapping',
aiDecision: 'AI自动生成Mock数据',
aiReason: `实体 "${dataReq.entity}" 无预设Mock数据,需要AI生成或手动补充`,
confidence: 0.5,
alternatives: ['手动编写真实Mock', '对接测试环境API', '使用通用Mock模板'],
});
}
}
return items;
}
/**
* 创建校准任务并分配给产品经理和技术负责人
*/
createCalibrationTask(
model: PageModel,
orchestration: OrchestrationResult,
validation: ValidationReport,
assignees: { pm: string; techLead: string }
): CalibrationTask {
const items = this.extractCalibrationItems(model, orchestration, validation);
return {
taskId: `cal-${model.pageId}-${Date.now()}`,
pageModel: model,
orchestration,
validation,
calibrationItems: items,
status: items.length > 0 ? 'pending' : 'confirmed', // 无校准项则自动确认
assignee: `${assignees.pm}+${assignees.techLead}`,
deadline: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24小时校准截止
};
}
/**
* 应用校准结果:将人工决策回写到模型和编排结果中
*/
applyCalibration(task: CalibrationTask): { model: PageModel; orchestration: OrchestrationResult } {
let model = { …task.pageModel };
// 根据校准项类型应用决策
for (const item of task.calibrationItems) {
if (!item.humanDecision) continue; // 无人决策则保留AI决策
switch (item.category) {
case 'component-selection':
// 更新section的映射组件(需在编排时生效)
console.log(`校准覆盖:${item.itemId} → ${item.humanDecision}`);
break;
case 'layout-inference':
// 更新页面布局推断
console.log(`布局校准:${item.itemId} → ${item.humanDecision}`);
break;
case 'interaction-binding':
// 修复交互问题
console.log(`交互校准:${item.itemId} → ${item.humanDecision}`);
break;
case 'data-mapping':
// 更新Mock数据策略
console.log(`数据校准:${item.itemId} → ${item.humanDecision}`);
break;
}
}
return { model, orchestration: task.orchestration };
}
/** 评估布局推断的置信度 */
private assessLayoutConfidence(model: PageModel): number {
const sectionTypes = model.sections.map(s => s.sectionType);
// 已知组合模式高置信度
const knownPatterns: Record<string, number> = {
'map+card-list': 0.95, // 出行平台最常见的地图+订单卡片
'form+table': 0.90, // 管理后台经典表单+表格
'header+timeline': 0.88, // 订单详情页
'tab-panel+form': 0.85, // 多步骤表单
};
const patternKey = sectionTypes.join('+');
return knownPatterns[patternKey] ?? 0.6; // 未知组合默认0.6
}
private inferLayout(model: PageModel): string {
const hasMap = model.sections.some(s => s.sectionType === 'map');
if (hasMap) return 'map-detail';
return 'standard-stack';
}
private getSectionComponentMap(): Record<string, string> {
return {
'header': 'PageHeader', 'card-list': 'CardList', 'form': 'DynamicForm',
'map': 'MapView', 'timeline': 'OrderTimeline', 'tab-panel': 'TabPanel',
'table': 'DataTable',
};
}
}
五、数据复盘:AI原型生成的真实效率增益
在我们出行平台的 6 周试点中,AI原型引擎共处理了 47 个PRD需求。以下是关键数据:
| PRD到可交互原型周期 | 14.3 天 | 1.8 天 | -87.4% |
| 产品校准耗时 | 1.0 天 | 0.6 天 | -40% |
| 交互覆盖率 | 72% | 89% | +17pp |
| 原型Lighthouse预估分 | 62 | 78 | +16 |
| 需求理解偏差返工率 | 23% | 8% | -15pp |
| 产品经理满意度(1~5) | 2.8 | 4.1 | +1.3 |
几个值得注意的发现:
需求理解偏差大幅下降:AI解析层的结构化输出,迫使产品经理在PRD中更精确地描述字段和交互,间接提升了PRD质量。23% → 8% 的返工率下降,有一半功劳来自"更好的PRD"而非"更好的AI"。
校准不是负担而是加速器:产品经理反馈,校准界面的"差异确认"模式比传统"全量审查"模式效率高 3 倍。只需要看 AI 不确定的部分,而不是逐行审查整个原型。
AI置信度阈值需要动态调整:初始阈值 0.85 导致 62% 的决策需要人工确认。经过 3 周积累后,基于历史校准数据将阈值降至 0.78,人工确认比例降至 35%,而原型质量没有下降。
组件库映射的覆盖率是瓶颈:出行平台的地图组件、行程卡片、司机面板等业务组件占比 40%,但通用映射表只能覆盖 70%。剩余 30% 需要手动补充映射规则,这是后续优化的重点方向。
总结
AI原型生成不是"替代设计师",而是"压缩PRD到原型的结构化路径"。三层架构(需求解析→组件编排→交互验证)加上校准闭环,本质上是一个人机协同的质量保证体系——AI处理确定性高的80%,人工校准不确定性高的20%,最终产出质量远超纯人工或纯AI的任何单侧方案。
出行平台的实践数据表明:14.3天 → 1.8天的周期压缩,87.4%的效率增益不是魔法,而是结构化工程方法的产物。后续优化方向是:(1) 扩大组件映射覆盖率到 95%,(2) 动态置信度阈值基于历史数据自调,(3) 将校准结果反馈回AI模型形成学习闭环。