Создание простого ИИ-помощника с помощью Web Speech API

В этом руководстве мы создадим виртуального помощника по имени Sony в браузере (например, Siri или Cortana) с помощью всего нескольких строк JavaScript. Бот будет слушать голосовые команды пользователей и отвечать искусственным голосом. Цель состоит в том, чтобы использовать только Web Speech API и сделать приложение максимально простым, не жертвуя слишком многими функциями. Вы можете протестировать приложение здесь.

Инструменты, которые вам нужны

  • "Гугл Хром"
  • Текстовый редактор на ваш выбор

API для функции преобразования речи в текст

Что нам нужно, так это API, способный преобразовывать текст из речи и речь из текста. Будем надеяться, что этот инструмент существует, и это Web Speech API, который обеспечивает две отдельные области функциональности — распознавание речи и синтез речи (преобразование текста в речь). В этой истории мы рассмотрим обе области.

Web Speech API пока экспериментальный, поэтому приложение работает во многих браузерах, но не во всех. На данный момент он работает в последних версиях Chrome, Edge, Opera и Safari. К сожалению, он пока не полностью работает в Firefox 😔.

Что нам нужно реализовать

Чтобы создать это веб-приложение, нам нужно реализовать четыре компонента:

  1. Простой интерфейс чата для отображения:
  • что говорят пользователи
  • что отвечает помощник
  • кнопка, которая запускает помощника (например, команда «Привет, Siri»)
  • и небольшая область справки вверху, чтобы объяснить несколько вещей пользователю

2. Преобразование речи пользователя в текст (с помощью Web Speech API)

3. Обработать текст и выполнить действие в зависимости от текста

4. Преобразовать текст обратно в речь, изменив голос и высоту звука Сони.

Пользовательский интерфейс

Первым шагом является создание простого пользовательского интерфейса. Мы создадим файл HTML, который содержит button для запуска помощника и небольшую область справки вверху, чтобы показать пользователю некоторые доступные команды. Мы также создадим элемент div для отображения того, что говорит пользователь и что отвечает Соня, и элемент p для отображения информации об обработке. В JavaScript мы будем извлекать объекты элементов, используя их уникальное свойство id.

HTML

<html>
<head>
  ...
</head>
<body>
  <div id="content">
    <h1>Sonya</h1>
    <p>Give it a try with 'hi/hello', 'how are you', 'joke', 'coin 
    flip', 'what's your name', 'what time is it', 'bye/stop'</p>
    <p>You can also try 'play despacito' &#128516;. It will play 
    on Youtube. Note: the pop-up may be blocked by your browser</p>
  </div>
  <button id="startBtn">Start listening</button>
  <div id="result"></div>
  <p id="processing"></p>
  <script src="script.js"></script>
</body>
</html>

JavaScript

const startBtn = document.getElementById("startBtn");
const result = document.getElementById("result");
const processing = document.getElementById("processing");

Речь в текст

Давайте создадим компонент, который захватывает голосовую команду и преобразует ее в текст для дальнейшей обработки. Мы будем использовать SpeechRecognition interface. Поскольку этот API доступен только в поддерживаемых браузерах, мы отобразим предупреждающее сообщение и заблокируем отображение кнопки «Пуск» в неподдерживаемых браузерах.

const SpeechRecognition = window.SpeechRecognition 
                          || window.webkitSpeechRecognition;
let toggleBtn = null;
if (typeof SpeechRecognition === "undefined") {
  startBtn.remove();
  result.innerHTML = "Your browser doesn't support Speech API. 
                      Please download latest Chrome version.";
}

Мы создадим экземпляр SpeechRecognition и установим некоторые свойства для настройки распознавания речи. Мы установим continuous и interimResults на true, чтобы отображать произносимый текст в режиме реального времени и продолжать запись, даже если пользователь сделает небольшую паузу. Время паузы составляетоколо 15 секунд, если для параметра continuous задано значение true.

const recognition = new SpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;

Затем мы добавляем дескриптор для обработки события onresult из API речи. Этот код внутри onresult вызывается каждый раз, когда Speech API захватывает строку. Все захваченные нами строки хранятся в event, который является объектом SpeechRecognitionEvent. Поскольку нам нужна только текущая строка, мы используем свойство resultIndex, чтобы получить результат с наименьшим значением индекса в массиве SpeechRecognitionResultList, который изменился.

Наконец, мы получаем transcript того, что сказал пользователь в виде текста. Этот текст проходит через функцию process, которая вызывается для обработки вопроса пользователя и поиска ответа. Затем вызывается функция readOutLoud для считывания ответа. Функции readOutLoud и process реализуются на следующих шагах. Голосовые команды пользователя и ассистента также отображаются в виде текста.

recognition.onresult = event => {
  const current = event.resultIndex;
  const recognitionResult = event.results[current];
  const recognitionText = recognitionResult[0].transcript;
 
  if (recognitionResult.isFinal) {
    processing.innerHTML = "processing ...";

    const response = process(recognitionText);
    const p = document.createElement("p");
    p.innerHTML = `<strong>You said:</strong> ${recognitionText} 
                   </br><strong>Sonya said:</strong> ${response}`;
    processing.innerHTML = "";
    result.appendChild(p);
    
    readOutLoud(response);
  } else {
    processing.innerHTML = `listening: ${recognitionText}`;
  }
};

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

Метод start() запускает службу распознавания речи, прослушивая входящий звук, чтобы распознать грамматики, связанные с текущим SpeechRecognition.

Метод stop() останавливает службу распознавания речи от прослушивания и пытается вернуть результат, используя уже захваченный звук.

let listening = false;

toggleBtn = () => {
  if (listening) {
    recognition.stop();
    startBtn.textContent = "Start listening";
  } else {
    recognition.start();
    startBtn.textContent = "Stop listening";
  }
  listening = !listening;
};

startBtn.addEventListener("click", toggleBtn);

Обработайте текст и выполните действие

На этом этапе мы построим простую логику диалога и выполним некоторые основные действия. Хотя процессор — это разум Сони, это всего лишь пример условных операторов, позволяющих Соне реагировать на команды hi, how are you, what's your name, joke, coin flip, what time is it и другие. Если Соня не знает ответа, она откроет новую вкладку для поиска через Google. Наконец, мы можем остановить ее прослушивание, сказав bye или stop.

Чтобы создать простое process, просто подумайте, что вы хотите, чтобы Соня делала, и добавьте для этого случай if. Если добавить больше условий, Соня сможет выполнять больше задач. Вы можете расширить эту функцию process, используя API или библиотеки AI, чтобы сделать помощника настолько умным, насколько вам нравится.

function process(rawText) {
  let text = rawText.replace(/\s/g, "").replace(/\'/g, "");
  text = text.toLowerCase();
  let response = null;

  if (text.includes("hello") || text.trim() == "hi" || text.includes("hey")) {
    response = getRandomItemFromArray(hello);
  } else if (text.includes("your name")) {
    response = "My name's Sonya.";
  } else if (text.includes("howareyou")||text.includes("whatsup")) {
    response = "I'm fine. How about you?";
  } else if (text.includes("whattimeisit")) {
    response = new Date().toLocaleTimeString();
  } else if (text.includes("joke")) {
    response = getRandomItemFromArray(joke);
  } else if (text.includes("play")) && text.includes("despacito")) {
    response = "Opened it in another tab";
    window.open("https://www.youtube.com/watch?v=kJQP7kiw5Fk",
                "_blank", "noopener");
  } else if (text.includes("flip") && text.includes("coin")) {
    response = Math.random() < 0.5 ? 'heads' : 'tails';
  } else if (text.includes("bye") || text.includes("stop")) {
    response = "Bye!!";
    toggleBtn();
  }

  if (!response) {
    window.open(`http://google.com/search?q=${rawText.replace("search", "")}`,
                "_blank");
    return `I found some information for ${rawText}`;
  }

  return response;
}

* Обратите внимание, что всплывающие окна, открывающиеся в новом окне, могут быть заблокированы браузером, и вам необходимо разрешить доступ.

Случайные ответы

На некоторые ответы Соня дает случайный ответ на основе набора ответов, указанных в массиве. Например, если вы говорите «Привет», Соня отвечает случайно выбранным элементом из набора приветствий. Для получения этих ответов реализована функция getRandomItemFromArray.

function getRandomItemFromArray(array) {
  const randomItem = array[Math.floor(Math.random() * array.length)];
  return randomItem;
};

Текст в речь

На последнем шаге мы используем контроллер speechSynthesis API Web Speech, чтобы дать нашему помощнику голос. После загрузки голосов заменил голос Сони на более приятный. Если вы хотите увидеть больше голосов, ознакомьтесь с документацией. Я также немного изменил высоту тона и скорость речи.

function readOutLoud(message) {
  const speech = new SpeechSynthesisUtterance();
  
  speech.text = message;
  speech.volume = 1;
  speech.rate = 1;
  speech.pitch = 1.8;
  speech.voice = voices[3];

  window.speechSynthesis.speak(speech);
}

Параметры преобразования текста в речь

Голос

Начав с голоса, я хотел изменить голос Сони с дефолтного на более женский, напоминающий других помощников вроде Siri или Cortana. Для этого мне пришлось получить все доступные голоса с помощью метода getVoices() и выбрать один из них. Сначала я включил все голоса и предоставил пользователю выбирать, но потом подумал, что это не обязательно.

Обратите внимание, что согласно Web Speech API Errata (E11 2013–10–17), список голосов загружается на страницу асинхронно, и при их загрузке запускается событие onvoiceschanged. Так что я использовал это событие, чтобы установить голоса.

// wait on voices to be loaded before fetching list
window.speechSynthesis.onvoiceschanged = function() {
    voices = window.speechSynthesis.getVoices();
};

voiceschanged: запускается, когда содержимое списка SpeechSynthesisVoiceList, возвращаемого функцией getVoices(), изменилось. например,: синтез на стороне сервера, когда список определяется асинхронно, или когда голоса на стороне клиента (не)установлены.

Шаг, скорость и громкость

Другими параметрами, которые я использовал, являются высота, скорость и громкость, которые являются свойствами интерфейса SpeechSynthesisUtterance, которые получают и устанавливают высоту, скорость или громкость, с которыми будет произноситься высказывание.

Для этих параметров используются следующие значения:

pitch = 1,8: это число с плавающей запятой от 0 до 2, где 1 в настоящее время является шагом по умолчанию. Интуитивно понятно, что высота тона зависит от частоты звука. Высокочастотные звуки воспринимаются как высокие, а низкочастотные – как низкие. Поэтому я выбрал более высокий тон, потому что женщины говорят более высоким тоном, а у ассистента более женский голос.

скорость = 1: скорость речи — это просто скорость, с которой говорит помощник (слов, произносимых в минуту). Это число с плавающей запятой в диапазоне от 0,1 до 10, при этом 1 является ставкой по умолчанию. Значения от 1 до 1,5 соответствуют вполне нормальной скорости речи.

volume = 1: насколько громкий голос бота. Это число с плавающей запятой между 0 и 1.

Вот и все люди!!! У нас есть классный помощник всего в 100 строк кода. Код можно найти в этом репозитории GitHub, а демо можно посмотреть здесь.

Что дальше? Следуйте за мной на Medium, чтобы первыми читать мои истории.

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord . Заинтересованы в хакинге роста? Ознакомьтесь с разделом Схема.