How to Build a ChatGPT Messaging Application with Flutter
Nowadays, it’s so time-consuming keeping up with friends. Before social media and messaging apps arrived, you actually had to spend time with people in real life. Although it has gotten a bit better now that you can just message them, it is still a pain having to think of what to say and interact with them.
Now there is a solution! You no longer need to think, you can just have an AI chat with your friends!
We’re joking, of course: you should definitely still hang out with friends and not outsource your relationships to AI. But this is still a cool use case that can be used for replies to bots or even to help you keep the conversation going when it gets a bit stale.
In this article we will cover how you can build your own chat application using Agora Chat and then making it even fancier by adding ChatGPT.
You can find the full code here.
Agora Chat
Agora Chat enables you to communicate with your friends and include text, images, GIFs, emojis, and other media. Agora Chat can be used as a standalone application, or it can be integrated into your existing applications that use real-time communication (RTC) such as video calling.
ChatGPT
OpenAI’s ChatGPT is their AI model, which can generate very realistic responses to messages. We will be using the OpenAI API to send requests to the model and get responses that we will insert into our chat.
Setup
To interact with these services, we will need to create accounts on both Agora and OpenAI and retrieve some tokens.
How to retrieve Agora Chat information
- Create an account on Agora.io.
- Create a project
- Scroll down to
Chat
and clickEnable/Configure
- Click the toggle button and copy and paste the AppKey.
- On the left side under
Operation Management
selectUser
and create two users. (Note down the user IDs.) - Go back to
Application Information
under theBasic Information
tab and enter the user ID for each user in theChat User Temp Token
field. ClickGenerate
. Copy and paste the tokens and user IDs into aconsts.dart
file.
How to get an OpenAI token
- Create an account on OpenAI.
- In the top right corner click your image and select
View API Keys
- Create a new secret key and copy and paste it into the
consts.dart
file.
Chat Page
We need to get to the chat page, with the current users ID, token, and ID of the user you want to message. There are multiple ways to do this, but for this example I just created two buttons where you can log in as each individual user. You can see more in the main.dart
file.
As soon as we launch into the chat page, three things need to happen for our chat to be initialized. Since we haven’t done any setup with the Agora Chat SDK, we need to initialize the SDK, then add a listener for messages, and finally log in the current user in to have chat application fully ready to use. These three functions need to be called in the initState
.
void _initSDK() async {
ChatOptions options = ChatOptions(
appKey: widget.chatKey,
autoLogin: false,
);
await ChatClient.getInstance.init(options);
await ChatClient.getInstance.startCallback();
}
void _addChatListener() {
ChatClient.getInstance.chatManager.addMessageEvent(
"UNIQUE_HANDLER_ID",
ChatMessageEvent(
onSuccess: (msgId, msg) {
debugPrint(
"send message succeed: ${(msg.body as ChatTextMessageBody).content}");
_addMessage(
DemoMessage(
text: (msg.body as ChatTextMessageBody).content,
senderId: widget.userId),
);
},
onProgress: (msgId, progress) {
debugPrint("send message succeed");
},
onError: (msgId, msg, error) {
debugPrint(
"send message failed, code: ${error.code}, desc: ${error.description}",
);
},
));
ChatClient.getInstance.chatManager.addEventHandler(
"UNIQUE_HANDLER_ID",
ChatEventHandler(onMessagesReceived: onMessagesReceived),
);
}
void _signIn() async {
try {
await ChatClient.getInstance.loginWithAgoraToken(
widget.userId,
widget.agoraToken,
);
debugPrint("login succeed, userId: ${widget.userId}");
} on ChatError catch (e) {
debugPrint("login failed, code: ${e.code}, desc: ${e.description}");
}
}
Since we have the sign-in method here, let’s also create our sign-out method. This will be called in the dispose
method.
void _signOut() async {
ChatClient.getInstance.chatManager.removeMessageEvent("UNIQUE_HANDLER_ID");
ChatClient.getInstance.chatManager.removeEventHandler("UNIQUE_HANDLER_ID");
try {
await ChatClient.getInstance.logout(true);
debugPrint("sign out succeed");
} on ChatError catch (e) {
debugPrint(
"sign out failed, code: ${e.code}, desc: ${e.description}");
}
}
You’ll notice that a couple of functions called in the Chat Listener aren’t shown:
_addMessage
adds the incoming message to the list of visible messages.debugPrint
adds the incoming message to the console log.onMessagesReceived
is a function that is called when a message is received. (We will also use_addMessage
to keep a list from both parties.)
Below are the definitions for those functions.
void onMessagesReceived(List<ChatMessage> messages) {
for (var msg in messages) {
switch (msg.body.type) {
case MessageType.TXT:
{
ChatTextMessageBody body = msg.body as ChatTextMessageBody;
debugPrint(
"receive text message: ${body.content}, from: ${msg.from}",
);
_addMessage(
DemoMessage(text: body.content, senderId: msg.from),
);
}
break;
default:
break;
}
}
}
void _addMessage(DemoMessage message) {
_messages.add(message);
setState(() {
scrollController.jumpTo(scrollController.position.maxScrollExtent + 40);
});
}
Show the Chat
A ListView.builder
will display all the messages in the _messages
list. You will also need to add a TextField
at the bottom of the screen that will allow us to send messages.
ListView.builder(
controller: scrollController,
itemCount: _messages.length,
itemBuilder: (_, index) {
//show first 10 characters
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 2),
if (widget.userId != _messages[index].senderId)
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.lightBlue[100],
borderRadius: BorderRadius.circular(10),
),
child: Text(
_messages[index].text!,
),
)
else
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.lightGreen[100],
borderRadius: BorderRadius.circular(10),
),
child: Text(
_messages[index].text!,
),
),
),
],
),
],
);
},),
Send a Message
To send a message we need to call the sendMessage
function from the Agora Chat SDK. This function takes in a ChatMessage
object. We also need to pass in the user ID of the person we want to send the message to.
void _sendMessage(String sentTo, String? message) async {
if (message == null) {
debugPrint("single chat id or message content is null");
return;
}
var msg = ChatMessage.createTxtSendMessage(
targetId: sentTo,
content: message,
);
ChatClient.getInstance.chatManager.sendMessage(msg);
setState(() {
_messageController.text = "";
});
}
We now have a fully functioning Agora Chat application. The last step is to integrate ChatGPT to enable AI generated responses.
ChatGPT
To use ChatGPT we first need to connect to the API. We also need to declare a isWaitingResponse
variable that is used to disable the send button while we wait for a response from the API.
Once the variable is declared we can set up functions to send either a happy or an angry response to the other user’s last message
final openAI = OpenAI.instance.build(
token: openAIToken,
baseOption: HttpSetup(receiveTimeout: const Duration(seconds: 30)),
isLog: true);
var _isWaitingResponse = false;
void _onTapSendHappyMessage() async {
final List<DemoMessage> otherMessages = _messages
.where((element) => element.senderId != widget.userId)
.toList();
_reset();
_sendAIMessage(
"Give me a happy response to the message: ${otherMessages.last.text}",
).then((value) {
setState(() {
_isWaitingResponse = false;
_messageController.text = value.trim();
});
});
}
void _onTapSendAngryMessage() async {
final List<DemoMessage> otherMessages = _messages
.where((element) => element.senderId != widget.userId)
.toList();
_reset();
_sendAIMessage(
"Give me an angry response to the message: ${otherMessages.last.text}",
).then((value) {
setState(() {
_isWaitingResponse = false;
_messageController.text = value.trim();
});
});
}
void _reset() {
setState(() {
_isWaitingResponse = true;
});
}
Future<String> _sendAIMessage(String message) async {
final request = CompleteText(
prompt: message,
model: Model.textDavinci3,
maxTokens: 200,
);
final response = await openAI.onCompletion(
request: request,
);
return response!.choices.first.text;
}
That’s the full functionality of our application. You now can add buttons that generate those responses and put them into the TextController.value
of the TextField
to enable the user to send them.
Thank you for reading. Click here to read and learn more about Agora.