wisp-diagram:產出 Wisp 風格圖表
目標:產出風格一致、可主題化、可移植的圖表。原始檔(.excalidraw / .drawio)+ 產生的 *.wisp.svg:同一個 SVG 在 Wisp 站點內嵌時跟隨站點主題,在 GitHub README / 其他部落格以 <img> 使用時跟隨系統明暗。
本 skill 目錄自包含,可整體複製到任何專案(~/.claude/skills/ 或 <專案>/.claude/skills/)。腳本是 Python,用 uv 執行:入口 scripts/wisp_diagram.py 頭部用 PEP 723 宣告了依賴(playwright、fonttools),uv run 會自動建好隔離環境,不需要 npm / pip install。
SKILL.md
palette.json 調色板(唯一的顏色來源)
reference/ style-guide.md excalidraw-skeleton.md drawio-cheatsheet.md ml-patterns.md
assets/ wisp.excalidrawlib wisp-drawio-library.xml drawio-config.json drawio-colors.txt
scripts/ wisp_diagram.py(統一入口) wisp_diagram_lib/(實作) tests/(pytest)
一次性準備
下文命令都在專案根目錄執行,$W 代表 skill 的入口腳本(按 skill 實際位置調整路徑):
W="uv run skills/wisp-diagram/scripts/wisp_diagram.py" # 例如 .claude/skills/wisp-diagram/... 或 ~/.claude/skills/wisp-diagram/...
uv run --with playwright==1.64.1 playwright install chromium # 匯出用的無頭瀏覽器(已裝過可跳過)
$W probe # 查看 Chromium、Excalidraw、draw.io 匯出方式(cli / docker / browser)是否可用
首次執行會下載並快取(之後離線可用):
| 內容 | 來源 | 大小 |
|---|---|---|
| Python 依賴 playwright 1.64.1、fonttools | PyPI(uv 管理,放在 uv 自己的快取) | ≈ 55 MB |
| Chromium(只在沒裝過時) | playwright install chromium,放在 ~/.cache/ms-playwright/ | 數百 MB |
| @excalidraw/excalidraw 0.18.1 及其依賴(≈110 個 ES 模組) | jsDelivr,按鎖定版本 | ≈ 1.6 MB |
| svgo 4.1.0 瀏覽器構建(SVG 精簡,在 Chromium 中執行) | jsDelivr | 0.8 MB |
| 字型:Excalifont、Inter 5.3.0、Noto Sans SC 5.3.1(按 unicode-range 分片,用到哪片下哪片) | jsDelivr | 全部 ≈ 4.5 MB |
| draw.io viewer(只在 browser 匯出方式下) | viewer.diagrams.net | ≈ 4.7 MB |
除 Python 依賴與 Chromium 外,快取都在 ~/.cache/wisp-diagram/(可用 WISP_DIAGRAM_CACHE 改位置)。$W cache status 查看,$W cache clear 清空。
離線使用:先在聯網環境執行一次 $W cache warm(下載上表全部前端資源,≈ 11 MB),之後設定 WISP_DIAGRAM_OFFLINE=1 即可完全離線;快取缺檔時會報錯並提示預熱,不會靜默聯網。也可以用 WISP_EXCALIDRAW_BUNDLE=<本地 JS> 指定自己的 Excalidraw 構建(定義了 window.__wisp 的 IIFE,或匯出 exportToSvg 等函式的 ES 模組),用 WISP_DRAWIO_VIEWER=<本地檔案> 指定 draw.io viewer。
draw.io 匯出按 cli → docker → browser 順序自動回退。new、build、export 都需要 Chromium(Excalidraw 匯出、svgo 精簡都在無頭瀏覽器裡執行);lint、normalize、check 不需要。
1. 先選工具
| 場景 | 選擇 | 原始檔 |
|---|---|---|
| 流程圖、時序圖、狀態圖、類圖等結構化圖,節點關係為主 | Mermaid(最省事,直接寫進正文的 ```mermaid 程式碼區塊;Wisp 站點建置期渲染) | 無獨立檔案 |
| 概念圖、直覺示意、手繪風格、ML 運算子資料流 | Excalidraw | name.excalidraw(JSON) |
| 精確對齊的模型結構圖、分層架構圖、需要直角折線和多頁 | draw.io | name.drawio(未壓縮 XML) |
| 逐步示範、可互動 | 不屬於本 skill(React 元件) | — |
原則:能用 Mermaid 講清楚就別畫圖;手繪風格不超過約 12 個節點;同一篇文章裡風格統一。
2. 工作流(5 步)
以下以 diagrams/attention 為例,路徑按實際位置替換。
① 判斷類型(見上表)。選定後讀對應 reference:Excalidraw → reference/excalidraw-skeleton.md;draw.io → reference/drawio-cheatsheet.md;常見模型圖式直接從 reference/ml-patterns.md 複製修改。佈局原則見 reference/style-guide.md。
② 產生原始檔
- Excalidraw:寫骨架 JSON(位置、尺寸、label、語義色名、箭頭起止 id),再轉換。禁止手寫完整的 Excalidraw 元素(seed、boundElements、version 等欄位極易出錯)。
骨架檔轉換後可以刪除或留作產生腳本;之後以$W new diagrams/attention.skeleton.json -o diagrams/attention.excalidraw.excalidraw為準。 - draw.io:直接寫未壓縮 mxGraph XML,樣式字串只用
reference/drawio-cheatsheet.md的預設值。拿到壓縮過的.drawio時先$W normalize diagrams/file.drawio解壓。 - 手動在編輯器裡畫:匯入
assets/wisp.excalidrawlib(Excalidraw 右側 Library → Open)或assets/wisp-drawio-library.xml(draw.io:File → Open Library from → Device),按assets/drawio-colors.txt設定調色板。
③ lint(先修復)再 build
$W lint diagrams/attention.excalidraw --fix # 非調色板顏色吸附到最近令牌;無法自動修的會列出
$W lint diagrams/attention.excalidraw --strict # 無任何 error/warn 才算通過
$W build diagrams/attention.excalidraw # 產生 diagrams/attention.wisp.svg
build 常用參數:--embed-fonts(內嵌字型子集,發佈到外部平台時用)、--fix、--title T --desc D(覆蓋無障礙文字,僅單一檔案)、--drawio cli|docker|browser、--out-dir DIR、--strict。多頁 draw.io 的第 2 頁起輸出為 name.page-2.wisp.svg。
④ 自檢:匯出 light 與 dark 預覽,用 Read 工具查看
$W export diagrams/attention.wisp.svg --theme light -o /tmp/attention.light.png
$W export diagrams/attention.wisp.svg --theme dark -o /tmp/attention.dark.png
檢查:文字重疊、箭頭穿過節點、標籤壓住箭頭頭部、暗色下看不清、中文被截斷。發現問題改原始檔,回到 ③ 重新 build。預覽 PNG 放暫存目錄,不要提交。
⑤ 引用並寫 alt:見下文「使用產物」。必須提供有意義的 alt 文字(描述圖要表達的結論,而不是「圖 1」)。
3. 調色板
顏色只能來自 palette.json。作畫時只用淺色值(下表左值);build 會把它們映射成 var(--wd-*)(暗色值由 SVG 自動切換)。不要手選暗色值,不要用調色板之外的任何顏色(lint 報錯,--fix 吸附到最近的令牌)。
| hue | stroke(線條、文字)light / dark | fill(填充)light / dark | 語義名 |
|---|---|---|---|
blue | #1d4ed8 / #60a5fa | #dbeafe / #1b2740 | q、active |
amber | #b45309 / #fbbf24 | #fef3c7 / #38290c | k、highlight |
green | #15803d / #4ade80 | #dcfce7 / #122d1c | v、done |
rose | #be123c / #fb7185 | #ffe4e6 / #3a1620 | grad、error |
violet | #7c3aed / #a78bfa | #ede9fe / #272040 | weight |
teal | #0f766e / #2dd4bf | #ccfbf1 / #0f2c29 | activation |
orange | #c2410c / #fb923c | #ffedd5 / #3a2010 | bias |
slate | #475569 / #94a3b8 | #e2e8f0 / #222831 | mask、visited |
基礎色:正文 fg #1d1d1f(Excalidraw 預設黑 #000000/#1e1e1e 也會被視為 fg),次要 muted #5c6067,面 surface #f4f4f2,邊框 border #e2e2dd,畫布白 #ffffff(build 會去掉背景)。
語義色(所有文章、圖表一致)
| 語義名 | 含義 | hue |
|---|---|---|
q | 查詢 Query | blue |
k | 鍵 Key | amber |
v | 值 Value | green |
weight | 權重、參數 | violet |
activation | 激活、softmax | teal |
grad | 梯度 | rose |
bias | 偏置 | orange |
mask | 遮罩、輸入 | slate |
highlight | 高亮 | amber |
visited | 已訪問 | slate |
active | 目前/進行中 | blue |
done | 完成 | green |
error | 錯誤、損失 | rose |
骨架的 color 欄位可直接寫語義名("color": "q")或 hue 名;draw.io 中查表取 hue 對應的 fill/stroke。
4. 風格硬規則
| 項目 | 規則 |
|---|---|
| 網格 | 所有元素 x/y/寬/高取 8px 的倍數 |
| 字級 | 只用 12(註釋)/ 14(標籤)/ 18(標題);lint 容差 ±1 |
| 線寬 | 1.5(常規),2.5(強調,一張圖一兩處);細線 1 僅用於分隔 |
| 圓角 | 矩形/容器圓角:Excalidraw round,draw.io rounded=1;arcSize=8 |
| 填充 | 純色填充,使用 hue 的 fill;禁止漸層 |
| 陰影 | 禁止 |
| 字型 | Excalidraw 用 Excalifont(中文回落 Noto Sans SC,不用小賴體);draw.io 用 Inter |
| 手繪 | roughness: 1;draw.io 不用 sketch=1 |
| 背景 | 不畫背景矩形,保持透明 |
| 原始檔 | .excalidraw 為 JSON;.drawio 為未壓縮 XML;嵌入圖片會使檔案膨脹(>2MB 警告) |
5. 自檢清單
- lint
--strict通過:無非調色板顏色、無陰影/漸層、字級合規、元素在 8px 網格上 - 語義色用對:Q 藍、K 琥珀、V 綠、權重紫、激活青、梯度/損失玫紅、偏置橙、遮罩/輸入灰
- 一張圖只講一件事,節點 ≤ 12(手繪)
- 連線不穿過節點、不交叉;短連線(間距 < 96px)上沒有標籤
- 中文標籤每行 ≤ 8–10 個漢字,沒有被截斷
- 有標題(
size: "title"的文字 / draw.io 頁面名)和一句註釋 - light 和 dark 的預覽 PNG 都用 Read 看過
- 已產生
.wisp.svg,且原始檔修改後重新 build 過 - alt 文字有意義
6. 使用產物
在 Wisp 站點中(可選)
站點的 pnpm diagram build|check|lint [--fix]、pnpm diagram new <skeleton.json> --build、pnpm diagram export <檔案> --png --theme dark 是本 skill 的薄包裝(轉調 uv run skills/wisp-diagram/scripts/wisp_diagram.py,並帶上 --root <倉庫> --manifest scripts/diagram/manifest.json 做增量構建)。
原始檔與文章放在一起(diagrams/ 目錄),.wisp.svg 也要提交。在 MDX/Markdown 中用標準圖片語法引用原始檔(站點會內嵌同名 .wisp.svg,並附原始檔下載連結):


alt 寫入 SVG 的 <title>,引號中的說明成為圖註;#page=<頁面名> 選擇 draw.io 的頁面。pnpm diagram check 只校驗內容雜湊,CI 裡不需要瀏覽器(但需要 uv)。
其他專案也可以用同樣的「專案模式」:$W build --root . --manifest diagrams.manifest.json 只重建有變化的原始檔(掃描 <root>/src/content/**/diagrams/),$W check --root . --manifest diagrams.manifest.json 在 CI 中校驗雜湊。
在其他地方(GitHub README、其他部落格、IDE 預覽)
直接引用產生的 *.wisp.svg:它自帶 <style>,作為 <img> 時透過 prefers-color-scheme 跟隨系統明暗,沒有 CSS 變數支援的渲染器會使用屬性裡的淺色 fallback。

- 發佈到外部平台前用
$W build <檔案> --embed-fonts內嵌字型子集(每張圖通常幾十 KB),避免字型回落。 - 公眾號、知乎等不支援 SVG 的平台:
$W export <檔案> --theme light --scale 2 -o out.png(--transparent可得到透明背景;預設內嵌字型)。 - 原始檔一併保存在倉庫中,便於以後修改;
.excalidraw可在 excalidraw.com 開啟,.drawio可在 app.diagrams.net 開啟。
7. 常見錯誤
| 錯誤 | 怎麼避免 |
|---|---|
| 手寫完整 Excalidraw 元素 | 只寫骨架,用 $W new 轉換 |
| 在編輯器裡用了調色板外的顏色(滴管、預設色) | 用素材庫;lint --fix 吸附;仍報錯就改原始檔 |
| 手選暗色值作畫 | 只用淺色值,暗色靠 SVG 自動切換 |
| 骨架箭頭穿過第三個節點 | 箭頭是中心連線上的直線;調整佈局,或改用 draw.io 折線 |
| 短連線加標籤,標籤壓住箭頭頭部 | 節點間距 ≥ 96 再加標籤,否則去掉標籤 |
| 座標不在 8px 網格上 | 所有數值取 8 的倍數 |
.drawio 提交了壓縮格式 | $W normalize 解壓;File → Properties 取消 Compressed |
draw.io 標籤裡的 & 沒跳脫 | html=1 時寫成 &amp;,換行寫 <br> |
| 中文標籤過長被截斷 | 每行 ≤ 8–10 個漢字,用 \n / <br> 換行,加寬節點 |
| 改了原始檔忘記重新 build | 修改後重跑 $W build(站點裡 pnpm diagram check 會發現雜湊不一致) |
| 沒看暗色預覽就交付 | 步驟 ④ 的 dark 預覽必須看 |
| 空洞的 alt(「圖 1」「diagram」) | alt 寫圖要說明的結論 |
| 用圖表工具畫數據圖 | 折線/長條等數據圖不屬於本 skill |
8. 排錯
uv: command not found:安裝 uv(curl -LsSf https://astral.sh/uv/install.sh | sh)。需要 Python ≥ 3.11,uv 會按需自動下載。無法啟動 Chromium:uv run --with playwright==1.64.1 playwright install chromium(版本要與腳本鎖定的 playwright 一致)。下載失敗/離線模式下快取缺少 N 個檔案:聯網執行一次$W cache warm;快取位置見$W cache status。- draw.io 匯出失敗:
$W probe看哪種方式可用;可用--drawio browser|cli|docker強制指定。 - 想看瀏覽器裡的報錯:設定
WISP_DIAGRAM_DEBUG=1。 - lint 報
unrecognised colour:顏色不是十六進位(如rgb()之外的特殊值),手工改成調色板裡的淺色值。