こんにちは、エンジニアのオオバです。

お悩みさん
お悩みさん
  • AIに図解を作らせたい
  • 既存の作図ツールに独自機能を足せない
  • エディタとPNG書き出しで見た目がズレる
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    ブログや SNS に載せる図解を、もっと手早く作りたい。

    しかも、AI にも図を作ってもらいたい。

    そう考えて、Lucidchart 風の図解ツール「Zukai」を自作しました。

    既存の作図ツールだと、自分で機能を足したり、AI に直接いじらせたりするのが難しいんですよね。

    図の正本をJSONにして描画を自前のSVGに集約すると、AIがJSONを書くだけで図になり、CLIでPNGまで出せる

    先に結論をまとめます。

    AIが図を作れる図解ツールの作り方

    ①図の正本を diagrams/*.json にし、zodのスキーマから型・バリデーション・JSON Schemaを1箇所で作る

    ②スタイルは上書き分だけ保存する。AIは id・type・x・y・text だけ書けば図形になる

    ③描画を自前のSVG 1コンポーネントに集約し、エディタ・ブラウザ書き出し・CLIで同じコードを使う

    ここから、作ったものとハマった点を順番に書いていきます。

    →11万文字で徹底解説した「DOTweenの教科書」Unityアニメーションの超効率化ツールはこちら

    やりたかったこと:AIも人間も同じ図をいじれるツール

    目的は2つありました。

    AI が図を作るなら、AI が読み書きしやすい形で図を保存する必要があります。

    さらに、AI がエディタを開かずに出来上がりを確認できると便利です。

    この2点を軸に設計しました。

    やったこと1:図の正本をJSONにしてzodでスキーマを定義

    図の正本は diagrams/*.json にしました。

    スキーマは zod で定義しています。

    zod のスキーマ1つから、次の3つを作ります。

    定義が1箇所なので、型とバリデーションがズレる心配がありません。

    スタイルは上書き分だけ保存する

    図形の色や線の太さは、既定値から変えた分だけを JSON に保存する設計です。

    そのため、AI は {id, type, x, y, text} だけ書けば図形になります。

    色や角丸をいちいち書かなくていいので、AI が書く JSON が短く済みます。

    一方で、スキーマに無い未知のキーはエラーにしました。

    AI がキー名を綴り間違えたとき、黙って無視されると原因が分かりにくいからです。

    やったこと2:描画を自前のSVG 1コンポーネントに集約

    React Flow のような作図ライブラリは使っていません。

    描画は自前の SVG コンポーネント1つに集約しました。

    このコンポーネントを、次の3か所で同じように使います。

    CLI があるので、AI はエディタを開かずに zukai render で PNG を作れます。

    PNG を見て、おかしければ JSON を直す。

    AI だけでこの往復ができるようになりました。

    実際に AI が書いた JSON を、CLI で PNG にしたのが次の図です。

    AIが書いたJSONをCLIでPNG化した例

    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 が足した図形は選択状態にして、どこが変わったか分かるようにしました。

    実測

    実際に試した結果です。

    既存の図形を動かさずに足してくれたのは、地味に嬉しいポイントでした。

    キャンセル

    キャンセルはブラウザ側の 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つでした。

    特に CLI で PNG を出せるようにしたのが大きかったです。

    AI が自分で画像を見て直せるので、僕が毎回エディタを開いて確認しなくて済みます。

    まとめ:AIに図を作らせるなら「JSONが正本」「描画は1か所」「CLIで確認」

    AIが図を作れる図解ツールを自作して分かったこと

    ①図の正本をJSONにし、zodのスキーマから型・バリデーション・JSON Schemaを作ると、AIが書く図も同じルールで検証できる

    ②描画を自前のSVG 1コンポーネントに集約すると、エディタ・書き出し・CLIで見た目が揃う

    ③CLIでPNGを出せると、AIがエディタを開かずに図を作って確認できる

    既存の作図ツールで困っていなければ、無理に自作する必要は無いと思います。

    ただ、AI にも図を作ってもらいたいなら、図を JSON で持てるかどうかが分かれ目になります。

    同じように AI と一緒に図解を作りたい人の参考になれば嬉しいです。

    オススメ記事
    検証環境