NCMBでは公式SDKとしてSwift/Objective-C/Kotlin/Java/Unity/JavaScript SDKを用意しています。また、それ以外にもコミュニティSDKとして、非公式ながらFlutter/React Native/Google Apps Script/C#/Ruby/Python/PHPなど幅広い言語向けにSDKが開発されています。
今回はコミュニティSDKの一つ、Flutter/Dart SDKを使ってOpenAIの画像生成APIを用いたAI画像生成アプリを作ってみます。前回は画像生成に関わるスクリプトの処理について解説しましたので、今回はチャット画面を作成します。
コード
今回のコードは NCMBMania/flutter-image-gen にアップロードしてあります。実装時の参考にしてください。
チャット画面について
このファイルは lib/chat.dart
として作成しています。チャット画面はflutter_chat_ui | Flutter Packageを用いるので、とても簡単です。
import 'dart:core';
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';
import 'dart:typed_data';
import 'dart:html' as webFile;
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
@override
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
@override
Widget build(BuildContext context) => Scaffold(
body: Chat(
messages: _messages,
onSendPressed: _handleSendPressed,
showUserAvatars: false,
showUserNames: false,
user: const types.User(
id: 'user',
),
),
);
// 省略
}
Dartの実装
以下のコードはすべて lib/chat.dart
の _ChatPageState
中に記述します。
必要な変数の定義
まずチャットメッセージ用の変数を定義します。
List<types.Message> _messages = [];
初期化された際の処理
初期化時の処理で、過去のメッセージを取得します。
@override
void initState() {
super.initState();
_loadMessages();
}
void _loadMessages() async {
// メッセージを取得するクエリを作成
var query = NCMBQuery('ImageGen');
// 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のリストをチャットメッセージに変換
final messages = await Future.wait(ary.map((e) => toMessage(e)));
// メッセージを更新
setState(() {
_messages = messages.expand((l) => l).toList();
});
}
toMessage
関数はNCMBObjectをチャットメッセージに変換します。チャットメッセージはプロンプトとしてのテキストメッセージと、レスポンスとしての画像メッセージの2つに分かれます。
// NCMBObjectをチャットメッセージに変換する関数
Future<List<types.Message>> toMessage(NCMBObject obj) async {
var messages = <types.Message>[];
var message = toTextMessage(obj);
messages.add(message);
var imageMessage = await toImageMessage(obj);
messages.add(imageMessage);
return messages.reversed.toList();
}
// NCMBObjectをTextMessageに変換する関数
types.TextMessage toTextMessage(NCMBObject obj) {
return types.TextMessage(
author: types.User(id: obj.getString('role', defaultValue: 'user')),
createdAt: obj.getDateTime('createDate').microsecondsSinceEpoch,
id: "${obj.objectId}-text",
text: obj.getString('content'),
);
}
// NCMBObjectをImageMessageに変換する関数
Future<types.ImageMessage> toImageMessage(NCMBObject obj) async {
// 画像ファイルをダウンロード
final imageName = obj.getString('image', defaultValue: '');
final file = await NCMBFile.download(imageName);
// 画像ファイルを表示するためのURIを作成
final blob = ByteData.sublistView(file.data);
final path = webFile.Url.createObjectUrlFromBlob(webFile.Blob([blob]));
return types.ImageMessage(
author: types.User(id: obj.getString('role', defaultValue: 'assistant')),
createdAt: obj.getDateTime('createDate').microsecondsSinceEpoch,
id: "${obj.objectId}-image",
name: imageName,
height: 256,
width: 256,
size: file.data.length,
uri: path,
);
}
チャットを送信・保存する処理
チャットメッセージを書き込んで送信ボタンを押すと _handleSendPressed
関数が呼び出されます。
void _handleSendPressed(types.PartialText message) async {
// 以下はこの中に記述
};
入力テキストを取得
まず入力されたテキストを取得し、そのデータをNCMBのデータストアインスタンスにします。そして、保存後にテキストメッセージを描画します。
// メッセージを保存
var obj = NCMBObject('ImageGen');
obj.set('content', message.text);
obj.set('role', 'user');
await obj.save();
final textMessage = toTextMessage(obj);
_addMessage(textMessage);
OpenAI APIの呼び出し
NCMBのスクリプトを実行します。これでOpenAI APIを呼び出して、結果を受け取ります。
// OpenAIのAPIを呼び出す
var script = NCMBScript('image.js').body('prompt', message.text);
var res = (await script.post()) as Map<String, dynamic>;
結果をデータストアに保存
受け取った結果は obj
の image
に紐付けて保存します。
// OpenAIの返答を保存
obj.set('image', res['fileName'] as String);
await obj.save();
後は画像をチャット画面に表示して完了です。
final imageMessage = await toImageMessage(obj);
_addMessage(imageMessage);
これで完成です。
実行例
以下は実行例です。
全体のコード
lib/chat.dart
のコードは以下の通りです。
import 'dart:core';
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';
import 'dart:typed_data';
import 'dart:html' as webFile;
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
@override
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
List<types.Message> _messages = [];
@override
void initState() {
super.initState();
_loadMessages();
}
@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.Message message) {
setState(() {
_messages.insert(0, message);
});
}
void _handleSendPressed(types.PartialText message) async {
// メッセージを保存
var obj = NCMBObject('ImageGen');
obj.set('content', message.text);
obj.set('role', 'user');
await obj.save();
final textMessage = toTextMessage(obj);
_addMessage(textMessage);
// OpenAIのAPIを呼び出す
var script = NCMBScript('image.js').body('prompt', message.text);
var res = (await script.post()) as Map<String, dynamic>;
// OpenAIの返答を保存
obj.set('image', res['fileName'] as String);
await obj.save();
final imageMessage = await toImageMessage(obj);
_addMessage(imageMessage);
}
// NCMBObjectをチャットメッセージに変換する関数
Future<List<types.Message>> toMessage(NCMBObject obj) async {
var messages = <types.Message>[];
var message = toTextMessage(obj);
messages.add(message);
var imageMessage = await toImageMessage(obj);
messages.add(imageMessage);
return messages.reversed.toList();
}
// NCMBObjectをTextMessageに変換する関数
types.TextMessage toTextMessage(NCMBObject obj) {
return types.TextMessage(
author: types.User(id: obj.getString('role', defaultValue: 'user')),
createdAt: obj.getDateTime('createDate').microsecondsSinceEpoch,
id: "${obj.objectId}-text",
text: obj.getString('content'),
);
}
// NCMBObjectをImageMessageに変換する関数
Future<types.ImageMessage> toImageMessage(NCMBObject obj) async {
// 画像ファイルをダウンロード
final imageName = obj.getString('image', defaultValue: '');
final file = await NCMBFile.download(imageName);
// 画像ファイルを表示するためのURIを作成
final blob = ByteData.sublistView(file.data);
final path = webFile.Url.createObjectUrlFromBlob(webFile.Blob([blob]));
return types.ImageMessage(
author: types.User(id: obj.getString('role', defaultValue: 'assistant')),
createdAt: obj.getDateTime('createDate').microsecondsSinceEpoch,
id: "${obj.objectId}-image",
name: imageName,
height: 256,
width: 256,
size: file.data.length,
uri: path,
);
}
void _loadMessages() async {
// メッセージを取得するクエリを作成
var query = NCMBQuery('ImageGen');
// 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 = await Future.wait(ary.map((e) => toMessage(e)));
// メッセージを更新
setState(() {
_messages = messages.expand((l) => l).toList();
});
}
}
まとめ
今回のAI画像生成アプリではNCMBの以下の機能を利用しました。
- データストア
- データ保存
- データ検索
- ファイルストア
- ファイルアップロード
- ファイルダウンロード
- スクリプト
- OpenAI APIの実行
NCMBには他にも認証、プッシュ通知などの機能があります。ぜひそれらの機能も利用してアプリを開発してください。