Flutter Japan User Group (Osaka) のハンズオンも、かれこれ第 11 弾を迎えました。
(ハンズオン開催履歴)
作る手段 | 周辺技術を学ぶ | アプリ or Web | Flutter バージョン | 主催 |
---|---|---|---|---|
辞書アプリを作る | Firebase | アプリ | v1.9.1+hotfix.6-stable | Flutter Japan User Group |
Instagram のようなアプリを作る | StatefulWidget | アプリ | 1.17.2-stable | Flutter Japan User Group |
通話アプリを作る | WebRTC | アプリ | 1.17.2-stable | Flutter Japan User Group |
ポータルサイトを作る | Atomic Design | Web | 1.23.0-18.1.pre-beta | Flutter Japan User Group |
人狼ゲームを作る | Riverpod | Web | 1.26.0-17.6.pre-beta | Flutter Japan User Group |
家計簿アプリを作る | Riverpod (+ API server) | アプリ | 2.2.2-stable | Flutter Japan User Group |
カレンダーアプリを作る | Plugins | アプリ | 3.0.0 (特に指定せず) | Flutter Japan User Group |
チャットアプリを作る | Supabase | アプリ | 3.7.7 (特に指定せず) | Flutter Japan User Group |
今回 Supabase の タイラー さんを招聘して、ハンズオン企画第 11 弾を開催させていただきました。
前回のカレンダーアプリ同様、詳細は Zenn book に書かれていますので、そちらをご確認いただければ。
実際 Flutter アプリで Supabase を使う手順について、チャットのロジック実装と UI 装飾に分けて進めてきました。
タイラーさんの作成したプロジェクトに参加者が乗り込む、という案は私自身考え付きませんでした。
<video controls playsinline width="100%" autoplay loop muted="true" type="video/mp4" src="https://i.imgur.com/lhOwgvI.mp4">
Sorry, your browser doesn't support embedded videos.
</video>
飲み込みの早さには大変驚かれされます。
その延長戦企画のひとつとして Flutter アプリの Supabase チャットに、いま流行りの ChatGPT を組み合わせる記事を書かせていただきました。
この記事では、いくつか準備させていただいたチャレンジ課題を解説していきます。
下記マイルストーンに沿って、チャレンジ課題を進めてみましょう。
- 関数
requestChatGPT()
を作成、ChatGPT の回答を生成する - 関数
requestImageGenerate()
を作成、画像を生成する
なお、ChatGPT の API へアクセスする関数は requestChatGPT()
や requestImageGenerate()
とします。
OpenAI の API 実行環境を整備する
OpenAI を使うために OpenAI Console より OPENAI SECRET を発行、作成します。実際、その OpenAI Console で作成した OPENAI SECRET を OPENAI_API_KEY
に設定します。
String OPENAI_CHAT_COMPLETIONS_API =
'https://api.openai.com/v1/chat/completions';
String OPENAI_API_KEY = '';
ChatGPT の回答を生成する
API の詳細は Chat completions 公式 を確認しながら、その実行を書いてみましょう。
これより解答例になります。
まずは、関数 requestChatGPT()
を作成します。
今回 Flutter アプリで呼び出す HTTP クライアントに dio を選定します。
先日メンテ継続不可との記事が投稿されましたが、すぐ CFUG (http://flutter.cn/) へ移管されています。
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">The dio package for <a href="https://twitter.com/FlutterDev?ref_src=twsrc^tfw">@FlutterDev</a> and <a href="https://twitter.com/dart_lang?ref_src=twsrc^tfw">@dart_lang</a> has been transferred to CFUG (<a href="https://t.co/YmZownRg3p">https://t.co/YmZownRg3p</a>) and we'll soon start to maintain the package again. Sorry for the inconvenience and looking forward to seeing you guys again. We'll also explore funding for the package.</p>— Alex Li (@AlexV525) <a href="https://twitter.com/AlexV525/status/1624978831860989952?ref_src=twsrc^tfw">February 13, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
(CFUG のことば)
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">We're thrilled to officially take over the maintenance of the amazing <a href="https://twitter.com/hashtag/dio?src=hash&ref_src=twsrc^tfw">#dio</a> package for <a href="https://twitter.com/hashtag/Flutter?src=hash&ref_src=twsrc^tfw">#Flutter</a> and <a href="https://twitter.com/hashtag/Dart?src=hash&ref_src=twsrc^tfw">#Dart</a> developers. Big thanks to <a href="https://twitter.com/duwen65952787?ref_src=twsrc^tfw">@duwen65952787</a> and the dio / diox contributors for their fantastic work. 🚀 <a href="https://twitter.com/hashtag/flutterdev?src=hash&ref_src=twsrc^tfw">#flutterdev</a> <a href="https://twitter.com/hashtag/dartlang?src=hash&ref_src=twsrc^tfw">#dartlang</a>
Read more: <a href="https://t.co/z7hIiOMHd9">https://t.co/z7hIiOMHd9</a></p>— Flutter China 💙 (@FlutterChina) <a href="https://twitter.com/FlutterChina/status/1625322040252317697?ref_src=twsrc^tfw">February 14, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
では、dio
をインストールします。
dependencies:
flutter:
sdk: flutter
dio: ^5.1.1 # 今回は dio をインストールします
supabase_flutter: ^1.6.0
なお、CFUG への移管と時を同じくして、dio は v5 へアップデートされています。
エラーの enum 定義が変更されていたり、地味に変更点が多いので注意したい。
dio 5.0.0 がリリースされたのでマイグレーションガイドなんかをね
String OPENAPI_CHAT_COMPLETIONS_API =
'https://api.openai.com/v1/chat/completions'
String OPENAPI_SECRET = ''
void requestChatGPT(List<Map<String, String>> messages) async {
Dio dio = Dio();
dio.options.headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $OPENAI_API_KEY'
};
try {
Response response = await dio.post(
OPENAI_CHAT_COMPLETIONS_API,
data: {
'model': 'gpt-3.5-turbo',
'messages': messages,
},
);
String result = response.data['choices'][0]['message']['content'];
setState(() {
answer = result;
});
} catch (e) {
String result = e.toString();
setState(() {
answer = result;
});
}
}
状態の変化したことを教えてくれる setState()
で ChatGPT のレスポンスを取得します。
そして、そのまま Supabase へその値をデータベースに更新します。
setState(() {
answer = result;
final myUserId = supabase.auth.currentUser!.id;
sendMessage(myUserId, answer);
});
取り急ぎ、Flutter アプリ内で ChatGPT を動かすロジックを作るなら、基本的に OpenAI の API を実行するだけで ChatGPT からの答えが返ってきます。
回答は ver.2023.2.1-rc.1
branch をご確認いただければ幸いです。
UI 仕上げ
自分自身のコメントに限らず ChatGPT からの答えも、コメントの表示位置を反転させるのが望ましいでしょう。
// 自分自身のコメントでかつ ChatGPT ではないものに対して表示位置を反転させるよう更新
if (message.isMine && !message.isChatGPT) {
chatContents = chatContents.reversed.toList();
}
Supabase 管理画面上で ChatGPT 用のユーザーを作成します。
あらかじめ ChatGPT 用の UID を記録すると、実際 ChatGPT かどうかの判別を付けられます。
String CHATGPT_UID = '';
Message.fromMap({
required Map<String, dynamic> map,
required String myUserId,
}) : id = map['id'],
profileId = map['profile_id'],
content = map['content'],
createdAt = DateTime.parse(map['created_at']),
isMine = myUserId == map['profile_id'],
isChatGPT = myUserId == CHATGPT_UID;
逐一 UID をチェックいただきつつ、常に会話している感覚を味合わせれば、ユーザー体験として良さそうです。
最後に
現時点で個人的にまだ GPT-4 の model を使えない状態ですが、少なくとも今後当たり前の存在となってくるのは間違い無いでしょう。
(再掲) ざっとソースコードを確認したい方は、下記コミットログをご確認ください。