2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Flutter + NCMB + ChatGPTでAIチャットアプリを作る(その3: チャット画面の作成)

Last updated at Posted at 2023-05-10

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));

これで完成です。

localhost_63838_ (4).png

全体のコード

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には他にもファイルストアや認証、プッシュ通知などの機能があります。ぜひそれらの機能も利用してアプリを開発してください。

mBaaSでサーバー開発不要! | ニフクラ mobile backend

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?