ビデオリサーチ公式テックブログ

ビデオリサーチ公式テックブログ

低予算プロジェクトで手探りでAI駆動UI開発を導入してみた

こんにちは。ビデオリサーチのキクチです。
今回はタイトルの通り、とある低予算プロジェクトで手探りながらAI駆動UI開発を導入してみた話を書いてみたいと思います。

はじめに

昨年末にとある Web アプリ開発プロジェクトにアサインされました。
しかし、プロジェクト予算の都合上、専任のデザイナーやフロントエンドエンジニアを配置できない状況でした。
手元にあるのはパワーポイントで作成された画面スケッチのみ。
図形とテキストだけで構成されたざっくりしたスケッチは数多くありましたが、そこからデザインを起こすには手間がかかる上、改修が入るたびに修正するのは非常に非効率でした。
そこで思い切って、AI デザインツールの「tldraw.makereal」を導入してみることにしました。

なぜ tldraw.makereal を選定したのか

導入にあたって特に重視したポイントは以下のとおりです。

  • 軽量・シンプル:チームメンバー全員がサクッと使えること
  • ローカルで完結:セキュリティ要件上、セルフホスト出来ることが望ましい
  • コードとの連動が容易:Tailwind CSSと相性が良いこと
ローカルで実行可能

最も大きかったのは「ローカル環境で動かせる」ことです。
ランサムウェア等のセキュリティインシデントが連日報道される昨今では、企業において外部SaaSクラウドツールの導入に際し、事前審査や承認プロセスが必要になることが一般的かと思います。その点、tldraw.makerealはローカルでのセットアップおよびセルフホストが可能なので、複雑な承認プロセスを簡略化でき、より迅速にプロジェクトへの導入を進められる点が魅力でした。

Tailwind CSS でデザインされるため実装移行がスムーズ

tldraw.makerealで生成される基本デザインはTailwind CSSで組まれていて、実装に流用しやすいクラス名やコンポーネント構造になっています。
パワーポイントのスケッチしかない状態から、「それっぽいUIプロトタイプ」を tldraw.makereal で作ってしまえば、エンジニアはそこから Tailwind CSSベースの実装に進むことができるため、開発工数の大幅な短縮になります。
さらに余計なデザイン崩れが起こりにくいので、後々の修正コストも抑えられました。

簡単なインタラクションの実装も可能

単純な画面デザインだけでなく、フォームやボタンを押したら別画面へ遷移する、といった軽めのインタラクションが実装可能です。
実際に動くプロトタイプとしてクライアントに見せられるので、「イメージがわきやすい」「要件の抜け漏れを減らせる」のは大きい利点でした。

その他のメリット

学習コストが低い:分かりやすいUIでフロントエンジニアやデザイナーだけでなく、マネージャーやディレクターも直感的に使える。

ローカル環境構築のステップ

ここからは、実際にローカル環境で tldraw.makereal を動かす手順を紹介します。

リポジトリのクローン
  • tldraw.makereal のリポジトリをローカルにクローンします。
  • ※READMEではローカルで動作させる場合、starter repoを推奨していますが、メンテナンスされていないのか、そのままでは動作しないようです。
必要なパッケージのインストール
npm install

で依存パッケージをインストールします。

サーバーの起動
npm run dev

でローカルサーバーを立ち上げれば、ブラウザで http://localhost:3000 にアクセスしてツールを利用できます。
起動直後に以下のように表示されるので、APIキーをセットして保存。モデルはgpt-4oを利用しました。

postgres周りの設定

このままでもデザイン生成自体は可能ですが、ローカル環境でホストする場合は生成したデザインの保存が行えません。
デフォルトでは生成したデザインの保存にNeon Serverless Postgresの使用を想定しているようですが、これをローカルのpostgresに向けるようソースを少し修正する必要があります。
今回は例としてローカルにdockerでpostgresを構築し、向き先を変更する手順にしてみます。

ローカル用postgresの構築
docker run --name my-postgres -e POSTGRES_USER=myuser -e POSTGRES_PASSWORD=mypass -e POSTGRES_DB=mydb -p 5432:5432 -d postgres
スキーマ作成用スクリプトの作成
echo "CREATE TABLE links (id SERIAL PRIMARY KEY, shape_id VARCHAR(255) UNIQUE NOT NULL, html TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);" > schema.sql
スキーマの投入
docker exec -i my-postgres psql -U myuser -d mydb < schema.sql
envの作成
echo "POSTGRES_URL=postgres://myuser:mypass@localhost:5432/mydb?sslmode=disable" > .env.local
pgドライバーのインストール
npm install pg
ソースの修正

以下の2ファイルを次のように修正し、neonではなくpgを利用するよう変更します。

  • make-real/app/makereal.tldraw.link/[linkId]/page.tsx
import { Pool } from 'pg'
import { notFound } from 'next/navigation'
import { LinkComponent } from '../../components/LinkComponent'

export const dynamic = 'force-dynamic'

const pool = new Pool({
    connectionString: process.env.POSTGRES_URL,
})

export default async function LinkPage({
   params,
   searchParams,
}: {
    params: { linkId: string }
    searchParams: { preview?: string }
}) {
    const { linkId } = params
    const isPreview = !!searchParams.preview

    const result = await pool.query('SELECT html FROM links WHERE shape_id = $1', [linkId])
    if (result.rows.length !== 1) notFound()

    let html: string = result.rows[0].html

    const SCRIPT_TO_INJECT_FOR_PREVIEW = `
    // send the screenshot to the parent window
    window.addEventListener('message', function(event) {
      if (event.data.action === 'take-screenshot' && event.data.shapeid === "shape:${linkId}") {
        html2canvas(document.body, {useCors: true, foreignObjectRendering: true, allowTaint: true}).then(function(canvas) {
          const data = canvas.toDataURL('image/png');
          window.parent.parent.postMessage({screenshot: data, shapeid: "shape:${linkId}"}, "*");
        });
      }
    }, false);
    // and prevent the user from pinch-zooming into the iframe
    document.body.addEventListener('wheel', e => {
      if (!e.ctrlKey) return;
      e.preventDefault();
    }, { passive: false })
  `

    if (isPreview) {
        html = html.includes('</body>')
            ? html.replace(
                    '</body>',
                    `<script src="https://unpkg.com/html2canvas"></script><script>${SCRIPT_TO_INJECT_FOR_PREVIEW}</script></body>`
              )
            : html + `<script>${SCRIPT_TO_INJECT_FOR_PREVIEW}</script>`
    }

    return <LinkComponent linkId={linkId} isPreview={isPreview} html={html} />
}
  • make-real/app/lib/uploadLink.tsx
'use server'

import { Pool } from 'pg'

const pool = new Pool({
  connectionString: process.env.POSTGRES_URL,
})

export async function uploadLink(shapeId: string, html: string) {
  if (typeof shapeId !== 'string' || !shapeId.startsWith('shape:')) {
    throw new Error('shapeId must be a string starting with shape:')
  }
  if (typeof html !== 'string') {
    throw new Error('html must be a string')
  }

  shapeId = shapeId.replace(/^shape:/, '')
  const client = await pool.connect()
  try {
    await client.query(
      'INSERT INTO links (shape_id, html) VALUES ($1, $2)',
      [shapeId, html]
    )
  } catch (error) {
    console.error('Error in uploadLink:', error)
    throw error
  } finally {
    client.release()
  }
}

問題なくコンパイルが成功すれば、以下のように出力されるはずです。

 % npm run dev

> draw-a-ui@0.1.0 dev
> next dev

   ▲ Next.js 14.0.1
   - Local:        http://localhost:3000
   - Environments: .env.local

 ⚠ Invalid next.config.js options detected: 
 ⚠     Unrecognized key(s) in object: 'functions'
 ⚠ See more info here: https://nextjs.org/docs/messages/invalid-next-config
 ✓ Ready in 1626ms
 ✓ Compiled /middleware in 128ms (55 modules)
 ○ Compiling /makereal.tldraw.com/page ...
 ✓ Compiled /makereal.tldraw.com/page in 3.1s (2305 modules)
 ✓ Compiled in 1214ms (1182 modules)
 ○ Compiling /makereal.tldraw.link/[linkId]/page ...
 ✓ Compiled /makereal.tldraw.link/[linkId]/page in 1328ms (2309 modules)
 ✓ Compiled /makereal.tldraw.com/api/openai/route in 425ms (1198 modules)
 ✓ Compiled in 938ms (2323 modules)

使用出来るAIプロバイダとモデルについて

現状では、OpenAIとAnthropicに対応しており、使用出来るモデルはそれぞれ、

OpenAI
  • gpt-4o
  • gpt-4o-mini
  • gpt-4-turbo
Anthropic
  • claude-3-7-sonnet-20250219
  • claude-3-7-sonnet-20250219 (thinking)
  • claude-3-5-sonnet-20241022
  • claude-3-5-sonnet-20240620
  • claude-3-opus-20240229
  • claude-3-sonnet-20240229
  • claude-3-haiku-20240307

のようです。
ソースを見ると、app/lib/settings.tsx で使用するモデルが定義されており、ここを修正すればgpt-4.5やo1、o3-mini-high等にも変更できそうでしたが、内部で使用しているVercel AI SDKが未対応のため呼び出せないようでした。

デザインの生成

今回はサンプルとして、swaggerのAPIリファレンス画面スクリーンショットを元にデザインを生成してみます。 画面上にスクリーンショットドラッグアンドドロップし、右上の Make Real ボタンを押すとウニョウニョとデザインの生成が始まります。

生成されたデザイン

色味や細かいコンポーネントの配置などは調整の余地がありますが「それっぽい」デザインが出力されました。
一撃でこのレベルのデザインを生成してくれるのであれば、プロトタイプ作成の取っ掛かりとして十分では無いでしょうか。

Tips

今回はスクリーンショットからのデザイン生成例を示しましたが、tldrawはワイヤフレームの作成ツールなので、公式の動画にあるようにワイヤフレームの作成を行いつつmakerealでデザイン生成する、といった利用が本来の利用イメージに近いと思われます。

また、内部的には make-real/app/prompt.ts で定義されたプロンプトを投げているようなので、ここを修正することでプロンプトチューニングも可能そうです。
プロンプト内に

- The HTML file should be self-contained and not reference any external resources except those listed below:
    - Use tailwind (via \`cdn.tailwindcss.com\`) for styling.
    - Use unpkg or skypack to import any required JavaScript dependencies.
    - Use Google fonts to pull in any open source fonts you require.
    - If you have any images, load them from Unsplash or use solid colored rectangles as placeholders.
    - Create SVGs as needed for any icons.

との記載があるので、 Use tailwind (via \cdn.tailwindcss.com\) for styling. の部分を別のCSSフレームワークなどに置き換えることで、tailwind以外にも対応できるのかもしれません。

まとめ

今回 tldraw.makerealを使ってみた結果、以下のようなメリットを感じました。

  • 初期デザインから実装までのブレが少なくなる
  • パワーポイント等の紙芝居ベースでも素早くビジュアル化できる
  • セルフホスト出来るのでセキュリティ要件もクリアしやすい
  • Tailwind CSS で実際のコードにつなぎやすい
  • 簡単な画面遷移やインタラクションを事前に確認できる

プロジェクトでは、 初期にある程度形にしたモックやプロトタイプベースでステークホルダーと会話できるかが鍵になると考えています。
tldraw.makereal は「簡易UIデザイン」+「コードへの変換」のプロトタイプ作成の架け橋を効率的に担ってくれるため、実際にフロントエンドの実装工数を大幅に減らす一手になりました。

  • 「デザインのラフスケッチをとりあえずコードにしやすい形でまとめたい」
  • 「早くモックを作ってプロジェクト内に共有したい」

こんな課題を抱えている方に、tldraw.makereal は最適だと感じています。ぜひ一度試してみてはいかがでしょうか?