让 AI 当自己的 UI 审查员:Codex 生成组件 + 视觉模型截图验证闭环实战

我让 AI 给我生成了一个登录表单。

代码看起来无懈可击——语义正确、类名规范、逻辑清晰。我满怀期待地跑起来,然后看到了这个:提交按钮跑到了输入框下面三屏的位置,像一个迷路的孩子站在空旷的白色页面里。

你有没有经历过这种事?让 AI 生成组件 → 复制粘贴 → 跑起来一看惨不忍睹 → 回去改 Prompt → 再试一次 → 还是不对 → 循环往复,直到你忘了自己最开始想做什么。

问题的根源不是 AI 写的代码质量差,而是"生成"和"验证"之间有一道肉眼检查的断层。AI 不知道自己生成的东西跑起来长什么样,你也没有时间每次都手动截图对比。

这篇文章要填上这道断层。

---

全局地图:三步闭环是什么

在动手之前,先建立完整的心智模型。整个流程只有三步:

┌─────────────────────────────────────────────────────────┐

│ │

│ ① Codex 生成组件代码 │

│ └─ 输入:组件需求 Prompt │

│ └─ 输出:React/Vue 组件文件 │

│ │ │

│ ▼ │

│ ② Playwright 无头渲染 + 自动截图 │

│ └─ 输入:组件文件路径 │

│ └─ 输出:PNG 截图存入 /output 目录 │

│ │ │

│ ▼ │

│ ③ 视觉模型读图 → 输出结构化评审意见 │

│ └─ 输入:截图 base64 + 审查 Prompt │

│ └─ 输出:PASS / FAIL + 具体问题描述 │

│ │ │

│ ┌───────────────┴───────────────┐ │

│ │ FAIL? │ PASS? │

│ ▼ ▼ │

│ 将问题描述拼入新 Prompt 流程结束 ✓ │

│ 触发第二轮 Codex 生成 │

│ (最多重试 N 次) │

│ │

└─────────────────────────────────────────────────────────┘

用到的工具和端点:
  • 代码生成:Codex(通过 Chat Completions API,model 参数指定)
  • 无头渲染:Node.js + Playwright
  • 视觉评审:支持图像输入的多模态模型(本文以视觉理解能力强的模型为例)
  • 统一 API 入口api.884819.xyz(所有调用共用同一个 baseURL,文中代码直接可用)

---

手把手实操:把三步跑通

在开始之前:文中所有 API 调用均通过统一端点完成。如果你还没有可用的 API Key,或者想用一个稳定支持 Codex 和视觉模型全系的转发服务,直接访问 [api.884819.xyz](https://api.884819.xyz),注册即送体验 token,把文中代码里的 baseURL 替换一下就能跑。

第一步:用 Codex 生成组件代码

我们以生成一个 Button 组件为例。Prompt 的关键是约束输出格式,让返回值直接是可运行的代码,不要多余的解释文字。

// codex-generate.js

import OpenAI from "openai";

const client = new OpenAI({

apiKey: process.env.API_KEY,

baseURL: "https://api.884819.xyz/v1",

});

async function generateComponent(requirement) {

const response = await client.chat.completions.create({

model: "codex-mini-latest", // 或使用你有权限的 Codex 模型

messages: [

{

role: "system",

content: 你是一个 React 组件专家。

只输出完整的 .jsx 文件内容,不要任何解释文字,不要 markdown 代码块标记。

组件必须:使用 Tailwind CSS 样式、可直接在浏览器渲染、export default 导出。,

},

{

role: "user",

content: requirement,

},

],

});

return response.choices[0].message.content;

}

// 示例调用

const buttonCode = await generateComponent(

"生成一个主色调为蓝色的 Primary Button 组件," +

"包含 hover 状态、disabled 状态,尺寸 medium,圆角 8px。"

);

// 写入文件

import { writeFileSync } from "fs";

writeFileSync("./components/Button.jsx", buttonCode);

console.log("✅ 组件已生成:./components/Button.jsx");

Codex 通常会返回类似这样的组件:

// 生成的 Button.jsx(示例)

export default function Button({ children, disabled, onClick }) {

return (

);

}

代码看起来没问题。但"看起来"不算数——我们继续。

第二步:Playwright 无头渲染 + 自动截图

这一步的核心是时机。组件渲染需要时间,截图太早只会拍到空白页面(这是后面踩坑部分要讲的第一个坑)。

// screenshot.js

import { chromium } from "playwright";

import { readFileSync, mkdirSync } from "fs";

async function screenshotComponent(componentPath, outputPath) {

// 构造一个最小化的 HTML 测试页面

const html =

;

const browser = await chromium.launch();

const page = await browser.newPage();

await page.setViewportSize({ width: 800, height: 400 });

await page.setContent(html, { waitUntil: "networkidle" }); // 关键:等网络空闲

// 额外等待 500ms,确保 Tailwind CDN 样式全部应用

await page.waitForTimeout(500);

mkdirSync("./output", { recursive: true });

await page.screenshot({ path: outputPath, fullPage: false });

await browser.close();

console.log(📸 截图已保存:${outputPath});

}

await screenshotComponent("./components/Button.jsx", "./output/button-v1.png");

到这里,截图已经自动存到 /output 文件夹了。 打开看看,如果组件正常渲染,继续下一步;如果是空白图,先检查 waitUntil 参数和 CDN 加载时间。

第三步:视觉模型读图 + 输出结构化评审

这是整个流程最有意思的一步。我们把截图 base64 编码,连同一段精心设计的审查 Prompt,一起发给支持图像输入的多模态模型。

// review.js

import OpenAI from "openai";

import { readFileSync } from "fs";

const client = new OpenAI({

apiKey: process.env.API_KEY,

baseURL: "https://api.884819.xyz/v1",

});

async function reviewScreenshot(imagePath, componentRequirement) {

const imageData = readFileSync(imagePath);

const base64Image = imageData.toString("base64");

const reviewPrompt =

你是一个专业的 UI 审查员。请仔细观察这张组件截图,对照以下需求进行评审:

【原始需求】

${componentRequirement}

【评审维度】

1. 视觉呈现:颜色、圆角、间距是否符合需求描述

2. 状态完整性:所有要求的状态(hover/disabled等)是否可见

3. 布局正确性:元素位置和对齐是否合理

4. 可用性:按钮文字是否清晰可读

【输出格式】请严格按以下 JSON 格式返回,不要任何额外文字:

{

"result": "PASS" | "FAIL",

"score": 0-100,

"issues": ["问题1", "问题2"],

"suggestions": ["建议1", "建议2"],

"summary": "一句话总结"

}

;

const response = await client.chat.completions.create({

model: "gpt-4o", // 使用支持视觉的模型

messages: [

{

role: "user",

content: [

{ type: "text", text: reviewPrompt },

{

type: "image_url",

image_url: {

url: data:image/png;base64,${base64Image},

},

},

],

},

],

response_format: { type: "json_object" },

});

return JSON.parse(response.choices[0].message.content);

}

const requirement = "主色调为蓝色的 Primary Button,包含 hover 状态、disabled 状态,尺寸 medium,圆角 8px";

const reviewResult = await reviewScreenshot("./output/button-v1.png", requirement);

console.log("📋 评审结果:", JSON.stringify(reviewResult, null, 2));

一次真实的评审返回长这样:

{

"result": "FAIL",

"score": 72,

"issues": [

"disabled 状态的按钮颜色过浅,与背景对比度不足,可读性差",

"两个按钮之间的间距偏大,视觉上显得松散"

],

"suggestions": [

"disabled 状态建议使用 bg-gray-400 而非 bg-gray-300,提升文字可见度",

"按钮间距从 ml-4 调整为 ml-3"

],

"summary": "基本结构正确,主按钮样式符合需求,但 disabled 状态可访问性需要改进"

}

issuessuggestions 这两个字段是金子——它们将直接作为下一轮 Codex 的修复指令。

---

进阶玩法:让评审结果自动触发修复

三步跑通之后,加一层主控脚本,把整个流程串成真正的闭环。

// auto-loop.js — 完整闭环主控脚本

import { generateComponent } from "./codex-generate.js";

import { screenshotComponent } from "./screenshot.js";

import { reviewScreenshot } from "./review.js";

import { writeFileSync } from "fs";

const MAX_RETRIES = 3; // 最大重试次数,防止 AI 自嗨式无限循环

async function runLoop(initialRequirement) {

let requirement = initialRequirement;

let iteration = 0;

while (iteration < MAX_RETRIES) {

iteration++;

console.log(\n🔄 第 ${iteration} 轮生成中...);

// Step 1: 生成代码

const code = await generateComponent(requirement);

writeFileSync(./components/Button-v${iteration}.jsx, code);

// Step 2: 截图

const screenshotPath = ./output/button-v${iteration}.png;

await screenshotComponent(./components/Button-v${iteration}.jsx, screenshotPath);

// Step 3: 评审

const review = await reviewScreenshot(screenshotPath, initialRequirement);

console.log(📊 评审得分:${review.score} | 结果:${review.result});

if (review.result === "PASS") {

console.log(✅ 第 ${iteration} 轮通过评审!流程结束。);

console.log(最终组件:./components/Button-v${iteration}.jsx);

return { success: true, iterations: iteration, finalCode: code };

}

// FAIL:把问题描述拼进新 Prompt,触发下一轮

const issuesList = review.issues.join("\n- ");

const suggestionsList = review.suggestions.join("\n- ");

requirement =

【原始需求】${initialRequirement}

【上一版本的问题,必须修复】

  • ${issuesList}

【修复建议】

  • ${suggestionsList}

请在修复以上所有问题的基础上,重新生成完整组件代码。

.trim();

console.log(❌ 评审未通过,问题已记录,准备第 ${iteration + 1} 轮修复...);

}

console.log(⚠️ 已达到最大重试次数 ${MAX_RETRIES},请人工介入检查。);

return { success: false, iterations: MAX_RETRIES };

}

// 启动闭环

await runLoop("主色调为蓝色的 Primary Button,包含 hover 状态、disabled 状态,尺寸 medium,圆角 8px");

关于循环终止条件,这里有两个设计原则:

1. 评审通过即终止:不要贪心,第一次 PASS 就停下来,不要追求满分

2. 硬上限保底MAX_RETRIES = 3 是经验值,超过 3 次还没过,大概率是 Prompt 本身有问题,需要人工介入

实测中,第二轮生成的组件,视觉模型给出了 PASS,得分从 72 提升到 91。那一刻的感觉,确实有点爽。

---

踩坑记录 & 成本控制

三个真实的坑

坑一:截图时机不对

最常见的问题。用 waitUntil: "load" 截图,Tailwind CDN 还没加载完,拍出来是一张没有任何样式的白板。

解决方案:改用 waitUntil: "networkidle",并在之后额外 waitForTimeout(500)。如果你的组件有动画,这个数字还要加大。 坑二:视觉模型对细微颜色差异误判 bg-blue-600(#2563EB)和 bg-blue-500(#3B82F6)在截图里肉眼几乎看不出差别,但模型有时会把这当成"颜色不符合需求"来报错,触发不必要的重试。 解决方案:在审查 Prompt 里加一句:"颜色判断请以视觉观感为准,不要纠结具体色值的细微差异。" 能显著减少误报率。 坑三:Codex 重复生成相同错误代码

如果问题描述不够具体,Codex 可能在第二轮生成几乎相同的代码,只改了一个无关紧要的地方。这是真正的"AI 自嗨"。

解决方案:把视觉模型的 suggestions 字段直接转成代码级指令("将 bg-gray-300 改为 bg-gray-400"),而不是模糊的"提升对比度"。越具体,Codex 越听话。

费用估算

按"每个组件跑一次完整闭环(平均 1.5 轮)"估算:

| 调用步骤 | 模型 | 预估 Token | 约合美元 | | Codex 生成(每轮) | Codex | ~800 tokens | ~$0.004 | | 视觉模型评审(每轮) | 视觉模型 | ~1200 tokens + 图像 | ~$0.015 | | 单组件完整闭环(1.5轮) | — | — | ~$0.03 | | 100个组件批量跑 | — | — | ~$3.00 |
如果你用的是 [api.884819.xyz](https://api.884819.xyz) 的聚合服务,可以在控制台实时看到每次调用的 token 消耗,比自己算省心很多。
这套流程适合哪些场景:
  • 组件库批量生成和质检
  • Design Token 变更后的回归验证
  • 外包代码的 UI 验收自动化
不适合哪些场景:
  • 需要交互测试的复杂组件(截图只能验证静态视觉)
  • 对像素级还原度有严苛要求的场景(误差容忍度有限)
  • 实时性要求极高的 CI/CD 流水线(单次闭环约 15-30 秒)

---

现在就可以跑起来

整个流程的代码量不超过 200 行,没有复杂的依赖,没有需要自己搭建的服务。

你现在就可以用文中的脚本跑一遍,整个流程不超过 20 分钟。第一次看到"第 2 轮通过评审"那行日志打印出来,你会理解为什么我觉得这件事值得写一篇文章。

---

这套流程目前还是"组件级"的验证——它只能看一个组件对不对。

下一篇,我们会把镜头拉远:让 AI 截下整个页面,对照 Figma 设计稿做像素级 Diff,自动标出"哪里和设计稿不一样"

如果你做过还原度验收,你知道那有多痛。敬请期待。

---

本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。

#AI编程 #前端开发 #Codex #自动化测试 #UI验证 #8848AI #AI工具 #Playwright