見出し画像

レンタルサーバでWebアプリ作り: チャット画面作り

ロリポップレンタルサーバではできることが限られていたので、ホスティングサービスを使ってWebアプリを公開するまでの道のりを記録します。

データ分析を仕事としているのでパソコンやITのことは多少は詳しいですが、インフラまわりは全くの素人です。そんな人が見て参考になる情報をまとめていきたいと思います。

背景

前回までに、GPTの回答をテケテケ表示(ストリーミング)させることができました。今回は画面作りを進めようと思います。チャット画面を作っていきます。


ゴール=チャット風画面を作る

ベース画面作り

GPT先生に全力で協力してもらいます。

app.pyのflaskからtemplates/index.htmlを呼び出して画面を作る。
以下の機能を持ったindex.HTMLのコードを書いてください。
・チャット風の画面
・一番上に「チャット」ボタンがある。
・ボタンが1つあり、1回押すと「私:こんにちわ」という吹き出しを出す
・もう一回ボタンを押すと、私のチャットの前に「あなた:こんにちわ。元気ですか?」という吹き出しを出す。最新の会話が先頭に来るようにする。
・もう1回ボタンを押すと、「私:元気ですよ」という吹き出しを出す。
・私のチャット背景は薄緑色、あなたのチャット背景は薄黄色。
・1つのファイル(index.html)で作成する
・チャットの表示枠はなるべく大きく、横は700pxで。

GPT先生へのお願い1

チャットが下に出るようなので、もう一回お願い。

ボタンを押すと、前のチャットの下に表示されている。上に表示されるように修正してください。

GPT先生へのお願い2

縦幅が制限されていたので、再度お願い。

チャットの表示枠の縦幅は、なるべく大きくしてください。できれば制限をなくしてください。

GPT先生へのお願い3

で、以下のような画面。なかなかGood♪

GPT先生が作ってくれたチャット画面

ちなみに、コードは以下の通りです。100%GPT産ですw

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Chat Application</title>
    <style>
        .chat-container {
            width: 700px;
            margin: 0 auto;
            padding: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
            overflow-y: auto;
            display: flex;
            flex-direction: column; /* 最新のチャットが上に来るように */
            /* height を削除して、自動的にサイズが調整されるようにする */
        }
        .chat-bubble {
            max-width: 60%;
            margin: 10px;
            padding: 10px;
            border-radius: 10px;
        }
        .mine {
            background-color: lightgreen;
            align-self: flex-end;
        }
        .yours {
            background-color: lightyellow;
            align-self: flex-start;
        }
    </style>
</head>
<body>
    <button onclick="addChat()">チャット</button>
    <div class="chat-container" id="chat-container">
        <!-- チャットがここに追加される -->
    </div>

    <script>
        var chatCount = 0;

        function addChat() {
            var container = document.getElementById('chat-container');
            var newChat;

            if (chatCount == 0) {
                newChat = '<div class="chat-bubble mine">私: こんにちは</div>';
            } else if (chatCount == 1) {
                newChat = '<div class="chat-bubble yours">あなた: こんにちは。元気ですか?</div>' +
                          container.innerHTML; // 新しいチャットを前に追加
            } else {
                newChat = '<div class="chat-bubble mine">私: 元気ですよ</div>' +
                          container.innerHTML; // 新しいチャットを前に追加
            }

            container.innerHTML = newChat;
            chatCount++;
        }
    </script>
</body>
</html>


機能追加

なかなか良い感じに仕上がったので、追加でお願い。

とても良い感じです。これに、以下の機能を追加してください。
・ボタンの上にインプットボックス(id=txt)を作る。
・ボタンを押したら、そのテキスト文をチャットに表示させる。
・直前が私であればあなた、直前があなたであれば私のチャットにする。一番初めは私からスタートする。

GPT先生に追加のお願い1(褒めるのも忘れずに♪)


とってもいい感じ♪ 追加で以下もお願い。

ボタンを押してチャットを表示させたら、インプットボックスにフォーカスを持っていくようにしてください。

GPT先生に追加のお願い2

これまた一発合格! まだGPT先生が100%生成。画面作りはめっぽう強い。

Socket機能追加

ではいよいよ、ソケット通信させるようにします。前回のLangchainで作った形を流用し、SOCKET仕様を指定しています。

addChatを以下のような仕様に変更してください。

仕様: """
・socket通信を行うので、以下のSOCKET仕様のように構築
・addChatで、インプットボックスのテキスト内容を私のチャットとして挿入
・続いて、あなたのチャットを空白で表示。idは`yours_*`とする。*は、addChatが呼ばれた回数が入る。初めて呼ばれたらyours_1となる。
・socket.emitでインプットボックスのテキストをPythonに送信。
・Pythonから回答がSocket通信で帰ってくるので、それをsocket.onで受信。受け取ったらyours_*に表示
"""

SOCKET仕様: """
document.addEventListener('DOMContentLoaded', function() {
var socketUrl;
var options = {
transports: ['websocket'] // WebSocketのみを使用
};

if (location.protocol === 'https:') {
socketUrl = 'https://' + document.domain; // HTTPSの設定
} else {
socketUrl = 'http://' + document.domain + ':5000'; // ローカル環境の設定(HTTP、ポート5000)
}
var socket = io.connect(socketUrl, options);

socket.on('count_response', function(msg) {
});

// addChatの処理
});
"""

GPT先生にSOCKETのお願い

回答はほぼ完ぺき!通信時のIDなどがちょっとずれていたりしたので、そこを微修正して、以下のようなHTML文になりました。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Chat Application</title>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
    <style>
        .chat-container {
            width: 700px;
            margin: 0 auto;
            padding: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
            overflow-y: auto;
            display: flex;
            flex-direction: column; /* 最新のチャットが上に来るように */
        }
        .chat-bubble {
            max-width: 60%;
            margin: 10px;
            padding: 10px;
            border-radius: 10px;
        }
        .mine {
            background-color: lightgreen;
            align-self: flex-end;
        }
        .yours {
            background-color: lightyellow;
            align-self: flex-start;
        }
    </style>
</head>
<body>
    <input type="text" id="txt" placeholder="メッセージを入力">
    <button onclick="addChat()">チャット</button>
    <div class="chat-container" id="chat-container">
        <!-- チャットがここに追加される -->
    </div>


    <script>
        document.addEventListener('DOMContentLoaded', function() {
            var chatCount = 0;
            var socketUrl;
            var options = {
                transports: ['websocket']
            };

            if (location.protocol === 'https:') {
                socketUrl = 'https://' + document.domain;
            } else {
                socketUrl = 'http://' + document.domain + ':5000';
            }
            var socket = io.connect(socketUrl, options);

            socket.on('count_response', function(msg) {
                var responseElement = document.getElementById('yours_' + chatCount);
                responseElement.textContent = 'あなた: ' + msg.res_txt; // サーバーからのメッセージを表示
            });

            window.addChat = function() {
                var input = document.getElementById('txt');
                var text = input.value.trim();
                if (text === '') {
                    return; // テキストが空の場合は何もしない
                }

                chatCount++;

                var container = document.getElementById('chat-container');
                var myChat = '<div class="chat-bubble mine">私: ' + text + '</div>';
                var yourChat = '<div class="chat-bubble yours" id="yours_' + chatCount + '"></div>'; // あなたのチャットの空の吹き出し

                container.innerHTML = yourChat + myChat + container.innerHTML;

                socket.emit('on_start_gptquestion', { q: text }); // サーバーにメッセージを送信
                input.value = ''; // 入力ボックスをクリア
                input.focus(); // フォーカスを入力ボックスに戻す
            }
        });
    </script>

</body>
</html>


app.pyの修正

app.pyは本来修正不要のはずですが、前回のものバグがありました。
load_conversation を毎回読み込んでいたため過去の会話履歴が毎回消えるような仕様になっていたので、インスタンスを最初に作り、load_conversationも最初に1回だけ処理するように変更しました。

具体的には以下の通りです。

import os
import uuid

from flask import Flask, render_template
from flask_socketio import SocketIO
import threading
from collections import deque

from openai import OpenAI
import myopenai

from dotenv import load_dotenv
load_dotenv() # 環境変数を読み込む


#--- 各種インスタンスや設定 --------------------
client = OpenAI()
mo = myopenai.myopenai('gpt-3.5-turbo')
mo.set_systemprompt('あなたはバリバリの関西人で、会話は常にノリノリな人です。')
conv = mo.load_conversation(streaming=True)  #★最初に読み込ませる(都度読み込まない)

#Flaskの設定
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY') #環境変数から設定
socketio = SocketIO(app)


#--- GPTやスレッド関数 --------------------------
def thread_gptquestion(text:str):
    res = conv.predict(input=text)
    mo.set_queue('[[end]]') 


def start_gptquestion(text):
    #threadingでGPTを動かす
    thread = threading.Thread(target=thread_gptquestion, args=(text,))
    thread.start()

    #token_queueにGPTの回答が入ってくるのを待つ(handle_tokenで小分けにされて入ってくる)
    msg = ''
    while True :
        token_queue = mo.get_queue()
        if token_queue :
            token = token_queue.popleft()
            token = token.replace('\n', '<BR>')
            if token == '[[end]]' :
                mo.reset_queue() #★これも追加してます
                print('-------------end----------------')
                break
            msg += token
            socketio.emit('count_response', {'res_txt': msg})
        else :
            socketio.sleep(0.1) #キューに何もなくなったところで0.1秒待つ


#--- Flaskのルーティング --------------------------
@app.route('/')
def index():
    return render_template('index_chattest.html')

@socketio.on('on_start_gptquestion')
def on_start_gptquestion(message):
    q = str(message['q'])
    socketio.start_background_task(start_gptquestion, q)

if __name__ == '__main__':
    socketio.run(app)


実演

以下が、DigitalOceanに上げて試してみた結果です。いい感じでチャットできてます!

実演結果 @DigitalOcean

それにしても、GPT3.5の嘘つきっぷりはやっぱりすごい。。。


最後まで見ていただきありがとうございました!

今回は99%GPT先生に作ってもらいました♪
こんな感じで、GPT先生に聞くときは簡単なお願いからちょっとずつ付け足す感じでお願いすると、いい塩梅で難しいソースコードも作ってくれます。

ご参考になれば幸いです!



DigitalOceanのアカウント登録
(200ドル分チケット付き)

以下は紹介リンクですが、ここから手続きを進めてもらえると200ドル分の無料チケット(有効期間2ヶ月)がもらえるようですので、ぜひご活用ください。
※ 2023/12/29時点

紹介リンク : https://m.do.co/c/a8b31ed34b75

サポート問い合わせ先

DigitalOceanのサポート問い合わせリンクがなかなか見つからないので、リンクを載せておきます。

https://cloudsupport.digitalocean.com/s/

場所は、トップページの右下にある「Ask a question」に行き、そのページの一番下(欄外っぽいところ)にひっそりと「Support」というリンクがあります(Contact内)。そのページの一番最後に「Contact Support」ボタンがあります。

この記事が気に入ったらサポートをしてみませんか?