Как работать с JavaScript и Mendix

В разгар самоизоляции у меня было много свободного времени, и, как и многие люди, большую часть этого времени я проводил за просмотром сериалов и марафонами фильмов. Во время почти месячного киномарафона мне стало любопытно посмотреть, смогу ли я создать голосовое приложение в Mendix с использованием JavaScript.

Те, кто знаком с фильмами о супергероях и другими научно-фантастическими жанрами, будут знакомы с цифровыми помощниками, взламывающими системы безопасности, и многим другим для главного героя этой истории. Но для моей сборки я бы сосредоточился на аспектах преобразования речи в текст и преобразования текста в речь моего голосового приложения цифрового помощника. Я имею в виду, насколько тяжело это может быть в конце концов? Мало заботясь о собственном здравомыслии или уровне стресса, я с головой погрузился в бездну технологий синтеза речи и того, как их реализовать в Javascript и Mendix.

«JavaScript — единственный известный мне язык, который, по мнению людей, им не нужно изучать, прежде чем они начнут его использовать».

— Дуглас Крокфорд

Идея

Дизайн прост — настолько прост, насколько я мог его сделать. Идея состоит в том, чтобы создать приложение Mendix, которое может слышать и понимать произносимые пользователем слова, а затем отвечать своим собственным «голосом». Звучит легко, верно? Как только я определился с дизайном, пришло время искать любые существующие технологии, которые я мог бы использовать — нет особого смысла переделывать это с нуля, когда уже существует множество голосовых платформ, таких как IBM Watson и Google Cloud AI Platform.

Однако я создавал чат-ботов и раньше — на прошлогодней выставке Mendix World я организовал живую сборку Low Code с Яном де Врисом. В ходе сеанса я разработал навык Alexa, который позволял пользователю взаимодействовать с приложением Mendix, разговаривая с Alexa. На этот раз я решил меньше сосредотачиваться на реальной беседе и диалогах, а больше на разговорном аспекте этой сборки. Если вы хотите узнать больше о построении деревьев разговоров, рекомендую посмотреть запись Mendix World 2020.

Дизайн

Так что же я на самом деле собираюсь построить? После некоторых исследований я остановился на дизайне моего приложения, которое будет сосредоточено на двух основных аспектах:

  1. Подключаемый виджет преобразования речи в текст, который сможет слышать и понимать голос пользователя.
  2. Действие JavaScript для преобразования текста в речь, которое позволит приложению вслух отвечать пользователю.

Для виджета я бы использовал библиотеку, которую нашел на Github, которая использует библиотеки синтеза речи Mozilla.

Удобно для действия JavaScript, я наткнулся на учебник по JavaScript, созданный Mendix, который делает именно это в нашей собственной документации.

Наконец, в фильмах у героя всегда есть крутое имя для своего роботизированного бокового удара. В честь этого я решил назвать свое приложение MAEVIS, что означает «Потрясающая превосходная очень интеллектуальная система Mendix».

Здание МАЕВИС

Обычно при создании приложения я стараюсь сначала сосредоточиться на самых сложных или сложных процессах. Поскольку у меня уже было приблизительное представление о том, как заставить приложение говорить, я решил сосредоточиться на создании виджета, который позволил бы MAEVIS слышать меня. Как я упоминал выше, я решил использовать эту библиотеку от NikValdez на Github.

Я использовал генератор виджетов Mendix для создания своего каркаса виджетов. Я решил построить это с использованием JavaScript ES6, и он создан для веб-приложений и гибридных мобильных приложений.

Основная проблема, с которой я столкнулся при адаптации этого кода для работы в MAEVIS, заключалась в том, что в примере используется функциональный компонент, а каркас виджета генерирует код как компонент класса. Как только я понял проблему, решить ее было просто.

В итоге я получил это как окончательный код виджета:

import React,{ Component, createElement, useState, useEffect } from "react";
import "./ui/SpeechToText.css";
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const mic = new SpeechRecognition();
mic.continuous = true;
mic.interimResults = true;
mic.lang = 'en-US';
export default function SpeechToText(){
const [isListening, setIsListening] = useState(false);
const [note, setNote] = useState('');
const [savedNotes, setSavedNotes] = useState([]);
useEffect(() => {
handleListen()
}, [isListening]);
const handleListen = () => {
if (isListening) {
mic.start()
mic.onend = () => {
console.log('continue..')
mic.start()
}
} else {
mic.stop()
mic.onend = () => {
console.log('Stopped Mic on Click')
handleSaveNote()
}
}
mic.onstart = () => {
console.log('Mics on')
}
mic.onresult = event => {
const transcript = Array.from(event.results)
.map(result => result[0])
.map(result => result.transcript)
.join('')
console.log(transcript)
//textAttribute(transcript)
setNote(transcript)
mic.onerror = event => {
console.log(event.error)
}
}
}
const handleSaveNote = () => {
setSavedNotes([...savedNotes, note])
setNote('')
}
return  <span className="flexColumn">
<>
<p>{note}</p>
<button
className={isListening ? 'pulse-button btn-danger' : 'pulse-button'}
onClick={() => setIsListening(prevState => !prevState)}>
{isListening ? <span>🎙️Stop</span> : <span>🛑Start</span>}
</button>
</>
<>
<h2>Notes</h2>
{savedNotes.map(n => (
<p key={n}>{n}</p>
))}
</>
</span>;
}

Я также добавил несколько стилей для изменения интерфейса виджетов, чтобы они выглядели лучше, чем обычные кнопки на экране:

.flexColumn{
display: inline-flex;
flex-direction: column;
}
.container {
width: 200px;
height: 100%;
margin: 0 auto 0;
perspective: 1000;
-webkit-perspective: 1000;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
background: #fff;
}
.pulse-button {
position: relative;
margin: auto;
display: block;
width: 10em;
height: 10em;
font-size: 1.3em;
font-weight: light;
font-family: 'Trebuchet MS', sans-serif;
text-transform: uppercase;
text-align: center;
line-height: 100px;
letter-spacing: -1px;
color: white;
border: none;
border-radius: 50%;
background: #5a99d4;
cursor: pointer;
box-shadow: 0 0 0 0 rgba(90, 153, 212, 0.5);
-webkit-animation: pulse 1.5s infinite;
animation: pulse 1.5s infinite;
}
.pulse-button:hover {
-webkit-animation: none;
animation: none;
}
@-webkit-keyframes pulse {
0% {
-moz-transform: scale(0.9);
-ms-transform: scale(0.9);
-webkit-transform: scale(0.9);
transform: scale(0.9);
}
70% {
-moz-transform: scale(1);
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
box-shadow: 0 0 0 50px rgba(90, 153, 212, 0);
}
100% {
-moz-transform: scale(0.9);
-ms-transform: scale(0.9);
-webkit-transform: scale(0.9);
transform: scale(0.9);
box-shadow: 0 0 0 0 rgba(90, 153, 212, 0);
}
}
@keyframes pulse {
0% {
-moz-transform: scale(0.9);
-ms-transform: scale(0.9);
-webkit-transform: scale(0.9);
transform: scale(0.9);
}
70% {
-moz-transform: scale(1);
-ms-transform: scale(1);
-webkit-transform: scale(1);
transform: scale(1);
box-shadow: 0 0 0 50px rgba(90, 153, 212, 0);
}
100% {
-moz-transform: scale(0.9);
-ms-transform: scale(0.9);
-webkit-transform: scale(0.9);
transform: scale(0.9);
box-shadow: 0 0 0 0 rgba(90, 153, 212, 0);
}
}

Должен сказать, я считаю вывод этого виджета на экран одним из моих величайших достижений в области разработки на сегодняшний день, и впервые в жизни я действительно почувствовал это «АГА!» момент при кодировании в JavaScript. Для тех, кому интересно, сколько времени это заняло, скажу, что я три дня рвал на себе волосы и кричал на свой ноутбук, но в конце концов отдача для меня была огромной.

После того, как сложная часть была выполнена, было легко просто следовать этому учебнику по созданию действий JavaScript.

Всего через час или два у меня был экшен, который мог читать вслух любой текст, который я давал ему в качестве параметра. Для тех, кто просто ищет код, вот он, но я рекомендую следовать этому руководству, если вы не знакомы с действиями JavaScript в Mendix.

// This file was generated by Mendix Studio Pro.
//
// WARNING: Only the following code will be retained when actions are regenerated:
// - the import list
// - the code between BEGIN USER CODE and END USER CODE
// - the code between BEGIN EXTRA CODE and END EXTRA CODE
// Other code you write will be lost the next time you deploy the project.
import { Big } from "big.js";
// BEGIN EXTRA CODE
// END EXTRA CODE
/**
* @param {string} text
* @returns {Promise.<boolean>}
*/
export async function JS_TextToSpeech(text) {
// BEGIN USER CODE
if (!text) {
return false;
}
if ("speechSynthesis" in window === false) {
throw new Error("Browser does not support text to speech");
}
// const utterance = new SpeechSynthesisUtterance(text);
// window.speechSynthesis.speak(utterance);
// return true;
return new Promise(function(resolve, reject) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.onend = function() {
resolve(true);
};
utterance.onerror = function(event) {
reject("An error occured during playback: " + event.error);
};
window.speechSynthesis.speak(utterance);
});
// END USER CODE
}

Время проверить это

Итак, без лишних слов, я хотел бы познакомить вас со своим творением MAEVIS:

Подведение итогов

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

В идеале я хотел, чтобы сам виджет запускал Nanoflow, который запускает действие JavaScript Text to Speech. И я думаю, было бы здорово использовать props для извлечения диалога из виджета обратно в Mendix, вместо того, чтобы просто сохранять его в состоянии виджета. Я продолжу работу над этими функциями и, возможно, опубликую продолжение в будущем, но до тех пор я думаю, что это отличное упражнение для всех вас, чтобы попытаться сделать именно это! Пожалуйста, свяжитесь со мной, если вы используете это, я хотел бы увидеть, какие сумасшедшие идеи это дает всем вам. А пока помни — давай, сделай это!

Читать далее







От издателя —

Если вам понравилась эта статья, вы можете найти другие похожие на нашей странице Medium или на нашем собственном сайте блога сообщества.

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

Заинтересованы в более активном участии в нашем сообществе? Вы можете присоединиться к нам в нашем Канале сообщества Slack или, если вы хотите принять более активное участие, присоединиться к одной из наших Встреч.