Integrarea unui agent Dialogflow într-o aplicație React
Publicat: 2022-03-10Dialogflow este o platformă care simplifică procesul de creare și proiectare a unui asistent de chat conversațional de procesare a limbajului natural care poate procesa introducerea vocală sau text atunci când este utilizat fie din consola Dialogflow, fie dintr-o aplicație web integrată.
Deși agentul Dialogflow integrat este explicat pe scurt în acest articol, este de așteptat să înțelegeți Node.js și Dialogflow. Dacă învățați despre Dialogflow pentru prima dată, acest articol oferă o explicație clară a ceea ce este Dialogflow și conceptele sale.
Acest articol este un ghid despre cum a construit un agent Dialogflow cu suport vocal și chat care poate fi integrat într-o aplicație web cu ajutorul unei aplicații back-end Express.js ca legătură între o aplicație web React.js și agent pe Dialogflow însuși. Până la sfârșitul articolului, ar trebui să vă puteți conecta propriul agent Dialogflow la aplicația web preferată.
Pentru ca acest ghid să fie ușor de urmărit, puteți sări la oricare parte a tutorialului care vă interesează cel mai mult sau să le urmați în următoarea ordine pe măsură ce apar:
- Configurarea unui agent Dialogflow
- Integrarea unui agent Dialogflow
- Configurarea unei aplicații Node Express
- Autentificarea cu Dialogflow
- Ce sunt conturile de servicii?
- Gestionarea intrărilor vocale
- Integrarea într-o aplicație web
- Crearea unei interfețe de chat
- Înregistrarea intrării vocale a utilizatorului
- Concluzie
- Referințe
1. Configurarea unui agent Dialogflow
După cum se explică în acest articol, un asistent de chat pe Dialogflow se numește agent și cuprinde componente mai mici, cum ar fi intenții, îndeplinirea, baza de cunoștințe și multe altele. Dialogflow oferă utilizatorilor o consolă pentru a crea, antrena și proiecta fluxul de conversație al unui agent. În cazul nostru de utilizare, vom restaura un agent care a fost exportat într-un folder ZIP după ce a fost instruit, folosind funcția de export și import al agentului.
Înainte de a efectua importul, trebuie să creăm un nou agent care va fi fuzionat cu agentul care urmează să fie restaurat. Pentru a crea un agent nou din consolă, este nevoie de un nume unic și, de asemenea, de un proiect pe Google Cloud cu care să conectați agentul. Dacă nu există un proiect existent pe Google Cloud cu care să se conecteze, aici poate fi creat unul nou.
Un agent a fost creat și instruit anterior să recomande produse vinicole unui utilizator în funcție de bugetul acestuia. Acest agent a fost exportat într-un fișier ZIP; puteți descărca folderul de aici și îl puteți restaura în agentul nou creat din fila Export și import aflată în pagina Setări agent.
Agentul importat a fost instruit în prealabil să recomande utilizatorului un produs de vin în funcție de bugetul utilizatorului pentru achiziționarea unei sticle de vin.
Trecând prin agentul importat, vom vedea că are trei intenții create din pagina de intenții. Una este o intenție de rezervă, utilizată atunci când agentul nu recunoaște intrarea de la un utilizator, cealaltă este o intenție de bun venit utilizată atunci când se începe o conversație cu agentul, iar ultima intenție este utilizată pentru a recomanda un vin utilizatorului pe baza parametrul de sumă în cadrul propoziției. Ne preocupă intenția de a get-wine-recommendation
Această intenție are un singur context de intrare de wine-recommendation
provine din intenția de bun venit implicită de a lega conversația cu această intenție.
„Un context este un sistem din cadrul unui agent folosit pentru a controla fluxul unei conversații de la o intenție la alta.”
Sub contexte sunt frazele de antrenament, care sunt propoziții folosite pentru a instrui un agent cu privire la ce tip de declarații să se aștepte de la un utilizator. Printr-o mare varietate de fraze de antrenament în cadrul unei intenții, un agent este capabil să recunoască propoziția unui utilizator și intenția în care se încadrează.
Frazele de instruire din cadrul agenților noștri pentru get-wine-recommendation
(așa cum se arată mai jos) indică alegerea vinului și categoria de preț:
Privind imaginea de mai sus, putem vedea frazele de antrenament disponibile enumerate, iar cifra monedei este evidențiată în culoarea galbenă pentru fiecare dintre ele. Această evidențiere este cunoscută ca o adnotare pe Dialogflow și se face automat pentru a extrage tipurile de date recunoscute cunoscute ca entitate din propoziția unui utilizator.
După ce această intenție a fost potrivită într-o conversație cu agentul, se va face o solicitare HTTP către un serviciu extern pentru a obține vinul recomandat pe baza prețului extras ca parametru din propoziția unui utilizator, prin utilizarea webhook-ului activat găsit în secțiunea Îndeplinire din partea de jos a acestei pagini de intenții.
Putem testa agentul folosind emulatorul Dialogflow situat în secțiunea din dreapta a consolei Dialogflow. Pentru a testa, începem conversația cu un mesaj „ Bună ” și continuăm cu cantitatea dorită de vin. Webhook-ul va fi apelat imediat și un răspuns bogat similar cu cel de mai jos va fi afișat de către agent.
Din imaginea de mai sus putem vedea URL-ul webhook generat folosind Ngrok și răspunsul agentului în partea dreaptă care arată un vin în intervalul de preț de 20 USD introdus de utilizator.
În acest moment, agentul Dialogflow a fost complet configurat. Acum putem începe să integrăm acest agent într-o aplicație web pentru a permite altor utilizatori să acceseze și să interacționeze cu agentul fără acces la consola noastră Dialogflow.
Integrarea unui agent Dialogflow
Deși există și alte mijloace de conectare la un agent Dialogflow, cum ar fi efectuarea de solicitări HTTP către punctele sale finale REST, modalitatea recomandată de conectare la Dialogflow este prin utilizarea bibliotecii sale oficiale de client, disponibilă în mai multe limbaje de programare. Pentru JavaScript, pachetul @google-cloud/dialogflow este disponibil pentru instalare de la NPM.
Pe plan intern, pachetul @google-cloud/dialogflow folosește gRPC pentru conexiunile sale de rețea, ceea ce face ca pachetul să nu fie acceptat într-un mediu de browser, cu excepția cazului în care este corelat folosind webpack, modalitatea recomandată de utilizare a acestui pachet este dintr-un mediu Node. Putem face acest lucru prin configurarea unei aplicații de back-end Express.js pentru a utiliza acest pachet, apoi a difuza date aplicației web prin punctele sale finale API și aceasta este ceea ce vom face în continuare.
Configurarea unei aplicații Node Express
Pentru a configura o aplicație expres, creăm un nou director de proiect, apoi luăm dependențele necesare folosind yarn
dintr-un terminal deschis de linie de comandă.
# 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
Cu dependențele necesare instalate, putem trece la configurarea unui server Express.js foarte slab care gestionează conexiunile pe un port specificat cu suportul CORS activat pentru aplicația 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}`));
Când este executat, codul din fragmentul de mai sus pornește un server HTTP care ascultă conexiunile pe un PORT Express.js specificat. Are, de asemenea, partajarea resurselor între origini (CORS) activată pentru toate solicitările care utilizează pachetul cors ca middleware Express. Deocamdată, acest server ascultă doar conexiuni, nu poate răspunde la o solicitare deoarece nu are nicio rută creată, așa că haideți să creăm asta.
Acum trebuie să adăugăm două rute noi: una pentru trimiterea de date text, iar cealaltă pentru trimiterea unei intrări vocale înregistrate. Ambii vor accepta o solicitare POST
și vor trimite mai târziu datele conținute în corpul cererii agentului 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
Mai sus am creat o instanță separată de router pentru cele două rute POST
create, care deocamdată răspund doar cu un cod de stare 200
și un răspuns fals codificat. Când terminăm autentificarea cu Dialogflow, putem reveni pentru a implementa o conexiune reală la Dialogflow în aceste puncte finale.
Pentru ultimul pas în configurarea aplicației noastre backend, montăm instanța de router creată anterior creată în aplicația Express folosind app.use și o cale de bază pentru rută.
// 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}`));
Mai sus, am adăugat o cale de bază la cele două rute, două putem testa oricare dintre ele printr-o solicitare POST
folosind cURL dintr-o linie de comandă, așa cum se face mai jos cu un corp de cerere gol;
curl -X https://localhost:5000/api/agent/text-response
După finalizarea cu succes a solicitării de mai sus, ne putem aștepta să vedem un răspuns care conține date despre obiecte tipărite pe consolă.
Acum ne rămâne să facem o conexiune reală cu Dialogflow, care include gestionarea autentificării, trimiterea și primirea datelor de la agent pe Dialogflow folosind pachetul @google-cloud/dialogflow.
Autentificarea cu Dialogflow
Fiecare agent Dialogflow creat este conectat la un proiect pe Google Cloud. Pentru a ne conecta extern la agentul Dialogflow, ne autentificăm cu proiectul pe cloud Google și folosim Dialogflow ca una dintre resursele proiectului. Dintre cele șase moduri disponibile de a vă conecta la un proiect pe google-cloud, folosirea opțiunii Service accounts este cea mai convenabilă atunci când vă conectați la un anumit serviciu pe google cloud prin biblioteca sa client.
Notă : Pentru aplicațiile pregătite pentru producție, se recomandă utilizarea cheilor API de scurtă durată în detrimentul cheilor de cont de serviciu pentru a reduce riscul ca o cheie de cont de serviciu să ajungă în mâini greșite.
Ce sunt conturile de servicii?
Conturile de serviciu sunt un tip special de cont pe Google Cloud, creat pentru interacțiunea non-umană, în principal prin intermediul API-urilor externe. În aplicația noastră, contul de serviciu va fi accesat printr-o cheie generată de biblioteca client Dialogflow pentru a se autentifica cu Google Cloud.
Documentația cloud despre crearea și gestionarea conturilor de serviciu oferă un ghid excelent pentru a crea un cont de serviciu. La crearea contului de serviciu, rolul de administrator API Dialogflow ar trebui să fie atribuit contului de serviciu creat, așa cum se arată în ultimul pas. Acest rol oferă contului de serviciu control administrativ asupra agentului Dialogflow conectat.
Pentru a utiliza contul de serviciu, trebuie să creăm o cheie de cont de serviciu. Următorii pași de mai jos descriu cum să creați unul în format JSON:
- Faceți clic pe contul de serviciu nou creat pentru a naviga la pagina contului de serviciu.
- Derulați la secțiunea Chei și faceți clic pe meniul drop-down Adăugare cheie și faceți clic pe opțiunea Creare cheie nouă care deschide un mod.
- Selectați un format de fișier JSON și faceți clic pe butonul Creare din colțul din dreapta jos al modalului.
Notă: este recomandat să păstrați o cheie de cont de serviciu privată și să nu o trimiteți la niciun sistem de control al versiunilor, deoarece conține date extrem de sensibile despre un proiect pe Google Cloud. Acest lucru se poate face prin adăugarea fișierului în fișierul .gitignore
.
Cu un cont de serviciu creat și o cheie de cont de serviciu disponibilă în directorul proiectului nostru, putem folosi biblioteca client Dialogflow pentru a trimite și primi date de la agentul 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;
Întreaga rută de mai sus trimite date către agentul Dialogflow și primește un răspuns prin următorii pași.
- Primul
Se autentifică cu cloud-ul Google, apoi creează o sesiune cu Dialogflow folosind ID-ul proiectului Google cloud legat de agentul Dialogflow și, de asemenea, un ID aleatoriu pentru a identifica sesiunea creată. În aplicația noastră, creăm un identificator UUID pentru fiecare sesiune creată folosind pachetul JavaScript UUID. Acest lucru este foarte util atunci când înregistrați sau urmăriți toate conversațiile gestionate de un agent Dialogflow. - Al doilea
Creăm datele unui obiect de solicitare urmând formatul specificat în documentația Dialogflow. Acest obiect de cerere conține sesiunea creată și datele mesajului obținute de la corpul cererii care urmează să fie transmise agentului Dialogflow. - Al treilea
Folosind metodadetectIntent
din sesiunea Dialogflow, trimitem obiectul de solicitare asincron și așteptăm răspunsul agentului folosind sintaxa ES6 async / await într-un bloc try-catch, dacă metodadetectIntent
returnează o excepție, putem prinde eroarea și o putem returna, mai degrabă. decât blocarea întregii aplicații. O mostră a obiectului răspuns returnat de la agent este furnizată în documentația Dialogflow și poate fi inspectată pentru a ști cum să extragă datele din obiect.
Putem folosi Postman pentru a testa conexiunea Dialogflow implementată mai sus în ruta dialogflow-response
. Postman este o platformă de colaborare pentru dezvoltarea API-urilor cu funcții de testare a API-urilor construite fie în fazele de dezvoltare, fie în fazele de producție.
Notă: Dacă nu este deja instalată, aplicația desktop Postman nu este necesară pentru a testa un API. Începând din septembrie 2020, clientul web Postman a trecut într-o stare Generally Available (GA) și poate fi utilizat direct dintr-un browser.
Folosind Postman Web Client, putem fie să creăm un nou spațiu de lucru, fie să folosim unul existent pentru a crea o solicitare POST
către punctul nostru final API la https://localhost:5000/api/agent/text-input
și să adăugăm date cu o cheie de message
și valoarea „ Hi There ” în parametrii de interogare.
La apăsarea butonului Trimitere , se va face o solicitare POST
către serverul Express care rulează - cu un răspuns similar cu cel prezentat în imaginea de mai jos:
În imaginea de mai sus, putem vedea datele de răspuns frumos de la agentul Dialogflow prin serverul Express. Datele returnate sunt formatate în conformitate cu definiția răspunsului exemplu dată în documentația Dialogflow Webhook.
Gestionarea intrărilor vocale
În mod implicit, toți agenții Dialogflow sunt activați să proceseze atât text, cât și date audio și, de asemenea, să returneze un răspuns în format text sau audio. Cu toate acestea, lucrul cu date de intrare sau ieșire audio poate fi puțin mai complex decât datele text.
Pentru a gestiona și procesa intrările vocale, vom începe implementarea pentru punctul final /voice-input
care l-am creat anterior pentru a primi fișiere audio și a le trimite către Dialogflow în schimbul unui răspuns din partea Agentului:
// 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}`); } });
La o imagine de ansamblu ridicată, ruta /voice-input
mai sus primește intrarea vocală a utilizatorului ca fișier care conține mesajul rostit asistentului de chat și îl trimite agentului Dialogflow. Pentru a înțelege mai bine acest proces, îl putem împărți în următorii pași mai mici:
- În primul rând, adăugăm și folosim connect-busboy ca middleware Express pentru analizarea datelor din formulare trimise în cererea din aplicația web. După care ne autentificăm cu Dialogflow folosind cheia de serviciu și creăm o sesiune, așa cum am procedat în ruta anterioară.
Apoi, folosind metoda promisify din modulul util Node.js încorporat, obținem și salvăm un echivalent promis al metodei Stream pipeline pentru a fi folosit mai târziu pentru a canaliza mai multe fluxuri și, de asemenea, pentru a efectua o curățare după ce fluxurile sunt finalizate. - Apoi, creăm un obiect de cerere care conține sesiunea de autentificare Dialogflow și o configurație pentru fișierul audio care urmează să fie trimis către Dialogflow. Obiectul de configurare audio imbricat permite agentului Dialogflow să efectueze o conversie Speech-To-Text pe fișierul audio trimis.
- Apoi, folosind sesiunea creată și obiectul de solicitare, detectăm intenția unui utilizator din fișierul audio folosind metoda
detectStreamingIntent
care deschide un nou flux de date de la agentul Dialogflow către aplicația backend. Datele vor trimite înapoi în biți mici prin acest flux și folosind „ evenimentul ” de date din fluxul care poate fi citit, stocăm datele în variabilastreamData
pentru o utilizare ulterioară. După ce fluxul este închis, evenimentul „ sfârșit ” este declanșat și trimitem răspunsul de la agentul Dialogflow stocat în variabilastreamData
către aplicația web. - În cele din urmă, folosind evenimentul de flux de fișiere de la connect-busboy, primim fluxul fișierului audio trimis în corpul solicitării și îl transmitem în continuare în echivalentul de promisiune al Pipeline pe care l-am creat anterior. Funcția acestuia este de a canaliza fluxul de fișiere audio care vine de la cerere către fluxul Dialogflow, transmitem fluxul de fișiere audio către fluxul deschis prin metoda
detectStreamingIntent
de mai sus.
Pentru a testa și a confirma că pașii de mai sus funcționează conform prevederilor, putem face o solicitare de testare care conține un fișier audio în corpul solicitării către punctul final /voice-input
folosind Postman.
Rezultatul Poștașului de mai sus arată răspunsul primit după efectuarea unei cereri POST cu datele din formularul unui mesaj vocal înregistrat care spune „ Bună ” inclus în corpul cererii.
În acest moment, avem acum o aplicație funcțională Express.js care trimite și primește date de la Dialogflow, cele două părți ale acestui articol sunt terminate. Unde rămâne acum cu integrarea acestui agent într-o aplicație web prin consumarea API-urilor create aici dintr-o aplicație Reactjs.
Integrarea într-o aplicație web
Pentru a consuma API-ul REST construit, vom extinde această aplicație React.js existentă, care are deja o pagină de pornire care arată o listă de vinuri preluate dintr-un API și suport pentru decoratori folosind pluginul babel proposal decorators. Îl vom refactoriza puțin prin introducerea Mobx pentru managementul de stat și, de asemenea, o nouă caracteristică pentru a recomanda un vin dintr-o componentă de chat folosind punctele finale REST API adăugate din aplicația Express.js.
Pentru a începe, începem să gestionăm starea aplicației folosind MobX pe măsură ce creăm un magazin Mobx cu câteva valori observabile și câteva metode ca acțiuni.
// 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();
Mai sus am creat un magazin pentru caracteristica componentei de chat în cadrul aplicației având următoarele valori:
-
isChatWindowOpen
Valoarea stocată aici controlează vizibilitatea componentei de chat în care sunt afișate mesajele Dialogflow. -
isLoadingChatMessages
Acesta este folosit pentru a afișa un indicator de încărcare atunci când se face o solicitare de a prelua un răspuns de la agentul Dialogflow. -
agentMessages
Această matrice stochează toate răspunsurile care provin de la solicitările făcute pentru a obține un răspuns de la agentul Dialogflow. Datele din matrice sunt afișate ulterior în componentă. -
handleConversation
Această metodă decorată ca acțiune adaugă date în matriceaagentMessages
. În primul rând, adaugă mesajul utilizatorului transmis ca argument, apoi face o solicitare folosind Axios la aplicația backend pentru a obține un răspuns de la Dialogflow. După ce solicitarea este rezolvată, adaugă răspunsul de la cerere în matriceaagentMessages
.
Notă: În absența suportului pentru decoratori într-o aplicație, MobX oferă makeObservable care poate fi utilizat în constructorul clasei de magazin țintă. Vezi un exemplu aici .
Odată cu configurarea magazinului, trebuie să împachetăm întregul arbore al aplicației cu componenta de ordin superior MobX Provider, începând de la componenta rădăcină din fișierul 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;
Mai sus împachetăm componenta App rădăcină cu MobX Provider și trecem în magazinul creat anterior ca una dintre valorile Furnizorului. Acum putem continua să citim din magazin în componentele conectate la magazin.
Crearea unei interfețe de chat
Pentru a afișa mesajele trimise sau primite din solicitările API, avem nevoie de o nouă componentă cu o interfață de chat care să arate mesajele enumerate. Pentru a face acest lucru, creăm o nouă componentă pentru a afișa mai întâi unele mesaje hard-coded, apoi mai târziu afișăm mesajele într-o listă ordonată.
// ./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
Componenta de mai sus are marcajul HTML de bază necesar pentru o aplicație de chat. Are un antet care arată numele Agentului și o pictogramă pentru închiderea ferestrei de chat, un balon de mesaj care conține un text codificat într-o etichetă de listă și, în sfârșit, un câmp de intrare cu un handler de evenimente onChange
atașat câmpului de introducere pentru a stoca textul introdus în starea locală a componentei utilizând useState de la React.
Din imaginea de mai sus, componenta de chat funcționează așa cum ar trebui, arătând o fereastră de chat stilată cu un singur mesaj de chat și câmpul de introducere în partea de jos. Cu toate acestea, dorim ca mesajul afișat să fie răspunsuri reale primite de la solicitarea API și nu text codificat.
Continuăm să refactorăm componenta Chat, de data aceasta conectând și utilizând valorile din magazinul MobX din cadrul componentei.
// ./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));
Din părțile evidențiate ale codului de mai sus, putem vedea că întreaga componentă de chat a fost acum modificată pentru a efectua următoarele operațiuni noi;
- Are acces la valorile magazinului MobX după injectarea valorii
ApplicationStore
. Componenta a fost, de asemenea, făcută un observator al acestor valori de stocare, astfel încât se redă din nou atunci când una dintre valori se schimbă. - Începem conversația cu agentul imediat după ce componenta de chat este deschisă prin invocarea metodei
handleConversation
într-un hookuseEffect
pentru a face o solicitare imediat ce componenta este redată. - Acum folosim valoarea
isLoadingMessages
din antetul componentei Chat. Când o solicitare pentru a obține un răspuns de la agent este în zbor, setăm valoareaisLoadingMessages
latrue
și actualizăm antetul la tastarea Zara... - Matricea
agentMessages
din magazin este actualizată la un proxy de către MobX după ce valorile sale sunt setate. Din această componentă, convertim acel proxy înapoi într-o matrice folosind utilitarultoJS
de la MobX și stocăm valorile într-o variabilă din componentă. Această matrice este iterată în continuare pentru a popula bulele de chat cu valorile din cadrul matricei folosind o funcție de hartă.
Acum, folosind componenta de chat, putem introduce o propoziție și așteptăm ca un răspuns să fie afișat în agent.
Înregistrarea intrării vocale a utilizatorului
În mod implicit, toți agenții Dialogflow pot accepta introducerea vocală sau text în orice limbă specificată de la un utilizator. Cu toate acestea, necesită câteva ajustări de la o aplicație web pentru a obține acces la microfonul unui utilizator și pentru a înregistra o intrare vocală.
Pentru a realiza acest lucru, modificăm magazinul MobX pentru a folosi API-ul HTML MediaStream Recording pentru a înregistra vocea unui utilizator prin două metode noi în magazinul 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}`)); }; };
La clic pe pictograma de înregistrare din componenta de chat, metoda startAudioConversation
din magazinul MobX de mai sus este invocată pentru a seta metoda în care proprietatea observabilă isRecording
este true , pentru ca componenta de chat să ofere feedback vizual pentru a arăta că o înregistrare este în curs.
Folosind interfața de navigare a browserului, obiectul Media Device este accesat pentru a solicita microfonul dispozitivului utilizatorului. După ce se acordă permisiunea cererii getUserMedia
, aceasta își rezolvă promisiunea cu o date MediaStream pe care le transmitem în continuare constructorului MediaRecorder pentru a crea un recorder folosind melodiile media din fluxul returnat de la microfonul dispozitivului utilizatorului. Apoi stocăm instanța de înregistrare media în proprietatea de recorder
a magazinului, deoarece o vom accesa din altă metodă mai târziu.
Apoi, apelăm metoda de pornire pe instanța de înregistrare și după ce sesiunea de înregistrare este încheiată, funcția ondataavailable
este declanșată cu un argument de eveniment care conține fluxul înregistrat într-un Blob pe care îl stocăm în proprietatea recordedBits
array.
În deconectarea datelor din argumentul eveniment transmis în evenimentul declanșat ondataavailable
, putem vedea blob-ul și proprietățile sale în consola browserului.
Acum că putem porni un flux MediaRecorder, trebuie să putem opri fluxul MediaRecorder atunci când un utilizator a terminat de înregistrat intrarea vocală și să trimitem fișierul audio generat la aplicația Express.js.
Noua metodă adăugată în magazinul de mai jos oprește fluxul și face o solicitare POST
care conține intrarea vocală înregistrată.
//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!
Concluzie
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.
Referințe
- 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