
在第一部分中,我们了解了为Google Assistant设计和开发应用程序的基本原理。 现在是时候编写您自己的助手,以便用户最终可以选择当晚的电影。 Shipa_o , raenardev和ComradeGuest设计师继续交谈。
写你的代码
让我们尝试写一些更复杂的东西。
假设我们的经纪人按类型推荐电影。
我们要求他“显示恐怖”,代理将解析该流派,按流派在收藏集中查找电影并将其显示在屏幕上。
首先,我们将电影收藏存储在一个变量中:var json = { "filmsList": [ { "id": "1", "title": " : ", "description": " ", "genres": ["", "", ""], "imageUrl": "http://t3.gstatic.com/images?q=tbn:ANd9GcQEA5a7K9k9ajHIu4Z5AqZr7Y8P7Fgvd4txmQpDrlQY2047coRk", "trailer": "https://www.youtube.com/watch?v=RNksw9VU2BQ" }, { "id": "2", "title": " : 2 – ", "description": " ", "genres": ["", "", "", ""], "imageUrl": "http://t3.gstatic.com/images?q=tbn:ANd9GcTPPAiysdP0Sra8XcIhska4MOq86IaDS_MnEmm6H7vQCaSRwahQ", "trailer": "https://www.youtube.com/watch?v=vX_2QRHEl34" }, { "id": "3", "title": "", "description": " ", "genres": ["", "", ""], "imageUrl": "https://www.kinopoisk.ru/images/film_big/386.jpg", "trailer": "https://www.youtube.com/watch?v=xIe98nyo3xI" } ] };
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { const agent = new WebhookClient({ request, response }); let result = request.body.queryResult; let parameters = result.parameters; let outputContexts = result.outputContexts; let intentMap = new Map(); // agent parameters, intentMap.set('search-by-genre', searchByGenre.bind(this, agent, parameters)); agent.handleRequest(intentMap); }); function searchByGenre(agent, parameters) { let filmsList = json.filmsList; // let filteredFilms = filmsList.filter((film) => { // , return film.genres.some((genre) => genre == parameters.genre); }); // let firstFlim = filteredFilms[0]; // agent.add(firstFlim.title); // agent.add(new Card({ title: firstFlim.title, imageUrl: firstFlim.imageUrl, text: firstFlim.description, buttonText: ' ', buttonUrl: firstFlim.trailer })); // agent.add([ " ?", new Suggestion(" ?"), new Suggestion(""), new Suggestion("") ]); }
现在答案变得更加有用。
我们显示文字,带有信息和提示的卡片:

Dialogflow的一个很好的功能是,它开箱即用,适用于不同的设备。
如果设备有扬声器,那么将发送给我们发送给add方法的所有短语,如果没有屏幕,则不会显示Card
和Suggestion
对象。
我们连接数据库
让我们使任务复杂化,并添加从数据库(DB)获取数据。
最简单的方法是使用Firebase实时数据库。
例如,我们将使用管理数据库API。
首先,您需要创建一个数据库并填充它。
您可以在为Cloud Functions创建的同一项目中执行此操作:

数据库填满后,将其连接到实现: // firebase-admin const firebaseAdmin = require('firebase-admin'); // firebaseAdmin firebaseAdmin.initializeApp({ credential: firebaseAdmin.credential.applicationDefault(), databaseURL: 'https://<ID->.firebaseio.com' }); // , function getFilmsList() { return firebaseAdmin .database() .ref() .child('filmsList') .once('value') .then(snapshot => { const filmsList = snapshot.val(); console.log('filmsList: ' + JSON.stringify(filmsList)); return filmsList; }) .catch(error => { console.log('getFilmsList error: ' + error); return error; }); }
访问数据库需要多线程。 firebase数据库API设计为使用Promise 。 .once('value')
方法返回一个Promise。 然后,我们在then()
块中获取数据,并随函数执行的结果一起返回Promise。
将这个Promise返回到handleRequest()
方法很重要,否则代理将退出我们的回调而无需等待响应和处理结果。
使用数据库按流派按版本搜索电影: 'use strict'; const functions = require('firebase-functions'); const firebaseAdmin = require('firebase-admin'); const { WebhookClient } = require('dialogflow-fulfillment'); const { Card, Suggestion } = require('dialogflow-fulfillment'); firebaseAdmin.initializeApp({ credential: firebaseAdmin.credential.applicationDefault(), databaseURL: 'https://<ID->.firebaseio.com' }); exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { const agent = new WebhookClient({ request, response }); let result = request.body.queryResult; let parameters = result.parameters; let outputContexts = result.outputContexts; let intentMap = new Map(); intentMap.set('search-by-genre', searchByGenre.bind(this, agent, parameters)); agent.handleRequest(intentMap); }); function getFilmsList() { return firebaseAdmin .database() .ref() .child('filmsList') .once('value') .then(snapshot => { const filmsList = snapshot.val(); console.log('filmsList: ' + JSON.stringify(filmsList)); return filmsList; }) .catch(error => { console.log('getFilmsList error: ' + error); return error; }); } function searchByGenre(agent, parameters) { return getFilmsList() .then(filmsList => { let filteredFilms = filmsList.filter((film) => { return film.genres.some((genre) => genre == parameters.genre); }); let firstFlim = filteredFilms[0]; agent.add(firstFlim.title); agent.add(new Card({ title: firstFlim.title, imageUrl: firstFlim.imageUrl, text: firstFlim.description, buttonText: ' ', buttonUrl: firstFlim.trailer })); agent.add([ " ?", new Suggestion(" ?"), new Suggestion(""), new Suggestion("") ]); }) .catch(error => { console.log('getFilmsList error' + error); }); }
增加不可预测性
我们的技能每次都会以一个顺序制作电影。 相同的答案会首先使用户烦恼,然后他将停止与我们的机器人交谈。
使用shuffle-array数组混合库修复此问题。
将依赖项添加到package.json
文件:
"dependencies": { // ... "shuffle-array": "^1.0.1" // ... }
添加数组的混合: // const shuffle = require('shuffle-array'); function searchByGenre(agent, parameters) { return getFilmsList() .then(filmsList => { let filteredFilms = filmsList.filter((film) => { return film.genres.some((genre) => genre == parameters.genre); }); // shuffle(filteredFilms); let firstFlim = filteredFilms[0]; agent.add(firstFlim.title); agent.add(new Card({ title: firstFlim.title, imageUrl: firstFlim.imageUrl, text: firstFlim.description, buttonText: ' ', buttonUrl: firstFlim.trailer })); agent.add([ " ?", new Suggestion(" ?"), new Suggestion(""), new Suggestion("") ]); }) .catch(error => { console.log('getFilmsList error' + error); }); }
现在每次发行新电影。
以相同的方式,您可以添加不同短语的输出:创建一个包含短语的数组,将其混合并获取数组的第一个。
处理上下文
我们要求代理商:
显示幻想
代理商向我们展示了电影《指环王》。
然后我们问:
他在说什么
人们没有说:“电影是什么电影《指环王》”是不自然的。 因此,我们需要保存有关所显示电影的信息。 这可以在以下情况下完成:
// , agent.setContext({ name: 'current-film', lifespan: 5, parameters: { id: firstFlim.id } });
然后我们可以像这样阅读有关电影的信息: function genreSearchDescription(agent) { // current-film const context = agent.getContext('current-film'); console.log('context current-film: ' + JSON.stringify(context)); // id const currentFilmId = context.parameters.id; // return getFilmsList() .then(filmsList => { // id const currentFilm = filmsList.filter(film => film.id === currentFilmId); agent.add(currentFilm[0].description); agent.add([ ' ?', new Suggestion(''), new Suggestion(' ') ]); }) .catch(error => { console.log('getFilmsList error:' + error); }); }
同样,我们可以过滤已经显示的电影列表。
电报整合
文档和有用的链接:
要与Telegram集成,几乎不需要任何功能,但是需要考虑几个功能。
1)如果显示了卡片或建议,则在电报中它们也将起作用。
但是有一个错误 :要快速答复,您必须指定标题,否则“ Telegram”中将显示“ Choose a item”。
到目前为止,我们还没有成功解决标题显示为完整的问题。
2)如果意图使用Google助手的建议芯片

那么可以通过两种方式实现Telegram的相同功能:
快速回复

自定义有效载荷
在这里,您可以使用主键盘实现快速解答:

{ "telegram": { "text": " :", "reply_markup": { "keyboard": [ [ "", "", "", "", "" ] ], "one_time_keyboard": true, "resize_keyboard": true } } }
和内置键盘:

{ "telegram": { "text": " :", "reply_markup": { "inline_keyboard": [ [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }] ] } } }
主键盘将发送一条将存储在历史记录中的消息,而内置键盘则不会。
重要的是要记住,主键盘不会随着时间消失。 Telegram API中对此有特殊要求 。 因此,您需要确保用户始终具有相关的提示。
3)如果您需要Telegram和Google Assistant不同的逻辑,则可以这样做:
let intentRequest = request.body.originalDetectIntentRequest; if(intentRequest.source == 'google'){ let conv = agent.conv(); conv.ask(' ?'); agent.add(conv); } else { agent.add(' ?'); }
4)发送音频文件可以实现如下:
{ "telegram": { "text": "https://s0.vocaroo.com/media/download_temp/Vocaroo_s0bXjLT1pSXK.mp3" } }
5)Dialogflow中的上下文将存储20分钟。 设计Telegram机器人时,您需要考虑这一点。 如果用户分心20分钟,那么他将无法从同一位置继续。
例子
我们将很快发布技能源代码。 发布后立即。
PS。 黑客马拉松发生了什么。

忙了两天。
最初有教育讲座,下午我们开始实施我们的项目。
第二天是积极修改项目并准备演示文稿。
谷歌一直以来都在为我们提供帮助,并回答了工作中不可避免出现的一系列问题。 这是一个很好的机会,可以学到很多东西,并在铁杆仍然很热的时候留下反馈。
感谢所有参与者,Google的组织者和专家们,他们在整个hackathon上做了演讲并为我们提供了帮助!
顺便说一下,我们获得了第二名。
如有疑问,可以写:
希帕
拉纳尔代夫
战友
还有一个专门讨论语音接口的电报聊天,请转到:
https://t.me/conversational_interfaces_ru