本日核心價值 (Core Focus): 把設計圖(screenshot / Figma export)當成視覺規格,而不是「看圖說故事」。Workflow 固定三階段:vision 盤點元件 JSON → 用 tokens / spacing / states 約束生成 → 只輸出一張訂單狀態卡的 HTML/CSS,不生成整站 SPA。
概念說明與實戰情境 (Overview)
設計稿進工程最常失敗的點是:模型直接吐一個「大概長得像」的頁面,顏色、間距、狀態全是幻覺。正確做法是拆階段。先把 PNG(Figma export 或 screenshot)用 Chat Completions 的 image_url 送給 vision 模型,強制輸出元件盤點 JSON(元件名、層級、顏色 token、間距、狀態)。人工或程式檢視 JSON 後,第二輪只生成一個元件——本日是訂單狀態卡,不是整個 checkout SPA。範圍越小,spacing 與 hover/disabled 才對得上稿。
關鍵操作與範例 (Implementation & Example)
建議輸入:Figma 將 Frame 匯出 2x PNG,或瀏覽器截「單一元件」而非整頁。圖太雜,盤點 JSON 會漏狀態。Chat Completions 的 image 放在 user content 陣列,與文字 Prompt 並列:
{"type": "image_url", "image_url": {"url": image_url, "detail": "high"}}
url 可以是 https://... 或 data:image/png;base64,...。本機檔案請自行讀 bytes 再 base64,不要把磁碟路徑傳給 API。detail: "high" 適合元件稿;整頁縮圖可用 low 降 Token。模型用 gpt-4o(可換團隊現用的 vision 模型)。
兩段 Prompt 分開:第一段禁止產出 HTML,只准 JSON;第二段禁止發明盤點裡沒有的顏色與間距。合併成「看圖寫出完整頁面」時,模型會同時幻覺色票、漏掉 disabled、再順便生一個假的 React router。分階段才能在 JSON 上做機械檢查(hex 格式、spacing 是否為 4 的倍數、states 鍵是否齊全),不過關就不要進 HTML。
視覺規格要逼模型講「看得到的數值」,不要講形容詞。tokens 是單一真相:顏色、字級、行高、radius、padding。states 則防止只做 default:稿上有 hover 就要寫;沒有就 null,階段 2 不准發明 drop-shadow。unknowns 是逃生口——看不清的 1px 線列入清單,交給設計,而不是讓 CSS 用 box-shadow: 0 8px 32px 填洞。
階段 1 Prompt(元件盤點,貼在文字 content):
你是 UI 規格抽取器,不是前端工程師。只根據圖片,輸出一個 JSON 物件,不要 Markdown。
Schema:
{
"frame": {"width_px": number, "height_px": number, "background": "#RRGGBB"},
"tokens": {
"color": {"name": "#RRGGBB"},
"space": {"name": number},
"radius": {"name": number},
"font": {"name": {"family": string, "size_px": number, "weight": number, "line_height_px": number}}
},
"components": [
{
"id": string,
"type": "card|badge|button|text|row",
"copy": string,
"layout": {"padding_px": [t,r,b,l], "gap_px": number},
"tokens_used": [string],
"states": {
"default": "可見樣式摘要",
"hover": "若無則 null",
"focus": "若無則 null",
"disabled": "若無則 null",
"loading": "若無則 null",
"error": "若無則 null",
"empty": "若無則 null"
}
}
],
"unknowns": [string]
}
硬性規則:
- spacing 必須是 4 的倍數(4/8/12/16/24/32)。看不出來就列入 unknowns,禁止猜 13px。
- 顏色寫 hex;無法確定則 unknowns,禁止發明品牌色。
- 每個可互動元件必須填 states;圖上沒有 hover 就填 null,不要編造陰影。
- 只盤點「訂單狀態卡」範圍;忽略瀏覽器 chrome 與其他頁面區塊。
階段 2 Prompt(只准一張卡):
根據下列 component inventory JSON 產出單一檔案:訂單狀態卡。
只允許 HTML + 一個 <style>。禁止 React/Vue、禁止路由、禁止整頁 layout、禁止假資料 API。
硬性規則:
- 所有顏色、font-size、padding、gap、border-radius 必須引用 JSON 的 tokens;禁止新增 token。
- 卡片結構:標題列(訂單編號 + 狀態 badge)→ 商品列 → 底部 CTA。
- 必須實作 JSON 裡非 null 的 states(:hover / :focus-visible / :disabled / [data-state=loading|error|empty])。
- 互動只用 CSS;CTA 用 <button type="button">。
- 若 JSON.unknowns 非空,在 HTML 註解列出,不要用猜測填洞。
階段 1 的 Python 呼叫(本機 PNG → base64 image_url):
把規則從 Prompt 再翻譯成驗收條件,避免「Prompt 很完整、產出卻沒人查」:
json.loads,且含 tokens / components / unknowns。tokens.space 每個值是 4 的倍數;否則退回重抽,不要在 CSS 裡四捨五入。button / cta 的 states.hover 與 states.focus 若為 null,階段 2 仍至少做 :focus-visible(無障礙底線),但不得發明稿上沒有的漸層。article.card 或 form),禁止 nav、禁止第二張卡。image_url 與文字 Prompt 必須在同一則 user message 的 content 陣列;只傳圖不傳規則,盤點會變成形容詞。"""vision_inventory.py
依賴: pip install openai
環境: OPENAI_API_KEY;OPENAI_MODEL 預設 gpt-4o(須支援 vision,可換)
執行: python vision_inventory.py path/to/order-card.png
"""
from __future__ import annotations
import base64
import json
import mimetypes
import os
import sys
from pathlib import Path
from openai import OpenAI
MODEL = os.environ.get("OPENAI_MODEL", "gpt-4o")
INVENTORY_PROMPT = """你是 UI 規格抽取器。只輸出 JSON,不要 Markdown。
(此處貼上階段 1 完整規則)"""
def file_to_data_url(path: Path) -> str:
mime = mimetypes.guess_type(path.name)[0] or "image/png"
b64 = base64.b64encode(path.read_bytes()).decode("ascii")
return f"data:{mime};base64,{b64}"
def extract_inventory(image_path: Path) -> dict:
client = OpenAI()
resp = client.chat.completions.create(
model=MODEL,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": INVENTORY_PROMPT},
{
"type": "image_url",
"image_url": {
"url": file_to_data_url(image_path),
"detail": "high",
},
},
],
}
],
response_format={"type": "json_object"},
)
return json.loads(resp.choices[0].message.content or "{}")
if __name__ == "__main__":
inv = extract_inventory(Path(sys.argv[1]))
print(json.dumps(inv, ensure_ascii=False, indent=2))
第二輪把 json.dumps(inventory) 貼進階段 2 Prompt 即可。以下是符合「tokens + spacing + states」的訂單狀態卡範例(可當階段 2 的驗收基準,而不是唯一答案):
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8" />
<title>Order status card</title>
<style>
:root {
--color-bg: #f4f1ea;
--color-card: #fffaf2;
--color-ink: #1f1b16;
--color-muted: #6b6258;
--color-line: #e6dccf;
--color-paid: #0f7a4a;
--color-paid-bg: #e3f6ec;
--color-cta: #c45c26;
--color-cta-ink: #fffaf2;
--space-8: 8px;
--space-12: 12px;
--space-16: 16px;
--space-24: 24px;
--radius-8: 8px;
--radius-16: 16px;
--font: 16px/24px "Segoe UI", "Noto Sans TC", sans-serif;
--font-sm: 13px/20px "Segoe UI", "Noto Sans TC", sans-serif;
}
body { margin: 24px; background: var(--color-bg); font: var(--font); color: var(--color-ink); }
.card {
width: 360px; background: var(--color-card); border: 1px solid var(--color-line);
border-radius: var(--radius-16); padding: var(--space-24); display: flex;
flex-direction: column; gap: var(--space-16);
}
.row { display: flex; justify-content: space-between; align-items: center; gap: var(--space-12); }
.muted { color: var(--color-muted); font: var(--font-sm); }
.badge {
background: var(--color-paid-bg); color: var(--color-paid); border-radius: 999px;
padding: 4px 12px; font: var(--font-sm);
}
.item { border-top: 1px solid var(--color-line); padding-top: var(--space-16); }
.cta {
border: 0; border-radius: var(--radius-8); background: var(--color-cta);
color: var(--color-cta-ink); padding: var(--space-12) var(--space-16); cursor: pointer;
}
.cta:hover { filter: brightness(1.05); }
.cta:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 2px; }
.cta:disabled { opacity: 0.45; cursor: not-allowed; }
.card[data-state="loading"] .cta::after { content: " …"; }
.card[data-state="error"] { border-color: #b42318; }
.card[data-state="empty"] .item { display: none; }
.card[data-state="empty"]::after { content: "尚無商品列"; color: var(--color-muted); }
</style>
</head>
<body>
<article class="card" data-state="default">
<div class="row">
<strong>ORD-1001</strong>
<span class="badge">已付款</span>
</div>
<div class="item row">
<span>Widget M × 2</span>
<span class="muted">NT$ 1,280</span>
</div>
<button class="cta" type="button">查看物流</button>
</article>
</body>
</html>
驗收清單(對稿用,不要靠「感覺很像」):badge 是否 pill、padding 是否 24、CTA hover / focus-visible / disabled 是否存在、error / empty 是否用 data-state 而不是另做一頁。差 4px 就是沒對上 token。把驗收寫成 checklist,下一張卡(例如退貨表單)才能複用同一套 Prompt,而不是每次重新「請寫得精緻一點」。
階段 2 的輸出應可直接開瀏覽器核對,不要再丟回模型問「像不像」。人眼對稿比 vision 二次評分便宜且可重現:量 padding、對 hex、Tab 一次看 focus ring。若 JSON 寫 space-24 但 HTML 寫死 padding: 22px,視為生成失敗並重跑階段 2,不要手工改到「差不多」。Figma 若有 Variables / Design Tokens,匯出後可把官方 token 名預先寫進階段 1 Schema,減少模型自創 --color-brand-1。
注意事項與常見失敗 (Pitfalls)
#4A90D9。必須先 JSON 後程式碼;JSON 未過關不要進階段 2。機械檢查失敗時把 JSON 與錯誤鍵名一併存檔,下一輪只修規格、不要同時改 CSS。image_url,否則背景灰與瀏覽器主題會變成「品牌色」。image_url 的 detail: 小字、1px 邊框在 low 下會消失,spacing 全變成猜的。元件稿用 high。圖超過 API 尺寸上限時先縮小長邊、不要改 detail 來省 Token 而犧牲對稿。image_url: API 需要可抓的圖(公開 URL 或 data URL),不是 Figma 檔案權限連結。先 export PNG。團隊若用私有 CDN,確認 OpenAI 能抓該 URL,否則改走 base64。unknowns: 看不清的陰影硬編造。規定列 unknowns,階段 2 用註解標出,交設計確認。註解應寫「無法從稿讀取的項目」,不要寫「可能是某某色」,以免下一個人把猜測當規格。本日總結 (Takeaways)
content 陣列傳 image_url(https 或 base64 data URL),detail 依稿件精細度選擇。gpt-4o 可換現用 vision 模型;換模型時用同一張 PNG + 同一 Schema 做回歸。明日預告 (Next)
明日進入 後端系統整合實戰:將 ChatGPT API 嵌入 C# .NET / PHP Web 架構,用 ASP.NET Core Minimal API 包一層 /ai/summarize-order,並附對等 PHP 片段。