AI Design-to-Code 的两个根本问题,和我的解法

AI 写业务逻辑已经很顺手,但设计稿还原?样式丢、布局乱、代码难维护。这不是模型不够强,是我们喂给它的输入不对。

落地过程中,我发现 AI D2C 的困难归结为两个根本问题。


问题一:AI 没有空间认知

LLM 是序列模型,处理的是 token 流,不是二维平面。当它看到:

json 复制代码
{ "x": 285, "y": 725, "width": 700, "height": 440 }
{ "x": 1005, "y": 725, "width": 370, "height": 440 }
{ "x": 285, "y": 1165, "width": 340, "height": 400 }

它看到的是三组数字,不是「第一行两张卡片并排,第二行一张卡片靠左」。

人看设计稿是空间扫描 --- 一眼看出对齐、等距、分栏。LLM 看坐标是数值推理 --- 要算 1005 - 285 = 720,再跟 width: 700 比较,才能推断「这两个元素是水平排列的」。而数值推理恰恰是 LLM 最弱的能力之一。

这导致几类典型错误:

空间关系 人的判断 LLM 容易犯的错
水平对齐 y 值接近就是一行 把 y=725 和 y=730 判断成两行
等分布局 三个等宽元素占满容器 生成固定 px 而不是 flex:1
嵌套层级 小元素在大元素内部 坐标包含关系算错,层级打平
间距规律 所有模块间距 20px 部分写 20,部分写 16,不一致

本质原因:Transformer 的自注意力机制是在 token 维度上建立关联的,它没有内置的二维坐标系。它理解「猫坐在垫子上」比理解「x=100 的元素在 x=500 的元素左边」要容易得多------前者是语言语义,后者是空间计算。

问题二:上下文窗口造成注意力涣散

Transformer 的注意力是一个 softmax 分布------所有 token 共享一个概率空间。上下文从 2k 增长到 50k 时,每个 token 分到的平均注意力从 1/2000 降到 1/50000。

具体表现:

现象 示例
遗忘 前面强调"必须带 data-ir-id",后面生成的代码就忘了
偏移 样式值从精确的 rgba(51,51,51,1) 变成随意的 #333
简化 该生成 10 个模块,只生成了 3 个就停了
幻觉 编造设计稿里不存在的元素

这不是模型能力问题,是 Transformer 架构的固有特性------上下文越长,早期信息的影响力越弱。

两个问题互相放大

最要命的是,这两个问题是耦合的:

markdown 复制代码
空间推理弱 → 需要更详细的布局描述来补偿
                 ↓
         上下文变大
                 ↓
         注意力涣散 → 连详细的描述也读不准了
                 ↓
         布局错误更多 → 需要更多修复指令
                 ↓
         上下文进一步膨胀 → 恶性循环

直接把 16MB 的设计稿丢给 AI,这两个问题同时爆发。


我的解法:在 LLM 之前把这两件事做掉

核心思路:别让 LLM 做空间计算,别让 LLM 处理长上下文。用工程手段在模型介入之前,把坐标关系翻译成语言描述,把大上下文拆成小片段。

css 复制代码
[前置] 扫描项目代码风格
   ↓
设计文件 → DSL 压缩 → 语义理解 → 意图推断 → 代码生成

每个阶段都在对抗这两个根本问题:

阶段 对抗「无空间认知」 对抗「注意力涣散」
DSL 压缩 --- 16MB → 2MB,减少无关 token
区域拆分 预计算空间分组,告诉 AI「这些在一行」 2MB → 0.34MB,进一步缩小上下文
语义理解 把坐标关系翻译成语言:「card + list」 用语义标签替代原始坐标
意图推断 把「三个 300px 元素」翻译成「等分布局」 意图确认后不再需要原始数据
代码生成 输入已经是「flex:1」而不是坐标 每个模块独立生成,上下文极小

下面展开每个阶段的具体做法。


前置阶段:扫描项目代码风格

在处理设计稿之前,先扫描目标项目,提取现有代码的风格和规范。生成的代码要和项目保持一致。

提取什么

维度 来源 示例
框架类型 package.json Vue3 + Element Plus + TypeScript
编码规范 eslint/prettier/editorconfig 单引号、无分号、缩进 2 空格
已有组件 src/components/*.vue ContentWrap、Pagination、Icon
样式 Token src/styles/*.scss --el-color-primary: #409eff

从现有代码学习

配置文件只能告诉你缩进是 2 空格还是 4 空格,真正的代码风格要从现有组件里学:

typescript 复制代码
// 从项目现有 .vue 文件中提取的模式
interface LearnedPatterns {
  // 脚本风格
  scriptStyle: 'setup' | 'options';       // <script setup> vs <script>
  useDefineOptions: boolean;               // defineOptions({ name: '...' })
  propsStyle: 'type-based' | 'runtime';   // defineProps<T>() vs defineProps({})
  emitsStyle: 'type-based' | 'runtime';   // defineEmits<T>() vs defineEmits([])
  // 导入风格
  importOrder: string[];                   // ['vue', 'element-plus', '@/xxx']
  importGrouping: 'grouped' | 'flat';
  pathAlias: Record<string, string>;       // { '@': 'src', '#': 'types' }
  // 样式风格
  styleLang: 'scss' | 'less' | 'css';
  styleScoped: boolean;
  useDeepSelector: ':deep()' | '::v-deep'; // 深度选择器风格
  // 命名风格
  cssClassStyle: 'kebab-case' | 'BEM';    // .card-header vs .card__header
  refNaming: 'xxxRef' | 'refXxx';         // tableRef vs refTable
}

学习方法:扫描项目现有组件(至少 3 个样本),统计各模式出现频率,取多数。每条结论必须附带来源文件路径。

实测基线(OA 项目,195 个组件样本):

arduino 复制代码
✔ 184/195 组件用 <script setup>      → 采用 setup 风格
✔ 169/195 组件用 defineOptions        → 加 defineOptions
✔ 120/195 组件用 scoped 样式          → 加 scoped
✔ 123/195 组件用 lang="scss"          → 采用 SCSS
✔ 引号统计 single:3577 行 / double:46 行 → 单引号

还会按业务目录二次扫描。比如首页组件目录 23 个文件,script setup + defineOptions + scoped scss 全量命中------这比全局统计更可信。

优先级

yaml 复制代码
1. 现有代码学习   # 最高 --- 实际代码说了算
2. 配置文件         # 其次 --- ESLint/Prettier
3. 框架默认         # 最低 --- Vue3 官方推荐

配置文件与代码样本冲突时,以样本为准。项目组件数 < 3 时才用框架默认。


拿到设计文件

输入通常是这几种:

  • 蓝湖导出的 JSON(3-10MB)
  • Figma 导出的 JSON
  • 设计图片(降级方案)

实际案例 :我们的 OA 首页设计稿,蓝湖导出的 ui.json16.55MB ,画板尺寸 1920×3210px,包含 27 个模块 (审批、考勤、红黑榜、销售漏斗等)、107 个图片资源1387 个节点

原始 JSON 长这样:

json 复制代码
{
  "transform": [[1, 0, 0], [0, 1, 0]],
  "combinedFrame": { ... },
  "paths": [ ... ],
  "masks": [ ... ]
}

这些字段是渲染引擎需要的,开发者不需要,LLM 更不需要。


压缩成 DSL

目标:把「能画 UI」的数据变成「能描述 UI」的数据。

保留这些字段:

  • id --- 节点标识
  • name --- 节点名称(常有语义提示)
  • type --- 节点类型
  • frame --- 位置和尺寸
  • style --- 样式(fills / borders / shadows)
  • text --- 文本内容和样式
  • image --- 图片 URL

删掉这些:

  • transform 矩阵
  • paths 路径数据
  • 蒙版、裁切信息
  • 无意义的分组中间层

实测效果(OA 首页):

文件 大小 说明
ui.json(原始) 16.55 MB 蓝湖导出,含全部绘图指令
ui-dsl.json(压缩后) 2.02 MB 只保留结构+样式+文本
region-row1.json(区域拆分) 0.34 MB 单个区域的 DSL,可直接喂给 LLM

压缩率 87%。进一步按区域拆分后,单个文件只有 300KB 左右,LLM 能轻松处理。

图片资源处理 --- ui.json 里的图片是 URL 链接,需要建立映射:

json 复制代码
// image-manifest.json (生成的映射表)
{
  "images": [
    {
      "url": "https://lanhuapp.com/.../icon-approve.png",
      "local": "assets/icons/icon-approve.png",
      "type": "png",
      "size": "25×25"
    }
  ],
  "total": 107,
  "uniquePng": 64,
  "uniqueSvg": 97
}

生成代码时,LLM 直接使用 local 路径,不用处理外部 URL。


语义理解

回答「这是什么」。

给每个节点打标签:container、card、button、list、table、nav、header、footer...

实际案例 --- OA 首页 DSL 解析出的模块结构:

ini 复制代码
顶部区域 (y=0~265, 30节点)
    ├── logo (228×68)
    ├── 按钮: 刷新缓存
    ├── 按钮: 进入后台
    └── 对话: "下午好,何冰玉"
区域 Row1 (y=285~725, 234节点)
    ├── 本月目标完成情况 (700×440) → card + chart
    ├── 常用功能 (370×440) → grid
    └── 审批+预计收益+考勤+报销 (770×440) → 2×2 grid
区域 Row2 (y=745~1145, 194节点)
    ├── 红黑榜单 (340×400) → card + list
    ├── 异常榜 (340×400) → card + list
    ├── 销售榜 (370×400) → card + list
    └── 企业公告 (770×400) → card + list
...共 7 个区域,27 个模块

怎么推断:

  1. 看名称 --- 设计师命名的 name 字段常有提示:

    • name: "按钮" → button,name: "标题" → header
    • name: "内容" → content、name: "导航" → nav
  2. 看结构 --- 子节点重复 → list;有文本+边框+背景 → card

  3. 看尺寸 --- 25×25px 的图片节点 → icon;700×440 的容器 → card

每个推断带置信度分数。名称明确提示 → 0.9+,仅靠尺寸猜 → 0.5-0.7。低置信度的后面会让你确认。


意图推断

回答「设计师想要什么效果」。

这步最关键,也是传统方案最容易忽略的。

看这个例子:

设计稿上三张卡片,都是 300px 宽,间距 20px。问题:这是等分布局还是固定宽度?

视觉上一样,代码完全不同:

css 复制代码
/* 等分 --- 容器变宽,卡片跟着变宽 */
.card { flex: 1; }

/* 固定 --- 容器变宽,卡片还是 300px */
.card { width: 300px; }

不搞清楚意图,生成的代码「看起来对,但行为错」。

需要推断的意图:

  • 等分还是固定宽度?
  • 允许换行吗?
  • 超出怎么处理?截断、滚动、换行?
  • 小屏幕怎么响应?堆叠、隐藏、缩小?

推断依据:

  • 三个元素宽度相等,加起来接近容器宽度 → 可能等分
  • 使用 8px 栅格 → 大概率响应式
  • 后台管理系统 → 大概率固定宽度

置信度低于 0.7 时,直接问你:

css 复制代码
检测到三张卡片,宽度均为 300px。请确认布局意图:
[ ] 等分布局(卡片宽度随容器变化)
[ ] 固定宽度(卡片始终 300px)

代码生成

输入:带语义标签的 DSL + 明确的意图 + 目标框架

语义映射到组件:

语义 Vue3 + Element Plus React + Ant Design
card el-card Card
button el-button Button
table el-table Table

意图映射到布局:

意图 CSS
等分 flex + flex:1
固定宽度 flex + width
允许换行 flex-wrap: wrap

只写差异样式(隐式样式分析):

这里有个关键概念:隐式样式层

每个组件库都有默认样式,比如 Element Plus 的 el-card:

yaml 复制代码
el-card:
  background: "#fff"
  border-radius: "4px"
  border: "1px solid #ebeef5"

设计稿的背景也是 #fff?不用写。只写与默认值不同的部分:

scss 复制代码
// ❌ 冗余
.card {
  background: #fff;      // el-card 默认
  border-radius: 4px;    // el-card 默认
  padding: 20px;
}

// ✅ 最小化
.card {
  padding: 20px;
}

这样做的好处:

  • 代码更干净
  • 不会覆盖组件库的主题变量
  • 后续换主题时不会出问题

样式值从 DSL 精确提取:

OA 首页提取出的设计 Token:

用途 DSL 来源
主文字 rgba(51,51,51,1) 节点 3:793 text.style.color
模块标题 Source Han Sans CN Bold 16px 标题节点 text.style.font
页面边距 20px 从容器 frame.x 计算
模块间距 20px 从相邻模块 frame 差值计算

不让 LLM 凭记忆编颜色值------它会编错的。

Token 匹配:

设计值能匹配到项目的 CSS 变量?优先用变量:

scss 复制代码
// 设计值 20px 匹配到 --el-component-size
padding: var(--el-component-size);

代码更规范,也能响应主题切换。


这套方法比「直接丢给 AI」好在哪

以 OA 首页为例:

维度 直接丢给 AI 五阶段模型
输入大小 16.55MB 原始 JSON 0.34MB 区域 DSL
节点数 1387 个全部丢入 按模块拆分,每次 30-200 个
图片资源 无法处理 107 个图片自动建立 URL→本地文件映射
布局准确性 靠猜 意图明确后再推断
样式精确度 可能编造 从源数据提取
模块覆盖 可能漏掉模块 27 个模块全部识别

现有代码已实现 11 个模块,通过这套方案识别出了 13 个待开发模块、确定了 6 个可复用的排行榜组件。


回到根本问题:工程缓解策略

上面的流程是「预防」,但落地时注意力涣散仍然会发生。还需要额外的工程手段来弥补:

用工程手段弥补

规则强化 --- 在关键约束上用强语气标记:

markdown 复制代码
⚠️ **核心原则:必须从 DSL 节点提取精确值**
❌ **禁止**:让 LLM 凭记忆生成颜色值
✅ **必须**:每个样式值标注来源节点 ID

⚠️❌ 禁止✅ 必须 这类标记能显著提升约束遵守率。

检查点机制 --- 流程中设强制验证点,没过就不能继续:

yaml 复制代码
checkpoint:
  name: "DSL 分析完成检查"
  required_outputs:
    - module_list_table  # 模块清单
    - colors             # 颜色值列表
    - coverage_status    # 覆盖率状态
  on_missing:
    action: "阻止继续,要求补全"

流水线上的质量门禁,错误不传递到下游。

Subagent 分治 --- 大任务拆成小任务,每个 Subagent 只处理一个模块:

角色 上下文大小 职责
主 Agent ~2k tokens 调度、分配、合并
Subagent 1 ~5k tokens 生成模块 A 代码
Subagent 2 ~5k tokens 生成模块 B 代码

避免单个 Agent 处理 50k+ tokens,每个 Agent 保持聚焦。

显式行范围 --- 精确指定该读哪段:

arduino 复制代码
// 模糊(容易遗漏)
"读取 ui-dsl.json,找到模块信息"

// 精确(更可靠)
"读取 ui-dsl.json 的第 285-725 行,这是模块 A 的 DSL 数据"

来源标注 --- 每个样式值标注 DSL 节点 ID:

scss 复制代码
// 来源:节点 3:793 的 text.style.color
color: rgba(51, 51, 51, 1);

要标注来源,就必须去读原始数据,而不是凭印象编造。

本质

这些策略的本质:用工程手段弥补模型的注意力缺陷

  • 规则强化 → 提升关键信息的权重
  • 检查点 → 阻断错误传播
  • 分治 → 缩小单次处理的上下文
  • 显式范围 → 减少无关信息干扰
  • 来源标注 → 强制回溯原始数据

演进方向:Teams Agents 架构

当前方案用「主 Agent + Subagent」,主 Agent 还是要理解全局,上下文逐步累积。更彻底的方案:多 Agent 协作

当前方案

markdown 复制代码
主 Agent(上下文膨胀)
    ├── 读 DSL
    ├── 分析语义
    ├── 推断意图
    ├── 调度 Subagent
    └── 合并结果

Teams Agents 方案

css 复制代码
共享状态(DSL + 分析结果)
    │
    ├── DSL Agent      → 只做压缩和结构化,输出写入共享状态
    ├── 语义 Agent     → 读共享状态,只做语义标签
    ├── 意图 Agent     → 读共享状态,只做意图推断
    ├── 代码 Agent 1   → 读共享状态,只生成模块 A
    ├── 代码 Agent 2   → 读共享状态,只生成模块 B
    └── Leader Agent   → 不处理细节,只做检查和调度

为什么更好

维度 单 Agent Teams Agents
上下文隔离 Subagent 有隔离,主 Agent 没有 每个 Agent 都隔离
Leader 负担 要理解全部细节 只看摘要和检查点
信息传递 prompt 传递,容易丢失 共享状态,精确读取
并行能力 受主 Agent 调度限制 完全并行
单点故障 主 Agent 出错全崩 单个 Agent 出错可重试

Leader Agent 的职责

不再做: 读 DSL、理解设计细节、记住样式值

只做:

  • 定义任务边界(哪个 Agent 负责哪个模块)
  • 检查产出完整性(模块数对不对、覆盖率够不够)
  • 处理冲突(两个 Agent 输出不一致时决策)
  • 最终合并

跑通需要什么

  1. 共享状态存储 --- 文件系统 / 数据库 / 内存 KV
  2. Agent 通信协议 --- 谁先跑、谁依赖谁、怎么通知完成
  3. 支持 Teams 的平台 --- OpenAI Swarm、AutoGen、CrewAI、或自己搭

五阶段模型是基础,Teams 架构是优化。


边界

这套方案解决的是结构化设计稿到静态代码。这些不覆盖:

  1. 复杂动效 --- 需要额外的动效描述层
  2. 交互逻辑 --- 按钮点了做什么,得单独定义
  3. 数据绑定 --- 哪些是静态文本、哪些是动态数据,得标注
  4. 设计稿本身有问题 --- 方案假设设计稿是规范的

总结

AI D2C 的两个根本问题------无空间认知注意力涣散------不会因为模型变强而彻底消失。它们是 Transformer 架构的固有特性。

所以解法不是等更强的模型,而是用工程手段绕过它们:

  1. 扫描 --- 读懂项目现有代码风格
  2. 压缩 --- 去噪,缩小上下文
  3. 语义 --- 把坐标翻译成语言
  4. 意图 --- 把像素翻译成意图
  5. 生成 --- 用项目风格写对代码

再加上规则强化、检查点、分治等工程手段对抗注意力涣散,用 Teams 架构进一步隔离上下文。


欢迎讨论

这套方案是我在 OA 项目中落地的实践,不是最优解。

我很想听到不同的思路:

  • 空间认知问题有没有更好的解法?比如给 LLM 加一个视觉编码器,或者用多模态模型直接看设计图?
  • 注意力涣散除了分治和规则强化,还有什么工程技巧?
  • 如果用 Teams Agents 架构,共享状态怎么设计最合理?
  • 有没有人在做 Figma 插件 + LLM 的方案,跟这套思路有什么交集?

欢迎 PR、Issue 或直接讨论。

相关推荐
mCell8 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell9 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭9 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清9 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木9 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076609 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声9 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易9 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得010 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
anOnion10 小时前
构建无障碍组件之Dialog Pattern
前端·html·交互设计