day14-Codex对接figma-数字人工作流

1 Html版原型图导入Figma微调

把html版本的原型图--》导入到figma中

可以一个个使用html.to.design导入,太麻烦

我们一次性导入

1.1 把所有页面平铺在index.html中

# 使用提示词把所有页面平铺到index.html中
把所有页面,平铺展示在index.html中,不增加其它任何内容,不要引用每个页面,而是把所有页面代码汇总到一起

1.2 使用 html.to.design 插件导入

# 在figma中使用 html.to.design 插件导入
	-插件有时候会打不开
    	-1 白天尝试
        -2 换个账号尝试
        -3 使用网页版
        -4 使用老师分享的:https://www.figma.com/proto/CwjUiNxMAqVIl3gfEZDxxY/%E6%95%B0%E5%AD%97%E4%BA%BA?node-id=0-1&t=mUdiOkavp689oXGs-1
        -5 直接在社区中找到,复制成自己的:https://www.figma.com/community/file/1669251192772382519
  		-6 把老师的  数字.fig 直接导入

image-20260812130730766

image-20260812130757253

image-20260812135937487

image-20260812141235545

image-20260812152716603

image-20260812152906741

1.3 微调设计图

2 Codex-MCP之Figma

2.1 Figma会员功能

# 1 免费 (免费版)账号,不能开启 Desktop MCP Server
Desktop MCP(本地 127.0.0.1:3845/mcp)功能本身要求:Pro/Organization 付费账号,并且是 Dev / Full 席位才能打开开关,免费账号在 Dev Mode 面板里看不到这个开关,按钮是灰色不可启用

# 2 免费 Starter 套餐:每月 6 次工具调用(tool‑call)
不管 Remote MCP,还是理论上的 Desktop MCP,只要是免费账号,读取类操作统一受这个每月 6 次限制,按月重置。
什么叫 1 次 tool‑call:
Codex 向 Figma 读取图层、读取样式、读取组件树,每执行一次 API 操作就消耗 1 次配额。
读取一个中等页面,经常一次性消耗 3‑5 次,基本一次会话就把 6 次直接耗尽,只能简单测试,无法干活。
写回画布的部分接口(生成设计、创建节点)beta 阶段不计入这 6 次读取配额,但免费账号主要限制是读取设计数据

image-20260812152007733

账号席位 MCP 读取调用额度 能否开启 Desktop MCP 本地服务
免费 Starter(任意席位) 6 次 / 月 ❌不能开启
Pro 计划 Dev / Full 席位 200 次 / 天,10 次 / 分钟 ✅可以开启
Org 计划 Dev / Full 席位 200 次 / 天,15 次 / 分钟 ✅可以开启
# 1 官方:每月只能使用6次
	-如果Codex++ 版本升到 v1.2.40+;新版本调整了纯 API 的插件列表过滤逻辑,不再把 OAuth 插件渲染出来,figma不显示
    -
# 2 第三方:请求过于频繁会被限制
	

2.2 直接下载figma插件

# 如果在插件中能搜到,直接安装

image-20260812171214247

2.3 手动添加figma的mcp服务

# 1 让codex自己配置

## 1.1 让Codex自己配置
按如下配置,帮我配置figma的mcp服务
[mcp_servers.figma]
url = "https://mcp.figma.com/mcp"
http_headers = { "X-Figma-Region" = "us-east-1" }
## 1.2 使用命令:codex mcp login figma 帮我触发 Figma 的 OAuth 授权流程
# Codex会全局搜索codex这个命令,找到后执行,需要些时间
# 我的在这个目录下:C:\Users\30633\AppData\Local\OpenAI\Codex\bin\68de26ad08be95cd
codex mcp login figma
# 保存到:C:\Users\30633\.codex\secrets\mcp_oauth.age

## 1.3 检查 figma mcp 是否可用

## 1.4 操作figma--》最终回到figma的草稿中可以看到【免费用户次数有限】
帮我按照这个图片,创建一个figma的设计稿



# 2 直接修改 config.toml
'''
Windows:%USERPROFILE%\.codex\config.toml
Mac:~/.codex/config.toml
'''

[mcp_servers.figma]
url = "https://mcp.figma.com/mcp"
http_headers = { "X-Figma-Region" = "us-east-1" }



# 3 注销figma mcp
C:\Users\30633\AppData\Local\OpenAI\Codex\bin\68de26ad08be95cd\codex logout login figma

image-20260812160422291

image-20260812161703924

image-20260812170603389

image-20260812170637494

image-20260812171058243

2.4 第三方:figma-mcp插件

# 1 地址:https://github.com/GLips/Figma-Context-MCP
# 2 让codex自己安装:我们需要先获取figma-api-key
	-figd_kw_z9KrNae_UW1hnbQ9mt1OhjHQ2TA0hhqhmoJo5
# 3 让codex自己安装
帮我安装这个mcp服务
{
  "mcpServers": {
    "Framelink MCP for Figma": {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "figma-developer-mcp", "--figma-api-key=figd_kw_z9KrNae_UW1hnbQ9mt1OhjHQ2TA0hhqhmoJo5", "--stdio"]
    }
  }
}

# 4 需要安装node环境

# 5 测试:

使用figma-developer MCP读取下面Figma画板链接,根据画板内容,生成微信小程序页面 (只生成我给你的页面)
Figma链接:https://www.figma.com/design/CwjUiNxMAqVIl3gfEZDxxY/%E6%95%B0%E5%AD%97%E4%BA%BA?node-id=1-475&t=rVtEov8o9jYvTH2L-4
约束:
1.输出完整4个文件:wxml / wxss / js / json;
2.全部rpx单位,Figma中1px=2rpx,禁止px;
3.优先flex布局,尽量消除position:absolute;必须定位才保留;
4.图标输出SVG,路径统一写 "/assets/icons/xxx.svg";照片、复杂插画输出PNG‑2x,路径/assets/images/xxx.png;
5.禁止使用Figma临时http图片链接,输出图片导出清单;小程序svg全部用image组件引入,不要把svg源码写进wxml;
6.只修改UI骨架,业务接口逻辑保持不变;bindtap点击事件空占位;
7.适配底部safe‑area‑inset‑bottom安全区;
8.wxss抽class,不引入第三方UI组件库;
9.输出完成列出人工修复风险清单。

image-20260812171620125

image-20260812172828200

image-20260812173820714

2.4 坑点

# 1 figma‑context‑mcp(社区开源 MCP)免费用户速率限制
不是 Figma 官方 Remote MCP Server,这个是第三方开源项目,走 Personal Access Token + Figma REST API,不走官方 MCP 托管服务。
# 2 免费 Starter 用户,双重限制都存在:
Tier‑1 接口硬配额(最致命):GET file / file‑nodes / images 这类核心读取接口,免费账号每月最多 6 次请求,用完直接 429,每月重置。
哪怕是社区 MCP、本地跑、加缓存,底层调用 Figma 官方 REST API,该限制由 Figma 侧强制,MCP 本身无法绕过。
哪怕你的 token 是 Pro 账号,只要访问的文件归属免费 Starter 团队,依然会被锁到每月 6 次。
# 3 分钟级速率限制:
Tier‑1(文件 / 节点 / 图片):免费账号没有可用分钟配额,直接受上面每月 6 次约束;
Tier‑2/Tier‑3:免费账号还有 5‑10次/分钟 的瞬时限流,短时间爆发请求会报 429 Too Many Requests。
figma‑context‑mcp 自身做了双层缓存(内存 LRU + 磁盘持久缓存),目的是减少 API 调用次数,可以缓解消耗,但不能突破 Figma API 底层配额。缓存命中时不消耗 Figma 计数;换页面、新节点、缓存过期依然会消耗配额。

2.5 Node环境安装

# 1 很多skill 基于Python或者Node环境运行
	-Python我们都安装了
    
# 2 我们安装node环境
	-下载:https://nodejs.org/zh-cn/download
    -安装:一路下一步
    -验证:打开cmd ,输入 
    node -version

image-20260804175051305

image-20260804175205666

image-20260804175215905

image-20260804175227818

image-20260804175251513

2.6 突破频率限制插件

https://github.com/gethopp/figma-mcp-bridge

3 创建Coze-口播数字人工作流

image-20260812173604281

3.1 开始

image-20260812184401774

3.2 创建飞影口播

https://www.coze.cn/store/plugin/7472321639192084506

image-20260812184438044

3.3 循环

image-20260812184519100

3.3.1 代码休息一下

import time
async def main(args: Args) -> Output:
    params = args.params
    # 睡15s
    time.sleep(15)
    ret: Output = {
        "out": "",
    }
    return ret

image-20260812184555455

3.3.2 查询数字人结果

image-20260812184637529

3.3.3 选择器

image-20260812184707149

3.3.4 终止或继续循环

3.4 代码取出视频地址

def filter_first_non_empty(data):
    # 确保输入是列表
    if not isinstance(data, list):
        raise ValueError("Input data.output must be a list.")
    
    # 找到第一个非空的元素
    for item in data:
        # 确保元素是字符串类型并去除空白后检查
        if isinstance(item, str) and item.strip() != "":
            return item
    
    # 如果没有找到非空元素,返回空字符串
    return ""


async def main(args: Args) -> Output:
    params = args.params

    # 构建输出对象
    ret: Output = {
        "out": filter_first_non_empty(params["input"])
   
    }
    return ret

image-20260812184750260

3.5 结束

image-20260812184817045

4 后端环境搭建

Codex可以帮我们开发后端项目,但是最好,我们先把环境搭建好

在本地开发--》需要搭建本地的开发环境

直接让Codex写代码,然后写完,我们运行测试即可

4.1 Python环境

# 1 老师用的Python3.13.11
	-如果大家会装python解释器---》最好跟我一样
    -如果大家不会--》之前装了其他版本,用其他版本也可以--》3.14 
    
# 2 验证一下自己python版本
	#  验证-打开cmd,输入
    python --version
    Python 3.13.11 

image-20260616220148774

4.2 Django环境

# 1 Django 环境,可以不用搭建
# 2 Trae会帮我们搭建的

# 3 想搭建:按照如下步骤
	1 打开cmd
    2 pip install django==4.2
    
# 4 在我们本地的python解释器上,装了django框架
	python3.13+django4.2 ---》就可以开发Django项目了

image-20260616220728813

4.3 Python调用Coze口播工作流代码

# 1 同学们不需要懂--》是我+AI写的
	-纯AI写有bug---》python调用coze工作流--》API是Coze提供的--》有的ai读不到Coze提供的文档--》有时候Coze文档改了--》ai还是用老办法写,就会有bug
    -把coze文档的说明网页给AI不行吗?--》》可以解决百分之90的问题
    -Coze的官方文档--》可能更新不及时
    -
    
    
# 3 使用代码之前:
	1 获取换脸工作流的id号:7669850629418647590
    2 获取令牌:https://www.coze.cn/open/oauth/pats
    	-pat_v3bcwP3HOJxuUr7a6VWau4HEbFBMwFNzlHvy6vhQ3URBIaxMBFZOpDBuFUrEYBfE
    3 修改下面代码
    

image-20260616221321758

import requests
import json
import time
import os
from typing import Optional, Dict, Any
'''
个人访问令牌:https://www.coze.cn/open/oauth/pats
'''

class CozeAPI:
    """Coze API客户端,用于调用Coze的换脸工作流"""

    def __init__(self, api_key: str = None, workflow_id: str = None):
        self.api_key = api_key or 'pat_v3bcwP3HOJxuUr7a6VWau4HEbFBMwFNzlHvy6vhQ3URBIaxMBFZOpDBuFUrEYBfE'
        self.workflow_id = workflow_id or '7669850629418647590'
        self.base_url = 'https://api.coze.cn'
        self.upload_url = f"{self.base_url}/v1/files/upload"
        self.run_url = f"{self.base_url}/v1/workflow/run"
        self.headers = {'Authorization': f'Bearer {self.api_key}', 'Content-Type': 'application/json'}

    def _get_save_path(self, task_id: str) -> str:
        """内部方法:生成视频保存路径(精简路径处理逻辑)"""
        result_dir = os.path.join('coze_results')
        os.makedirs(result_dir, exist_ok=True)
        filename = f"result_{task_id}_{int(time.time())}.mp4"
        return os.path.join(result_dir, filename)

    def download_video(self, image_url: str, task_id: str) -> Optional[str]:
        """下载视频到本地,返回跨平台路径"""
        try:
            save_path = self._get_save_path(task_id)
            resp = requests.get(image_url, stream=True)
            resp.raise_for_status()

            with open(save_path, 'wb') as f:
                for chunk in resp.iter_content(chunk_size=8192):
                    f.write(chunk)

            print(f"视频下载成功: {save_path}")
            return save_path.replace('\\', '/')
        except Exception as e:
            print(f"视频下载失败: {str(e)}")
            return None

    def upload_file(self, file_path: str) -> str:
        """上传文件到Coze并获取文件ID"""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")

        try:
            print(f"开始上传文件: {file_path}")
            with open(file_path, 'rb') as f:
                files = {'file': (os.path.basename(file_path), f)}
                resp = requests.post(self.upload_url, headers={'Authorization': f'Bearer {self.api_key}'}, files=files)

            print(f"上传响应: {resp.status_code} | {resp.text}")
            result = resp.json()

            if resp.status_code == 200 and result.get('code') == 0 and 'data' in result:
                file_id = result['data'].get('id')
                if not file_id:
                    raise Exception(f"未提取到文件ID: {result}")
                print(f"文件上传成功,ID: {file_id}")
                return file_id
            raise Exception(f"文件上传失败: {result}")
        except Exception as e:
            print(f"文件上传异常: {str(e)}")
            raise

    def run_workflow(self, source_image: str, target_image: str) -> Dict[str, Any]:
        """运行Coze换脸工作流,返回任务信息"""
        try:
            # 批量上传文件
            source_id, target_id = self.upload_file(source_image), self.upload_file(target_image)

            payload = {
                "workflow_id": self.workflow_id,
                "parameters": {
                    "voice": json.dumps({"file_id": source_id}),
                    "video": json.dumps({"file_id": target_id}),
                    "text": "执行数字人操作"
                },
                "app_id": "",
                "is_async": True
            }

            print(f"调用工作流 {self.workflow_id}...")
            resp = requests.post(self.run_url, headers=self.headers, json=payload)

            if resp.status_code != 200:
                raise Exception(f"工作流调用失败: {resp.status_code} | {resp.text}")

            result = resp.json()
            if (result.get('code') == 0 or result.get('msg') == '') and (task_id := result.get('execute_id')):
                print(f"工作流调用成功,任务ID: {task_id}")
                return {'code': 0, 'msg': 'success',
                        'data': {'task_id': task_id, 'workflow_id': self.workflow_id, 'status': 'pending'}}

            raise Exception(f"工作流调用失败: {result.get('msg')} | {result}")
        except Exception as e:
            print(f"工作流调用异常: {str(e)}")
            raise

    def get_workflow_result(self, task_id: str) -> Dict[str, Any]:
        """查询工作流执行结果"""
        try:
            print(f"查询任务 {task_id} 结果...")
            # 简化URL拼接(移除模板字符串,直接格式化)
            query_url = f"{self.base_url}/v1/workflows/{self.workflow_id}/run_histories/{task_id}"
            resp = requests.get(query_url, headers=self.headers)

            if resp.status_code != 200:
                raise Exception(f"查询失败: {resp.status_code} | {resp.text}")

            result = resp.json()
            if result.get('code') != 0:
                raise Exception(f"查询结果失败: {result.get('msg')} | {result}")

            # 提取核心数据
            data = result.get('data', [{}])[0]
            exec_status = data.get('execute_status', '')

            if exec_status == 'Success':
                return self._parse_success_result(data, task_id)
            elif exec_status == 'Fail':
                return {'status': 'failed', 'error_message': '任务执行失败', 'task_id': task_id}
            return {'status': 'processing', 'task_id': task_id}

        except Exception as e:
            print(f"查询结果异常: {str(e)}")
            raise

    def _parse_success_result(self, data: Dict[str, Any], task_id: str) -> Dict[str, Any]:
        """内部方法:解析成功的任务结果(拆分复杂的解析逻辑)"""
        try:
            output = json.loads(data.get('output', '{}'))
            result_data = json.loads(output.get('Output', '{}'))
            video_url = result_data.get('output')

            if not video_url:
                return {'status': 'success', 'message': '任务成功但未返回视频', 'task_id': task_id}

            local_path = self.download_image(video_url, task_id)
            return {
                'status': 'success',
                'result_video_url': video_url,
                'local_video_path': local_path,
                'processing_time': data.get('processing_time', 0),
                'task_id': task_id
            }
        except (json.JSONDecodeError, KeyError) as e:
            print(f"解析输出失败: {str(e)}")
            return {'status': 'success', 'message': '任务成功但解析输出失败', 'task_id': task_id}


if __name__ == '__main__':
    try:
        coze_api = CozeAPI()
        voice_path, video_path = './lqz.mp3', './lqz.mp4'
        print("开始测试Coze数字人工作流...")

        # 运行工作流并获取任务ID
        run_result = coze_api.run_workflow(voice_path, video_path)
        print(f"工作流提交结果: {run_result}")

        if run_result.get('code') == 0:
            task_id = run_result['data']['task_id']
            print(f"任务ID: {task_id}")
            time.sleep(60)  # 等待任务处理

            # 查询结果
            result = coze_api.get_workflow_result(task_id)
            print(f"任务结果: {result}")

            # 精简结果判断逻辑
            status_map = {
                'success': f"测试成功!\nURL: {result.get('result_video_url')}\n本地路径: {result.get('local_video_path')}",
                'processing': "任务仍在处理中",
                'failed': f"任务失败: {result.get('error_message')}"
            }
            print(status_map.get(result.get('status'), " 未知状态"))
    except Exception as e:
        print(f" 测试失败: {str(e)}", exc_info=True)

image-20260616221700175

image-20260616221737309

4.4 前后端架构提示词

# 1 架构说明书是由公司架构师编写,规定,规范的--》后期开发人员按照这个说明书的要求开发项目
	-内部涵盖开发技术,数据库等等
    
# 2 使用Codex生成---》大家不用改了,如果大家不懂后端和小程序开发,好多技术点大家可能看不懂


根据01-需求分析/项目需求.md 和02-UI设计下所有html设计图,生成项目架构设计文档,包含后端架构文档和微信小程序架构文档

要求:

1.后端使用Python+Django 4 + Mysql8 + DjangoRestFramework等技术实现。

2.微信小程序端使用小程序原生框架。

3.后台管理使用django 自带admin和Simpleui美化。

4.前后端目录结构都一并生成,后端接口格式和参数和返回值格式也生成。

5.后端数据库设计:设计出项目所需表和字段(简要ER图),用户表使用Django自带的User表。

6.以Markdown格式生成并输出。

7.后端文档写入到文件中: 3-架构设计 /1-项目后端架构文档.md。

8.微信小程序文档写入文件: 3-架构设计 /2-微信小程序架构文档.md。
posted @ 2026-08-15 22:11  凫弥  阅读(22)  评论(0)    收藏  举报