我用 DeepSeek 搭建了一个 AI 驱动的 React 代码生成引擎,全流程踩坑记录
起因
前端开发中有大量重复性工作:接到需求 → 画原型 → 写组件 → 调样式 → 改布局。一直在想,能不能用 AI 把"暗黑风格数据看板,包含 KPI 卡片和趋势图"这种自然语言,直接变成可运行的 React 代码?
上周末终于动手了。花了两天时间,用 DeepSeek Pro API 搭建了一个三阶段流水线,跑通了从自然语言到浏览器渲染的完整链路。
架构设计
整个系统叫 Athena,分为三个独立引擎:
用户自然语言
↓
[引擎1: 意图补全] → TaskJSON (结构化页面描述)
↓
[引擎2: 代码生成] → React TSX 代码
↓
[引擎3: 沙箱预览] → 浏览器渲染
三个引擎各自调用独立的 AI 会话,通过 JSON 协议解耦。引擎1 和引擎2 都用 DeepSeek Pro,引擎3 预留了千问视觉做设计反馈(还没来得及做)。
技术栈
- 运行时: Node.js + TypeScript
- 服务端: Fastify
- AI SDK: OpenAI 兼容协议(调用 DeepSeek)
- 包管理: pnpm monorepo(Turborepo)
- 前端框架: React 18 + Next.js 14(控制台部分)
- 图表: Recharts(后来因 CDN 问题换成了 SVG mock)
- 沙箱: Babel standalone 浏览器端编译
引擎1:意图补全 — 自然语言 → 结构化 JSON
这是整个系统的"翻译层"。用户输入"暗黑风格数据看板:2 个 KPI 和 1 个折线图",DeepSeek 需要输出一个严格符合 Schema 的 TaskJSON。
核心挑战:让 LLM 输出严格格式的 JSON
最初以为很简单,加上 response_format: { type: 'json_object' } 就行。实际踩坑后发现:
-
DeepSeek 会自由发挥字段名。Prompt 里写
components,它可能输出comps或widgets。解决方式:在 System Prompt 里给出完整的 JSON 示例结构,每个字段都标注清楚。 -
Props 格式最容易出错。我需要
"props": [{ "name": "title", "value": "销售额" }]这种数组格式,但 DeepSeek 总想输出"props": { "title": "销售额" }这种对象。Prompt 里加了 3 个反例才纠正:错误 ❌:{ "title": "xxx" } 正确 ✅:[{ "name": "title", "value": "xxx" }] -
中文乱码。这是最隐蔽的问题。
originalIntent字段要求复制用户原始输入,但 API 返回总是???????。试了各种方法——最后发现不是 DeepSeek 的问题,是 PowerShell 的Invoke-RestMethod默认用 ASCII 编码发送 body。改成 UTF-8 字节数组后解决:$bytes = [System.Text.Encoding]::UTF8.GetBytes($jsonBody) Invoke-WebRequest -Body $bytes -ContentType "application/json; charset=utf-8"这个问题花了我将近 1 小时。
架构要点
- 用 Zod 对 DeepSeek 的输出做运行时校验,校验失败返回详细错误给前端
originalIntent不依赖 LLM 输出,后端直接填入request.intentsummary让 LLM 自己总结,效果好很多
引擎2:代码生成 — JSON → React 组件
引擎2 接受引擎1 产出的 TaskJSON,生成完整的 React 代码。
Prompt 设计
Prompt Builder 是整个引擎2 的核心。它需要拼接:
- 完整的 TaskJSON(包括组件列表、布局、主题)
- 组件库的元数据(有哪些组件、每个组件的 Props 是什么)
- 生成约束(框架、样式方案、禁止项)
第一次生成的代码质量很差。检查后发现 Prompt 里的 TaskJSON 中文被吃掉了——问题出在模板字符串拼接。改用 .join("\n") 拼接字符串数组后解决。
生成的代码长什么样
function Dashboard() {
var theme = { mode: "dark", primaryColor: "#6366f1", ... };
return (
<div style={{ backgroundColor: theme.backgroundColor, minHeight: '100vh', padding: 24 }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(12, 1fr)', gap: 16 }}>
<div style={{ gridColumn: 'span 6' }}>
<KPICard title="总销售额" value={0} />
</div>
...
</div>
</div>
);
}
踩坑:TypeScript 类型注解残留
DeepSeek 生成的代码经常带 : React.FC 这种 TypeScript 注解。浏览器里的 Babel standalone 不认识,直接报错。解决方案:sandbox controller 里加了一个 cleanCode() 函数,正则删掉所有类型注解和 import 语句。
引擎3:沙箱预览 — 代码 → 浏览器渲染
这是最让我头疼的部分。目标是让生成的代码能在浏览器里真正跑起来。
踩坑1:Recharts CDN 死活加载不了
最开始用 unpkg.com/recharts 的 UMD 包,浏览器报 Cannot read properties of undefined (reading 'oneOfType')。换了 3 个 CDN 版本都不行,最后发现是 Recharts 内部依赖 react-smooth 在 UMD 构建时有 bug。
最终方案:放弃 Recharts CDN,自己用纯 SVG mock 图表。 虽然是占位图表,但渲染效果出奇好——折线图有真实的数据点、填充区域和网格线,柱状图有渐变色的柱子。这部分代码不到 200 行。
踩坑2:file:// 协议无法加载 CDN
保存的 HTML 直接双击打开,CDN 脚本全部报 CORS 错误。必须用 HTTP 服务器 serve。最终用 npx serve 开本地服务器解决。
踩坑3:Babel standalone 的 JSX 作用域
<script type="text/babel"> 里定义的变量(如 mock 组件)无法在用户代码里访问,因为 Babel 编译 JSX 时会做作用域提升。解决:把全局组件挂在 window 上(window.KPICard),Babel 编译后就能正确引用了。
最终成果
三天开发 + 两天调 Bug,最终实现:
输入:"暗黑风格:2 个 KPI(总销售额、订单数),1 个折线图"
↓
引擎1 (3-6秒) → TaskJSON
↓
引擎2 (5-8秒) → React 代码
↓
浏览器渲染 → 暗黑背景 + KPI 卡片 + SVG 折线图
未完成的部分
- 引擎3(视觉反馈):用 Puppeteer 截图 + 千问 VL 分析,形成设计审查闭环
- 真实数据绑定:目前 KPI 值是 0,需要接入 API 或 mock 数据源
- 组件库完善:目前只有 KPICard + 4 种图表,需要扩展到 20+ 组件
最深的感悟
-
Prompt 工程不是写提示词,是设计协议。你需要把 Prompt 当成 API 文档来写——明确的输入输出格式、边界情况处理、错误示例。
-
AI 编码的本质是"翻译"而非"创造"。 LLM 擅长把结构化数据转成代码,但生产级质量需要无数细节打磨。它更像一个超级快的初级工程师,而不是架构师。
-
前后端全栈的 AI 应用,真正的瓶颈在基础设施。 沙箱执行、类型校验、路由注册、CORS、编码问题——这些"杂活"占了 70% 的开发时间。
-
一个人做 AI 工程,最大的挑战不是技术,是保持动力。 技术链路跑通后的兴奋感会迅速消退,因为接下来是无穷无尽的细节优化。