こんにちは、エンジニアのオオバです。
ブログや SNS に載せる図解を、もっと手早く作りたい。
しかも、AI にも図を作ってもらいたい。
そう考えて、Lucidchart 風の図解ツール「Zukai」を自作しました。
既存の作図ツールだと、自分で機能を足したり、AI に直接いじらせたりするのが難しいんですよね。
図の正本をJSONにして描画を自前のSVGに集約すると、AIがJSONを書くだけで図になり、CLIでPNGまで出せる
先に結論をまとめます。
①図の正本を diagrams/*.json にし、zodのスキーマから型・バリデーション・JSON Schemaを1箇所で作る
②スタイルは上書き分だけ保存する。AIは id・type・x・y・text だけ書けば図形になる
③描画を自前のSVG 1コンポーネントに集約し、エディタ・ブラウザ書き出し・CLIで同じコードを使う
ここから、作ったものとハマった点を順番に書いていきます。
→11万文字で徹底解説した「DOTweenの教科書」Unityアニメーションの超効率化ツールはこちら
やりたかったこと:AIも人間も同じ図をいじれるツール
目的は2つありました。
- ブログ・SNS の図解素材を作るツールを、独自に機能を足せる形で持つ
- AI も図を作る前提にする
AI が図を作るなら、AI が読み書きしやすい形で図を保存する必要があります。
さらに、AI がエディタを開かずに出来上がりを確認できると便利です。
この2点を軸に設計しました。
やったこと1:図の正本をJSONにしてzodでスキーマを定義
図の正本は diagrams/*.json にしました。
スキーマは zod で定義しています。
zod のスキーマ1つから、次の3つを作ります。
- TypeScript の型
- 読み込み時のバリデーション
- JSON Schema
定義が1箇所なので、型とバリデーションがズレる心配がありません。
スタイルは上書き分だけ保存する
図形の色や線の太さは、既定値から変えた分だけを JSON に保存する設計です。
そのため、AI は {id, type, x, y, text} だけ書けば図形になります。
色や角丸をいちいち書かなくていいので、AI が書く JSON が短く済みます。
一方で、スキーマに無い未知のキーはエラーにしました。
AI がキー名を綴り間違えたとき、黙って無視されると原因が分かりにくいからです。
やったこと2:描画を自前のSVG 1コンポーネントに集約
React Flow のような作図ライブラリは使っていません。
描画は自前の SVG コンポーネント1つに集約しました。
このコンポーネントを、次の3か所で同じように使います。
- エディタの表示
- ブラウザからの書き出し
- CLI(
react-dom/serverで SVG を作り、resvg で PNG にする)
CLI があるので、AI はエディタを開かずに zukai render で PNG を作れます。
PNG を見て、おかしければ JSON を直す。
AI だけでこの往復ができるようになりました。
実際に AI が書いた JSON を、CLI で PNG にしたのが次の図です。

開始・終了の角丸、処理の四角、判断のひし形、データベースの円柱、付箋が入ったフローチャートです。
「はい」「いいえ」の線ラベルや、点線の矢印も出ています。
文字の折り返しは近似幅で自前計算
SVG は、文字を自動で折り返してくれません。
そのため、行の分割を自前で計算しています。
ここで困るのが、ブラウザと Node で結果を揃えることです。
ブラウザで文字幅を実測すると、Node の CLI では同じ値が取れません。
そこで実測はせず、文字種ごとの近似幅を使いました。
日本語は1文字 = 1em として計算しています。
エディタと CLI で折り返し位置が同じになるので、AI が CLI で確認した見た目と、人間がエディタで見る見た目が揃います。
やったこと3:AIがJSONを書き換えると約1秒でエディタに反映
エディタは Vite の開発サーバーで動かしています。
Vite のプラグインで、保存用の API とファイル監視を実装しました。
AI が diagrams/*.json を書き換えると、約1秒でエディタに反映 されます。
反映は1手の操作として扱うので、⌘ + Z で戻せます。
書きかけのJSONは読み込まない
AI がファイルを書いている途中で、JSON が壊れた状態になることがあります。
不正な JSON を見つけたら、読み込みを保留して自動保存も止めるようにしました。
エディタが壊れた中身を読んで、そのまま上書き保存してしまうのを防ぐためです。
エディタの画面は次のとおりです。

左に基本図形とフローチャート用の図形のパレット、右に図の設定と操作の一覧を置いています。
点線の枠が、PNG に書き出される範囲です。
右上から PNG・SVG・JSON の書き出しと、PNG のクリップボードコピーができます。
ハマった点
作っている途中で、3回ハマりました。
ドラッグで作った線が消えた
新しい線をドラッグで作ると、線が消えてしまいました。
原因は、ドラッグ中の更新を「線を追加する前の図」に対して行っていたことです。
ドラッグ中に図を書き換えるたびに、追加したはずの線が無い状態に戻っていました。
Undo 用の基準になる図と、ドラッグ中に書き換える元の図を分けて解決しました。
ダブルクリックで文字編集にならなかった
図形をダブルクリックすると、文字の編集になるはずでした。
ところが実際には、新しいテキストが作られてしまう。
原因は setPointerCapture です。
直前のクリックで SVG が setPointerCapture していたため、dblclick の target が SVG 自身になっていました。
document.elementFromPoint で、クリックした座標から要素を引き直して解決しました。
読み込み直後の全体表示がずれた
図を読み込んだ直後に、全体が収まるように表示する処理を入れていました。
ところが、この処理がキャンバスの実寸が決まる前に走って、表示がずれていました。
ユーザーが画面を触るまでは、サイズが変わるたびに全体表示をやり直す方式にしました。
追加1:エディタから「AIに指示」できるようにした
翌日、エディタに「AIに指示」(⌘ + K)を追加しました。
エディタから文章で指示すると、AI が図を書き換えてくれます。
Claude Code CLIをツール無しで呼ぶ
サーバーから Claude Code の CLI を次のように呼んでいます。
claude -p --safe-mode --tools "" --output-format json --json-schema '{summary, diagram}'
--tools "" でツールを全部無効にしているので、AI はファイル操作をしません。
図は、構造化出力の structured_output から受け取ります。
図の検証はzodで行う
--json-schema には、図そのものの JSON Schema は渡していません。
渡しているのは {summary: string, diagram: object} という外側の形だけです。
図の中身の検証は、エディタと同じ zod のスキーマで行います。
スキーマ違反なら、理由を添えて1回だけ AI にやり直させます。
仕様の説明はガイドを1つだけ
AI への仕様説明には、人間と AI 向けに書いたガイド docs/ai-guide.md をそのまま --system-prompt に入れました。
仕様の正本が1箇所で済むので、ガイドを直せば AI への指示も揃います。
結果はエディタ側で1手として適用する
AI の結果は、サーバーでは保存しません。
エディタが1手の操作として適用するので、⌘ + Z で戻せます。
AI が足した図形は選択状態にして、どこが変わったか分かるようにしました。
実測
実際に試した結果です。
- 「公開!の後にXで告知を追加して」: 21秒。既存の図形は1つも動かさずに追加された
- Mac mini の常駐版(Sonnet 5.5)で文言の変更: 7秒
既存の図形を動かさずに足してくれたのは、地味に嬉しいポイントでした。
キャンセル
キャンセルはブラウザ側の AbortController で接続を切ります。
サーバーは res の close を見て、spawn の signal で CLI を止めます。
追加2:画面から更新できるようにした
Mac mini で常駐させている版に、画面から更新する「⬇ 更新」ボタンを付けました。
押すと、常駐版が自分の checkout を origin/main に追従させます。
サーバーが自分を再起動する問題
更新の最後に、サーバー自身が launchctl kickstart -k で再起動します。
サーバーの子プロセスのままだと、再起動に巻き込まれてしまいます。
そのため、update.sh は spawn の detached と unref で切り離して動かし、結果は .update/state.json に書くようにしました。
画面は「新しいコミットで動いていて、state が succeeded」になるまでポーリングしてから reload します。
ビルドに失敗したら元に戻す
ビルドに失敗したときは、trap で元のコミットへ reset --hard して npm ci し直します。
戻さないと、次に OS を再起動したとき、壊れたソースから起動してしまうからです。
使い捨ての bare リポジトリを origin にして、成功と失敗の両方を e2e で確認しました。
追加3:Tailnet内でHTTPSで開けるようにした
PNG のクリップボードコピー(navigator.clipboard.write)と crypto.randomUUID は、HTTPS か localhost の画面でしか使えません。
Tailnet 内の別の端末から http:// のホスト名で開くと、動きませんでした。
そこで、Tailscale の管理コンソールで HTTPS Certificates を有効にし、Mac mini で次を設定しました。
tailscale serve --bg --https=8443 http://127.0.0.1:5181
SSE の通知も、このプロキシ越しに届いています。
初回アクセスだけ、証明書の発行待ちで失敗しました。
学び
作ってみて、AI に使わせるツールで効いた設計は次の3つでした。
- 正本を1つにする。 スキーマは zod、仕様の説明は
docs/ai-guide.mdに寄せた - AI が書く量を減らす。上書き分だけ保存し、未知のキーはエラーにする
- AI が自分で確認できるようにする。描画を共通化して、CLI で PNG を出せるようにした
特に CLI で PNG を出せるようにしたのが大きかったです。
AI が自分で画像を見て直せるので、僕が毎回エディタを開いて確認しなくて済みます。
まとめ:AIに図を作らせるなら「JSONが正本」「描画は1か所」「CLIで確認」
①図の正本をJSONにし、zodのスキーマから型・バリデーション・JSON Schemaを作ると、AIが書く図も同じルールで検証できる
②描画を自前のSVG 1コンポーネントに集約すると、エディタ・書き出し・CLIで見た目が揃う
③CLIでPNGを出せると、AIがエディタを開かずに図を作って確認できる
既存の作図ツールで困っていなければ、無理に自作する必要は無いと思います。
ただ、AI にも図を作ってもらいたいなら、図を JSON で持てるかどうかが分かれ目になります。
同じように AI と一緒に図解を作りたい人の参考になれば嬉しいです。

この記事が気に入ったらフォローしよう
- Vite 6 / React 18 / TypeScript 5.9 / zod 4 / resvg-js 2.6
