Intégration d'un agent Dialogflow dans une application React
Publié: 2022-03-10Dialogflow est une plate-forme qui simplifie le processus de création et de conception d'un assistant de chat conversationnel de traitement du langage naturel qui peut traiter la saisie vocale ou textuelle lorsqu'il est utilisé à partir de la console Dialogflow ou d'une application Web intégrée.
Bien que l'agent Dialogflow intégré soit brièvement expliqué dans cet article, vous devez comprendre Node.js et Dialogflow. Si vous découvrez Dialogflow pour la première fois, cet article explique clairement ce qu'est Dialogflow et ses concepts.
Cet article est un guide expliquant comment créer un agent Dialogflow avec prise en charge de la voix et du chat pouvant être intégré dans une application Web à l'aide d'une application back-end Express.js en tant que lien entre une application Web React.js et l'agent. sur Dialogflow lui-même. À la fin de l'article, vous devriez être en mesure de connecter votre propre agent Dialogflow à votre application Web préférée.
Pour rendre ce guide facile à suivre, vous pouvez passer à la partie du didacticiel qui vous intéresse le plus ou les suivre dans l'ordre suivant au fur et à mesure qu'elles apparaissent :
- Configurer un agent Dialogflow
- Intégrer un agent Dialogflow
- Configuration d'une application Node Express
- Authentification avec Dialogflow
- Que sont les comptes de service ?
- Gestion des entrées vocales
- Intégration dans une application Web
- Créer une interface de chat
- Enregistrement de la saisie vocale de l'utilisateur
- Conclusion
- Les références
1. Configurer un agent Dialogflow
Comme expliqué dans cet article, un assistant de chat sur Dialogflow s'appelle un agent et comprend des composants plus petits tels que les intentions, la réalisation, la base de connaissances et bien plus encore. Dialogflow fournit une console permettant aux utilisateurs de créer, former et concevoir le flux de conversation d'un agent. Dans notre cas d'utilisation, nous restaurerons un agent qui a été exporté dans un dossier ZIP après avoir été formé, à l'aide de la fonctionnalité d'exportation et d'importation de l'agent.
Avant d'effectuer l'importation, nous devons créer un nouvel agent qui sera fusionné avec l'agent sur le point d'être restauré. Pour créer un nouvel agent à partir de la console, un nom unique est nécessaire ainsi qu'un projet sur Google Cloud auquel lier l'agent. S'il n'y a pas de projet existant sur Google Cloud avec lequel lier, un nouveau peut être créé ici.
Un agent a été préalablement créé et formé pour recommander des produits viticoles à un utilisateur en fonction de son budget. Cet agent a été exporté dans un ZIP ; vous pouvez télécharger le dossier ici et le restaurer dans notre agent nouvellement créé à partir de l'onglet Exporter et importer de la page Paramètres de l'agent.
L'agent importé a été préalablement formé pour recommander un produit vinicole à l'utilisateur en fonction du budget de l'utilisateur pour l'achat d'une bouteille de vin.
En parcourant l'agent importé, nous verrons qu'il a trois intentions créées à partir de la page des intentions. L'une étant une intention de secours, utilisée lorsque l'agent ne reconnaît pas l'entrée d'un utilisateur, l'autre est une intention de bienvenue utilisée lorsqu'une conversation avec l'agent est lancée, et la dernière intention est utilisée pour recommander un vin à l'utilisateur en fonction de la paramètre de quantité dans la phrase. Ce qui nous préoccupe, c'est l'intention get-wine-recommendation
Cet intent a un seul contexte d'entrée de wine-recommendation
provenant de l'intent de bienvenue par défaut pour lier la conversation à cet intent.
"Un contexte est un système au sein d'un agent utilisé pour contrôler le flux d'une conversation d'une intention à l'autre."
Sous les contextes se trouvent les phrases d'entraînement, qui sont des phrases utilisées pour former un agent sur le type d'instructions à attendre d'un utilisateur. Grâce à une grande variété de phrases d'entraînement dans une intention, un agent est capable de reconnaître la phrase d'un utilisateur et l'intention dans laquelle elle se situe.
Les phrases de formation dans l'intention de nos agents get-wine-recommendation
(comme indiqué ci-dessous) indiquent le choix de vin et la catégorie de prix :
En regardant l'image ci-dessus, nous pouvons voir les phrases d'entraînement disponibles répertoriées et le chiffre de la devise est surligné en jaune pour chacune d'entre elles. Cette mise en surbrillance est connue sous le nom d'annotation sur Dialogflow et elle est automatiquement effectuée pour extraire les types de données reconnus connus sous le nom d'entité à partir de la phrase d'un utilisateur.
Une fois que cette intention a été mise en correspondance dans une conversation avec l'agent, une requête HTTP sera envoyée à un service externe pour obtenir le vin recommandé en fonction du prix extrait en tant que paramètre de la phrase d'un utilisateur, grâce à l'utilisation du webhook activé trouvé dans la section Exécution au bas de cette page d'intention.
Nous pouvons tester l'agent à l'aide de l'émulateur Dialogflow situé dans la section droite de la console Dialogflow. Pour tester, nous commençons la conversation avec un message « Salut » et poursuivons avec la quantité de vin souhaitée. Le webhook sera immédiatement appelé et une réponse riche similaire à celle ci-dessous sera affichée par l'agent.
À partir de l'image ci-dessus, nous pouvons voir l'URL du webhook générée à l'aide de Ngrok et la réponse de l'agent sur le côté droit montrant un vin dans la fourchette de prix de 20 $ tapée par l'utilisateur.
À ce stade, l'agent Dialogflow est entièrement configuré. Nous pouvons maintenant commencer à intégrer cet agent dans une application Web pour permettre à d'autres utilisateurs d'accéder et d'interagir avec l'agent sans accéder à notre console Dialogflow.
Intégrer un agent Dialogflow
Bien qu'il existe d'autres moyens de se connecter à un agent Dialogflow, tels que l'envoi de requêtes HTTP à ses points de terminaison REST, la méthode recommandée pour se connecter à Dialogflow consiste à utiliser sa bibliothèque cliente officielle disponible dans plusieurs langages de programmation. Pour JavaScript, le package @google-cloud/dialogflow peut être installé à partir de NPM.
En interne, le package @google-cloud/dialogflow utilise gRPC pour ses connexions réseau, ce qui rend le package non pris en charge dans un environnement de navigateur, sauf lorsqu'il est corrigé à l'aide de webpack, la méthode recommandée pour utiliser ce package est à partir d'un environnement Node. Nous pouvons le faire en configurant une application back-end Express.js pour utiliser ce package, puis fournir des données à l'application Web via ses points de terminaison API et c'est ce que nous ferons ensuite.
Configuration d'une application Node Express
Pour configurer une application express, nous créons un nouveau répertoire de projet, puis récupérons les dépendances nécessaires à l'aide de yarn
à partir d'un terminal de ligne de commande ouvert.
# create a new directory and ( && ) move into directory mkdir dialogflow-server && cd dialogflow-server # create a new Node project yarn init -y # Install needed packages yarn add express cors dotenv uuid
Une fois les dépendances nécessaires installées, nous pouvons procéder à la configuration d'un serveur Express.js très léger qui gère les connexions sur un port spécifié avec la prise en charge CORS activée pour l'application Web.
// index.js const express = require("express") const dotenv = require("dotenv") const cors = require("cors") dotenv.config(); const app = express(); const PORT = process.env.PORT || 5000; app.use(cors()); app.listen(PORT, () => console.log(` server running on port ${PORT}`));
Lorsqu'il est exécuté, le code dans l'extrait ci-dessus démarre un serveur HTTP qui écoute les connexions sur un PORT Express.js spécifié. Le partage de ressources cross-origin (CORS) est également activé sur toutes les requêtes utilisant le package cors en tant que middleware Express. Pour l'instant, ce serveur n'écoute que les connexions, il ne peut pas répondre à une requête car il n'a pas de route créée, alors créons ceci.
Nous devons maintenant ajouter deux nouvelles routes : une pour envoyer des données textuelles et l'autre pour envoyer une entrée vocale enregistrée. Ils accepteront tous les deux une requête POST
et enverront ultérieurement les données contenues dans le corps de la requête à l'agent Dialogflow.
const express = require("express") const app = express() app.post("/text-input", (req, res) => { res.status(200).send({ data : "TEXT ENDPOINT CONNECTION SUCCESSFUL" }) }); app.post("/voice-input", (req, res) => { res.status(200).send({ data : "VOICE ENDPOINT CONNECTION SUCCESSFUL" }) }); module.exports = app
Ci-dessus, nous avons créé une instance de routeur distincte pour les deux routes POST
créées qui, pour l'instant, ne répondent qu'avec un code d'état 200
et une réponse factice codée en dur. Lorsque nous aurons fini de nous authentifier avec Dialogflow, nous pourrons revenir pour implémenter une connexion réelle à Dialogflow dans ces points de terminaison.
Pour la dernière étape de la configuration de notre application backend, nous montons l'instance de routeur créée précédemment dans l'application Express à l'aide de app.use et d'un chemin de base pour la route.
// agentRoutes.js const express = require("express") const dotenv = require("dotenv") const cors = require("cors") const Routes = require("./routes") dotenv.config(); const app = express(); const PORT = process.env.PORT || 5000; app.use(cors()); app.use("/api/agent", Routes); app.listen(PORT, () => console.log(` server running on port ${PORT}`));
Ci-dessus, nous avons ajouté un chemin de base aux deux routes, nous pouvons tester n'importe laquelle d'entre elles via une requête POST
en utilisant cURL à partir d'une ligne de commande comme c'est fait ci-dessous avec un corps de requête vide ;
curl -X https://localhost:5000/api/agent/text-response
Une fois la requête ci-dessus terminée avec succès, nous pouvons nous attendre à voir une réponse contenant des données d'objet imprimées sur la console.
Il nous reste maintenant à établir une connexion réelle avec Dialogflow, ce qui inclut la gestion de l'authentification, l'envoi et la réception de données de l'agent sur Dialogflow à l'aide du package @google-cloud/dialogflow.
Authentification avec Dialogflow
Chaque agent Dialogflow créé est lié à un projet sur Google Cloud. Pour se connecter en externe à l'agent Dialogflow, nous nous authentifions auprès du projet sur le cloud Google et utilisons Dialogflow comme l'une des ressources du projet. Parmi les six méthodes disponibles pour se connecter à un projet sur Google Cloud, l'utilisation de l'option Comptes de service est la plus pratique lors de la connexion à un service particulier sur Google Cloud via sa bibliothèque cliente.
Remarque : Pour les applications prêtes pour la production, l'utilisation de clés API de courte durée est recommandée plutôt que les clés de compte de service afin de réduire le risque qu'une clé de compte de service tombe entre de mauvaises mains.
Que sont les comptes de service ?
Les comptes de service sont un type spécial de compte sur Google Cloud, créé pour une interaction non humaine, principalement via des API externes. Dans notre application, le compte de service sera accessible via une clé générée par la bibliothèque cliente Dialogflow pour s'authentifier auprès de Google Cloud.
La documentation cloud sur la création et la gestion des comptes de service fournit un excellent guide pour créer un compte de service. Lors de la création du compte de service, le rôle d'administrateur de l'API Dialogflow doit être attribué au compte de service créé, comme indiqué à la dernière étape. Ce rôle donne au compte de service le contrôle administratif sur l'agent Dialogflow associé.
Pour utiliser le compte de service, nous devons créer une clé de compte de service. Les étapes suivantes expliquent comment en créer un au format JSON :
- Cliquez sur le compte de service nouvellement créé pour accéder à la page du compte de service.
- Faites défiler jusqu'à la section Clés et cliquez sur le menu déroulant Ajouter une clé et cliquez sur l'option Créer une nouvelle clé qui ouvre un modal.
- Sélectionnez un format de fichier JSON et cliquez sur le bouton Créer en bas à droite du modal.
Remarque : Il est recommandé de garder une clé de compte de service privée et de ne pas la valider dans un système de contrôle de version, car elle contient des données très sensibles concernant un projet sur Google Cloud. Cela peut être fait en ajoutant le fichier au fichier .gitignore
.
Avec un compte de service créé et une clé de compte de service disponible dans le répertoire de notre projet, nous pouvons utiliser la bibliothèque cliente Dialogflow pour envoyer et recevoir des données de l'agent Dialogflow.
// agentRoute.js require("dotenv").config(); const express = require("express") const Dialogflow = require("@google-cloud/dialogflow") const { v4 as uuid } = require("uuid") const Path = require("path") const app = express(); app.post("/text-input", async (req, res) => { const { message } = req.body; // Create a new session const sessionClient = new Dialogflow.SessionsClient({ keyFilename: Path.join(__dirname, "./key.json"), }); const sessionPath = sessionClient.projectAgentSessionPath( process.env.PROJECT_ID, uuid() ); // The dialogflow request object const request = { session: sessionPath, queryInput: { text: { // The query to send to the dialogflow agent text: message, }, }, }; // Sends data from the agent as a response try { const responses = await sessionClient.detectIntent(request); res.status(200).send({ data: responses }); } catch (e) { console.log(e); res.status(422).send({ e }); } }); module.exports = app;
L'ensemble de la route ci-dessus envoie des données à l'agent Dialogflow et reçoit une réponse via les étapes suivantes.
- Première
Il s'authentifie auprès du cloud Google puis crée une session avec Dialogflow en utilisant le projectID du projet cloud Google lié à l'agent Dialogflow et également un ID aléatoire pour identifier la session créée. Dans notre application, nous créons un identifiant UUID sur chaque session créée à l'aide du package JavaScript UUID. Ceci est très utile lors de la journalisation ou du suivi de toutes les conversations gérées par un agent Dialogflow. - Seconde
Nous créons des données d'objet de requête en suivant le format spécifié dans la documentation Dialogflow. Cet objet de requête contient la session créée et les données de message obtenues à partir du corps de la requête qui doivent être transmises à l'agent Dialogflow. - La troisième
À l'aide de la méthodedetectIntent
de la session Dialogflow, nous envoyons l'objet de requête de manière asynchrone et attendons la réponse de l'agent à l'aide de la syntaxe ES6 async / await dans un bloc try-catch si la méthodedetectIntent
renvoie une exception, nous pouvons attraper l'erreur et la renvoyer, plutôt que de planter toute l'application. Un exemple de l'objet de réponse renvoyé par l'agent est fourni dans la documentation de Dialogflow et peut être inspecté pour savoir comment extraire les données de l'objet.
Nous pouvons utiliser Postman pour tester la connexion Dialogflow implémentée ci-dessus dans la dialogflow-response
. Postman est une plate-forme de collaboration pour le développement d'API avec des fonctionnalités permettant de tester les API intégrées aux étapes de développement ou de production.
Remarque : si elle n'est pas déjà installée, l'application de bureau Postman n'est pas nécessaire pour tester une API. À partir de septembre 2020, le client Web de Postman est passé à un état généralement disponible (GA) et peut être utilisé directement depuis un navigateur.
À l'aide du client Web Postman, nous pouvons soit créer un nouvel espace de travail, soit en utiliser un existant pour créer une demande POST
à notre point de terminaison API à https://localhost:5000/api/agent/text-input
et ajouter des données avec une clé de message
et la valeur de « Hi There » dans les paramètres de requête.
Au clic du bouton Envoyer , une requête POST
sera faite au serveur Express en cours d'exécution — avec une réponse similaire à celle montrée dans l'image ci-dessous :
Dans l'image ci-dessus, nous pouvons voir les données de réponse embellies de l'agent Dialogflow via le serveur Express. Les données renvoyées sont formatées selon l'exemple de définition de réponse fourni dans la documentation du Webhook Dialogflow.
Gestion des entrées vocales
Par défaut, tous les agents Dialogflow sont activés pour traiter à la fois les données textuelles et audio et renvoient également une réponse au format texte ou audio. Cependant, travailler avec des données d'entrée ou de sortie audio peut être un peu plus complexe que des données textuelles.
Pour gérer et traiter les entrées vocales, nous allons commencer la mise en œuvre du point de terminaison /voice-input
que nous avons précédemment créé afin de recevoir des fichiers audio et de les envoyer à Dialogflow en échange d'une réponse de l'Agent :
// agentRoutes.js import { pipeline, Transform } from "stream"; import busboy from "connect-busboy"; import util from "promisfy" import Dialogflow from "@google-cloud/dialogflow" const app = express(); app.use( busboy({ immediate: true, }) ); app.post("/voice-input", (req, res) => { const sessionClient = new Dialogflow.SessionsClient({ keyFilename: Path.join(__dirname, "./recommender-key.json"), }); const sessionPath = sessionClient.projectAgentSessionPath( process.env.PROJECT_ID, uuid() ); // transform into a promise const pump = util.promisify(pipeline); const audioRequest = { session: sessionPath, queryInput: { audioConfig: { audioEncoding: "AUDIO_ENCODING_OGG_OPUS", sampleRateHertz: "16000", languageCode: "en-US", }, singleUtterance: true, }, }; const streamData = null; const detectStream = sessionClient .streamingDetectIntent() .on("error", (error) => console.log(error)) .on("data", (data) => { streamData = data.queryResult }) .on("end", (data) => { res.status(200).send({ data : streamData.fulfillmentText }} }) detectStream.write(audioRequest); try { req.busboy.on("file", (_, file, filename) => { pump( file, new Transform({ objectMode: true, transform: (obj, _, next) => { next(null, { inputAudio: obj }); }, }), detectStream ); }); } catch (e) { console.log(`error : ${e}`); } });
Dans une vue d'ensemble élevée, la route /voice-input
ci-dessus reçoit l'entrée vocale d'un utilisateur sous la forme d'un fichier contenant le message prononcé à l'assistant de chat et l'envoie à l'agent Dialogflow. Pour mieux comprendre ce processus, nous pouvons le décomposer en étapes plus petites suivantes :
- Tout d'abord, nous ajoutons et utilisons connect-busboy en tant que middleware Express pour analyser les données de formulaire envoyées dans la demande de l'application Web. Après quoi, nous nous authentifions auprès de Dialogflow à l'aide de la clé de service et créons une session, de la même manière que nous l'avons fait dans la route précédente.
Ensuite, en utilisant la méthode promisify du module util Node.js intégré, nous obtenons et enregistrons une promesse équivalente à la méthode de pipeline Stream à utiliser ultérieurement pour diriger plusieurs flux et également effectuer un nettoyage une fois les flux terminés. - Ensuite, nous créons un objet de requête contenant la session d'authentification Dialogflow et une configuration pour le fichier audio sur le point d'être envoyé à Dialogflow. L'objet de configuration audio imbriqué permet à l'agent Dialogflow d'effectuer une conversion de la parole en texte sur le fichier audio envoyé.
- Ensuite, à l'aide de la session créée et de l'objet de requête, nous détectons l'intention d'un utilisateur à partir du fichier audio à l'aide de la méthode
detectStreamingIntent
qui ouvre un nouveau flux de données de l'agent Dialogflow à l'application backend. Les données seront renvoyées en petits bits via ce flux et en utilisant les données « événement » du flux lisible, nous stockons les données dans la variablestreamData
pour une utilisation ultérieure. Une fois le flux fermé, l'événement " end " est déclenché et nous envoyons la réponse de l'agent Dialogflow stockée dans la variablestreamData
à l'application Web. - Enfin, en utilisant l'événement de flux de fichier de connect-busboy, nous recevons le flux du fichier audio envoyé dans le corps de la requête et nous le transmettons ensuite dans l'équivalent promis de Pipeline que nous avons créé précédemment. La fonction de ceci est de diriger le flux de fichiers audio provenant de la demande vers le flux Dialogflow, nous dirigeons le flux de fichiers audio vers le flux ouvert par la méthode
detectStreamingIntent
ci-dessus.
Pour tester et confirmer que les étapes ci-dessus fonctionnent comme indiqué, nous pouvons envoyer une demande de test contenant un fichier audio dans le corps de la demande au point de terminaison /voice-input
à l'aide de Postman.
Le résultat Postman ci-dessus montre la réponse obtenue après avoir fait une requête POST avec les données de formulaire d'un message vocal enregistré disant « Hi » inclus dans le corps de la requête.
À ce stade, nous avons maintenant une application Express.js fonctionnelle qui envoie et reçoit des données de Dialogflow, les deux parties de cet article sont terminées. Où en est-il maintenant de l'intégration de cet Agent dans une application web en consommant les API créées ici à partir d'une application Reactjs.
Intégration dans une application Web
Pour consommer notre API REST construite, nous allons étendre cette application React.js existante qui a déjà une page d'accueil montrant une liste de vins extraite d'une API et un support pour les décorateurs utilisant le plugin babel propose des décorateurs. Nous allons le refactoriser un peu en introduisant Mobx pour la gestion de l'état et également une nouvelle fonctionnalité pour recommander un vin à partir d'un composant de chat en utilisant les points de terminaison de l'API REST ajoutés à partir de l'application Express.js.
Pour commencer, nous commençons à gérer l'état de l'application à l'aide de MobX lorsque nous créons un magasin Mobx avec quelques valeurs observables et certaines méthodes en tant qu'actions.
// store.js import Axios from "axios"; import { action, observable, makeObservable, configure } from "mobx"; const ENDPOINT = process.env.REACT_APP_DATA_API_URL; class ApplicationStore { constructor() { makeObservable(this); } @observable isChatWindowOpen = false; @observable isLoadingChatMessages = false; @observable agentMessages = []; @action setChatWindow = (state) => { this.isChatWindowOpen = state; }; @action handleConversation = (message) => { this.isLoadingChatMessages = true; this.agentMessages.push({ userMessage: message }); Axios.post(`${ENDPOINT}/dialogflow-response`, { message: message || "Hi", }) .then((res) => { this.agentMessages.push(res.data.data[0].queryResult); this.isLoadingChatMessages = false; }) .catch((e) => { this.isLoadingChatMessages = false; console.log(e); }); }; } export const store = new ApplicationStore();
Ci-dessus, nous avons créé un magasin pour la fonctionnalité de composant de chat dans l'application avec les valeurs suivantes :
-
isChatWindowOpen
La valeur stockée ici contrôle la visibilité du composant de chat où les messages de Dialogflow sont affichés. -
isLoadingChatMessages
Ceci est utilisé pour afficher un indicateur de chargement lorsqu'une demande de récupération d'une réponse de l'agent Dialogflow est effectuée. -
agentMessages
Ce tableau stocke toutes les réponses provenant des requêtes effectuées pour obtenir une réponse de l'agent Dialogflow. Les données du tableau sont ensuite affichées dans le composant. -
handleConversation
Cette méthode décorée comme une action ajoute des données dans le tableauagentMessages
. Tout d'abord, il ajoute le message de l'utilisateur transmis en tant qu'argument, puis envoie une requête à l'aide d'Axios à l'application backend pour obtenir une réponse de Dialogflow. Une fois la demande résolue, il ajoute la réponse de la demande dans le tableauagentMessages
.
Remarque : En l'absence de prise en charge des décorateurs dans une application, MobX fournit makeObservable qui peut être utilisé dans le constructeur de la classe de magasin cible. Voir un exemple ici .
Avec la configuration du magasin, nous devons envelopper toute l'arborescence de l'application avec le composant d'ordre supérieur du fournisseur MobX en commençant par le composant racine dans le fichier index.js
.
import React from "react"; import { Provider } from "mobx-react"; import { store } from "./state/"; import Home from "./pages/home"; function App() { return ( <Provider ApplicationStore={store}> <div className="App"> <Home /> </div> </Provider> ); } export default App;
Ci-dessus, nous encapsulons le composant racine de l'application avec le fournisseur MobX et nous transmettons le magasin précédemment créé comme l'une des valeurs du fournisseur. Nous pouvons maintenant procéder à la lecture à partir du magasin dans les composants connectés au magasin.
Créer une interface de chat
Pour afficher les messages envoyés ou reçus à partir des requêtes API, nous avons besoin d'un nouveau composant avec une interface de chat affichant les messages répertoriés. Pour ce faire, nous créons un nouveau composant pour afficher d'abord certains messages codés en dur, puis plus tard, nous affichons les messages dans une liste ordonnée.
// ./chatComponent.js import React, { useState } from "react"; import { FiSend, FiX } from "react-icons/fi"; import "../styles/chat-window.css"; const center = { display: "flex", jusitfyContent: "center", alignItems: "center", }; const ChatComponent = (props) => { const { closeChatwindow, isOpen } = props; const [Message, setMessage] = useState(""); return ( <div className="chat-container"> <div className="chat-head"> <div style={{ ...center }}> <h5> Zara </h5> </div> <div style={{ ...center }} className="hover"> <FiX onClick={() => closeChatwindow()} /> </div> </div> <div className="chat-body"> <ul className="chat-window"> <li> <div className="chat-card"> <p>Hi there, welcome to our Agent</p> </div> </li> </ul> <hr style={{ background: "#fff" }} /> <form onSubmit={(e) => {}} className="input-container"> <input className="input" type="text" onChange={(e) => setMessage(e.target.value)} value={Message} placeholder="Begin a conversation with our agent" /> <div className="send-btn-ctn"> <div className="hover" onClick={() => {}}> <FiSend style={{ transform: "rotate(50deg)" }} /> </div> </div> </form> </div> </div> ); }; export default ChatComponent
Le composant ci-dessus contient le balisage HTML de base nécessaire pour une application de chat. Il a un en-tête indiquant le nom de l'agent et une icône pour fermer la fenêtre de chat, une bulle de message contenant un texte codé en dur dans une balise de liste, et enfin un champ de saisie ayant un gestionnaire d'événements onChange
attaché au champ de saisie pour stocker le texte tapé dans l'état local du composant à l'aide de useState de React.
À partir de l'image ci-dessus, le composant de chat fonctionne comme il se doit, affichant une fenêtre de chat stylée ayant un seul message de chat et le champ de saisie en bas. Cependant, nous souhaitons que le message affiché soit les réponses réelles obtenues à partir de la demande d'API et non du texte codé en dur.
Nous allons de l'avant pour refactoriser le composant Chat, cette fois en connectant et en utilisant les valeurs du magasin MobX au sein du composant.
// ./components/chatComponent.js import React, { useState, useEffect } from "react"; import { FiSend, FiX } from "react-icons/fi"; import { observer, inject } from "mobx-react"; import { toJS } from "mobx"; import "../styles/chat-window.css"; const center = { display: "flex", jusitfyContent: "center", alignItems: "center", }; const ChatComponent = (props) => { const { closeChatwindow, isOpen } = props; const [Message, setMessage] = useState(""); const { handleConversation, agentMessages, isLoadingChatMessages, } = props.ApplicationStore; useEffect(() => { handleConversation(); return () => handleConversation() }, []); const data = toJS(agentMessages); return ( <div className="chat-container"> <div className="chat-head"> <div style={{ ...center }}> <h5> Zara {isLoadingChatMessages && "is typing ..."} </h5> </div> <div style={{ ...center }} className="hover"> <FiX onClick={(_) => closeChatwindow()} /> </div> </div> <div className="chat-body"> <ul className="chat-window"> {data.map(({ fulfillmentText, userMessage }) => ( <li> {userMessage && ( <div style={{ display: "flex", justifyContent: "space-between", }} > <p style={{ opacity: 0 }}> . </p> <div key={userMessage} style={{ background: "red", color: "white", }} className="chat-card" > <p>{userMessage}</p> </div> </div> )} {fulfillmentText && ( <div style={{ display: "flex", justifyContent: "space-between", }} > <div key={fulfillmentText} className="chat-card"> <p>{fulfillmentText}</p> </div> <p style={{ opacity: 0 }}> . </p> </div> )} </li> ))} </ul> <hr style={{ background: "#fff" }} /> <form onSubmit={(e) => { e.preventDefault(); handleConversation(Message); }} className="input-container" > <input className="input" type="text" onChange={(e) => setMessage(e.target.value)} value={Message} placeholder="Begin a conversation with our agent" /> <div className="send-btn-ctn"> <div className="hover" onClick={() => handleConversation(Message)} > <FiSend style={{ transform: "rotate(50deg)" }} /> </div> </div> </form> </div> </div> ); }; export default inject("ApplicationStore")(observer(ChatComponent));
À partir des parties en surbrillance du code ci-dessus, nous pouvons voir que l'ensemble du composant de chat a maintenant été modifié pour effectuer les nouvelles opérations suivantes ;
- Il a accès aux valeurs du magasin MobX après avoir injecté la valeur
ApplicationStore
. Le composant a également été transformé en observateur de ces valeurs de stockage afin qu'il soit restitué lorsque l'une des valeurs change. - Nous commençons la conversation avec l'agent immédiatement après l'ouverture du composant de chat en invoquant la méthode
handleConversation
dans un crochetuseEffect
pour faire une demande dès que le composant est rendu. - Nous utilisons maintenant la valeur
isLoadingMessages
dans l'en-tête du composant Chat. Lorsqu'une demande pour obtenir une réponse de l'agent est en cours, nous définissons la valeurisLoadingMessages
surtrue
et mettons à jour l'en-tête pour que Zara tape… - Le tableau
agentMessages
dans le magasin est mis à jour vers un proxy par MobX une fois ses valeurs définies. À partir de ce composant, nous reconvertissons ce proxy en tableau à l'aide de l'utilitairetoJS
de MobX et stockons les valeurs dans une variable du composant. Ce tableau est encore itéré pour remplir les bulles de discussion avec les valeurs du tableau à l'aide d'une fonction de carte.
Maintenant, en utilisant le composant de chat, nous pouvons taper une phrase et attendre qu'une réponse s'affiche dans l'agent.
Enregistrement de la saisie vocale de l'utilisateur
Par défaut, tous les agents Dialogflow peuvent accepter les entrées vocales ou textuelles dans n'importe quelle langue spécifiée de la part d'un utilisateur. Cependant, il nécessite quelques ajustements à partir d'une application Web pour accéder au microphone d'un utilisateur et enregistrer une entrée vocale.
Pour y parvenir, nous modifions le magasin MobX pour utiliser l'API HTML MediaStream Recording afin d'enregistrer la voix d'un utilisateur dans deux nouvelles méthodes du magasin MobX.
// store.js import Axios from "axios"; import { action, observable, makeObservable } from "mobx"; class ApplicationStore { constructor() { makeObservable(this); } @observable isRecording = false; recorder = null; recordedBits = []; @action startAudioConversation = () => { navigator.mediaDevices .getUserMedia({ audio: true, }) .then((stream) => { this.isRecording = true; this.recorder = new MediaRecorder(stream); this.recorder.start(50); this.recorder.ondataavailable = (e) => { this.recordedBits.push(e.data); }; }) .catch((e) => console.log(`error recording : ${e}`)); }; };
Au clic de l'icône d'enregistrement du composant de chat, la méthode startAudioConversation
dans le magasin MobX ci-dessus est appelée pour définir la méthode de la propriété isRecording
observable sur true , pour que le composant de chat fournisse un retour visuel pour montrer qu'un enregistrement est en cours.
À l'aide de l'interface de navigation du navigateur, l'objet Périphérique multimédia est accessible pour demander le microphone du périphérique de l'utilisateur. Une fois l'autorisation accordée à la demande getUserMedia
, elle résout sa promesse avec des données MediaStream que nous transmettons ensuite au constructeur MediaRecorder pour créer un enregistreur à l'aide des pistes multimédias dans le flux renvoyé par le microphone de l'appareil de l'utilisateur. Nous stockons ensuite l'instance de l'enregistreur multimédia dans la propriété de l' recorder
du magasin, car nous y accéderons ultérieurement à partir d'une autre méthode.
Ensuite, nous appelons la méthode start sur l'instance de l'enregistreur, et une fois la session d'enregistrement terminée, la fonction ondataavailable
est déclenchée avec un argument d'événement contenant le flux recordedBits
dans un Blob que nous stockons dans la propriété de tableau recordingBits.
En déconnectant les données de l'argument d'événement transmis à l'événement ondataavailable
déclenché, nous pouvons voir le Blob et ses propriétés dans la console du navigateur.
Maintenant que nous pouvons démarrer un flux MediaRecorder, nous devons pouvoir arrêter le flux MediaRecorder lorsqu'un utilisateur a terminé d'enregistrer son entrée vocale et envoyer le fichier audio généré à l'application Express.js.
La nouvelle méthode ajoutée au magasin ci-dessous arrête le flux et effectue une requête POST
contenant l'entrée vocale enregistrée.
//store.js import Axios from "axios"; import { action, observable, makeObservable, configure } from "mobx"; const ENDPOINT = process.env.REACT_APP_DATA_API_URL; class ApplicationStore { constructor() { makeObservable(this); } @observable isRecording = false; recorder = null; recordedBits = []; @action closeStream = () => { this.isRecording = false; this.recorder.stop(); this.recorder.onstop = () => { if (this.recorder.state === "inactive") { const recordBlob = new Blob(this.recordedBits, { type: "audio/mp3", }); const inputFile = new File([recordBlob], "input.mp3", { type: "audio/mp3", }); const formData = new FormData(); formData.append("voiceInput", inputFile); Axios.post(`${ENDPOINT}/api/agent/voice-input`, formData, { headers: { "Content-Type": "multipart/formdata", }, }) .then((data) => {}) .catch((e) => console.log(`error uploading audio file : ${e}`)); } }; }; } export const store = new ApplicationStore();
The method above executes the MediaRecorder's stop method to stop an active stream. Within the onstop
event fired after the MediaRecorder is stopped, we create a new Blob with a music type and append it into a created FormData.
As the last step., we make POST
request with the created Blob added to the request body and a Content-Type: multipart/formdata
added to the request's headers so the file can be parsed by the connect-busboy middleware from the backend-service application.
With the recording being performed from the MobX store, all we need to add to the chat-component is a button to execute the MobX actions to start and stop the recording of the user's voice and also a text to show when a recording session is active.
import React from 'react' const ChatComponent = ({ ApplicationStore }) => { const { startAudiConversation, isRecording, handleConversation, endAudioConversation, isLoadingChatMessages } = ApplicationStore const [ Message, setMessage ] = useState("") return ( <div> <div className="chat-head"> <div style={{ ...center }}> <h5> Zara {} {isRecording && "is listening ..."} </h5> </div> <div style={{ ...center }} className="hover"> <FiX onClick={(_) => closeChatwindow()} /> </div> </div> <form onSubmit={(e) => { e.preventDefault(); handleConversation(Message); }} className="input-container" > <input className="input" type="text" onChange={(e) => setMessage(e.target.value)} value={Message} placeholder="Begin a conversation with our agent" /> <div className="send-btn-ctn"> {Message.length > 0 ? ( <div className="hover" onClick={() => handleConversation(Message)} > <FiSend style={{ transform: "rotate(50deg)" }} /> </div> ) : ( <div className="hover" onClick={() => handleAudioInput()} > <FiMic /> </div> )} </div> </form> </div> ) } export default ChatComponent
From the highlighted part in the chat component header above, we use the ES6 ternary operators to switch the text to “ Zara is listening …. ” whenever a voice input is being recorded and sent to the backend application. This gives the user feedback on what is being done.
Also, besides the text input, we added a microphone icon to inform the user of the text and voice input options available when using the chat assistant. If a user decides to use the text input, we switch the microphone button to a Send button by counting the length of the text stored and using a ternary operator to make the switch.
We can test the newly connected chat assistant a couple of times by using both voice and text inputs and watch it respond exactly like it would when using the Dialogflow console!
Conclusion
In the coming years, the use of language processing chat assistants in public services will have become mainstream. This article has provided a basic guide on how one of these chat assistants built with Dialogflow can be integrated into your own web application through the use of a backend application.
The built application has been deployed using Netlify and can be found here. Feel free to explore the Github repository of the backend express application here and the React.js web application here. They both contain a detailed README to guide you on the files within the two projects.
Les références
- Dialogflow Documentation
- Building A Conversational NLP Enabled Chatbot Using Google's Dialogflow by Nwani Victory
- MobX
- https://web.postman.com
- Dialogflow API: Node.js Client
- Using the MediaStream Recording API