生成AI で思い通りのサイトを出力しよう

こんにちは。日頃は blockchain の記事ばかり書いていますが、たまには生成 AI についても書きたいと思います。テーマは生成 AI で出来るだけ思い通りにサイトの生成を行います。

## 前提

今回は以下条件のサイトを生成します。

* Next.js
* Tailwind 
* LP向け
* レスポンシブ向け

また、検証で利用した GPT は ChatGPT 3.5 です。 Bing Copilot を使用する場合、可能であれば Web 検索との連動を OFF にすることをオススメします。また、自社独自の専門用語をプロンプトへ加える場合は適宜補足説明を手動、または RAG 等でプロンプトへ加えてください。

## 背景

この記事を書くに当たって感じていた課題と解決したことを記載します。

### 課題

普段は、GPT系を駆使して素早くコードを作成することを心がけています。バックエンドの生成にはそれほど問題はないのですが、フロントエンドでは生成されたAIに機能だけでなく、適切なレイアウトも伝えなければなりません。これを言葉で説明するのは難しく、何度か生成しても意図したコードにならないことがあります。

### 解決

通常のプロンプトに加えて、構造情報として簡素化されたJSXを追加することで、より詳細な情報を的確に伝えることが可能です。この手法を用いることで、生成AIに対して機能だけでなく、具体的なコンポーネントの配置や階層構造も明確に示すことができます。

## 具体例

### ヒーローエリアの生成

まずは LP のヒーローエリアを作成してみます。用意したプロンプトは次の通りです。

```markdown 
以下は「${ここにサイトのタイトルを追加してください}」という「${ここにサイトの説明を簡潔に追加してください}」の LP の下書き React JSX です。
「${ここにサイトのタイトルを追加してください}」を紹介する適切なテキストと CSS を追加してください。 CSS は Tailwind を使用します。

条件
* 必須 = Next.js/React 向けの JSX を作成する
* 必須 = CSS は Tailwind を使って作成する
* 必須 = レスポンシブ
* 任意 = 画像を使用する場合 href は /image.webp とする

>>>
<div>
  <header>
    <!-- 画面上部に固定表示されるバー。左側にサイト名 右側に Drawer を開くボタン -->
  </header>
  <section>
    <!-- サイトトップ -->
    <div>
      <!-- デスクトップでは 2 column, モバイルでは 1 column の hero view -->
      <div>
        <!-- サイトのタイトルと簡単な説明 -->
      </div>
      <div>
        <!-- サイトを象徴する画像の挿入 -->
      </div>
    </div>
  </section>
</div>
>>>
```

早速以下の通りプロンプトを用意してみます。

```markdown 
以下は「世界初の自律駆動形お洗濯ロボット」という「当社の一般家庭向け家事お手伝い用の新たなロボット商品を紹介する」の LP の下書き React JSX です。
「世界初の自律駆動形お洗濯ロボット」を紹介する適切なテキストと CSS を追加してください。 CSS は Tailwind を使用します。

条件
* 必須 = Next.js/React 向けの JSX を作成する
* 必須 = CSS は Tailwind を使って作成する
* 必須 = レスポンシブ
* 任意 = 画像を使用する場合 href は /image.webp とする

>>>
<div>
  <header>
    <!-- 画面上部に固定表示されるバー。左側にサイト名 右側に Drawer を開くボタン -->
  </header>
  <section>
    <!-- サイトトップ -->
    <div>
      <!-- デスクトップでは 2 column, モバイルでは 1 column の hero view -->
      <div>
        <!-- サイトのタイトルと簡単な説明 -->
      </div>
      <div>
        <!-- サイトを象徴する画像の挿入 -->
      </div>
    </div>
  </section>
</div>
>>>
```

結果は次のようになりました。

```tsx 
// pages/index.js (Next.jsページファイル)

import Head from 'next/head';

const Home = () => {
  return (
    <div>
      <header className="fixed top-0 left-0 right-0 bg-gray-800 text-white p-4 flex justify-between items-center">
        <span className="text-xl font-bold">Your Site Name</span>
        <button className="text-white">
          {/* Drawer 開くボタンのアイコンやアクションをここに追加 */}
          Open Drawer
        </button>
      </header>
      <section className="mt-16 p-4">
        <div className="lg:flex">
          {/* デスクトップでは2列、モバイルでは1列 */}
          <div className="lg:w-1/2 mb-4 lg:mb-0">
            <div className="text-4xl font-bold mb-4">世界初の自律駆動形お洗濯ロボット</div>
            <p className="text-gray-700 mb-4">
              新しいレベルの自律駆動型お洗濯ロボットが登場しました。家事の手助けをこの革新的な製品で体験しましょう。
            </p>
          </div>
          <div className="lg:w-1/2">
            {/* 画像の挿入 */}
            <img src="/image.webp" alt="Laundry Robot" className="w-full h-auto" />
          </div>
        </div>
      </section>
    </div>
  );
};

export default Home;
```

早速 Next.js で確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633687/16d7ddd2-f948-f1ee-1b60-5765588c21a8.png)

![Screenshot 2024-01-04 at 15.22.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633687/3cd71cf3-8fa4-dc97-6813-b5f21e46b3b9.png)

ほぼ意図通りです。ここからの細かい位置や色調整は自分でやりましょう。もしくはここへの返信でテーマカラー等を指定して再生成させればそれも作成させる事は可能でしょう。（私はそこは自分でやった方が楽ですが）

### 特徴紹介エリアの作成

次は LP で自社商品の特徴を一覧で紹介するエリアを作ってみましょう

```markdown 
以下は「${ここにサイトのタイトルを追加してください}」という「${ここにサイトの説明を簡潔に追加してください}」の LP の下書き React JSX です。
「${ここにサイトのタイトルを追加してください}」を紹介する適切なテキストと CSS を追加してください。 CSS は Tailwind を使用します。

条件
* 必須 = Next.js/React 向けの JSX を作成する
* 必須 = CSS は Tailwind を使って作成する
* 必須 = レスポンシブ
* 任意 = 画像を使用する場合 href は /image.webp とする

挿入したい特徴
* ${特徴-1}
* ${特徴-2}
* ${特徴-3}

>>>
<>
 {/* 最大3列 */}
  {[
     // 特徴
   ].map(e,i) => <div>
      <div>
        {/* カード形式 */}
        <div>{/* カードのコンテンツを説明する画像を丸く表示 */}</div>
        <div>{/* カードのコンテンツ。特徴のタイトル */}</div>
        <div>{/* カードのコンテンツ。特徴のタイトルを説明する詳細 */}</div>
        <div>{/* 必ずカードの底に表示されるリンク */}</div>
      </div>
   </div>
  }
</>
>>>
```

また、以下の通り投入してみます。

```markdown
以下は「世界初の自律駆動形お洗濯ロボット」という「当社の一般家庭向け家事お手伝い用の新たなロボット商品を紹介する」の LP の下書き React JSX です。
「世界初の自律駆動形お洗濯ロボット」を紹介する適切なテキストと CSS を追加してください。 CSS は Tailwind を使用します。

条件
* 必須 = Next.js/React 向けの JSX を作成する
* 必須 = CSS は Tailwind を使って作成する
* 必須 = レスポンシブ
* 任意 = 画像を使用する場合 href は /image.webp とする

挿入したい特徴
* お手頃価格
* 親しみやすい見た目
* 人間の3倍高速に完了

>>>
<>
 {/* 最大3列 */}
  {[
     // 特徴
   ].map(e,i) => <div>
      <div>
        {/* カード形式 */}
        <div>{/* カードのコンテンツを説明する画像を丸く表示 */}</div>
        <div>{/* カードのコンテンツ。特徴のタイトル */}</div>
        <div>{/* カードのコンテンツ。特徴のタイトルを説明する詳細 */}</div>
        <div>{/* 必ずカードの底に表示されるリンク */}</div>
      </div>
   </div>
  }
</>
>>>
```

結果は次の通りです。

```tsx 
import React from 'react';

const LaundryRobotLP = () => {
  const features = [
    {
      image: '/image.webp', // 画像を使用する場合
      title: 'お手頃価格',
      description: '家計に優しい価格で、手頃なお値段でご提供します。',
    },
    {
      image: '/image.webp', // 画像を使用する場合
      title: '親しみやすい見た目',
      description: '可愛らしいデザインで、家庭に馴染む外観を実現しました。',
    },
    {
      image: '/image.webp', // 画像を使用する場合
      title: '人間の3倍高速に完了',
      description: '高いパフォーマンスで、驚異的なスピードでお洗濯を完了します。',
    },
  ];

  return (
    <>
      <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8">
        {features.map((feature, index) => (
          <div key={index} className="p-4 border rounded-md">
            {/* カード形式 */}
            <div className="mb-4">
              {/* カードのコンテンツを説明する画像を丸く表示 */}
              <img
                src={feature.image}
                alt={feature.title}
                className="w-20 h-20 rounded-full mx-auto"
              />
            </div>
            <div className="mb-2 font-bold text-lg text-center">
              {/* カードのコンテンツ。特徴のタイトル */}
              {feature.title}
            </div>
            <div className="text-sm text-gray-600 text-center">
              {/* カードのコンテンツ。特徴のタイトルを説明する詳細 */}
              {feature.description}
            </div>
            <div className="mt-4 text-center">
              {/* 必ずカードの底に表示されるリンク */}
              <a href="/image.webp" className="text-blue-500 hover:underline">
                詳細を見る
              </a>
            </div>
          </div>
        ))}
      </div>
    </>
  );
};

export default LaundryRobotLP;
```

next/link や next/image が使われていない点はプロンプトの改善余地ありですが、いい感じですね。実際にまた確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633687/eb2ba464-fd6d-d202-b4f2-d751eeafeb6e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633687/e3201f8f-d3d8-cbfa-c7fd-4da903a2e5c8.png)

また、イメージ通りです。微調整は勿論しますが、ゼロから作成するよりはとっても楽です。

## まとめ　

他にも作成したプロンプトはあるのですが、長くなってしまうのでこれくらいにしておきます。

最近は画像も認識する生成AI が登場し、フロントエンドのイメージ画像を書くだけでも作成されるようになってきましたね。ただ、私は画像だけだと各コンポーネントのより細かい条件の指定が逆に手間かなと感じていて構造情報はこういった簡素化した JSX で指定してしまった方がコメントで微調整しやすく、かつ絵を書くよりも寧ろこっちの方が早いと感じているので今回の方法をご紹介しました。

ある程度書き溜めたら JSX 部分を RAG にすれば、もっと楽にプロンプトを生成出来るかもしれませんね。以上。