【AIでWEBアプリ開発】AIを利用したジョークアプリの作成方法と実装内容

AI

笑撃!AIジョーク!~AIに笑わせられたら負け~

補足

上のアプリ画面ではAIの考えた「一発ギャグ」「大喜利」「漫才」「アメリカンジョーク」を無料で見ることが出来ます。

それぞれのボタンから専用画面に入れば見れますので、ご自由に使用してください。

尚、本アプリは1日の利用制限を設けており、それぞれのジョークは1日5回ずつまでとなっています。

これまでに作成したもの

私がこれまでに作成したアプリやゲームについても載せておきます。

AIを使ったアプリ開発の方法については「センコちゃんのブロック崩し」の記事で解説していますので、気になる方は閲覧してみて下さい。

本アプリについて

根本的なアプリの作成目的等は「センコちゃんのブロック崩し」の記事で解説した通りなので詳細は省きます。

今回のAIジョークアプリを作った大きな理由は「APIを利用してAIが応答を返すようなアプリを試作したい」というものとなります。

今後のアプリやゲームはAIと連携したものが主となると私は考えており、それらアプリやゲームを作っていくための叩き台とするために、本アプリを作成しました。

APIはChatGPTの「gpt-4o-mini」を利用しています(性能は微妙ですが、API費用の関係上でこれが一番マシだった)。

まぁ、APIの使用モデルは外部ファイルで持たせているので、後から簡単に変えられるので問題はないです。

ゲーム作成方法

基本的には「センコちゃんのブロック崩し」の作成方法と同じですが、AIに作成してもらう際のプロンプトは既に作成済みのアプリをベースにするように依頼しました。

実際にChatGPTに作成を依頼した時のプロンプトが以下です。このプロンプトの続きとして、更に前回作成したソースコードの全量を記載しました。

滅茶苦茶細かく指定していますが、前回の記事でもいった通り、AIを使ったコーディングはこれくらい指定しないと、望むものが出来ないのです。

ちなみに今回の実装もChatGPTのo1モデルを利用しています。さすがにgpt-4oでは性能が低すぎて、このレベルの実装は出来ませんので。
【リクエスト概要】  
既に構築済みのapp1のアプリのソースを参考にして、以下の要件を満たすWebアプリケーションをapp2として構築してください。  
既存のapp2関連のソースはサンプルソースしか入っていないので気にしないで良いです。
また、既存のapp1同様に以下のコマンドで起動できるようにしてください。

docker build -t my-multi-app .
docker run -d --name multi-app -p 8080:80 my-multi-app

構築したアプリのプロジェクト構成図並びに全ソースを提示してください。

---

【アプリ概要】  
・アプリのタイトルは「笑撃!AIジョーク!~毎日を笑って過ごしたい人へ~」
・アプリの目的は「毎日AIの生成した面白いジョークを見ることで、笑いのある1日をユーザーに提供したいため」
・アプリに必要な画面数は5つ
・アプリの根本的な仕様として、ジョークの作成にAI(ChatGPT)を活用する

---

【各画面の概要】
・トップ画面:アプリ起動時に出る画面、他の画面に遷移するためのボタンを設ける
・一発ギャグ画面:AI生成した一発ギャグを表示する画面
・大喜利画面:AI生成した大喜利を表示する画面
・漫才画面:AI生成した漫才を表示する画面
・アメリカン・ジョーク画面:AI生成したアメリカン・ジョークを表示する画面


【トップ画面の仕様】
・アプリを開いた際に最初に表示される画面
・画面上に「今日の一発ギャグ」、「今日の大喜利」、「今日の漫才」、「今日のアメリカン・ジョーク」の4ボタンを設ける
・「今日の一発ギャグ」ボタン押下で「一発ギャグ画面」画面に遷移
・「今日の大喜利」ボタン押下で「大喜利画面」画面に遷移
・「今日の漫才」ボタン押下で「漫才画面」画面に遷移
・「今日のアメリカン・ジョーク」ボタン押下で「アメリカン・ジョーク画面」画面に遷移

【一発ギャグ画面の仕様】
・画面には「今日の一発ギャグ」と「トップに戻る」の2つのボタンを表示
・「今日の一発ギャグ」ボタン押下すると、OpenAIのAPIに「今日の面白い一発ギャグを一つお願いします」とプロンプトを連携し、返却された一発ギャグの内容を画面に表示する
・「トップに戻る」ボタン押下すると、トップ画面に遷移

【大喜利画面の仕様】
・画面には「今日の大喜利」と「トップに戻る」の2つのボタンを表示
・「今日の大喜利」ボタン押下すると、OpenAIのAPIに「今日の面白い大喜利を一つお願いします」とプロンプトを連携し、返却された大喜利の内容を画面に表示する
・「トップに戻る」ボタン押下すると、トップ画面に遷移

【漫才画面の仕様】
・画面には「今日の漫才」と「トップに戻る」の2つのボタンを表示
・「今日の漫才」ボタン押下すると、OpenAIのAPIに「今日の面白い漫才を一つお願いします」とプロンプトを連携し、返却された漫才の内容を画面に表示する
・「トップに戻る」ボタン押下すると、トップ画面に遷移

【アメリカン・ジョーク画面の仕様】
・画面には「今日のアメリカン・ジョーク」と「トップに戻る」の2つのボタンを表示
・「今日の漫才」ボタン押下すると、OpenAIのAPIに「今日の面白いアメリカン・ジョークを一つお願いします」とプロンプトを連携し、返却されたアメリカン・ジョークの内容を画面に表示する
・「トップに戻る」ボタン押下すると、トップ画面に遷移

【AIについて】
・OpenAI社のAPI(https://api.openai.com/v1/chat/completions)を利用する(APIキーは.envから取得する)
・OpenAIのAPIでは「gpt-4o-mini」モデルを利用する
・APIの出力は日本語で行うように指定する
・APIの入力・出力の際にはエラーが起きていないか判定する処理を入れる
・APIの入力・出力の際にはUTF-8の文字コードとなっているかチェックする処理を入れる

【追加要項】
・何度も同じ人に使われるとAPIの料金が嵩む為、同じ人が同じ種類のジョークを閲覧できるのは1日5回までの制限を設けて欲しい
・画面は全体的にお笑いをイメージした明るいものにして欲しい
・セキュリティについても考慮して欲しい
・今回作成するアプリは私が運営しているWordpressのブログ(https://senkohome.com/)からiframeで呼び出す想定としている(https接続は出来るようにapp1の時に基盤設定済み)

---

この仕様に基づき、Node.js + Expressのプロジェクトをゼロから作るソースコード一式の例を示してください。  
できれば以下のディレクトリ構成例や、インストールすべき依存パッケージ例も教えてください。  

【期待する回答内容】  
- package.json の記述例  
- ディレクトリ構成の提案例  
- app.js や routerコードなど一通りのサーバーサイドソースコード例  
- .env の書き方例(APIキーやモデル名など)  
- 動作確認の手順  

以上を踏まえた上で、あなたが考えるベストプラクティスな構成とソースをわかりやすく提示してください。  

【現在のプロジェクト構成図】root:.
│  Dockerfile
│  entrypoint.sh
│  README.md
│
├─apps
│  ├─app1
│  │  │  index.js
│  │  │  package.json
│  │  │
│  │  └─public
│  │      │  index.html
│  │      │  main.js
│  │      │  style.css
│  │      │
│  │      ├─images
│  │      └─sound
│  │
│  └─app2
│          index.js
│          package.json
│
└─nginx
        default.conf

Open AIのAPIを利用する方法

今回のアプリの肝となっているのは、Open AI社のAPIを利用する方法についてです。

このAPIを利用することで、Open AI社のAI(ChatGPTを利用している時のように)が出力した回答をアプリで使うことが出来るのです。

ただ、ぶっちゃけ実装方法については今回のプロンプトの見本のように指定しておくことで、ChatGPTが勝手に実装してくれるので、そこまで難しく考える必要はありません。

ですが、APIの利用のためには事前にOpen AI社のAPIが利用できるようにしておく必要があります。

手順としては主に以下のような流れとなります。

OpenAIのアカウント作成(ChatGPTのアカウントとは別)
②APIキーの作成
③課金設定
④発行されたAPIキーを使って、APIを使えることを確認したら終了


具体的な方法は様々なサイトで解説されているので、それらをご確認ください。大して難しい作業ではないです。

これで利用可能になったAPIキーを、ChatGPTが実装してくれたソースに設定することで、AIの出力した回答をアプリ内で利用することが出来るようになります。

ただし、注意点としてAPIは利用する度に費用が発生します(ChatGPTとは別に)。

APIの費用については少し複雑な計算ですが、大まかには「指定したモデル」「インプット文章量(インプットトークン)」「アウトプット文章量(アウトプットトークン)」の3つで決まります。

「指定したモデル」というのは「gpt-4o」「gpt-4o-mini」等になりますが、基本的には性能の高いモデルを利用するほど高くなります(ただ、mini系は結構安い)。

インプットとアウトプットの文章量は言葉通りです。仮に「gpt-4o-mini」を利用して、500文字前後のインプットとアウトプットであれば、費用は1回約0.2円程度ですかね。

もちろん、文章量が多ければ多いほど費用はかさみますし、1度の費用が安くても大勢の人が利用するアプリであれば、当然費用は膨大なものになる可能性もあります。

そのため、APIを実装したアプリには一人当たりの利用制限を掛ける等の対策が必要となります。

今回のアプリでも一人が1日に利用できる回数はそれぞれ5回までとしました。これなら全力で利用されても一人数円程度なので、大して気にするほどではありません。

また、使った分だけユーザーに課金するような設計のアプリにするかですね。有料アプリはかなりのレベルを求められるので、作る場合は気合を入れたものにする必要はありますが。

ゲームの実装内容

今回実装したアプリの実装内容について記載します。基本的な構成は前回作成したアプリ基盤のうえで稼働させている通りのため、詳しい考え方等は前回の記事を読んでください。

プロジェクト構成

root:.
│  Dockerfile
│  entrypoint.sh
│  README.md
│
├─apps
│  ├─app1 ※前回作成したソース
│  └─app2
│      │  config.json
│      │  index.js
│      │  package.json
│      │
│      ├─public
│      │      american.html
│      │      gag.html
│      │      index.html
│      │      main.css
│      │      main.js
│      │      manzai.html
│      │      ogiri.html
│      │
│      └─routes
│              jokeRoutes.js
│
└─nginx
        default.conf

Dockerfile

# 1. Node.js公式イメージをベース
FROM node:18-bullseye

# 2. Nginxインストール & デフォルト設定削除
RUN apt-get update && \
    apt-get install -y nginx && \
    rm -rf /var/lib/apt/lists/* && \
    rm -f /etc/nginx/sites-enabled/default

# 3. 作業ディレクトリ
WORKDIR /usr/src/app

# 4. アプリソースをコピー
COPY apps/ ./apps/

# 5. app1, app2 の 依存関係インストール
WORKDIR /usr/src/app/apps/app1
RUN npm install

WORKDIR /usr/src/app/apps/app2
RUN npm install

# ここで config.json もコピーされるはず (すでにCOPY apps/ ./apps/ しているのでOK)
# もし別にするなら:
# COPY apps/app2/config.json /usr/src/app/apps/app2/config.json

# 6. Nginx設定ファイルをコピー
WORKDIR /usr/src/app
COPY nginx/default.conf /etc/nginx/conf.d/default.conf

# 7. entrypoint.sh をコピー & 実行権限
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# 8. ポート公開
EXPOSE 80

# 9. コンテナ起動時に実行
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

entrypoint.sh

#!/usr/bin/env bash
set -e

echo "Starting App1 (Block Breaker)..."
PORT=3001 node /usr/src/app/apps/app1/index.js &
APP1_PID=$!

echo "Starting App2..."
PORT=3002 node /usr/src/app/apps/app2/index.js &
APP2_PID=$!

sleep 3

# app1の生存確認
if ! ps -p $APP1_PID > /dev/null; then
    echo "Error: App1 (Block Breaker) failed to start."
    exit 1
fi

# app2の生存確認
if ! ps -p $APP2_PID > /dev/null; then
    echo "Error: App2 failed to start."
    exit 1
fi

echo "Starting Nginx..."
exec nginx -g 'daemon off;'

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>笑撃!AIジョーク!~AIに笑わせられたら負け~</title>
  <link rel="stylesheet" href="/app2/main.css">
</head>
<body>
  <h1>笑撃!AIジョーク!~AIに笑わせられたら負け~</h1>
  <p>AIが生成した面白いジョークをチェックして、笑顔あふれる1日を過ごしてみよう!</p>

  <!-- 縦に並べる例:ボタンの後に <br> を入れるなど -->
  <button id="btn-gag">今日の一発ギャグ</button><br>
  <button id="btn-ogiri">今日の大喜利</button><br>
  <button id="btn-manzai">今日の漫才</button><br>
  <button id="btn-american">今日のアメリカン・ジョーク</button>

  <script src="/app2/main.js"></script>
</body>
</html>

american.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>アメリカン・ジョーク | 笑撃!AIジョーク!</title>
  <link rel="stylesheet" href="/app2/main.css">
</head>
<body>
  <h1>アメリカン・ジョーク</h1>
  <button id="btn-get-american">アメリカン・ジョークをお願いします</button>
  <button id="btn-back-top">トップに戻る</button>

  <div id="result-area"></div>

  <script src="/app2/main.js"></script>
</body>
</html>

main.css

/* apps/app2/public/main.css */

body {
  margin: 0;
  padding: 0;
  background: #ffeecc;
  font-family: "ヒラギノ角ゴ ProN", "メイリオ", sans-serif;
  text-align: center;
}

h1 {
  margin: 20px 0;
  color: #d44f0c;
}

button {
  margin: 5px;
  padding: 10px 20px;
  font-size: 1rem;
  cursor: pointer;
  border: 2px solid #fa6400;
  border-radius: 4px;
  background-color: #ffcc66;
}

#result-area {
  margin: 20px auto;
  width: 80%;
  background: #fff8e8;
  padding: 10px;
  border: 1px dashed #ffa500;
  min-height: 100px;
  text-align: left;
  white-space: pre-wrap;
  box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
}

main.js

/**
 * apps/app2/public/main.js
 *
 * フロント側のイベント処理
 * - 「今日の◯◯」ボタン → APIコールでジョーク取得
 * - 「トップに戻る」ボタン → index.html に戻る
 */

(function() {
    // 各HTMLページで共通
    const btnGag = document.getElementById('btn-get-gag');
    const btnOgiri = document.getElementById('btn-get-ogiri');
    const btnManzai = document.getElementById('btn-get-manzai');
    const btnAmerican = document.getElementById('btn-get-american');
    const btnBackTop = document.getElementById('btn-back-top');
  
    const resultArea = document.getElementById('result-area');
  
    // 「トップに戻る」ボタン
    if (btnBackTop) {
      btnBackTop.addEventListener('click', () => {
        window.location.href = '/app2/';
      });
    }
  
    // トップ画面ボタン → 個別ページへ移動
    const topPageButtons = [
      { id: 'btn-gag', path: 'gag.html' },
      { id: 'btn-ogiri', path: 'ogiri.html' },
      { id: 'btn-manzai', path: 'manzai.html' },
      { id: 'btn-american', path: 'american.html' }
    ];
    topPageButtons.forEach(item => {
      const el = document.getElementById(item.id);
      if (el) {
        el.addEventListener('click', () => {
          window.location.href = `/app2/${item.path}`;
        });
      }
    });
  
    // APIコール
    async function fetchJoke(category) {
      if (!resultArea) return;
      resultArea.textContent = '考え中...';
      try {
        const res = await fetch(`/app2/api/joke/${category}`);
        if (!res.ok) {
          const errData = await res.json();
          throw new Error(errData.error || 'API Error');
        }
        const data = await res.json();
        resultArea.textContent = data.joke;
      } catch (err) {
        console.error(err);
        resultArea.textContent = '' + err.message;
      }
    }
  
    // 各画面の「今日の◯◯」ボタン
    if (btnGag) {
      btnGag.addEventListener('click', () => fetchJoke('gag'));
    }
    if (btnOgiri) {
      btnOgiri.addEventListener('click', () => fetchJoke('ogiri'));
    }
    if (btnManzai) {
      btnManzai.addEventListener('click', () => fetchJoke('manzai'));
    }
    if (btnAmerican) {
      btnAmerican.addEventListener('click', () => fetchJoke('american'));
    }
  })();

config.json

{
    "OPENAI_API_KEY": "自分で取得したAPIキー情報を設定",
    "OPENAI_MODEL": "gpt-4o-mini",
    "PORT": 3002
  }

package.json

{
  "name": "ai-joke-app2",
  "version": "1.0.0",
  "description": "笑撃!AIジョーク!~毎日を笑って過ごしたい人へ~",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "axios": "^1.3.4",
    "cookie-parser": "^1.4.6",
    "express": "^4.18.2",
    "helmet": "^6.1.2"
  }
}

まとめ

本記事ではOpenAIのAPIを呼び出して、その応答結果を画面に表示するジョークアプリの作成方法について解説しました。

このAPI利用方法を応用すれば、AIが自動で応答してくれるChatBotの作成や、AI彼女みたいな感じのアプリを作成することも難しくないでしょう。

今後そういったものを作るかどうかは別として、OpenAI社のAPIを自由に利用できるようなアプリを作れるというのは非常に開発の幅が広がると思います。

しかし、作るときはそこまで期待していなかったのですが、思ったよりAIジョークは面白いものが出てくるときが有るので、意外に良い開発だったかもしれません。

他にもアプリを作るたびに記事にはするつもりなので、興味があればまた閲覧いただけると幸いです。

Canvaで簡単にマンガを作る方法 AIイラストで誰でもマンガ家になれる時代! – センコの活動記録 (senkohome.com)

コメント

タイトルとURLをコピーしました