NCMBでは公式SDKとしてSwift/Objective-C/Kotlin/Java/Unity/JavaScript SDKを用意しています。また、それ以外にもコミュニティSDKとして、非公式ながらFlutter/React Native/Google Apps Script/C#/Ruby/Python/PHPなど幅広い言語向けにSDKが開発されています。
今回はコミュニティSDKの一つ、Flutter SDKを使ってChatGPTを用いたチャットアプリを作ってみます。前回はスクリプトでの処理について解説しましたので、今回はチャット画面を作成します。
コード
今回のコードは NCMBMania/flutter-gpt-chat にアップロードしてあります。実装時の参考にしてください。
チャット画面について
このファイルは lib/chat.dart
として作成しています。チャット画面はflutter_chat_ui | Flutter Packageを使うことで、簡単に実現できます。
// パッケージのインポート
import 'package:flutter/material.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:ncmb/ncmb.dart';
// チャットページのウィジェットを定義
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
// StatefulWidgetを継承すると、Stateを作成するメソッドが必要になります
@override
State<ChatPage> createState() => _ChatPageState();
}
// _ChatPageStateクラスを定義
class _ChatPageState extends State<ChatPage> {
// メインのUI構造を作成
@override
Widget build(BuildContext context) => Scaffold(
body: Chat(
messages: _messages,
onSendPressed: _handleSendPressed,
showUserAvatars: false,
showUserNames: false,
user: const types.User(id: 'user'),
),
);
// 省略
}
Flutterの実装
必要な変数の定義
まずチャットメッセージ用の変数を定義します。
// メッセージのリストを作成
List<types.TextMessage> _messages = [];
画面が初期化された際の処理
画面が初期化された際の処理で、過去のメッセージを取得します。
// 初期化時にメッセージをロード
@override
void initState() {
super.initState();
_loadMessages();
}
_loadMessages
関数で過去1日分からのデータを取得します。あまりメッセージが多いとOpenAIのAPI消費量が増えてしまうので注意してください。
// 過去のメッセージをロードする関数
void _loadMessages() async {
// メッセージを取得するクエリを作成
var query = NCMBQuery('Chat');
// 1日前以降のメッセージを取得
var date = DateTime.now().subtract(const Duration(days: 1));
query.greaterThanOrEqualTo('createDate', date);
query.order('createDate', descending: true); // 古いデータが上に来るようにする
var ary = (await query.fetchAll()).map((o) => o as NCMBObject).toList();
// 取得したNCMBObjectのリストをTextMessageに変換
final messages = ary.map((e) => _toMessage(e)).toList();
// メッセージを更新
setState(() {
_messages = messages;
});
}
_toMessage
関数はNCMBObjectをflutter_chat_uiのテキストメッセージに変換する関数です。
// NCMBObjectをTextMessageに変換する関数
types.TextMessage _toMessage(NCMBObject obj) {
return types.TextMessage(
author: types.User(id: obj.getString('role', defaultValue: 'user')),
createdAt: obj.getDateTime('createDate').microsecondsSinceEpoch,
id: obj.objectId!,
text: obj.getString('content'),
);
}
チャットを送信・保存する処理
チャットメッセージを書き込んで送信ボタンを押すと _handleSendPressed
関数が呼び出されます。
void _handleSendPressed(types.PartialText message) async {
// 以下はこの中に記述
};
テキストを取得
まず入力されたテキストをNCMBに保存します。保存したら、それをチャット画面に反映します。
// メッセージを保存
var obj = NCMBObject('Chat');
obj.set('content', message.text);
obj.set('role', 'user');
await obj.save();
// メッセージをチャット画面に追加
_addMessage(_toMessage(obj));
_addMessage
関数はチャット画面にテキストメッセージを追加する関数です。
// メッセージを追加する関数
void _addMessage(types.TextMessage message) {
setState(() {
_messages.insert(0, message);
});
}
これまでのメッセージ履歴を作成
ここからOpenAI API部分の処理になります。まずデータストアから取得したメッセージ履歴を使って、過去のやり取りを作成します。
// 過去のメッセージ内容を作成
final messages = _messages
.map((message) => {
'role': message.author.id,
'content': message.text,
})
.toList();
OpenAI APIの呼び出し
そしてNCMBのスクリプトを実行します。これでOpenAI APIを呼び出して、結果を受け取ります。
// OpenAIのAPIを呼び出す
var script = NCMBScript('text.js')
.body('content', message.text)
.body('messages', messages);
var res = (await script.post()) as Map<String, dynamic>;
結果をデータストアに保存
受け取った結果はボット(assistant)のメッセージとしてデータストアに保存します。
// OpenAIの返答を保存
var answer = NCMBObject('Chat');
answer.set('content', res['text'] as String);
answer.set('role', 'assistant');
await answer.save();
// OpenAIの返答をチャット画面に追加
_addMessage(_toMessage(answer));
これで完成です。
全体のコード
chat.dart
のコードは以下の通りです。
// パッケージのインポート
import 'package:flutter/material.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:ncmb/ncmb.dart';
// チャットページのウィジェットを定義
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
// StatefulWidgetを継承すると、Stateを作成するメソッドが必要になります
@override
State<ChatPage> createState() => _ChatPageState();
}
// _ChatPageStateクラスを定義
class _ChatPageState extends State<ChatPage> {
// メッセージのリストを作成
List<types.TextMessage> _messages = [];
// 初期化時にメッセージをロード
@override
void initState() {
super.initState();
_loadMessages();
}
// メインのUI構造を作成
@override
Widget build(BuildContext context) => Scaffold(
body: Chat(
messages: _messages,
onSendPressed: _handleSendPressed,
showUserAvatars: false,
showUserNames: false,
user: const types.User(id: 'user'),
),
);
// メッセージを追加する関数
void _addMessage(types.TextMessage message) {
setState(() {
_messages.insert(0, message);
});
}
// NCMBObjectをTextMessageに変換する関数
types.TextMessage _toMessage(NCMBObject obj) {
return types.TextMessage(
author: types.User(id: obj.getString('role', defaultValue: 'user')),
createdAt: obj.getDateTime('createDate').microsecondsSinceEpoch,
id: obj.objectId!,
text: obj.getString('content'),
);
}
// メッセージ送信時に呼ばれる関数
void _handleSendPressed(types.PartialText message) async {
// メッセージを保存
var obj = NCMBObject('Chat');
obj.set('content', message.text);
obj.set('role', 'user');
await obj.save();
// メッセージをチャット画面に追加
_addMessage(_toMessage(obj));
// 過去のメッセージ内容を作成
final messages = _messages
.map((message) => {
'role': message.author.id,
'content': message.text,
})
.toList();
// OpenAIのAPIを呼び出す
var script = NCMBScript('text.js')
.body('content', message.text)
.body('messages', messages);
var res = (await script.post()) as Map<String, dynamic>;
// OpenAIの返答を保存
var answer = NCMBObject('Chat');
answer.set('content', res['text'] as String);
answer.set('role', 'assistant');
await answer.save();
// OpenAIの返答をチャット画面に追加
_addMessage(_toMessage(answer));
}
// 過去のメッセージをロードする関数
void _loadMessages() async {
// メッセージを取得するクエリを作成
var query = NCMBQuery('Chat');
// 1日前以降のメッセージを取得
var date = DateTime.now().subtract(const Duration(days: 1));
query.greaterThanOrEqualTo('createDate', date);
query.order('createDate', descending: true); // 古いデータが上に来るようにする
var ary = (await query.fetchAll()).map((o) => o as NCMBObject).toList();
// 取得したNCMBObjectのリストをTextMessageに変換
final messages = ary.map((e) => _toMessage(e)).toList();
// メッセージを更新
setState(() {
_messages = messages;
});
}
}
まとめ
今回のAIチャットアプリではNCMBの以下の機能を利用しました。
- データストア
- データ保存
- データ検索
- スクリプト
- OpenAI APIの実行
NCMBには他にもファイルストアや認証、プッシュ通知などの機能があります。ぜひそれらの機能も利用してアプリを開発してください。