一、入门指南
API 简介
“嘿 Alexa,几点了…?”
在一个智能助理(SA)设备的时代,我敢打赌,这些话在世界范围内每天都会被说上几十次——无论在哪里;智能助手变得非常受欢迎。事实上,Juniper Research 预测,智能助理的数量将从 2018 年底的 25 亿增加到 2023 年的 80 亿。想象一下——通过语音改变电视频道(已经成为可能,仅这一项就有望在未来五年内增长 120%)或者只是做一些平凡的事情,比如从亚马逊之类的网站重新订购商品。
但是我跑题了。智能助手很棒,但是如果我们可以用它们来控制我们在线网站或应用的功能会怎么样呢?“怎么会?”我听到你问了。好吧,我来介绍一下 HTML5 语音 API 它使用与智能助手相同的原理,将语音转换为文本,反之亦然。它现在可以在浏览器中使用,尽管还有些实验性。
最初创建于 2012 年,但现在才真正全面投入使用,这种实验性的 API 可以通过语音的力量来执行各种不同的任务。如何使用它来将产品添加到购物车并支付它们——所有这些都通过语音远程完成?加入语音功能为我们打开了一些真正的可能性。在本书的整个过程中,我们将详细探索其中的一些,向您展示我们如何很好地使用这个 API。在我们这样做之前,有一点家务我们必须先处理。在我们继续这个 API 的旅程之前,让我们先把这个覆盖掉。
如果你想深入了解这个 API 是如何构建的,以及浏览器厂商必须遵循的标准,那么看看 W3C 在 https://wicg.github.io/speech-api/
为这个 API 制定的指导方针。当心——这会导致枯燥无味的阅读!
设置我们的开发环境
我很确定没有人喜欢 admin,但是在这个例子中,在使用 API 之前,我们必须执行几个任务。
别担心,它们很简单。这是我们需要做的:
-
该 API 只能在安全的 HTTPS 环境中工作(是的,甚至不要尝试在 HTTP 下运行它——它不能工作——这意味着我们需要一些安全的网络空间来用于我们的演示。有几种方法可以实现这一点:
-
最简单的是使用 CodePen(
https://www.codepen.io
)——你将需要创建一个帐户来保存工作,但如果你还没有一个可以使用的帐户,注册是免费的。 -
你有其他项目可以临时使用的网络空间吗?只要它能在 HTTPS 的统治下得到保障,那么这将对我们的演示起作用。
-
如果您碰巧是使用 MS tech stack 的开发人员,您可以创建一个 ASP.Net 核心 web 应用,选择“为 HTTPS 配置”,并在运行该应用时,在提示信任自签名证书时单击“确定”。这将很好地适用于本书中的演示。
-
你可以试着运行一个本地的网络服务器——网上有很多。我个人最喜欢的是 MAMP PRO,可从
https://www.mamp.info
买到。这是一个在 Windows 和 Mac 上运行的付费选项;这使得生成我们需要使用的 SSL 证书变得轻而易举。或者,如果您安装了 Node.js 之类的程序,那么您可以使用一个本地 web 服务器(https://github.com/lwsjs/local-web-server
),或者创建自己的程序。您需要为它创建一个证书,并将其添加到您的证书库中——创建证书的简便方法在https://bit.ly/30RjAD0
中有所介绍。
-
-
下一个重要任务是准备一个合适的麦克风——毕竟,没有麦克风我们显然走不远!你可能已经有一个了;如果没有,几乎任何麦克风都可以正常工作。我个人倾向于使用麦克风/耳机组合,就像通过 Skype 通话一样。你应该可以通过亚马逊或者当地的音像店买到相对便宜的。
注意一点如果你是笔记本电脑用户,那么你可以使用笔记本电脑内置的任何麦克风。缺点是接收效果不会很好——你可能会发现自己不得不非常前倾才能获得最好的接收效果!
- 对于我们所有的演示,我们将使用一个中心项目文件夹——出于本书的目的,我将假设您已经创建了一个名为 speech 的文件夹,它存储在您的 C: drive 的根目录下。确切的位置并不重要;如果您选择了不同的位置,那么当我们来完成演示时,您需要相应地调整位置。
太棒了。现在没有了管理员,我们可以专注于有趣的事情了!HTML5 Speech API(或“API”)由两部分组成:第一部分是 SpeechSynthesis API ,它负责将任何给定的文本作为语音复述出来。第二,相比之下——套用一句话——演讲识别 API 做的和它名字里说的差不多。我们可以说出一个短语,如果它与预先配置好的文本相匹配,它就可以执行我们在收到该短语时分配的任何数量的任务。
我们可以深入了解它们是如何工作的,但我知道你渴望深入了解,对吗?绝对的。所以事不宜迟,让我们快速演示两次,这样在本书后面的项目中使用 API 之前,您就可以了解它是如何工作的。
不要担心这意味着什么——我们绝对会在每次练习后详细研究代码!我们将依次研究这两者,首先从 SpeechSynthesis API 开始。
实现我们的第一个示例
尽管这两种 API 都需要一点配置才能工作,但它们相对容易设置;两者都不需要使用任何特定的框架或外部库来进行基本操作。
为了理解我的意思,我用 CodePen 做了两个快速演示——它们演示了入门所需的基础知识,并将形成我们将在本书后面的项目中使用的代码。让我们依次看一下每一个,从使用 SpeechSynthesis API 将文本作为语音读回开始。
将文本作为语音回读
我们的第一个练习将保持简单,并使用 CodePen 来托管我们的代码;为此,如果您想保存您的工作以供将来参考,您需要创建一个帐户。如果你以前没有用过 CodePen,那么不要担心——它是免费注册的!这是开始使用 API 的好方法。在随后的演示中,我们将使用一些更具本地特色的东西。
本书示例中使用的所有代码都可以在本书附带的代码下载中找到。我们将在大多数演示中混合使用 ECMAScript 2015 和普通 JavaScript 如果您想使用 ECMAScript 的新版本,可能需要进行调整。
Reading Back Text
假设你已经注册了,现在有一个可以使用的 CodePen 帐户,让我们开始创建我们的第一个例子:
图 1-1
我们完整的文本到语音转换演示
-
首先,打开你的浏览器,然后导航到
https://codepen.io
,用你的账户信息登录。完成后,点击左边的笔来创建我们的演示。 -
我们需要为这个演示添加标记——为此,继续将以下代码添加到 HTML 窗口:
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet"> <div id="page-wrapper"> <h2>Introducing HTML5 Speech API: Reading Text back as Speech</h2> <p id="msg"></p> <input type="text" name="speech-msg" id="speech-msg"> <div class="option"> <label for="voice">Voice</label> <select name="voice" id="voice"></select> <button id="speak">Speak</button> </div> </div>
-
如果我们现在运行它,我们的演示将看起来非常普通——更不用说它实际上不会像预期的那样工作了!我们可以轻松解决这个问题。让我们首先添加一些基本的风格,使我们的演示更像样。有几个样式要添加进去,所以我们将一个块一个块地做。在每个块之间留一条线,当你把它加入到演示中:
*, *:before, *:after { box-sizing: border-box; } html { font-family: 'Open Sans', sans-serif; font-size: 100%; } #page-wrapper { width: 640px; background: #ffffff; padding: 16px; margin: 32px auto; border-top: 5px solid #9d9d9d; box-shadow: 0 2px 10px rgba(0,0,0,0.8); } h2 { margin-top: 0; }
-
我们需要添加一些样式来表明我们的浏览器是否支持 API:
#msg { font-size: 14px; line-height: 22px; } #msg.not-supported strong { color: #cc0000; } #msg > span { font-size: 24px; vertical-align: bottom; } #msg > span.ok { color: #00ff00; } #msg > span.notok { color: #ff0000; }
-
接下来是声音下拉菜单的样式:
#voice { margin: 0 70px 0 -70px; vertical-align: super; }
-
对于 API 来说,我们需要有一种输入文本的方法来将它转换成语音。为此,添加以下样式规则:
input[type="text"] { width: 100%; padding: 8px; font-size: 19px; border-radius: 3px; border: 1px solid #d9d9d9; box-shadow: 0 2px 3px rgba(0,0,0,0.1) inset; } label { display: inline-block; float: left; width: 150px; } .option { margin: 16px 0; }
-
样式的最后一个元素是演示右下角的 Speak 按钮:
button { display: inline-block; border-radius: 3px; border: none; font-size: 14px; padding: 8px 12px; background: #dcdcdc; border-bottom: 2px solid #9d9d9d; color: #000000; -webkit-font-smoothing: antialiased; font-weight: bold; margin: 0; width: 20%; text-align: center; } button:hover, button:focus { opacity: 0.75; cursor: pointer; } button:active { opacity: 1; box-shadow: 0 -3px 10px rgba(0, 0, 0, 0.1) inset; }
-
有了合适的样式,我们现在可以把注意力转移到添加胶水上了。我不是指字面上的意思,而是比喻意义上的!我们需要添加的所有代码都在 CodePen 的 JS 窗口中;我们首先检查我们的浏览器是否支持 API:
var supportMsg = document.getElementById('msg'); if ('speechSynthesis' in window) { supportMsg.innerHTML = '<span class="ok">☑</span> Your browser <strong>supports</strong> speech synthesis.'; } else { supportMsg.innerHTML = '<span class="notok">☒</span> Sorry your browser <strong>does not support</strong> speech synthesis.'; supportMsg.classList.add('not-supported'); }
-
接下来,我们定义三个变量来存储对演示中元素的引用:
var button = document.getElementById('speak'); var speechMsgInput = document.getElementById('speech-msg'); var voiceSelect = document.getElementById('voice');
-
当使用 API 时,我们可以使用各种不同的声音来回放语音——我们需要在使用它们之前将它们加载到我们的演示中。为此,继续添加以下几行:
```html
function loadVoices() {
var voices = speechSynthesis.getVoices();
voices.forEach(function(voice, i) {
var option = document.createElement('option');
option.value = voice.name;
option.innerHTML = voice.name;
voiceSelect.appendChild(option);
});
}
loadVoices();
window.speechSynthesis.onvoiceschanged = function(e) {
loadVoices();
};
```
- 我们开始演示的真正内容——这是我们看到我们添加的文本被转换成语音的地方!为此,在前一个块之后留下一行,并添加以下代码:
```html
function speak(text) {
var msg = new SpeechSynthesisUtterance();
msg.text = text;
if (voiceSelect.value) {
msg.voice = speechSynthesis.getVoices()
.filter(function(voice) {
return voice.name == voiceSelect.value;
})[0];
}
window.speechSynthesis.speak(msg);
}
```
- 我们快到了。最后一步是添加一个事件处理程序,当我们点击 Speak 按钮时,这个事件处理程序触发从文本到语音的转换:
```html
button.addEventListener('click', function(e) {
if (speechMsgInput.value.length > 0) {
speak(speechMsgInput.value);
}
});
```
- 继续保存您的工作。如果一切正常,我们应该会看到类似于图 1-1 所示的截图。
试着输入一些文本,然后点击语音按钮。如果一切按预期进行,那么你会听到有人向你复述你的话。如果你从下拉列表中选择一个声音,你会听到你的话带着口音说回来;根据你输入的内容,你会得到一些非常有趣的结果!
这个演示的完整版本可以在本书附带的代码下载中找到——它在readingback
文件夹中。
在这个阶段,我们现在已经有了基本的设置,允许我们的浏览器读回我们想要的任何文本——当然这听起来可能还是有点机械。然而,当使用一个仍然是实验性的 API 时,这是可以预料的!
除此之外,我敢打赌你的脑海中有两个问题:这个 API 是如何工作的?更重要的是,即使它在技术上仍然是一个非官方的 API,它仍然可以安全使用吗?不要担心——这些问题以及更多问题的答案将在本章稍后揭晓。让我们首先更详细地探讨一下我们的演示是如何工作的。
了解发生了什么
如果我们仔细看看我们的代码,你可能会觉得它看起来有点复杂——但实际上,它非常简单。
我们从一些简单的 HTML 标记和样式开始,在屏幕上显示一个输入框,用于播放内容。我们还有一个下拉菜单,我们将使用它来列出可用的声音。真正神奇的事情发生在我们使用的脚本中——首先执行检查,看看我们的浏览器是否支持 API,并显示合适的消息。
假设您的浏览器支持 API(过去 3-4 年的大多数浏览器都支持),那么我们为页面上的各种元素定义一些占位符变量。然后,在用结果填充下拉菜单之前,我们(通过loadVoices()
函数)遍历可用的声音。特别值得注意的是对loadVoices()
的第二次调用;这是必要的,因为 Chrome 异步加载它们。
值得注意的是,额外的声音(以“Chrome…”开头)是作为与谷歌交互的 API 的一部分添加的,因此只出现在 Chrome 中。
如果我们跳到演示的结尾,我们可以看到按钮元素的事件处理程序;这将调用speak()
函数,该函数创建一个新的SpeechSynthesisUtterance()
对象的发声,作为一个发言请求。然后它会检查以确保我们选择了一种声音,这是通过使用speechSynthesis.getVoices()
功能完成的。如果选择了一种声音,那么 API 会将该声音排队,并通过 PC 的扬声器以音频的形式呈现出来。
好吧,我们继续。我们已经探索了如何将文本呈现为语音的基础。然而这只是故事的一半。如何将口头内容转换成文本?我们可以通过使用 SpeechRecognition API 来做到这一点——这需要更多一点的努力,所以让我们进入两个演示中的第二个,看看让我们的笔记本电脑说话涉及到什么。
将语音转换为文本
通过我们电脑的扬声器(甚至是耳机)来表达内容的能力当然是有用的,但是有一点局限性。如果我们可以让浏览器使用我们的声音来执行一些事情,会怎么样?我们可以使用两个语音 API 中的第二个来实现。我来介绍一下 SpeechRecognition API!
这个姊妹 API 允许我们对着任何连接到 PC 的麦克风说话,让我们的浏览器执行任何方式的预配置任务,从简单的转录任务到搜索离您给定位置最近的餐馆。我们将在本书的后面探索如何在项目中使用这个 API 的一些例子,但是现在,让我们实现一个简单的演示,这样您就可以看到 API 是如何工作的。
当处理使用语音识别 API 的演示时,我不推荐使用 Firefox 尽管 Mozilla Developer Network (MDN)站点上的文档表明它是受支持的,但事实并非如此,您很可能会在控制台日志中出现“speech recognition is not a constructor”错误。
“What Did I Say?”
让我们继续下一个练习:
-
我们首先浏览到
https://www.codepen.io
,然后点击笔。确保您已经使用在第一个练习中创建的帐户登录。 -
我们的演示使用了字体 Awesome 作为麦克风图标,您将很快看到它的使用——为此,我们需要添加两个 CSS 库的引用。继续并点击设置➤ CSS。然后在对话框底部的备用槽中添加以下链接:
https://use.fontawesome.com/releases/v5.0.8/css/fontawesome.css https://use.fontawesome.com/releases/v5.0.8/css/solid.css
-
接下来,切换到 HTML 窗格,添加以下标记,这将构成我们演示的基础:
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet"> <div id="page-wrapper"> <h2>Introducing HTML5 Speech API: Converting Speech to Text</h2> <button> <i class="fa fa-microphone"></i> Click and talk to me! </button> <div class="response"> <span class="output_log"></span> </div> <p class="output">You said: <strong class="output_result"></strong></p> <span class="voice">Spoken voice: US English</span> </div>
-
就其本身而言,我们的标记肯定不会赢得任何风格方面的奖项!为了解决这个问题,我们需要添加一些样式,使我们的演示看起来像样。为此,将以下规则添加到 CSS 窗格中,从一些基本规则开始,为我们的演示设置容器的样式:
*, *:before, *:after { box-sizing: border-box; } html { font-family: 'Open Sans', sans-serif; font-size: 100%; } #page-wrapper { width: 640px; background: #ffffff; padding: 16px; margin: 32px auto; border-top: 5px solid #9d9d9d; box-shadow: 0 2px 10px rgba(0,0,0,0.8); } h2 { margin-top: 0; }
-
接下来是我们需要设计通话按钮的规则:
button { color: #0000000; background: #dcdcdc; border-radius: 6px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); font-size: 19px; padding: 8px 16px; margin-right: 15px; } button:focus { outline: 0; } input[type=text] { border-radius: 6px; font-size: 19px; padding: 8px; box-shadow: inset 0 0 5px #666; width: 300px; margin-bottom: 8px; }
-
我们的下一个规则利用字体 Awesome 在通话按钮上显示一个合适的麦克风图标:
.fa-microphone:before { content: "\f130"; }
-
一旦输出被转录,下一组规则将对输出进行样式化,以及置信度和所使用的声音特征:
.output_log { font-family: monospace; font-size: 24px; color: #999; display: inline-block; } .output { height: 50px; font-size: 19px; color: #000000; margin-top: 30px; } .response { padding-left: 260px; margin-top: -35px; height: 50px} .voice { float: right; margin-top: -20px; }
-
好了,我们有了标记,看起来还不错。少了什么?啊,是的,让这一切工作的脚本!为此,继续将以下代码添加到 JS 窗格中。我们有一大块代码,所以让我们从一些变量声明开始,一个块一个块地分解它:
'use strict'; const log = document.querySelector('.output_log'); const output = document.querySelector('.output_result'); const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const recognition = new SpeechRecognition(); recognition.interimResults = true; recognition.maxAlternatives = 1;
-
接下来是触发麦克风的事件处理程序。留下一个空行,然后添加以下代码:
document.querySelector('button').addEventListener('click', () => { let recogLang = 'en-US'; recognition.lang = recogLang.value; recognition.start(); });
-
当使用语音识别 API 时,我们触发了许多我们必须响应的事件;第一个识别我们何时开始说话。继续将下面几行添加到我们的 CodePen 演示的 JS 窗格中:
```html
recognition.addEventListener('speechstart', () => {
log.textContent = 'Speech has been detected.';
});
```
- 留下一个空行,然后添加这些行——这个事件处理程序负责识别和记录我们对麦克风说的任何话,并计算准确性的置信水平:
```html
recognition.addEventListener('result', (e) => {
log.textContent = 'Result has been detected.';
let last = e.results.length - 1;
let text = e.results[last][0].transcript;
output.textContent = text;
log.textContent = 'Confidence: ' + (e.results[0][0].confidence * 100).toFixed(2) + "%";
});
```
- 我们差不多完成了,但是还需要添加两个事件处理程序——它们负责在我们完成时关闭识别 API,并在屏幕上显示任何可能出现的错误。留下一行然后放入下面的代码:
```html
recognition.addEventListener('speechend', () => {
recognition.stop();
});
recognition.addEventListener('error', (e) => {
output.textContent = 'Error: ' + e.error;
});
```
- 至此,我们完成了代码编辑。继续并点击保存按钮来保存我们的工作。
这个演示的完整版本可以在本书附带的代码下载中找到——它在whatdidIsay
文件夹中。
在这一点上,我们应该可以运行我们的演示了,但是如果您这样做,很可能您不会得到任何响应。怎么会这样原因很简单,我们必须在浏览器中授权使用我们电脑的麦克风。可以通过网站证书细节中的设置条目来激活它,但这不是最干净的方法。有一种更好的方法来提示访问,我将在下一个练习中演示。
允许使用麦克风
使用 Speech API 时,有一件事我们必须记住——出于安全原因,默认情况下对麦克风的访问是禁用的;在使用它之前,我们必须明确地启用它。
这很容易做到,尽管具体步骤会因浏览器而异——它涉及到在我们的演示中添加几行代码来请求访问麦克风,并根据提示更改设置。我们将在下一个练习中看到如何做到这一点,假设您使用 Chrome 作为浏览器。
Adjusting Permissions
让我们开始设置权限:
-
点击它。确保选择了“总是允许
https://codepen.io
……”选项。然后单击完成。 -
刷新窗口。图标将变为纯黑色,不显示禁止的十字符号。
-
首先,在 Chrome 中浏览麦克风设置,你可以通过
chrome://settings/content/microphone
进入。确保“访问前询问…”的滑块位于右侧。 -
在单独的选项卡中,切换回您在上一个练习中创建的 CodePen 中的 SpeechRecognition API 演示。寻找这一行:
const output = document.querySelector('.output_result');
-
在它下面留一行空白,然后加入这个代码:
navigator.mediaDevices.getUserMedia({ audio: true }).then(function(stream) {
-
向下滚动代码,直到到达末尾。然后添加这一小块代码:
}) .catch(function(err) { console.log(err); });
-
接下来,单击 JS 窗格最右边的下拉箭头。当它弹出的时候,你会看到一个关于 Tidy JS 的条目。单击它可以正确地重新格式化代码。
-
Save the update and then refresh the page. If all is well, you will see an icon appear at the end of the address bar (Figure 1-2).
图 1-2
麦克风支持已被添加,但被禁用…
尝试点击“点击和我说话!”按钮,然后对着麦克风说话。如果一切正常,我们应该会看到类似于图 1-3 所示的屏幕截图,其中显示了口语测试短语的结果,以及置信度。
图 1-3
对着我们的麦克风说话的结果…
交谈时,您是否注意到红点/圆圈是如何出现在浏览器窗口选项卡中的(如背页图 1-4 所示)?这表示麦克风处于活动状态,将录制任何语音。
图 1-4
红点表示活跃的麦克风
如果我们点击“点击和我说话!”按钮,这个红色的圆形图标将会消失,表示麦克风已经关闭。在我们之前的演示中,我们利用了navigator.mediaDevices.getUserMedia()
来实现这一点——这是我们在任何实现语音的站点都必须做的事情,因为我们不能确定用户是否已经启用了他们的麦克风!
如果你想了解更多关于使用navigator.mediaDevices.getUserMedia()
的知识,在 Mozilla 开发者网站 https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
上有一篇有用的文章。
设置访问:一种替代方法
然而(就像生活中的许多事情一样),有一种不同的方式来破解这个难题;它不需要代码,但是它不是一个干净的方法。这涉及到像我们以前一样设置正确的权限,但这次是去一个我们知道可以使用麦克风的站点。
Enabling the Microphone: An Alternative Method
这种方法假设使用 Chrome,尽管 Firefox 和其他浏览器也可能使用类似的方法:
图 1-5
从浏览器请求访问麦克风
-
在单独的选项卡中,浏览到
chrome://settings/content/siteDetails?site=https%3A%2F%2Fcodepen.io
,然后确保麦克风的条目设置为“询问”。 -
返回到运行 CodePen 演示的选项卡,并刷新窗口。您应该会看到一个提示,如图 1-5 所示。
在最后几页中,我们做了三个练习。重要的是要注意,当使用这个 API 时,需要做一些额外的工作。除了发起对 API 的请求之外,我们还必须添加代码来启用对麦克风的访问。稍后我们将再次讨论使用它的主题(和安全含义),但是现在,让我们更详细地回顾一下我们在前两个练习中使用的代码。
打破我们的代码
和前面的文本到语音转换演示一样,我们从一些基本的标记和样式开始,给我们一个按钮,我们可以用它来激活记录我们的声音,以及两个占位符槽,用于转换后的文本和置信度。
然而,神奇之处在于我们添加的 JavaScript 代码。我们首先在标记中定义对.output_
元素的引用。接下来,我们定义window.SpeechRecognition``as a reference to the API
;请注意,我们将它设置为 OR 语句,以确保涵盖那些仍然需要供应商前缀支持的浏览器。作为其中的一部分,我们还设置了两个属性:recognition.interimResults
设置为 true,以便在从语音转换成文本时显示文本。另一个是recognition.maxAlternatives
,设置为 1,当语音识别服务识别出它时,最多显示一个备选单词。
值得注意的是,我们的大部分 JavaScript 代码将被封装在一个navigator.mediaDevices.getUserMedia()
块中,所以一旦我们启用了对麦克风的访问,它就会运行。
然后,我们的代码包含一组事件处理程序,以识别不同的事件:第一个事件是通过单击“单击并和我说话!”按钮。这将设置要使用的语言(美国英语)并启动识别服务。第二个事件处理程序speechstart
,负责识别我们何时开始说话,并记录任何说话的内容。最后两个(result
和error
)在我们停止讲话或出现错误时被触发,比如对麦克风的访问被阻止。在这个扩展演示的最后部分,我们将探索启用麦克风的几个选项;我们讨论代码路径如何对用户更好。
好吧,我们继续。现在我们已经了解了这两个 API,是时候深入研究一些理论了,看看这些 API 是如何工作的!我们将在下一章更详细地检查每个 API,但是现在,让我们回答两个关键问题:这些 API 支持得如何(我能提供后备支持吗)?如何管理远程访问他人麦克风的安全隐患?
允许浏览器支持
回想一下本章的开头——还记得我提到的“实验 API”这个词吗?是的,不得不说,这些 API 还没有达到官方的地位。然而,在你跑到山上思考“我让自己进来是为了什么?”,没有听起来那么糟糕!让我解释一下我的意思。
诚然,该 API 仍处于试验阶段——我们可以通过仔细的研究来考虑这一点,并在我们只是增强现有服务而不是取代它的基础上工作。首先,我们的第一站应该是像 CanIUse.com 这样的网站;快速检查表明,SpeechSynthesis API 具有出色的支持,至少在桌面上是如此(图 1-6 )。
图 1-6
语音合成 API 的浏览器支持
相比之下,对语音识别 API 的支持就不那么先进了,如图 1-7 所示。
图 1-7
浏览器对语音识别 API 的支持
来源:cani use . com/# search = speech
我们可以清楚地看到,对语音合成 API 的支持没有那么先进,但是图表隐藏了一个秘密:Safari 确实支持这两种 API!虽然像 CanIUse.com 这样的网站是一个很好的起点,但它的准确性取决于它所基于的信息。尽可能多地检查每个浏览器供应商的支持确实是值得的;否则,我们可能会将未来的财务信息建立在不准确的信息上。
现在,我听到你问,“移动呢?”对这两种 API 的支持仍在开发中;虽然它尚未扩展到所有平台,但它涵盖了 Android(Chrome 和 Firefox)和三星的主要平台。
既然我们知道了每个浏览器提供的支持级别,那么那些不支持任何一种 API 的浏览器呢?有没有后备方案或我们可以使用的其他替代方案…?
提供后备支持
在讨论结束时,这两个问题的答案并不像我们希望的那样简单。让我解释一下我的意思。
这个问题的核心在于重要的一点——语音合成 API 依赖于使用谷歌的神经人工智能能力来解码文本,并以选定的风格将文本作为语音返回。那么这对我们意味着什么呢?对谷歌合成 API 的依赖意味着支持将仅限于较新的浏览器;这涵盖了除 IE 之外的所有最新的桌面浏览器。对于那些支持移动设备的人来说,它只适用于 Android 的 Chrome 或 Firefox,三星互联网和两个较小的专业浏览器。
目前,从严格意义上来说,确实没有合适的退路。虽然对一些人来说这可能令人失望,但有一种观点认为,在浏览器支持方面,我们应该向前看,而不是向后看。IE 不支持语音合成 API 对很多人来说并不奇怪;那些不支持该 API 的移动平台(如 Android 浏览器)加起来占总使用量的 5%左右,因此这可以放心地打折扣。同样有一种观点认为,我们不应该依赖 API 来获得我们站点或应用的核心功能;语音提供应该增强基本服务,而不是取代它。
如果我们切换到语音识别 API,支持就是另一回事了——支持仍然处于初级阶段。它仅限于桌面支持的 Edge、Firefox 和 Chrome 的最新版本;移动世界的大部分支持都落在了 Android 平台的 Chrome 上。同样的观点也适用于展望未来;诸如语音之类的 API 应该被看作是一种逐渐增强体验的工具。
谈到渐进式改进,我们可以考虑几个选项。这些是
-
responsive voice–这是一项商业服务,可从
https://responsivevoice.com
获得;它提供了额外的支持,如更好的导航可访问性,但这需要每月 39 美元的价格,这将需要考虑到任何运营成本。 -
Annyang 是 Tal Ater 的免费库,旨在让语音识别 API 更容易使用;这是在麻省理工学院许可下从
https://www.talater.com/annyang/
获得的。
但是这些方法的缺点是它们只能逐步增强已经支持 API 的浏览器所提供的服务;这为我们应该鼓励人们尽可能使用更新的浏览器的观点增加了额外的份量!
了解安全问题
在本章的过程中,我们已经第一次了解了 Speech API,并了解了它的基本工作原理。然而,我确信(和任何新技术一样)有一个迫切的问题我们还没有问:安全和隐私呢?随着现在生效的全欧洲范围的 GDPR 立法的存在,隐私问题已经成为突出的问题;这并不比使用语音 API 更重要。
主要考虑的是在使用语音 API 时获得使用麦克风的许可;过去,每当在不安全的 HTTP 环境中发出请求时,都会出现这种情况。曾几何时,这是不必要的,但可疑的网站开始利用广告和诈骗。因此,谷歌(现在还有其他公司)强制要求在 HTTPS 安全的环境中使用 API,并且使用麦克风的许可必须由用户明确给出。
如果您想了解这方面的技术原因,详细信息请参见关于此漏洞的官方错误报告,该报告列在 https://bugs.chromium.org/p/chromium/issues/detail?id=812767
作为一个用户,在一个完全安全的网站上,在音频被捕获之前,你可能只被要求一次许可;在页面刷新之前,同一会话中的后续使用将使用相同的权限。对于一些人来说,这可能被视为一个漏洞,因为一个安全的网页可以有效地记录任何内容,一旦它被授权。这是由于 Chrome API 与谷歌交互的事实,所以不会停留在你的浏览器范围内!
那么,我们能做些什么来帮助维护我们的安全和隐私呢?使用 API 时,我们需要记住几件事:
-
尽管 Chrome 中使用语音识别 API 的任何页面都可以与谷歌进行交互,但发送给谷歌的唯一信息是音频记录、网站的域、默认浏览器语言和网站的当前语言设置(不发送 cookies)。如果在不同的浏览器中使用语音识别 API,它不会与 Google 交互。
-
如果您正在使用语音识别 API,请确保您没有创建任何包含敏感信息的事件处理程序,这些信息可能会被发送到 Google。理想情况下,这些信息应该存储在本地,发送的任何命令实际上都是打开访问的钥匙。
-
整个页面都可以访问音频捕获的输出,因此,如果您的页面或站点受到威胁,可以读取音频实例中的数据。这使得我们有责任确保访问安全(这已经成为许多网站的默认设置),同时也确保我们在适当安全和更新的服务器上使用高质量的证书。
-
API(尤其是语音识别 API)仍然处于不断变化的状态;谷歌的角色有可能在未来某个时候发生变化或被终止。在 W3C 正式认可在浏览器中使用 API 之前,任何东西都不能被认为是官方的。
-
此时,我建议仔细检查你网站的分析,探索哪些浏览器支持这个 API。如果有足够的需求,那么你可以考虑开始添加功能,但正如前面提到的,我强烈建议采取谨慎和有分寸的方法,以便为客户保持良好的体验。
好吧,确实有些值得思考的东西!希望这不会让你分心;与任何新技术一样,拥抱它很重要,但要采取有分寸的方法,而不是盲目地投入!在本书的整个过程中,我们将更详细地挖掘 API,并在许多示例项目中使用它,这样您就能感受到它在实际环境中的用法。想象一下:使用 API 将产品添加到购物车中并为其付款,而这一切都用您的声音来完成,怎么样?
摘要
在现代智能助手(如亚马逊的 Alexa)的时代,创建可以使用语音控制的网络应用的能力开辟了一些真正有趣的可能性。我们同样必须考虑如何最好地利用 API,尤其是在用户最关心隐私的时候!在本章的过程中,我们已经开始详细了解语音 API 让我们花点时间更详细地回顾一下我们所学的内容。
我们首先介绍了语音合成和识别 API,然后快速看一下开始使用这些 API 进行开发需要什么。
然后,我们继续实现我们的第一个例子——我们从读回文本作为语音开始,然后切换到使用语音识别 API 创建一个例子。然后,我们简要讨论了如何为这两个 API 中的第二个启用对麦克风的访问,然后探讨了在使用 API 时提供支持以及考虑隐私和安全的一些问题。
唷!伙计们,我们才刚刚开始。希望你已经准备好真正投入到细节中!接下来,我们将更详细地了解 API,同时创建一个更实用的示例,并探索如何为不同的语言提供更多的支持。就像有人用荷兰语说的那样,Laten we doorgaan,或者让我们继续干吧!
二、更详细地探索 API
理解 API 术语
“太好了!我的电脑现在可以说话并识别我的声音。但是我在代码中看到的 SpeechSynthesisUtterance 关键字是什么意思…?”
这是个很好的问题。既然您已经看到了运行中的 API,我敢打赌您一定很想了解它是如何结合在一起的,对吗?我们只是触及了让我们的电脑说话或识别我们声音的基础。我们能做的还有很多!
在本章的课程中,我们将在使用它(或它们——取决于你如何看待它)之前,深入研究 API 背后的一些理论,以创建一些更实用的东西。与此同时,我们还将赋予我们的代码一点国际风味——是的,我们不局限于只说英语!在本章的后面,一切将变得更清楚,但是现在,让我们从把语音识别 API 分解成它的组成部分开始。
探索语音合成 API
回头看看我们为第一个演示创建的代码,我们的 PC 将一些示例文本作为语音回放。乍一看,似乎我们需要相当多的代码来实现这一点,对吗?如果我说你只用一行代码就能做到这一点,会怎么样?
是的,你没听错。该演示的关键围绕这一行代码:
window.speechSynthesis.speak(msg);
在这里我们调用对speechSynthesis
的调用,并要求它说出msg
的值。就其本身而言,这是行不通的,但是如果我们稍微改变一下,变成这样:
speechSynthesis.speak(new SpeechSynthesisUtterance('Hello, my name is [insert your name here]'))
当在浏览器控制台中执行时,它会工作得很好(如果你使用 Firefox,你可能需要允许在控制台中粘贴)。继续,把你的名字放进去,试一试!然而,除了这个简单的一行程序之外,我们还可以用 API 做更多的事情。我在代码中看到的这个SpeechSynthesisUtterance()
或者对getVoices()
的调用是怎么回事?一个是对象,另一个是方法。让我们更深入地了解一下这个 API 是如何工作的。
分解 API
语音识别 API 的核心是语音合成接口;这是我们进入语音服务的界面。我们可以使用许多方法来控制活动,但在此之前,我们必须首先定义SpeechSynthesisUtterance
对象。这个对象代表一个语音请求,我们向其中传递一个字符串,浏览器应该大声读出:
const utterance = new SpeechSynthesisUtterance('Hey')
一旦定义好了,我们就可以用它来调整单个的语音属性,比如表 2-1 中列出的那些,更完整的列表在本书后面的附录中。
表 2-1
SpeechSynthesisUtterance 对象的属性
|财产
|
目的
|
| — | — |
| 话语速率 | 设置速度,接受[0.1 和 10]之间的值,默认为 1。 |
| 话语.音调 | 设置间距,接受[0 和 2]之间的值,默认为 1。 |
| 话语量 | 设置音量,接受[0 和 1]之间的值,默认为 1。 |
| 话语.郎 | 设置语言(值使用当前最佳实践 47 [BCP47]语言标记,如 en-US 或 it-IT)。 |
| 话语.文本 | 您可以将它作为属性传递,而不是在构造函数中设置它。文本最多可包含 32767 个字符。 |
| 话语声音: | 设置声音(下面会详细介绍)。 |
如果我们把这些放在一个简单的例子中,我们可以在一个控制台会话中运行(不要忘记按照指示添加我们的名字!),它看起来会像这样:
const utterance = new SpeechSynthesisUtterance('Hey, my name is [insert your name here]')
utterance.pitch = 1.5
utterance.volume = 0.5
utterance.rate = 8
speechSynthesis.speak(utterance)
然后我们可以使用speak(), pause(), resume(),
或cancel()
方法来控制SpeechSynthesis
对象。
在我们的下一个练习中,我们将充分利用这个额外的功能,并扩展我们从第一章开始的原始演示,以包括对返回的语音提供更好控制的选项,作为我们的下一个演示。当我们完成后,我们的演示将看起来像图 2-1 所示的截图。
图 2-1
我们更新的语音合成演示,增加了控件
我们要做的改变相对来说比较简单,但是很好的说明了我们如何开始开发我们的原创。让我们更详细地了解一下需要什么。
改进我们的演讲合成演示
在我们的下一个练习中,我们将添加三个滑块来控制音量、音高和速率等级别,以及暂停和继续朗读内容的按钮。
Adding Functionality
让我们开始添加演示所需的额外标记:
本演示所需的所有代码都在本书附带的代码下载中的updating speechsynthesis
文件夹中。
-
我们将从浏览回你在 CodePen 中创建的演示开始,回到第一章——在那里,确保你登录,这样我们可以保存对演示的更改。
-
首先,寻找这段代码:
<div class="option"> <label for="voice">Voice</label> <select name="voice" id="voice"></select> <button id="speak">Speak</button> </div>
-
紧接在这个块的下面(并且在关闭页面包装器
<div>
之前),插入下面的代码——这增加了音量、速率和音调水平的滑块:<div class="option"> <label for="volume">Volume</label> <input type="range" min="0" max="1" step="0.1" name="volume" id="volume" value="1"> </div> <div class="option"> <label for="rate">Rate</label> <input type="range" min="0.1" max="10" step="0.1" name="rate" id="rate" value="1"> </div> <div class="option"> <label for="pitch">Pitch</label> <input type="range" min="0" max="2" step="0.1" name="pitch" id="pitch" value="1"> </div>
-
接下来,查找这一行代码,并将其从标记中的当前位置删除:
<button id="speak">Speak</button>
-
向下滚动到标记的末尾,在紧接结束的
</div>
之前添加以下三行,突出显示:<button id="speak">Speak</button> <button id="pause">Pause</button> <button id="resume">Resume</button> </div>
-
标记就绪后,我们需要对样式进行一些调整;否则,元素将无法在页面上正确显示。为此,继续注释掉或删除
#voice
样式规则中突出显示的代码行:#voice { /*margin-left: -70px;*/ margin-right: 70px; vertical-align: super; }
-
我们添加的范围滑块也需要调整。继续将它添加到
input[type="text"]
规则的下面,在该规则之后留一个空行:input[type="range"] { width: 300px; }
-
是时候添加 JavaScript 代码,为我们的新按钮和范围控件注入活力了。查找
button
变量声明,然后添加下面突出显示的代码:var button = document.getElementById('speak'); var pause = document.getElementById('pause'); var resume = document.getElementById('resume');
-
接下来,留下一个空行,然后添加以下声明——这些是我们用来调整音量、速率和音高的每个范围滑块的缓存引用:
// Get the attribute controls. var volumeInput = document.getElementById('volume'); var rateInput = document.getElementById('rate'); var pitchInput = document.getElementById('pitch');
-
向下滚动,直到看到
onvoiceschanged
事件处理程序。然后在其下方留下一个空行,并添加这个新的错误处理程序:
```html
window.speechSynthesis.onerror = function(event) {
console.log('Speech recognition error detected: ' + event.error);
console.log('Additional information: ' + event.message);
}
```
- 下一个块是
speak()
函数——在里面,寻找msg.text = text
,然后留下一个空行,并添加这些赋值:
```html
// Set the attributes.
msg.volume = parseFloat(volumeInput.value);
msg.rate = parseFloat(rateInput.value);
msg.pitch = parseFloat(pitchInput.value);
```
- 我们快完成了。滚动到 JS 代码部分的末尾,然后留下一个空行,并添加这两个事件处理程序。第一个负责暂停语音内容:
```html
// Set up an event listener for when the 'pause' button is clicked.
pause.addEventListener('click', function(e) {
if (speechMsgInput.value.length > 0 && speechSynthesis.speaking) {
speechSynthesis.pause();
}
});
```
- 当单击 resume 按钮时,第二个事件处理程序被触发——为此,在前一个处理程序后留一个空行,并添加以下代码:
```html
// Set up an event listener for when the 'resume' button is clicked.
resume.addEventListener('click', function(e) {
if (speechSynthesis.paused) {
speechSynthesis.resume();
}
});
```
- 我们已经完成了代码添加。请确保保存您的工作。如果一切正常,我们应该会看到类似于本练习开始时显示的屏幕截图。
尝试运行演示程序,在文本框中添加一些内容,然后改变控件。稍加练习,就能产生一些有趣的效果!我们的代码现在开始成形,并给了我们可以使用的更完整的东西。让我们更详细地快速回顾一下我们对代码所做的更改。
剖析我们的代码
我们通过添加一些标记来创建合适的范围滑块来控制音量、音高和速率设置——在所有情况下,我们都使用标准的输入元素并将它们标记为 HTML5 范围类型。随后,我们添加了两个新按钮,用于暂停和恢复语音内容。
真正的奇迹出现在我们加入剧本的时候。我们首先添加对我们创建的两个新按钮的引用;这些分别被分配了 idpause
和resume,
。
接下来,我们创建了对三个范围滑块的引用;这些分别被称为volumeInput
、rateInput,
和pitchInput
、??。然后,我们在speak()
函数中添加声明,以捕捉为这些范围滑块设置的值,然后根据需要将它们分配给SpeechSynthesisUtterance
对象。然后,我们添加了三个新的事件处理程序来结束演示——第一个用于将生成的任何错误呈现到控制台,第二个用于在计算机说话时暂停内容,第三个用于在用户单击恢复按钮时恢复内容。
这很简单,对吧?这只是我们可以做出的改变的一部分。妹子 API 呢,语音识别?正如我们很快会看到的,这一个需要一个不同的思维定势来做出改变。让我们更详细地看看我们可以做出的一些改变,以增强整体体验。
探索语音识别 API
我们已经探索了如何让浏览器说话,但是如何识别我们说的话呢?在我们在第一章中创建的演示中,我们遇到了诸如navigator.mediaDevices.getUserMedia()
、speechstart
事件处理程序和recognition.interimResults
这样的术语。他们都是做什么的?
第一个严格来说不是 SpeechRecognition API 的一部分;我们用它来控制从浏览器中对麦克风的访问。然而,另外两个确实是 API 的一部分;与 SpeechSynthesis API 不同,这不是一个我们可以在控制台中作为一行程序运行的 API。相反,在使用这个 API 时,我们需要指定一些设置——最关键的一点是在我们做任何事情之前允许访问麦克风!
分解 API
SpeechRecognition API 的核心是 SpeechRecognition 接口;这控制对浏览器中语音识别界面的访问。我们首先必须定义对此的引用;一旦就位,我们就可以使用这行代码创建 API 接口的实例:
const recognition = new SpeechRecognition();
值得注意的是,在 Chrome 中,这个 API 利用一个基于远程服务器的识别引擎来处理所有请求。这意味着它不能离线工作——为此,我们必须使用不同的浏览器,比如 Firefox。
然后,我们可以为设置指定合适的值,如interimResults
或maxAlternatives
,以及合适的事件处理程序来停止或启动语音服务。让我们更详细地看看其中的一些设置;这些在表 2-2 中列出。
表 2-2
SpeechRecognition API 的属性
|财产
|
财产用途
|
| — | — |
| 演讲认知。语法 | 返回并设置 SpeechGrammar 对象的集合,这些对象代表 SpeechRecognition API 的当前实例可以理解的语法。 |
| 演讲识别。lang | 返回并设置当前演讲人识别的语言。如果未指定,则默认为 HTML lang 属性值,或者用户代理的语言设置(如果也未设置)。 |
| 演讲识别。连续 | 控制是为每个识别返回连续的结果,还是只返回一个结果。默认为 single(或 false)。 |
| 演讲认知. interim 结果 | 控制是否应该返回中期结果(true)或不返回(false)。临时结果是尚未最终确定的结果(例如,speechrecognitionresult . is final 属性为 false)。 |
| speech recognition . max alternatives | 设置每个结果提供的最大备选项数。默认值为 1。 |
| SpeechRecognition.serviceURI | 指定当前语音识别用来处理实际识别的语音识别服务的位置。默认值是用户代理的默认语音服务。 |
一旦我们为SpeechRecognition
对象定义了我们选择的设置,我们就可以使用三种方法来控制它。我们可以start()
服务,stop()
它,或者abort()
引用一个当前的SpeechRecognition
对象,就像我们在本章前面所做的语音合成演示一样。
本书末尾的附录中提供了 API 命令的完整列表。
然而,与语音合成 API 不同,以完全相同的方式定制体验的选项并不多;尽管如此,我们仍然可以实现一些改变来改善体验。记住这一点,让我们来看看我们可以增加我们的原始演示。
更新我们的演讲识别演示
当使用 SpeechSynthesis API 演示时,我们能够从 API 中添加一些额外的属性来帮助微调用户体验;SpeechRecognition API 的情况并非如此。相反,我们将采取不同的策略;我们将添加一些额外的错误管理功能和更好的控制来使用navigator.mediaDevices.getUserMedia()
自动关闭麦克风。
Expanding the Options
出于本练习的目的,我们将在更小的块中检查每个变更;任何视觉变化的屏幕截图将在适当的时候显示。
本演示的代码可以在本书附带的代码下载中找到——在updating speechrecognition
文件夹中查找。
让我们开始吧:
-
首先,在 CodePen 网站上浏览到您在第一章中创建的语音识别——确保您也登录了,这样您就可以保存您的更改。
-
接下来,查找这一行代码,并在它下面添加以下内容(突出显示),在新代码后面留下一行:
recognition.interimResults = true; recognition.maxAlternatives = 1; recognition.continuous = true;
-
我们要实现的第一个变化是开始改进错误处理——目前,我们正在逐字逐句地排除错误消息,这看起来不太好。通过一些修改,我们可以使它更友好,所以继续修改错误事件处理程序,如下所示:
recognition.addEventListener("error", e => { if (e.error == "no-speech") { output.textContent = "Error: no speech detected"; } else { output.textContent = "Error: " + e.error; } });
值得注意的是,如果以后需要,我们可以用其他错误代码来扩展它。
图 2-3
我们更新的语音识别演示
-
第三个也是最后一个变化是对麦克风的关闭施加更多的控制——有时我们可能想要控制它何时关闭,而不是让它看起来有自己的想法!幸运的是,对此的更改非常简单——首先是在我们的 HTML 标记中添加一个元素,如下所示:
<p class="output">You said: <strong class="output_result"> </strong></p> <button id="micoff">Turn off</button>
-
第二个变化需要我们添加一个新的事件处理程序——我们可以控制何时关闭麦克风,而不是依赖语音识别 API 自动关闭或试图转录它听到的不是故意的语音。为此,查找这行代码:
recognition.continuous = true
然后留下一个空行,放入下面的代码:
document.getElementById("micoff").addEventListener("click", () => { stream.getTracks().forEach(function(track) { track.stop() }); console.log("off"); });
-
我们已经做了所有需要的改变。继续保存您的工作成果。如果一切正常,我们应该会看到类似于图 2-3 所示的截图,在这里我们可以看到我们改进的错误处理。
-
The second change will be an auto turn-off for the speech recognition engine – after all, we don’t necessarily want our microphone to stay enabled if we’re not using it for a period of time, right? For this change, look for the
speechend
event handler, then leave a blank line, and add in this function:recognition.onspeechend = function() { log.textContent = 'You were quiet for a while so voice recognition turned itself off.'; stream.getTracks().forEach(function(track) { track.stop() }); console.log("off"); }
We can see the result of this change in Figure 2-2.
图 2-2
增加了自动关闭功能
如果您尝试单击“关闭”按钮关闭麦克风,请耐心等待,红色指示灯可能需要几秒钟才会消失!
在研究这个演示的代码时,我对使用语音识别 API 时可以触发的事件的明显交叉数量感到震惊。
正是由于这个原因,尽管我们没有像语音合成 API 那样多的配置选项可以调整,但语音识别 API 中不同的事件处理程序仍然会使我们出错!记住这一点,让我们更详细地看看我们刚刚完成的演示中的代码。
理解代码
在过去的几页中,我们采用了不同的方法来开发我们的原始演示,这一次,我们通过增加或改善整体体验来扩展它,而不是简单地添加更多选项。让我们花点时间更详细地回顾一下我们对原始演示所做的更改。
我们从更新错误事件处理程序开始,在那里我们检查了no-speech
错误属性,并向用户返回了一个更容易接受的消息。我们的下一个变化实现了一个自动关闭选项——在使用语音识别 API 时,我们必须记住一点(我们不希望它在没有控制的情况下运行!)
我们做的最后一个改变是改变自动关闭功能——自动关闭是一个有用的功能,但有时我们可能希望控制这种情况何时发生。这是特别有用的,有助于防止我们的麦克风自动录制东西,这是不应该共享的!
好了,我们该继续前进了。我们已经探索了如何实现语音识别和合成 API 的基础知识;是时候让它们有更实际的用途了!为了展示我们如何将它们结合在一起,我们将创建一个简单的视频播放器,可以通过语音控制;它会用声音确认我们要求的任何动作,而不是在屏幕上显示消息。这将使用我们创建的两个演示中的原则。让我们深入研究一下,看看这是如何工作的。
创造一个更实际的例子
在下一个练习中,我们将为使用 HTML5 <video>
元素的视频播放器添加基本的语音功能。
我们现在将着重于添加播放和暂停命令,但是我们可以在以后很容易地添加额外的命令,例如增加音量或静音。完成后,看起来会像图 2-4 所示的截图。
图 2-4
我们的声控视频播放器
在您设置演示时,仔细看看一些函数和处理程序——希望您能从以前的演示中认出一些元素!记住这一点,让我们开始我们的演示。
Adding Speech Capabilities to Video
下一个演示有几个要求:你需要确保你有一个合适的视频可用(MP4 格式可以;代码下载里有个例子视频如果没有合适的)。我们将建立一个 CodePen 会话,因此在继续之前,请确保您已经浏览了位于 https://codepen.io
的网站并已登录:
-
我们将开始添加我们需要作为演示基础的标记——为此,继续将
HTML.txt
文件的内容从practical example
文件夹复制到右侧的 HTML 窗格中。 -
接下来,让我们添加一些基本的样式,这样我们至少可以让我们的演示看起来像模像样——为此,继续将
CSS.txt
文件的内容添加到 CSS 窗格中。 -
我们现在可以把注意力转向真正重要的部分——我们的剧本!有一个很好的块要添加,所以我们将一个块一个块地做,从一些变量声明开始。继续在 JS 窗格的顶部添加以下代码行:
"use strict"; const log = document.querySelector(".output_log"); const output = document.querySelector(".output"); const confidence = document.querySelector(".confidence em"); // Simple function that checks existence of s in str var userSaid = function(str, s) { return str.indexOf(s) > -1; };
-
下一个代码块负责将我们选择的视频加载到我们的视频播放器中——这样我们就可以在发出命令时准备好播放。留下一个空行,然后添加以下代码:
video_file.onchange = function() { var files = this.files; var file = URL.createObjectURL(files[0]); video_player.src = file; };
-
接下来的部分有点棘手——我们需要允许用户请求访问他们的麦克风。为此,我们使用
navigator.mediaDevices.getUserMedia
;我们将首先添加这个结构。留下一个空行,然后添加这个方法调用:navigator.mediaDevices .getUserMedia({ audio: true }) .then(function(stream) { ...add in code here... }).catch(function(err) { console.log(err); });
-
有了这些,我们现在可以开始添加操作语音识别 API 所需的各种组件;我们首先需要定义 API 的一个实例。继续添加,替换文本
...add in code here...
:const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const recognition = new SpeechRecognition();
-
接下来是我们的第一个事件处理程序,我们将使用它来调用对麦克风的访问。为此,在 SpeechRecognition API 声明后留下一行,然后添加以下代码:
document.querySelector("button").addEventListener("click", () => { let recoglang = "en-US"; recognition.lang = recoglang; recognition.continuous = true; recognition.start(); });
-
我们现在需要添加事件处理程序,负责在我们开始说话时打开 API,或者在适当的时候关闭它;在前一个处理程序后留出一行空白,然后添加以下代码:
recognition.addEventListener("speechstart", e => { log.textContent = "Speech has been detected."; }); recognition.addEventListener("speechend", e => { recognition.stop(); }); recognition.onspeechend = function() { log.textContent = "You were quiet for a while so voice recognition turned itself off."; stream.getTracks().forEach(function(track) { track.stop(); }); console.log("off"); };
-
下一个模块是真正神奇的地方——它通过将我们的口头命令转换成它能识别的东西并翻译成适当的命令来控制我们的视频播放器。在前一个事件处理程序后留下一行,然后放入以下代码:
// Process the results when they are returned from the recogniser recognition.onresult = function(e) { // Check each result starting from the last one for (var i = e.resultIndex; i < e.results.length; ++i) { var str = e.results[i][0].transcript; console.log("Recognised: " + str); // If the user said 'video' then parse it further if (request(str, "video")) { // Play the video if (request(str, "play")) { video_player.play(); log.innerHTML = "playing video..."; } else if (request(str, "pause")) { // Stop the video video_player.pause(); log.innerHTML = "video paused..."; } } } confidence.textContent = (e.results[0][0].confidence * 100).toFixed(2) + "%"; };
-
我们快完成了。要添加的最后一个事件处理程序将负责一些基本的错误捕获。继续添加这个事件处理程序,在前一个块之后留出一行空白:
```html
recognition.addEventListener("error", e => {
if (e.error == "no-speech") {
log.textContent = "Error: no speech detected";
} else {
log.textContent = "Error: " + e.error;
}
});
```
- 保存您的工作。如果一切正常,我们应该会看到类似于本练习开始时显示的屏幕截图。
尝试使用“选择文件”按钮选择一个视频,然后说“视频播放”开始播放——是的,这有点小花招,但它确实提供了一个有效的观点。谁说你总是要按下按钮才能启动一个东西?是的,我绝对会认为自己是一个老派方法的粉丝,但总有一天,一个人必须适应…!
如果你仔细看看这个演示中的代码,你会发现它使用了我们在早期演示中已经见过的术语;总的来说,大部分现在应该开始看起来熟悉了!但是有一个例外——我没有看到结果的事件处理程序;我看到的这个.onresult
句柄是什么…?
详细研究代码
啊哈!对于我们跟踪 API 最终输出的方式来说,这是一个重要的改变!它的工作方式与我们之前使用的结果事件处理程序类似,但是有一点不同:该事件处理程序只被调用一次。我将很快解释我的意思,但是首先,让我们更详细地检查我们的代码。我将把重点放在 JavaScript 上,因为使用的 CSS 和 HTML 是非常标准的,应该是不言自明的。
我们从声明一些变量开始,这些变量用于存储对标记中元素的引用,并帮助在我们的口头输入中查找文本(稍后将详细介绍)。然后,我们继续创建一个基本函数,将我们选择的视频加载到我们的视频播放器中,准备根据命令播放它。
下一个模块是我们演示真正关键的开始——在声明 API 实例之前(取决于我们使用的浏览器),我们初始化对navigator.getUserMedia()
的调用以允许访问我们的麦克风。
然后,我们添加了一个事件处理程序,用各种属性初始化我们的 API 实例——lang 被设置为 US English 和 continuous,以防止 API 在打开之前关闭过快。接下来出现了三个事件处理程序来响应语音——speechstart
在 API 检测到语音内容时启动,speechend
将终止它,onspeechend
将识别 API 是否安静并自动关闭。
我们演示的真正重点是接下来——这里我们使用了onresult
。这与我们之前使用的 result 事件处理程序不同,它不会触发一次(result 会),而是在每次我们说话并且 API 检测到我们停止说话时触发。我应该指出,这是而不是完全停止说话,更多的是我们发出的每个命令之间的停顿!该函数使用 for 循环解析结果,将每个结果依次分配给 str,然后根据听到的内容执行适当的视频命令。因此,如果我们说“播放视频”,它会单独搜索每个单词。根据它听到的内容,它会检测到我们说了视频,因此它会检查我们是否说了播放或暂停。如果我们说播放(正如我们在这里所做的),它会暂停视频并在屏幕上显示确认。
好吧,让我们开始吧!尽管我们到目前为止做的许多演示可能都是英文的,但有一件事我们应该牢记在心:对不同语言的支持呢?在一个全球互联的时代,我们不能假设人们只会说英语(或者实际上只会一种语言!).我们绝对应该考虑增加对不同语言的支持;值得庆幸的是,使用语音 API 时,我们可以轻松做到这一点。让我们深入了解一下我们需要做些什么来更详细地添加多语言支持。
走向多语言
在一个理想的世界里,如果我们都说同一种语言就太好了——毕竟,我们可以和不同国家的人交流,不会有误解……但是那会很无聊!
用不同的语言和别人说话是有道理的;拥抱不同的文化和语言为假期或旅行增添了额外的元素。这同样适用于阅读文本,例如在博物馆里找到的文本;当然,你可能不太懂,但你仍然会对这个国家过去的历史有所了解。
但是我跑题了。回到现实,我们已经讨论了如何将语音转换成文本,反之亦然。那么其他语言呢?我们并不都说英语(或者实际上是同一种语言),那么这在两种 API 中是如何工作的呢?
探索对语言的支持
使用语音识别或语音合成 API 的好处之一是它支持其他语言——有许多不同的选项可供我们使用。正如我们很快会看到的,确切的数字将取决于我们使用的浏览器;这可能多达 21 个,也可能少到只有 3 个!
我们已经在第一章创建的语音合成演示中提到了语言支持——还记得那个演示中显示的相当有趣的名字列表吗?我们可以在图 2-5 中看到它的摘录。
图 2-5
可用于语音合成 API 的语言(摘录)
为了实现这一点,我们创建了一个loadVoices()
函数来遍历每个语言选项,然后将它添加到下拉菜单中。然后,在将更改应用到 SpeechRecognition 对象之前,我们使用了getVoices()
方法来选择我们选择的语言。
如果您想知道我们是如何做到这一点的,请尝试在您的浏览器控制台中运行这个简单的示例——我建议您在一个 CodePen 演示的控制台日志中运行它,这样您就可以触发对麦克风的访问:
console.log(`Voices #: {speechSynthesis.getVoices().length}`);
speechSynthesis.getVoices().forEach(voice => {
console.log(voice.name, voice.lang)
});
在这一点上,值得注意的是,这应该在大多数现代浏览器中工作。你可能会发现,尽管你在 SpeechSynthesis API 中使用这段代码时会遇到跨浏览器的问题——在 Chrome 的一些旧版本中,我们使用的原始代码无法运行。
它在 Firefox 和 Edge 中运行良好(对于那些 Mac 用户来说,可能还有 Safari);相反,在使用之前,您可能会发现必须使用回调来显示列表。获取声音以显示列表:
const voiceschanged = () => {
console.log(`Voices #: ${speechSynthesis.getVoices().length}`);
speechSynthesis.getVoices().forEach(voice => {
console.log(voice.name, voice.lang)
})
}
speechSynthesis.onvoiceschanged = voiceschanged
不过你可能会发现,使用 Chrome 时返回的语言数量有所不同——以“Google…”开头的额外语言只有在网络连接有效的情况下才可用。在图 2-6 中完整显示了该列表的副本。
图 2-6
API 中支持的语言列表
否则会减少;Edge 目前显示三个,Firefox 显示两个。可以说,这只是使用语音 API 时需要考虑的另一点!
相比之下,在语音合成 API 中添加语言支持变得更加有趣——我们不仅可以选择语言,甚至可以设置方言!这确实需要更多的工作来实现。在下一个练习中,我们将很快看到如何实现这一点。
设置自定义语言
如果我们在使用语音识别 API 时需要设置语言支持,我们必须采取不同的策略——不是简单地从 API 调用列表,而是向提供一个列表。这实际上采取了双数组的形式。这解释起来有点复杂,所以请原谅我;我将使用下一个练习中的摘录。
我们从数组开始,注意到不是所有的条目都相等吗?好吧,在你说话之前,我指的是数字,不是里面的文字!在大多数情况下,我们只有语言和 BCP47 代码(比如 af-ZA),但是在最后一个例子中,我们有三个值:
var langs =
[['Afrikaans', ['af-ZA']],
['Bahasa Indonesia',['id-ID']],
['Bahasa Melayu', ['ms-MY']],
['Català', ['ca-ES']],
['Čeština', ['cs-CZ']],
['Deutsch', ['de-DE']],
['English', ['en-AU', 'Australia'],
...
(abridged for brevity)
BCP47 或最佳当前实践 47 是用于识别人类语言的 IEFT 国际标准,例如德语的 de-DE。如果你想了解更多,那就去维基百科的 https://en.wikipedia.org/wiki/IETF_language_tag
找一篇好的介绍文章。
然后,我们使用这样的结构遍历数组:
for (var i = 0; i < langs.length; i++) {
select_language.options[i] = new Option(langs[i][0], i);
}
将它放入一个对象中,我们可以从中选择默认显示的项目(在本例中为英语):
select_language.selectedIndex = 6;
这本身不会对 API 产生任何影响;为了让它工作,我们需要增加一个功能。在高层次上,我们再次遍历数组,但是这一次挑选出方言值(在这种情况下,来自第二列的值),然后将它们添加到一个<select>
下拉框中。然后,我们需要设置可见性,这样,如果我们选择一种有多种方言的语言,方言下拉列表就会相应地显示或隐藏。希望这将开始有一些意义;要了解这在实践中是如何工作的,让我们快速进入下一个练习,在这里我们将看到这段代码如何融入我们的演示中。
Allowing for Languages in Speech Recognition
在下一个练习中,我们需要回到我们在第一章中创建的语音合成演示——为了保留您之前代码的副本,我建议您登录 CodePen 并点击 Fork 按钮。我们的演示将从您准备好编辑 HTML 代码开始,因此在继续本演示中的步骤之前,请确保您已经到了这一步。
尽管我们将要做的一些改变很简单,但其他的更复杂。我建议你一定要为这本书下载一份代码副本;所有内容都将保存在language support
文件夹中。
假设您在那里,让我们开始更新我们的演示:
图 2-7
对我们的演示说法语的结果
-
第一个变化确实出现在我们的 HTML 标记中,所以请查找这一行并将其注释掉:
<span class="voice">Spoken voice: US English</span>
-
接下来,将这个块直接插入到它的下面:
<span class="voice"> Spoken voice and dialect: <div id="div_language"> <select id="select_language" onchange="updateCountry()"> </select> <select id="select_dialect"></select> </div> </span>
-
我们现在需要调整新的下拉列表的位置——为此,在 CSS 窗格的底部添加下面的 CSS 样式:
.voice { float: right; margin-top: -20px; }
-
但是真正的变化是在我们的 JavaScript 代码中(当然!)–为此,继续从代码下载中打开一个
JS.txt
文件的副本,然后查找这行代码:var langs =
(大约在第 7 行)。 -
复制它和下面的行,直到(包括)这一行后面的右括号:
select_dialect.style.visibility = list[1].length == 1 ? 'hidden' : 'visible'; }
-
将代码下载中的 JavaScript 内容粘贴到这一行之后,在它和您的新块之间留一行:
const output = document.querySelector(".output_result");
-
好的,下一个变化:向下滚动直到你看到这个事件处理程序的开始:
document.querySelector("button").addEventListener("click", ()
-
注释掉该函数中的
let recoglang = "en-US"
,替换为:recognition.lang = select_dialect.value;
-
向下滚动,直到看到这一行:
output.textContent = text;
-
接下来,添加一个空行,然后放入这行代码,在右括号和圆括号之前:
```html
log.textContent = "Confidence: " + (e.results[0][0].confidence * 100).toFixed(2) + "%";
```
-
此时,我们应该已经完成了所有的代码更改;继续保存您的工作。
-
尝试运行演示。如果一切正常,我们应该有类似于图 2-7 所示的截图。试着把声音换成不同的语言,然后说点什么——希望你或你的朋友能知道足够多的单词来说些有意义的话!
从演示中可以看出,这说明我确实懂一些法语;我的西班牙语也还过得去,虽然和我的水平相差甚远!除此之外,我们还在这个演示中添加了一个非常重要的特性,值得我们更详细地了解一下——让我们花点时间来更详细地了解一下它是如何工作的。
打破我们的代码
如果我们仔细看看我们刚刚编写的代码,您可能会发现一个奇怪的地方——如果您没有发现,也不要担心,因为它不会立即显现出来!我会给你一个开始的线索:它与我们赋予语言的价值有关——它不是你最初可能期望的那样…
好吧,我跑题了。回到我们的演示,我们首先注释掉原始文本,指出使用了哪种语言;我们用两个下拉菜单替换了它,一个用于语言,另一个用于方言。然后我们引入大量代码,首先建立一个数组langs
,存储语言和方言值。接下来我们用一个for
循环遍历第一组值,并将每个值插入到select_language
下拉列表中。然后,我们为语言和方言属性设置了两个默认值——在本例中是英语。
接下来是updateCountry()
函数——它看起来有点复杂,但不难理解。我们只是清除了方言下拉列表(select_dialect
),然后用第二列数据中的值填充它(在本例中,我们有前面谈到的 BCP47 值)。
剩下的变化很小——我们将来自select_dialect
下拉列表的输出值重新分配给recognition.lang
,并在output_log
span 元素中添加了一个置信度声明。有道理?嗯,会的,如果只有一个困扰的问题。为什么我们看起来像是在设置方言值,而不是语言值…?
语言和方言的区别
如果我把这一部分的主题作为一个问题问你,希望你会说语言是我们会说的东西,方言实际上是那种语言的地区变体…或者类似的东西!然而,如果我说,至少在这个 API 的上下文中,这两者实际上是同一个事物的两个部分,并且在某些情况下是相同的,你会感到非常困惑!什么给了…?
答案就在五个字里——bcp 47。这是我之前提到的国际标准,我们可以看到 pt-BR 或葡萄牙语的巴西方言等代码。但是真正的诀窍在于我们如何在代码中利用这一点——尽管我们选择了语言和方言(后者可用),但直到我们选择了那个方言值,我们才得到实际使用的值。
例如,如果我们选择葡萄牙语方言,我们将得到 pt-BR;这是 lang 属性进行语音识别所需的值。实际上,在通过方言下拉列表选择真正的语言用于我们的演示之前,我们使用语言下拉列表来过滤我们的选择。
好吧,我们继续。在我们进入构建项目的实际乐趣之前,我们还需要探索一个特性!正如我希望你已经从演示中看到的,语音识别发展得很好,但它并不完美。可能有些时候,我们会想伸出援助之手。我给你介绍一下SpeechRecognition.Grammars
。
利用语法对象
在本章的整个过程中,我们已经更详细地探索了语音 API,涵盖了诸如添加多语言支持、对何时可以使用麦克风提供更好的控制,以及当我们在使用 API 时遇到错误时细化返回的内容等功能。
然而,可能有些情况下我们需要这种帮助——这就是 SpeechRecognition API 的语法部分可以发挥作用的地方。然而,这个特性有点奇怪,而且可能会带来一些麻烦。为什么?
许多人发现它最多令人困惑,或者实际上没有做他们原本期望它做的事情。一部分原因可能是因为最初的规范是什么时候写的;这是在单词识别率没有现在这么好的时候完成的,所以它需要一些东西来提供可以被描述为提升的东西。
因此,对SpeechGrammarList
界面的支持很差——目前只有 Chrome 支持。它还利用了 JSpeech 语法格式(或 JSGF ),这种格式已经从大多数浏览器中删除了。因此,除非绝对必要,否则我不建议使用该功能,并且请注意,使用该功能需要您自担风险,并且它很可能会在未来被删除。
如果你想了解技术细节和关于移除提议的讨论,请访问 W3C GitHub 网站 https://github.com/w3c/speech-api/pull/57
和 https://github.com/w3c/speech-api/pull/58
。
摘要
当使用语音 API 时,我们可以使用许多选项;当我们在第一章第一次介绍 API 时,我们已经介绍了其中的一些。在本章的课程中,我们在我们所学的基础上增加了额外的选项。让我们花点时间回顾一下我们所学的内容。
我们通过创建演示来更详细地探索语音合成和语音识别 APIs 在向每个演示添加功能之前,我们首先介绍了每个 API 中可用的更多选项。
接着,我们看了看如何在使用 API 时添加多语言支持。我们探讨了每个 API 背后的基本原则以及如何设置自定义语言。紧接着是一个演示,然后探索设置语言和方言属性之间的区别,以及两者如何相互作用以给出我们想要的语言设置。
然后我们看了一下语音语法界面,结束了这一章。我们讨论了如何使用它,但是有计划在将来放弃对它的支持;我们讨论了为什么会出现这种情况的一些原因,以及它在实践中如何影响或不影响您的代码。
唷!涵盖了很多,是吧?嗯,节奏不会慢下来——事情会变得越来越有趣!在接下来的几章中,我们将实现一些示例项目来说明如何在实际环境中使用 API。这将涵盖从留下口头审查反馈到自动化部分或全部购买过程的任何事情;我们真的只是被我们的想象力所限制!首先,我们将从一些相对较新的网站开始。例如,使用 API 开发聊天机器人怎么样?翻到下一页,了解我们如何开始与您的网站进行适当的对话…
三、支持移动设备
“Juniper Research 预测,智能助理的数量将从 2018 年底的 25 亿增加到 2023 年的 80 亿,增长两倍。”
还记得第一章第一章开头的那段令人震惊的话吗?考虑到移动使用现在已经超过了桌面,这是一个强大的组合!但是——我听到你说:“这两个事实的意义是什么?”好吧,让我全部透露。
到目前为止,在前面的章节中,你可能已经注意到使用桌面作为我们的环境。这本身没有错,但它忽略了一个关键点:使用移动设备怎么样?鉴于越来越多的人使用智能设备购买产品,那么在使用 Web 语音 API 时考虑移动设备是绝对有意义的。
在本章的课程中,我们将看一下我们在前面章节中创建的一些演示,并探索如何在移动设备上使用它们。根据您目前所看到的,您可能认为这不应该是一个问题,因为大多数最新的浏览器都支持桌面上的 API,对吗?嗯,事情并不像看上去的那样——做好做决定的准备。
支持语音合成 API
是的,最后一个评论可能看起来有点有趣,但是我们将做出一些决定,关于我们如何在移动环境中使用 API!让我解释一下我的意思,首先从语音合成(图 3-1 )开始,说明在更流行的移动平台上对 API 的支持程度。
图 3-1
支持语音合成 API 来源:CanIUse.com
哎哟!这看起来不如台式机好,对吧?授予的覆盖范围不像标准桌面用户那样广泛,但是由于有太多不同的可用平台,支持不那么一致也就不足为奇了!然而,这并不像看起来那么糟糕——要理解为什么取决于我们对一个关键问题做出有意识的决定:我们希望在多大程度上支持谷歌浏览器?
分解数字
为了理解最后一个问题的答案,我们应该首先看看谁支持这个 API 以及这个浏览器的当前使用情况。表 3-1 显示了从图 3-1 中呈现的信息的更详细版本,其中我们可以看到哪些更流行的浏览器支持该 API。
表 3-1
支持移动设备上的语音合成 API
|移动浏览器
|
支持?
|
截至 2019 年 12 月的使用百分比
|
| — | — | — |
| iOS 浏览器 | 是 | Two point eight nine |
| 迷你歌剧 | 不 | One point one seven |
| 安卓浏览器 | 不 | Zero |
| 歌剧手机 | 不 | Zero point zero one |
| 安卓版 Chrome 浏览器 | 是 | Thirty-five point one six |
| 安卓火狐 | 是 | Zero point two three |
| 适用于 Android 的 UC 浏览器 | 不 | Two point eight eight |
| 三星互联网 | 是 | Two point seven three |
| 手机 QQ 浏览器 | 是 | Zero point two |
| 百度浏览器 | 不 | Zero |
| KaiOS 浏览器 | 是 | Zero point two |
显而易见,谷歌 Chrome 的使用率远远超过了所有其他浏览器的总和,几乎是 3 比 1!因此,它提出了我们应该支持谁的问题,特别是对于任何最低可行产品(或 MVP)。
由于所有其他浏览器制造商都不支持移动设备上的 API,或者该浏览器的使用率远低于 5%,所以专注于 Chrome 是有意义的。要真正把重点放在家里(好像这是必要的!),我们可以看到图 3-2 中使用了多少 Chrome。
图 3-2
截至 2019 年 12 月的 Chrome 使用情况来源:CanIUse.com
削减对如此多浏览器的支持似乎有些过激,但在当今世界,我们需要务实:我们有资源或时间为所有不同的浏览器开发吗?对 Chrome 的支持远远超过其他浏览器,因此专注于这款浏览器并仅在收入足够大以保证部署资源的情况下包括其他浏览器是有商业意义的(例如对于非常大的客户)。
支持语音识别 API
我们已经探索了对语音合成 API 的支持。它和它的姐妹——语音识别 API 相比如何?
嗯,乍一看,支持并不是那么好——在某些方面,这并不是一个真正的冲击,因为这个 API 比语音合成 API 更复杂,所以支持没有那个 API 那么先进。我们可以在背面的图 3-3 中看到流行移动平台的概要。
图 3-3
支持移动设备上的语音识别 API 来源:CanIUse.com
乍一看,主要的区别是对这个 API 的任何支持还没有达到完全批准的状态(而另一个 API 已经达到了);这只是意味着我们在使用这个 API 时需要使用前缀-webkit
。我们很快就会看到,这没什么大不了的;然而真正的问题在于使用浏览器的人数!为了理解我的意思,让我们深入下去,更详细地看看这些数字,就像我们对语音合成 API 所做的那样。
理解数字
如果我们看看谁支持语音识别 API 的细节,我们会看到使用每个浏览器的人数和以前一样多。不过这一次,每个浏览器对 API 的支持比支持语音合成 API 的少 25%(允许使用供应商前缀,并且一个浏览器需要手动启用)。我们可以看到表 3-2 中列出的结果。
表 3-2
支持移动设备上的语音识别 API
|移动浏览器
|
支持?
|
截至 2019 年 12 月的使用百分比
|
| — | — | — |
| iOS 浏览器 | 不 | Two point eight nine |
| 迷你歌剧 | 不 | One point one seven |
| 安卓浏览器 | 不 | Zero |
| 歌剧手机 | 不 | Zero point zero one |
| Android 版 chrome*(使用 webkit 前缀)* | 是–部分 | Thirty-five point one six |
| 安卓火狐 | 不 | Zero point two three |
| 适用于 Android 的 UC 浏览器 | 不 | Two point eight eight |
| 三星互联网*(使用 webkit 前缀)* | 是–部分 | Two point seven three |
| QQ 浏览器*(使用 webkit 前缀)* | 是–部分 | Zero point two |
| 百度浏览器*(使用 webkit 前缀)* | 是–部分 | Zero |
| KaiOS 浏览器*(使用 webkit 前缀)* | 可以启用 | Zero point two |
那么 Chrome 在这个列表中脱颖而出就不足为奇了,就像它在语音合成 API 中的表现一样。如果我们将鼠标悬停在 CanIUse.com 网站的数字上,我们将看到与之前显示的结果相同的结果!
对表 3-2 中显示的数字的检查表明,集中精力开发谷歌 Chrome 是非常明智的;任何花在其他浏览器上的时间都应该只给大客户,在那里收入机会可以证明所需要的努力是值得的!既然我们已经看到了这两个 API 的数字,那就有必要花点时间总结一下为什么我们应该考虑只为 Chrome 开发:
-
Chrome 是最受欢迎的,所以我们将通过专注于这款浏览器获得最大的曝光率,我们可以在移动和桌面环境中重用相同的核心功能。
-
如果(但愿不会)我们遇到任何问题,我们应该很快看到它们出现,然后可以更快地决定停用或暂停语音选项。在使用率低得多的浏览器上很难发现问题,我们可能不会像在 Chrome 上那样很快发现问题。
好了,鉴于 Chrome 的受欢迎程度和支持水平,我们已经概述了关注 Chrome 的理由;是时候实际一点了!在我们这样做之前,关于本章中的演示,有几点我们需要掩盖;这是为了确保您在测试每次练习的结果时获得最佳效果。
几个先决条件
在本章的过程中,我们将重温一些我们在 CodePen 中创建的来自前面章节的练习,目的是使它们适合在移动设备上显示,比如你的手机。
我们当然可以创建新的演示——这种方法没有错,但更有益的方法是看看如何轻松地将现有的演示应用到移动平台上。考虑到这一点,有几点需要注意:
-
代码可以在我们在桌面上创建的笔内编辑,但是为了在测试我们的演示时获得最佳效果,它应该在手机上显示。
-
为了每个演示的目的,也为了证明我们之前的讨论,我们将只使用 Chrome 鉴于 Chrome 的使用水平远远高于所有其他浏览器,使用最流行的浏览器是有意义的!
-
我们将像以前一样使用本书的代码下载——在开始本章的演示之前,请确保您手头有一份副本。
记住这一点,让我们开始开发一些代码,从检查浏览器对 API 的支持开始。
检查对 API 的支持
我们的第一个演示将有助于确定所选的浏览器是否支持 APIs 值得指出的是,如果我们决定只使用 Chrome(如前所述),那么这个测试似乎有点多余!
不过,还是值得一跑;我们不仅检查支持,而且我们还将使用不同的方法来实现相同的结果。这两种方法都没有比另一种更好的理由;每一种都可以正常工作,您可以选择在自己的项目中使用哪一种。
Establishing Support for Speech APIs
我们将从检查语音合成 API 开始,但是代码也将与语音识别 API 一起工作(只需在代码中将单词“Synthesis”的所有实例替换为“Recognition”,然后保存并运行它)。
要确定浏览器是否支持 API,请执行以下步骤:
-
接下来,打开来自
HTML.txt
的代码的副本,并将内容粘贴到我们的笔的 HTML 窗格中。 -
Once saved, we can test the results – for this, go ahead and browse to your CodePen from your cell phone, then make sure you have Editor View displayed, and hit the Console button. We don’t need to do anything. If your browser supports either API, then we will see confirmation of this in the CodePen console, as shown in Figure 3-5.
图 3-5
证明我们的手机支持 API
-
我们首先从代码下载中提取一个
checksupport
文件夹的副本,并将其保存到我们的项目区域。 -
接下来,继续浏览至
https://codepen.io
的 CodePen。然后使用你在第一章中使用的账户信息登录。 -
On the left, choose Create ➤ Pen. Then copy and paste the contents of
JS.txt
into the JS pane of our Pen – make sure you save the Pen! If all is well, we should have something akin to the screenshot shown in Figure 3-4.图 3-4
在 CodePen 的 JS 窗格中输入的代码
太棒了!我们已经确认我们的移动浏览器可以支持 API(是的,我假设你已经使用了 Chrome!).
这意味着我们现在可以继续前进,并开始调整我们以前的练习,以在移动设备上显示。在此之前,我想快速浏览一下一个小技巧:计算出可用的视区。是的,我知道你可能会问这和语音 API 有什么关系,但是这是有原因的;请忍耐,我会解释一切。
确定可用空间
任何花时间为移动设备设计的人无疑会意识到页面上可用空间的限制——这是一个老问题,当视窗区域如此之小时,提供什么。
这在使用语音 API 时尤其重要——我们必须注意显示元素需要多少空间,例如语音合成 API 所需的输入字段,或者使用语音识别 API 时显示转录文本所需的空间。
这就是使用代码计算出视窗区域可能有所帮助的地方——不仅是为了确定我们在代码中有多少空间,而且也是为了设置 Chrome 的响应视图以适应手机的可用空间。让我们更详细地看看这在现实中意味着什么,以及它们如何帮助语音 API。
使用代码设置可用空间
由于空间非常珍贵,我们需要计算出我们能够使用多少空间。我们可以使用 JavaScript 自动获取值,而不是试图猜测值或通过反复试验来计算。
这对于在多种设备上进行测试来说是非常好的,因此当涉及到为语音 API 布局元素时,我们可以感受到我们将不得不使用多少空间。为此,您可以使用我在 CodePen 中设置的 https://codepen.io/alexlibby/pen/MWYVBGJ
功能。
顺便说一句,不要忘记添加正确的元标签——你将需要这样的东西:<meta name="viewport" content="width=device-width, initial-scale=1.0">
配置 Chrome 的响应模式
此外,我们可以使用这些值来帮助设置 Chrome 的响应模式(官方称之为移动仿真模式)。不言而喻,没有两个手机会有相同的空间或视窗,所以为了帮助这一点,我们可以设置自己的自定义区域。让我们在下一个练习中了解一下。
Setting up Viewports in Chrome
如果你想练习这些步骤,下一个练习可以在 Chrome for mobile 或 desktop 上进行;最终你需要在 Chrome 中设置它,以在测试中获得最佳效果。我们可以使用以下步骤设置视窗:
-
单击左侧的下拉菜单,并选择编辑➤添加自定义设备…
-
在设备名称字段中,输入您的手机或所选移动设备的品牌和型号。
-
下面是三个字段–在左边输入宽度,在中间输入高度,右边的字段保持不变。确保使用代理字符串设置为移动。
-
点击添加。现在,在测试语音 API 时,您可以将它设置为您选择的视窗区域。
-
启动 Chrome,浏览到一个站点——我假设我们将使用 CodePen,它非常适合测试响应视图。
-
接下来,我们需要启用响应模式,这可以使用 Ctrl+Shift+I (Windows 和 Linux)或 Cmd+Shift+I (Mac)来完成。
-
At the top of the resized page will be an option to choose a different viewport; it will look something akin to the screenshot shown in Figure 3-6.
图 3-6
Chrome 的响应模式选项
这只是使用 Speech APIs 时要考虑的一个小方面——我们在这里只涉及了两个方法,并没有探究返回值的一些奇怪之处。但是,它应该会给你一个很好的提示,告诉你可能会有多少空间,这样你就可以设置一个简单的方法来分配空间和测试特性,而不必完全在移动设备上工作或使用 BrowserStack 之类的外部服务。
这并不是说我们应该忽略 BrowserStack 之类的服务,它们执行一个有用的功能——这是为了让这个小技巧在开发期间,在完成正确的测试之前发挥作用!
好吧,让我们回到 API 的话题上。既然我们有了一个快速而简单的方法来计算出可用的视口区域,那么是时候让我们进入 API,看看它们是如何在移动设备上工作的了!我怀疑你可能在想我们必须做出很多改变,对吗?如果不是对 API 本身,至少对样式,肯定…?
嗯,我不想让人失望,但答案是否定的——如果我们对自己的造型很小心,那么我们应该不需要太多的调整。让我们通过改编本书前面的两个 CodePen 演示来测试这个理论,看看它是如何工作的。
实现语音合成 API
还记得第一章中的这个演示(如图 3-7 所示)吗?
图 3-7
我们最初的语音合成 API 演示来自第章第一部分
这是一个简单的演示,展示了我们如何实现语音合成——它允许我们在输入字段中输入任何我们希望的文本,然后在要求计算机将文本呈现为语音之前,调整语音和音高等设置。
希望你还有第一章第一章的版本保存为钢笔——你有吗?如果没有,我建议你使用来自readingback
演示的代码再次设置它;以后的演示也会用到它!
假设你已经重新设置了它,或者有你之前创建的版本的链接,试着在你的手机上运行它。如果您在输入栏中输入了内容,请点击“朗读”;你应该会发现 Chrome 会把它还原成语音。
问题是用户界面看起来不是很好,不是吗?这是和以前一样的代码,但是这一次我们需要来回滚动——这是一种让人厌烦的方式!具有讽刺意味的是,这整个设置是我提倡只使用 Chrome 的原因之一,至少在不久的将来是这样。我们不需要接触语音合成 API 所需的任何 JavaScript,而是可以专注于调整我们的标记和样式,以更好地适应可用空间。为了理解我的意思,让我们来测试一下,并调整演示以更好地适应您的手机,作为下一个练习的一部分。
适应手机的设计
在下一个练习中,我们将重复使用第一章中的一个演示(我知道,这并不是很久以前的事,尽管看起来可能不是这样!).我们将进行一些调整,以确保它更好地适应有限的可用空间,同时确保功能仍按预期运行。准备好进去看看了吗?
Speaking on a Mobile
让我们按照以下步骤继续更新我们的演示:
图 3-8
我们更新的语音合成 API,运行在手机上
-
我们将首先在
https://codepen.io
浏览到 CodePen,然后使用你在第一章中使用的相同账户信息登录。 -
切换到 HTML 窗格。然后把页面标题周围的
<h1>
标签改成<h3>
。 -
接下来,切换到 CSS 窗格,并在该窗格的底部添加以下 CSS 修改:
/* ADAPTATIONS FOR MOBILE */ h3 { margin: 0; } #page-wrapper { width: 350px; margin: 13px auto; padding: 5px 16px; } #voice { vertical-align: super; width: 320px; margin-left: -3px; } button { width: 28%; } input[type="text"] { padding: 2px 5px; font-size: 16px; }
-
保存笔。如果一切正常,我们应该看到我们的演示的风格已经更新;我们可以在图 3-8 的截图中看到证据。
虽然这是一个过于简单的演示,但它表明如果我们足够勇敢,只在 Chrome 上工作,那么就没有必要改变与语音合成 API 相关的核心功能!我们所要做的只是调整一些样式,让可视 UI 更好地适应可用空间,而核心 JavaScript 代码保持不变。
不过,还有一件小事,那就是……德国被选为我们的默认声音了吗?的确是;在移动设备中使用语音合成 API 的一个奇怪之处在于,与在标准桌面上看到的声音相比,您可能会发现您选择了不同的声音作为默认声音。如果我们点击下拉菜单,我们可以看到德国确实是默认的,如图 3-9 所示。
图 3-9
手机上的默认语音不同…
在这一点上,我敢打赌,你们中的一些人可能会问,“语音识别 API 怎么样?”假设我们选择只使用 Chrome,我们需要做什么样的改变呢?这些都是很好的问题,我很高兴地告诉大家,当涉及到更新我们的代码时,我们可以在这里应用相同的原则。为了理解我的意思,让我们深入了解一下这些变化的更多细节。
实现语音识别 API
尽管对语音识别 API 的支持没有那么先进,但我们完全可以应用同样的变化。如果我们要在手机上运行谷歌 Chrome 的原始演示版本,图 3-10 显示了它的样子。
图 3-10
我们在手机上的原始语音识别演示
不太好,是吧?试着点击一下,跟我说话!按钮,对着电话的麦克风说些什么。它会在屏幕上呈现一些东西,但不容易阅读,对不对?不过最棒的是,为了让这个演示更好地工作,我们只需要对我们的样式做最小的改动。我们的下一个练习将更详细地探讨这些变化是什么。
适应移动应用
在我们刚刚完成的前一个练习中,我们看到了如何调整我们的设计,以使语音合成 API 更好地适应可用空间,并且我们不必更改任何用于创建该功能的 JavaScript 代码。重要的是,我们也可以将相同的原理用于语音识别 API。让我们在下一个练习中探索这意味着什么。
Recognizing Speech on a Mobile
好的,让我们按照以下步骤继续:
图 3-11
使用 API 在手机上识别语音
-
我们将首先在
https://codepen.io
浏览到 CodePen,然后使用你在第一章中使用的相同账户信息登录。 -
切换到 HTML 窗格,然后将页面标题周围的
<h1>
标签改为<h3>
。 -
接下来,切换到 CSS 窗格,并在该窗格的底部添加以下 CSS 修改:
/* ADAPTATIONS FOR MOBILE */ h3 { margin: 0 0 20px 0; } #page-wrapper { width: 350px; } .voice { float: right; margin-top: 5px; } .response { padding-left: 0px; margin-top: 0px; height: inherit; } .output_log { font-size: 20px; margin-top: 5px; }
-
保存笔。如果一切正常,我们应该看到我们的演示的风格已经更新;我们可以在背页的图 3-11 中看到这一点的证据,其中口语单词已经被呈现,并且 API 由于不活动而被关闭。
稍等片刻。有些选择器看起来很眼熟,对吧?是的,这确实是正确的,尽管如果你仔细观察在中定义的属性,会发现有一些不同。
我不确定这是纯粹的偶然还是设计(老实说,桌面版本最先出现!),但这表明尽管对这两种 API 的支持在不同的浏览器中并不平等,但基本的 JavaScript 代码在这两种情况下都保持不变。
虽然它确实假设我们只使用 Chrome——虽然你们中的一些人可能会担心这似乎限制了我们的选择,但值得记住的是,API(在撰写本文时)仍处于不断变化的状态,即使它们在现阶段运行得相当好。在这个阶段限制功能是完全可以接受的,因为我们正在提供一个新功能,而且我们可以更容易地监控新功能的使用情况。
好吧,我们继续。我们已经单独讨论了这两个 API,但是把它们放在一起怎么样呢?没问题,这是我们将在本书后面的项目中做得更多的事情,但是现在,让我们看看在为移动环境编码时这可能是如何工作的。
把它放在一起:一个实际的例子
在这一章中,我们已经看到了 API 在移动设备上是如何工作的,如果我们乐于使用 Chrome,这可以减少我们需要做的 JavaScript 修改量!
是时候将这两个 API 结合起来进行本章的另一个演示了——为此,我们将设置一个小应用来告诉我们我最喜欢的城市之一哥本哈根的时间。在这个演示中,我们将使用两个 API——语音识别 API 来请求它告诉我们时间,语音合成 API 给我们响应。让我们开始吧,看看如何更详细地设置我们的演示。
如果你想使用不同的城市,那么你需要改变时区——维基百科有一个广泛的合适时区列表,在 https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
。
Getting Time
要创建我们的演示,请遵循以下步骤:
图 3-12
我们的实际例子
-
保存后,我们可以测试结果——为此,请从手机上浏览到 CodePen 演示,然后确保显示编辑器视图,并点击控制台按钮。
-
我们不需要做任何事。如果您的浏览器支持任一 API,那么我们将在 CodePen 控制台中看到确认,如图 3-12 所示。
-
我们首先从代码下载中提取一个
practicalexample
文件夹的副本,并将其保存到我们的项目区域。 -
接下来,继续浏览到
https://codepen.io
的 CodePen,然后使用你在第一章中使用的相同账户信息登录。 -
在左侧,选择“创建➤钢笔”。
-
我们需要添加几个外部库来帮助演示,为此,单击设置➤ CSS,然后在对话框底部的槽中添加这两个链接:
https://use.fontawesome.com/releases/v5.0.8/css/fontawesome.css
-
接下来,在同一个对话框中单击 JavaScript–这一次,添加此链接,这将有助于获得我们所选城市的正确时间:
https://cdn.jsdelivr.net/npm/luxon@1.21.3/build/global/luxon.min.js
-
点击保存并关闭。然后将
JS.txt
的内容复制粘贴到我们笔的 JS 窗格中。 -
接下来,打开来自
HTML.txt
的代码的副本,并将内容粘贴到我们的笔的 HTML 窗格中。 -
继续对 CSS.txt 文件做同样的事情,将它粘贴到 CSS 窗格中。
确保您点击了保存按钮或按 Ctrl+S(或 Cmd+S)来保存您的工作!
在最后一个练习中,我们将两个早期演示的代码放在一起,并修改了 UI,使我们能够获取和显示时间。虽然代码现在应该开始变得更熟悉了,但还是值得花点时间更详细地浏览一下代码,看看 API 如何在移动环境中协同工作。
详细剖析代码
再看一下我们在这个演示中使用的 JavaScript 当然涉及到相当多的内容,但大部分都不是新的。它是从我们在本书前面创建的两个演示中提取的。这同样适用于所使用的 CSS 和标记;虽然我们删除了一些不需要的元素(比如输入字段),但是剩下的都是标准的 HTML,直接来自相同的两个演示。
真正神奇的是我们使用的 JavaScript 在检查我们的浏览器是否支持合成 API 之前,我们通过定义一些变量来缓存元素。理想情况下,我们会在这里包含对识别 API 的检查,但是考虑到我们使用的是 Chrome,我们也会在这里得到同样的正面响应。
接下来是我们以前用过的相同的loadVoices()
函数;这将从浏览器中获取可用的声音,并将它们加载到下拉框中。然后我们用onerror
事件来捕捉任何问题;这将在控制台日志区域显示任何内容。然后,我们用speak()
函数完成这个演示的第一部分,这个函数是我们在前面的演示中创建的,并在我们的实际例子中重用。
该代码块的后半部分用于语音识别 API 第一个函数是一个新函数,它使用 Luxon 时间库来获取我们选择的城市(在本例中是哥本哈根)的当前时间。在将时区切换到哥本哈根并相应地重新格式化时间之前,我们使用luxon.DateTime.local()
设置初始时间值。
然后,我们继续将语音识别 API 的一个实例定义为一个对象,然后分配一些属性,比如是否显示interimResults
,是否有多个选择,或者是否将 API 设置为运行continuously
。
接下来是 click 事件处理程序——虽然我们的代码会自动触发麦克风,但我们仍然需要它来启动识别 API,以及设置语言(这里它被设置为en-GB
,但如果需要,我们可以将其设置为任何适当的值)。然后我们有和以前一样的事件处理程序,用于speechstart
、speechend
、onspeechend,
和error
。唯一改变的处理程序是result
——大部分保持不变,但我们添加了一个块来分割转录的文本,然后确定我们是否说出了单词“Copenhagen ”,如果是这样,就做出相应的响应。
使用移动设备:附录
我们在本章中构建的演示非常简单——如果有人认为生活很简单,实现 API 是轻而易举的事情,这是可以理解的!但是事情并不总是像它们看起来的那样;在我们进入下一章之前,我想留给你一个想法。
在为这本书做研究时,我的初衷是创建一个可以在移动设备上工作的语音控制视频播放器——毕竟,我们为桌面做了一个,所以这应该只是一个调整风格的问题,对吗?嗯,答案是肯定的,也可能是否定的
组装演示非常容易——大部分工作已经在前面完成了,所以我重用了代码并关闭了一些关于关闭服务的选项。然而,语音识别在手机上的工作方式似乎有所不同——是的,Chrome 确实受到支持,但我怀疑不是每个浏览器(支持 API)都提供相同的一致水平的支持!我可以在手机上播放视频演示,但它会很快停止识别服务,或者可能引发网络错误。
我认为这可能是由于 CodePen 的工作方式和它的频繁刷新——演示在桌面上运行良好,因此指出了环境中某个地方的潜在问题!这一点需要记住——您需要彻底测试您的解决方案,以确保它们如客户预期的那样工作。
你可以看到我在 CodePen 创建的视频 demo,在https://codepen.io/alexlibby/pen/xxbpOBN
;如果你需要一个视频样本,试着从 https://file-examples.com/index.php/sample-video-files/sample-mp4-files/
下载一个。
摘要
提到“使用移动设备工作”,你可能会让任何开发人员不寒而栗——如果没有额外的移动平台,跨桌面工作已经够难的了!不过,语音 API 是功能性问题较少的一个领域。在本章的整个过程中,我们已经探索了如何使现有的代码适应移动设备,假设我们乐于根据所使用的浏览器类型来限制暴露。我们已经在本章中讨论了一些重要的主题,所以让我们休息一下,回顾一下我们所学的内容。
我们首先检查了当前对语音合成和识别 API 的支持水平,然后才理解限制接触 Chrome 在短期内有什么意义。然后,我们讨论了如何确定您选择的移动浏览器是否确实支持 API,然后探索了一个快速技巧来计算可用的屏幕空间以及这对 API 的重要性。
接下来,我们进行了两个原始演示(每个 API 一个),并将其转换为在移动平台上工作,然后才理解我们必须做出什么样的改变才能让它们运行。然后,我们通过更新一个更实际的例子来结束这一章,最后,我们思考了如何在移动设备上使用 API。
唷!理论现在结束了。娱乐时间到了!在这一点上,我们开始使用一些示例项目。用你的声音来寻找附近的餐馆,询问时间,甚至支付产品费用怎么样?这些只是我们将在即将到来的项目中介绍的三个技巧;我们将从给出反馈这一至关重要的任务开始,以及我们如何对一个古老的问题进行新的阐述……感兴趣吗?请继续关注我,因为我会反馈我们如何使用 API(是的,这绝对是双关语!)下一章。
四、组合 API:构建聊天机器人
在上几章的课程中,我们已经详细研究了 Speech API,并使用它建立了一些基本的语音功能示例。然而,这仅仅是开始,我们还可以做更多的事情!利用 API 为我们提供了许多创新的想法,这还是在我们对提供给客户的功能进行个性化之前。
在本书的剩余部分,我们将充分利用 API 来构建各种项目,展示我们如何添加语音功能。这将包括从留下语音反馈到结账过程自动化部分等功能。不过现在,我们将把这两个 API 结合在一起,构建一个简单的聊天机器人,它将响应一些基本短语,并在屏幕上显示结果。我相信有人曾经说过,我们必须从某个地方开始,所以没有比设置场景和探索如何使用聊天机器人对我们有益的更好的地方了。
为什么要使用聊天机器人?
那么,我们为什么要使用聊天机器人呢?是什么让他们如此特别?
传统上,公司有客户服务团队,他们可能会处理各种不同的请求,从安排退款到帮助诊断您的互联网接入问题。这将成为一种昂贵的资源使用,尤其是当客户可能经常问同样类型的问题时!只要小心,我们可以创建一个聊天机器人来为我们处理这些问题,这有助于将员工从需要人工干预的更复杂的查询中解放出来。
这是好事吗?好吧,可以,也可以不可以。聊天机器人可以被设置成允许针对特定任务进行基于上下文的对话;虽然这让员工可以处理更复杂的请求,但如果聊天机器人没有配置为最佳体验,同样会导致问题!从某种意义上说,如果我们决定使用机器人,我们应该更加重视让客户感觉特别——它们可以很好地完成日常任务,但如果他们觉得我们对机器人的使用不够完美,我们会被视为廉价并让客户却步。这一点尤其重要,因为 Gartner 等重量级公司预测,到 2020 年,30%的浏览将由使用无屏幕设备的用户完成。这意味着聊天机器人的使用将会增加,特别是在社交媒体领域——毕竟,在哪里最有可能找到人,特别是如果他们需要抱怨糟糕的服务?
构建聊天机器人时需要考虑的事项
好的,我们已经决定我们需要建立一些东西,但是它应该是什么,它应该为谁服务?
这些都是好问题;构建一个机器人不应该被视为省钱的借口,而是可以帮助增加现有人员,让他们承担更高要求或更复杂的查询。不过,我们考虑哪个利基市场并不重要。我们应该将一些最佳实践视为构建 bot 的第一步:
-
你的顾客或用户会希望只由真人来服务吗?
-
您的用例是否更适合替代渠道——例如网站或本地应用?
-
你如何让终端用户知道他们在和一个机器人或者一个真人聊天?对话可能会从前者开始,但有时可能需要将它们交给一名现场代理。
-
机器人需要处理多少任务?您的机器人可能会收集各种信息,但理想情况下应该负责处理每个流中的一两个项目——这是一个质量超过数量的问题!
-
机器人会对您的环境产生多大影响——自动化少量任务是否会为您的公司带来真正的好处,或者回报是否不值得付出努力?
在这一点上,您可能认为我们稍微偏离了使用语音 API 的主题,但这是有充分理由的:如果基本对话不是最佳的,那么添加语音功能就没有什么意义!重要的是,不仅要考虑要使用的声音和他们是否可以选择使用哪个等话题,还要考虑对话是否自然,是否包含正确的短语,以及我们的回答是否符合客户在与我们的聊天机器人互动时会使用的短语。
机器人的缺点
创造一个机器人,尤其是一个会说话的机器人,是件好事,但他们可能会有一个潜在的缺点——他们只能模拟人类的互动。一个机器人的好坏取决于它的配置;从功能上来说,它可能是完美的,但如果使用的短语和术语选择不当,那么这只会让人们望而却步!
他们是否会说话并不重要,事实上,如果他们的谈话不自然,增加语音功能只会让客户更加沮丧。任何依靠语音交互来完成在线任务的人都会特别感受到这一点。这意味着,作为使用我们在本书中探索的语音 API 创建东西的一部分,我们绝对需要考虑一些主题,如使用正确的声音,以及在配置我们的机器人时正确的术语或措辞。
这是一个值得进行大量研究的领域——你能承担的越多越好!作为其中的一部分,了解可用的机器人类型很重要,因为这不仅会影响我们如何构建它们,还会影响它们的语音功能。机器人有各种各样的伪装,但可以大致分为两种不同的类型。让我们依次看一下它们,并更详细地看一下它们是如何相互叠加的。
不同类型的聊天机器人
为了帮助理解和最小化使用机器人的缺点,我们可以将它们大致分为两个不同的组:事务型(或无状态)和会话型(有状态)。这对我们意味着什么?嗯,有一些关键的区别:
-
事务性或无状态机器人不需要历史记录——每个请求都被视为离散的,机器人只需要理解用户的请求就可以采取行动。事务型机器人非常适合自动化快速任务,我们期待简单的结果,例如检索当前的互联网带宽使用情况。
-
对话式或状态式机器人依靠历史和信息收集来完成任务。在这种情况下,机器人可以提出问题,解析响应,并根据用户的响应确定下一步行动。这种类型的 bot 非常适合自动化更长、更复杂的任务,这些任务有多种可能的结果,但可以在构建过程中预测到。
考虑到这一点,让我们把它变成更实际的东西。我们已经指出,每种机器人类型更适合某些任务;表 4-1 显示了这些任务的一些示例。
表 4-1
bot 类型的一些实例
|bot 的类型
|
一些实际应用的例子
|
| — | — |
| 交易机器人 | 交易型机器人无法记住之前与用户的交互,也无法与用户保持长时间的对话:Alexa 关灯、播放歌曲或启动/解除室内警报通过短信确认预约谷歌助手检查和报告天气 |
| 对话机器人 | 对话机器人维护对话的状态,并在对话之间传递信息:在餐厅预订——机器人需要知道聚会的规模、预订时间和座位偏好,以便进行有效的预订进行多问题调查采访用户以报告问题 |
唷!我们几乎已经到了开始建造我们自己的工厂的时候了。我保证!我知道看起来我们已经讨论了很多理论,但这很重要:增加语音功能只是成功的一半。决定性因素(用一个战斗术语?)是我们需要做的,以确保当我们的机器人说话时,它看起来很自然,并且对我们的客户和我们的初始需求都有预期的效果。
如果你想更深入地研究构建聊天机器人背后的理论,在聊天机器人杂志网站上有一篇很棒的文章,网址是 https://chatbotsmagazine.com/how-to-develop-a-chatbot-from-scratch-62bed1adab8
c 。
好了,我们终于完成了理论。让我们转向更实际的问题吧!我们将构建一个简单的例子,在屏幕上同时呈现口头和视觉上的响应;和任何项目一样,让我们从设置本章将要构建的背景开始。
设置背景
我的一个朋友给我发了一封电子邮件,提出了一个相当有趣的请求:
“嘿,亚历克斯,你知道我在网上开了一家小公司,卖覆盆子酱包,对吧?嗯,我真的想添加一些东西来帮助我的客户更容易找到工具包!我知道你喜欢尝试新东西。想帮我创造点什么吗?如果可以的话,我很想让它有所创新。有想法吗?”
好吧,我承认:那是一个虚构的朋友,但除此之外,这正是我喜欢做的事情!如果这是一个真实的请求,我的第一反应将是创建一个聊天机器人,我们可以在其中添加语音功能。出于本书的目的,我们将保持简单,仅限于搜索不同的 Raspberry Pi 第 4 版电路板。同样的原则也可以用于搜索其他相关产品(我们将在本章末尾更多地讨论这一点)。
我们将为我虚构的朋友 Hazel(是的,这个名字在早期的演示中应该很熟悉)构建一个聊天机器人,他经营着一家名为 PiShack 的公司。我们的演示将主要基于文本,但包括一些简单的元素,如在我们的对话中显示图像和基本的 HTML 标记。聊天机器人将用于查找 Raspberry 4 板产品,然后在屏幕上显示选择的产品,并向客户提供链接和基本股票信息。
保持事物在范围内
与任何项目一样,我们需要定义应该包括什么的参数,以帮助保持事情在正轨上。
谢天谢地,这对于我们的演示来说非常简单;我们将进行一次基本对话,引导客户从三种板类型中选择一种。根据他们的选择,我们将显示该电路板类型的图像,以及零件号和库存可用性。然后,我们将模拟显示一个虚拟产品页面的链接;对于本演示,我们将不包括产品页面。可以说,这可以链接到现有项目中的任何页面,因为这只是一个标准链接-确切的链接可以生成,这取决于我们的客户的反应。
好吧,记住这一点,让我们继续。现在我们已经设置好了场景,我们需要构建我们演示的各种元素,这样我们就可以更详细地看到它们是如何组合在一起的。
构建我们的演示
对于我们的演示,我选择保持简单,强调除非必要,否则不要使用额外的工具。有几个原因:第一个原因是,为我们的聊天机器人引入依赖关系可能会引入与我们项目中的其他元素不兼容的软件。
有几十个不同的聊天机器人库可用,但我决定在这个项目中使用的是 RiveScript。可从 https://www.rivescript.com/
获得,它是一种开源脚本语言,有许多不同的语言解释器,如 Python,Go,或者,在我们的情况下,JavaScript。虽然这是个人的选择,但是使用这个库有几个好处:
-
它(解释器,而不是库)是用纯 JavaScript 编写的,所以依赖性很小;如果需要,可以在 Node.js 下运行一个版本,尽管对于简单的使用来说这不是必需的。
-
它的语法非常容易学习——您可以非常快速地整理一个基本的配置文件,从而有更多的时间来微调与聊天机器人交互的触发器和响应。
-
它不是由要求您在线编辑 XML 文件或复杂配置的大型商业公司制作的——您需要的只是一个文本编辑器和您的想象力!
-
它是开源的,所以如果需要可以修改;如果您有问题,那么其他开发人员可能会帮助提供修复,或者您可以根据自己的需要进行调整。
-
它可以托管在内容交付网络(或 CDN)链接上,以便从本地点快速访问;以这种方式交付的内容也将被缓存,这使得它更快。如果需要,我们还可以在 CDN 链路无法运行时提供本地备用方案。
在这一点上,有必要问一个问题:我还可以使用什么其他选项?有几十种,但是许多依赖于复杂的 API 或者必须在线管理。这不一定是一件坏事,但是当开始使用聊天机器人,特别是 Web 语音 API 时,它确实增加了一层额外的复杂性!除此之外,让我们花一点时间来更详细地介绍一些现在或将来可能感兴趣的替代方案。
可用的替代工具
在为这本书进行研究时,我遇到了几十种不同的工具和库,它们提供了构建聊天机器人的能力——我选择在我们即将构建的演示中不使用其中的许多工具和库,主要是因为它们已经有一段时间没有更新了,涉及复杂的设置,必须在线设置,或者被绑定到一个专有产品中,如果情况发生变化,很难摆脱这些工具和库。
也就是说,我确实遇到了一些有趣的例子,它们设置起来并不复杂,值得一试:
-
wit . ai-可从
https://wit.ai/
获得,这个脸书拥有的平台是开源的,易于设置和使用。它有各种可用的集成,包括 Node.js,因此可以很好地与语音识别和合成 API 一起工作。 -
BotUI–这是一个简单易用的框架,可从
https://botui.org/
获得;它处理每个触发器/响应的结构比 RiveScript 更严格,需要将每一对都构建到主代码中,而不是配置文件中。 -
如果你曾经花时间为 CMS 系统开发代码,那么你肯定听说过 WordPress。位于
https://botpress.io/
的 Botpress 将自己描述为“聊天机器人的 WordPress”,在这里任何人都可以为聊天机器人创建和重用模块。这是一个混合产品,虽然主要是开源的,但它也有针对更多企业级需求的许可,因此这将是未来发展的一个好方法。这个库还有一个可视化编辑器,在与 Node.js 集成之前,可以很容易地构建初始的 chatbot 触发器和响应。
需要注意的重要一点是,将语音 API 集成到聊天机器人中不太可能是简单的“轻触开关”或设置配置参数的事情。任何这样的整合都需要努力——多少取决于我们使用的聊天机器人库!
好了,设置的下一步是为我们的文本编辑器添加语法高亮支持。RiveScript 提供了一些更流行的插件,如 Sublime Text 或 Atom。在进入更紧迫的问题之前,让我们赶快把这件事解决掉吧!
如果你使用一个更专业的编辑器或者一个没有在 https://www.rivescript.com/plugins#text-editors
中列出的编辑器,那么请随意跳到下一节;这不会影响演示的操作方式。
添加文本编辑器支持
虽然我们在编辑 RiveScript 文件时几乎可以使用任何语法荧光笔(毕竟它们只是纯文本文件),但添加一个专用的荧光笔绝对有助于使您的代码更容易阅读。
RiveScript 为更受欢迎的编辑提供了几个,分别是https://www.rivescript.com/plugins#text-editors
;这包括 Atom、Sublime Text 和 Emacs。图 4-1 展示了在安装了插件的 Atom 中运行代码时的样子(见下页)。
图 4-1
文本编辑器中 RiveScript 语法的屏幕截图
我相信你会同意,这当然有助于阅读(并随后理解)代码!假设你已经安装了一个合适的语法荧光笔(本章后面的截图将展示 Sublime 文本中的例子),让我们继续并完成剩下的准备过程。
将工具放置到位
对于我们的下一个项目,我们将需要利用一些额外的工具来帮助开发和运行。让我们看看我们需要什么:
-
我们将需要一些网络空间,已经使用 HTTPS 访问安全-你可以使用测试服务器上的网络空间或安装一个本地网络服务器,如 MAMP 专业为此目的。这个 web 服务器特别擅长创建 SSL 证书;您将需要一些东西来允许 Web 语音 API 正确运行!这是一个商业产品,从
https://www.mamp.info
开始提供,适用于 Windows 和 Mac 平台。如果你更喜欢手动操作,那么我建议你试试 Daksh Shah 的脚本,可以在他的 GitHub repo 的https://github.com/dakshshah96/local-cert-generator/
找到。这包含安装 Linux 和 Mac 证书的说明;使用“为 windows 安装证书”在线搜索有关如何为 Windows 安装证书的文章。 -
为了方便起见,我们将为这一章建立一个项目文件夹——出于本书的目的,我将假设你称它为 speech,并且它在你本地 PC 的硬盘上。如果您使用不同的方法,请根据需要调整演示中的步骤。
-
我们将用来构建聊天机器人的主要工具是 RiveScript 库,可从
https://www.rivescript.com/
获得。这是一种用于编写聊天机器人的基于 JavaScript 的语言,它有各种不同的接口,很容易学习。该库以 CDN 格式提供,也可以使用 Node.js 安装——为了简单起见,我们将在演示中使用前者。
Note
为了这个演示的目的,我将假设项目区域被设置为在https://speech/
下工作;如果您的环境不同,或者您更喜欢继续使用 CodePen,那么演示可以在这种环境下工作;使用本地设置会给你更多的控制。
好了,有了这三个管理任务,让我们继续,开始构建我们的演示!
构建我们的聊天机器人
我们的演示将包含相当多的代码,所以我们将通过两个练习把它们放在一起;在我们继续第二部分之前,这将给你一个休息的机会。第二部分将负责用我们选择的问题和答案来配置聊天机器人;在此之前,让我们先来看看如何设置我们的聊天机器人的功能。
Building the Chatbot, Part 1: The Functionality
我们演示的第一步是获得 RiveScript 的最新副本——可以从 https://www.rivescript.com
获得。出于本练习的目的,我们将使用从unpkg.com
获得的 CDN 版本,它已经在 HTML 标记文件中设置好了。
Node.js 也有一个版本——详情请见 https://www.rivescript.com/interpreters#js
。
让我们继续构建演示的第一部分:
-
我们将从本书附带的代码下载中提取一个
chatbot
文件夹的副本开始;将它保存到我们项目区域的根目录。 -
在该文件夹中,在
js
子文件夹中创建一个新文件,将其另存为script.js
。 -
我们将使用它来为我们的聊天机器人设置功能,为此我们将有相当多的代码。不要担心,我们会一条一条来!我们首先声明将在整个演示中使用的全局变量,并初始化 RiveScript 的一个实例:
let bot = new RiveScript(); const message_container = document.querySelector('.messages'); const form = document.querySelector('form'); const input_box = document.querySelector('input'); const question = document.querySelector('#help'); const voiceSelect = document.getElementById('voice');
-
接下来,漏掉一行,然后添加以下函数和函数调用——它们负责将声音加载到我们的演示中:
function loadVoices() { var voices = window.speechSynthesis.getVoices(); voices.forEach(function(voice, i) { var option = document.createElement('option'); option.value = voice.name; option.innerHTML = voice.name; voiceSelect.appendChild(option); }); } loadVoices(); // Chrome loads voices asynchronously. window.speechSynthesis.onvoiceschanged = function(e) { loadVoices(); };
-
接下来的两个函数完成了我们演示的第一部分——第一个函数负责基本的错误处理,而第二个函数负责在请求时发出声音。添加上一步下面的代码,中间留一个空行:
window.speechSynthesis.onerror = function(event) { console.log('Speech recognition error detected: ' + event.error); console.log('Additional information: ' + event.message); }; function speak(text) { var msg = new SpeechSynthesisUtterance(); msg.text = text; if (voiceSelect.value) { msg.voice = speechSynthesis.getVoices().filter(function(voice) { return voice.name == voiceSelect.value; })[0]; } speechSynthesis.speak(msg); }
-
现在,我们继续设置和配置我们的聊天机器人——我们从声明一个常量来导入我们的触发器和响应开始。下一行需要放在 speak()函数之后,中间留一个空行:
const brains = [ './js/brain.rive' ];
-
接下来,继续添加这个事件处理程序——它管理任何使用聊天机器人的人提交的每个问题:
form.addEventListener('submit', (e) => { e.preventDefault(); selfReply(input_box.value); input_box.value = “; });
-
我们现在需要在屏幕上呈现每个问题(或触发)和适当的回答——这是下面两个函数的责任:
function botReply(message){ message_container.innerHTML += `<div class="bot">${message}</div>`; location.href = '#edge'; } function selfReply(message){ var response; response = message.toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,""); message_container.innerHTML += `<div class="self">${message}</div>`; location.href = '#edge'; bot.reply("local-user", response).then(function(reply) { botReply(reply); speak(reply); }); }
-
本演示的脚本部分已经接近尾声;我们还要添加两个函数和一个事件处理程序。在上一个步骤之后留出一行空白,然后添加以下内容:
function botReady(){ bot.sortReplies(); botReply('Hello, my name is David. How can I be of help?'); } function botNotReady(err){ console.log("An error has occurred.", err); } question.addEventListener("click", function() { speak("hello. my name is David. How can I be of help?"); bot.loadFile(brains + "?" + parseInt(Math.random() * 100000)).then(botReady).catch(botNotReady); });
-
现在,继续保存文件,我们可以暂时将其最小化,然后进入本演示的下一部分。
在这个阶段,我们有一个半完整的演示,但是如果我们现在运行它,它不会做很多事情!这样做的原因是,我们还有一部分需要添加:我们聊天机器人的问题和答案。尽管设置这一部分相对简单,但仍有大量代码需要完成。让我们深入了解一下细节。
配置我们的聊天机器人
为了让我们的聊天机器人可以运行,我们将使用 RiveScript 库;它为各种语言提供了不同的解释器,比如 Python、Go 或 JavaScript。
这是一种很容易学习的语言,尽管它有一个怪癖,需要一点时间来适应:我们预先配置机器人的所有问题都必须是小写的!谢天谢地,在屏幕上显示它们时,这不是问题;我将在下一个演示结束时解释更多,但让我们把注意力集中在如何配置我们的聊天机器人并准备好使用。
Building the Chatbot, Part 2: The Functionality
让我们通过添加聊天机器人缺少的问题和答案来完成我们的演示。我们将在每个模块中添加内容,但为了有助于编辑,我还会在不同的地方添加截图,以便您可以检查进度:
-
首先创建一个空白文件,将其作为
brain.rive
保存在我们在本演示的第一部分中创建的 chatbot 文件夹的js
子文件夹中。 -
在文件的顶部,继续添加这一行——这将强制 RiveScript 编译器使用 2.0 版的 RiveScript 规范:
! version = 2.0
-
我们的文件包含了一个简单的 RiveScript 函数,我们用它来确保我们客户的名字是大写的(在本章的后面会有更多的介绍)。留下一行然后加入下面的代码:
> object keepname javascript var newName for (var i = 0; i < args.length; i++) { newName = args[i] } return newName.charAt(0).toUpperCase() + newName.slice(1) < object
-
接下来,我们开始添加每个陈述(成对出现-一个问题和一个答案)。你会注意到每个都以+或-开头;前者是问题或触发器,而–表示回应。继续添加第一个,客户可以向机器人问好并收到适当的响应:
+ hello - hello, what is your name?
-
接下来的三个陈述有点复杂,这一次,顾客说出他们的名字,并说出他们想要什么:
+ hi my name is * im looking for a raspberry pi 4 - <set name=<star>>Nice to meet you, <call>keepname <star></call>. No problem, I have 3 available. Are you looking for a particular version? - <set name=<star>>Nice meeting you, <call>keepname <star></call>. No problem, I have 3 available. Are you looking for a particular version?
此时,如果一切正常,我们的 brain.rive 文件中应该有以下代码,如图 4-2 所示。
图 4-2
brain.rive 文件的第一部分
让我们继续下一部分:
-
列表中的下一个问题包含一个条件语句,这一次,我们将询问客户更喜欢查看哪个版本:
+ * versions do you have available - I have ones that come with 1 gigabyte 2 gigabyte or 4 gigabyte RAM. Which would you prefer?
-
在接下来的一对陈述中,客户确认他们想要看到哪个版本;我们展示该产品的适当图像:
+ i would prefer the (1|2|4) gigabyte version - <set piversion=<star>>Excellent, here is a picture: <img src="img/<star>.webp">
让我们暂停一下。如果一切顺利,图 4-3 显示了我们现在应该拥有的代码,作为我们brain.rive
文件的下一部分。
图 4-3
brain.rive 文件的第二部分
让我们继续添加代码:
-
接下来是一个简单的问题——这一次,我们要查看所需产品是否有货:
+ is this one in stock - Yes it is: we have more than 10 available for immediate despatch
-
接下来的部分是最复杂的部分,在这里,我们建立了一些关于产品的简要细节和一个链接,让客户可以直接导航到该产品的产品页面:
+ how can i get it - No problem, here is a link directly to the product page for the 4 gigabyte version of the Raspberry Pi <get version>: ^ <span class="productname"><p><h2>Raspberry Pi 4 - <get piversion>GB RAM</h2><img src="img/<get piversion>.webp"></p><p class="stock">More than 10 in stock</p><p class="stockid">PSH047</p><a class="productlink" href="rasp<get piversion>.html">Go to product page</a></span> ^ Just click on Add to Cart when you get there, to add it to your basket. Is there anything else I can help with today?
在图 4-4 中,我们可以更清楚地看到我们的代码应该是什么样子。
图 4-4
brain.rive 文件中的下一个代码块
-
然后,我们以两个问题结束——第一个承认不需要进一步的帮助,第二个是一个通用的总括问题,以防我们的聊天机器人在理解问题时出现问题:
+ no thats fine thankyou - You're welcome, thankyou for choosing to use PiShack's Raspberry Pi Selector tool today + * - Sorry, I did not get what you said - I am afraid that I do not understand you - I did not get it - Sorry, can you please elaborate that for me?
我们可以在图 4-5 中看到 brain.rive 文件的最终部分。
图 4-5
brain.rive 文件的最后一部分
图 4-6
我们完成的聊天机器人,在对话的开始
- 此时,继续保存文件,我们现在可以测试结果了!为此,浏览至
https://speech/chatbot
,点击提问,开始输入信息,如图 4-6 摘录所示。
当您测试您的演示时,您可能会发现使用 RiveScript 的一个特殊的怪癖 brain.rive 文件被缓存,如果您随后对其进行更改,这将使您更难确定您运行的是一个更新的版本!有一个快速的技巧可以帮助你做到这一点,尽管它只在你使用 Chrome 的时候有效。只需点击并按住 reload 按钮,强制其显示一个清除缓存的选项,并执行硬重新加载,如图 4-7 所示。
图 4-7
使用 Chrome 执行硬重新加载
好了,我们完成了构建,现在我们应该有了一个工作聊天机器人的基础,它可以发出每个响应并在屏幕上显示出来。我们在本书的前面已经看到了用于前者的大部分代码。然而,这个演示展示了一些有用的观点,所以让我们深入研究一下这个代码。
详细研究代码
如果我们仔细看看我们刚刚创建的演示中的代码,我可以想象您的第一反应会是什么——哎呀!是的,代码看起来确实有点复杂,但实际上它比乍看起来要简单。让我们从 HTML 标记开始,一块一块地把它拆开。
剖析我们的 HTML 标记
这个文件中的大部分内容相当简单——一旦定义了对 CSS 样式文件的引用,我们就设置一个#page-wrapper
div 来包含我们所有的内容。然后我们创建一个.voicechoice
部分来放置允许我们选择使用哪种语言的下拉菜单,以及询问问题的初始按钮。
接下来是.chat
部分,我们用它来呈现我们与机器人的对话;消息呈现在.messages <div>
元素中。然后我们有一个表单来提交每个问题,最后引用 RiveScript 库和我们的自定义script.js
文件。
拆开 script . js:Web 语音 API
我们已经介绍了演示中最简单的部分,即标记。这是事情变得更有趣的地方!script.js
和brain.rive
文件是最神奇的地方——在前者中,我们将语音/音频代码与我们的聊天机器人功能相结合,而在后者中,我们为我们的聊天机器人存储各种问题和回答。让我们打开script.js
文件的副本,更详细地看看我们的聊天机器人演示是如何工作的。
在定义一系列变量之前,我们首先将 RiveScript 的一个实例初始化为一个对象,以缓存 HTML 标记中的各种元素。我们代码中的第一个函数loadVoices()
,负责调用语音合成 API 来获取我们将在代码中使用的各种声音,比如英语(英国)。值得注意的是,我们指定引用来调用这个函数两次;这是为了允许一些旧的浏览器(特别是 Chrome),它们要求我们异步加载下拉菜单。大多数情况下,我们会简单的调用loadVoices()
;对于那些需要它的浏览器,下拉列表将使用来自window.SpeechSynthesis
接口的onvoiceschanged
事件处理程序来填充。
继续,我们创建的下一个函数是onerror
事件处理程序,同样来自于window.SpeechSynthesis
接口;这是对使用接口时出现的任何错误的基本概括。现在,我们简单地呈现使用event.error
和error.message
给出的错误类型。值得注意的是event.error
会给出一个具体的错误代码,比如音频捕获。任何error.message
语句都应该由我们作为开发者来定义;该规范没有定义要使用的确切措辞。
错误代码列表可在 MDN 网站 https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionError/error
获得。
这部分代码分解的最后一个功能是speak()
——这是我们表达内容的地方!首先初始化一个新的SynthesisUtterance
实例,然后定义要使用的文本(例如,来自机器人的响应),以及应该使用的声音。假设没有发现问题,那么就由 API 使用speechSynthesis.speak(msg)
语句来说。
唷!我们已经完成了演示的大部分,尽管还有一部分:配置我们的机器人!我建议在这个阶段休息一下——也许去喝一杯或者呼吸一些新鲜空气。一旦你准备好继续,让我们继续探索用于配置我们的机器人更详细的语句。
了解我们的机器人是如何配置的
尽管大部分神奇的事情发生在我们的script.js
文件中,但是如果没有 bot 配置文件brain.rive
,我们的演示将是不完整的。
快速浏览一下这个文件,我们应该能认出一些元素——毕竟,它的大部分看起来像纯文本,开头是一些基本的 JavaScript 代码,对吗?是的,你这么说是对的,但是 RiveScript 有一些不寻常的字符关键字,我们需要在这段代码中注意。让我们从代码的顶部开始,一点一点地浏览它——在我们这样做之前,现在是一个快速了解 RiveScript 如何工作的好机会。
探索 RiveScript 如何工作:概述
我们使用 RiveScript 创建的任何配置都存储为.rive
文件。.rive
文件的一个共同特征是,你会看到大多数行以感叹号、加号或减号开头,箭头和括号会出现几次,就像我们的例子一样。这很重要,因为它们定义了所使用的语句类型。表 4-2 中列出了我们在示例中使用的方法。
表 4-2
brain.rive 中使用的特殊字符类型
|性格;角色;字母
|
目的
|
| — | — |
| +或加号 | 这表示来自用户的触发问题。 |
| *或星形 | 这充当了一个占位符来接受来自用户的数据,比如一个名字或者像“哪个版本…”或者“什么版本…”这样的问题(就像我们的例子一样)。 |
| -或者负号 | 这充当了机器人对用户的响应。 |
| ( )和|或括号和管道符号 | 当一起使用时,这表示一种选择——rive script 将以类似于星号占位符的方式处理它接收到的内容,但这一次,我们将选择限制在三个选项中的一个,即 1、2 或 4。 |
| ^或箭头 | 这是一个换行符,在这种情况下,响应最好在多行中提供。 |
| !或者感叹号 | 这表示一个 RiveScript 指令,比如指定使用哪个版本的规范。 |
在大多数情况下,我们可能会使用加号或减号(如我们的例子)。考虑到这一点,让我们更详细地逐一探究这些语句。
详细剖析 brain.rive 文件
我们从! version=2.0
开始,它告诉 RiveScript 我们正在开发库的 2.0 规范;如果这被设置为一个较低的数字(例如,早期版本),那么我们的代码就有可能无法按预期工作。
现在,我们将跳转到第 12 行,在那里我们有+ hello
——我们将很快回到> object...< object
标签中的代码。第 12 行的代码应该是不言自明的;此时,用户将输入 hello 作为我们的初始触发器,机器人将对此做出相应的响应。
下一个街区更有趣一点。这里,我们使用+
符号指定了一个触发问题;在这个例子中,我们使用了星星。星号是用户给定的一段特定文本的占位符——例如,如果他们使用了名称标记,那么给定的文本将等同于"hi my name is Mark im looking for a raspberry pi 4."
。这本身很简单,但是看看响应:我看到的<call>
标记是什么?那么<set name=....>
代码又是怎么回事呢…?
前者是对 RiveScript/JavaScript 函数的调用。还记得我在本节开始时说过要跳过的代码吗?这就是这个函数的代码——我们用它来确保不管传递给它什么名字,它总是以首字母大写的形式出现在屏幕上。值得注意的是,RiveScript 在触发器中使用时将始终以小写形式格式化变量;我们使用这个函数来显示更适合用户的东西。
接下来的三个问题遵循类似的原则,其中触发器文本是小写的,我们在这三个问题的第一个中使用了一个星号占位符。不过有一个例外:管道和支架的使用。这里我们指定了一些可以被识别的选项;与星号不同的是,任何东西都可能与陈述相匹配,唯一允许的匹配将是数字1
、2,
或4
。然后,我们利用<star>
占位符中匹配的数字来设置一个名为piversion
的变量(我们稍后会用到),作为插值标签的一部分,为所选版本的 Raspberry Pi 板显示适当的图像。
继续前进,下一个街区是最大的——它看起来很吓人,但实际上,它并没有那么复杂!有两件事需要注意:首先,我们<get piversion>
并使用它在屏幕上的一小块 HTML 标记中呈现产品名称和图像。第二种是使用^
或帽子符号;这允许我们将来自 bot 的响应分成几行。我相信你可以想象,像我们这样的一段文字如果组合成一行,看起来会很糟糕。这使我们更容易在屏幕上观看。
然后,我们以两个触发器结束–最后一个触发器来自客户,确认这是他们唯一需要帮助的事情,以及来自机器人的适当确认。最后一个触发器是一个通用的总括触发器,如果出现问题,它就会发挥作用:这很可能是因为用户输入了与我们预先编写的响应不匹配的内容。我们提供了许多可供机器人使用的替代方案;如果它需要在与用户的对话中使用它,它会依次自动选择一个。
唷!这是一个冗长的解释。如果你能走到这一步,那就太好了!在这个演示中有很多内容要介绍,但是希望它向您展示了在使用自动聊天机器人时,我们如何利用 Speech API 来添加额外的维度。我们只是触及了可能的表面,更不用说我们应该考虑的了;后者有几个要点,让我们暂停一下喘口气。去喝杯咖啡或饮料,让我们继续深入一些领域,在这些领域中,我们可以将我们的演示开发成一个功能更加完整的示例。
更进一步
在本章的过程中,我们构建了一个简单的聊天机器人,允许我们从三个 Raspberry Pi 4 板中选择一个,并询问它们的可用性以及如何购买。这是一个直截了当的请求,但是正如您可能已经看到的,还有一些改进的空间!
在这种情况下,我们应该如何调整体验呢?一个领域是我们用过的触发问题;它们有些僵硬,感觉不像我们演示中那样自然或直观。这是需要考虑的一个方面。以下是一些帮助你开始的想法:
-
添加多语言支持–虽然英语被广泛使用,但并不是每个人都会说英语!由于文化差异,它也引入了误解的风险;能够用客户的母语交谈消除了这种风险,让他们感到更受欢迎。
-
让它成为一个双向的过程——我们关注的只是视觉上和口头上呈现我们的回答,但是如何让你也可以用语言表达你的问题呢?这将特别吸引那些可能有障碍的人,在那里使用键盘将是困难的或不可能的。
在本书的后面部分,当我们构建 Alexa 的(简单)克隆时,我们会看到类似的东西。
-
微调使用的短语——我们使用的短语是有目的的,但我认为还有改进的空间。例如,我们可能会将某些单词缩写,如“我是”缩写成“我是”,但我们的聊天机器人不允许这样做!当然,这可能更多地与我们如何配置聊天机器人有关,但不要忘记,我们在聊天机器人中放入的内容最终会影响它作为语音输出的方式。
-
包括其他产品——重要的是要考虑我们如何才能最好地做到这一点,以及语音合成配置所需的变化;这种改变需要使得将来添加其他产品变得更加容易,并且最大限度地减少麻烦。
我确信,为了开发我们的项目,我们能够或者可能想要做更多的事情,但是现在,我想把注意力集中在一个特别的变化上:增加语言支持。
网络语音 API 的一个伟大之处在于,我们不以任何方式仅限于英语。我们完全可以添加对多种不同语言的支持!为了证明这一点,在我们的下一个演示中,我们将通过添加法语支持来更新原始聊天机器人。让我们更详细地看看需要做哪些更改来实现这次更新。
添加语言支持
对于这个演示,我们将使用原始聊天机器人的现有副本,但添加了语言支持-我选择了法语,因为我会说法语。我们可以很容易地修改代码以使用不同的语言,或者根据需要使用多种语言。我们将通过几个步骤来更新我们的代码。让我们更详细地看看需要什么。
这个演示使用了 www.gosquared.com/resources/flag-icons/
的旗帜图标——如果你喜欢使用不同的东西,你也可以使用你自己的。
更新我们的演示
为了更新我们的演示,我们需要进行四项更改:
-
第一个是更新我们的标记和样式,以便我们添加我们使用的每个国家的旗帜——在本例中,是美国英语和法语。
-
我们需要基于设置一个变量来更新语音合成配置,以接受我们的语言选择。
-
接下来是翻译——我们必须创建一个翻译成每种新语言的
brain.rive
配置文件的版本,并重新配置我们的script.js
文件以适当地导入每个版本。 -
所需的最后一项更改是添加事件处理程序,根据需要将
SpeechSynthesisUtterance.lang
设置为我们选择的语言。
考虑到这一点,让我们开始设置我们的演示吧!如前所述,我们将增加法语语言支持——如果您愿意,可以随意将其更改为另一种语言,但您需要手动更新brain.rive
文件中的翻译文本。
Adding Language Support
开始之前,我们需要做几件事:
-
从本章前面的原始演示中复制一个您创建的
chatbot
文件夹,并将其作为chat language
保存在我们的项目文件夹的根目录下。 -
从本书附带的代码下载副本中,解压
brain config
文件夹,并将内容复制到chat language
文件夹下的js
子文件夹中。这些包含了我们的brain.rive
文件的更新版本,有英语和法语版本。 -
从相同的代码下载中,继续提取
img
文件夹——将其保存在chat language
文件夹中img
文件夹的顶部。这将添加我们将在演示中使用的两个旗帜图标。
一旦你完成了这个,继续这些步骤:
-
我们需要做的第一组改变是在我们的标记中——我们将引入两个标志作为语言选择器。打开
index.html
,查找从<button id="help"...
开始的代码行,然后在它之前添加该代码块:<section class="flags"> <span class="en-us"><img src="img/en-us.png" alt="en-us">EN</span>| <span class="fr-fr"><img src="img/fr-fr.png" alt="fr fr">FR</span> </section>
-
接下来,将 disabled 属性添加到
<button>
标签中,如下所示:<button id="help" disabled>Ask a question</button>
-
继续保存此文件-保持打开状态,但现在可以将其最小化。
-
在这一点上,切换到
scripts.js
文件——我们在这里做了一些改变,从定义一些额外的变量开始。在第一行代码之后,添加如下声明:let bot = new RiveScript(); let langSupport, intro, brains;
-
接下来,我们需要缓存更多的元素作为变量——为此,继续添加以下四行代码,紧跟在
const question =...
行之后:const voiceSelect = document.getElementById('voice'); const english = document.querySelector(".en-us"); const french = document.querySelector(".fr-fr"); const voice = document.querySelector(".voicechoice");
-
现在我们引入了多语言支持,我们不能硬编码我们的初始问候。相反,我们将把它们作为变量提供,所以在前面的代码块之后留下一行,并添加这两个声明:
const enIntro = "Hello. my name is Hazel. How can I be of help?"; const frIntro = "Bonjour. Je m'appelle Hélène. Comment puis-je vois aider?";
-
向下滚动直到到达
speak()
功能。到目前为止,该语言被隐式设置为'en-us'
;这需要改变!为此,查找speakSynthesis.speak
语句,然后修改该函数的最后一部分,如下所示:})[0]; } msg.lang = langSupport; speechSynthesis.speak(msg); }
-
接下来,删除以
const brains = [...
开头的行,替换为:function setLanguage(langUsed, selIndex, langIntro) { voiceSelect.selectedIndex = selIndex; langSupport = langUsed; intro = langIntro; brains = [ './js/brain-' + langSupport + '.rive' ]; question.disabled = false; }
-
我们现在需要添加两个函数来处理当我们点击标志时会发生什么——为此,留下一行,然后放入下面的代码:
english.addEventListener("click", function() { setLanguage('en-us', 3, enIntro); question.innerHTML = "Ask a question"; }); french.addEventListener("click", function() { setLanguage('fr-fr', 8, frIntro); question.innerHTML = "Poser une question"; });
-
在我们完成编辑这个文件之前,还有两个变化要做——下一个变化是改变
botReady()
函数。向下滚动到它,然后按照下面突出显示的内容进行编辑:
```html
function botReady(){
bot.sortReplies();
botReply(intro);
}
```
- 最后要做的改变是相似的——这里我们需要改变我们称呼开场白的方式。向下滚动到问题事件处理程序,然后根据指示更新
speak()
调用:
```html
question.addEventListener("click", function() {
speak(intro);
bot.loadFile(brains + "?" + parseInt(Math.random() * 100000)).then(botReady).catch(botNotReady);
});
```
-
保存文件-我们现在可以最小化它。
-
接下来,启动
styles.css
文件,并在文件末尾添加以下样式:
```html
/* flags */
section.flags {
width: 150px;
float: right;
margin-top: -30px;
}
section.flags img { vertical-align: middle; padding-right: 5px;}
section.flags img:hover { cursor: pointer; }
button { width: 30%; padding: 10px 15px; }
```
- At this point, go ahead and save the file – we can now test the results! For this, browse to
https://speech/chatbot
, then click Ask a question, and start to enter information as shown in the extract in Figure 4-8.

图 4-8
我们更新的演示,现在有法语语言支持
唷!又一个怪物演示!这看起来很多,但实际上大部分代码都是一次性的修改;这将是我们需要适应转换成使用不同语言的代码。
一旦完成,接下来就是简单地添加标志(和它的标记),潜在的一点样式,以及每个附加标志所需的事件处理程序。诚然,我们的代码可以写得更有效,以自动识别新的标志并正确处理它们,但嘿,我们必须从某处开始!
剖析代码
好吧,改变一下策略,我们在最新的更新中涵盖了很多变化,那么这些变化是如何适应的呢?乍一看,看起来我们确实做了一些改动,但实际上,我们的演示并没有什么异常复杂的地方。也就是说,让我们花点时间更详细地回顾一下我们所做的更改。
我们从添加标志标记开始;这是标准的 HTML,用来在聊天机器人的右边显示旗帜图标。我们为此添加了一个容器——将来我们可以很容易地添加更多的行,这些行指向我们想要添加到演示中的任何附加标志。同时,我们为按钮添加了一个禁用的属性——这是为了防止人们在点击其中一个标志之前使用它。
接下来,我们添加了一些额外的变量,其中一些将用于缓存页面上的新标志元素。然后我们添加了两个最重要的变化——第一个是敬礼。我们不能将这些硬编码到我们的演示中,所以我们需要将适当的文本作为变量传入(在本例中是enIntro
和frIntro
)。我们接着在这行字里加上:msg.lang = langSupport
;这阻止了SpeechSynthesis
界面默认假设语言支持是美国英语,当点击我们选择的标志时将会是任何语言。
接下来的三个变化更加重要——这里我们设置了一个通用的 setLanguage()函数,将voiceSelect
下拉菜单更改为我们选择的语言(对于法语,它选择 Google French,依此类推)。然后,我们为 SpeechSynthesis 接口设置适当的 BCP47 代码(例如,"fr-fr"
代表法语),并使用它来定义我们应该使用哪个大脑配置文件(在本例中,应该是brain-fr-fr.rive
)。如果一切正常,我们就从“提问”按钮中删除 disabled 属性,这样我们的客户就可以使用它了。
接下来的两个事件处理程序调用我们刚刚定义的setLanguage()
函数,我们将适当的 BCP47 代码、要使用的语音索引和我们的开场白传递给它。与此同时,我们还更新了“提问”按钮上的文本,根据选择的按钮,文本可以是英语,也可以是法语。虽然它们的工作方式相似,但我们已经设置它们为所选语言传递适当的值——对于我们决定添加到演示中的任何其他语言,这些值都是重复的。
剩下的两个变化非常简单——因为我们不能硬编码开场白,我们必须将文本作为变量传入。这里我们使用了一个通用的 intro 变量,在演示的前面,我们已经将特定语言变量的文本传递给了这个变量。
摘要
聊天机器人是一项肯定会存在的技术——研究表明,它们的使用将在未来几年内呈爆炸式增长,因此确保它们尽可能有效并且客户参与度不会因此下降非常重要!我们已经讨论了一些要点,关于如何在使用聊天机器人时添加语音合成 API 来提供额外的优势;让我们花点时间来回顾一下我们在这一章中学到了什么。
在为本章的项目演示做准备之前,我们从一些基本的理论开始,比如为什么我们应该使用聊天机器人,可以使用的不同类型,以及使用它们的一些缺点。我们花了一点时间来构建我们的演示的各种元素,然后讨论了一些我们将来可能会考虑使用的替代方案。
然后,我们进入了构建我们的机器人的重要阶段——在运行构建和配置我们的机器人的主要步骤之前,我们首先添加了文本编辑器语法支持。构建完成后,我们详细研究了我们创建的代码,包括完成使我们的 bot 正确运行的配置文件。然后,我们总结了这一章,看看我们可以做些什么来改进我们的机器人,特别强调在我们的演示中添加额外的语言支持。
唷!当然是通过聊天机器人的短暂停留之旅!当我们试着构建一个 Alexa 克隆时,我们将在后面重新讨论本章涉及的一些主题,但是让我们做一些要求稍微低一点的事情。你遇到过多少次在网站上留下反馈的请求?通常这可能是通过电子邮件,甚至是评论部分。不过,我们有可能必须以书面形式提供反馈。太老套了。如果我们可以口头上做,然后让网站把它转换成文本呢?是的,这看起来像是极度的懒惰,但是,嘿,我完全支持创新!好奇吗?好吧,听我说,我将在下一章揭示一切。