2026年7月2日 周四晚上19:30,报名腾讯会议了解“如何构建自进化的动态知识库(Brain)”(限30人)
免费POC, 零成本试错
FDE知识库

FDE知识库

学习大模型的前沿技术与行业落地应用


收藏

从零打造 AI 设计智能体

发布日期:2024-10-18 18:34:12 浏览次数: 3175
作者:腾讯技术工程

微信搜一搜,关注“腾讯技术工程”


在设计产业中,虽然设计师和产品经理的角色各有不同,但两者之间的界限正变得日益模糊。设计师在考虑用户体验和产品功能的同时,产品经理也被要求具备一定的设计思维。今天,我们将探讨在设计思维转型为产品开发的过程中,如何打造一款简化创意流程的灵感设计插件工具。

在设计领域,捕捉和实现灵感通常涉及一系列复杂的步骤。从概念草图到最终的视觉呈现,设计师必须对每一个环节进行细致的打磨。但这个过程常常受到技术限制和沟通障碍的制约。作为一名追求效率的设计师,我一直在寻找一种工具,能够帮助我提升工作效率。


如何将设计思维转型为产品开发,研发一款简化创意流程的灵感工具?

因为设计师在创意过程中经常面临技术限制和沟通难题,所以我尝试去开发一款能够简化创意流程的灵感工具。这款工具将在短时间内帮助设计师将新概念的视觉效果快速呈现于眼前,加速品牌形象的更新和用户界面设计的迭代过程。

具体来说,希望具备以下能力:

  1. 快速捕捉和展示设计灵感,帮助设计师迅速将概念转化为视觉设计稿。
  2. 支持快速探索和更新视觉语言,配合产品经理快速确定视觉风格方向。
  3. 在新网站构建过程中,能够帮助快速迭代和测试不同的设计方案,加速界面设计流程。

在技术评估方面,我们需要考虑:

  1. 工具的界面设计是否直观易用,以适应不同水平的设计师。
  2. 技术实现的可行性,包括所需的算法、数据处理能力和图形渲染技术。
  3. 产品的市场定位和目标用户群体,以及与现有竞品的差异化优势。
  4. 开发成本、时间和资源的预估,确保项目的经济可行性。

这样我们可以确保这款灵感工具不仅能够满足设计师的核心需求,而且在技术和市场层面都是可行的。

通过DesignGenie插件可以快速捕获网页上的创意,将其转化成 figma 等工具中可以操作的设计资源。

为设计师提供了一种全新的工作方式:

安装插件:

在Figma社区里搜索DesignGenie并运行。

快速直达:

DesignGenie 插件链接: https://www.figma.com/community/plugin/1398619471957761832/designgenie

"DesignGenie" —— 功能亮点:

1.无限导入:用户无限次网站导入,大幅提高工作效率。2.多视口导入:支持多视口,满足不同设备的设计需求。3.主题模式:深色模式/浅色模式。(源网页是否支持) 4.编辑与协作:在Figma编辑网页元素,团队协作实时反馈。5.提炼功能:提炼生成视觉设计稿页面中优秀的图标等元素。6.字体配置:生成同时可以快速匹配需要替换的字体样式。7.高度还原:保留原有网页布局、样式,高度还原实际效果。8.协作便捷:支持团队共享,提升产设研协作效率。

高级功能(此功能目前处于灰度测试阶段):

1.可创建自动布局。2.导入需要登录的页面:对于需要登录状态的页面,用户可以使用DesignGenie Chrome插件。登录并访问所需网页后,点击插件图标,下载.h2d文件,然后将该文件拖放到Figma插件中。

DesignGenie插件核心工作就是获取到html结构和对应的资源,并将这些内容转化成设计稿结构。


因此主要任务分为 获取、解析、生成 三个阶段:

1. 第一个阶段获取html

我们在服务端使用 playwright 工具抓取目标页面内容。那么我们是如何组织html的内容和结构的?

通过 nodeType 和 tagName 对 elem.node 进行分类, 通过 elem.childNodes 进行子节点获取和计算嵌套关系。

如下下图所示,我们将 html.dom 主要分为了 FrameObj, TextObj, SvgObj, ImageObj, VideoObj,RectangleObj 6种类型。

1.1 FrameObj

FrameObj 对应 figma中的 FrameNode (The frame node is a container used to define a layout hierarchy. It is similar to

in HTML. )用于于定义布局层次结构的容器 。因此主要的属性包含 Rect(x,y,width,height)、children、styles。


Rect属性如何获取?

通过 Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,根据其提供了元素的大小及其相对于视口的位置我们可以计算得到 Rect。



Children属性如何获取?

通过 Node.childNodes 返回包含指定节点的子节点的集合处理 div的嵌套关系。

Styles属性如何获取?

通过使用 window.getComputedStyle(element, [pseudoElt]); 我们能够获取到所需的全部Style属性。并且会将 transform:rotate、shew 等属性计算后给出 matrix 属性,通过matrix直接对应figma的 relativeTransform。

1.2 TextObj

nodeType === TEXT_NODE (3) 是一个文本节点,文本 ITextObj 继承了 IBaseObj。在Rect内容的基础上 记录了 value 和 font。这里的 value 一般从 TextNode.nodeValue 取。但是在input,select,text 中 TextNode.nodeValue空,会兜底获取 placeholder.

1.3 SvgObj

nodeType === TEXT_NODE (1) && elem.tagName == 'SVG' 会将 elem 分类为一个 svgObj. 同时记录 svg = elem.outerHTML

1.4 ImageObj & VideoObj

image & video 的获取过程类似,获取对应标签的 src属性即可。值得注意的是在 video 中 src 属性可能包裹在 source 标签下。

1.5 RectangeleObj

这是一个特殊分类,用来记录 elem 中的伪元素。在伪元素中无法通过elem.getBoundingClientRect() 直接获取对应的 x, y。因此需要伪元素的 Rect 需要根据父级&定位属性进行叠加计算。

首先根据 position 进行分类:

  1. 相对定位节点根据父级 Rect 属性进行 padding 的相对位置计算。
  2. 在 absolute 定位中 通过 marginLeft + left ,marginTop + top ... 的方式计算定位。会存在各方向都有margin值需要确定优先级 left >right , top >bottom, 及left存在只需叠加 marginleft即可,忽略right属性。同时要处理好 % 和 px 单位的转换。
  3. 在 fixed 定位中 与absolute计算过程一致,父级是在最外层 html.Node 定位上进行计算。
1.6 资源内容assets

在html中我们通过获取了需要的内容资源。如字体 和图片等。部分网站的资源在插件端通过src请求可能会有跨域问题,因此通过playwright的route监控直接在服务端直接加载到对应内容处理好 content & mimeType 保持在 assets 中直接发给插件端。

page.route('**/*.{woff,woff2,ttf,otf,eot,svg,jpg,jpeg,png,webp,gif}**',
 () => { 
   // 加载资源转u8a保存
  }
)

export interface Asset {
  content?: uint8array;
  mimeType: image/svg+xml | image/png | image/jpeg | image/gif | image/webp | image/jpg;
  base64Encoded?: true;
}

2. 第二个阶段解析 DSL

解析层是本插件的核心部分:在这个阶段对 获取到的 htmlObj 进行计算映射到 figma 的node属性。如下图:我们将根据获取到的内容 分类成 IFrameNode、IRectangleNode、ITextNode 以及figma对应的 Radius、Stroke、Fills 等属性。



2.1 Positon 属性

在获取阶段的 obj 都是从左上角{ x: 0, y: 0}, 开始计算位置的。用 FrameObj.children 记录层级关系。在 figma FrameNode 中 子级节点的x,y 是相对父级进行定位。通过递归 IFrameNode的子节点 计算相对父级的x,y , 最外层节点在生成时相对画布给定可见区 x,y,就能得到 所有节点在 figma 中的位置。

2.2 Fill 属性

在填充属性已经支持实现了 SolidPaint,GradientPaint,ImagePaint 和 VideoPaint 所有的figma Paint类型。

SolidPaint属性的计算

interface SolidPaint {
  readonly type: 'SOLID'
  readonly color: RGB
  readonly opacity?: number
}

填充在 TextNode 中通过 styles.color + styles.opacity计算,其它节点通过 styles.backgroundColor+ styles.opacity 计算。

GradientPaint属性的计算

interface GradientPaint {
  readonly type: 'GRADIENT_LINEAR' | 'GRADIENT_RADIAL' | 'GRADIENT_ANGULAR' | 'GRADIENT_DIAMOND'
  readonly gradientTransform: Transform
  readonly gradientStops: ReadonlyArray<ColorStop>
}

渐变需要计算的核心属性是 gradientTransform 和 gradientStops。

如下图所示:先对 渐变css部分进行拆词:

expect(parseGradient('linear-gradient(to left, red, blue)')).toMatchInlineSnapshot(`
      [
        {
          "colorStops": [
            {
              "rgba": {
                "a": 1,
                "b": 0,
                "g": 0,
                "r": 255,
              },
              "type": "color-stop",
            },
            {
              "rgba": {
                "a": 1,
                "b": 255,
                "g": 0,
                "r": 0,
              },
              "type": "color-stop",
            },
          ],
          "gradientLine": {
            "type": "side-or-corner",
            "value": "left",
          },
          "type": "linear-gradient",
        },
      ]
    `
);

gradientStops:通过下面这个结构获取转化:

export type ColorStop = {
  type'color-stop';
  rgba: RgbaColor;
  position?: Length;
};

export type AngularColorStop = {
  type'angular-color-stop';
  rgba: RgbaColor;
  angle?: Length | [Length, Length];
};

export type ColorHint = {
  type'color-hint';
  hint: Length;
};

gradientTransform: transform核心代码如下图所示,通过获取到的 渐变拆词对象计算对应的 缩放,角度和偏移。通过figma中初始化tranfrom位置获取到对于的 gradientTransform。

import { rotate, translate, compose, scale } from 'transformation-matrix';
  
  const gradientLength = calculateLength(parsedGradient, width, height);
  const [sx, sy] = calculateScale(parsedGradient);
  const rotationAngle = calculateRotationAngle(parsedGradient);
  const [tx, ty] = calculateTranslationToCenter(parsedGradient);

  const gradientTransform = compose(translate(00.5), scale(sx, sy), rotate(rotationAngle), translate(tx, ty));

ImagePaint属性的计算

interface ImagePaint {
  readonly type: 'IMAGE'
  readonly scaleMode: 'FILL' | 'FIT' | 'CROP' | 'TILE'
  readonly imageHash: string | null
}

imageHash的获取:通过 figma Api.createImage(data: Uint8Array): { hash, width, height } 上传图片可以获取 image在figma fill 中对应的 imageHash 。

scaleMode的计算:根据 style.backgroundSize 计算 scaleMode。

if (backgroundSize === 'cover') {
      // 图像将被缩放以覆盖整个容器
      this.fills = [
        {
          type'IMAGE',
          scaleMode'FILL',
          imageHash: imageHash,
        },
      ];
    } else if (backgroundSize === 'contain') {
      // 图像将保持其比例缩放以适应容器的尺寸,整个图像会显示在容器内,可能会有空白区域
      this.fills = [
        {
          type'IMAGE',
          scaleMode'FIT',
          imageHash: imageHash,
        },
      ];
    } else {
      // 通过 background-size 设置图像的缩放,并使用 background-position 来控制图像的位置,裁剪掉超出容器的部分
      this.fills = [
        {
          type'IMAGE',
          scaleMode'CROP',
          imageHash: imageHash,
        },
      ];
    }

VideoPaint属性的计算

interface VideoPaint {
  readonly type: 'VIDEO'
  readonly scaleMode: 'FILL' | 'FIT' | 'CROP' | 'TILE'
  readonly videoHash: string | null
}

videoPaint的计算过程与 ImagePaint 类似,通过 createVideoAsync(data: Uint8Array): Promise获取对应的 videoHash。

2.3 Stroke 属性

在获取阶段我们能够取到 borderColor, borderWidth, borderStyle。通过 borderStyle 计算 strokeAlign('CENTER' | 'INSIDE' | 'OUTSIDE'), borderColor 计算 strokes(SolidPaint[]), borderWidth 计算 strokeWeight(number)。

2.4 Effects 属性

通过 boxShadow 计算Effects 属性,目前支持 InnerShadowEffect 和 DropShadowEffect。

{
  readonly type: 'INNER_SHADOW' | 'DROP_SHADOW'
  readonly color: RGBA
  readonly offset: Vector
  readonly radius: number
  readonly spread?: number
  readonly visible: boolean
  readonly blendMode: BlendMode
  readonly boundVariables?: {
    [field in VariableBindableEffectField]?: VariableAlias
  }
}

我们在获取阶段取到的 "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgb(217, 219, 227) 0px 0px 0px 1px inset" ,为了方便计算我们实现了 parseCSSNodes 对 css 内容进行拆词。从拆词内容 shadowDetails.push(extractPixelValue(node['value']));const [offsetX, offsetY, blurRadius, spreadRadius] = shadowDetails计算对应属性。

const boxShadow =
      'rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgb(217, 219, 227) 0px 0px 0px 1px inset';
    const nodes = parseCSSNodes(boxShadow);
    expect(nodes).toMatchInlineSnapshot(`
      [
        {
          "nodes": [
            {
              "sourceEndIndex": 15,
              "sourceIndex": 5,
              "type": "rgba",
              "value": "rgba(0, 0, 0, 0)",
            },
          ],
          "sourceEndIndex": 16,
          "sourceIndex": 0,
          "type": "function",
          "value": "rgba",
        },
        {
          "sourceEndIndex": 17,
          "sourceIndex": 16,
          "type": "space",
          "value": " ",
        },
        {
          "sourceEndIndex": 20,
          "sourceIndex": 17,
          "type": "word",
          "value": "0px",
        },
        ...
        {
          "nodes": [
            {
              "sourceEndIndex": 85,
              "sourceIndex": 72,
              "type": "rgb",
              "value": "rgb(217, 219, 227)",
            },
          ],
          "sourceEndIndex": 86,
          "sourceIndex": 68,
          "type": "function",
          "value": "rgb",
        },
 
        {
          "sourceEndIndex": 108,
          "sourceIndex": 103,
          "type": "word",
          "value": "inset",
        },
      ]
    `
);

3. 第三个阶段生成 Design

基于解析阶段生产的DSL 数据结构,我们能够通过figma.api 直接生成设计稿。

下图是生成部分的部分代码, 解析后的数据结构进行递归创建 ,使用 figma.currentPage.appendChild 和 frameNode.appendChild 设置层级关系。

figma.createFrame()
figma.createText()
figma.createNodeFromSvg()
figma.createRectangle()
export const createFigmaNode = async (node: IFrameNode, parentNodeId: string | undefined) => {
  if (node.type === 'FRAME') {
    const nodeId = await toFrameNode(node, parentNodeId);
    node.children.forEach(async (child: IDslNode) => {
        await createFigmaNode(child as IFrameNode, nodeId);
    });
  }
  if (node.type === 'TEXT' && parentNodeId) {
    await toTextNode(node, parentNodeId);
  }
  if (node.type === 'SVG' && parentNodeId) {
    await toSvgNode(node, parentNodeId);
  }
  if (node.type === 'RECTANGLE' && parentNodeId) {
    await toRectangleNode(node, parentNodeId);
  }
};

职业转型是一段充满挑战和成长的旅程,我相信随着时间的推移,我会有新的认知和理解。虽然我已经踏上了产品经理的职业道路,但我仍然深深热爱设计。设计带给我的创意和满足感是我灵魂的一部分。在未来的职业生涯中,我希望能够继续发挥设计师的创造力,同时运用产品经理的战略思维,打造出既满足用户需求又能实现商业价值的优秀产品。


总结下感悟:

平衡设计和产品思维设计师注重细节和用户体验,而产品经理则需要考虑商业价值和用户需求之间的平衡。这要求我在创意和实用性之间找到最佳平衡点,同时确保每一个设计决策都能带来实际的商业收益。

学习新技能除了设计之外,我还需要学习许多新技能,包括项目管理、数据分析、市场策略等。这些新技能的掌握不仅需要时间和精力,还需要在实际项目中不断实践和改进。

适应新的角色作为产品经理,我需要为产品的最终结果负责。这意味着在必要时,我需要做出艰难的决策,比如调整产品方向或终止某些不具备商业前景的项目。这种责任感和决策压力是我在设计师岗位上不曾经历过的。

用户体验与用户价值的平衡设计师追求卓越的用户体验,而产品经理则更注重用户价值与商业价值的交换。用户体验是价值交换过程中的润滑剂,但其重要性取决于产品类别和具体情境。例如,在阅读类产品中,字体和行间距的优化对提升用户体验至关重要,而在工具类产品中,这些细节对用户价值的贡献可能较小。

资源分配与试错对于团队而言,在资源有限的情况下,如何合理分配资源进行试错,以及如何确定用户对产品的需求和价值认可,是持续思考和权衡的重点。这要求我在有限的资源条件下,制定出最有效的产品策略,以最大化团队的工作效率和产品的市场表现。

市场定位与用户识别产品设计不仅限于创意实现,更重要的是市场定位、用户识别、产品市场契合度(PMF)的验证和产品策略的调整。我的目标是缩短从创意到市场反馈的周期,提高产品的迭代速度和市场响应能力。

期待在未来的道路上,和大家一起探索未知,一起成长。


53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询

扫码登录
登录即表示您同意《53AI网站服务协议》
服务协议

欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。

在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。

一、 定义

本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。

会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。

知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。

二、 账号注册与登录

登录方式:本网站支持以下登录方式,您可根据实际情况选择:

微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。

手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。

账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。

实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。

未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。

三、 服务内容与规范

知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。

服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。

禁止行为:您在使用服务时不得实施以下行为:

利用技术手段批量爬取、下载、转存知识库内容;

将知识库内容用于商业目的或未经授权地向第三方传播;

干扰本网站正常运行或侵犯其他用户合法权益;

发布违法违规信息或从事违反公序良俗的活动。

四、 知识产权声明

权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。

有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。

侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。

五、 个人信息保护

我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。

您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。

您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。

六、 免责声明

内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。

不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。

第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。

七、 违约责任

如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。

如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。

八、 法律适用与争议解决

本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。

因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。

九、 其他

本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。

本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。

我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。


已查阅