makerealでWebページ生成してみた

この記事は [レコチョク Advent Calendar 2023](https://qiita.com/advent-calendar/2023/recochoku) の16日目の記事となります。

## はじめに
はじめまして！株式会社レコチョクでサーバーサイドエンジニアをしている西村です。
最近の音楽ブームはPeople In The Boxです。Billboard live tokyoでのライブ最高でした。

最近AIに関する技術で何か面白そうなものがないか探している際に「**makereal**」を見つけました。それがかなり衝撃的な内容だったので、今回そちらを紹介しようと思います。

## makerealとは？
**makereal**とは「tldraw」というホワイトボードを作成するReactライブラリと
ChatGPT-4Vを利用して作成された、ワイヤーフレームからWebページ（HTML）を生成するアプリです。
※ 作成者はFigmaのエンジニアで、ホスティングする形でtldraw社がアプリを公開しています。

実際にtldraw社のX（旧:Twitter）にはこんな映像が上がっていました

https://twitter.com/tldraw/status/1724892287304646868?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1724892287304646868%7Ctwgr%5E7d3fcbe00f54cccf92f0a4d4b3d23d5b9225c7dd%7Ctwcon%5Es1_&amp;ref_url=https%3A%2F%2Fembed.zenn.studio%2Ftweetzenn-embedded__cd49ded7cd9

このようにワイヤーフレームからWebページが生成できます！
またHTMLコードも生成することが可能で、とてつもなく便利な代物の予感です。

## 利用手順

### 1. OpenAI APIのKey取得
**makereal**を使用するためにはOpenAIのAPIキーが必要です。OpenAIのサイトにログインし、 
左メニュー[API keys](https://platform.openai.com/api-keys)から`create new sercet key`でキーを生成しましょう。
生成するとAPIキーが発行されるので、安全な場所に保管してください。
※ **makereal**を利用するにはOpenAI APIの有料プランに変更が必要です。

![スクリーンショット 2023-11-28 12.08.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957390/20881872-f407-dd84-da5f-a181a3035838.png)

### 2. makereal
生成したAPIキーを画面中央下のフォー厶に入力すると設定完了です。
※ 利用するにはサイト側にAPIキーを登録する必要があります。
※ ご利用する際は自己責任となりますのでご注意ください。
※ APIを外部サイトに登録したくないという方はdockerを利用してローカルで環境構築できるそうなので、もしよければ調べてみてください。

![スクリーンショット 2023-11-28 19.32.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957390/6ac95c05-7c87-d548-373a-646cfdae968a.png)

あとはワイヤーフレームを作成し、右上MakeRealボタンを押すとWebページの生成ができます！

## 実践
今回は簡単な掲示板アプリのワイヤーフレームを作って、Webページを生成してみました。
写真左が自分が作成したワイヤーフレームで、写真右が**makereal**で生成したWebページになります。

### ログイン画面
- Goodポイント
    - 指定通りの画面を生成した
    - Google＆Facebookの画像&amp;リンクを設定した
    - Passwordの最大文字数を15文字で設定した
- Badポイント
    - 特になし！！
    
![スクリーンショット 2023-11-28 21.04.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957390/e5afd00a-3909-ba3b-191b-324521dda499.png)

### 生成されたHTMLコード
```HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login Page</title>
    <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body {
            font-family: 'Open+Sans', sans-serif;
        }
        .social-btn {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 44px;
            height: 44px;
            border-radius: 22px;
            margin-right: 0.5rem;
        }
        .social-btn img {
            width: 20px;
            height: 20px;
        }
    </style>
</head>
<body class="bg-gray-50 flex items-center justify-center h-screen">
    <div class="bg-white p-8 rounded-lg shadow-md w-full max-w-sm">
        <h2 class="text-2xl font-semibold mb-6 text-center">Sign up</h2>
        <form>
            <div class="mb-4">
                <label for="email" class="block text-sm font-medium text-gray-700">Email</label>
                <input type="email" id="email" name="email" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500" required>
            </div>
            <div class="mb-6">
                <label for="password" class="block text-sm font-medium text-gray-700">Password</label>
                <input type="password" id="password" name="password" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500" maxlength="15" required>
            </div>
        <button type="submit" class="w-full bg-indigo-600 text-white rounded-md py-2 font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-50">SIGN UP</button>
        </form>
        <div class="flex items-center justify-center mt-6">
            <a href="https://google.com" class="social-btn bg-blue-600 text-white" title="Sign up with Google">
                <img src="https://img.icons8.com/color/48/000000/google-logo.png" alt="Google">
            </a>
            <a href="https://facebook.com" class="social-btn bg-blue-800 text-white" title="Sign up with Facebook">
                <img src="https://img.icons8.com/color/48/000000/facebook-new.png" alt="Facebook">
            </a>
        </div>
    </div>
</body>
</html>
```


### 画面遷移(ログイン画面 → 投稿画面)
- Goodポイント
    - 表示崩れがない画面を生成した
    - ログイン → 投稿画面への遷移を実現した
- Badポイント
    - 要件を満たすWebページになるまでに2回、生成を失敗した(HTML要素の表示 &amp; 遷移)
    - 実用的ではないコードが生成された(HTML要素の表示・非表示で遷移を再現)

![スクリーンショット 2023-11-28 21.35.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957390/cb8d9ac2-7067-c2ae-0dbf-71e34afe23ab.png)

### 生成されたHTMLコード

```HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login and Post Page</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Open+Sans', sans-serif;
        }
    </style>
</head>
<body class="bg-gray-100">

    <!-- Login Page -->
    <div class="min-h-screen flex items-center justify-center">
        <div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
            <h2 class="text-2xl font-semibold mb-6 text-center">Sign Up</h2>
            <form>
                <div class="mb-4">
                    <label for="email" class="block text-gray-700 text-sm font-bold mb-2">Email</label>
                    <input type="email" id="email" name="email" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" required>
                </div>
                <div class="mb-6">
                    <label for="password" class="block text-gray-700 text-sm font-bold mb-2">Password</label>
                    <input type="password" id="password" name="password" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" maxlength="15" required>
                </div>
                <div class="mb-4">
                    <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full">SIGN UP</button>
                </div>
            </form>
            <div class="flex items-center justify-between">
                <a href="#" class="bg-blue-100 hover:bg-blue-200 text-blue-800 font-bold py-2 px-4 rounded inline-flex items-center justify-center">
                    <span class="mr-2">G</span> Google
                </a>
                <a href="#" class="bg-blue-100 hover:bg-blue-200 text-blue-800 font-bold py-2 px-4 rounded inline-flex items-center justify-center">
                    <span class="mr-2">F</span> Facebook
                </a>
            </div>
        </div>
    </div>

    <!-- Post Page -->
    <div class="hidden min-h-screen flex items-center justify-center">
        <div class="bg-white p-8 rounded-lg shadow-lg w-full max-w-2xl">
            <form>
                <div class="mb-4">
                    <textarea id="postContent" name="postContent" rows="4" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" maxlength="100" placeholder="What's on your mind?"></textarea>
                </div>
                <div class="flex justify-end">
                    <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">POST</button>
                </div>
            </form>
        </div>
    </div>

    <script>
        // Handle form submission to switch between pages
        const loginForm = document.forms[0];
        const loginPage = document.querySelector('.min-h-screen.flex');
        const postPage = document.querySelectorAll('.min-h-screen.flex')[1];

        loginForm.addEventListener('submit', function(event) {
            event.preventDefault();
            loginPage.classList.add('hidden');
            postPage.classList.remove('hidden');
        });
    </script>

</body>
</html>
```

### 投稿画面
- Goodポイント
    - メッセージを送信・表示できる画面を生成した
    - 投稿内容を良しなに表示してくれた
- Badポイント
    - 投稿日時箇所の生成に複数回失敗した
![スクリーンショット 2023-11-29 10.53.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957390/480f4acf-54dc-ed6b-9186-7d00af5c2f8d.png)

### 生成されたHTMLコード

```HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Post Page</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        /* Additional styles if needed */
    </style>
</head>
<body class="bg-gray-100">
    <div class="container mx-auto p-4">
        <h1 class="text-2xl font-bold mb-4">Post Page</h1>
        <div class="bg-white shadow rounded p-4">
            <h2 class="text-xl font-semibold mb-2">Timeline</h2>
            <div id="posts" class="space-y-2">
                <!-- Posts will be added here dynamically -->
            </div>
            <div class="mt-4">
                <textarea id="postInput" class="border-2 border-gray-300 rounded w-full p-2" placeholder="What's on your mind?" maxlength="100"></textarea>
                <button id="postButton" class="mt-2 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
                    POST
                </button>
            </div>
        </div>
    </div>

    <script>
        const postsContainer = document.getElementById('posts');
        const postInput = document.getElementById('postInput');
        const postButton = document.getElementById('postButton');

        postButton.addEventListener('click', () => {
            const postMessage = postInput.value.trim();
            if (postMessage) {
                const postDate = new Date().toISOString().replace('T', ' ').substring(0, 19);
                const newPostHtml = `
                    <div class="border-b-2 border-gray-200 pb-2">
                        <div class="text-sm text-gray-500">${postDate}</div>
                        <div class="text-gray-700">${postMessage}</div>
                    </div>
                `;
                postsContainer.innerHTML = newPostHtml + postsContainer.innerHTML;
                postInput.value = '';
            }
        });
    </script>
</body>
</html>
```

## まとめ
今回は**makereal**を使ってWebページを生成してみました。
簡単な内容であれば理想通りのWebページ(HTML)を作成してくれました。
テキスト指示による生成と比べて、直感的かつ具体的に指示できるのでとても使いやすかったです。

内容が複雑なものに関してはまだまだ上手く生成されないことが多いのですが、
コンポーネント単位で生成するなど、利用者が工夫することで業務にも活かせるのではないかと思いました。

他にも**makereal**に遷移図などの情報を与えて、
凄いアプリを作られている方がいたので、もしよければそちらも調べてみてください。


明日の [レコチョク Advent Calendar 2023](https://qiita.com/advent-calendar/2023/recochoku) は17日目「【JavaScript】Svelteを触ってみた」です。お楽しみに！


## 参考文献
* [革新的デザイン？プログラミング？の未来：Make RealとTldrawで創造性を解放](https://note.com/panda_lab/n/n9de8e8bde96d)
* [makereal(tldraw)を試してみる](https://synamon.hatenablog.com/entry/makereal)
* [図からプログラムを自動生成してくれるmakereal ( tldraw )について](https://zenn.dev/mobdev/articles/c6ca9d902481a5)

---

この記事はレコチョクのエンジニアブログの記事を転載したものとなります。 
 
https://techblog.recochoku.jp/9986