
Dans la première partie, nous avons défini les principes de base de la conception et du développement d'applications pour l'Assistant Google. Il est maintenant temps d'écrire votre propre assistant afin que les utilisateurs puissent enfin choisir un film pour la soirée. Shipa_o , raenardev et le concepteur de ComradeGuest continuent de parler.
Écrivez votre code
Essayons d'écrire quelque chose de plus compliqué.
Disons que notre agent recommande des films par genre.
Nous lui demandons de "montrer l'horreur", et l'agent analysera le genre, recherchera un film dans la collection par genre et l'affichera à l'écran.
Pour commencer, nous allons stocker la collection de films dans une variable: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("") ]); }
Maintenant, la réponse est devenue plus informative.
Nous affichons du texte, une carte avec des informations et des conseils:

Une bonne caractéristique de Dialogflow est qu'il est prêt à l'emploi et adapté à différents appareils.
Si l'appareil a des haut-parleurs, alors toutes les phrases que nous envoyons à la méthode d'ajout seront exprimées, et s'il n'y a pas d'écran, les objets Card
et Suggestion
ne seront tout simplement pas affichés.
Nous connectons la base de données
Compliquons la tâche et ajoutons l'obtention de données à partir de la base de données (DB).
La façon la plus simple est d'utiliser la base de données en temps réel de Firebase.
Par exemple, nous utiliserons l' API Admin Database.
Vous devez d'abord créer une base de données et la remplir.
Vous pouvez le faire dans le même projet qui a été créé pour les fonctions cloud :

Une fois la base de données pleine, connectez-la à la réalisation: // 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; }); }
L'accès à la base de données nécessite le multithreading. L'API de base de données Firebase est conçue pour utiliser Promise . La .once('value')
nous renvoie une promesse. Ensuite, nous obtenons nos données dans le bloc then()
et retournons Promise avec elles comme résultat de l'exécution de la fonction.
Il est important de renvoyer cette promesse à la méthode handleRequest()
, sinon l'agent se terminera avec notre rappel sans attendre de réponse et traiter le résultat.
Recherche de version de film par genre en utilisant la base de données: '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); }); }
Ajout d'imprévisibilité
Notre compétence produira des films en une seule séquence à chaque fois. Les mêmes réponses ennuieront d'abord l'utilisateur, puis il cessera de parler à notre robot.
Corrigez cela avec la bibliothèque de mixage de tableaux à mélange aléatoire.
Ajoutez la dépendance au fichier package.json
:
"dependencies": { // ... "shuffle-array": "^1.0.1" // ... }
Ajoutez le mélange du tableau: // 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); }); }
Maintenant, chaque fois qu'un nouveau film sortira.
De la même manière, vous pouvez ajouter la sortie de différentes phrases: créez un tableau avec des phrases, mélangez-le et prenez le premier du tableau.
Travailler avec le contexte
Nous demandons à l'agent:
Afficher la fantaisie
L'agent nous montre le film "Le Seigneur des anneaux".
Ensuite, nous demandons:
De quoi parle-t-il?
Les gens ne disent pas: "Quel est le film" Le Seigneur des anneaux "" n'est pas naturel. Par conséquent, nous devons enregistrer des informations sur le film affiché. Cela peut être fait dans le contexte de:
// , agent.setContext({ name: 'current-film', lifespan: 5, parameters: { id: firstFlim.id } });
Ensuite, nous pouvons lire les informations sur le film comme ceci: 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); }); }
De la même manière, nous pouvons filtrer la liste des films déjà montrés.
Intégration de télégramme
Documentation et liens utiles:
Pour s'intégrer à Telegram, presque rien n'est requis, mais plusieurs fonctionnalités doivent être prises en compte.
1) Si en plein affichage de la carte ou de la suggestion, alors dans Telegram, ils fonctionneront également.
Mais il y a un bogue : pour les réponses rapides, vous devez spécifier un titre, sinon "Choisir un élément" sera affiché dans Telegram.
Jusqu'à présent, nous n'avons pas réussi à résoudre le problème de l'indication du titre en intégralité.
2) Si l'intention utilise des puces de suggestion pour Google Assistant

alors la même fonctionnalité pour Telegram peut être implémentée de deux manières:
Réponses rapides

Charge utile personnalisée
Ici, vous pouvez implémenter des réponses rapides à l'aide du clavier principal:

{ "telegram": { "text": " :", "reply_markup": { "keyboard": [ [ "", "", "", "", "" ] ], "one_time_keyboard": true, "resize_keyboard": true } } }
et clavier intégré:

{ "telegram": { "text": " :", "reply_markup": { "inline_keyboard": [ [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }], [{ "text": "", "callback_data": "" }] ] } } }
Le clavier principal enverra un message qui sera stocké dans l'historique, contrairement au clavier intégré.
Il est important de se rappeler que le clavier principal ne disparaît pas avec le temps. Il y a une demande spéciale pour cela dans l'API Telegram. Par conséquent, vous devez vous assurer que l'utilisateur dispose toujours de conseils pertinents.
3) Si vous avez besoin d'une logique différente pour Telegram et Google Assistant, vous pouvez le faire comme ceci:
let intentRequest = request.body.originalDetectIntentRequest; if(intentRequest.source == 'google'){ let conv = agent.conv(); conv.ask(' ?'); agent.add(conv); } else { agent.add(' ?'); }
4) L'envoi d'un fichier audio peut être implémenté comme suit:
{ "telegram": { "text": "https://s0.vocaroo.com/media/download_temp/Vocaroo_s0bXjLT1pSXK.mp3" } }
5) Le contexte dans Dialogflow sera stocké pendant 20 minutes. Vous devez en tenir compte lors de la conception d'un bot Telegram. Si l'utilisateur est distrait pendant 20 minutes, il ne pourra pas continuer au même endroit.
Des exemples
Nous publierons le code source des compétences sous peu. Immédiatement après sa sortie.
PS. Qu'est-il arrivé au hackathon.

C'était 2 jours occupés.
Au début, il y avait des conférences éducatives, et dans l'après-midi, nous avons commencé à mettre en œuvre nos projets.
Le lendemain, nous avons activement révisé les projets et préparé des présentations.
Les gars de Google pendant tout ce temps nous ont aidés et ont répondu à un tas de questions qui se posent inévitablement dans le travail. Ce fut une excellente occasion d'apprendre beaucoup et de laisser des commentaires pendant que le fer est encore chaud.
Merci à tous les participants, aux organisateurs de Google et aux experts qui ont donné des conférences et nous ont aidés tout au long du hackathon!
Au fait, nous avons pris la deuxième place.
Si vous avez des questions, vous pouvez écrire:
shipa_o
raenardev
camarade
Et il y a aussi un chat Telegram dédié à la discussion des interfaces vocales, allez:
https://t.me/conversational_interfaces_ru