AI 生成设计 Token:从品牌规范到代码变量的自动转换

cover

一、设计 Token 的"手工翻译":从 Figma 到代码的断层

设计 Token 是设计系统的原子单位——颜色、字号、间距、圆角等视觉属性被抽象为命名变量,确保设计与代码的一致性。但品牌规范文档(PDF/Keynote)中的色值和字号,需要人工逐条翻译为 CSS 变量、Swift 常量和 Android 资源文件。一个包含 80 个颜色 Token、40 个间距 Token 和 30 个字号 Token 的设计系统,手工翻译需要 1-2 天,且每次品牌升级都需要重复这个过程。更致命的是,翻译过程中的手动复制粘贴极易引入错误——一个色值从 #3B82F6 误写为 #3B82E6,在视觉上几乎不可察觉,却导致品牌色不一致。

二、设计 Token 的体系结构与转换链路

2.1 从品牌规范到多平台 Token 的完整链路

flowchart TB
    A[品牌规范文档<br/>PDF/图片] --> B[AI 解析提取]
    B --> C[原始 Token 集<br/>JSON/DTCG 格式]
    C --> D[语义映射<br/>primary/secondary/...]
    D --> E[多平台转换]

    E --> F[CSS Variables]
    E --> G[Swift UIColor]
    E --> H[Android XML]
    E --> I[Flutter ThemeData]
    E --> J[React Native]

    subgraph AI解析模块
        B1[视觉模型:提取色值/字号]
        B2[文本模型:理解命名语义]
        B3[规则引擎:校验一致性]
        B1 --> B2 --> B3
    end

    A --> B1

2.2 DTCG 设计 Token 标准格式

import json
from dataclasses import dataclass, asdict
from typing import Any

@dataclass
class DesignToken:
    """符合 DTCG (Design Tokens Community Group) 规范的 Token 定义"""
    value: Any
    type: str          # color, dimension, fontFamily, ...
    description: str = ""
    extensions: dict = None

def create_token_set() -> dict:
    """创建符合 DTCG 规范的 Token 集"""
    return {
        "color": {
            "brand": {
                "primary": DesignToken(
                    value="#3B82F6",
                    type="color",
                    description="品牌主色,用于主要按钮和链接",
                ),
                "secondary": DesignToken(
                    value="#10B981",
                    type="color",
                    description="品牌辅色,用于成功状态和辅助操作",
                ),
            },
            "neutral": {
                "50": DesignToken(value="#FAFAFA", type="color"),
                "100": DesignToken(value="#F5F5F5", type="color"),
                "900": DesignToken(value="#171717", type="color"),
            },
        },
        "dimension": {
            "spacing": {
                "xs": DesignToken(value="4px", type="dimension"),
                "sm": DesignToken(value="8px", type="dimension"),
                "md": DesignToken(value="16px", type="dimension"),
                "lg": DesignToken(value="24px", type="dimension"),
            },
            "radius": {
                "sm": DesignToken(value="4px", type="dimension"),
                "md": DesignToken(value="8px", type="dimension"),
                "lg": DesignToken(value="16px", type="dimension"),
            },
        },
    }

三、AI 驱动的 Token 自动生成方案

3.1 品牌规范文档解析

class BrandDocParser:
    """从品牌规范文档中提取设计 Token"""

    def __init__(self, vision_client, llm_client):
        self.vision = vision_client
        self.llm = llm_client

    def parse_brand_pdf(self, pdf_path: str) -> dict:
        """解析品牌规范 PDF,提取色值、字号、间距等 Token"""
        # 1. 将 PDF 转为图片逐页分析
        pages = self._pdf_to_images(pdf_path)

        all_tokens = {}
        for page_img in pages:
            # 2. 视觉模型提取色板和字号信息
            visual_info = self.vision.analyze(
                page_img,
                prompt="提取此品牌规范页面中的所有色值(HEX格式)、字号(px/pt)和间距值。"
                       "输出JSON格式:{colors: [{name, hex}], fonts: [{name, size}], spacing: [{name, value}]}"
            )

            # 3. LLM 语义化命名
            named_tokens = self._assign_semantic_names(visual_info)
            all_tokens.update(named_tokens)

        return all_tokens

    def _assign_semantic_names(self, raw_tokens: dict) -> dict:
        """使用 LLM 为提取的原始值分配语义化名称"""
        prompt = f"""为以下设计属性分配语义化 Token 名称。

原始色值: {raw_tokens.get('colors', [])}
原始字号: {raw_tokens.get('fonts', [])}
原始间距: {raw_tokens.get('spacing', [])}

命名规范:
- 颜色:使用 语义.变体 格式,如 brand.primary, neutral.100, semantic.error
- 字号:使用 用途.层级 格式,如 text.heading.lg, text.body.md
- 间距:使用 尺寸等级 格式,如 spacing.xs, spacing.md

输出 DTCG 兼容的 JSON 格式。"""

        response = self.llm.chat(prompt)
        return json.loads(response)

3.2 多平台 Token 转换引擎

class TokenTransformer:
    """将 DTCG Token 转换为各平台代码格式"""

    def to_css_variables(self, tokens: dict) -> str:
        """转换为 CSS 自定义属性"""
        lines = [":root {"]
        self._flatten_tokens(tokens, lines, prefix="")
        lines.append("}")
        return "\n".join(lines)

    def _flatten_tokens(self, obj: dict, lines: list, prefix: str):
        """递归展平 Token 树为 CSS 变量"""
        for key, value in obj.items():
            var_name = f"{prefix}-{key}" if prefix else f"--{key}"
            if isinstance(value, dict) and 'value' in value:
                # 叶子节点:Token 定义
                css_value = self._to_css_value(value['value'], value['type'])
                lines.append(f"  {var_name}: {css_value};")
            elif isinstance(value, dict):
                # 分支节点:递归
                self._flatten_tokens(value, lines, var_name)

    def _to_css_value(self, value: str, token_type: str) -> str:
        """将 Token 值转换为 CSS 合法值"""
        if token_type == "color":
            return value  # HEX 值直接使用
        elif token_type == "dimension":
            return value  # px 值直接使用
        return value

    def to_swift(self, tokens: dict) -> str:
        """转换为 Swift UIColor 扩展"""
        lines = ["import UIKit", "", "extension UIColor {"]
        colors = self._extract_by_type(tokens, "color")

        for name, value in colors.items():
            swift_name = name.replace("-", "").replace(".", "_").lower()
            r, g, b = self._hex_to_rgb(value)
            lines.append(
                f"    static let {swift_name} = UIColor("
                f"red: {r/255:.3f}, green: {g/255:.3f}, blue: {b/255:.3f}, alpha: 1.0)"
            )

        lines.append("}")
        return "\n".join(lines)

    def to_android_xml(self, tokens: dict) -> str:
        """转换为 Android colors.xml"""
        lines = ['<?xml version="1.0" encoding="utf-8"?>', '<resources>']
        colors = self._extract_by_type(tokens, "color")

        for name, value in colors.items():
            android_name = name.replace("-", "_").replace(".", "_").lower()
            lines.append(f'    <color name="{android_name}">{value}</color>')

        lines.append('</resources>')
        return "\n".join(lines)

    def _extract_by_type(self, tokens: dict, target_type: str) -> dict:
        """按类型提取所有 Token"""
        result = {}
        for key, value in tokens.items():
            if isinstance(value, dict):
                if 'type' in value and value['type'] == target_type:
                    result[key] = value['value']
                else:
                    result.update(self._extract_by_type(value, target_type))
        return result

    @staticmethod
    def _hex_to_rgb(hex_color: str) -> tuple:
        hex_color = hex_color.lstrip('#')
        return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

3.3 一致性校验与自动修复

class TokenConsistencyChecker:
    """校验 Token 集的一致性和完整性"""

    def check(self, tokens: dict) -> list:
        issues = []

        # 1. 检查色值间距是否均匀(中性色梯度)
        neutral_colors = self._get_neutral_scale(tokens)
        if neutral_colors:
            lightness_values = [self._perceived_lightness(c) for c in neutral_colors]
            diffs = [lightness_values[i+1] - lightness_values[i]
                     for i in range(len(lightness_values) - 1)]
            if max(diffs) / (min(diffs) + 1e-8) > 2.0:
                issues.append({
                    'type': 'UNEQUAL_SPACING',
                    'message': '中性色梯度间距不均匀,建议调整明度分布',
                    'detail': f'最大间距差: {max(diffs):.1f}, 最小: {min(diffs):.1f}',
                })

        # 2. 检查语义色是否有对应的暗色变体
        semantic_colors = self._get_semantic_colors(tokens)
        for name, value in semantic_colors.items():
            dark_name = name.replace('light', 'dark')
            if dark_name not in semantic_colors:
                issues.append({
                    'type': 'MISSING_DARK_VARIANT',
                    'message': f'语义色 {name} 缺少暗色变体',
                })

        # 3. 检查字号是否形成合理的模块化比例
        font_sizes = self._get_font_sizes(tokens)
        if font_sizes:
            ratios = [font_sizes[i+1] / font_sizes[i]
                      for i in range(len(font_sizes) - 1)]
            if any(r < 1.1 or r > 1.5 for r in ratios):
                issues.append({
                    'type': 'IRREGULAR_TYPE_SCALE',
                    'message': '字号比例不一致,建议使用 1.2-1.333 的模块化比例',
                })

        return issues

    @staticmethod
    def _perceived_lightness(hex_color: str) -> float:
        r, g, b = [int(hex_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)]
        return 0.299 * r + 0.587 * g + 0.114 * b

四、边界分析与架构权衡

4.1 视觉模型提取的精度限制

品牌规范 PDF 中的色值可能以 CMYK 格式标注,视觉模型从截图中提取的 RGB 值与原始 CMYK 值存在色域转换偏差。关键品牌色应从源文件(Figma/Sketch)直接导出,视觉模型仅用于辅助提取和校验。

4.2 语义命名的歧义性

同一色值在不同上下文中可能有不同语义——#EF4444 既是"错误红"也是"促销红"。LLM 的语义命名依赖上下文理解,可能产生不符合团队约定的命名。需要提供命名规范作为约束条件,并在生成后由设计师审核。

4.3 多平台格式差异

CSS 变量支持运行时动态切换(暗色模式),Swift 和 Android 的颜色资源是编译时常量。跨平台 Token 转换时,需要为支持动态切换的平台生成额外的主题变体文件,这增加了维护复杂度。

4.4 版本同步问题

设计 Token 在 Figma 中更新后,代码中的 Token 可能未同步更新,导致设计与实现不一致。需要建立 Figma Plugin → Token 文件 → 代码的自动化同步管线,而非依赖人工同步。

五、总结

AI 生成设计 Token 的核心价值在于将品牌规范文档到代码变量的"手工翻译"自动化。视觉模型从品牌文档提取色值和字号,LLM 分配语义化命名,转换引擎输出多平台代码格式,一致性校验器检测间距不均匀、暗色变体缺失和字号比例失调等问题。工程实践中需注意视觉提取的精度限制、语义命名的歧义性、多平台格式差异和版本同步问题。AI 生成最适合作为 Token 初始化和批量更新的加速工具,关键品牌色的准确性仍需设计师确认。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐