슈퍼히어로 영화와 다른 공상과학 장르에 익숙한 사람이라면 디지털 조수가 보안 시스템을 해킹하는 모습과 스토리의 주인공에 대한 더 많은 모습을 알고 있을 것입니다. 하지만 제 빌드에서는 디지털 보조 음성 앱의 음성-텍스트 및 텍스트-음성 측면에 집중할 것입니다. 결국 얼마나 어려울 수 있겠습니까? 제 정신 건강이나 스트레스 수준에 대한 배려 없이 저는 음성 합성 기술의 심연에 머리를 먼저 담갔고, Javascript에서 이를 구현하는 방법을 Mendix.
"JavaScript는 사람들이 사용하기 전에 배울 필요가 없다고 생각하는 유일한 언어입니다."
— 더글러스 크록포드
아이디어
디자인은 간단합니다. 제가 만들 수 있는 한 가장 간단합니다. 아이디어는 Mendix 사용자의 말을 듣고 이해한 다음 자체 "음성"으로 응답할 수 있는 앱입니다. 쉬운 것 같죠? 디자인을 결정한 후, 사용할 수 있는 기존 기술을 찾아야 했습니다. IBM의 Watson과 Google의 Cloud AI Platform과 같이 이미 많은 음성 플랫폼이 출시되어 있는 상황에서 처음부터 다시 개발하는 것은 별 의미가 없습니다.
하지만 저는 작년에 채팅 봇을 만든 적이 있습니다. Mendix World, 저는 Jan de Vries와 함께 Low Code 라이브 빌드를 호스팅했습니다. 세션에서 저는 사용자가 Mendix 앱에서 Alexa에게 말을 걸면 됩니다. 이번에는 실제 대화와 대화에 덜 집중하고 이 빌드의 실제 말하는 단어 측면에 더 집중하기로 했습니다. 대화 트리를 만드는 방법에 대해 자세히 알아보려면 다음에서 녹화된 내용을 시청하는 것이 좋습니다. Mendix 세계 2020.
디자인
그럼 실제로 무엇을 만들까요? 조사를 좀 한 후 디자인을 정했고, 제 앱은 두 가지 핵심 측면에 초점을 맞출 것입니다.
- 사용자의 음성을 듣고 이해할 수 있는 음성-텍스트 변환 위젯입니다.
- 앱이 사용자에게 큰 소리로 응답할 수 있도록 해주는 텍스트 음성 변환 JavaScript 액션입니다.
위젯의 경우 내가 찾은 라이브러리를 사용할 것입니다. 깃허브, 사용하는 모질라의 음성 합성 라이브러리.
JavaScript 작업을 위해 편리하게 내가 발견한 JavaScript 튜토리얼 작성자 Mendix 정확히 이 내용은 우리 문서 내에서 다루겠습니다.
마지막으로, 영화에서 주인공은 항상 로봇 조수에게 멋진 이름을 붙입니다. 이를 기념하여 저는 앱에 다음과 같은 이름을 붙이기로 했습니다. 메이비스 이는 “Mendix's 굉장하고 훌륭하고 매우 지능적인 시스템"
MAEVIS 빌딩
일반적으로 앱을 빌드할 때 저는 가장 어렵거나 복잡한 프로세스에 먼저 집중하려고 합니다. 앱이 말을 하도록 하는 방법에 대한 대략적인 아이디어가 이미 있었기 때문에 MAEVIS가 말할 수 있도록 하는 위젯을 빌드하는 데 집중하기로 했습니다. 내 말을 들어. 위에서 언급했듯이 나는 이것을 사용하기로 결정했습니다. 도서관 by 닉발데즈 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);
}
}
이 위젯을 화면에 렌더링하는 것을 지금까지 내가 한 가장 위대한 개발 업적 중 하나로 생각합니다. 그리고 자바스크립트로 코딩하는 동안 "아하!" 순간을 진정으로 느낀 것은 이번이 처음입니다. 얼마나 걸렸는지 궁금한 사람들을 위해 말씀드리자면, 머리카락을 뽑고 노트북에 소리를 지르며 3일 정도 걸렸지만, 결국 저에게는 엄청난 보상이 있었습니다.
어려운 부분이 끝났으므로 간단히 이것을 따르는 것이 쉬운 작업이었습니다. 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);
});
// 최종 사용자 코드
}
이제 테스트할 시간입니다
그럼 더 이상 미루지 않고 내 작품을 여러분께 소개하고 싶습니다. 메이비스.
최대 포장
이 프로젝트에서 얼마나 많은 것을 배웠는지 강조하고 싶고, 도전적이기는 했지만 직접 시도해보는 것을 강력히 추천합니다. 이 빌드에 더 많은 것을 추가하고 싶었지만, 진행 중인 다른 정말 흥미로운 프로젝트가 있어서 여기서 마무리해야 합니다.
이상적으로는 위젯 자체가 Text to Speech JavaScript 동작을 트리거하는 Nanoflow를 트리거하기를 원했습니다. 그리고 위젯에서 대화를 다시 추출하기 위해 props를 활용하는 것이 좋을 것 같습니다. Mendix, 위젯의 상태에 그냥 저장하는 대신. 저는 이 기능들에 대해 계속 작업할 것이고, 나중에 이에 대한 후속 조치를 게시할 수도 있지만, 그때까지는 여러분 모두가 정확히 이것을 시도해보는 것이 좋은 연습이라고 생각합니다! 이것을 사용한다면 저에게 연락주세요. 저는 이것이 여러분에게 어떤 미친 아이디어를 주는지 보고 싶습니다. 그때까지는 기억하세요. Go Make it!