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 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.
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.
To interact with these services, we will need to create accounts on both Agora and OpenAI and retrieve some tokens.
Chat
and click Enable/Configure
Operation Management
select User
and create two users. (Note down the user IDs.)Application Information
under the Basic Information
tab and enter the user ID for each user in the Chat User Temp Token
field. Click Generate
. Copy and paste the tokens and user IDs into a consts.dart
file.View API Keys
consts.dart
file.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
.
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);
});
}
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!,
),
),
),
],
),
],
);
},),
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.
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.