Front End

14 dez, 2018

Construção de um ChatBot com Angular 7 e DialogFlow – Parte 02

Publicidade

Nesta segunda e última parte da série, implementaremos em Angular 7, nosso chatbot.

Para começar, abra o terminal no diretório de sua preferência e crie um projeto. Para mais detalhes na geração do projeto, veja meu artigo.

No arquivo app.module.ts vamos importar dois módulos. O HttClientModule e o FormsModule.

boot.service.ts

// Outros imports omitidos
import { FormsModule }   from '@angular/forms';
import { HttpClientModule } from "@angular/common/http";

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

O próximo passo é a criação do serviço que acessa a API do DialogFlow.

No terminal de comando, informe:

> ng g service boot

Comentando o código do BootService:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class BootService {

  private baseURL: string = "https://api.dialogflow.com/v1/query?v=20150910";
  private token: string = 'SEU TOKEN'

  constructor(private http: HttpClient) { }

  public getResponse(query: string) {
    let data = {
      query: query,
      lang: 'en',
      sessionId: '12345'
    }

    return this.http
      .post(`${this.baseURL}`, data, { headers: this.getHeaders() })
  }

  public getHeaders() {
    let headers = new HttpHeaders();
    headers = headers.set('Authorization', `Bearer ${this.token}`);
    return headers;
  }
}

Vamos declarar duas propriedades para o endereço e o token (linhas 9 e 10). O token é obtido nas configurações do projeto, no console do DialogFlow.

Figura 2 — Propriedades do projeto no console do DialogFlow

O método getResponse será responsável pelas interações com o Agente. Realizamos um post da classe HttpClient, injetado no método construtor (linha 12).

Basicamente informamos o endereço, alguns parâmetros na query e o token de autorização no cabeçalho (linha 22), passado no método getHeaders (linha 26).

Finalizado o serviço, a próxima etapa é a codificação do script.

No arquivo app.component.ts, definimos uma interface para mapear os campos do chat. A interface Message possui dois atributos string e um campo Date(linhas 5 a 9). Isso nos ajudará a formatar os campos com base nas mensagens do bot ou do usuário.

import { element } from 'protractor';
import { BootService } from './boot.service';
import { Component, ViewChild, ElementRef } from '@angular/core';

export interface Message {
  remetente?: string;
  mensagem: string;
  data?: Date;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  @ViewChild('scrollMe') private myScrollContainer: ElementRef;

  msg: string;
  resultados: Message[]

  constructor(private chatBoot: BootService) {
    this.initBoot()
  }

  initBoot() {
    this.resultados = []
    this.chatBoot.getResponse('oi')
      .subscribe((lista: any) => {
        lista.result.fulfillment.messages.forEach((element) => {
          this.resultados.push({ remetente: 'boot', mensagem: element.speech, data: lista.timestamp })
        });
      })
  }

  sendMessage() {
    this.resultados.push({ remetente: 'eu', mensagem: this.msg, data: new Date() })
    this.chatBoot.getResponse(this.removerAcentos(this.msg))
      .subscribe((lista: any) => {
        lista.result.fulfillment.messages.forEach((element) => {
          this.resultados.push({ remetente: 'boot', mensagem: element.speech, data: lista.timestamp })
        });
      })

    this.msg = '';
  }
  
  ngAfterViewChecked() {
    this.scrollToBottom();
  }
  
  scrollToBottom(): void {
    try {
      this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight;
    } catch (err) { }
  }

  private removerAcentos(s) {
    return s.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
  }

}

Na linha 18 vamos controlar o elemento DOM para acionar o scroll de uma div, conforme a inclusão de mais mensagens.

A linha 20 é o input do usuário e a linha 21 um array de Message de respostas do boot.

No método initBoot(), iniciamos a primeira chamada ao Agente, passando um ‘oi’ (linha 29). Esse comportamento foi tratado na primeira parte do artigo.

O objeto JSON retornado possui várias propriedades. Daí o fato de no momento do subscribe, acessarmos especificamente a propriedade lista.result.fulfillment.messages, adicionando ao array de messages e identificando o remetente como boot (linha 32).

O método sendMessage adiciona ao array, mas desta vez definindo o remetente como usuário (‘eu’) utilizando uma função acessória para remover acentos (linha 39).

O restante da implementação é semelhante ao método anterior, populando o array com as respostas do boot (linha 42).

Para acionar o scroll, verificamos toda vez que a página sofre atualizações no método ngAfterViewChecked (linha 48).

Vamos finalizar o projeto. No template app.component.html, limpe o código padrão que foi gerado na criação e deixe-o assim:

<div class="container">

  <div class="row align-items-center">
    <div class="mx-auto">
      <h3><i class="fas fa-robot"></i>ChatBoot com DialogFLow </h3>
    </div>
  </div>

  <div class="row align-items-center justify-content-center">
    <div class="col-lg-8">
      <ul #scrollMe class="messages">
        <div *ngFor="let msg of resultados ">
          <li class="message appeared" [ngClass]="{'left':msg.remetente === 'boot',
                        'right':msg.remetente === 'eu'     }">
            <div class="avatar"></div>
            <div class="text_wrapper">
              <div class="text">
                <p class="time"> ({{msg.data | date:'HH:mm:ss'}})</p>
                {{msg.mensagem}}
              </div>
            </div>
          </li>
        </div>
      </ul>
    </div>
  </div>

  <div class="row align-items-center justify-content-center">
    <div class="col-sm-6">
      <input type="text" class="form-control " (keyup.enter)="sendMessage()" [(ngModel)]="msg" />
    </div>
    <div class="col-sm-2">
      <button class="btn btn-success sendbtn" (click)="sendMessage()"><i class="far fa-share-square"></i> Enviar</button>
    </div>
  </div>

Na linha 11 declaramos a variável para manipular o scroll #scrollMe. Na linha 12 utilizamos a diretiva *ngFor para iterarmos sobre o array das respostas do boot.

Note que utilizamos o ngClass (linha 13) para decidir qual estilo será aplicado na formatação da lista de mensagens, criando o efeito de chats.

Todo o código do projeto está no GIT.

Abraços!

Referências