Интеграция 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
. Он специально обучен для задач генерации естественного языка и обладает большими возможностями для понимания и создания текста, подобного человеческому. Модель была оптимизирована для различных задач, включая завершение текста, подведение итогов, ответы на вопросы и многое другое. Он был обучен на огромном количестве разнообразных текстовых данных.
Затем мы создаем POST
route для 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 может быть снабжена аннотацией @JSExport
property с помощью 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.loader
JavaScript 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 (keyCode
13) и вызывает функцию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 и найдите прекрасную работу