Интеграция ChatGPT во Flutter
Мы кратко расскажем:
- Локальное создание сервера ChatGPT
- Создание пользовательского интерфейса ChatGPT
- Встроить пользовательский интерфейс ChatGPT во Flutter
Локальное создание сервера ChatGPT
Чтобы создать наш локальный хост-сервер, мы будем использовать NodeJS. Создаем папку под названием server и запускаем
npm init -y
Это создает файл package.json в папке server. Затем мы устанавливаем такие зависимости, как
npm install @openai/api express cors body-parser
После этого мы создаем файл с именем server.js и начинаем писать в нем наш серверный код. Но сначала, прежде чем мы начнем программировать
нам требуется ключ API OpenAI.
Создайте учетную запись на веб-сайте OpenAI, нажав здесь. Далее мы переходим к View API Keys

Здесь вы создаете свой секретный ключ, и все!!!!!
Возвращаясь к кодовой базе, мы сначала импортируем зависимости, используя
import express from 'express'
import * as dotenv from 'dotenv'
import cors from 'cors'
import { Configuration, OpenAIApi } from 'openai'
dotenv.config()
Затем мы создаем файл с именем .env и внутри него создаем переменную с именем OPENAI_API_KEY со значением secretKey, полученным из ключей OpenAI.
Теперь создадим объект конфигурации с помощью пакета OpenAI.
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const app = express()
app.use(cors())
app.use(express.json())
app.post('/', async (req, res) => {
try {
const prompt = req.body.prompt;
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: `${prompt}`,
temperature: 0,
max_tokens: 3000,
top_p: 1,
frequency_penalty: 0.5,
presence_penalty: 0,
});
res.status(200).send({
bot: response.data.choices[0].text
});
} catch (error) {
console.error(error)
res.status(500).send(error || 'Something went wrong');
}
})
app.listen(5001, () => console.log('https://localhost:5001'))
Параметры конфигурации для отправки вызовов API в API OpenAI хранятся и управляются объектом Configuration. Для выполнения успешных запросов API он содержит такие параметры, как API key, endpoint URL и request timeouts. Указываем наш apiKey, который мы получили с сайта Open AI.

Объект OpenAIApi представляет собой класс в библиотеке OpenAI Node.js, предоставляющий методы для взаимодействия с API OpenAI. Он используется для аутентификации ключа API, настройки запроса API и выполнения вызовов API на серверы OpenAI.
Отдельное спасибо FeedSpot, за номинацию меня на их сайте! Проверьте FeedSpot здесь для захватывающего контента Flutter !!🙌 🎉🎉
Далее мы настраиваем экспресс-сервер и добавляем необходимое промежуточное ПО. В OpenAI доступны различные модели, но мы выбрали text-davinci-003 их для нашего случая.
Разновидностью языковой модели OpenAI GPT (Generative Pre-trained Transformer) является модель text-davinci-003. Он специально обучен для задач генерации естественного языка и обладает большими возможностями для понимания и создания текста, подобного человеческому. Модель была оптимизирована для различных задач, включая завершение текста, подведение итогов, ответы на вопросы и многое другое. Он был обучен на огромном количестве разнообразных текстовых данных.

Затем мы создаем POSTroute для ChatGPT с именем / и отправляем ответ обратно клиенту.
await openai.createCompletion({
model: "text-davinci-003",
prompt: `${prompt}`,
temperature: 0,
max_tokens: 3000,
top_p: 1,
frequency_penalty: 0.5,
presence_penalty: 0,
});
Этот фрагмент использует API OpenAI для создания дополнений текста на основе заданной подсказки. Он создает завершение, отправляя запрос к API OpenAI со следующими параметрами:
model: Модель OpenAI для использованияtext-davinci-003в нашем случаеprompt: текстовое приглашение для создания завершения.temperature: от 0 до 1. Более высокие значения означают, что модель будет больше рисковать при создании текста.max_tokens: максимальное количество токенов (слов или знаков препинания), которое может сгенерировать модель.top_p: выбирает наиболее вероятные токены, пока не будет достигнута определенная пороговая вероятность.frequency_penalty: значение штрафа от -2,0 до 2,0, которое наказывает модель за создание токенов, уже появившихся в сгенерированном тексте.presence_penalty: значение штрафа от -2,0 до 2,0, которое наказывает модель за создание токенов, похожих на текст в подсказке.
Затем мы отправляем ответ JSON внутри свойства с именем «bot», значение которого извлекается из свойства «text» первого элемента в массиве «choices» объекта данных «response».
Мы слушаем входящие запросы на порту 5001 Наконец, мы запускаем сервер, выполнив следующую команду в терминале
# LISTEN TO THE PORT
app.listen(5001, () => console.log('https://localhost:5001'))
# TO RUN THE SERVER
npm run server
И если мы попытаемся свернуть приведенный выше URL-адрес с помощью примера запроса, мы получим следующее
# SEND REQUEST TO LOCALHOST
curl -X \
POST "https://localhost:5001" -H \
"accept: application/json" -H \
"Content-Type: application/json" -d "{\"prompt\" : \"Hello\"}"
Получаем следующее:

Создание пользовательского интерфейса ChatGPT
Начнем с создания веб-проекта Flutter.
Необходимо
- Мы должны использовать канал флаттера
master - Версия Dart должна быть
3.0.0или выше.
Давайте создадим проект, используя
flutter create chatgpt_embedding --platforms web
Примечание. Мы указываем
platformкакweb, что означает, что проект поддерживает толькоweb.
По умолчанию Flutter предоставляет нам приложение-счетчик.
Мы включаем пакет js в наш проект, чтобы обеспечить бесшовное взаимодействие кода JavaScript и Dart. Любая функция в вашем коде Dart может быть снабжена аннотацией @JSExportproperty с помощью js, что позволит вам вызывать ее из кода JavaScript.
flutter pub add js
Изменения в приложении счетчика
В нашем проекте всего 2 файла дротика, как показано ниже.

Мы реорганизуем main.dart и удалим всю логику счетчика. Затем мы создаем файл с именем gpt.dart. Он содержит MyHomePage, который является виджетом с отслеживанием состояния, и _MyHomePageState — это класс состояния для виджета MyHomePage.
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
@js.JSExport()
class _MyHomePageState extends State<MyHomePage> {}
Далее мы импортируем пакеты js и js_util как
import 'package:js/js.dart' as js; import 'package:js/js_util.dart' as js_util;
_MyHomePageState аннотируется атрибутом @JSExport, так как нам нужно передать пользовательские запросы на сторону JavaScript. Это делает объект Dart _MyHomePageState экспортируемым.
Внутри нашего initState мы вызываем createDartExport для _MyHomePageState, который создает литерал объекта JS, который перенаправляется в наш экспортированный класс Dart (в нашем случае это _MyHomePageState)
Метод createDartExport() используется для создания объекта JavaScript, имеющего ссылку на объект Dart, к которому затем можно получить доступ из кода JavaScript. Методы setProperty() и callMethod() используются для настройки свойств JavaScript и вызова функций JavaScript соответственно.
void initState() {
super.initState();
final export = js_util.createDartExport(this);
// These two are used inside the [js/js-interop.js]
js_util.setProperty(js_util.globalThis, '_appState', export);
js_util.callMethod<void>(js_util.globalThis, '_stateSet', []);
}
Экземпляр JsObject export создается путем вызова метода createDartExport(), предоставляемого библиотекой js_util. Это создает объект JavaScript, который имеет ссылку на экспортируемый объект Dart.
Два свойства JavaScript задаются с помощью метода setProperty(), предоставляемого js_util. Первое свойство _appState, для которого установлено значение export объекта, созданного на предыдущем шаге. Доступ к этому свойству можно получить из кода JavaScript для взаимодействия с объектом Dart.
Второе свойство_stateSet — это функция JavaScript, определенная в другом файле (js-interop.js). Эта функция вызывается с использованием метода callMethod(), предоставляемого js_util. Пустой массив [], переданный в качестве второго аргумента, указывает на то, что в функцию не передаются никакие аргументы.
Примечание.
_appStateи_stateSetприсутствуют внутри файла js, о чем мы поговорим позже.
Мы создаем TextEditingController и FloatingActionButton внутри нашего gpt.dart. По сути, это будет принимать пользовательские запросы и передавать их в пользовательский интерфейс ChatGPT. Существует функция, которая вызывается, когда пользователь завершает запрос или когда пользователь нажимает кнопку FAB.
// This stores the user query
String _textQuery = '';
void textInputCallback(String value) {
textFocusNode.requestFocus();
setState(() {
_textQuery = value;
// This line makes sure the handler gets invoked
_streamController.add(null);
});
}
<script src="js/js-interop.js" defer></script>
Атрибут src указывает URL-адрес загружаемого файла JavaScript, в данном случае js-interop.js.

Все взаимодействие с Dart присутствует внутри этого файла. Мы используем IIFE для создания функции
(function () {
window._stateSet = function () {
console.log('HELLO From Flutter!!')
};
}());
Этот код устанавливает функцию _stateSet как свойство глобального объекта window. Затем мы получаем доступ к _appState и сохраняем его внутри переменной внутри JS.
let appState = window._appState;
HTML и CSS для ChatGPT

Чтобы не удлинять статью, прикрепляю файлы css и html для ChatGPT UI.
Встроить пользовательский интерфейс ChatGPT во Flutter
Поскольку мы хотим показать приложение флаттера, мы создаем div с идентификатором flutter_target.
Мы настраиваем тег script внутри index.html, сначала добавляя прослушиватель событий к window. Получите идентификатор div, который представляет flutter app в нашем случае flutter_target, используя селектор запросов.
window.addEventListener("load", function (ev) {
let target = document.querySelector("#flutter_target");
_flutter.loader.loadEntrypoint({
onEntrypointLoaded: async function (engineInitializer) {
let appRunner = await engineInitializer.initializeEngine({
hostElement: target,
});
await appRunner.runApp();
},
});
});
- Используя
_flutter.loaderJavaScript API, предлагаемыйflutter.js, мы изменяем способ запуска приложения Flutter в Интернете.
Следующие этапы составляют процесс инициализации:
- Сценарий точки входа загружается, и сервисный работник инициализируется после получения сценария
main.dart.js. - Инициализация механизма Flutter, который загружает необходимые файлы, включая CanvasKit, шрифты и ресурсы, для запуска веб-движка Flutter.
- Запуск приложения, которое выполняет ваше приложение Flutter после подготовки для него DOM.
Взаимодействие между Javascript и Dart
Чтобы передать значение _textQuery из Flutter в JS, мы создаем функцию textQuery внутри файла gpt.dart и аннотируем ее свойством @JSExport (что делает ее доступной со стороны JS).
@js.JSExport() String get textQuery => _textQuery;
Изменения JS
const form = document.querySelector('form')
const chatContainer = document.querySelector('#chat_container')
const formData = new FormData(form)
Выбираем первый элемент формы на странице методом document.querySelector(). Затем мы выбираем элемент HTML с идентификатором chat_container, используя метод document.querySelector().
Затем мы создаем новый экземпляр объекта FormData, используя переменную form. Этот FormData объект используется для извлечения данных из формы при ее отправке.
Мы создаем div с unique idкаждый раз, когда пользователь отправляет свои запросы из приложения Flutter.
Примечание. Идентификатор в основном представляет собой текущую отметку времени.
function chatStripe(isAi, value, uniqueId) {
return (
`
<div class="wrapper ${isAi && 'ai'}">
<div class="chat">
<div class="profile">
<img
src=${isAi ? './assets/bot.svg' : './assets/user.svg'}
alt="${isAi ? 'bot' : 'user'}"
/>
</div>
<div class="message" id=${uniqueId}>${value}</div>
</div>
</div>
`
)
}
У нас также есть функция с именем chatStripe(), которая принимает три аргумента: isAi, value и uniqueId.

- Аргумент
isAi— это логическое значение, которое определяет, предназначена ли полоса чата для бота или пользователя. Еслиtrue, то для бота, а еслиfalse, то для пользователя. - Аргумент
value— это текст сообщения, который будет отображаться в полосе чата. - Аргумент
uniqueId— это уникальный идентификатор, который мы генерируем из приведенной выше функции.
Атрибут
srcэлементаimgустанавливается на путь к значкуbot.svgилиuser.svgв зависимости от аргументаisAi.
Атрибут id элемента message div устанавливается равным аргументу uniqueId.
Наконец, у нас есть функция JavaScript, которая обрабатывает отправку форм. Функция запускается, когда пользователь отправляет сообщение боту, нажав кнопку отправки или нажав клавишу ввода в поле ввода текста.
- Добавляем полосу чата пользователя в контейнер чата с помощью функции
chatStripe() - Затем мы генерируем уникальный идентификатор с помощью функции
generateUniqueId().
Добавить сервер
Мы создаем запрос POST на локальный сервер по адресу https://localhost:5001/ с сообщением пользователя в качестве полезной нагрузки JSON.
Примечание. Сервер был создан на первом шаге выше.
Если ответ сервера — OK, мы извлекаем ответ бота из данных JSON и обрезаем все конечные пробелы или символы новой строки с помощью метода trim().
И мы печатаем ответ бота в div сообщения, используя функцию typeText().
function typeText(element, text) {
let index = 0
let interval = setInterval(() => {
if (index < text.length) {
element.innerHTML += text.charAt(index)
index++
} else {
clearInterval(interval)
}
}, 20)
}
Если ответ от сервера not OK, мы отображаем сообщение об ошибке в блоке сообщений и предупреждаем об ошибке.
Интеграция с флаттером

Мы определяем функцию с именем updateTextState, которая устанавливает для свойства prompt элемента формы значение appState.textQuery. Функция updateTextState не вызывается напрямую, а вместо этого регистрируется как обратный вызов с использованием метода appState.addHandler(). Это означает, что updateState функция будет вызываться каждый раз при изменении appState.
let updateTextState = function () {
formData.set('prompt', appState.textQuery);
handleSubmit.call(form)
};
// Register a callback to update the text field from Flutter.
appState.addHandler(updateTextState);
// CHAT GPT FUNCTIONS
form.addEventListener("submit", (e) => {
handleSubmit(e)
});
// CHAT GPT FUNCTIONS
form.addEventListener("keyup", (e) => {
if (e.keyCode === 13) {
handleSubmit(e)
}
});
Мы также подключаем прослушиватели событий для
submitсобытие элементаform. Он прослушивает отправку формы и вызывает функциюhandleSubmit()с объектомeventв качестве аргумента.keyupсобытие элементаform. Он прослушивает нажатие пользователем клавиши Enter (keyCode13) и вызывает функциюhandleSubmit()с объектомeventв качестве аргумента, который имеет тот же эффект, что и первый прослушиватель событий.
Функция
handleSubmit()отвечает за обработку отправки формы, отображение сообщения пользователя, отправку его на сервер и отображение ответа сервера.
На стороне флаттера
Экземпляр StreamController _streamController определяется типом StreamController<void>.broadcast(). Метод broadcast() создает контроллер потока, который может обрабатывать несколько подписчиков.
final _streamController = StreamController<void>.broadcast();
@js.JSExport()
void addHandler(void Function() handler) {
// This registers the handler we wrote in [js/js-interop.js]
_streamController.stream.listen((event) {
handler();
});
}
Внутри метода создается StreamSubscription путем вызова метода listen() для _streamController.stream. Метод listen() принимает в качестве аргумента функцию обратного вызова, которая будет вызываться каждый раз при добавлении события в поток.
Затем мы модифицируем наши существующие функции и добавляем событие в streamController, чтобы убедиться, что handler, который мы написали в js-interop.js, вызывается.
Внутри метода setState() значение _textQuery устанавливается значением текстового контроллера, а затем экземпляр StreamController _streamController уведомляется об изменении, добавляя нулевое значение в поток с помощью _streamController.add(null).
@js.JSExport()
void textInputCallback(String value) {
setState(() {
_textQuery = value;
// This line makes sure the handler gets invoked
_streamController.add(null);
});
}
Таким образом, пользовательский интерфейс обновляется всякий раз, когда передается textQuery со стороны Dart и отправляется на сторону JS.
"Исходный код"
Другие статьи:
Повышение уровня кодирования
Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:
- 👏 Хлопайте за историю и подписывайтесь на автора 👉
- 📰 Смотрите больше контента в публикации Level Up Coding
- 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
- 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"
🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу