Credit: Nguyen Ngoc Duy (Mobile Developer)
Lời mở đầu
Chào mừng các bạn đến với bài viết hướng dẫn lập trình Flutter qua những ứng dụng thú vị. Trong bài viết này, chúng ta sẽ tìm hiểu về API của OpenAI và lý do tại sao bạn nên sử dụng nó. Bạn sẽ cùng mình tìm hiểu cách sài API của OpenAI trong một dự án Flutter và sử dụng nó để xây dựng một ứng dụng AI, một trợ lý ảo giọng nói thông minh. Nếu bạn là người đang tìm hiểu Flutter và có một kiến thức nhất định về Flutter, muốn thử xây dựng một con chatbot AI bằng flutter, bài viết này sẽ là nguồn thông tin rất hữu ích cho bạn. Hãy cùng bắt đầu nào! Gét Gô.
ChatGPT là gì?
ChatGPT là một công cụ chatbot AI do công ty nghiên cứu trí tuệ nhân tạo (AI) OpenAI tạo ra, dựa trên các đối thoại nguyên mẫu để hiểu ngôn ngữ tự nhiên và phản hồi bằng ngôn ngữ tự nhiên (giống như hai con người đang nói chuyện trực tiếp) với phạm vi trao đổi không giới hạn.
Kể từ khi ra mắt, công cụ này đã và đang gây bão trên Internet và có rất nhiều người sử dụng trong vòng chưa đầy một tuần. Hầu hết người dùng đều ngạc nhiên về mức độ thông minh của công cụ chatbot này. Một số thậm chí còn coi ChatGPT là sự thay thế cho Google, vì ChatGPT có khả năng đưa ra giải pháp cho các vấn đề phức tạp một cách trực tiếp — gần giống như một người thầy dạy kiến thức cá nhân. Liệu nó có thực sự thay thế được Google hay không thì thời gian sẽ kiểm chứng, trong giới hạn của bài viết này mình sẽ không tập trung vào phân tích mà sẽ tập trung hướng dẫn các bạn sử dụng API của OpenAI để xây dựng một trợ lý ảo thông minh bằng Flutter.
Bây giờ chúng ta cần bắt đầu tạo ra trợ lý ảo cho riêng mình bằng việc tạo tài khoản và lấy API Key của OpenAI.
Hướng dẫn tạo tài khoản và lấy API Key của OpenAI
Trước khi bắt đầu tạo tại khoản thì bạn có một chiếc Visa có tối thiểu 1$ bởi vì khi thuê số điện thoại nước ngoài thì mỗi lần request OTP sẽ có giá 0.6$.
Để lấy API Key, bạn cần tạo tài khoản của OpenAi tại địa chỉ https://beta.openai.com/signup.
Tuy nhiên hiện tại OpenAI vẫn chưa available tại Việt Nam nên trong quá trình đăng kí bạn sẽ gặp hình ảnh sau:
Lúc này bạn hãy làm theo theo các bước sau:
- Cài https://chrome.google.com/webstore/detail/touch-vpn-secure-and-unli/bihmplhobchoageeokmgbdihknkjbknd/related và fake IP ở Mẽo.
- Ở trình duyệt Chrome ấn F12 hoặc bấm vào dấu ba chấm ở góc trên, bên phải cửa sổ trình duyệt, chọn More Tools → Developer Tools. Chọn thẻ Application và xóa theo các bước trong ảnh. Sau đó ấn F5 để tải lại trang.
- Lúc này OpenAI sẽ cho phép bạn đăng ký. Điền các thông tin theo yêu cầu. Đến bước xác thực qua số điện thoại thì vào trang https://sms-activate.org hoặc http://smspool.net thuê một số điện thoại để nhận tin nhắn.
- Sau khi thuê được số điện thoại thì điền vào và đợi mã xác thực gửi về smspool. Điền mã xác thực và tiến hành trải nghiệm ChatGPT.
Sau khi tạo tài khoản thành công, bạn chuyển tới trang quản lý tài khoản https://beta.openai.com/account/api-keys. Trên trang này, bạn sẽ thấy một nút “Create new secret key”. Nhấp vào nút này để tạo một API Key mới cho tài khoản của bạn.
Tổng kết, mình đã hướng dẫn các bạn tạo tài khoản và tạo API Key. Và sau đây là một số điều quan trọng mà bạn cần lưu ý nếu không muốn bị mất tiền oan hay bị ban tài khoản:
- Khi tạo tài khoản và lấy API bạn sẽ có 18$ trong tài khoản OpenAI để sử dụng dịch vụ.
- Đối với dịch vụ Language models (vì chúng ta dùng nó để trả lời tự động) của OpenAI thì có đơn giá là $0.02/1Ktokens. (Chi tiết https://openai.com/api/pricing/)
- Hãy chắc chắn rằng bạn không chia sẻ API key của mình với bất kỳ ai khác. (Chi tiết hãy xem tại https://beta.openai.com/docs/usage-policies)
- Hãy chắc chắn rằng bạn luôn luôn sử dụng API key của mình một cách an toàn và không làm spam hay sử dụng với mục đích xấu hoặc làm ảnh hưởng đến hệ thống của OpenAI. (Chi tiết hãy xem tại https://beta.openai.com/docs/usage-policies)
Giờ thì đi đến công đoạn xây dựng UI cho ứng dụng của bạn nào.
Xây dựng giao diện
Đầu tiên hãy tạo một flutter project mới. Sau khi tạo thành công project thì chúng ta bắt đầu tạo một file có tên là model.dart trong thư mục lib
.
Đặt tên cho model này là ChatMessage
.
Trong đó, class ChatMessage
là model chứa nội dung message và ChatMessageType
là một enum có hai value là user
và bot
giúp chúng ta phân biệt được tin nhắn bản thân gửi và tin nhắn của bot gửi.
Sau đó, ở file main.dart bạn hãy xoá hết code có sẵn và tạo ra một StatefulWidget là ChatPage, nó chính là màn hình Chat:
import 'package:flutter/material.dart';
import 'package:flutter_application_1/model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: ChatPage(),
debugShowCheckedModeBanner: false,
);
}
}
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
@override
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
@override
Widget build(BuildContext context) {
return Container();
}
}
Tại _ChatPageState
chúng ta tạo các biến lần lượt:
class _ChatPageState extends State<ChatPage> {
final _textController = TextEditingController();
final _scrollController = ScrollController();
final List<ChatMessage> _messages = [];
late bool isLoading;
@override
void initState() {
super.initState();
isLoading = false;
}
_textController
là một controller giúp chúng ta lấy được text do user nhập vào.
_scrollController
dùng để scroll đến message mới nhất mỗi khi user gửi hoặc nhận một tin nhắn nào đó.
_messages
là sanh sách các tin nhắn.
Cuối cùng là biến isLoading
dùng để show/hide Progress Indicator khi chúng ta gửi tin nhắn cho bot và đợi nó phản hồi.
Tiếp theo, chúng ta sẽ code hàm build
của class _ChatPageState
. Chúng ta sẽ chỉ code một UI hết sức đơn giản, nó sẽ có một Column
chứa một ListView
(output của hàm _buildList
) để hiển thị các message của cả user và bot, một TextField
để user nhập text (hàm _buildInput
) và một button để send message (hàm _buildSubmit
).
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 100,
title: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"OpenAI's ChatGPT Flutter",
maxLines: 2,
textAlign: TextAlign.center,
),
),
backgroundColor: const Color(0xff10a37f),
),
backgroundColor: Colors.white,
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return SafeArea(
child: Column(
children: [
Expanded(
child: _buildList(),
),
Visibility(
visible: isLoading,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(
color: Color(0xff10a37f),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
_buildInput(),
const SizedBox(width: 5),
_buildSubmit(),
],
),
),
],
),
);
}
Tiếp theo, ta sẽ code hàm _buildList
Widget _buildList() {
if (_messages.isEmpty) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
backgroundColor: const Color(0xff10a37f),
radius: 50,
child: Image.asset(
'assets/bot.png',
color: Colors.white,
scale: 0.6,
),
),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 50),
child: Text(
'Hi, I\'m Duy\nTell me your dreams and i\'ll make them happen',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Colors.black,
),
textAlign: TextAlign.center,
),
),
],
);
}
return ListView.builder(
physics: const BouncingScrollPhysics(),
controller: _scrollController,
itemCount: _messages.length,
itemBuilder: (context, index) {
var message = _messages[index];
return ChatMessageWidget(
text: message.text,
chatMessageType: message.chatMessageType,
);
},
);
}
Đoạn code trên là mình kiểm tra nếu tin nhắn rỗng sẽ hiển thị giao diện như bên dưới
Tiếp theo chúng ta sẽ code UI cho các item của ListView là class ChatMessageWidget
:
class ChatMessageWidget extends StatelessWidget {
const ChatMessageWidget(
{super.key, required this.text, required this.chatMessageType});
final String text;
final ChatMessageType chatMessageType;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
padding: const EdgeInsets.all(16),
color: chatMessageType == ChatMessageType.bot
? const Color(0xff10a37f)
: Colors.white,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
chatMessageType == ChatMessageType.bot
? Container(
margin: const EdgeInsets.only(right: 16.0),
child: CircleAvatar(
backgroundColor: const Color(0xff10a37f),
child: Image.asset(
'assets/bot.png',
color: Colors.white,
scale: 1.5,
),
),
)
: Container(
margin: const EdgeInsets.only(right: 16.0),
child: const CircleAvatar(
child: Icon(
Icons.person,
),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: const EdgeInsets.all(8.0),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
child: Text(
text,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: chatMessageType == ChatMessageType.bot
? Colors.white
: Colors.black,
),
),
),
],
),
),
],
),
);
}
}
ChatMessageWidget
sẽ nhận 2 thông tin là text
và chatMessageType
. Mục đích là để kiểm tra xem là tin nhắn của bot hay user và tạo style tương ứng cho nó.
Như vậy là đã hoàn thành UI của ListView
, chúng ta sang code hàm _buildInput
Expanded _buildInput() {
return Expanded(
child: TextField(
textCapitalization: TextCapitalization.sentences,
minLines: 1,
maxLines: 9,
controller: _textController,
decoration: InputDecoration(
filled: true,
fillColor: Colors.grey[300],
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
),
),
);
}
Và cuối cùng là hàm _buildSubmit
.
Widget _buildSubmit() {
return Visibility(
visible: !isLoading,
child: Container(
decoration: BoxDecoration(
color: const Color(0xff10a37f),
borderRadius: BorderRadius.circular(
6,
),
),
child: IconButton(
icon: const Icon(
Icons.send_rounded,
color: Colors.white,
),
onPressed: () async {
setState(
() {
_messages.add(
ChatMessage(
text: _textController.text,
chatMessageType: ChatMessageType.user,
),
);
isLoading = true;
},
);
_textController.clear();
Future.delayed(const Duration(milliseconds: 50))
.then((_) => _scrollDown());
setState(() {
isLoading = false;
_messages.add(
ChatMessage(
text: "hello",
chatMessageType: ChatMessageType.bot,
),
);
});
},
),
),
);
}
void _scrollDown() {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
Cụ thể là khi user click vào button send thì ta sẽ làm các việc sau:
- Thêm tin nhắn mình vừa gửi vào
ListView
- Clear text trong
TextField
- Scroll đến cuối trang tức là vị trí của tin nhắn mới nhất.
- Fake data cho con bot để nó trả lời lại với nội dung là “hello”. Đoạn này vì chưa có dữ liệu từ ChatGPT nên mình cần phải fake data như vậy để hoàn thành UI.
Sau khi hoàn thành tất cả các bước trên, chúng ta có một giao diện như sau:
Kết bài
Tạm thời đến đây thôi là đủ. Bạn có thể tuỳ chỉnh và thiết kế lại giao diện đẹp hơn tuỳ vào sở thích của bạn.
Trong phần sau của series này:
- Mình sẽ tiến hành tích hợp API OpenAI vào ứng dụng Flutter
- Tích hợp xử lý voice để nó trở thành một Smart Voice Assistant