【flutter_chat_ui】chatGPT-APIからの戻り値を綺麗に表示できない
Q&A
Closed
解決したいこと
現在、chatGPT-APIからの戻り値をflutter_chat_uiを利用して、SSEでリアルタイムに表示したいと考えています。(実際のchatGPTのように、1文字ずつ順番に表示されていくようにしたいということです。)
しかし、1文字ごとにチャットUIが再表示されてガチャガチャしてしまいます。
発生している問題・エラー
下記のように文字が追加されていくのではなく、1文字ごとに再表示しているようで、画面がガチャガチャしています。
該当する箇所
// 'content'を取得して文字列を追加していく
List<dynamic> choices = data["choices"];
Map<String, dynamic> choice = choices[0];
Map<String, dynamic> delta = choice["delta"];
String content = delta["content"];
returnMessageText += content;
final returnMessage = types.TextMessage(
author: _ai,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: randomString(),
text: returnMessageText,
);
setState(() {
_messages[0] = returnMessage;
});
ソースコード全体
import "dart:convert";
import 'package:flutter/material.dart';
import 'dart:math';
import "package:http/http.dart" as http;
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
/*
* チャット画面
*/
String randomString() {
final random = Random.secure();
final values = List<int>.generate(16, (i) => random.nextInt(255));
return base64UrlEncode(values);
}
/*
* チャットルーム画面を生成するクラス
*/
class ExecuteAIChatPage extends StatefulWidget {
final bool isQuestion;
const ExecuteAIChatPage({Key? key, required this.isQuestion})
: super(key: key);
@override
State<ExecuteAIChatPage> createState() => ExecuteAIChatState();
}
/*
* チャットルーム画面ウィジェットのクラス
*/
class ExecuteAIChatState extends State<ExecuteAIChatPage> {
final List<types.Message> _messages = [];
final _user = const types.User(id: '82091008-a484-4a89-ae75-a22bf8d6f3ac');
final _ai = const types.User(id: 'otheruser');
String returnMessageText = "";
@override
Widget build(BuildContext context) {
return Scaffold(
body: Chat(
user: _user,
messages: _messages,
onSendPressed: _handleSendPressed,
l10n: const ChatL10nEn(
emptyChatPlaceholder: 'メッセージがありません。\nAIに質問してみましょう',
inputPlaceholder: 'AIに質問する'),
),
);
}
void _handleSendPressed(types.PartialText message) async {
final textMessage = types.TextMessage(
author: _user,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: randomString(),
text: message.text,
);
setState(() {
_messages.insert(0, textMessage);
});
final returnMessage = types.TextMessage(
author: _ai,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: randomString(),
text: "",
);
setState(() {
_messages.insert(0, returnMessage);
});
sendChatCompletionRequest(message.text);
}
/*
* chatgpt-API アクセス
*/
void sendChatCompletionRequest(String text) {
final client = http.Client();
var request = http.Request(
'POST',
Uri.parse('https://api.openai.com/v1/chat/completions'),
);
Map<String, String> header = {
'accept': 'text/event-stream',
'Authorization': 'Bearer ${dotenv.env['OPENAI_APIKEY']!}',
'Content-Type': 'application/json'
};
header.forEach((key, value) {
request.headers[key] = value;
});
Map<String, dynamic> body = {
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": text},
],
"stream": true
};
request.body = jsonEncode(body);
Future<http.StreamedResponse> response = client.send(request);
response.asStream().listen((data) {
// ByteStreamをStringに変換し、改行で分割して1行ずつ処理する
data.stream
.transform(const Utf8Decoder())
.transform(const LineSplitter())
.listen(
(dataLine) {
// dataLineが空の場合や、[DONE]が返ってきた場合は早期リターン
if (dataLine.isEmpty || dataLine == 'data: [DONE]') {
return;
}
// dataLineの中身は'data: 'で始まっているので、それを削除してからMapに変換
final map = dataLine.replaceAll('data: ', '');
Map<String, dynamic> data = json.decode(map);
// finish_reasonがstopの場合は早期リターン
if (data['choices'][0]['finish_reason'] == 'stop') {
return;
}
// 'content'を取得して文字列を追加していく
List<dynamic> choices = data["choices"];
Map<String, dynamic> choice = choices[0];
Map<String, dynamic> delta = choice["delta"];
String content = delta["content"];
returnMessageText += content;
final returnMessage = types.TextMessage(
author: _ai,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: randomString(),
text: returnMessageText,
);
setState(() {
_messages[0] = returnMessage;
});
},
);
});
}
}
参考にしているサイト
確認の程、よろしくお願いいたします。
0