Найти тему

Интеграция user-media и Chat-gpt в приложение на React

В данной статье будет рассмотрена тема написания собственного frontend-приложения на основании React и некоторых других библиотек для использования чата (как посредством ручного ввода вопроса, так и посредством голоса).

Данная статья не создана для начинающих, так как многие вещи (такие, как правила использования хуков и тому подобное объяснены не будут).

Библиотеки, которые я использовал:

  • axios (может быть заменена на ky, либо не использована вовсе);
  • react;
  • typescript;
  • react-use-animation.

Начнем со структуры папок:

->components --->UI

->hooks

->server-api

->styles

Создадим пользовательский hook useMedia. В нем будут следующие строчки:

Если ts ругается на speechRecognition, вручную дополните type window этими двумя свойствами. Если пишите на js, то проблем не возникнет.
Если ts ругается на speechRecognition, вручную дополните type window этими двумя свойствами. Если пишите на js, то проблем не возникнет.

Далее создаём хук useEffect с пустым массивом зависимостей (т.е. срабатывает при инициализации приложения) и навесим в нем два события onresult и onspeechend на интерфейс взаимодействия с микрофоном:

Тут, в принципе, ничего сложного. Единственная проблема в типизации события event, т. к. ts не имеет встроенного типа для него и приходится либо писать костыль, либо писать any. Но по большому счету можете один раз вывести в консоль и понять, где находятся данные.
Тут, в принципе, ничего сложного. Единственная проблема в типизации события event, т. к. ts не имеет встроенного типа для него и приходится либо писать костыль, либо писать any. Но по большому счету можете один раз вывести в консоль и понять, где находятся данные.

И последнее в данном хуке (после чего просто ретурним все переменные и функции) это функция playback для воспроизведения текста:

Здесь просто проверяем разрешено ли воспроизведение, превращаем строку в необходимый для воспроизведения объект, настраиваем его и включаем звук. На воспроизведение звука без разрешения пользователя браузер может ругаться поэтому необходимо добавить кнопку (которая регулирует isPlaybackAllowed).
Здесь просто проверяем разрешено ли воспроизведение, превращаем строку в необходимый для воспроизведения объект, настраиваем его и включаем звук. На воспроизведение звука без разрешения пользователя браузер может ругаться поэтому необходимо добавить кнопку (которая регулирует isPlaybackAllowed).

Еще один хук, если вы хотите, чтобы запрос можно было не только говорить, но и писать ручками, это хук useDebounce. Там ничего замудренного, можете посмотреть примерную реализацию по этой ссылке.

Далее перейдем к server-api. Внутри я создал файл request-api. Создаем axios.instance, который будет содержать параметр авторизации: headers:{Authorization: `Bearer ${API_KEY || user_api_key}`} под API_KEY подразумевается, что вы либо создадите файл с переменной и экспортируете ее, либо просто вставите свой ключ с сайта OpenAI (чтобы его получить надо зайти во вкладку developers -> API-reference -> API-KEY (в тексте будет выделена бирюзовым) и там нажать create key.

Далее создаем метод запроса к чату:

На момент написания статьи gpt-4 доступен только избранным, так что выбираем модель gpt-3.5-turbo
На момент написания статьи gpt-4 доступен только избранным, так что выбираем модель gpt-3.5-turbo

Оборачиваем это все в try/catch для отлавливания простейших ошибок и вуаля. Осталось только немного побаловаться с React-компонентами, чтобы это все красиво выглядело. Да-да, согласен, что просто выводить в консоль ошибку - плохая практика: мог бы хотя бы попробовать сделать повторный запрос :)

Рассмотрим компоненту Main (думаю, раз речь идет о компоненте, то не стоит упоминать в какую папку я её положил).

Тут есть небольшой нюансик в виде того, что если вы хотите, чтобы ваш диалог с чатом был в контексте, а не в виде одиночных вопросов-ответов, то придется создать еще один useState для хранения всех вопросов/ответов (который удобно пригодиться, чтобы выводить их на экран, а не колупаться во всех хуках и выцеплять оттуда все данные).

К делу: создаем хук const [resp, setResp] = useState<messages>([]) (тип messages, который уже мелькал ранее это объект из двух свойств role: user/assistant и content - строка с ответом на ваш вопрос).

Далее берём useEffect, который реагирует только на изменение запроса query (он заполняется либо после окончания Debounce, либо когда человек закончил говорить в микрофон) и пишем туда следующее:

Если запрос есть, то создаем сообщение, пушим его в resp, и запрашиваем у сервера ответ на вопрос (тут принципиально важно 2 раза использовать [...resp, message], т. к. сэттеры у useState асинхронные. Далее чтобы не подключать Redux/MobX и подобное пришлось воспользоваться старым добрым then, в котором мы пушим уже ответ чата и просим браузер его прочитать (если доступа нет, то playback сразу ретёрнет и не сработает).
Если запрос есть, то создаем сообщение, пушим его в resp, и запрашиваем у сервера ответ на вопрос (тут принципиально важно 2 раза использовать [...resp, message], т. к. сэттеры у useState асинхронные. Далее чтобы не подключать Redux/MobX и подобное пришлось воспользоваться старым добрым then, в котором мы пушим уже ответ чата и просим браузер его прочитать (если доступа нет, то playback сразу ретёрнет и не сработает).

С большего это все, что я хотел рассказать. Стили показывать не вижу смысла - это дело вкуса. Могу только сказать, что есть классный сайт, который вместо дизайнера подберет вам цвета.

Реализацию компонент в виде jsx тоже наверное нету смысла показывать, тоже дело вкуса да и кто как сверстает. Единственное, что могу посоветовать это добавить какую-нибудь иконку включения/выключения чтения ответа от чата (за это у меня отвечает isPlaybackAllowed, setIsPlaybackAllowed) потому что браузер будет ругаться; Какое-нибудь обозначение, что ведётся запись вашего голоса (isRecording, setIsRecording) и, но тут уже на свой вкус и цвет, было бы неплохо добавить иконку Debounce для ввода текста. У меня стоит задержка в анимации на 0,2с (чтобы она не мелькала пока человек печатает) и сама анимация, по окончании которой отправляется запрос на сервер, 1.8 с.

Будет вопросы - пишите в комментарии/на почту. С умными людьми мне всегда интересно пообщаться :)

Конечный результат у меня получился примерно такой:

-6