ChatGPTのWebUIを30分で作る

# はじめに
ChatGPT APIは、プロンプト(messages)を工夫することで、OpenAI WebUIにはできない様々なタスクに対応できることがわかってきているが、成果物がコマンドラインだとどうしても使いづらいし、スマホなどからはアクセスできないので、Flaskを使ってWebUIを実装できないかと考えた。

筆者はフロントエンド開発に疎く、チャットインターフェイスのようなものはすぐには作れない。そこでGPT-4に依頼してみることにしたら(課題は多いが)30分くらいで組み上がってしまったので紹介する。

# TL;DR

* GPT-4を使えばチャットWebUIくらいはすぐ作れるよ
* ただし適切なプロンプトを書く必要があるよ
* ソースコードはここで見られるよ https://github.com/canada/chatgpt-webui

# 準備

30分と書いたが、以下までは準備してあった。

* APIキーの取得
* LangChainのインストールと動作確認

LangChainを利用するとChatGPT APIに非常に簡単にアクセスできる。まず.envを用意する。

```bash
OPENAI_API_KEY='sk-***************'
```

あとは以下のコードで動作する。

```python
from dotenv import load_dotenv
from langchain.schema import HumanMessage
from langchain.chat_models import ChatOpenAI
!
load_dotenv()  # take environment variables from .env.

llm = ChatOpenAI(model_name="gpt-3.5-turbo")
print(llm([HumanMessage(content="コンピューターサイエンティストとして、GPTChatについて概要を説明して")]).content)
```

出力:

> GPTChatは、自然言語処理技術を利用したチャットボットシステムです。GPTChatは、OpenAI社が開発したGPT（Generative Pre-trained Transformer）と呼ばれる深層学習モデルを利用しています。このモデルは、大量のテキストデータを学習し、与えられた入力に対して適切な回答を生成することが可能です。
> (以下略)

# スクリプトの自動生成

まずGPT-4に対し、Azure WebAppで動作するFlaskの雛形を用意するよう指示する。その際、仕様もざっくりと伝えておく。

## Flaskフレームワークの雛形を生成

> FlaskでAzure WebAppやAWSにGitFlowでデプロイ可能なWebAPIアプリケーションの雛形を作って。なお、「/」トップ階層は静的なindex.htmlを見に行くようにして、/chatはChatGPTの出力をJSONで返すようにしなさい。
>
> /chatは値をPOSTのJSONとして受け取るようにしなさい。

そうすると、準備するファイル階層や、`app.py`、`requirement.py`、`template/index.html`、`.gitignore`などが出力されるのでコピペしてその通りに配置する。

以下がAIが出力した`app.py`になる。

```python
from flask import Flask, request, jsonify
from openai import ChatCompletion

app = Flask(__name__, static_url_path='', static_folder='static')

@app.route('/')
def root():
    return app.send_static_file('index.html')

@app.route('/chat', methods=['POST'])
def chat():
    data = request.get_json()
    message = data['message']
    # Note: you'll need to replace 'model_name' and 'api_key' with your actual model name and API key
    response = ChatCompletion.create(
      model="model_name", 
      messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": message}
        ]
    )
    return jsonify(response['choices'][0]['message']['content'])

if __name__ == '__main__':
    app.run(debug=True)
```

頼んでもないのに`ChatCompletion.create()`を呼んでくれるように作ってくれた。このままでも動きそうだが、今回は以下で置き換えた。

```python
    load_dotenv()  # take environment variables from .env.
    llm = ChatOpenAI(model_name="gpt-3.5-turbo")
    response = llm([HumanMessage(content=message)]).content
    return jsonify({"answer": response})
```

置換が完了したら、

```bash
$ python app.py
```

で動作確認できる。動作確認にはPostmanを使用した。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64722/10be92d7-7f80-895e-2576-8c7bc664bd23.png)

APIは一発で動作した。

# HTML文の生成

あとはindex.htmlの生成。追加でGPT-4に以下のように指示する。

> それでは、このテンプレートを使って、チャットシステムを作成するためのHTMLファイルを作りなさい。
Bootstrap5を使用して視認性が良いものを作ってください。
HTMLは日本語にしなさい。
入力エリアはテキストボックスで、Ctrl+Enterで送信できるようにしなさい。会話は吹き出しのように角丸のテキストボックスに色分けをして表示してください。AIの回答は左側、人間の回答は右側に出力するようにしてください。モックでよいので、チャットの吹き出しのAIの吹き出しの左側と人間の吹き出しの右側に、それぞれアイコンを付けてください。
送信の操作をすると「/chat」に問い合わせます。JSONが以下のように返却されるのでAIの吹き出しに表示されるようにしてください。
{
    "answer": "こんにちはAIです"
}

アイコンの配置がおかしいのと、400エラーが出るので修正を指示する。

> POSTに渡しているヘッダがapplication/x-www-form-urlencoded; charset=UTF-8なので動作しません。JSONを渡してください。
人間の角丸吹き出しの外側かつ右側にアイコンを配置してください。
AIの角丸吹き出しの左側に同様にアイコンを配置してください。

この吹き出しの指示が良くなく、初回はめちゃくちゃ微妙なものができてしまった。

![初回生成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64722/a2ba4bc2-ef13-a3be-1bb5-e593e47e7d03.png)

追加でオーダーする。

> アイコンは、モックを作るサービスを使って生成したものを使用してください。
角丸は半径を固定し、文章がきちんと収まるようにしてください。
人間のアイコンは削除してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64722/8597c9d7-0cc9-e251-e406-72c4e8edd7e3.png)

うーん、まあ良いだろう、ということで最終成果物は以下。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>AIチャット</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        .message-box {
            max-width: 70%;
            position: relative;
            padding: 20px;
            margin-top: 20px;
            border-radius: 25px;
        }
        .my-message {
            margin-left: 30%;
            text-align: right;
            background-color: #007bff;
            color: white;
        }
        .ai-message {
            margin-right: 30%;
            text-align: left;
            background-color: #6c757d;
            color: white;
        }
        .chat-icon {
            width: 30px;
            height: 30px;
            position: absolute;
        }
        .chat-text {
            padding-left: 40px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1 class="mt-5">AIチャット</h1>
        <div id="chatBox" class="mt-3">
            <!-- メッセージはここに追加されます -->
        </div>
        <div class="input-group mt-3">
            <textarea id="messageInput" class="form-control" placeholder="メッセージを入力してください(Ctrl+Enterで送信)"></textarea>
            <button id="sendButton" class="btn btn-primary">送信</button>
        </div>
    </div>

    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script>
        $(document).ready(function () {
            $('#messageInput').keydown(function (e) {
                if (e.ctrlKey && e.keyCode == 13) {
                    $('#sendButton').click();
                }
            });

            $('#sendButton').click(function () {
                let message = $('#messageInput').val().trim();
                if (message) {
                    // Add the user's message to the chat box
                    $('#chatBox').append(`
                        <div class="message-box my-message">
                            <div class="chat-text">${message}</div>
                        </div>
                    `);

                    // Send the message to the server
                    $.ajax({
                        url: '/chat',
                        type: 'POST',
                        contentType: 'application/json',
                        data: JSON.stringify({ message: message }),
                        dataType: 'json',
                        success: function (data) {
                            // Add the AI's message to the chat box
                            $('#chatBox').append(`
                                <div class="message-box ai-message">
                                    <img src="https://via.placeholder.com/30" class="chat-icon float-start">
                                    <div class="chat-text">${data.answer}</div>
                                </div>
                            `);
                        }
                    });
                }

                // Clear the input box
                $('#messageInput').val('');
            });
        });
    </script>
</body>
</html>
```

# 最後に

HTML/Javascriptを出力させるという試みはChatGPT(GPT-3.5)でも行ったことがあるのだが、表示崩れやケアレスミスで動作しないということが多く、かなり手入れが必要だった。

今回はGPT-4を使うことによって手で修正することがなく、かなり精度の高いスクリプトおよびHTML文の生成を行うことができた。