Создание клона ChatGPT на Flutter с помощью OpenAI API
21 февраля 2023 г.ChatGPT (Generative Pre-trained Transformer) – это чат-бот, запущенный OpenAI в ноябре 2022 года. Он построен на основе семейства больших языковых моделей OpenAI GPT-3.5 и точно настроен как с помощью методов обучения с учителем, так и с помощью методов обучения с подкреплением.
ChatGPT был запущен в качестве прототипа 30 ноября 2022 года и быстро привлек внимание своими подробными и четкими ответами во многих областях знаний. Однако его неравномерная фактическая точность была определена как существенный недостаток.
В этой статье мы узнаем, как использовать API OpenAI для создания приложения ChatGPT на Flutter.
Для создания этого приложения нам понадобится следующее:
- Токен API: нам понадобится токен API от OpenAI. Вы можете получить токен API на панели управления учетной записи OpenAI. Если у вас нет учетной записи, вы можете создать ее.
2. http: пакет http flutter для обработки http-запросов.
3. provider: пакет Provider — это простой в использовании пакет, который по сути является оболочкой вокруг inheritedwidget
, который упрощает использование и управление. Он предоставляет метод управления состоянием, который используется для управления частью данных в приложении.
4. анимированный текстовый набор: флаттер-пакет, содержащий коллекцию крутых текстовых анимаций.
5. flutter_svg: библиотека рендеринга SVG и виджетов для флаттера, которая позволяет размещать и отображать масштабируемую векторную графику. файлы.
Когда все готово, начинаем строить. 🍾 🍻
Откройте свой терминал и создайте свое приложение для флаттера, используя флаттер кли
flutter create openai-chat
Когда приложение будет создано, откройте папку в VSCode или любом другом текстовом редакторе, которым вы пользуетесь.
Откройте папку lib и откройте файл main, очистите первоначальный код, который был создан с приложением, потому что мы собираемся начать создавать наше приложение с нуля.
Ваш файл main.dart
теперь будет выглядеть так, если создать виджет с отслеживанием состояния:
import 'package:flutter/material.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Open AI Chat",
home: SafeArea(
bottom: true,
top: false,
child: Scaffold(
backgroundColor: const Color(0xff343541),
appBar: AppBar(
backgroundColor: const Color(0xff343541),
leading: IconButton(
onPressed: () {},
icon: const Icon(
Icons.menu,
color: Color(0xffd1d5db),
),
),
elevation: 0,
title: const Text("New Chat"),
centerTitle: true,
actions: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.add,
color: Color(0xffd1d5db),
),
),
],
),
body: Stack(
[],
),
),
),
);
}
}
Теперь, когда мы настроили наше приложение, мы можем начать создавать все различные виджеты. Мы стремимся к четырем (4) различным виджетам:
- Виджет ввода данных пользователем
- Виджет сообщения пользователя
- Виджет сообщений AI
- Виджет загрузки
Создайте папку под названием widgets, в ней будут все четыре виджета, над которыми мы скоро будем работать.
- Виджет ввода данных пользователем
import 'package:flutter/material.dart';
class UserInput extends StatelessWidget {
final TextEditingController chatcontroller;
const UserInput({
Key? key,
required this.chatcontroller,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 5,
right: 5,
),
decoration: const BoxDecoration(
color: Color(0xff444654),
border: Border(
top: BorderSide(
color: Color(0xffd1d5db),
width: 0.5,
),
),
),
child: Row(
children: [
Expanded(
flex: 1,
child: Image.asset(
"images/avatar.png",
height: 40,
),
),
Expanded(
flex: 5,
child: TextFormField(
onFieldSubmitted: (e) {
},
controller: chatcontroller,
style: const TextStyle(
color: Colors.white,
),
decoration: const InputDecoration(
focusColor: Colors.white,
filled: true,
fillColor: Color(0xff343541),
suffixIcon: Icon(
Icons.send,
color: Color(0xffacacbe),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0),
),
),
),
),
),
],
),
),
);
}
}
UserInput
принимает один параметр, chatcontroller
. У нас также есть метод обратного вызова onFieldSubmitted
, который поступает в проигрыватель, когда пользователь отправляет свое сообщение.
- Виджет сообщения пользователя
class UserMessage extends StatelessWidget {
final String text;
const UserMessage({
Key? key,
required this.text,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
"images/avatar.png",
height: 40,
width: 40,
fit: BoxFit.contain,
),
),
),
Expanded(
flex: 5,
child: Padding(
padding: const EdgeInsets.only(
left: 3,
top: 8,
),
child: Text(
text,
style: const TextStyle(
color: Color(0xffd1d5db),
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
),
),
],
),
);
}
}
Сообщение пользователя передает сообщение пользователя в качестве параметра классу Usermessage
, который будет добавлен к ListView.
- Виджет сообщений AI
class AiMessage extends StatelessWidget {
final String text;
const AiMessage({
Key? key,
required this.text,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: const Color(0xff444654),
padding: const EdgeInsets.all(8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: const Color(0xff0fa37f),
padding: const EdgeInsets.all(3),
child: SvgPicture.asset(
"images/ai-avatar.svg",
height: 30,
width: 30,
fit: BoxFit.contain,
),
),
),
),
Expanded(
flex: 5,
child: AnimatedTextKit(
animatedTexts: [
TypewriterAnimatedText(
text,
textStyle: const TextStyle(
color: Color(0xffd1d5db),
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
],
totalRepeatCount: 1,
),
),
],
),
);
}
}
Сообщение AI передает пользовательское сообщение в качестве параметра классу AiMessage
, который будет добавлен к ListView.
С помощью пакета AnimatedTextKit мы можем анимировать наш текст, используя анимацию пишущей машинки.
- Виджет загрузки
class Loading extends StatelessWidget {
final String text;
const Loading({
Key? key,
required this.text,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: const Color(0xff444654),
padding: const EdgeInsets.all(8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: const Color(0xff0fa37f),
padding: const EdgeInsets.all(3),
child: SvgPicture.asset(
"images/ai-avatar.svg",
height: 30,
width: 30,
fit: BoxFit.contain,
),
),
),
),
Expanded(
flex: 5,
child: Text(
text,
style: const TextStyle(
color: Color(0xffd1d5db),
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
),
],
),
);
}
}
Виджет загрузчика используется для ожидания ответа от вызова API, после завершения ответа мы удаляем загрузчик из списка.
Константа приложения
const endpoint = "https://api.openai.com/v1/";
const aiToken = "sk-------------------------------------";
Создайте файл с именем api_constants.dart, он будет содержать нашу конечную точку и токен API. Вы можете получить свой токен API на панели инструментов токенов API OpenAI.
Репозиторий OpenAI
class OpenAiRepository {
static var client = http.Client();
static Future<Map<String, dynamic>> sendMessage({required prompt}) async {
try {
var headers = {
'Authorization': 'Bearer $aiToken',
'Content-Type': 'application/json'
};
var request = http.Request('POST', Uri.parse('${endpoint}completions'));
request.body = json.encode({
"model": "text-davinci-003",
"prompt": prompt,
"temperature": 0,
"max_tokens": 2000
});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
final data = await response.stream.bytesToString();
return json.decode(data);
} else {
return {
"status": false,
"message": "Oops, there was an error",
};
}
} catch (_) {
return {
"status": false,
"message": "Oops, there was an error",
};
}
}
}
Теперь давайте пообщаемся с API OpenAI. Мы должны создать файл с именем openai_repository.dart
в папке репозитория. В файле у нас есть класс OpenAIRepository со статическим методом sendMessage, который принимает только один параметр prompt
Аутентификация
API OpenAI использует ключи API для аутентификации. Получите ключ API, который вы будете использовать в своих запросах.
Все запросы API должны включать ваш ключ API в заголовок HTTP Authorization
следующим образом:
Authorization: Bearer YOUR_API_KEY
n Отправка запроса
{
"model": "text-davinci-003",
"prompt": prompt,
"temperature": 0,
"max_tokens": 2000
}
Этот запрос запрашивает модель Davinci для завершения текста, начиная с подсказки, которую вы отправили из пользовательского ввода. Параметр max_tokens
устанавливает верхнюю границу того, сколько токенов будет возвращать API. температура
означает, что модель будет подвергаться большему риску. Попробуйте 0,9 для более творческих приложений и 0 для тех, у кого есть четко определенный ответ.
Это вернет ответ Map
{
"id": "cmpl-GERzeJQ4lvqPk8SkZu4XMIuR",
"object": "text_completion",
"created": 1586839808,
"model": "text-davinci:003",
"choices": [
{
"text": "nnThis is indeed a test",
"index": 0,
"logprobs": null,
"finish_reason": "length"
}
],
"usage": {
"prompt_tokens": 5,
"completion_tokens": 7,
"total_tokens": 12
}
}
Модель чата
class ChatModel extends ChangeNotifier {
List<Widget> messages = [];
List<Widget> get getMessages => messages;
Future<void> sendChat(String txt) async {
addUserMessage(txt);
Map<String, dynamic> response =
await OpenAiRepository.sendMessage(prompt: txt);
String text = response['choices'][0]['text'];
//remove the last item
messages.removeLast();
messages.add(AiMessage(text: text));
notifyListeners();
}
void addUserMessage(txt) {
messages.add(UserMessage(text: txt));
messages.add(const Loading(text: "..."));
notifyListeners();
}
}
Поскольку мы используем провайдера в качестве нашего управления состоянием, мы создаем класс с именем ChatModel, который расширяет ChangeNotifier. Мы создаем пустой List
Мы создаем метод с именем sendChat, который принимает пользовательский ввод, а затем вызывает addUserMessage, который помещает новый виджет, содержащий сообщение пользователя, а также виджет-загрузчик в список сообщений.
Затем мы отправляем запрос в репозиторий OpenAI, который затем отправляет ответ. Затем мы сохраняем текст в переменную String с именем text
.Затем мы удаляем виджет загрузчика из списка и добавляем виджет AIMessage
Почти готово… 🤞🏽
Мы должны вернуться к виджету userInput и вызвать sendChat, когда пользователь попытается отправить свое сообщение. Теперь ваш код будет выглядеть примерно так.
TextFormField(
onFieldSubmitted: (e) {
context.read<ChatModel>().sendChat(e);
chatcontroller.clear();
},
Нажми🚀
Все, что нужно сделать сейчас, это отредактировать наш файл main.dart. Оберните тело в MultiProvider, и ваш код будет выглядеть примерно так.
body: MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ChatModel()),
],
child: Consumer<ChatModel>(builder: (context, model, child) {
List<Widget> messages = model.getMessages;
return Stack(
children: [
//chat
Container(
margin: const EdgeInsets.only(bottom: 80),
child: ListView(
children: [
const Divider(
color: Color(0xffd1d5db),
),
for (int i = 0; i < messages.length; i++) messages[i]
],
),
),
//input
UserInput(
chatcontroller: chatcontroller,
)
],
);
}),
),
Приложение запущено 🛸🚁
Все готово, вы можете начать использовать ChatGPT в своем приложении Flutter. Вы также можете клонировать репозиторий прямо здесь.
Также опубликовано здесь.
Если у вас есть вопросы, оставьте свой комментарий здесь, и я отвечу на них как можно скорее.
Оригинал