Mengintegrasikan Agen Dialogflow Ke Aplikasi React

Diterbitkan: 2022-03-10
Ringkasan cepat Dalam hal membangun asisten obrolan percakapan yang dapat digunakan di tingkat kecil atau perusahaan, Dialogflow kemungkinan besar akan menjadi salah satu opsi pertama yang akan muncul di daftar pencarian Anda — dan mengapa tidak? Ini menawarkan beberapa fitur seperti kemampuan untuk memproses input audio dan teks, memberikan respons dinamis menggunakan webhook khusus, terhubung ke jutaan perangkat yang mendukung Google dengan menggunakan asisten Google, dan banyak lagi. Namun selain konsolnya yang disediakan untuk merancang dan mengelola Agen, bagaimana kita bisa membuat asisten obrolan yang juga dapat digunakan dalam aplikasi web yang kita buat?

Dialogflow adalah platform yang menyederhanakan proses pembuatan dan perancangan asisten obrolan percakapan pemrosesan bahasa alami yang dapat memproses input suara atau teks saat digunakan baik dari konsol Dialogflow atau dari aplikasi web terintegrasi.

Meskipun Agen Dialogflow terintegrasi dijelaskan secara singkat dalam artikel ini, diharapkan Anda memiliki pemahaman tentang Node.js dan Dialogflow. Jika Anda baru pertama kali mempelajari Dialogflow, artikel ini memberikan penjelasan yang jelas tentang apa itu Dialogflow dan konsepnya.

Artikel ini adalah panduan tentang cara membangun agen Dialogflow dengan dukungan suara dan obrolan yang dapat diintegrasikan ke dalam aplikasi web dengan bantuan aplikasi back-end Express.js sebagai penghubung antara aplikasi Web React.js dan Agen pada Dialogflow itu sendiri. Pada akhir artikel, Anda seharusnya dapat menghubungkan agen Dialogflow Anda sendiri ke aplikasi web pilihan Anda.

Untuk membuat panduan ini mudah diikuti, Anda dapat melompat ke bagian mana pun dari tutorial yang paling Anda minati atau mengikutinya dalam urutan berikut saat muncul:

  • Menyiapkan Agen Dialogflow
  • Mengintegrasikan Agen Dialogflow
  • Menyiapkan Aplikasi Node Express
    • Mengautentikasi Dengan Dialogflow
    • Apa Itu Akun Layanan?
    • Menangani Input Suara
  • Mengintegrasikan Ke Dalam Aplikasi Web
    • Membuat Antarmuka Obrolan
    • Merekam Masukan Suara Pengguna
  • Kesimpulan
  • Referensi

1. Menyiapkan Agen Dialogflow

Seperti yang dijelaskan dalam artikel ini, asisten obrolan di Dialogflow disebut Agen dan terdiri dari komponen yang lebih kecil seperti maksud, pemenuhan, basis pengetahuan, dan banyak lagi. Dialogflow menyediakan konsol bagi pengguna untuk membuat, melatih, dan mendesain alur percakapan Agen. Dalam kasus penggunaan kami, kami akan memulihkan agen yang diekspor ke folder ZIP setelah dilatih, menggunakan fitur Ekspor dan Impor agen.

Sebelum kita melakukan impor, kita perlu membuat agen baru yang akan digabungkan dengan agen yang akan dipulihkan. Untuk membuat Agen baru dari konsol, diperlukan nama unik dan juga, sebuah proyek di Google Cloud untuk menautkan agen tersebut. Jika tidak ada proyek yang sudah ada di Google Cloud untuk ditautkan, proyek baru dapat dibuat di sini.

Agen sebelumnya telah dibuat dan dilatih untuk merekomendasikan produk anggur kepada pengguna berdasarkan anggaran mereka. Agen ini telah diekspor ke dalam ZIP; Anda dapat mengunduh folder di sini dan mengembalikannya ke agen kami yang baru dibuat dari tab Ekspor dan Impor yang ada di halaman Pengaturan agen.

Memulihkan agen yang sebelumnya diekspor dari folder ZIP
Memulihkan agen yang sebelumnya diekspor dari folder ZIP. (Pratinjau besar)

Agen impor sebelumnya telah dilatih untuk merekomendasikan produk anggur kepada pengguna berdasarkan anggaran pengguna untuk membeli sebotol anggur.

Melalui agen yang diimpor, kita akan melihatnya memiliki tiga maksud yang dibuat dari halaman maksud. Salah satunya adalah maksud mundur, digunakan saat Agen tidak mengenali input dari pengguna, yang lainnya adalah niat Selamat Datang yang digunakan saat percakapan dengan Agen dimulai, dan maksud terakhir digunakan untuk merekomendasikan anggur kepada pengguna berdasarkan parameter jumlah dalam kalimat. Yang menjadi perhatian kami adalah niat get-wine-recommendation

Intent ini memiliki konteks input tunggal dari wine-recommendation berasal dari intent Selamat Datang Default untuk menautkan percakapan ke intent ini.

“Konteks adalah sistem di dalam Agen yang digunakan untuk mengontrol aliran percakapan dari satu maksud ke maksud lainnya.”

Di bawah konteks adalah frase Pelatihan, yang merupakan kalimat yang digunakan untuk melatih agen tentang jenis pernyataan yang diharapkan dari pengguna. Melalui berbagai macam frasa pelatihan dalam suatu maksud, agen dapat mengenali kalimat pengguna dan maksud yang menjadi tujuan kalimat tersebut.

Frase pelatihan dalam maksud get-wine-recommendation agen kami (seperti yang ditunjukkan di bawah) menunjukkan pilihan anggur dan kategori harga:

Daftar frasa pelatihan yang tersedia dengan maksud get-wine-recommendation.
Halaman maksud mendapatkan-rekomendasi-anggur yang menampilkan frasa pelatihan yang tersedia. (Pratinjau besar)

Melihat gambar di atas, kita dapat melihat frasa pelatihan yang tersedia terdaftar, dan angka mata uang disorot dengan warna kuning untuk masing-masing frasa tersebut. Penyorotan ini dikenal sebagai anotasi pada Dialogflow dan secara otomatis dilakukan untuk mengekstrak tipe data yang dikenali yang dikenal sebagai entitas dari kalimat pengguna.

Setelah maksud ini dicocokkan dalam percakapan dengan agen, permintaan HTTP akan dibuat ke layanan eksternal untuk mendapatkan anggur yang direkomendasikan berdasarkan harga yang diekstraksi sebagai parameter dari kalimat pengguna, melalui penggunaan webhook yang diaktifkan yang ditemukan di dalam bagian Pemenuhan di bagian bawah halaman maksud ini.

Kami dapat menguji agen menggunakan emulator Dialogflow yang terletak di bagian kanan konsol Dialogflow. Untuk menguji, kami memulai percakapan dengan pesan " Hai " dan menindaklanjuti dengan jumlah anggur yang diinginkan. Webhook akan segera dipanggil dan respons kaya yang mirip dengan yang di bawah ini akan ditampilkan oleh agen.

Menguji webhook agen agen yang diimpor.
Menguji webhook pemenuhan agen yang diimpor menggunakan emulator Agen di konsol. (Pratinjau besar)

Dari gambar di atas kita dapat melihat URL webhook yang dihasilkan menggunakan Ngrok dan respons agen di sebelah kanan menunjukkan anggur dalam kisaran harga $20 yang diketik oleh pengguna.

Pada titik ini, agen Dialogflow telah sepenuhnya disiapkan. Sekarang kita dapat mulai mengintegrasikan agen ini ke dalam aplikasi web untuk memungkinkan pengguna lain mengakses dan berinteraksi dengan agen tanpa akses ke konsol Dialogflow kami.

Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

Mengintegrasikan Agen Dialogflow

Meskipun ada cara lain untuk terhubung ke Agen Dialogflow seperti membuat permintaan HTTP ke titik akhir REST, cara yang disarankan untuk terhubung ke Dialogflow adalah melalui penggunaan pustaka klien resminya yang tersedia dalam beberapa bahasa pemrograman. Untuk JavaScript, paket @google-cloud/dialogflow tersedia untuk penginstalan dari NPM.

Secara internal paket @google-cloud/dialogflow menggunakan gRPC untuk koneksi jaringannya dan ini membuat paket tidak didukung dalam lingkungan browser kecuali saat ditambal menggunakan webpack, cara yang disarankan untuk menggunakan paket ini adalah dari lingkungan Node. Kita dapat melakukan ini dengan menyiapkan aplikasi back-end Express.js untuk menggunakan paket ini kemudian menyajikan data ke aplikasi web melalui titik akhir API-nya dan inilah yang akan kita lakukan selanjutnya.

Menyiapkan Aplikasi Node Express

Untuk menyiapkan aplikasi ekspres, kami membuat direktori proyek baru kemudian mengambil dependensi yang diperlukan menggunakan yarn dari terminal baris perintah yang dibuka.

 # 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

Dengan dependensi yang diperlukan terinstal, kita dapat melanjutkan untuk menyiapkan server Express.js yang sangat ramping yang menangani koneksi pada port tertentu dengan dukungan CORS yang diaktifkan untuk aplikasi 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}`));

Saat dijalankan, kode dalam cuplikan di atas memulai server HTTP yang mendengarkan koneksi pada PORT Express.js yang ditentukan. Ini juga memiliki berbagi sumber daya Cross-Origin (CORS) diaktifkan pada semua permintaan menggunakan paket cors sebagai middleware Express. Untuk saat ini, server ini hanya mendengarkan koneksi, tidak dapat menanggapi permintaan karena tidak memiliki rute yang dibuat, jadi mari kita buat ini.

Kita sekarang perlu menambahkan dua rute baru: satu untuk mengirim data teks sementara yang lain untuk mengirim input suara yang direkam. Mereka berdua akan menerima permintaan POST dan mengirim data yang terdapat dalam badan permintaan ke agen Dialogflow nanti.

 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

Di atas kami membuat instance router terpisah untuk dua rute POST yang dibuat yang untuk saat ini, hanya merespons dengan 200 kode status dan respons dummy hardcoded. Setelah selesai mengautentikasi dengan Dialogflow, kita dapat kembali mengimplementasikan koneksi aktual ke Dialogflow dalam titik akhir ini.

Untuk langkah terakhir dalam pengaturan aplikasi backend kami, kami memasang instance router yang dibuat sebelumnya yang dibuat ke dalam aplikasi Express menggunakan app.use dan jalur dasar untuk rute tersebut.

 // 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}`));

Di atas, kami telah menambahkan jalur dasar ke dua rute dua kami dapat menguji salah satu dari mereka melalui permintaan POST menggunakan cURL dari baris perintah seperti yang dilakukan di bawah ini dengan badan permintaan kosong;

 curl -X https://localhost:5000/api/agent/text-response

Setelah berhasil menyelesaikan permintaan di atas, kita dapat mengharapkan untuk melihat respons yang berisi data objek yang dicetak ke konsol.

Sekarang tinggal membuat koneksi aktual dengan Dialogflow yang mencakup penanganan autentikasi, pengiriman, dan penerimaan data dari Agen di Dialogflow menggunakan paket @google-cloud/dialogflow.

Mengautentikasi Dengan Dialogflow

Setiap agen Dialogflow yang dibuat ditautkan ke proyek di Google Cloud. Untuk terhubung secara eksternal ke agen Dialogflow, kami mengautentikasi dengan proyek di Google cloud dan menggunakan Dialogflow sebagai salah satu sumber daya proyek. Dari enam cara yang tersedia untuk terhubung ke proyek di google-cloud, menggunakan opsi Akun layanan adalah yang paling nyaman saat menghubungkan ke layanan tertentu di google cloud melalui perpustakaan kliennya.

Catatan : Untuk aplikasi siap produksi, penggunaan kunci API berumur pendek direkomendasikan daripada kunci Akun Layanan untuk mengurangi risiko kunci akun layanan jatuh ke tangan yang salah.

Apa Itu Akun Layanan?

Akun layanan adalah jenis akun khusus di Google Cloud, dibuat untuk interaksi non-manusia, sebagian besar melalui API eksternal. Dalam aplikasi kami, akun layanan akan diakses melalui kunci yang dibuat oleh pustaka klien Dialogflow untuk mengautentikasi dengan Google Cloud.

Dokumentasi cloud tentang membuat dan mengelola akun layanan memberikan panduan yang sangat baik untuk membuat akun layanan. Saat membuat akun layanan, peran Admin API Dialogflow harus ditetapkan ke akun layanan yang dibuat seperti yang ditunjukkan pada langkah terakhir. Peran ini memberikan kontrol administratif akun layanan atas agen Dialogflow tertaut.

Untuk menggunakan akun layanan, kita perlu membuat Kunci Akun Layanan. Langkah-langkah berikut di bawah ini menguraikan cara membuatnya dalam format JSON:

  1. Klik pada Akun Layanan yang baru dibuat untuk menavigasi ke halaman Akun layanan.
  2. Gulir ke bagian Kunci dan klik tarik-turun Tambahkan Kunci dan klik opsi Buat kunci baru yang membuka modal.
  3. Pilih format file JSON dan klik tombol Buat di kanan bawah modal.

Catatan: Direkomendasikan untuk merahasiakan kunci akun layanan dan tidak memasukkannya ke sistem kontrol versi apa pun karena berisi data yang sangat sensitif tentang proyek di Google Cloud. Ini dapat dilakukan dengan menambahkan file ke file .gitignore .

Dengan akun layanan yang dibuat dan kunci akun layanan yang tersedia dalam direktori proyek kami, kami dapat menggunakan pustaka klien Dialogflow untuk mengirim dan menerima data dari agen 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;

Seluruh rute di atas mengirimkan data ke agen Dialogflow dan menerima respons melalui langkah-langkah berikut.

  • Pertama
    Ini mengautentikasi dengan cloud Google kemudian membuat sesi dengan Dialogflow menggunakan projectID dari proyek cloud Google yang ditautkan ke agen Dialogflow dan juga ID acak untuk mengidentifikasi sesi yang dibuat. Dalam aplikasi kami, kami membuat pengenal UUID pada setiap sesi yang dibuat menggunakan paket JavaScript UUID. Ini sangat berguna saat mencatat atau melacak semua percakapan yang ditangani oleh agen Dialogflow.
  • Kedua
    Kami membuat data objek permintaan mengikuti format yang ditentukan dalam dokumentasi Dialogflow. Objek permintaan ini berisi sesi yang dibuat dan data pesan yang didapat dari badan permintaan yang akan diteruskan ke agen Dialogflow.
  • Ketiga
    Menggunakan metode detectIntent dari sesi Dialogflow, kami mengirim objek permintaan secara asinkron dan menunggu respons Agen menggunakan ES6 async / menunggu sintaks dalam blok try-catch jika metode detectIntent mengembalikan pengecualian, kami dapat menangkap kesalahan dan mengembalikannya, bukan daripada merusak seluruh aplikasi. Contoh objek respons yang dikembalikan dari Agen disediakan dalam dokumentasi Dialogflow dan dapat diperiksa untuk mengetahui cara mengekstrak data dari objek.

Kita dapat menggunakan Postman untuk menguji koneksi Dialogflow yang diimplementasikan di atas dalam rute dialogflow-response . Postman adalah platform kolaborasi untuk pengembangan API dengan fitur untuk menguji API yang dibangun di tahap pengembangan atau produksi.

Catatan: Jika belum diinstal, aplikasi desktop Postman tidak diperlukan untuk menguji API. Mulai September 2020, klien web Postman beralih ke status Tersedia Secara Umum (GA) dan dapat digunakan langsung dari browser.

Menggunakan Klien Web Tukang Pos, kami dapat membuat ruang kerja baru atau menggunakan yang sudah ada untuk membuat permintaan POST ke titik akhir API kami di https://localhost:5000/api/agent/text-input dan menambahkan data dengan kunci message dan nilai " Hai " ke dalam parameter kueri.

Dengan mengklik tombol Kirim , permintaan POST akan dibuat ke server Express yang sedang berjalan — dengan respons yang mirip dengan yang ditunjukkan pada gambar di bawah ini:

Menguji titik akhir API input teks menggunakan Tukang Pos.
Menguji titik akhir API input teks menggunakan Tukang Pos. (Pratinjau besar)

Pada gambar di atas, kita dapat melihat data respon yang telah diedit dari agen Dialogflow melalui server Express. Data yang dikembalikan diformat sesuai dengan definisi respons sampel yang diberikan dalam dokumentasi Dialogflow Webhook.

Menangani Input Suara

Secara default, semua agen Dialogflow diaktifkan untuk memproses data teks dan audio dan juga mengembalikan respons dalam format teks atau audio. Namun, bekerja dengan data input atau output audio bisa sedikit lebih kompleks daripada data teks.

Untuk menangani dan memproses input suara, kita akan memulai implementasi untuk titik akhir /voice-input yang telah kita buat sebelumnya untuk menerima file audio dan mengirimkannya ke Dialogflow sebagai imbalan atas tanggapan dari Agen:

 // 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}`); } });

Pada ikhtisar tinggi, rute /voice-input di atas menerima input suara pengguna sebagai file yang berisi pesan yang sedang diucapkan ke asisten obrolan dan mengirimkannya ke agen Dialogflow. Untuk memahami proses ini dengan lebih baik, kita dapat memecahnya menjadi langkah-langkah kecil berikut:

  • Pertama, kami menambahkan dan menggunakan connect-busboy sebagai middleware Express untuk mengurai data formulir yang dikirim dalam permintaan dari aplikasi web. Setelah itu kami mengautentikasi dengan Dialogflow menggunakan Kunci Layanan dan membuat sesi, dengan cara yang sama seperti yang kami lakukan pada rute sebelumnya.
    Kemudian dengan menggunakan metode promisify dari modul util bawaan Node.js, kita mendapatkan dan menyimpan janji yang setara dengan metode pipa aliran untuk digunakan nanti untuk menyalurkan beberapa aliran dan juga melakukan pembersihan setelah aliran selesai.
  • Selanjutnya, Kami membuat objek permintaan yang berisi sesi otentikasi Dialogflow dan konfigurasi untuk file audio yang akan dikirim ke Dialogflow. Objek konfigurasi audio bersarang memungkinkan agen Dialogflow untuk melakukan konversi Ucapan-Ke-Teks pada file audio yang dikirim.
  • Selanjutnya, menggunakan sesi yang dibuat dan objek permintaan, kami mendeteksi maksud pengguna dari file audio menggunakan metode detectStreamingIntent yang membuka aliran data baru dari agen Dialogflow ke aplikasi backend. Data akan dikirim kembali dalam bit kecil melalui aliran ini dan menggunakan data " peristiwa " dari aliran yang dapat dibaca, kami menyimpan data dalam variabel streamData untuk digunakan nanti. Setelah aliran ditutup, acara " akhir " diaktifkan dan kami mengirim respons dari agen Dialogflow yang disimpan dalam variabel streamData ke Aplikasi Web.
  • Terakhir menggunakan acara aliran file dari connect-busboy, kami menerima aliran file audio yang dikirim di badan permintaan dan selanjutnya kami meneruskannya ke dalam janji yang setara dengan Pipeline yang kami buat sebelumnya. Fungsinya adalah untuk menyalurkan aliran file audio yang masuk dari permintaan ke aliran Dialogflow, kami menyalurkan aliran file audio ke aliran yang dibuka dengan metode detectStreamingIntent di atas.

Untuk menguji dan memastikan bahwa langkah-langkah di atas berfungsi seperti yang dijelaskan, kita dapat membuat permintaan pengujian yang berisi file audio di badan permintaan ke titik akhir /voice-input menggunakan Tukang Pos.

Menguji titik akhir API input suara menggunakan Tukang Pos.
Menguji titik akhir API input suara menggunakan tukang pos dengan file suara yang direkam. (Pratinjau besar)

Hasil Postman di atas menunjukkan respons yang didapat setelah membuat permintaan POST dengan bentuk-data pesan catatan suara yang mengatakan " Hai " termasuk dalam isi permintaan.

Pada titik ini, kami sekarang memiliki aplikasi Express.js fungsional yang mengirim dan menerima data dari Dialogflow, dua bagian dari artikel ini selesai. Sekarang tinggal mengintegrasikan Agen ini ke dalam aplikasi web dengan menggunakan API yang dibuat di sini dari aplikasi Reactjs.

Mengintegrasikan Ke Dalam Aplikasi Web

Untuk menggunakan REST API yang kami buat, kami akan memperluas aplikasi React.js yang sudah ada yang telah memiliki halaman beranda yang menampilkan daftar wine yang diambil dari API dan dukungan untuk dekorator menggunakan plugin dekorator proposal babel. Kami akan refactor sedikit dengan memperkenalkan Mobx untuk manajemen negara dan juga fitur baru untuk merekomendasikan wine dari komponen chat menggunakan titik akhir REST API yang ditambahkan dari aplikasi Express.js.

Untuk memulai, kami mulai mengelola status aplikasi menggunakan MobX saat kami membuat toko Mobx dengan beberapa nilai yang dapat diamati dan beberapa metode sebagai tindakan.

 // 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();

Di atas kami membuat toko untuk fitur komponen obrolan dalam aplikasi yang memiliki nilai berikut:

  • isChatWindowOpen
    Nilai yang disimpan di sini mengontrol visibilitas komponen obrolan tempat pesan Dialogflow ditampilkan.
  • isLoadingChatMessages
    Ini digunakan untuk menunjukkan indikator pemuatan saat permintaan untuk mengambil respons dari agen Dialogflow dibuat.
  • agentMessages
    Array ini menyimpan semua respons yang berasal dari permintaan yang dibuat untuk mendapatkan respons dari agen Dialogflow. Data dalam array kemudian ditampilkan dalam komponen.
  • handleConversation
    Metode ini didekorasi sebagai tindakan menambahkan data ke dalam larik agentMessages . Pertama, menambahkan pesan pengguna yang diteruskan sebagai argumen kemudian membuat permintaan menggunakan Axios ke aplikasi backend untuk mendapatkan respons dari Dialogflow. Setelah permintaan diselesaikan, ia menambahkan respons dari permintaan ke dalam array agentMessages .

Catatan: Dengan tidak adanya dukungan dekorator dalam Aplikasi, MobX menyediakan makeObservable yang dapat digunakan dalam konstruktor kelas toko target. Lihat contohnya di sini .

Dengan penyiapan toko, kita perlu membungkus seluruh pohon aplikasi dengan komponen tingkat tinggi Penyedia MobX mulai dari komponen root di file 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;

Di atas kami membungkus komponen Aplikasi root dengan Penyedia MobX dan kami meneruskan toko yang dibuat sebelumnya sebagai salah satu nilai Penyedia. Sekarang kita dapat melanjutkan untuk membaca dari toko di dalam komponen yang terhubung ke toko.

Membuat Antarmuka Obrolan

Untuk menampilkan pesan yang dikirim atau diterima dari permintaan API, kita memerlukan komponen baru dengan beberapa antarmuka obrolan yang menampilkan pesan yang terdaftar. Untuk melakukan ini, kami membuat komponen baru untuk menampilkan beberapa pesan yang dikodekan terlebih dahulu, kemudian kami menampilkan pesan dalam daftar yang diurutkan.

 // ./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

Komponen di atas memiliki markup HTML dasar yang diperlukan untuk aplikasi chat. Ini memiliki tajuk yang menunjukkan nama Agen dan ikon untuk menutup jendela obrolan, gelembung pesan yang berisi teks yang di-hardcode dalam tag daftar, dan terakhir bidang input yang memiliki pengendali acara onChange yang dilampirkan ke bidang input untuk menyimpan teks yang diketik ke status lokal komponen menggunakan useState React.

Pratinjau Komponen Obrolan dengan pesan berkode keras dari Agen obrolan
Pratinjau Komponen Obrolan dengan pesan berkode keras dari Agen obrolan. (Pratinjau besar)

Dari gambar di atas, komponen obrolan berfungsi sebagaimana mestinya, menunjukkan jendela obrolan bergaya yang memiliki satu pesan obrolan dan bidang input di bagian bawah. Namun kami ingin pesan yang ditampilkan sebagai respons aktual yang didapat dari permintaan API dan bukan teks yang di-hardcode.

Kami bergerak maju untuk memfaktorkan ulang komponen Obrolan, kali ini menghubungkan dan memanfaatkan nilai-nilai di toko MobX di dalam komponen.

 // ./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));

Dari bagian kode yang disorot di atas, kita dapat melihat bahwa seluruh komponen obrolan kini telah dimodifikasi untuk melakukan operasi baru berikut;

  • Ini memiliki akses ke nilai toko MobX setelah menyuntikkan nilai ApplicationStore . Komponen juga telah dijadikan sebagai pengamat nilai simpanan ini sehingga akan dirender ulang ketika salah satu nilai berubah.
  • Kami memulai percakapan dengan Agen segera setelah komponen obrolan dibuka dengan memanggil metode handleConversation dalam kait useEffect untuk membuat permintaan segera komponen dirender.
  • Kami sekarang menggunakan nilai isLoadingMessages di dalam header komponen Obrolan. Saat permintaan untuk mendapatkan respons dari Agen sedang dalam penerbangan, kami menyetel nilai isLoadingMessages ke true dan memperbarui header ke Zara sedang mengetik…
  • Array agentMessages di dalam toko diperbarui ke proxy oleh MobX setelah nilainya ditetapkan. Dari komponen ini, kami mengonversi proxy itu kembali ke array menggunakan utilitas toJS dari MobX dan menyimpan nilai dalam variabel di dalam komponen. Array itu selanjutnya diulang untuk mengisi gelembung obrolan dengan nilai-nilai dalam array menggunakan fungsi peta.

Sekarang menggunakan komponen chat kita dapat mengetikkan sebuah kalimat dan menunggu respon ditampilkan di agen.

Komponen obrolan menampilkan daftar data yang dikembalikan dari permintaan HTTP ke aplikasi ekspres.
Komponen obrolan menampilkan daftar data yang dikembalikan dari permintaan HTTP ke aplikasi ekspres. (Pratinjau besar)

Merekam Masukan Suara Pengguna

Secara default, semua agen Dialogflow dapat menerima input berbasis suara atau teks dalam bahasa tertentu dari pengguna. Namun, memerlukan beberapa penyesuaian dari aplikasi web untuk mendapatkan akses ke mikrofon pengguna dan merekam input suara.

Untuk mencapai ini, kami memodifikasi toko MobX untuk menggunakan API Perekaman MediaStream HTML untuk merekam suara pengguna dalam dua metode baru di toko 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}`)); }; };

Pada klik ikon rekam dari komponen obrolan, metode startAudioConversation di toko MobX di atas dipanggil untuk menyetel metode properti isRecording yang dapat diamati menjadi true , agar komponen obrolan memberikan umpan balik visual untuk menunjukkan rekaman sedang berlangsung.

Menggunakan antarmuka navigator browser, objek Perangkat Media diakses untuk meminta mikrofon perangkat pengguna. Setelah izin diberikan ke permintaan getUserMedia , permintaan tersebut menyelesaikan janjinya dengan data MediaStream yang selanjutnya kami berikan ke konstruktor MediaRecorder untuk membuat perekam menggunakan trek media dalam aliran yang dikembalikan dari mikrofon perangkat pengguna. Kami kemudian menyimpan contoh perekam Media di properti recorder toko karena kami akan mengaksesnya dari metode lain nanti.

Selanjutnya, kita memanggil metode start pada instance recorder, dan setelah sesi perekaman berakhir, fungsi ondataavailable diaktifkan dengan argumen event yang berisi aliran yang direkam dalam Blob yang kita simpan di properti array recordedBits .

Keluar dari data dalam argumen event yang diteruskan ke event ondataavailable yang diaktifkan, kita dapat melihat Blob dan propertinya di konsol browser.

Konsol Browser Devtools menampilkan Blob yang keluar yang dibuat oleh Perekam Media setelah perekaman berakhir. Tarik Kutipan
Konsol Browser Devtools menampilkan Blob keluar yang dibuat oleh Perekam Media setelah perekaman berakhir. (Pratinjau besar)

Sekarang kita dapat memulai aliran MediaRecorder, kita harus dapat menghentikan aliran MediaRecorder ketika pengguna selesai merekam input suara mereka dan mengirim file audio yang dihasilkan ke aplikasi Express.js.

Metode baru yang ditambahkan ke penyimpanan di bawah menghentikan aliran dan membuat permintaan POST yang berisi masukan suara yang direkam.

 //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!

Kesimpulan

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.

Referensi

  • 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