NCMBでは公式SDKとしてSwift/Objective-C/Kotlin/Java/Unity/JavaScript SDKを用意しています。また、それ以外にもコミュニティSDKとして、非公式ながらFlutter/React Native/Google Apps Script/C#/Ruby/Python/PHPなど幅広い言語向けにSDKが開発されています。
今回は公式SDKの一つ、JavaScript SDKとFramework7を使ってChatGPTを用いたチャットアプリを作ってみます。前回はスクリプトでの処理について解説しましたので、今回はチャット画面を作成します。
コード
今回のコードは NCMBMania/monaca-chatgpt にアップロードしてあります。実装時の参考にしてください。
チャット画面について
このファイルは www/pages/chat.html
として作成しています。チャット画面はFramework7のメッセージコンポーネントを用いるので、HTMLはとても簡単です。
<template>
<div class="page" data-name="chat"> <!-- ページ全体のコンテナ -->
<div class="navbar"> <!-- ナビゲーションバー -->
<div class="navbar-bg"></div>
<div class="navbar-inner sliding">
<div class="title">AIチャット</div> <!-- タイトル -->
</div>
</div>
<div class="toolbar messagebar"> <!-- メッセージツールバー -->
<div class="toolbar-inner">
<div class="messagebar-area">
<textarea class="resizable" placeholder="Message"></textarea> <!-- テキストエリア -->
</div>
<a class="link icon-only demo-send-message-link" @click=${send}> <!-- メッセージ送信ボタン -->
<i class="icon f7-icons if-not-md">arrow_up_circle_fill</i>
<i class="icon material-icons md-only">send</i>
</a>
</div>
<div class="messagebar-sheet">
</div>
</div>
<div class="page-content messages-content"> <!-- メッセージ表示エリア -->
<div class="messages"> <!-- メッセージ表示用の要素 -->
</div>
</div>
</div>
</template>
JavaScriptの実装
JavaScriptは同じ chat.html
の中に記述します。まず、スクリプトタグと基本となる関数のエクスポートを定義します。
<script>
export default (props, { $f7, $onMounted, $store, $update, $tick }) => {
// この中に処理を記述する
return $render;
}
</script>
$f7
や $onMounted
はFramework7の用意するユーティリティ変数です。何ができるかはFramework7入門 ルーティング・DOM操作・ストアを学ぼう – モナカプレスを参考にしてください。
必要な変数の定義
まずNCMBのデータストアのクラスやチャットメッセージ用の変数を定義します。
const Chat = ncmb.DataStore("Chat"); // データストア"Chat"の生成
let messages; // メッセージを表示するための変数
let messageBar; // メッセージを入力するための変数
const chats = []; // チャット履歴を保持するための配列
画面がマウントされた際の処理
画面がHTMLとしてマウントされた際の処理でチャット画面の初期化や、過去のメッセージを取得します。
$onMounted(async () => { // コンポーネントがマウントされたときに実行される関数
messages = $f7.messages.create({ // messagesの初期化
el: $('.messages'), // 表示する要素
});
messageBar = $f7.messagebar.create({ // messageBarの初期化
el: $('.messagebar'), // 入力する要素
attachments: [] // 添付ファイルの初期値
});
const date = new Date;
date.setDate(date.getDate() - 1);
const ary = await Chat
.greaterThanOrEqualTo('createDate', date) // createDateが指定日以降のデータを取得
.fetchAll(); // 取得した全てのデータを配列で取得
ary.forEach(chat => addMessage(chat)); // 取得したデータをメッセージ表示エリアに追加
$update(); // レンダリングの更新
});
addMessage
関数はチャットデータを配列に登録し、チャット画面用のデータを作成します。
const addMessage = (chat) => { // メッセージ表示エリアにメッセージを追加する関数
messages.addMessage({ // メッセージを追加
text: chat.content, // メッセージの内容
type: chat.role === 'user' ? 'sent' : 'received', // メッセージの送信元
name: chat.role === 'user' ? 'Me': 'Chat GPT', // メッセージの送信先
avatar: chat.role === 'user' ? '/assets/icons/person.png' : '/assets/icons/robot.png', // アバター画像
});
chats.push(chat); // チャット履歴に追加
};
チャットを送信・保存する処理
チャットメッセージを書き込んで送信ボタンを押すと send
関数が呼び出されます。
const send = async () => { // メッセージ送信のイベントハンドラ
// 以下はこの中に記述
};
テキストを取得
まず入力されたテキストを取得し、そのデータをNCMBに保存します。
const content = messageBar.getValue().replace(/\n/g, '<br />').trim(); // 入力されたメッセージを取得
if (content === '') return; // メッセージが空の場合は送信しない
const chat = await saveChat('user', content); // チャットを保存
saveChat
関数は以下の通りです。これだけでクラウドのデータベースにチャットメッセージが保存されます。
const saveChat = (role, content) => { // チャットを保存する関数
const chat = new Chat(); // 新しいチャットを生成
return chat
.set('content', content) // チャットの内容を設定
.set('role', role) // チャットの送信元を設定
.save(); // チャットを保存
};
保存されたチャットは先ほどの addMessage
関数を使って配列に追加します。追加したら表示を更新し、さらにその画面更新が完了するのを $tick
で待ちます。
addMessage(chat); // メッセージ表示エリアにメッセージを追加
$update(); // レンダリングの更新
await $tick(); // レンダリングの完了を待つ
これまでのメッセージ履歴を作成
ここからOpenAI API部分の処理になります。まずデータストアから取得したメッセージ履歴を使って、過去のやり取りを作成します。
// ここからAIチャット
const pastMessages = chats.map(chat => { // チャット履歴をAIに送信するための配列を作成
return {
role: chat.role,
content: chat.content,
};
});
OpenAI APIの呼び出し
OpenAI APIは時間がかかるので、プログレスとしてチャット入力中の表示を出します。
messages.showTyping(); // タイピング中の表示
そしてNCMBのスクリプトを実行します。これでOpenAI APIを呼び出して、結果を受け取ります。
const res = await ncmb.Script // テキスト生成スクリプトを実行
.data({
content,
messages: pastMessages,
})
.exec("POST", "text.js");
結果をデータストアに保存
受け取った結果はボット(assistant)のメッセージとしてデータストアに保存します。
const { text } = JSON.parse(res.body); // スクリプトの結果を取得
const aiChat = await saveChat('assistant', text); // AIの返答を保存
addMessage(aiChat); // メッセージ表示エリアにAIの返答を追加
後は表示の更新と、入力中表示を消して完了です。
messages.hideTyping(); // タイピング中の表示を非表示にする
messageBar.clear(); // 入力欄をクリア
$update(); // レンダリングの更新
これで完成です。
全体のコード
chat.html
のJavaScript部分のコードは以下の通りです。
export default (props, { $f7, $onMounted, $store, $update, $tick }) => {
const Chat = ncmb.DataStore("Chat"); // データストア"Chat"の生成
let messages; // メッセージを表示するための変数
let messageBar; // メッセージを入力するための変数
const chats = []; // チャット履歴を保持するための配列
$onMounted(async () => { // コンポーネントがマウントされたときに実行される関数
messages = $f7.messages.create({ // messagesの初期化
el: $('.messages'), // 表示する要素
});
messageBar = $f7.messagebar.create({ // messageBarの初期化
el: $('.messagebar'), // 入力する要素
attachments: [] // 添付ファイルの初期値
});
const date = new Date;
date.setDate(date.getDate() - 1);
const ary = await Chat
.greaterThanOrEqualTo('createDate', date) // createDateが指定日以降のデータを取得
.fetchAll(); // 取得した全てのデータを配列で取得
ary.forEach(chat => addMessage(chat)); // 取得したデータをメッセージ表示エリアに追加
$update(); // レンダリングの更新
});
const addMessage = (chat) => { // メッセージ表示エリアにメッセージを追加する関数
messages.addMessage({ // メッセージを追加
text: chat.content, // メッセージの内容
type: chat.role === 'user' ? 'sent' : 'received', // メッセージの送信元
name: chat.role === 'user' ? 'Me': 'Chat GPT', // メッセージの送信先
avatar: chat.role === 'user' ? '/assets/icons/person.png' : '/assets/icons/robot.png', // アバター画像
});
chats.push(chat); // チャット履歴に追加
};
const saveChat = (role, content) => { // チャットを保存する関数
const chat = new Chat(); // 新しいチャットを生成
return chat
.set('content', content) // チャットの内容を設定
.set('role', role) // チャットの送信元を設定
.save(); // チャットを保存
};
const send = async () => { // メッセージ送信のイベントハンドラ
const content = messageBar.getValue().replace(/\n/g, '<br />').trim(); // 入力されたメッセージを取得
if (content === '') return; // メッセージが空の場合は送信しない
const chat = await saveChat('user', content); // チャットを保存
addMessage(chat); // メッセージ表示エリアにメッセージを追加
$update(); // レンダリングの更新
await $tick(); // レンダリングの完了を待つ
// ここからAIチャット
const pastMessages = chats.map(chat => { // チャット履歴をAIに送信するための配列を作成
return {
role: chat.role,
content: chat.content,
};
});
messages.showTyping(); // タイピング中の表示
const res = await ncmb.Script // テキスト生成スクリプトを実行
.data({
content,
messages: pastMessages,
})
.exec("POST", "text.js");
const { text } = JSON.parse(res.body); // スクリプトの結果を取得
const aiChat = await saveChat('assistant', text); // AIの返答を保存
addMessage(aiChat); // メッセージ表示エリアにAIの返答を追加
messages.hideTyping(); // タイピング中の表示を非表示にする
messageBar.clear(); // 入力欄をクリア
$update(); // レンダリングの更新
};
return $render;
}
まとめ
今回のAIチャットアプリではNCMBの以下の機能を利用しました。
- データストア
- データ保存
- データ検索
- スクリプト
- OpenAI APIの実行
NCMBには他にもファイルストアや認証、プッシュ通知などの機能があります。ぜひそれらの機能も利用してアプリを開発してください。