Dialogflow é uma plataforma que simplifica o processo de criação e design de um assistente de bate-papo conversacional de processamento de linguagem natural, que pode processar entrada de voz ou texto quando usado no console do Dialogflow ou de um integrado aplicativo da web.
Embora o agente Dialogflow integrado seja explicado brevemente neste artigo, espera-se que você compreenda Node.js e Dialogflow. Se você está aprendendo sobre o Dialogflow pela primeira vez, este artigo fornece uma explicação clara sobre o que é Dialogflow e seus conceitos.
Este artigo é um guia sobre como criar um agente Dialogflow com suporte de voz e bate-papo que pode ser integrado a um aplicativo da web com a ajuda de um Express. Aplicativo back-end js como um link entre um aplicativo da Web React.js e o agente no próprio Dialogflow. Ao final do artigo, você deverá conectar seu próprio agente do Dialogflow ao seu aplicativo da web preferido.
Para facilitar o acompanhamento deste guia, você pode pular para a parte do tutorial que mais lhe interessar ou segui-la na seguinte ordem em que aparecem:
- Configurando um agente Dialogflow
- Integrando um agente Dialogflow
- Configurando um aplicativo Node Express
- Integrando em um aplicativo da web
- Conclusão
- Referências
1. Configurando um agente Dialogflow
Conforme explicado este artigo, um assistente de bate-papo sobre Dialogflow é chamado de Agente e é composto por componentes menores, como intents , cumprimento , base de conhecimento e muito mais. O Dialogflow fornece um console para que os usuários criem, treinem e projetem o fluxo de conversação de um Agente. Em nosso caso de uso, restauraremos um agente que foi exportado para uma pasta ZIP após ser treinado, usando o agente Exportar e importar recurso.
Antes de realizarmos a importação, precisamos criar um novo agente que será mesclado com o agente prestes a ser restaurado. Para criar um novo Agente a partir do console, é necessário um nome exclusivo e também um projeto no Google Cloud para vincular o agente ao. Se não houver um projeto existente no Google Cloud para vincular, um novo pode ser criado aqui .
Um agente foi criado e treinado anteriormente para recomendar produtos vínicos a um usuário com base em seu orçamento. Este agente foi exportado para um ZIP; você pode baixar a pasta aqui e restaurá-la em nosso novo agente criado a partir da guia Exportar e importar encontrada na página de configurações do agente.
O agente importado foi previamente treinado para recomendar um produto vínico ao usuário com base no orçamento do usuário para a compra de uma garrafa de vinho.
Analisando o agente importado, veremos que ele possui três intents criados na página de intents. Sendo um um intent substituto, usado quando o Agente não reconhece a entrada de um usuário, o outro é um intent Bem-vindo usado quando uma conversa com o Agente é iniciada, e o último intent é usado para recomendar um vinho ao usuário com base no parâmetro de quantidade dentro da frase. Uma preocupação para nós é a intenção de get-wine-Recommendation
Este intent tem uma única entrada contexto de wine-recomendação
vindo da intenção de boas-vindas padrão para vincular a conversa a essa intenção.
“Um Contexto é um sistema dentro de um Agente usado para controlar o fluxo de uma conversa de uma intenção para a outra. ”
Abaixo dos contextos estão os Frases de treinamento , que são frases usadas para treinar um agente sobre que tipo de instruções esperar de um usuário. Por meio de uma grande variedade de frases de treinamento em uma intent, um agente é capaz de reconhecer a frase de um usuário e a intent em que ela se enquadra.
As frases de treinamento na intenção get-wine-recomendação
de nossos agentes (conforme mostrado abaixo) indicam a escolha do vinho e a categoria de preço:
Olhando para a imagem acima, podemos ver as frases de treinamento disponíveis listadas, e o valor da moeda é destacado em amarelo para cada uma delas. Esse realce é conhecido como uma anotação no Dialogflow e é feito automaticamente para extrair os tipos de dados reconhecidos, conhecidos como uma entidade, da frase de um usuário.
Após esta intent ter sido correspondida em uma conversa com o agente, uma solicitação HTTP será feita a um serviço externo para obter o vinho recomendado com base no preço extraído como parâmetro da frase de um usuário, por meio do uso do webhook encontrado na seção Fulfillment na parte inferior desta página de intent.
Podemos testar o agente usando o emulador Dialogflow localizado na seção direita do console do Dialogflow. Para testar, iniciamos a conversa com a mensagem “ Oi ” e seguimos com a quantidade de vinho desejada. O webhook será chamado imediatamente e uma resposta rica semelhante à mostrada abaixo será exibida pelo agente.
Na imagem acima, podemos ver o URL do webhook gerado usando Ngrok e a resposta do agente no lado direito, mostrando um vinho dentro do Faixa de preço de US $ 20 digitada pelo usuário.
Neste ponto, o agente Dialogflow foi totalmente configurado. Agora podemos começar a integrar este agente em um aplicativo da web para permitir que outros usuários acessem e interajam com o agente sem acesso ao nosso Console do Dialogflow .
Integração de um agente Dialogflow
Embora existam outros meios de se conectar a um agente do Dialogflow, como fazer solicitações HTTP ao seu Endpoints REST , a maneira recomendada de se conectar ao Dialogflow é por meio do uso de sua biblioteca cliente oficial, disponível em várias linguagens de programação. Para JavaScript, o pacote @ google-cloud/dialogflow está disponível para instalação em NPM .
Internamente, o pacote @ google-cloud/dialogflow usa gRPC para suas conexões de rede e isso torna o pacote sem suporte em um ambiente de navegador, exceto quando corrigido usando webpack , a maneira recomendada de usar este pacote é em um ambiente Node. Podemos fazer isso configurando um aplicativo de back-end Express.js para usar este pacote e, em seguida, fornecer dados para o aplicativo da web por meio de seus terminais de API e é isso que faremos a seguir.
Configurando um aplicativo Node Express
Para configurar um aplicativo expresso, criamos um novo diretório de projeto e, em seguida, pegamos as dependências necessárias usando yarn
em um terminal de linha de comando aberto.
# cria um novo diretório e (&&) move para o diretório
mkdir dialogflow-server && cd dialogflow-server # criar um novo projeto Node
yarn init-y # Instale os pacotes necessários
fio adicionar express cors dotenv uuid
Com as dependências necessárias instaladas, podemos prosseguir para configurar um servidor Express.js muito enxuto que lida com conexões em uma porta especificada com suporte CORS habilitado para o aplicativo da 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 ( 🔥 servidor em execução na porta $ {PORT}
));
Quando executado, o código no snippet acima inicia um servidor HTTP que escuta as conexões em um PORT Express.js especificado. Ele também tem Compartilhamento de recursos de origem cruzada (CORS) habilitado em todas as solicitações usando o cors pacote como um middleware expresso . Por enquanto, este servidor apenas escuta conexões, ele não pode responder a uma solicitação porque não criou uma rota , então vamos criar isso.
Agora precisamos adicionar duas novas rotas: uma para enviar dados de texto e a outra para enviar uma entrada de voz gravada. Ambos aceitarão uma solicitação POST
e enviarão os dados contidos no corpo da solicitação para o agente do Dialogflow posteriormente.
const express=require ("express") const app=express () app.post ("/text-input", (req, res)=> { res.status (200).send ({data:"TEXTO ENDPOINT CONNECTION SUCCESSFUL"})
}); app.post ("/entrada de voz", (req, res)=> { res.status (200).send ({data:"VOICE ENDPOINT CONNECTION SUCCESSFUL"})
}); module.exports=app
Acima, criamos uma instância de roteador separada para as duas rotas POST
criadas que, por enquanto, respondem apenas com um código de status 200
e uma resposta fictícia codificada. Quando terminarmos de autenticar com o Dialogflow, podemos voltar para implementar uma conexão real com o Dialogflow nesses endpoints.
Para a última etapa da configuração do nosso aplicativo de back-end, montamos a instância do roteador criada anteriormente no aplicativo Express usando app.use e um caminho básico para a rota.
//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", Rotas); app.listen (PORT, ()=> console.log ( 🔥 servidor rodando na porta $ {PORT}
));
Acima, adicionamos um caminho base para as duas rotas; podemos testar qualquer uma delas por meio de uma solicitação POST
usando cURL de uma linha de comando, como é feito abaixo com um corpo de solicitação vazio;
curl-X http://localhost: 5000/api/agent/text-response
Após a conclusão bem-sucedida da solicitação acima, podemos esperar ver uma resposta contendo dados do objeto sendo impressos no console.
Agora resta fazer uma conexão real com o Dialogflow, que inclui lidar com a autenticação, enviar e receber dados do agente no Dialogflow usando pacote @ google-cloud/dialogflow .
Autenticação com Dialogflow
Cada agente Dialogflow criado está vinculado a um projeto no Google Cloud . Para se conectar externamente ao agente do Dialogflow, nós nos autenticamos com o projeto na nuvem do Google e usamos o Dialogflow como um dos recursos do projeto. Das seis disponíveis maneiras de se conectar a um projeto no google-cloud, usando o
Observação : para aplicativos prontos para produção, o uso de chaves de API de curta duração é recomendado em vez de chaves de conta de serviço para reduzir o risco de uma chave de conta de serviço entrar em erro mãos.
O que são contas de serviço?
Contas de serviço são um tipo especial de conta no Google Cloud , criado para interação não humana, principalmente por meio de APIs externas. Em nosso aplicativo, a conta de serviço será acessada por meio de uma chave gerada pela biblioteca cliente do Dialogflow para autenticar com o Google Cloud.
A documentação da nuvem sobre a criação e gerenciamento de contas de serviço fornece um excelente guia para criar uma conta de serviço. Ao criar a conta de serviço, o papel Administrador da API Dialogflow deve ser atribuído à conta de serviço criada como mostrado na última etapa. Essa função fornece à conta de serviço controle administrativo sobre o agente do Dialogflow vinculado.
Para usar a conta de serviço, precisamos criar uma Chave de conta de serviço . As etapas a seguir descrevem como criar um no formato JSON:
- Clique na conta de serviço recém-criada para navegar até a página da conta de serviço.
- Role até a seção Chaves e clique no menu suspenso Adicionar chave e clique na opção Criar nova chave que abre um modal.
- Selecione um formato de arquivo JSON e clique no botão Criar na parte inferior direita do modal.
Observação: é recomendável manter a chave da conta de serviço privada e não comprometê-la com nenhuma sistema de controle de versão , pois contém dados altamente confidenciais sobre um projeto no Google Cloud. Isso pode ser feito adicionando o arquivo ao arquivo .gitignore
.
Com uma conta de serviço criada e uma chave de conta de serviço disponível no diretório de nosso projeto, podemos usar a biblioteca cliente Dialogflow para enviar e receber dados do agente 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", assíncrono (req, res)=> { const {mensagem}=req.body; //Crie uma nova sessão const sessionClient=new Dialogflow.SessionsClient ({ keyFilename: Path.join (__ dirname,"./key.json"), }); const sessionPath=sessionClient.projectAgentSessionPath ( process.env.PROJECT_ID, uuid () ); //O objeto de solicitação de dialogflow solicitação const={ sessão: sessionPath, queryInput: { texto: { //A consulta a ser enviada ao agente dialogflow mensagem de texto, }, }, }; //Envia dados do agente como resposta experimentar { respostas constantes=esperar sessionClient.detectIntent (pedido); res.status (200).send ({dados: respostas}); } catch (e) { console.log (e); res.status (422).send ({e}); }
}); module.exports=app;
Toda a rota acima envia dados ao agente do Dialogflow e recebe uma resposta por meio das etapas a seguir.
- Primeiro
Ele se autentica com a nuvem do Google e, em seguida, cria uma sessão com Dialogflow usando o projectID do projeto de nuvem do Google vinculado ao agente Dialogflow e também um ID aleatório para identificar a sessão criada. Em nosso aplicativo, estamos criando um identificador UUID em cada sessão criada usando o JavaScript pacote UUID . Isso é muito útil ao registrar ou rastrear todas as conversas tratadas por um agente Dialogflow. - Segundo
Criamos dados do objeto de solicitação seguindo o formato especificado na documentação do Dialogflow. Este objeto de solicitação contém a sessão criada e os dados da mensagem obtidos do corpo da solicitação que devem ser passados ao agente do Dialogflow. - Terceiro
Usando o métododetectIntent
da sessão Dialogflow, enviamos o objeto de solicitação de forma assíncrona e aguardamos a resposta do agente usando ES6 async/await sintaxe em um bloco try-catch deve odetectIntent
método retornar uma exceção, podemos capturar o erro e retorná-lo, em vez de travar o aplicativo inteiro. Uma amostra do objeto de resposta retornado do agente é fornecida na documentação do Dialogflow e pode ser inspecionada para saber como extrair os dados do objeto.
Podemos usar Postman para testar a conexão Dialogflow implementada acima na rota dialogflow-response
. Postman é uma plataforma de colaboração para desenvolvimento de API com recursos para testar APIs construídas em estágios de desenvolvimento ou produção.
Observação: se ainda não estiver instalado, o aplicativo Postman para desktop não é necessário para testar uma API. A partir de setembro de 2020, o cliente da Web do Postman mudou para um estado de disponibilidade geral (GA) e pode ser usado diretamente em um navegador.
Usando o Postman Web Client, podemos criar um novo espaço de trabalho ou usar um existente para criar uma solicitação POST
para nosso endpoint de API em http://localhost: 5000/api/agent/text-input
e adicionar dados com uma chave de mensagem
e valor de “ Hi There ” nos parâmetros de consulta.
Ao clicar no botão Enviar , uma solicitação POST
será feita ao servidor Express em execução-com uma resposta semelhante à mostrada na imagem abaixo:
Na imagem acima, podemos ver os dados de resposta aprimorados do agente Dialogflow por meio do servidor Express. Os dados retornados são formatados de acordo com a amostra de resposta definição fornecida no Dialogflow Webhook documentação .
Tratamento de entradas de voz
Por padrão, todos os agentes do Dialogflow podem processar dados de texto e áudio e também retornar uma resposta em formato de texto ou áudio. No entanto, trabalhar com dados de entrada ou saída de áudio pode ser um pouco mais complexo do que dados de texto.
Para manipular e processar entradas de voz, começaremos a implementação do endpoint /voice-input
que criamos anteriormente para receber arquivos de áudio e enviá-los ao Dialogflow em troca de uma resposta do Agente:
//agentRoutes.js
import {pipeline, Transform} de"stream";
importar busboy de"conectar-busboy";
importar util de"promisfy"
import Dialogflow de"@ google-cloud/dialogflow" const app=express (); app.use ( ajudante de garçom ({ imediato: verdadeiro, })
); app.post ("/entrada de voz", (req, res)=> { const sessionClient=new Dialogflow.SessionsClient ({ keyFilename: Path.join (__ dirname,"./recommender-key.json"), }); const sessionPath=sessionClient.projectAgentSessionPath ( process.env.PROJECT_ID, uuid () ); //transformar em uma promessa bomba const=util.promisify (pipeline); const audioRequest={ sessão: sessionPath, queryInput: { audioConfig: { audioEncoding:"AUDIO_ENCODING_OGG_OPUS", sampleRateHertz:"16000", languageCode:"en-US", }, singleUtterance: true, }, }; const streamData=null; const detectStream=sessionClient .streamingDetectIntent () .on ("erro", (erro)=> console.log (erro)) .on ("dados", (dados)=> { streamData=data.queryResult }) .on ("fim", (dados)=> { res.status (200).send ({data: streamData.fulfillmentText}} }) detectStream.write (audioRequest); experimentar { req.busboy.on ("arquivo", (_, arquivo, nome do arquivo)=> { bomba( Arquivo, nova transformação ({ objectMode: true, transformar: (obj, _, próximo)=> { próximo (nulo, {inputAudio: obj}); }, }), detectStream ); }); } catch (e) { console.log (`erro: $ {e}`); }
});
Em uma visão geral, a rota /voice-input
acima recebe a entrada de voz do usuário como um arquivo contendo a mensagem falada para o assistente de bate-papo e a envia ao agente do Dialogflow. Para entender melhor esse processo, podemos dividi-lo nas seguintes etapas menores:
- Primeiro, adicionamos e usamos connect-busboy como um Express middleware para analisar dados de formulários enviados na solicitação do aplicativo da web. Depois, autenticamos com Dialogflow usando a chave de serviço e criamos uma sessão, da mesma forma que fizemos na rota anterior.
Em seguida, usando o promisify método do Node.js util módulo, obtemos e salvamos uma promessa equivalente ao método de pipeline de fluxo para ser usado posteriormente para canalizar vários fluxos e também realizar uma limpeza após o streams estão concluídos. - Em seguida, criamos um objeto de solicitação contendo a sessão de autenticação do Dialogflow e uma configuração para o arquivo de áudio prestes a ser enviado ao Dialogflow. O objeto de configuração de áudio aninhado permite que o agente Dialogflow execute uma conversão de Speech-To-Text no arquivo de áudio enviado.
- Em seguida, usando a sessão criada e o objeto de solicitação, detectamos a intenção de um usuário no arquivo de áudio usando o método
detectStreamingIntent
, que abre um novo fluxo de dados do agente Dialogflow para o aplicativo de back-end. Os dados serão enviados de volta em pequenos bits por meio desse fluxo e, usando os dados “ evento ” do fluxo legível, armazenamos os dados na variávelstreamData
para uso posterior. Depois que o fluxo é fechado, o evento “ end ” é disparado e enviamos a resposta do agente Dialogflow armazenado na variávelstreamData
para o aplicativo da web. - Por último, usando o evento de stream de arquivo de connect-busboy , recebemos o stream do arquivo de áudio enviado o corpo da solicitação e depois o passamos para a promessa equivalente a Pipeline que criamos anteriormente. A função disso é canalizar o fluxo do arquivo de áudio proveniente da solicitação para o fluxo do Dialogflow, canalizamos o fluxo do arquivo de áudio para o fluxo aberto pelo método
detectStreamingIntent
acima.
Para testar e confirmar se as etapas acima estão funcionando conforme descrito, podemos fazer uma solicitação de teste contendo um arquivo de áudio no corpo da solicitação para o endpoint /voice-input
usando o Postman.
O resultado do Postman acima mostra a resposta obtida após fazer uma solicitação POST com os dados do formulário de uma mensagem de nota de voz gravada dizendo “ Olá ” incluída no corpo da solicitação.
Neste ponto, agora temos um aplicativo Express.js funcional que envia e recebe dados do Dialogflow. As duas partes deste artigo estão concluídas. O que resta agora com a integração deste Agente em um aplicativo da web, consumindo as APIs criadas aqui a partir de um aplicativo Reactjs .
Integração em um aplicativo da web
Para consumir nossa API REST construída, expandiremos este aplicativo React.js existente que já tem uma página inicial mostrando uma lista de vinhos obtidos de uma API e suporte para decoradores usando o plug-in de decoradores de proposta babel . Iremos refatorá-lo um pouco introduzindo Mobx para gerenciamento de estado e também um novo recurso para recomendar um vinho de um componente de chat usando os pontos de extremidade da API REST adicionados do aplicativo Express.js.
Para começar, começamos a gerenciar o estado do aplicativo usando MobX conforme criamos uma loja Mobx com alguns valores observáveis e alguns métodos como ações .
//store.js importar Axios de"axios";
import {action, observable, makeObservable, configure} de"mobx"; const ENDPOINT=process.env.REACT_APP_DATA_API_URL; class ApplicationStore { construtor () { makeObservable (this); } @observável isChatWindowOpen=false; @observável isLoadingChatMessages=false; @observável agentMessages=[]; @açao setChatWindow=(estado)=> { this.isChatWindowOpen=estado; }; @açao handleConversation=(mensagem)=> { this.isLoadingChatMessages=true; this.agentMessages.push ({userMessage: mensagem}); Axios.post ( $ {ENDPOINT}/dialogflow-response
, { mensagem: mensagem ||"Oi", }) .então ((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 ();
Acima, criamos uma loja para o recurso do componente de bate-papo dentro do aplicativo com os seguintes valores:
-
isChatWindowOpen
O valor armazenado aqui controla a visibilidade do componente de bate-papo onde as mensagens do Dialogflow são mostradas. -
isLoadingChatMessages
É usado para mostrar um indicador de carregamento quando uma solicitação para buscar uma resposta do agente Dialogflow é feita. -
agentMessages
Esta matriz armazena todas as respostas provenientes das solicitações feitas para obter uma resposta do agente Dialogflow. Os dados na matriz são exibidos posteriormente no componente. -
handleConversation
Este método decorado como uma ação adiciona dados ao arrayagentMessages
. Primeiro, ele adiciona a mensagem do usuário passada como argumento e, em seguida, faz uma solicitação usando Axios”> Axios para o aplicativo de back-end para obter uma resposta do Dialogflow. Depois que a solicitação é resolvida, ele adiciona a resposta da solicitação na matrizagentMessages
.
Observação: na ausência dos decoradores suporte em um aplicativo, MobX fornece makeObservable que pode ser usado no construtor da classe de armazenamento de destino. Veja um exemplo aqui .
Com a configuração da loja, precisamos agrupar toda a árvore do aplicativo com o componente de ordem superior do provedor MobX, começando pelo componente raiz no arquivo index.js
.
import React from"react";
import {Provider} de"mobx-react"; import {store} from"./state/";
importar a página inicial de"./pages/home"; function App () { Retorna ( );
} exportar aplicativo padrão;
Acima, envolvemos o componente raiz do App com o Provedor MobX e passamos a loja criada anteriormente como um dos valores do Provedor. Agora podemos prosseguir com a leitura da loja nos componentes conectados à loja.
Criando uma interface de bate-papo
Para exibir as mensagens enviadas ou recebidas das solicitações de API, precisamos de um novo componente com alguma interface de bate-papo mostrando as mensagens listadas. Para fazer isso, criamos um novo componente para exibir algumas mensagens codificadas primeiro e depois exibimos as mensagens em uma lista ordenada.
//./chatComponent.js import React, {useState} de"react";
importar {FiSend, FiX} de"react-icons/fi";
import"../styles/chat-window.css"; const center={ display:"flex", jusitfyContent:"center", alignItems:"center",
}; const ChatComponent=(props)=> { const {janela de bate-papo, isOpen}=adereços; const [Message, setMessage]=useState (""); Retorna ( Zara
closeChatwindow ()}/> -
Olá, bem-vindo ao nosso agente
);
}; export default ChatComponent
The component above has the basic HTML markup needed for a chat application. It has a header showing the Agent’s name and an icon for closing the chat window, a message bubble containing a hardcoded text in a list tag, and lastly an input field having an onChange
event handler attached to the input field to store the text typed into the component’s local state using React’s useState.
From the image above, the chat component works as it should, showing a styled chat window having a single chat message and the input field at the bottom. We however want the message shown to be actual responses gotten from the API request and not hardcoded text.
We move forward to refactor the Chat component, this time connecting and making use of values in the MobX store within the component.
//./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 ( Zara {isLoadingChatMessages &&"is typing..."}
closeChatwindow()}/> {data.map(({ fulfillmentText, userMessage })=> ( - {userMessage && (
.
{userMessage}
)} {fulfillmentText && ( {fulfillmentText}
.
)} ))}
);
}; export default inject("ApplicationStore")(observer(ChatComponent));
From the highlighted parts of the code above, we can see that the entire chat component has now been modified to perform the following new operations;
- It has access to the MobX store values after injecting the
ApplicationStore
value. The component has also been made an observer of these store values so it re-renders when one of the values changes. - We start the conversation with the Agent immediately after the chat component is opened by invoking the
handleConversation
method within auseEffect
hook to make a request immediately the component is rendered. - We are now making use of the
isLoadingMessages
value within the Chat component header. When a request to get a response from the Agent is in flight, we set theisLoadingMessages
value totrue
and update the header to Zara is typing… - The
agentMessages
array within the store gets updated to a proxy by MobX after its values are set. From this component, we convert that proxy back to an array using thetoJS
utility from MobX and store the values in a variable within the component. That array is further iterated upon to populate the chat bubbles with the values within the array using a map function.
Now using the chat component we can type in a sentence and wait for a response to be displayed in the agent.
Recording User Voice Input
By default, all Dialogflow agents can accept either voice or text-based input in any specified language from a user. However, it requires a few adjustments from a web application to gain access to a user’s microphone and record a voice input.
To achieve this, we modify the MobX store to use the HTML MediaStream Recording API to record a user’s voice within two new methods in the MobX store.
//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}`)); };
};
At the click of the record icon from the chat component, the startAudioConversation
method in the MobX store above is invoked to set the method the observable isRecording
property is to true , for the chat component to provide visual feedback to show a recording is in progress.
Using the browser’s navigator interface, the Media Device object is accessed to request the user’s device microphone. After permission is granted to the getUserMedia
request, it resolves its promise with a MediaStream data which we further pass to the MediaRecorder constructor to create a recorder using the media tracks in the stream returned from the user’s device microphone. We then store the Media recorder instance in the store’s recorder
property as we will access it from another method later on.
Next, we call the start method on the recorder instance, and after the recording session is ended, the ondataavailable
function is fired with an event argument containing the recorded stream in a Blob which we store in the recordedBits
array property.
Logging out the data in the event argument passed into the fired ondataavailable
event, we can see the Blob and its properties in the browser console.
Now that we can start a MediaRecorder stream, we need to be able to stop the MediaRecorder stream when a user is done recording their voice input and send the generated audio file to the Express.js application.
The new method added to the store below stops the stream and makes a POST
request containing the recorded voice input.
//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 ( Zara {} {isRecording &&"is listening..."}
closeChatwindow()}/> )
} 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.
References
- Dialogflow Documentation
- Building A Conversational N.L.P Enabled Chatbot Using Google’s Dialogflow by Nwani Victory
- MobX
- https://web.postman.com
- Dialogflow API: Node.js Client
- Using the MediaStream Recording API